From b7585420efe5c10cebaabab5f692f5ca5ec62dfc Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Tue, 10 Mar 2026 08:29:06 -0700 Subject: [PATCH] Reconcile project statuses against Docker on startup, update docs and CI - Add reconcile_project_statuses command that checks actual Docker container state on startup, preserving Running status for containers that are genuinely still running and resetting stale statuses to Stopped - Add is_container_running helper using Docker inspect API - Frontend calls reconciliation after Docker is confirmed available - Update TECHNICAL.md project structure, auth modes, and file listings to match current codebase - Update README.md and HOW-TO-USE.md with MCP servers, Mission Control, file manager, bash shells, clipboard/audio shims, and progress modal docs - Add workflow file self-triggers to CI path filters for build-app.yml and build.yml - Install Mission Control skills to ~/.claude/skills/ in entrypoint Co-Authored-By: Claude Opus 4.6 --- .gitea/workflows/build-app.yml | 1 + .gitea/workflows/build.yml | 2 + HOW-TO-USE.md | 131 ++++++++++++++++-- README.md | 63 ++++++--- TECHNICAL.md | 130 ++++++++++------- .../src/commands/project_commands.rs | 40 ++++++ app/src-tauri/src/docker/container.rs | 14 +- app/src-tauri/src/lib.rs | 1 + app/src-tauri/src/storage/projects_store.rs | 2 + app/src/App.tsx | 13 +- app/src/lib/tauri-commands.ts | 2 + container/entrypoint.sh | 9 ++ 12 files changed, 323 insertions(+), 85 deletions(-) diff --git a/.gitea/workflows/build-app.yml b/.gitea/workflows/build-app.yml index bfdc276..a51a8b0 100644 --- a/.gitea/workflows/build-app.yml +++ b/.gitea/workflows/build-app.yml @@ -10,6 +10,7 @@ on: branches: [main] paths: - "app/**" + - ".gitea/workflows/build-app.yml" workflow_dispatch: env: diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index 7628239..db45624 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -5,10 +5,12 @@ on: branches: [main] paths: - "container/**" + - ".gitea/workflows/build.yml" pull_request: branches: [main] paths: - "container/**" + - ".gitea/workflows/build.yml" env: REGISTRY: repo.anhonesthost.net diff --git a/HOW-TO-USE.md b/HOW-TO-USE.md index df8e6db..9dc9f43 100644 --- a/HOW-TO-USE.md +++ b/HOW-TO-USE.md @@ -65,11 +65,11 @@ Switch to the **Projects** tab in the sidebar and click the **+** button. ### 3. Start the Container -Select your project in the sidebar and click **Start**. The status dot changes from gray (stopped) to orange (starting) to green (running). +Select your project in the sidebar and click **Start**. A progress modal appears showing real-time status as the container starts. The status dot changes from gray (stopped) to orange (starting) to green (running). The modal auto-closes on success. ### 4. Open a Terminal -Click the **Terminal** button (highlighted in accent color) to open an interactive terminal session. A new tab appears in the top bar and an xterm.js terminal loads in the main area. +Click the **Terminal** button to open an interactive terminal session. A new tab appears in the top bar and an xterm.js terminal loads in the main area. Claude Code launches automatically with `--dangerously-skip-permissions` inside the sandboxed container. @@ -99,16 +99,16 @@ Claude Code launches automatically with `--dangerously-skip-permissions` inside │ Sidebar │ │ │ │ Terminal View │ │ Projects │ (xterm.js) │ +│ MCP │ │ │ Settings │ │ -│ │ │ ├────────────┴────────────────────────────────────────┤ │ StatusBar X projects · X running · X terminals │ └─────────────────────────────────────────────────────┘ ``` -- **TopBar** — Terminal tabs for switching between sessions. Status dots on the right show Docker connection (green = connected) and image availability (green = ready). -- **Sidebar** — Toggle between the **Projects** list and **Settings** panel. -- **Terminal View** — Interactive terminal powered by xterm.js with WebGL rendering. +- **TopBar** — Terminal tabs for switching between sessions. Bash shell tabs show a "(bash)" suffix. Status dots on the right show Docker connection (green = connected) and image availability (green = ready). +- **Sidebar** — Toggle between the **Projects** list, **MCP** server configuration, and **Settings** panel. +- **Terminal View** — Interactive terminal powered by xterm.js with WebGL rendering. Includes a **Jump to Current** button that appears when you scroll up, so you can quickly return to the latest output. - **StatusBar** — Counts of total projects, running containers, and open terminal sessions. --- @@ -134,11 +134,17 @@ Select a project in the sidebar to see its action buttons: |--------|---------------|--------------| | **Start** | Stopped | Creates (if needed) and starts the container | | **Stop** | Running | Stops the container but preserves its state | -| **Terminal** | Running | Opens a new terminal session in this container | +| **Terminal** | Running | Opens a new Claude Code terminal session | +| **Shell** | Running | Opens a bash login shell in the container (no Claude Code) | +| **Files** | Running | Opens the file manager to browse, download, and upload files | | **Reset** | Stopped | Destroys and recreates the container from scratch | | **Config** | Always | Toggles the configuration panel | | **Remove** | Stopped | Deletes the project and its container (with confirmation) | +### Renaming a Project + +Double-click the project name in the sidebar to rename it inline. Press **Enter** to confirm or **Escape** to cancel. + ### Container Lifecycle Containers use a **stop/start** model. When you stop a container, everything inside it is preserved — installed packages, modified files, downloaded tools. Starting it again resumes where you left off. @@ -147,6 +153,10 @@ Containers use a **stop/start** model. When you stop a container, everything ins Only **Remove** deletes everything, including the config volume and any stored credentials. +### Container Progress Feedback + +When starting, stopping, or resetting a container, a progress modal shows real-time status messages (e.g., "Setting up MCP network...", "Starting MCP containers...", "Creating container..."). If an error occurs, the modal displays the error with a **Close** button. A **Force Stop** option is available if the operation stalls. The modal auto-closes on success. + --- ## Project Configuration @@ -177,6 +187,19 @@ When enabled, the host Docker socket is mounted into the container so Claude Cod > Toggling this requires stopping and restarting the container to take effect. +### Mission Control + +Toggle **Mission Control** to integrate [Flight Control](https://github.com/msieurthenardier/mission-control) — an AI-first development methodology — into the project. When enabled: + +- The Flight Control repository is automatically cloned into the container +- Flight Control skills are installed to Claude Code's skill directory (`~/.claude/skills/`) +- Project instructions are appended with Flight Control workflow guidance +- The repository is symlinked at `/workspace/mission-control` + +Available skills include `/mission`, `/flight`, `/leg`, `/agentic-workflow`, `/flight-debrief`, `/mission-debrief`, `/daily-briefing`, and `/init-project`. + +> This setting can only be changed when the container is stopped. Toggling it triggers a container recreation on the next start. + ### Environment Variables Click **Edit** to open the environment variables modal. Add key-value pairs that will be injected into the container. Per-project variables override global variables with the same key. @@ -188,8 +211,8 @@ Click **Edit** to open the environment variables modal. Add key-value pairs that Click **Edit** to map host ports to container ports. This is useful when Claude Code starts a web server or other service inside the container and you want to access it from your host browser. Each mapping specifies: -- **Host Port** — The port on your machine (1–65535) -- **Container Port** — The port inside the container (1–65535) +- **Host Port** — The port on your machine (1-65535) +- **Container Port** — The port inside the container (1-65535) - **Protocol** — TCP (default) or UDP ### Claude Instructions @@ -198,6 +221,57 @@ Click **Edit** to write per-project instructions for Claude Code. These are writ --- +## MCP Servers (Beta) + +Triple-C supports [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers, which extend Claude Code with access to external tools and data sources. MCP servers are configured in a **global library** and **enabled per-project**. + +### Accessing MCP Configuration + +Click the **MCP** tab in the sidebar to open the MCP server library. This is where you define all available MCP servers. + +### Adding an MCP Server + +1. Type a name in the input field and click **Add**. +2. Configure the server in its card: + +| Setting | Description | +|---------|-------------| +| **Docker Image** | Optional. If provided, the server runs as an isolated Docker container. | +| **Transport Type** | **Stdio** (command-line) or **HTTP** (network endpoint) | + +#### Stdio Mode (Manual) +- **Command** — The executable to run (e.g., `npx`) +- **Arguments** — Space-separated arguments +- **Environment Variables** — Key-value pairs passed to the command + +#### HTTP Mode (Manual) +- **URL** — The MCP endpoint (e.g., `http://localhost:3000/mcp`) +- **Headers** — Custom HTTP headers + +#### Docker Mode +When a Docker image is specified, the server runs as a container on a per-project network: +- **Container Port** — Port the MCP server listens on inside its container (default: 3000) +- **Environment Variables** — Injected into the Docker container + +### Enabling MCP Servers Per-Project + +In a project's configuration panel, the **MCP Servers** section shows checkboxes for all globally defined servers. Toggle each server on or off for that project. Changes require a container restart. + +### How Docker-Based MCP Works + +When a project with Docker-based MCP servers starts: + +1. A dedicated **bridge network** is created for the project (`triple-c-net-{projectId}`) +2. Each enabled Docker MCP server gets its own container on that network +3. The main project container is connected to the same network +4. MCP server configuration is injected into Claude Code's config file + +**Stdio + Docker** servers communicate via `docker exec`, which automatically enables Docker socket access on the main container. **HTTP + Docker** servers are reached by hostname on the shared network (e.g., `http://triple-c-mcp-{serverId}:3000/mcp`). + +When MCP configuration changes (servers added/removed/modified), the container is automatically recreated on the next start to apply the new configuration. + +--- + ## AWS Bedrock Configuration To use Claude via AWS Bedrock instead of Anthropic's API, switch the auth mode to **Bedrock** on the project card. @@ -264,7 +338,11 @@ When an update is available, a pulsing **Update** button appears in the top bar. ### Multiple Sessions -You can open multiple terminal sessions (even for the same project). Each session gets its own tab in the top bar. Click a tab to switch, or click the **x** on a tab to close it. +You can open multiple terminal sessions (even for the same project). Each session gets its own tab in the top bar. Click a tab to switch, or click the **x** on a tab to close it. Tabs show the project name, with a "(bash)" suffix for shell sessions. + +### Bash Shell Sessions + +In addition to Claude Code terminals, you can open a plain **bash login shell** in any running container by clicking the **Shell** button. This is useful for manual inspection, package installation, debugging, or running commands that don't need Claude Code. ### URL Detection @@ -272,9 +350,28 @@ When Claude Code prints a long URL (e.g., during `claude login`), Triple-C detec Shorter URLs in terminal output are also clickable directly. +### Clipboard Support (OSC 52) + +Programs inside the container can copy text to your host clipboard. When a container program uses `xclip`, `xsel`, or `pbcopy`, the text is transparently forwarded to your host clipboard via OSC 52 escape sequences. No additional configuration is required — this works out of the box. + ### Image Paste -You can paste images from your clipboard into the terminal (Ctrl+V / Cmd+V). The image is uploaded to the container and the file path is injected into the terminal input so Claude Code can reference it. +You can paste images from your clipboard into the terminal (Ctrl+V / Cmd+V). The image is uploaded to the container as `/tmp/clipboard_.png` and the file path is injected into the terminal input so Claude Code can reference it. A toast notification confirms the upload. + +### Jump to Current + +When you scroll up in the terminal to review previous output, a **Jump to Current** button appears in the bottom-right corner. Click it to scroll back to the latest output. + +### File Manager + +Click the **Files** button on a running project to open the file manager modal. You can: + +- **Browse** the container filesystem starting from `/workspace`, with breadcrumb navigation +- **Download** any file to your host machine via the download button on each file entry +- **Upload** files from your host into the current container directory +- **Refresh** the directory listing at any time + +The file manager shows file names, sizes, and modification dates. ### Terminal Rendering @@ -356,6 +453,8 @@ The sandbox container (Ubuntu 24.04) comes pre-installed with: | build-essential | — | C/C++ compiler toolchain | | openssh-client | — | SSH for git and remote access | +The container also includes **clipboard shims** (`xclip`, `xsel`, `pbcopy`) that forward copy operations to the host via OSC 52, and an **audio shim** (`rec`, `arecord`) for future voice mode support. + You can install additional tools at runtime with `sudo apt install`, `pip install`, `npm install -g`, etc. Installed packages persist across container stops (but not across resets). --- @@ -378,7 +477,7 @@ You can install additional tools at runtime with `sudo apt install`, `pip instal - Check that the Docker image is "Ready" in Settings. - Verify that the mounted folder paths exist on your host. -- Look at the error message displayed in red below the project card. +- Look at the error message displayed in the progress modal. ### OAuth Login URL Not Opening @@ -394,4 +493,10 @@ You can install additional tools at runtime with `sudo apt install`, `pip instal ### Settings Won't Save - Most project settings can only be changed when the container is **stopped**. Stop the container first, make your changes, then start it again. -- Some changes (like toggling Docker access or changing mounted folders) trigger an automatic container recreation on the next start. +- Some changes (like toggling Docker access, Mission Control, or changing mounted folders) trigger an automatic container recreation on the next start. + +### MCP Containers Not Starting + +- Ensure the Docker image for the MCP server exists (pull it first if needed). +- Check that Docker socket access is available (stdio + Docker MCP servers auto-enable this). +- Try resetting the project container to force a clean recreation. diff --git a/README.md b/README.md index d8f7f84..e10eb9f 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,10 @@ Triple-C is a cross-platform desktop application that sandboxes Claude Code insi ### Container Lifecycle 1. **Create**: New container created with bind mounts, env vars, and labels -2. **Start**: Container started, entrypoint remaps UID/GID, sets up SSH, configures Docker group -3. **Terminal**: `docker exec` launches Claude Code with a PTY -4. **Stop**: Container halted (filesystem persists in named volume) -5. **Restart**: Existing container restarted; recreated if settings changed (e.g., Docker access toggled) +2. **Start**: Container started, entrypoint remaps UID/GID, sets up SSH, configures Docker group, sets up MCP servers +3. **Terminal**: `docker exec` launches Claude Code (or bash shell) with a PTY +4. **Stop**: Container halted (filesystem persists in named volume); MCP containers stopped +5. **Restart**: Existing container restarted; recreated if settings changed (detected via SHA-256 fingerprint) 6. **Reset**: Container removed and recreated from scratch (named volume preserved) ### Mounts @@ -41,14 +41,14 @@ Triple-C is a cross-platform desktop application that sandboxes Claude Code insi | `/home/claude/.claude` | `triple-c-claude-config-{projectId}` | Named Volume | Persists across container recreation | | `/tmp/.host-ssh` | SSH key directory | Bind | Read-only; entrypoint copies to `~/.ssh` | | `/home/claude/.aws` | AWS config directory | Bind | Read-only; for Bedrock auth | -| `/var/run/docker.sock` | Host Docker socket | Bind | Only if "Allow container spawning" is ON | +| `/var/run/docker.sock` | Host Docker socket | Bind | If "Allow container spawning" is ON, or auto-enabled by stdio+Docker MCP servers | ### Authentication Modes Each project can independently use one of: - **Anthropic** (OAuth): User runs `claude login` inside the terminal on first use. Token persisted in the config volume across restarts and resets. -- **AWS Bedrock**: Per-project AWS credentials (static keys, profile, or bearer token). +- **AWS Bedrock**: Per-project AWS credentials (static keys, profile, or bearer token). SSO sessions are validated before launching Claude for Profile auth. ### Container Spawning (Sibling Containers) @@ -56,6 +56,20 @@ When "Allow container spawning" is enabled per-project, the host Docker socket i If the Docker access setting is toggled after a container already exists, the container is automatically recreated on next start to apply the mount change. The named config volume (keyed by project ID) is preserved across recreation. +### MCP Server Architecture + +Triple-C supports [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers as a Beta feature. MCP servers extend Claude Code with external tools and data sources. + +- **Global library**: MCP servers are defined globally in the MCP sidebar tab and stored in `mcp_servers.json` +- **Per-project toggles**: Each project enables/disables individual servers via checkboxes +- **Docker isolation**: MCP servers can run as isolated Docker containers on a per-project bridge network (`triple-c-net-{projectId}`) +- **Transport types**: Stdio (command-line) and HTTP (network endpoint), each with manual or Docker mode +- **Auto-detection**: Config changes are detected via SHA-256 fingerprints and trigger automatic container recreation + +### Mission Control Integration + +Optional per-project integration with [Flight Control](https://github.com/msieurthenardier/mission-control) — an AI-first development methodology. When enabled, the repo is cloned into the container, skills are installed, and workflow instructions are injected into CLAUDE.md. + ### Docker Socket Path The socket path is OS-aware: @@ -75,17 +89,32 @@ Users can override this in Settings via the global `docker_socket_path` option. | `app/src/components/layout/StatusBar.tsx` | Running project/terminal counts | | `app/src/components/projects/ProjectCard.tsx` | Project config, auth mode, action buttons | | `app/src/components/projects/ProjectList.tsx` | Project list in sidebar | -| `app/src/components/settings/SettingsPanel.tsx` | API key, Docker, AWS settings | -| `app/src/components/terminal/TerminalView.tsx` | xterm.js terminal with WebGL, URL detection | -| `app/src/components/terminal/TerminalTabs.tsx` | Tab bar for multiple terminal sessions | -| `app/src-tauri/src/docker/container.rs` | Container creation, mounts, env vars, inspection | -| `app/src-tauri/src/docker/exec.rs` | PTY exec sessions for terminal interaction | +| `app/src/components/projects/FileManagerModal.tsx` | File browser modal (browse, download, upload) | +| `app/src/components/projects/ContainerProgressModal.tsx` | Real-time container operation progress | +| `app/src/components/mcp/McpPanel.tsx` | MCP server library (global configuration) | +| `app/src/components/mcp/McpServerCard.tsx` | Individual MCP server configuration card | +| `app/src/components/settings/SettingsPanel.tsx` | Docker, AWS, timezone, and global settings | +| `app/src/components/terminal/TerminalView.tsx` | xterm.js terminal with WebGL, URL detection, OSC 52 clipboard, image paste | +| `app/src/components/terminal/TerminalTabs.tsx` | Tab bar for multiple terminal sessions (claude + bash) | +| `app/src/hooks/useTerminal.ts` | Terminal session management (claude and bash modes) | +| `app/src/hooks/useFileManager.ts` | File manager operations (list, download, upload) | +| `app/src/hooks/useMcpServers.ts` | MCP server CRUD operations | +| `app/src/hooks/useVoice.ts` | Voice mode audio capture (currently hidden) | +| `app/src-tauri/src/docker/container.rs` | Container creation, mounts, env vars, MCP injection, fingerprinting | +| `app/src-tauri/src/docker/exec.rs` | PTY exec sessions, file upload/download via tar | | `app/src-tauri/src/docker/image.rs` | Image building/pulling | +| `app/src-tauri/src/docker/network.rs` | Per-project bridge networks for MCP containers | | `app/src-tauri/src/commands/project_commands.rs` | Start/stop/rebuild Tauri command handlers | -| `app/src-tauri/src/models/project.rs` | Project struct (auth mode, Docker access, etc.) | -| `app/src-tauri/src/models/app_settings.rs` | Global settings (image source, Docker socket, AWS) | -| `container/Dockerfile` | Ubuntu 24.04 sandbox image with Claude Code + dev tools | -| `container/entrypoint.sh` | UID/GID remap, SSH setup, Docker group config | +| `app/src-tauri/src/commands/file_commands.rs` | File manager Tauri commands (list, download, upload) | +| `app/src-tauri/src/commands/mcp_commands.rs` | MCP server CRUD Tauri commands | +| `app/src-tauri/src/models/project.rs` | Project struct (auth mode, Docker access, MCP servers, Mission Control) | +| `app/src-tauri/src/models/mcp_server.rs` | MCP server struct (transport, Docker image, env vars) | +| `app/src-tauri/src/models/app_settings.rs` | Global settings (image source, Docker socket, AWS, microphone) | +| `app/src-tauri/src/storage/mcp_store.rs` | MCP server persistence (JSON with atomic writes) | +| `container/Dockerfile` | Ubuntu 24.04 sandbox image with Claude Code + dev tools + clipboard/audio shims | +| `container/entrypoint.sh` | UID/GID remap, SSH setup, Docker group config, MCP injection, Mission Control setup | +| `container/osc52-clipboard` | Clipboard shim (xclip/xsel/pbcopy via OSC 52) | +| `container/audio-shim` | Audio capture shim (rec/arecord via FIFO) for voice mode | ## CSS / Styling Notes @@ -100,4 +129,6 @@ Users can override this in Settings via the global `docker_socket_path` option. **Pre-installed tools**: Claude Code, Node.js 22 LTS + pnpm, Python 3.12 + uv + ruff, Rust (stable), Docker CLI, git + gh, AWS CLI v2, ripgrep, openssh-client, build-essential -**Default user**: `claude` (UID/GID 1000, remapped by entrypoint to match host) \ No newline at end of file +**Shims**: `xclip`/`xsel`/`pbcopy` (OSC 52 clipboard forwarding), `rec`/`arecord` (audio FIFO for voice mode) + +**Default user**: `claude` (UID/GID 1000, remapped by entrypoint to match host) diff --git a/TECHNICAL.md b/TECHNICAL.md index e09883a..3985378 100644 --- a/TECHNICAL.md +++ b/TECHNICAL.md @@ -154,13 +154,12 @@ The `.claude` configuration directory uses a **named Docker volume** (`triple-c- ### Authentication Modes -Each project independently chooses one of three authentication methods: +Each project independently chooses one of two authentication methods: | Mode | How It Works | When to Use | |------|-------------|-------------| -| **Login (OAuth)** | User runs `claude login` or `/login` inside the terminal. OAuth URL opens in host browser via the web links addon. Token persists in the `.claude` config volume. | Personal use, interactive sessions | -| **API Key** | Key stored in OS keychain, injected as `ANTHROPIC_API_KEY` env var at container creation. | Automated workflows, team-shared keys | -| **AWS Bedrock** | Per-project AWS credentials (static, profile, or bearer token) injected as env vars. `~/.aws` config optionally bind-mounted read-only. | Enterprise environments using Bedrock | +| **Anthropic (OAuth)** | User runs `claude login` or `/login` inside the terminal. OAuth URL opens in host browser via URL detection. Token persists in the `.claude` config volume. | Default — personal and team use | +| **AWS Bedrock** | Per-project AWS credentials (static keys, profile, or bearer token) injected as env vars. `~/.aws` config optionally bind-mounted read-only. | Enterprise environments using Bedrock | ### UID/GID Remapping @@ -213,66 +212,93 @@ The `TerminalView` component works around this with a **URL accumulator**: ``` triple-c/ -├── LICENSE # MIT -├── TECHNICAL.md # This document -├── Triple-C.md # Project overview +├── README.md # Architecture overview +├── TECHNICAL.md # This document +├── HOW-TO-USE.md # User guide +├── BUILDING.md # Build instructions +├── CLAUDE.md # Claude Code instructions │ ├── container/ -│ ├── Dockerfile # Ubuntu 24.04 + all dev tools + Claude Code -│ └── entrypoint.sh # UID/GID remap, SSH setup, git config +│ ├── Dockerfile # Ubuntu 24.04 + all dev tools + Claude Code +│ ├── entrypoint.sh # UID/GID remap, SSH setup, git config, MCP injection +│ ├── osc52-clipboard # Clipboard shim (xclip/xsel/pbcopy via OSC 52) +│ ├── audio-shim # Audio capture shim (rec/arecord via FIFO) +│ ├── triple-c-scheduler # Bash-based cron task system +│ └── triple-c-task-runner # Task execution runner for scheduler │ -└── app/ # Tauri v2 desktop application - ├── package.json # React, xterm.js, zustand, tailwindcss - ├── vite.config.ts # Vite bundler config - ├── index.html # HTML entry point +├── .gitea/ +│ └── workflows/ +│ ├── build-app.yml # Build Tauri app (Linux/macOS/Windows) +│ ├── build.yml # Build container image (multi-arch) +│ ├── sync-release.yml # Mirror releases to GitHub +│ └── backfill-releases.yml # Bulk copy releases to GitHub +│ +└── app/ # Tauri v2 desktop application + ├── package.json # React, xterm.js, zustand, tailwindcss + ├── vite.config.ts # Vite bundler config + ├── index.html # HTML entry point │ - ├── src/ # React frontend - │ ├── main.tsx # React DOM root - │ ├── App.tsx # Top-level layout - │ ├── index.css # CSS variables, dark theme, scrollbars + ├── src/ # React frontend + │ ├── main.tsx # React DOM root + │ ├── App.tsx # Top-level layout + │ ├── index.css # CSS variables, dark theme, scrollbars │ ├── store/ - │ │ └── appState.ts # Zustand store (projects, sessions, UI) + │ │ └── appState.ts # Zustand store (projects, sessions, MCP, UI) │ ├── hooks/ - │ │ ├── useDocker.ts # Docker status, image build - │ │ ├── useProjects.ts # Project CRUD operations - │ │ ├── useSettings.ts # API key, app settings - │ │ └── useTerminal.ts # Terminal I/O, resize, session events + │ │ ├── useDocker.ts # Docker status, image build/pull + │ │ ├── useFileManager.ts # File manager operations + │ │ ├── useMcpServers.ts # MCP server CRUD + │ │ ├── useProjects.ts # Project CRUD operations + │ │ ├── useSettings.ts # App settings + │ │ ├── useTerminal.ts # Terminal I/O, resize, session events + │ │ ├── useUpdates.ts # App update checking + │ │ └── useVoice.ts # Voice mode audio capture │ ├── lib/ - │ │ ├── types.ts # TypeScript interfaces matching Rust models - │ │ ├── tauri-commands.ts # Typed invoke() wrappers - │ │ └── constants.ts # App-wide constants + │ │ ├── types.ts # TypeScript interfaces matching Rust models + │ │ ├── tauri-commands.ts # Typed invoke() wrappers + │ │ └── constants.ts # App-wide constants │ └── components/ - │ ├── layout/ # Sidebar, TopBar, StatusBar - │ ├── projects/ # ProjectList, ProjectCard, AddProjectDialog - │ ├── terminal/ # TerminalView (xterm.js), TerminalTabs - │ ├── settings/ # ApiKeyInput, DockerSettings, AwsSettings - │ └── containers/ # SiblingContainers + │ ├── layout/ # Sidebar, TopBar, StatusBar + │ ├── mcp/ # McpPanel, McpServerCard + │ ├── projects/ # ProjectCard, ProjectList, AddProjectDialog, + │ │ # FileManagerModal, ContainerProgressModal, modals + │ ├── settings/ # SettingsPanel, DockerSettings, AwsSettings, + │ │ # UpdateDialog + │ └── terminal/ # TerminalView (xterm.js), TerminalTabs, UrlToast │ - └── src-tauri/ # Rust backend - ├── Cargo.toml # Rust dependencies - ├── tauri.conf.json # Tauri app configuration + └── src-tauri/ # Rust backend + ├── Cargo.toml # Rust dependencies + ├── tauri.conf.json # Tauri app configuration ├── capabilities/ - │ └── default.json # Tauri v2 permission grants + │ └── default.json # Tauri v2 permission grants └── src/ - ├── lib.rs # App builder, plugin + command registration - ├── main.rs # Entry point - ├── commands/ # Tauri command handlers - │ ├── docker_commands.rs - │ ├── project_commands.rs - │ ├── settings_commands.rs - │ └── terminal_commands.rs - ├── docker/ # Docker API layer - │ ├── client.rs # bollard singleton connection - │ ├── container.rs # Create, start, stop, remove, inspect - │ ├── exec.rs # PTY exec sessions with bidirectional streaming - │ ├── image.rs # Build from embedded Dockerfile, pull from registry - │ └── sibling.rs # List non-Triple-C containers - ├── models/ # Data structures - │ ├── project.rs # Project, AuthMode, BedrockConfig - │ └── container_config.rs - └── storage/ # Persistence + ├── lib.rs # App builder, plugin + command registration + ├── main.rs # Entry point + ├── logging.rs # Log configuration + ├── commands/ # Tauri command handlers + │ ├── docker_commands.rs # Docker status, image ops + │ ├── file_commands.rs # File manager (list/download/upload) + │ ├── mcp_commands.rs # MCP server CRUD + │ ├── project_commands.rs # Start/stop/rebuild containers + │ ├── settings_commands.rs # Settings CRUD + │ ├── terminal_commands.rs # Terminal I/O, resize + │ └── update_commands.rs # App update checking + ├── docker/ # Docker API layer + │ ├── client.rs # bollard singleton connection + │ ├── container.rs # Create, start, stop, remove, fingerprinting + │ ├── exec.rs # PTY exec sessions with bidirectional streaming + │ ├── image.rs # Build from Dockerfile, pull from registry + │ └── network.rs # Per-project bridge networks for MCP + ├── models/ # Data structures + │ ├── project.rs # Project, AuthMode, BedrockConfig + │ ├── mcp_server.rs # MCP server configuration + │ ├── app_settings.rs # Global settings (image source, AWS, etc.) + │ ├── container_config.rs # Image name resolution + │ └── update_info.rs # Update metadata + └── storage/ # Persistence ├── projects_store.rs # JSON file with atomic writes - ├── settings_store.rs # App settings + ├── mcp_store.rs # MCP server persistence + ├── settings_store.rs # App settings (Tauri plugin-store) └── secure.rs # OS keychain via keyring ``` diff --git a/app/src-tauri/src/commands/project_commands.rs b/app/src-tauri/src/commands/project_commands.rs index a857351..9b1c67f 100644 --- a/app/src-tauri/src/commands/project_commands.rs +++ b/app/src-tauri/src/commands/project_commands.rs @@ -386,6 +386,46 @@ pub async fn rebuild_project_container( start_project_container(project_id, app_handle, state).await } +/// Reconcile project statuses against actual Docker container state. +/// Called by the frontend after Docker is confirmed available. Projects +/// marked as Running whose containers are no longer running get reset +/// to Stopped. +#[tauri::command] +pub async fn reconcile_project_statuses( + state: State<'_, AppState>, +) -> Result, String> { + let projects = state.projects_store.list(); + + for project in &projects { + if project.status != ProjectStatus::Running && project.status != ProjectStatus::Error { + continue; + } + + let is_running = if let Some(ref container_id) = project.container_id { + docker::is_container_running(container_id).await.unwrap_or(false) + } else { + false + }; + + if is_running { + log::info!( + "Project '{}' ({}) container is still running — keeping Running status", + project.name, + project.id + ); + } else { + log::info!( + "Project '{}' ({}) container is not running — setting to Stopped", + project.name, + project.id + ); + let _ = state.projects_store.update_status(&project.id, ProjectStatus::Stopped); + } + } + + Ok(state.projects_store.list()) +} + fn default_docker_socket() -> String { if cfg!(target_os = "windows") { "//./pipe/docker_engine".to_string() diff --git a/app/src-tauri/src/docker/container.rs b/app/src-tauri/src/docker/container.rs index 51da4f6..4e09342 100644 --- a/app/src-tauri/src/docker/container.rs +++ b/app/src-tauri/src/docker/container.rs @@ -47,8 +47,8 @@ The `/workspace/mission-control/` directory contains **Flight Control** — an A ### How It Works - **Mission Control is a tool, not a project.** It provides skills and methodology for managing other projects. -- All Flight Control skills live in `/workspace/mission-control/.claude/skills/` -- The projects registry at `/workspace/mission-control/projects.md` lists all active projects +- All Flight Control skills are installed as personal skills in `~/.claude/skills/` and are automatically available as `/slash-commands` +- The methodology docs and project registry live in `/workspace/mission-control/` ### When to Use @@ -1031,6 +1031,16 @@ pub async fn get_container_info(project: &Project) -> Result Result { + let docker = get_docker()?; + match docker.inspect_container(container_id, None).await { + Ok(info) => Ok(info.state.and_then(|s| s.running).unwrap_or(false)), + Err(_) => Ok(false), + } +} + pub async fn list_sibling_containers() -> Result, String> { let docker = get_docker()?; diff --git a/app/src-tauri/src/lib.rs b/app/src-tauri/src/lib.rs index 4844975..e98d66c 100644 --- a/app/src-tauri/src/lib.rs +++ b/app/src-tauri/src/lib.rs @@ -88,6 +88,7 @@ pub fn run() { commands::project_commands::start_project_container, commands::project_commands::stop_project_container, commands::project_commands::rebuild_project_container, + commands::project_commands::reconcile_project_statuses, // Settings commands::settings_commands::get_settings, commands::settings_commands::update_settings, diff --git a/app/src-tauri/src/storage/projects_store.rs b/app/src-tauri/src/storage/projects_store.rs index 5f930e4..6028e70 100644 --- a/app/src-tauri/src/storage/projects_store.rs +++ b/app/src-tauri/src/storage/projects_store.rs @@ -72,6 +72,8 @@ impl ProjectsStore { // Reconcile stale transient statuses: on a cold app start no Docker // operations can be in flight, so Starting/Stopping are always stale. + // Running/Error are left as-is and reconciled against Docker later + // via the reconcile_project_statuses command. let mut projects = projects; let mut needs_save = needs_save; for p in projects.iter_mut() { diff --git a/app/src/App.tsx b/app/src/App.tsx index 6c9584f..a06b551 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -10,6 +10,7 @@ import { useProjects } from "./hooks/useProjects"; import { useMcpServers } from "./hooks/useMcpServers"; import { useUpdates } from "./hooks/useUpdates"; import { useAppState } from "./store/appState"; +import { reconcileProjectStatuses } from "./lib/tauri-commands"; export default function App() { const { checkDocker, checkImage, startDockerPolling } = useDocker(); @@ -17,8 +18,8 @@ export default function App() { const { refresh } = useProjects(); const { refresh: refreshMcp } = useMcpServers(); const { loadVersion, checkForUpdates, startPeriodicCheck } = useUpdates(); - const { sessions, activeSessionId } = useAppState( - useShallow(s => ({ sessions: s.sessions, activeSessionId: s.activeSessionId })) + const { sessions, activeSessionId, setProjects } = useAppState( + useShallow(s => ({ sessions: s.sessions, activeSessionId: s.activeSessionId, setProjects: s.setProjects })) ); // Initialize on mount @@ -28,6 +29,14 @@ export default function App() { checkDocker().then((available) => { if (available) { checkImage(); + // Reconcile project statuses against actual Docker container state, + // then refresh the project list so the UI reflects reality. + reconcileProjectStatuses().then((projects) => { + setProjects(projects); + }).catch(() => { + // If reconciliation fails (e.g. Docker hiccup), just load from store + refresh(); + }); } else { stopPolling = startDockerPolling(); } diff --git a/app/src/lib/tauri-commands.ts b/app/src/lib/tauri-commands.ts index 5bc4503..9671477 100644 --- a/app/src/lib/tauri-commands.ts +++ b/app/src/lib/tauri-commands.ts @@ -24,6 +24,8 @@ export const stopProjectContainer = (projectId: string) => invoke("stop_project_container", { projectId }); export const rebuildProjectContainer = (projectId: string) => invoke("rebuild_project_container", { projectId }); +export const reconcileProjectStatuses = () => + invoke("reconcile_project_statuses"); // Settings export const getSettings = () => invoke("get_settings"); diff --git a/container/entrypoint.sh b/container/entrypoint.sh index f47919f..acb7a20 100644 --- a/container/entrypoint.sh +++ b/container/entrypoint.sh @@ -131,6 +131,15 @@ if [ "$MISSION_CONTROL_ENABLED" = "1" ]; then # Symlink into workspace so Claude sees it at /workspace/mission-control ln -sfn "$MC_HOME" "$MC_LINK" chown -h claude:claude "$MC_LINK" + + # Install skills to ~/.claude/skills/ so Claude Code discovers them automatically + if [ -d "$MC_HOME/.claude/skills" ]; then + mkdir -p /home/claude/.claude/skills + cp -r "$MC_HOME/.claude/skills/"* /home/claude/.claude/skills/ 2>/dev/null + chown -R claude:claude /home/claude/.claude/skills + echo "entrypoint: mission-control skills installed to ~/.claude/skills/" + fi + unset MISSION_CONTROL_ENABLED fi