diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..ec1a47c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,73 @@
+name: 'Bug Report'
+description: 'Report a bug to help us improve Commander'
+labels:
+ - 'kind/bug'
+ - 'status/need-triage'
+body:
+ - type: 'markdown'
+ attributes:
+ value: |-
+ > [!IMPORTANT]
+ > Thanks for taking the time to fill out this bug report!
+ >
+ > Please search **[existing issues](https://github.com/autohandai/commander/issues)** to see if an issue already exists for the bug you encountered.
+
+ - type: 'textarea'
+ id: 'problem'
+ attributes:
+ label: 'What happened?'
+ description: 'A clear and concise description of what the bug is.'
+ validations:
+ required: true
+
+ - type: 'textarea'
+ id: 'expected'
+ attributes:
+ label: 'What did you expect to happen?'
+ validations:
+ required: true
+
+ - type: 'textarea'
+ id: 'steps'
+ attributes:
+ label: 'Steps to reproduce'
+ description: 'Provide detailed steps to reproduce the issue.'
+ placeholder: |-
+ 1. Go to '...'
+ 2. Click on '...'
+ 3. Run command '...'
+ 4. See error
+ validations:
+ required: true
+
+ - type: 'textarea'
+ id: 'info'
+ attributes:
+ label: 'System information'
+ description: 'Please provide your system information. Include OS version, Commander version, and any relevant environment details.'
+ value: |-
+
+
+ ```
+ OS: [e.g., macOS 14.5, Windows 11, Ubuntu 22.04]
+ Commander Version: [e.g., 1.0.0]
+ Node Version: [if applicable]
+ Bun Version: [if applicable]
+ ```
+
+
+ validations:
+ required: true
+
+ - type: 'textarea'
+ id: 'logs'
+ attributes:
+ label: 'Error logs'
+ description: 'If applicable, paste any error messages or logs here.'
+ render: 'shell'
+
+ - type: 'textarea'
+ id: 'additional-context'
+ attributes:
+ label: 'Anything else we need to know?'
+ description: 'Add any other context about the problem here, such as screenshots or configuration files.'
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..60289ad
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,41 @@
+name: 'Feature Request'
+description: 'Suggest an idea for Commander'
+labels:
+ - 'kind/enhancement'
+ - 'status/need-triage'
+body:
+ - type: 'markdown'
+ attributes:
+ value: |-
+ > [!IMPORTANT]
+ > Thanks for taking the time to suggest an enhancement!
+ >
+ > Please search **[existing issues](https://github.com/autohandai/commander/issues)** to see if a similar feature has already been requested.
+
+ - type: 'textarea'
+ id: 'feature'
+ attributes:
+ label: 'What would you like to be added?'
+ description: 'A clear and concise description of the enhancement.'
+ validations:
+ required: true
+
+ - type: 'textarea'
+ id: 'rationale'
+ attributes:
+ label: 'Why is this needed?'
+ description: 'A clear and concise description of why this enhancement is needed and what problem it solves.'
+ validations:
+ required: true
+
+ - type: 'textarea'
+ id: 'alternatives'
+ attributes:
+ label: 'Alternative solutions'
+ description: 'Have you considered any alternative solutions or workarounds?'
+
+ - type: 'textarea'
+ id: 'additional-context'
+ attributes:
+ label: 'Additional context'
+ description: 'Add any other context, screenshots, or examples about the feature request here.'
diff --git a/.github/workflows/tauri-ci.yml b/.github/workflows/tauri-ci.yml
index 6b48d20..5ec5175 100644
--- a/.github/workflows/tauri-ci.yml
+++ b/.github/workflows/tauri-ci.yml
@@ -3,8 +3,8 @@ name: CI
on:
push:
branches: [ main ]
- tags: [ 'v*.*.*' ]
pull_request:
+ branches: [ main ]
jobs:
test:
@@ -40,12 +40,32 @@ jobs:
strategy:
fail-fast: false
matrix:
- os: [ macos-13, macos-14, windows-latest ]
+ os: [ ubuntu-latest, macos-13, macos-14 ]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
+ - name: Determine build metadata
+ id: metadata
+ shell: bash
+ run: |
+ if [ "${{ github.event_name }}" = "pull_request" ]; then
+ BUILD_ID="pr${{ github.event.number }}-${{ github.run_attempt }}"
+ else
+ BUILD_ID="${{ github.run_number }}"
+ fi
+ echo "COMMANDER_BUILD_ID=$BUILD_ID" >> "$GITHUB_ENV"
+ echo "build_id=$BUILD_ID" >> "$GITHUB_OUTPUT"
+
+ - name: Inject build metadata version
+ id: set_version
+ shell: bash
+ run: |
+ BUILD_VERSION=$(node scripts/ci-set-version.mjs)
+ echo "BUILD_VERSION=$BUILD_VERSION" >> "$GITHUB_ENV"
+ echo "build_version=$BUILD_VERSION" >> "$GITHUB_OUTPUT"
+
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
@@ -54,6 +74,13 @@ jobs:
with:
bun-version: latest
+ - name: Install Linux build dependencies
+ if: startsWith(matrix.os, 'ubuntu')
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y build-essential curl wget file libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev patchelf pkg-config
+ sudo apt-get install -y libwebkit2gtk-4.1-dev || sudo apt-get install -y libwebkit2gtk-4.0-dev
+
- name: Cache cargo registry
uses: actions/cache@v4
with:
@@ -74,14 +101,56 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
- name: tauri-bundles-${{ runner.os }}-${{ runner.arch }}
+ name: commander-${{ steps.set_version.outputs.build_version }}-${{ matrix.os }}-${{ runner.arch }}
path: src-tauri/target/release/bundle/**
- # Optional: signing & notarization (provide secrets to enable)
- # env:
- # TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
- # TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
- # APPLE_ID: ${{ secrets.APPLE_ID }}
- # APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
- # APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
+ release:
+ name: Release artifacts
+ needs: build
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Compute release version
+ id: release_version
+ run: |
+ BASE_VERSION=$(jq -r '.version' src-tauri/tauri.conf.json)
+ BASE_VERSION=${BASE_VERSION%%+*}
+ RELEASE_VERSION="$BASE_VERSION+build.${{ github.run_number }}"
+ echo "RELEASE_VERSION=$RELEASE_VERSION" >> "$GITHUB_ENV"
+ echo "release_version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"
+
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: dist
+
+ - name: Collect release assets
+ run: node scripts/collect-release-assets.mjs dist release
+
+ - name: Generate checksums
+ shell: bash
+ run: |
+ cd release
+ shopt -s nullglob
+ files=(*)
+ if [ ${#files[@]} -gt 0 ]; then
+ sha256sum "${files[@]}" > SHA256SUMS.txt
+ fi
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: v${{ steps.release_version.outputs.release_version }}
+ name: Commander ${{ steps.release_version.outputs.release_version }}
+ draft: false
+ prerelease: false
+ files: |
+ release/*
+ body: |
+ ## Commander ${{ steps.release_version.outputs.release_version }}
+ - Automated build for macOS (Intel & Apple Silicon) and Linux.
+ - SHA256 checksums included in `SHA256SUMS.txt`.
diff --git a/GEMINI.md b/GEMINI.md
deleted file mode 100644
index a6dc150..0000000
--- a/GEMINI.md
+++ /dev/null
@@ -1,245 +0,0 @@
-# Gemini Development Standards
-
-## Core Architecture Requirements
-
-This Commander application uses **modular architecture** with **comprehensive TDD**. As Gemini, you must follow these standards for all development work.
-
-## Project Structure - MANDATORY
-
-```
-src-tauri/src/
-├── models/ # Data structures and types
-├── services/ # Business logic layer
-├── commands/ # Tauri command handlers (planned)
-├── tests/ # Comprehensive test coverage
-├── lib.rs # Minimal main entry point
-└── error.rs # Centralized error handling (planned)
-```
-
-## Test-Driven Development Protocol
-
-### BEFORE any code implementation:
-
-1. **Write Tests First**
- - Create comprehensive test coverage
- - Test success scenarios
- - Test error conditions
- - Test edge cases
- - Follow existing patterns in `tests/`
-
-2. **Verify Tests Fail**
- ```bash
- cargo test # New tests should fail initially
- ```
-
-3. **Implement Feature**
- - Write minimal code to pass tests
- - Follow modular architecture principles
- - Use proper Rust error handling
-
-4. **Verify All Tests Pass**
- ```bash
- cargo test # ALL tests must pass (currently 12+)
- ```
-
-## Current Test Coverage
-
-The project maintains **12 comprehensive tests** that cover:
-- Git repository validation
-- Project creation workflows
-- File system operations
-- Command integrations
-- Error handling scenarios
-
-**CRITICAL:** All existing tests MUST continue to pass. Breaking tests is not acceptable.
-
-## Layer Responsibilities
-
-### Models Layer (`models/`)
-- Data structures and types only
-- Serde serialization/deserialization
-- No business logic
-- Clear, well-documented structs
-
-### Services Layer (`services/`)
-- All business logic implementation
-- Reusable functionality
-- Proper error handling
-- Testable functions
-- Single responsibility principle
-
-### Commands Layer (`commands/` - planned)
-- Thin Tauri command handlers
-- Delegate to services layer
-- Input validation only
-- No business logic
-
-### Tests Layer (`tests/`)
-- Comprehensive test coverage
-- Helper functions for testing
-- Mock external dependencies
-- Integration and unit tests
-
-## Implementation Standards for Gemini
-
-### New Feature Implementation:
-
-1. **Analyze Requirements**
- - Determine which layer(s) are affected
- - Identify existing patterns to follow
- - Plan test coverage strategy
-
-2. **Write Comprehensive Tests**
- ```rust
- // tests/services/new_feature_test.rs
- #[tokio::test]
- async fn test_new_feature_success() {
- // Arrange
- let input = create_test_input();
-
- // Act
- let result = new_feature_service::process(input).await;
-
- // Assert
- assert!(result.is_ok());
- assert_eq!(result.unwrap().expected_field, "expected_value");
- }
-
- #[tokio::test]
- async fn test_new_feature_handles_error() {
- let result = new_feature_service::process(invalid_input()).await;
- assert!(result.is_err());
- }
- ```
-
-3. **Implement in Appropriate Layer**
- ```rust
- // services/new_feature_service.rs
- use crate::models::*;
-
- pub async fn process(input: InputType) -> Result {
- // Business logic implementation
- // Proper error handling
- // Return Result type
- }
- ```
-
-4. **Verify Integration**
- - Run full test suite: `cargo test`
- - Check compilation: `cargo check`
- - Test application: `bun tauri dev`
-
-### Bug Fix Process:
-
-1. **Create Failing Test** that reproduces the bug
-2. **Identify Root Cause** in appropriate layer
-3. **Fix the Issue** following architectural patterns
-4. **Verify Fix** with test passing and no regressions
-
-## Code Quality Requirements
-
-- **Error Handling:** Use `Result` consistently
-- **Documentation:** Document public functions clearly
-- **Separation:** No business logic in command handlers
-- **Testing:** Test both happy path and error scenarios
-- **Modularity:** Keep services focused on single responsibility
-
-## Compilation and Testing Requirements
-
-Before any commit or submission:
-
-```bash
-# 1. Code must compile without errors
-cargo check
-
-# 2. All tests must pass
-cargo test
-
-# 3. Application must run successfully
-bun tauri dev
-```
-
-## Gemini-Specific Best Practices
-
-### When Working with AI/LLM Integration:
-- Create comprehensive tests for AI service interactions
-- Mock external API calls in tests
-- Handle API failures gracefully
-- Store API configurations in models layer
-- Implement AI logic in services layer
-
-### When Adding New Models:
-- Follow existing model patterns
-- Include proper serde attributes
-- Add to appropriate model module
-- Update mod.rs exports
-- Write tests for serialization/deserialization
-
-### When Extending Services:
-- Keep services focused on single domain
-- Use dependency injection patterns
-- Make functions testable
-- Handle errors consistently
-- Document complex business logic
-
-## Critical Rules for Gemini
-
-### ❌ NEVER:
-- Skip writing tests for new functionality
-- Break existing test coverage
-- Put business logic in lib.rs or command handlers
-- Ignore compilation errors or warnings
-- Change architecture without discussion
-
-### ✅ ALWAYS:
-- Follow TDD workflow religiously
-- Keep layers properly separated
-- Write comprehensive error handling
-- Document public API functions
-- Verify all tests pass before submitting
-
-## Success Validation
-
-Every change you make must pass ALL checks:
-
-1. ✅ **Tests Written:** New functionality has comprehensive tests
-2. ✅ **Tests Passing:** All tests (12+) pass successfully
-3. ✅ **Compilation:** Code compiles without errors
-4. ✅ **Architecture:** Follows modular structure
-5. ✅ **Functionality:** Application runs correctly
-
-## Example: Adding LLM Provider Support
-
-```rust
-// 1. Add test
-#[tokio::test]
-async fn test_add_custom_llm_provider() {
- let provider = create_test_provider();
- let result = llm_service::add_provider(provider).await;
- assert!(result.is_ok());
-}
-
-// 2. Add to model
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct CustomProvider {
- pub name: String,
- pub endpoint: String,
- pub api_key: Option,
-}
-
-// 3. Implement in service
-pub async fn add_provider(provider: CustomProvider) -> Result<(), String> {
- // Business logic for adding provider
- // Validation, storage, etc.
-}
-
-// 4. Add command (when commands/ exists)
-#[tauri::command]
-async fn add_llm_provider(provider: CustomProvider) -> Result<(), String> {
- llm_service::add_provider(provider).await
-}
-```
-
----
-
-**Remember: Quality and reliability come from disciplined adherence to TDD and modular architecture. Every line of code you write should make the system more maintainable and testable.**
\ No newline at end of file
diff --git a/NEW_FEATURES_LOG.md b/NEW_FEATURES_LOG.md
deleted file mode 100644
index 731adcf..0000000
--- a/NEW_FEATURES_LOG.md
+++ /dev/null
@@ -1,44 +0,0 @@
-Account management
-
-Now let's make the integration of auth the user in the app, atm I have only a pseudo integration of the left [app-sidebar.tsx](src/components/app-sidebar.tsx) but what i want is:
-
-
-- When the app is loaded, check if the user has a valid license
-- Checks if he is logged in on his account at Commander
-- Logout of the account
-- Integrated db with supabase for validation
-
-
-Initialization
-
-- checks if there are any previous configurations in ~/.commander/settings.json and load in the app
-- check which cli agents the user has on the account and installed in their systems
-- check if none of the supported one are installed they can see and install themselves in a intuitive ui
-
-
-
-
-Claude CLI modifications
-
-
-Claude supports native integrations for ui rendering the messages
-
-DONE
-/claude commands the way we're handling in [cli_commands.rs](src-tauri/src/commands/cli_commands.rs) is not helping us to create nice UI for the messages we have in the chat history, for claude, they support a bunch of options as param you can add to the cli, such as claude -p " WHAT THE USER TYPES" --output-format stream-json --verbose
-
-This allows us to focus on what we want to build in the message itself, let's refactor the integration with /claude first as it's the default ai cli agent to parse the response and we print like I'm showing you in the ui image I'm sharing, for each message, the stream json allows us to in real time parse what the output is coming from the cli, and the [AgentResponse.tsx](src/components/chat/AgentResponse.tsx) should be able to handle for any cli agent not just for claude.
-
-Apply the TDD process, build a plan and let's work out how to make this work.
-
-
-Workspace
-
-now let's focus on the functionality of Enable workspace, The same we have here is not the same we have in [GitSettings.tsx](src/components/settings/GitSettings.tsx) ,remember workspace is our concept for Commander that uses git worktree, so, by default is enabled and users can see that in the GitSettings, we don't need to show this big button there, When I make enable and disable, in the GitSettings, means that whenever a user types a message and send it, you will verify if the user is working on a workspace directory under .commander/ sub folder in the repo if doesn't have one, you will ask the user to to create one, by asking how do I name it your workspace, then you create it, we have this feature implemented in the [CodeView.tsx](src/components/CodeView.tsx) but I want to make sure pops up a dialog asking the user the name of the feature they want to use, automatically you will compact the first 4 words of the user message to autoname the git worktree, using the appropriate name space like word-typed-by-user and automatically focus the cursor on the field, and the user will press create, automatically you will send the message to the cli, and in the [ChatInterface.tsx](src/components/ChatInterface.tsx) you will add two controls, one to the user nvigate to which git worktree (workspace ) they want and if they wish create a new workspace.
-
-Keep in mind of the following:
-
-- When a worktree is already in place you won't ask for another worktree, you will keep working directly and showing the user in the contorls you will add to chatInteface which workspace(git worktree) they're currently working. And a new button create workspace which will create a new git worktree based on what the user will type, since they're initiating from the button, means they don't have any messages like the other use case, in this case, you will give a few random names taken from deserts across the world, you can make a little random dict from 50 probable, max 3 words though.
-- When the user is working on any cli commands ai agents, they will be working on that directory workspace(git worktree created)
-- Use TDD
-- Follow the architecture pattern strictly used by Tauri V2, best practices by Rust super star developers,
-- Don't duplicate any code, keep it DRY
\ No newline at end of file
diff --git a/README.md b/README.md
index a628d59..b4d7680 100644
--- a/README.md
+++ b/README.md
@@ -1,99 +1,90 @@
-# Autohand.ai Commander
-
-Native desktop application built with Tauri v2, React, and TypeScript. Commander streamlines developer workflows with project automation, Git operations, and AI‑assisted tooling — packaged as a secure, lightweight desktop app.
-
-This repo follows strict modular architecture and test‑driven development. All contributions must preserve existing tests and structure.
-
-## Features
-
-- Native Tauri v2 backend with focused services
-- Modern React UI with Vite and Tailwind
-- Git and filesystem operations (service‑layer only)
-- Clear error modeling and messages
-- Cross‑platform builds (Apple Silicon, Intel macOS, Windows)
-
-## Architecture
-
-Backend (Rust) strictly follows a modular layout:
-
+# Commander
+
+Commander is a native Tauri v2 desktop app that orchestrates multiple CLI coding agents against any Git project. It keeps every workflow local, manages agent workspaces with Git worktrees, and wraps advanced Git + filesystem automation inside a React/Vite interface.
+
+## What Works Today
+- Multi-agent chat surface for Claude Code CLI, OpenAI Codex CLI, Gemini CLI, and a local test harness – each with live streaming, plan mode, and parallel session tracking.
+- Workspace-aware execution that spins up Git worktrees under `.commander/` so every agent operates in an isolated branch without touching your main tree.
+- Project lifecycle management: clone repos with progress streaming, validate Git remotes, open existing repositories, and persist a capped MRU list with branch/status metadata.
+- Deep Git tooling: commit DAG visualization, diff viewer, branch/worktree selectors, and Git config/alias inspection exposed through the Tauri commands layer.
+- Persistent chat history, provider settings, execution modes, and prompts stored locally via `tauri-plugin-store`, so every project resumes exactly where you left off.
+- Settings modal backed by tests for loading/saving provider configuration, global system prompt management, agent enablement, and automatic CLI detection feedback.
+- Shadcn/ui-based desktop interface with file mentions, autocomplete for slash (`/`) and at (`@`) commands, rotating prompts, and a session control bar for replaying or clearing runs.
+
+## Requirements
+- macOS (Apple Silicon or Intel) or Windows 11 with Git installed and on the `PATH`.
+- Rust stable toolchain + Cargo.
+- Bun (preferred) or Node.js 18+.
+- CLI agents you want to use:
+ - [Claude Code CLI](https://www.npmjs.com/package/@anthropic-ai/claude-code)
+ - [OpenAI Codex CLI](https://github.com/openai/codex)
+ - [Google Gemini CLI](https://ai.google.dev/gemini-api/docs/gemini-cli)
+ - (Optional) GitHub CLI for repo metadata in the sidebar.
+
+## Quick Start
+1. Install dependencies: `bun install`
+2. Launch the desktop app: `bun tauri dev`
+3. Point Commander at an existing Git repository or use the clone flow from the project launcher.
+4. Start a chat with `/claude`, `/codex`, or `/gemini` – Commander will stream output, create worktrees when needed, and persist the conversation per project.
+
+### Agent Setup Cheatsheet
+```bash
+# Claude Code CLI
+npm install -g @anthropic-ai/claude-code
+claude # run once and authenticate with /login inside the shell
+
+# OpenAI Codex CLI
+npm install -g @openai/codex
+codex # follow the interactive auth flow
+
+# Gemini CLI
+npm install -g @google/gemini-cli@latest
+# or follow the official docs for your platform
+```
+Commander detects installed CLIs on launch and surfaces their status in **Settings → LLM Providers**. Disabled agents can be toggled per-session; missing installs display remediation tips.
+
+## Workspaces & Git Automation
+- When workspace mode is enabled, Commander creates worktrees under `/.commander/` and routes all CLI commands there.
+- Workspaces can be selected or created from the chat header; automatic naming uses the first words of your prompt, while manual creation offers curated name suggestions.
+- Git validation, branch detection, and status summaries are handled inside `src-tauri/src/services/git_service.rs` and exposed through thin Tauri commands.
+- The History view renders a commit graph (lane assignment + edge connections), lets you diff commits or compare a workspace against main, and refreshes branches/worktrees on demand.
+
+## Settings, Prompts, and Persistence
+- Provider settings (API keys, models, flags) are merged from system defaults, user config, and per-project overrides using the `agent_cli_settings_service`.
+- Global system prompt lives in the General tab; agent-specific prompts are removed for simplicity and backwards compatibility is handled with serde defaults.
+- Execution modes (`chat`, `collab`, `full`) and safety toggles persist via `execution_mode_service`, ensuring consistent behaviour between app restarts.
+- Chat transcripts are stored per project so you can close Commander and resume conversations later without losing streaming context.
+
+## Testing & Quality Gates
+Commander follows strict TDD.
+- Backend tests: `cd src-tauri && cargo test` (12+ tests covering services, commands, and error handling must stay green).
+- Frontend tests: `bun run test`
+- Combined helper: `./run_tests.sh`
+Before submitting changes, also run `cargo check` and, when relevant, `bun tauri build` to ensure the desktop bundle compiles.
+
+## What's Next
+- Harden multi-agent orchestration and connect additional CLI agents.
+- Export commands and chat history to a local proxy server for auditing.
+- Refine the diff view with richer context, comparisons, and navigation.
+- Handshake local model routing so on-device models run locally before falling back to remote agents.
+
+## Architecture Overview
```
src-tauri/src/
├── models/ # Data structures only
-├── services/ # Business logic only
-├── commands/ # Tauri command handlers (thin)
-├── tests/ # Comprehensive tests (MANDATORY)
-├── lib.rs # Minimal entry point
-└── error.rs # Error types
-```
-
-Rules:
-- No business logic in `commands/` or `lib.rs`
-- Add new logic in `services/` with corresponding `models/`
-- All changes must be covered by tests in `tests/`
-
-## Prerequisites
-
-- Rust (stable) and Cargo
-- Bun (recommended) or Node.js
-- macOS or Windows build tools (Xcode for macOS; Visual Studio Build Tools on Windows runners are preinstalled)
-
-## Local Development
-
-- Install dependencies: `bun install`
-- Start in dev mode: `bun tauri dev`
-
-The Tauri config runs the frontend with Vite and bundles assets from `dist/`:
-
-- `src-tauri/tauri.conf.json` → `build.beforeDevCommand` = `bun run dev`
-- `src-tauri/tauri.conf.json` → `build.beforeBuildCommand` = `bun run build`
-
-## Tests (TDD required)
-
-We practice strict TDD:
-
-1) Write failing tests (success + failure + edge cases) in `src-tauri/src/tests/`
-2) Implement minimal code in `services/` and `models/`
-3) Keep command handlers thin and delegating to services
-4) Verify all tests pass before submitting
-
-Run tests:
-
-```
-cd src-tauri
-cargo test
+├── services/ # Business logic (Git, agents, workspaces, settings, prompts)
+├── commands/ # Thin Tauri handlers delegating to services
+├── tests/ # Integration + service tests (TDD-required)
+├── lib.rs # Entry point wiring commands/plugins
+└── error.rs # Shared error types
```
-## Build
-
-Local release build:
-
-```
-# From repo root
-bun run tauri build
-```
-
-Artifacts are emitted under `src-tauri/target/release/bundle/`.
-
-## CI (GitHub Actions)
-
-This repository includes a cross‑platform workflow that:
-
-- Runs Rust tests on Linux
-- Builds notarization‑ready artifacts for:
- - Apple Silicon (`macos-14`)
- - Intel macOS (`macos-13`)
- - Windows (`windows-latest`)
-
-Unsigned artifacts are uploaded for each platform. You can add signing credentials later via repository secrets (see comments in workflow file).
+The React front end (see `src/`) is organized by feature domains (chat, settings, history, ui primitives). State is handled with hooks (`useChatExecution`, `useAgentEnablement`, `useChatPersistence`, etc.) so business rules stay testable at the service layer.
## Contributing
+- Follow the architecture rules in `AGENTS.md` and the Commander TDD checklist.
+- Add failing tests first, keep command handlers thin, and never regress the 12 baseline tests.
+- Use `user_prompts/` to document product context or feature briefs before shipping significant changes.
-Please read `CODE_OF_CONDUCT.md` before contributing. PRs must:
-
-- Preserve existing tests (no regressions)
-- Include tests for any change
-- Follow the architecture pattern
-
-## License
-
-This project is open source. If a `LICENSE` file is not yet present, contributions are accepted under the project’s intended license to be clarified during the initial public release.
+## Privacy & Data Handling
+All automation happens locally. Commander never uploads your code or chat history; only the CLI agents you enable communicate with their respective providers, following their opt-in policies.
diff --git a/cli/commander b/cli/commander
new file mode 100755
index 0000000..bee0abc
--- /dev/null
+++ b/cli/commander
@@ -0,0 +1,85 @@
+#!/bin/bash
+
+# Commander CLI - Opens Commander app with git project detection
+# Usage: commander [path]
+# commander . - Opens current directory (if it's a git repo)
+# commander path - Opens specified path (if it's a git repo)
+
+set -e
+
+# Default to current directory if no argument provided
+TARGET_PATH="${1:-.}"
+
+# Get absolute path
+if [[ "$TARGET_PATH" == "." ]]; then
+ TARGET_PATH="$(pwd)"
+elif [[ ! "$TARGET_PATH" =~ ^/ ]]; then
+ # Relative path - make it absolute
+ TARGET_PATH="$(pwd)/$TARGET_PATH"
+fi
+
+# Check if target path exists
+if [[ ! -d "$TARGET_PATH" ]]; then
+ echo "Error: Directory '$TARGET_PATH' does not exist"
+ exit 1
+fi
+
+# Function to find Commander app
+find_commander_app() {
+ # Check common locations for Commander app
+ local app_paths=(
+ "/Applications/commander.app"
+ "/Applications/Commander.app"
+ "$HOME/Applications/commander.app"
+ "$HOME/Applications/Commander.app"
+ )
+
+ for app_path in "${app_paths[@]}"; do
+ if [[ -d "$app_path" ]]; then
+ echo "$app_path"
+ return 0
+ fi
+ done
+
+ echo ""
+ return 1
+}
+
+# Find Commander app
+COMMANDER_APP=$(find_commander_app)
+
+if [[ -z "$COMMANDER_APP" ]]; then
+ echo "Error: Commander app not found in Applications folder"
+ echo "Please ensure Commander is installed via 'brew install --cask commander'"
+ exit 1
+fi
+
+# Check if Commander is already running
+COMMANDER_PID=$(pgrep -f "commander.app" || true)
+
+if [[ -n "$COMMANDER_PID" ]]; then
+ # Commander is running - use osascript to bring it to front and send path
+ osascript -e "
+ tell application \"System Events\"
+ tell process \"commander\"
+ set frontmost to true
+ end tell
+ end tell
+ " 2>/dev/null || true
+
+ # Use the Tauri command to open the project
+ # This uses the new open_project_from_path command we created
+ curl -s -X POST "http://localhost:1420/__tauri_cli__" \
+ -H "Content-Type: application/json" \
+ -d "{\"cmd\":\"open_project_from_path\",\"currentPath\":\"$TARGET_PATH\"}" >/dev/null 2>&1 || {
+ echo "Warning: Could not communicate with running Commander instance"
+ echo "Opening new Commander window..."
+ open -a "$COMMANDER_APP" --args "$TARGET_PATH"
+ }
+else
+ # Commander is not running - launch it with the path argument
+ echo "Opening Commander with project: $TARGET_PATH"
+ open -a "$COMMANDER_APP" --args "$TARGET_PATH"
+fi
+
+echo "✓ Commander launched with project: $TARGET_PATH"
\ No newline at end of file
diff --git a/create-test-cert.sh b/create-test-cert.sh
new file mode 100755
index 0000000..7f2f148
--- /dev/null
+++ b/create-test-cert.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+# Create a self-signed certificate for testing code signing
+# NOTE: This will NOT work for distribution - only for local testing
+
+echo "Creating self-signed certificate for local testing..."
+
+# Create a self-signed certificate
+security create-certificate-identity \
+ -c "Developer ID Application: Test Certificate" \
+ -e "test@example.com" \
+ -s "TestCert" \
+ -S "/System/Library/Keychains/login.keychain"
+
+echo "Certificate created. Check with:"
+echo "security find-identity -v -p codesigning"
\ No newline at end of file
diff --git a/scripts/ci-set-version.mjs b/scripts/ci-set-version.mjs
new file mode 100644
index 0000000..cc1eac6
--- /dev/null
+++ b/scripts/ci-set-version.mjs
@@ -0,0 +1,46 @@
+#!/usr/bin/env node
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const root = path.resolve(__dirname, '..');
+const buildId = process.env.COMMANDER_BUILD_ID || 'dev';
+
+function loadJson(fp) {
+ const text = fs.readFileSync(fp, 'utf8');
+ return JSON.parse(text);
+}
+
+function writeJson(fp, data) {
+ const text = JSON.stringify(data, null, 2) + '\n';
+ fs.writeFileSync(fp, text);
+}
+
+function stripBuild(v) {
+ return (v || '').split('+')[0] || '0.1.0';
+}
+
+const tauriPath = path.join(root, 'src-tauri', 'tauri.conf.json');
+const cargoPath = path.join(root, 'src-tauri', 'Cargo.toml');
+const packagePath = path.join(root, 'package.json');
+
+const tauriConfig = loadJson(tauriPath);
+const baseVersion = stripBuild(tauriConfig.version);
+const buildVersion = `${baseVersion}+build.${buildId}`;
+
+tauriConfig.version = buildVersion;
+writeJson(tauriPath, tauriConfig);
+
+const cargoText = fs.readFileSync(cargoPath, 'utf8');
+const cargoUpdated = cargoText.replace(/version = "[^"]+"/, `version = "${buildVersion}"`);
+fs.writeFileSync(cargoPath, cargoUpdated);
+
+if (fs.existsSync(packagePath)) {
+ const pkg = loadJson(packagePath);
+ pkg.version = buildVersion;
+ writeJson(packagePath, pkg);
+}
+
+process.stdout.write(buildVersion);
diff --git a/scripts/collect-release-assets.mjs b/scripts/collect-release-assets.mjs
new file mode 100644
index 0000000..b07a3b2
--- /dev/null
+++ b/scripts/collect-release-assets.mjs
@@ -0,0 +1,48 @@
+#!/usr/bin/env node
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+if (process.argv.length < 4) {
+ console.error('Usage: node collect-release-assets.mjs ');
+ process.exit(1);
+}
+
+const sourceDir = path.resolve(process.argv[2]);
+const targetDir = path.resolve(process.argv[3]);
+
+const allowedExtensions = new Set([
+ '.dmg',
+ '.pkg',
+ '.zip',
+ '.tar.gz',
+ '.AppImage',
+ '.deb',
+ '.rpm',
+ '.msi',
+ '.exe'
+]);
+
+function hasAllowedExtension(file) {
+ if (file.endsWith('.tar.gz')) return true;
+ const ext = path.extname(file);
+ return allowedExtensions.has(ext);
+}
+
+function collectFiles(dir) {
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
+ for (const entry of entries) {
+ const fullPath = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ collectFiles(fullPath);
+ continue;
+ }
+ if (!hasAllowedExtension(entry.name)) continue;
+ const destName = path.basename(fullPath);
+ const destPath = path.join(targetDir, destName);
+ fs.copyFileSync(fullPath, destPath);
+ }
+}
+
+fs.mkdirSync(targetDir, { recursive: true });
+collectFiles(sourceDir);
diff --git a/src-tauri/cli/commander b/src-tauri/cli/commander
new file mode 100755
index 0000000..bee0abc
--- /dev/null
+++ b/src-tauri/cli/commander
@@ -0,0 +1,85 @@
+#!/bin/bash
+
+# Commander CLI - Opens Commander app with git project detection
+# Usage: commander [path]
+# commander . - Opens current directory (if it's a git repo)
+# commander path - Opens specified path (if it's a git repo)
+
+set -e
+
+# Default to current directory if no argument provided
+TARGET_PATH="${1:-.}"
+
+# Get absolute path
+if [[ "$TARGET_PATH" == "." ]]; then
+ TARGET_PATH="$(pwd)"
+elif [[ ! "$TARGET_PATH" =~ ^/ ]]; then
+ # Relative path - make it absolute
+ TARGET_PATH="$(pwd)/$TARGET_PATH"
+fi
+
+# Check if target path exists
+if [[ ! -d "$TARGET_PATH" ]]; then
+ echo "Error: Directory '$TARGET_PATH' does not exist"
+ exit 1
+fi
+
+# Function to find Commander app
+find_commander_app() {
+ # Check common locations for Commander app
+ local app_paths=(
+ "/Applications/commander.app"
+ "/Applications/Commander.app"
+ "$HOME/Applications/commander.app"
+ "$HOME/Applications/Commander.app"
+ )
+
+ for app_path in "${app_paths[@]}"; do
+ if [[ -d "$app_path" ]]; then
+ echo "$app_path"
+ return 0
+ fi
+ done
+
+ echo ""
+ return 1
+}
+
+# Find Commander app
+COMMANDER_APP=$(find_commander_app)
+
+if [[ -z "$COMMANDER_APP" ]]; then
+ echo "Error: Commander app not found in Applications folder"
+ echo "Please ensure Commander is installed via 'brew install --cask commander'"
+ exit 1
+fi
+
+# Check if Commander is already running
+COMMANDER_PID=$(pgrep -f "commander.app" || true)
+
+if [[ -n "$COMMANDER_PID" ]]; then
+ # Commander is running - use osascript to bring it to front and send path
+ osascript -e "
+ tell application \"System Events\"
+ tell process \"commander\"
+ set frontmost to true
+ end tell
+ end tell
+ " 2>/dev/null || true
+
+ # Use the Tauri command to open the project
+ # This uses the new open_project_from_path command we created
+ curl -s -X POST "http://localhost:1420/__tauri_cli__" \
+ -H "Content-Type: application/json" \
+ -d "{\"cmd\":\"open_project_from_path\",\"currentPath\":\"$TARGET_PATH\"}" >/dev/null 2>&1 || {
+ echo "Warning: Could not communicate with running Commander instance"
+ echo "Opening new Commander window..."
+ open -a "$COMMANDER_APP" --args "$TARGET_PATH"
+ }
+else
+ # Commander is not running - launch it with the path argument
+ echo "Opening Commander with project: $TARGET_PATH"
+ open -a "$COMMANDER_APP" --args "$TARGET_PATH"
+fi
+
+echo "✓ Commander launched with project: $TARGET_PATH"
\ No newline at end of file
diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png
index 6be5e50..02b96c3 100644
Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ
diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png
index e81bece..6d3dc60 100644
Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ
diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png
index a437dd5..bd279f9 100644
Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ
diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png
index 0ca4f27..13567ab 100644
Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ
diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png
index b81f820..039e2a6 100644
Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ
diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png
index 624c7bf..2585167 100644
Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ
diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png
index c021d2b..ae098bf 100644
Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ
diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png
index 6219700..3e64ba6 100644
Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ
diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png
index f9bc048..e1668aa 100644
Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ
diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png
index d5fbfb2..cd067fe 100644
Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ
diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png
index 63440d7..78f2aea 100644
Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ
diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png
index f3f705a..b53bd6d 100644
Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ
diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png
index 4556388..6d3dc60 100644
Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ
diff --git a/src-tauri/icons/backup/128x128.png b/src-tauri/icons/backup/128x128.png
new file mode 100644
index 0000000..6be5e50
Binary files /dev/null and b/src-tauri/icons/backup/128x128.png differ
diff --git a/src-tauri/icons/backup/128x128@2x.png b/src-tauri/icons/backup/128x128@2x.png
new file mode 100644
index 0000000..e81bece
Binary files /dev/null and b/src-tauri/icons/backup/128x128@2x.png differ
diff --git a/src-tauri/icons/backup/32x32.png b/src-tauri/icons/backup/32x32.png
new file mode 100644
index 0000000..a437dd5
Binary files /dev/null and b/src-tauri/icons/backup/32x32.png differ
diff --git a/src-tauri/icons/backup/Square107x107Logo.png b/src-tauri/icons/backup/Square107x107Logo.png
new file mode 100644
index 0000000..0ca4f27
Binary files /dev/null and b/src-tauri/icons/backup/Square107x107Logo.png differ
diff --git a/src-tauri/icons/backup/Square142x142Logo.png b/src-tauri/icons/backup/Square142x142Logo.png
new file mode 100644
index 0000000..b81f820
Binary files /dev/null and b/src-tauri/icons/backup/Square142x142Logo.png differ
diff --git a/src-tauri/icons/backup/Square150x150Logo.png b/src-tauri/icons/backup/Square150x150Logo.png
new file mode 100644
index 0000000..624c7bf
Binary files /dev/null and b/src-tauri/icons/backup/Square150x150Logo.png differ
diff --git a/src-tauri/icons/backup/Square284x284Logo.png b/src-tauri/icons/backup/Square284x284Logo.png
new file mode 100644
index 0000000..c021d2b
Binary files /dev/null and b/src-tauri/icons/backup/Square284x284Logo.png differ
diff --git a/src-tauri/icons/backup/Square30x30Logo.png b/src-tauri/icons/backup/Square30x30Logo.png
new file mode 100644
index 0000000..6219700
Binary files /dev/null and b/src-tauri/icons/backup/Square30x30Logo.png differ
diff --git a/src-tauri/icons/backup/Square310x310Logo.png b/src-tauri/icons/backup/Square310x310Logo.png
new file mode 100644
index 0000000..f9bc048
Binary files /dev/null and b/src-tauri/icons/backup/Square310x310Logo.png differ
diff --git a/src-tauri/icons/backup/Square44x44Logo.png b/src-tauri/icons/backup/Square44x44Logo.png
new file mode 100644
index 0000000..d5fbfb2
Binary files /dev/null and b/src-tauri/icons/backup/Square44x44Logo.png differ
diff --git a/src-tauri/icons/backup/Square71x71Logo.png b/src-tauri/icons/backup/Square71x71Logo.png
new file mode 100644
index 0000000..63440d7
Binary files /dev/null and b/src-tauri/icons/backup/Square71x71Logo.png differ
diff --git a/src-tauri/icons/backup/Square89x89Logo.png b/src-tauri/icons/backup/Square89x89Logo.png
new file mode 100644
index 0000000..f3f705a
Binary files /dev/null and b/src-tauri/icons/backup/Square89x89Logo.png differ
diff --git a/src-tauri/icons/backup/StoreLogo.png b/src-tauri/icons/backup/StoreLogo.png
new file mode 100644
index 0000000..4556388
Binary files /dev/null and b/src-tauri/icons/backup/StoreLogo.png differ
diff --git a/src-tauri/icons/backup/icon.icns b/src-tauri/icons/backup/icon.icns
new file mode 100644
index 0000000..12a5bce
Binary files /dev/null and b/src-tauri/icons/backup/icon.icns differ
diff --git a/src-tauri/icons/backup/icon.ico b/src-tauri/icons/backup/icon.ico
new file mode 100644
index 0000000..b3636e4
Binary files /dev/null and b/src-tauri/icons/backup/icon.ico differ
diff --git a/src-tauri/icons/backup/icon.png b/src-tauri/icons/backup/icon.png
new file mode 100644
index 0000000..e1cd261
Binary files /dev/null and b/src-tauri/icons/backup/icon.png differ
diff --git a/src-tauri/icons/clean-icons.sh b/src-tauri/icons/clean-icons.sh
new file mode 100755
index 0000000..482b4d8
--- /dev/null
+++ b/src-tauri/icons/clean-icons.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# Clean up generated icon files while preserving commander.svg and backup/ folder
+echo "Cleaning up generated icon files..."
+
+# Remove all PNG files
+rm -f *.png
+
+# Remove platform-specific formats
+rm -f *.ico *.icns
+
+echo "✅ Cleanup complete! Preserved commander.svg and backup/ folder."
+echo "Ready for fresh icon generation."
\ No newline at end of file
diff --git a/src-tauri/icons/commander.svg b/src-tauri/icons/commander.svg
new file mode 100644
index 0000000..2b94a13
--- /dev/null
+++ b/src-tauri/icons/commander.svg
@@ -0,0 +1,23 @@
+
diff --git a/src-tauri/icons/generate-icons.sh b/src-tauri/icons/generate-icons.sh
new file mode 100755
index 0000000..746270d
--- /dev/null
+++ b/src-tauri/icons/generate-icons.sh
@@ -0,0 +1,101 @@
+#!/bin/bash
+
+# High-quality icon generation from commander.svg
+# Prevents quality loss with proper ImageMagick settings
+
+if [ ! -f "commander.svg" ]; then
+ echo "❌ Error: commander.svg not found in current directory"
+ exit 1
+fi
+
+echo "🎨 Generating high-quality icons from commander.svg..."
+
+# Ultra high-quality settings for crisp icons
+DENSITY="-density 600" # Double density for sharper rendering
+BACKGROUND="-background transparent"
+QUALITY="-quality 100"
+ANTIALIAS="-antialias"
+COLORSPACE="-colorspace sRGB" # Proper color management
+FILTER="-filter Lanczos" # High-quality resize algorithm
+UNSHARP="-unsharp 0x0.75+0.75+0.008" # Sharpen after resize
+
+# Ensure 8-bit RGBA output (Tauri/Tao expects 8-bit per channel)
+# Without this, ImageMagick may emit 16-bit PNGs, causing a runtime panic:
+# "invalid icon: The specified dimensions (WxH) don't match the number of pixels supplied by the rgba argument"
+BITDEPTH="-depth 8 -type TrueColorAlpha -define png:color-type=6"
+
+# Generate all required PNG sizes
+echo "📐 Generating PNG icons..."
+
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 30x30 $UNSHARP Square30x30Logo.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 32x32 $UNSHARP 32x32.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 44x44 $UNSHARP Square44x44Logo.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 71x71 $UNSHARP Square71x71Logo.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 89x89 $UNSHARP Square89x89Logo.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 107x107 $UNSHARP Square107x107Logo.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 128x128 $UNSHARP 128x128.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 142x142 $UNSHARP Square142x142Logo.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 150x150 $UNSHARP Square150x150Logo.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 256x256 $UNSHARP 128x128@2x.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 284x284 $UNSHARP Square284x284Logo.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 310x310 $UNSHARP Square310x310Logo.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 512x512 $UNSHARP icon.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 256x256 $UNSHARP StoreLogo.png
+
+echo "🖼️ Generating platform-specific formats..."
+
+# Generate ICO for Windows (multiple sizes in one file)
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH \
+ \( -clone 0 -resize 16x16 $UNSHARP \) \
+ \( -clone 0 -resize 32x32 $UNSHARP \) \
+ \( -clone 0 -resize 48x48 $UNSHARP \) \
+ \( -clone 0 -resize 256x256 $UNSHARP \) \
+ -delete 0 icon.ico
+
+# Generate ICNS for macOS with maximum quality
+echo "🍎 Creating macOS ICNS with all required sizes..."
+
+# Create iconset directory structure (Apple's preferred method)
+rm -rf commander.iconset
+mkdir -p commander.iconset
+
+# Generate all required sizes for iconset
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 16x16 $UNSHARP commander.iconset/icon_16x16.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 32x32 $UNSHARP commander.iconset/icon_16x16@2x.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 32x32 $UNSHARP commander.iconset/icon_32x32.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 64x64 $UNSHARP commander.iconset/icon_32x32@2x.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 128x128 $UNSHARP commander.iconset/icon_128x128.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 256x256 $UNSHARP commander.iconset/icon_128x128@2x.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 256x256 $UNSHARP commander.iconset/icon_256x256.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 512x512 $UNSHARP commander.iconset/icon_256x256@2x.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 512x512 $UNSHARP commander.iconset/icon_512x512.png
+magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 1024x1024 $UNSHARP commander.iconset/icon_512x512@2x.png
+
+# Convert iconset to ICNS using Apple's iconutil (best quality)
+iconutil -c icns commander.iconset
+
+# Clean up iconset directory
+rm -rf commander.iconset
+
+# Fallback: check if png2icns is available
+if [ ! -f "icon.icns" ] && command -v png2icns &> /dev/null; then
+ echo "🔄 Fallback: Using png2icns..."
+ magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 1024x1024 $UNSHARP temp_1024.png
+ png2icns icon.icns temp_1024.png
+ rm temp_1024.png
+fi
+
+# Final fallback: use libicns png2icns if available
+if [ ! -f "icon.icns" ] && command -v /opt/homebrew/bin/png2icns &> /dev/null; then
+ echo "🔄 Fallback: Using libicns png2icns..."
+ magick commander.svg $DENSITY $BACKGROUND $COLORSPACE $ANTIALIAS $QUALITY $FILTER $BITDEPTH -resize 1024x1024 $UNSHARP temp_1024.png
+ /opt/homebrew/bin/png2icns icon.icns temp_1024.png
+ rm temp_1024.png
+fi
+
+echo "✅ Icon generation complete!"
+echo "📋 Generated files:"
+ls -la *.png *.ico *.icns | grep -v commander.svg
+
+echo ""
+echo "🚀 Ready to build your Tauri app with new icons!"
diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns
index 12a5bce..1d54a9b 100644
Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ
diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico
index b3636e4..a3a9f91 100644
Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ
diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png
index e1cd261..d9a5a8d 100644
Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ
diff --git a/src-tauri/src/commands/cli_commands.rs b/src-tauri/src/commands/cli_commands.rs
index d8a2253..5775f85 100644
--- a/src-tauri/src/commands/cli_commands.rs
+++ b/src-tauri/src/commands/cli_commands.rs
@@ -10,6 +10,7 @@ use portable_pty::{native_pty_system, CommandBuilder, PtySize};
use crate::models::*;
use crate::commands::settings_commands::load_all_agent_settings;
+use crate::services::execution_mode_service::{ExecutionMode, codex_flags_for_mode};
// Constants for session management
const SESSION_TIMEOUT_SECONDS: i64 = 1800; // 30 minutes
@@ -69,7 +70,7 @@ fn get_agent_quit_command(agent: &str) -> &str {
}
}
-async fn build_agent_command_args(agent: &str, message: &str, app_handle: &tauri::AppHandle) -> Vec {
+async fn build_agent_command_args(agent: &str, message: &str, app_handle: &tauri::AppHandle, execution_mode: Option, dangerous_bypass: bool, permission_mode: Option) -> Vec {
let mut args = Vec::new();
// Try to get agent settings to include model preference
@@ -100,6 +101,14 @@ async fn build_agent_command_args(agent: &str, message: &str, app_handle: &tauri
args.push("stream-json".to_string());
args.push("--verbose".to_string());
+ // Permission mode for Claude (plan | acceptEdits | ask)
+ if let Some(pm) = permission_mode.as_ref() {
+ if !pm.is_empty() {
+ args.push("--permission-mode".to_string());
+ args.push(pm.clone());
+ }
+ }
+
// Add model flag if set in preferences
if let Some(ref model) = current_agent_settings.model {
if !model.is_empty() {
@@ -118,6 +127,14 @@ async fn build_agent_command_args(agent: &str, message: &str, app_handle: &tauri
args.push(model.clone());
}
}
+
+ // Add flags based on execution mode (if provided)
+ if let Some(mode_str) = execution_mode {
+ if let Some(mode) = ExecutionMode::from_str(&mode_str) {
+ let extra = codex_flags_for_mode(mode, dangerous_bypass && matches!(mode, ExecutionMode::Full));
+ args.extend(extra);
+ }
+ }
if !message.is_empty() {
args.push(message.to_string());
@@ -125,6 +142,13 @@ async fn build_agent_command_args(agent: &str, message: &str, app_handle: &tauri
}
"gemini" => {
args.push("--prompt".to_string());
+ // Permission-mode pass-through if provided (adjust flag here if CLI differs)
+ if let Some(pm) = permission_mode.as_ref() {
+ if !pm.is_empty() {
+ args.push("--permission-mode".to_string());
+ args.push(pm.clone());
+ }
+ }
// Add model flag if set in preferences
if let Some(ref model) = current_agent_settings.model {
@@ -393,6 +417,9 @@ pub async fn execute_persistent_cli_command(
agent: String,
message: String,
working_dir: Option,
+ execution_mode: Option,
+ dangerousBypass: Option,
+ permissionMode: Option,
) -> Result<(), String> {
println!("🔍 BACKEND RECEIVED - Agent: {}, Working Dir: {:?}", agent, working_dir);
let app_clone = app.clone();
@@ -438,7 +465,7 @@ pub async fn execute_persistent_cli_command(
}
// Build args once
- let command_args = build_agent_command_args(&agent_name, &actual_message, &app_clone).await;
+ let command_args = build_agent_command_args(&agent_name, &actual_message, &app_clone, execution_mode.clone(), dangerousBypass.unwrap_or(false), permissionMode.clone()).await;
// Resolve absolute path of the executable to avoid PATH issues in GUI contexts
let resolved_prog = which::which(&agent_name)
@@ -569,10 +596,13 @@ pub async fn execute_cli_command(
command: String,
args: Vec,
working_dir: Option,
+ execution_mode: Option,
+ dangerousBypass: Option,
+ permissionMode: Option,
) -> Result<(), String> {
// Legacy function - redirect to persistent session handler
let message = args.join(" ");
- execute_persistent_cli_command(app, session_id, command, message, working_dir).await
+ execute_persistent_cli_command(app, session_id, command, message, working_dir, execution_mode, dangerousBypass, permissionMode).await
}
#[tauri::command]
@@ -584,7 +614,7 @@ pub async fn execute_claude_command(
#[allow(non_snake_case)]
working_dir: Option,
) -> Result<(), String> {
- execute_persistent_cli_command(app, sessionId, "claude".to_string(), message, working_dir).await
+ execute_persistent_cli_command(app, sessionId, "claude".to_string(), message, working_dir, None, None, None).await
}
#[tauri::command]
@@ -595,8 +625,11 @@ pub async fn execute_codex_command(
message: String,
#[allow(non_snake_case)]
working_dir: Option,
+ executionMode: Option,
+ dangerousBypass: Option,
+ permissionMode: Option,
) -> Result<(), String> {
- execute_persistent_cli_command(app, sessionId, "codex".to_string(), message, working_dir).await
+ execute_persistent_cli_command(app, sessionId, "codex".to_string(), message, working_dir, executionMode, dangerousBypass, permissionMode).await
}
#[tauri::command]
@@ -608,7 +641,7 @@ pub async fn execute_gemini_command(
#[allow(non_snake_case)]
working_dir: Option,
) -> Result<(), String> {
- execute_persistent_cli_command(app, sessionId, "gemini".to_string(), message, working_dir).await
+ execute_persistent_cli_command(app, sessionId, "gemini".to_string(), message, working_dir, None, None, None).await
}
// Test command to demonstrate CLI streaming (this will always work)
diff --git a/src-tauri/src/commands/git_commands.rs b/src-tauri/src/commands/git_commands.rs
index 8f8333a..a1d7f6e 100644
--- a/src-tauri/src/commands/git_commands.rs
+++ b/src-tauri/src/commands/git_commands.rs
@@ -581,3 +581,56 @@ pub async fn append_project_chat_message(app: tauri::AppHandle, project_path: St
existing.push(message);
save_project_chat(app, project_path, existing).await
}
+
+static CLI_PROJECT_PATH: std::sync::Mutex