multi-code
CLAUDE.md1 starsChanged 8 days agoMITView on GitHub
Multi-Code is an Electron desktop app that manages multiple Claude Code CLI instances. It spawns real claude CLI processes via node-pty and renders their output in xterm.js terminals. The UI is compact and information-dense: a sidebar of sessions, a terminal, and a toolbox.
- Installs packages
- Commits and pushes
The file
We split this CLAUDE.md into the rules it gives an agent
6 rules. About 0 are common, 4 are distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 52
When building to release/ship a version (not a routine dev build):
- Very specific: line 54
Run pnpm build and confirm it succeeds.
- Very specific: line 58
Commit the bump on its own (e.g.
- Distinctive: line 71
All project files (code, comments, commit messages) in English
- Distinctive: line 72
No unnecessary abstractions — keep it simple and direct
- Distinctive: line 73
Prefer editing existing files over creating new ones
≈ “Common” is an estimate: at least 1 in 5 of the 26,980 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
Project Overview
Multi-Code is an Electron desktop app that manages multiple Claude Code CLI instances. It spawns real claude CLI processes via node-pty and renders their output in xterm.js terminals. The UI is compact and information-dense: a sidebar of sessions, a terminal, and a toolbox.
Architecture
- No abstraction layers — The app spawns the
claudeCLI directly. No SDK, no bridge, no hooks middleware. - Report-only hooks and plugins are allowed — Passed from
userDatato the processes Multi-Code spawns (Claude:--settings; OpenCode: a TUI plugin named inOPENCODE_CLI_CONFIG_CONTENT), never written to the user's own config. They report state and never block or change a call, so plainclaudeoropencodeoutside Multi-Code is unaffected. One exception (decisions.md, 2026-10-16): OpenCode dialogs answered from the phone or the secretary go through OpenCode's own API, by request id, as the builder would answer by keypress. - OpenCode 2 only — OpenCode runs every session in one shared background service per user; Multi-Code's terminals attach to it as VS Code's do (
opencode --session <id>; tabs as the user's own settings say). Multi-Code puts nothing into that service, starts it only when none is running (with the builder's login-shell environment), and never restarts or stops it. 1.x is refused with the upgrade command. Epicopencode-v2(docs/specs/opencode-v2/prd.md). - Phone link over the tailnet only — The phone app and its socket are served to the tailnet only through
tailscale serve(HTTPS at the Mac's tailnet name); the port binds to 127.0.0.1. Multi-Code runstailscale serveonly when the builder presses "Set up HTTPS", never over another 443 config, and never removes it. Push goes from the Mac straight to Apple's Web Push service, no relay. Epicmobile-v2(docs/specs/mobile-v2/prd.md). - Monorepo — pnpm workspace with
workspace/app/as the main package. - Electron — Main process manages PTY lifecycle; renderer shows terminals in React.
- Session monitoring — Claude instances report finished / needs-you through report-only hooks (
--settings <userData>/alert-settings.json) posting to the local/alertendpoint;backends/claudeHooks.tsturns them into activity. OpenCode instances' state comes from the shared service's event stream (backends/opencodeService.ts), and the session each terminal shows from Multi-Code's TUI plugin (backends/opencodePlugin.ts, posting to/alert);backends/opencodeAttention.tsturns both into activity. Epicsattention-alertsandopencode-v2. The session JSONL and OpenCode 2's database (session_v2,session_message, read-only) are still read for transcripts and context usage, never for state.
Key Paths
workspace/app/src/main/— Electron main process (process-manager, session-watcher, IPC, store)workspace/app/src/renderer/— React UI (App.tsx, components/, hooks/, audio/, styles/)workspace/app/src/shared/types.ts— Shared TypeScript interfacesworkspace/app/src/shared/remote-protocol.ts— Phone link wire protocol (v3), the phone ↔ desktop contractworkspace/app/src/renderer-mobile/— The phone web app (served to the iPhone, built byrspack.mobile.config.ts)workspace/app/scripts/mobile-harness/— Phone dev harness: the real server with scripted sessions, Playwright WebKit screenshots at iPhone size (pnpm harness,pnpm harness:shot)docs/— Business docs, specs, PRD, kanban
Tech Stack & Conventions
- TypeScript strict mode, target ES2024, ESM (
"type": "module") - React 19 for renderer
- xterm.js + FitAddon for terminal rendering
- node-pty for process spawning
- rspack for bundling the renderer
- oxlint + eslint for linting
- vitest for testing
- pnpm as package manager
Common Commands
pnpm install # Install dependencies
pnpm start # Build + launch Electron app
pnpm build # Build renderer (rspack) + main (tsc)
pnpm lint # oxlint && eslint --cache
pnpm lint:fix # Auto-fix lint issues
pnpm type # Type-check (tsc --noEmit)
pnpm test # Run vitestRelease Process
When building to release/ship a version (not a routine dev build):
- Run
pnpm buildand confirm it succeeds. - Bump the version in
workspace/app/package.json(the only place a version lives — the root package.json is private and has none). Ask which part to bump (patch/minor/major); default suggestion is patch. - Commit the bump on its own (e.g.
chore: bump version to X.Y.Z). - Bring
README.mdandREADME.zh-CN.mdup to date with what the release changes. - Build the installer with
pnpm distinworkspace/app: it writesrelease/Multi-Code-X.Y.Z-arm64.dmg(and its.blockmap). Then delete the intermediaterelease/mac-arm64/folder. Never shippnpm run pack's output: it is only the.app(--dir), for testing the Dock badge. git push origin master.
Routine dev builds during development do NOT bump the version — only do this in a release context so the number isn't pushed on every build.
Code Style
- All project files (code, comments, commit messages) in English
- No unnecessary abstractions — keep it simple and direct
- Prefer editing existing files over creating new ones
- UI should stay compact and information-dense
Data Storage
Everything persisted lives in Electron's app.getPath("userData"), which on macOS
is ~/Library/Application Support/multi-code/ for a dev build, and …/Multi-Code/
(the productName) for the installed app. macOS's default filesystem is
case-insensitive, so those are the same folder (observed 2026-10-07): running both at
once, they read the same contacts and overwrite each other's spawn files. Never hardcode these paths; call app.getPath("userData").
contacts.json— the instance list. Each entry: id, cwd (project directory), alias (display name), backend, sessionId (the session its next start resumes, so two contacts on one project stay apart)settings.json— theme, phone-link enabled, Secretary Mode, speech server address, each project's default drop folderremote-identity.json,remote-devices.json— phone-link keys and paired devices (each with its token, its Web Push subscription and its last push error); 0600remote-push.json— the VAPID keypair; its public half goes to each phone inauth-ok; 0600; generated once and kept (a missing or corrupt file gets a new pair); never logged, never sent to the renderer. Losing it drops every phone's subscription; each re-subscribes on its next launchmanager/— the working folder of the manager agent, retired 2026-11-01. Never touched: the builder kept their own files in it. Its contact is dropped on load, and the oldmanager-mcp.json,manager-settings.json,manager-hook.curlare removed on shutdown with the other spawn filesalert-settings.json,alert-hook.curl— every Claude instance's--settings(alert hooks only) and the curl config holding the/alerttoken; 0600, removed on shutdownopencode/tui/tui.js,opencode/alert.json— the report-only TUI plugin every OpenCode terminal Multi-Code starts loads (named in itsOPENCODE_CLI_CONFIG_CONTENT) and the/alertendpoint and token it reads (path inMULTICODE_ALERT_FILE); 0600, removed on shutdownspeech-key— the secretary's speech-server bearer key, alone in its file at 0600; never insettings.json, never logged, never sent to the renderer (it learns only whether one is set); kept across runs
IPC Pattern
Renderer communicates with main process via Electron's contextBridge:
- Renderer calls
window.electronAPI.someMethod() - Preload bridges to
ipcRenderer.invoke("channel-name", ...args) - Main process handler registered in
ipc-handlers.tsresponds - Events from main to renderer via
webContents.send()
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

