From 7d7a545ef8a2e76dfa65ba2bea05983bad633197 Mon Sep 17 00:00:00 2001 From: Amrit Subramanian Date: Fri, 3 Oct 2025 16:55:59 -0400 Subject: [PATCH] readme --- README.md | 97 +++++++++++++++++++++++++++++++++++++++++++++++ main.ts | 24 ++++++++++-- package-lock.json | 22 +++++++++++ package.json | 2 + renderer.ts | 1 + 5 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..523716d --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +# FleetCode + +A desktop terminal application for running multiple CLI coding agents simultaneously, each in isolated git worktrees. + +## Features + +- **Multiple Sessions**: Run multiple coding agent sessions (Claude, Codex) in parallel +- **Git Worktree Isolation**: Each session runs in its own git worktree, keeping work isolated +- **Persistent Sessions**: Sessions persist across app restarts with automatic resumption +- **Terminal Theming**: Choose from preset themes (macOS Light/Dark, Solarized Dark, Dracula, One Dark, GitHub Dark) +- **Setup Commands**: Configure shell commands to run before the coding agent starts +- **MCP Server Management**: Add and configure Model Context Protocol (MCP) servers +- **Session Management**: Rename, close, and delete sessions with automatic worktree cleanup + +## Prerequisites + +- Node.js 16+ +- Git +- Claude CLI (`npm install -g @anthropic-ai/claude-cli`) or Codex + +## Installation + +```bash +npm install +``` + +## Usage + +### Development + +```bash +npm run dev +``` + +### Production Build + +```bash +npm run build +npm start +``` + +## How It Works + +### Session Creation + +1. Select a project directory (must be a git repository) +2. Choose a parent branch for the worktree +3. Select your coding agent (Claude or Codex) +4. Optionally add setup commands (e.g., environment variables, source files) +5. FleetCode creates a new git worktree and spawns a terminal session + +### Session Management + +- **New Sessions**: Use `--session-id ` for first-time Claude sessions +- **Reopened Sessions**: Automatically resume with `--resume ` +- **Worktrees**: Each session gets its own isolated git worktree +- **Persistence**: Sessions are saved and can be reopened after closing the app + +### Terminal Settings + +Access settings via the gear icon (⚙️) in the sidebar: + +- **Font Family**: Choose from common monospace fonts +- **Font Size**: Adjust terminal text size +- **Theme**: Select from preset color themes +- **Cursor Blink**: Toggle cursor blinking + +### MCP Servers + +Configure Model Context Protocol servers for enhanced agent capabilities: + +- **stdio**: Direct process communication +- **SSE**: Server-sent events via HTTP + +## Project Structure + +``` +FleetCode/ +├── main.ts # Main Electron process (PTY management, git operations) +├── renderer.ts # Renderer process (UI, terminal instances) +├── index.html # Application layout +├── styles.css # Tailwind CSS styles +└── dist/ # Compiled output +``` + +## Technology Stack + +- **Electron**: Desktop application framework +- **xterm.js**: Terminal emulator +- **node-pty**: PTY (pseudo-terminal) spawning +- **simple-git**: Git operations (worktree management) +- **electron-store**: Persistent configuration storage +- **Tailwind CSS**: Styling + +## License + +ISC diff --git a/main.ts b/main.ts index 7838fe5..8bdd161 100644 --- a/main.ts +++ b/main.ts @@ -7,6 +7,7 @@ import { simpleGit } from "simple-git"; import Store from "electron-store"; import { exec } from "child_process"; import { promisify } from "util"; +import { v4 as uuidv4 } from "uuid"; const execAsync = promisify(exec); @@ -25,6 +26,7 @@ interface PersistedSession { config: SessionConfig; worktreePath: string; createdAt: number; + sessionUuid: string; } let mainWindow: BrowserWindow; @@ -182,6 +184,9 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => { // Create git worktree const worktreePath = await createWorktree(config.projectDir, config.parentBranch, sessionNumber); + // Generate UUID for this session + const sessionUuid = uuidv4(); + // Create persisted session metadata const persistedSession: PersistedSession = { id: sessionId, @@ -190,6 +195,7 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => { config, worktreePath, createdAt: Date.now(), + sessionUuid, }; // Save to store @@ -235,7 +241,11 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => { // Auto-run the selected coding agent if (config.codingAgent === "claude") { - const claudeCmd = config.skipPermissions ? "claude --dangerously-skip-permissions\r" : "claude\r"; + // New session always uses --session-id + const sessionFlag = `--session-id ${sessionUuid}`; + const skipPermissionsFlag = config.skipPermissions ? "--dangerously-skip-permissions" : ""; + const flags = [sessionFlag, skipPermissionsFlag].filter(f => f).join(" "); + const claudeCmd = `claude ${flags}\r`; ptyProcess.write(claudeCmd); } else if (config.codingAgent === "codex") { ptyProcess.write("codex\r"); @@ -261,7 +271,11 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => { // Auto-run the selected coding agent if (config.codingAgent === "claude") { - const claudeCmd = config.skipPermissions ? "claude --dangerously-skip-permissions\r" : "claude\r"; + // New session always uses --session-id + const sessionFlag = `--session-id ${sessionUuid}`; + const skipPermissionsFlag = config.skipPermissions ? "--dangerously-skip-permissions" : ""; + const flags = [sessionFlag, skipPermissionsFlag].filter(f => f).join(" "); + const claudeCmd = `claude ${flags}\r`; ptyProcess.write(claudeCmd); } else if (config.codingAgent === "codex") { ptyProcess.write("codex\r"); @@ -350,7 +364,11 @@ ipcMain.on("reopen-session", (event, sessionId: string) => { } if (session.config.codingAgent === "claude") { - const claudeCmd = session.config.skipPermissions ? "claude --dangerously-skip-permissions\r" : "claude\r"; + // Reopened session always uses --resume + const sessionFlag = `--resume ${session.sessionUuid}`; + const skipPermissionsFlag = session.config.skipPermissions ? "--dangerously-skip-permissions" : ""; + const flags = [sessionFlag, skipPermissionsFlag].filter(f => f).join(" "); + const claudeCmd = `claude ${flags}\r`; ptyProcess.write(claudeCmd); } else if (session.config.codingAgent === "codex") { ptyProcess.write("codex\r"); diff --git a/package-lock.json b/package-lock.json index a54b4bf..c87a0a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,14 @@ "electron-store": "^11.0.0", "node-pty": "^1.0.0", "simple-git": "^3.28.0", + "uuid": "^13.0.0", "xterm": "^5.3.0", "xterm-addon-fit": "^0.8.0" }, "devDependencies": { "@electron/rebuild": "^4.0.1", "@types/node": "^24.6.2", + "@types/uuid": "^10.0.0", "autoprefixer": "^10.4.21", "electron": "^38.2.0", "electron-rebuild": "^3.2.9", @@ -1017,6 +1019,13 @@ "@types/node": "*" } }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -5038,6 +5047,19 @@ "dev": true, "license": "MIT" }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/package.json b/package.json index fddd35c..3528a2f 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "devDependencies": { "@electron/rebuild": "^4.0.1", "@types/node": "^24.6.2", + "@types/uuid": "^10.0.0", "autoprefixer": "^10.4.21", "electron": "^38.2.0", "electron-rebuild": "^3.2.9", @@ -28,6 +29,7 @@ "electron-store": "^11.0.0", "node-pty": "^1.0.0", "simple-git": "^3.28.0", + "uuid": "^13.0.0", "xterm": "^5.3.0", "xterm-addon-fit": "^0.8.0" } diff --git a/renderer.ts b/renderer.ts index 8d9b76e..7116bcc 100644 --- a/renderer.ts +++ b/renderer.ts @@ -17,6 +17,7 @@ interface PersistedSession { config: SessionConfig; worktreePath: string; createdAt: number; + sessionUuid: string; } interface Session {