This commit is contained in:
Amrit Subramanian 2025-10-03 16:55:59 -04:00
parent c45ac9ea98
commit 7d7a545ef8
5 changed files with 143 additions and 3 deletions

97
README.md Normal file
View file

@ -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 <uuid>` for first-time Claude sessions
- **Reopened Sessions**: Automatically resume with `--resume <uuid>`
- **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

24
main.ts
View file

@ -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");

22
package-lock.json generated
View file

@ -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",

View file

@ -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"
}

View file

@ -17,6 +17,7 @@ interface PersistedSession {
config: SessionConfig;
worktreePath: string;
createdAt: number;
sessionUuid: string;
}
interface Session {