This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Dev server runs via Vite with HMR at http://localhost:3434 (or --port to change). Prod server uses :3456.
bun run dev:serve # Start dev server on :3434
bun run dev:serve --port 3456 # Custom port
bun run dev:serve --host 0.0.0.0 # LAN accessHMR handles most changes automatically - no restart needed for Svelte components, stores, routes, or API endpoints. Only vite.config.ts changes require a restart.
After each change, check tmux pane 0:1.1 to verify HMR worked. If the server died, restart it. Do not start a new server if one is already running. If the pane reference is wrong, find the correct one and update this file.
bun src/cli.ts <command> # Run CLI directly from source (no build needed)
bun run build # Build CLI + SvelteKit web app
bun run build:cli # Build CLI only (TypeScript)
bun run build:web # Build SvelteKit web app only
bun run dev:serve # Vite dev server with HMR
bun run prod:restart # Kill, rebuild, and relaunch prod server on :3456 (detached)
bun test # Run vitest tests
bun run lint # ESLint check
bun run format # Prettier formattingUse bun src/cli.ts for development — bun runs TypeScript directly, no build step needed. Only build for production (bun dist/cli.js).
Run a single test file:
bun vitest run tests/db/sessions.test.tsReleases are fully automated. Do not npm publish manually.
npm version minor # bumps package.json + src/utils/version.ts, commits, tags vX.Y.Z
git push origin main
git push origin vX.Y.Z # tag push triggers .github/workflows/release.ymlThe workflow builds CLI + web, publishes to npm via OIDC trusted publishing (no token, no MFA — the trust anchor is christinoleo/claude-mux + release.yml registered on npmjs.com), and creates a GitHub release. Publish runs as npx -y npm@latest publish --access public --provenance because the runner's bundled npm is too old for OIDC.
After release, other machines update with: claude-mux update.
For machines that should run claude-mux as a long-lived service (e.g. engage):
claude-mux service install # ~/.config/systemd/user/claude-mux.service, enabled + started
sudo loginctl enable-linger $USER # one-time: survive logout, start on boot
journalctl --user -u claude-mux -f # logsupdate auto-detects how the server is running and restarts it accordingly: systemctl --user stop/start claude-mux.service for systemd-managed hosts, or nohup claude-mux serve for hosts started manually. Use --skip-restart to leave the server alone.
claude-mux has three main components:
The hook script (src/hooks/claude-mux-hook.ts) runs inside Claude Code's process. It receives events via stdin (SessionStart, UserPromptSubmit, PreToolUse, Stop, etc.) and writes state to per-session JSON files in ~/.claude-mux/sessions/.
The web server (web/) is built with SvelteKit and svelte-adapter-bun:
- File watcher (
src/server/watcher.ts): Polls JSON files for changes (500ms interval) - WebSocket channels: Real-time updates for sessions list and terminal output
- API routes: REST endpoints for session management, tmux control, folder browsing
- Hooks (
web/src/hooks.server.ts): WebSocket upgrade handling, session managers
Hooks are authoritative for all state transitions. Pane content polling only catches one edge case: when the user presses Escape to interrupt. The checkForInterruption() function in src/tmux/pane.ts detects "Interrupted" or "User declined" messages.
Claude Code events → stdin → hook script → JSON files (~/.claude-mux/sessions/)
↓
file watcher (500ms polling)
↓
WebSocket broadcast to clients
↓
Svelte stores → UI update
| State | Color | Description |
|---|---|---|
idle |
Yellow | Ready for new task |
busy |
Green | Working (thinking, tool use) |
waiting |
Red | Asking user a question |
permission |
Red | Needs permission to proceed |
claude-mux/
├── src/ # CLI/TUI/Hooks
│ ├── cli.ts # Entry point
│ ├── app.tsx # React Ink TUI
│ ├── commands/
│ │ ├── serve.ts # Web server command
│ │ └── tui.ts # TUI command
│ ├── db/ # JSON file operations
│ ├── hooks/ # Claude Code hooks
│ ├── server/
│ │ └── watcher.ts # File watcher
│ └── tmux/ # tmux integration
├── web/ # SvelteKit app
│ ├── src/
│ │ ├── hooks.server.ts # WebSocket handlers
│ │ ├── lib/stores/ # Svelte 5 runes stores
│ │ └── routes/ # Pages and API routes
│ └── svelte.config.js
└── dist/ # Build output
├── cli.js # CLI
└── web/ # SvelteKit server
src/cli.ts- Entry point, routes to subcommandssrc/hooks/claude-mux-hook.ts- Runs in Claude's process, writes JSONsrc/db/sessions-json.ts- Session CRUD operations on JSON filessrc/tmux/pane.ts-checkForInterruption()detects Escape interruptionssrc/server/watcher.ts- File watcher for session changesweb/src/hooks.server.ts- WebSocket handlers and session managersweb/src/lib/stores/sessions.svelte.ts- Reactive session store
Use shadcn-svelte components from $lib/components/ui/. Do not build custom UI components—add shadcn components instead.
- TUI auto-creates a
watchtmux session prefix + Wkeybinding jumps to watch session (set dynamically)- Pane targets use format:
session:window.pane(e.g., "main:1.0")