Zpit (zac + cockpit) — a TUI-based AI development cockpit that orchestrates Claude Code agents across multiple projects. Zpit acts as a dispatch center — it selects projects, launches agents in separate terminal windows, monitors their progress, coordinates the full issue lifecycle from requirement clarification to PR, and enables real-time cross-agent communication via a built-in HTTP broker + MCP channel.
Key principle: Claude Code runs in independent terminal windows. Zpit never wraps or embeds it — it monitors via session logs, coordinates via issue trackers, and bridges agents via a local channel broker.
Four independently scrollable dock panels — Projects, Active Terminals, Loop Engine (left column, stacked), Hotkeys (right column). Catppuccin Mocha palette with a single-column ▎ mauve bar marking the focused panel.
Zpit v0.1 04/19 15:04 Windows Terminal
▎ PROJECTS 7 HOTKEYS
────── ──────
› AI Inspection Cleaning Demo ⚪ not deployed [Enter] Launch Claude Code
machine │ wpf, ethercat, basler [c] Clarify requirement
[l] Loop auto-implement
ENR DUC ⚪ not deployed [r] Review changes
machine │ wpf, secsgem [f] Efficiency agent
[s] Status overview
DisplayProfileManager ⚪ not deployed [o] Open project folder
desktop │ wpf, nlog [i] Open Issue Tracker
[p] Open PR
Zpit 🟢 deployed [u] Undeploy agents
terminal │ go, bubbletea [d] Redeploy all agents
[m] Channel communication
Zplex ⚪ not deployed [g] Git status
desktop │ go, electron, xterm [G] Open lazygit
[U] Run claude update
Zacfuse 🟢 deployed [a] Add project
web │ astro, typescript, docs [e] Edit config
[x] Close Terminal
[Tab] Switch Panel
ACTIVE TERMINALS 1 [?] Help
────── [q] Quit
›[1] Zpit │ 🟡 Waiting for input 00:15
Q: Commit 2198be6 pushed to `origin/dev`, working tre
Press ? for help, q to quit
Tab cycles focus between Projects → Active Terminals (when any) → Loop Engine (when any slot exists); Hotkeys stays docked to the right as read-only reference. ↑↓/PgUp/PgDn scroll the focused panel only; mouse wheel scrolls whichever panel the cursor hovers. Below ~40 cols the left/right widths auto-shrink, but Hotkeys never drops below the other panels.
Zpit v0.1 03/27 14:04 Windows Terminal
Issues — AI Inspection Cleaning Demo
────────────────────────────────────────────────────────────
› #25 [pending] feat(manual-control): Safety Interlock + Soft Limit + Reset Zero
[y] Confirm (pending→todo) [i] Open in browser [Esc] Back
You (TUI) Claude Code Agents
│
├─ [c] Clarify ──────────► Clarifier agent (new terminal)
│ requirement asks questions, creates structured issue
│ (press multiple times) agents auto-discover via agent_type, enter Facilitator/Advisor meeting mode
│
├─ [l] Loop ──────────────► Coding agent (worktree + new terminal)
│ auto-implement implements, commits, opens PR
│ │
│ └─ PR appears ───► Reviewer agent (same worktree)
│ │ checks AC, writes review report
│ ├─ PASS ────► waits for human merge
│ └─ NEEDS CHANGES → auto-retry coding (up to N rounds)
│
├─ [s] Status ────────────► shows issue list from tracker
├─ [r] Review ────────────► launches reviewer on demand
├─ [f] Efficiency ────────► lightweight agent (no hooks, no tracker, self-review)
├─ [d] Redeploy ──────────► undeploy + re-write all agents/hooks/docs (no launch)
└─ [Enter] ───────────────► launches Claude Code directly
- Multi-project dashboard — switch between projects with arrow keys, mouse scroll support
- Loop engine — fully automated: poll todo issues → create worktree → coding agent → reviewer → PR merge → cleanup
- Task decomposition — when an Issue Spec contains
## TASKS, the coding agent delegates totask-runnersubagents (sequential, or parallel batches with per-subagentisolation: "worktree") - Agent monitoring — real-time status via session log parsing (Working / Waiting / Permission / Ended), auto-detects running sessions on startup, survives
/resumesession switches - Notifications — Windows Toast + sound when an agent needs your input or awaits tool permission
- Issue tracker integration — Forgejo/Gitea and GitHub via REST API + MCP
- Cross-agent channel — real-time agent-to-agent communication via HTTP broker + MCP; supports same-project, cross-project, and global broadcast messaging
- Meeting mode — multiple clarifier agents auto-discover via
agent_typetracking, assign Facilitator/Advisor roles, and converge to a structured issue through coordinated channel communication - 5-layer safety system — agent-guidelines.md, allowed tools, PreToolUse hooks, git worktree isolation, human PR review
- Per-issue branch control — clarifier asks target branch, coding agent enforces it
- Auto-retry — reviewer judges NEEDS CHANGES → coding agent auto-fixes → re-review (configurable rounds)
- i18n — TUI chrome localized to English or Traditional Chinese (zh-TW) via
locale.T(); all agent output (Issue Specs, commits, PR bodies, channel messages) is always English regardless of TUI locale, for token efficiency - Per-role model selection —
[agent_models]lets you choose a model per agent role (defaults to Opus across all roles with 1M context, tuned for single-round coding→review accuracy); override any value in config - SSH remote access —
zpit serveruns a headless SSH daemon (Wish), multiple clients share one dashboard with real-time state sync;auto_servemode starts the server automatically when runningzpit, enabling seamless mobile access without workflow interruption - Desktop agent —
[w]launches a global Claude Code session that drives the OS (mouse, keyboard, screenshots, accessibility actions) through a policy-enforced MCP proxy. Backed byzpit-desktop-mcp, our fork of@zavora-ai/computer-use-mcp. Single-instance, macOS + Windows only.
When you work across multiple machines (laptop at home, desktop at the office) and want to continue a long Claude Code conversation on the other machine, the session JSONL file is the unit of replay — but Claude Code's ~/.claude/projects/<encoded-cwd>/ directory uses an OS-specific encoded folder name and embeds the absolute project path inside every session line. A naive file copy is invisible to claude --resume on the other side.
Zpit's [h] History view bridges this. From the main dock press h to open the Session Browser:
- Top level: every encoded folder under
~/.claude/projects/. Each row shows session count, total size, last-modified time, and a 🟢 marker when at least one session is currently alive on this machine. - Drill into a folder: every
*.jsonlsession with multi-select checkboxes. 🧩 marks sessions that have a sibling<id>/subagents/directory; 🟢 marks live sessions.
In the session list, select one or more sessions with Space (or [a] to toggle all). Press [e] to export. Zpit warns if any selected session is currently active (informational — you can still proceed) and writes a zip bundle containing:
- One
<session-id>.jsonlper selected session at the zip root. - One
<session-id>/subagents/subtree per selected session that has subagents on disk (preserved verbatim). - A
manifest.jsondescribing the source OS and absolute project path so the importer can rewrite paths. - Optionally a
memory/subtree (opt-in via the[ ] Include memory/checkbox at export time — off by default to avoid leaking project-private notes).
Default output path: ~/.zpit/exports/<folder-name>-<YYYYMMDD-HHMMSS>.zip.
You can also press [E] (capital) on a folder row in the top-level view to export every session in that folder in one shot.
From the top-level folder list, press [i] (or select the [+] Import bundle... row + Enter) to open the import wizard. Steps:
- Bundle path entry — type the path to a
.zipproduced by the exporter above. - Manifest preview — Zpit reads
manifest.jsonand shows source OS, source path, session count, memory inclusion. Per-session checkboxes let you deselect individual sessions. - Destination path — type the absolute path of the destination project on this machine.
- Final preview — Zpit shows the resolved
~/.claude/projects/<dest-encoded>/and the action plan. Confirm to run. - Run — Zpit rewrites
cwdfields in each session JSONL line to the destination path (only the JSONcwdfield, never literal path text in chat content), copies subagents verbatim, and optionally writesmemory/if the bundle included it.
If the destination already has a session with the same ID, you get a 3-button collision modal: Overwrite, Skip this session, or Cancel entire import. The same prompt applies to the memory/ directory.
After import, claude --resume <session-id> on this machine sees the new file.
The zip bundle contains:
<bundle.zip>
├── manifest.json # format_version, source_os, source_cwd,
│ # source_encoded_cwd, sessions[], exported_at,
│ # include_memory
├── <session-id>.jsonl # one per session (lines streamed)
├── <session-id>/subagents/... # one per session with subagents (verbatim)
└── memory/... # only when include_memory=true
source_cwd is sourced from any session line's cwd field when available; only when no line has one does the exporter fall back to a lossy reverse-derivation of the encoded folder name.
- Go 1.26+
- Claude Code CLI installed and authenticated
- Windows Terminal (Windows) or tmux (Linux/WSL)
- A Forgejo/Gitea or GitHub issue tracker
# Build
go build -o zpit .
# First run — creates config template at ~/.zpit/config.toml
./zpit
# Edit the config with your projects and tracker tokens, then:
./zpit
# SSH server mode (remote access)
./zpit serve # Start headless SSH daemon (default port 2200)
./zpit connect # SSH connect to local server
# Or enable auto_serve in config — then just "./zpit" starts
# the SSH server automatically and connects to it.
# You can SSH in from your phone at any time.Config lives at ~/.zpit/config.toml. Override with ZPIT_CONFIG env var.
language = "en" # en | zh-TW
broker_port = 17731 # HTTP broker port for cross-agent channel
# zpit_bin = "/usr/local/bin/zpit" # explicit binary path for .mcp.json generation
[terminal]
windows_mode = "new_tab" # new_tab | new_window
tmux_mode = "new_window" # new_window | new_pane
# windows_terminal_profile = "PowerShell 7" # WT profile name for -p flag
[notification]
tui_alert = true
windows_toast = true
sound = true
# sound_file = "D:/sounds/notify.mp3" # custom notification sound (WAV/MP3/M4A/OGG)
re_remind_minutes = 2
[worktree]
base_dir_windows = "D:/worktrees"
base_dir_wsl = "/mnt/d/worktrees"
max_per_project = 5
poll_seconds = 10 # todo issue polling interval
pr_poll_seconds = 10 # PR merge polling interval (only used when auto_merge = false)
max_review_rounds = 3 # auto-retry rounds before needs-human
# dir_format = "{project_id}/{issue_id}--{slug}"
# auto_cleanup = false
# Per-role model selection — passed to Claude Code via --model at launch.
# Aliases (opus/sonnet/haiku) resolve per provider; append [1m] to opt
# into the 1M-context tier. Pin to a full ID (e.g. claude-opus-4-7[1m])
# if you need cross-provider consistency.
[agent_models]
clarifier = "opus[1m]" # requirement clarification — deepest reasoning (1M context)
coding = "opus[1m]" # feature implementation (1M context)
reviewer = "opus[1m]" # PR review (1M context)
task_runner = "opus[1m]" # advisory — subagents inherit the coding session's model
efficiency = "opus[1m]" # efficiency-review agent (manual [f]) — deep reasoning
# Tracker providers — token read from env var, never stored in config
[providers.tracker.my-forgejo]
type = "forgejo_issues"
url = "https://your-forgejo.example.com"
token_env = "FORGEJO_TOKEN"
# Git providers (optional — for Forgejo/Gitea PR API)
# [providers.git.my-forgejo]
# type = "forgejo"
# url = "https://your-forgejo.example.com"
# token_env = "FORGEJO_TOKEN"
# Projects
[[projects]]
name = "My Project"
id = "my-project"
profile = "machine" # display tag: machine | desktop | web | android | terminal (for TUI icon)
hook_mode = "strict" # strict | standard | relaxed
log_policy = "standard" # strict | standard | minimal — agent logging strictness
tracker = "my-forgejo"
# tracker_project = "My_Project" # tracker project name if different from repo
# git = "my-forgejo" # git provider for PR operations
repo = "org/repo"
base_branch = "dev"
channel_enabled = false # enable cross-agent channel communication
channel_listen = [] # subscribe to other projects' events, e.g. ["_global", "other-proj"]
# auto_merge = false # when true, Zpit calls the tracker merge API after ai-review PASS (opt-in)
# merge_method = "squash" # squash | merge | rebase (used when auto_merge = true)
tags = ["go"]
[projects.path]
windows = "D:/Projects/my-project"
wsl = "/mnt/d/Projects/my-project"
# SSH server (optional — for remote TUI access)
# [ssh]
# port = 2200
# host = "0.0.0.0"
# host_key_path = "~/.zpit/ssh/host_ed25519"
# password_env = "ZPIT_SSH_PASSWORD"
# authorized_keys_path = "~/.ssh/authorized_keys"
# auto_serve = false # when true, "zpit" auto-starts SSH server + connects| Key | Action |
|---|---|
Enter |
Launch Claude Code in new terminal |
c |
Clarify — open clarifier agent to create structured issue |
l |
Loop — toggle automated coding + review cycle |
r |
Review — launch reviewer agent |
f |
Efficiency — lightweight agent (no hooks, no tracker, self-review) |
s |
Status — view issue list from tracker |
o |
Open project folder |
i |
Open issue tracker in browser |
p |
Open pull request in browser (in Loop Slot focus: the slot's PR; falls back to /pulls?head=<branch> if not yet open) |
u |
Undeploy — remove deployed agents, docs, hooks |
d |
Redeploy — undeploy then re-write all 4 agents + hooks + docs (no Claude launch) |
m |
Channel — view cross-agent communication events |
g |
Git Status — view branches (local + remote-only) and commit graph; [f] fetch, [p] pull (--ff-only) |
G |
Open lazygit in new terminal (project root; in Loop Slot focus: slot's worktree) |
U |
Run claude update in new terminal (stays open to show result) |
a |
Add project (coming soon) |
e |
Edit config — sub-menu: toggle channel, edit channel_listen, open in $EDITOR |
w |
Window — launch desktop agent (macOS + Windows only; single instance across all clients) |
x |
Close Terminal — force-close selected terminal (when Terminals panel focused) |
Tab |
Switch focus between panels (Projects, Terminals, Loop Slots) |
? |
Help |
q |
Quit |
The loop engine automates the full coding cycle:
- Poll tracker for
todoissues (configurable interval) - Create worktree — isolated git worktree from
base_branch - Launch coding agent — writes implementation, commits, opens PR, sets
reviewlabel - Detect completion — polls issue labels for
review(agents don't need to exit) - Launch reviewer — checks acceptance criteria, writes review report, sets verdict label
- Detect verdict — polls issue labels for
ai-review(PASS → wait for human merge) orneeds-changes(auto-retry up tomax_review_rounds) - Cleanup — after PR merge, removes worktree and branch
Multiple issues run in parallel, limited by max_per_project.
Zpit enforces 5 layers of safety to prevent agents from causing damage:
| Layer | Mechanism | Scope |
|---|---|---|
| 1 | agent-guidelines.md behavioral rules | Soft — agent reads on startup |
| 2 | --allowedTools per agent role |
Medium — Claude Code enforced |
| 3 | PreToolUse hooks | Hard — enforced even with --bypass-all-permissions |
| 4 | Git worktree isolation | Physical — agents can't touch main repo |
| 5 | Final merge gate (conditional) | When auto_merge = false (default): human PR review is the final gate. When auto_merge = true: the AI reviewer's ai-review PASS label replaces the human gate and triggers the tracker's merge API. Only enable auto_merge when you trust the reviewer model quality on your repo. |
PreToolUse hooks:
path-guard.sh— confines Write/Edit to worktree directorybash-firewall.sh— blocks destructive commands (rm -rf, curl|bash, force push, etc.)git-guard.sh— push whitelist (onlyfeat/*branches), blocks merge, rebase, branch-delete, force push
Notification hook:
notify-permission.sh— writes signal file when Claude Code needs tool permission approval; TUI detects and shows 🟠 status + toast notification
Hook strictness is per-project: strict (all hooks), standard (path-guard + git-guard), relaxed (git-guard only). Notification hook is always active in all modes.
The clarifier agent produces structured issues:
## CONTEXT
[Problem description with specific file names and behavior]
## APPROACH
[Selected solution + reasoning]
## ACCEPTANCE_CRITERIA
AC-1: [Specific, verifiable condition]
AC-2: ...
## SCOPE
[modify] path/to/file (reason)
[create] path/to/new-file (reason)
## CONSTRAINTS
[Hard limits]
## BRANCH
[Optional: PR target branch, defaults to project base_branch]
## TASKS
[Optional: Task decomposition — triggers subagent delegation]
T1: [task description] | [modify] path/to/file
T2: [task description] [P] | [modify] path/to/other # [P] = parallelizable
T3: [task description] [depends:T1] | [create] path/to/new
## COORDINATES_WITH
[Optional: Cross-agent coordination for parallel work]
#42: Brief description of related issue
## REFERENCES
[Optional: URLs, related files]go build ./... # Build
go test ./... # Run all tests
make test-hooks # Run hook tests (requires bash)
go run . # Local TUI (or auto-serve if ssh.auto_serve=true)
go run . serve # SSH server mode
go run . connect # SSH client shortcutLogs: ~/.zpit/logs/zpit-YYYY-MM-DD.log — daily rotation, 30-day retention.
Press [w] from the main view to launch the desktop agent — a standalone Claude Code session that controls the operating system (mouse, keyboard, screenshots, window/app management, accessibility actions). Unlike project-scoped agents, it is global (cwd = $HOME / %USERPROFILE%) and limited to one active instance across all connected TUI clients.
The desktop agent is backed by zpit-desktop-mcp — our fork of @zavora-ai/computer-use-mcp by James Karanja Maina / zavora.ai. The fork adds Windows AUMID launch support in open_application, a stdio-entrypoint fix for Windows backslash paths, and tracks the upstream @modelcontextprotocol/sdk Zod 4 bump. Many thanks to the upstream authors — none of this would exist without their original work.
Safety is enforced by zpit serve-desktop-proxy, a Go MCP proxy that sits between Claude Code and the npx zpit-desktop-mcp subprocess. Every JSON-RPC tools/call frame is intercepted, evaluated against ~/.zpit/desktop-policy.toml (auto-created on first run), and either forwarded or rejected with a structured error. Hard-blocked tools include run_script, filesystem, process_kill, registry, notification, and all virtual-desktop tools; deny_keys blocks OS-level escape hatches like win+r, ctrl+alt+del, and alt+f4. The conventional 5-layer hook stack does not apply to the desktop agent — the proxy is the safety layer.
Platform support: macOS and Windows. On Linux, [w] is a no-op (the upstream Rust NAPI module has no Linux backend).
See docs/architecture/desktop-agent.md for the full design rationale (why proxy over hooks, tool-by-tool allowlist justification, default deny_keys per platform).
See docs/architecture/ for the full architecture documents (split by topic, with an index).
Zpit is built on top of the following open source libraries:
| Library | Purpose | License |
|---|---|---|
| Bubble Tea | TUI framework | MIT |
| Bubbles | TUI components (list, text input, etc.) | MIT |
| Lip Gloss | TUI styling and layout | MIT |
| Huh | Form and confirm dialogs | MIT |
| Wish | SSH server for TUI remote access | MIT |
| BurntSushi/toml | TOML config parser | MIT |
| fsnotify | Filesystem watcher (session log monitoring) | BSD-3-Clause |
| go-colorful | Color math for terminal rendering | MIT |
| muesli/termenv | Terminal environment detection | MIT |
| rivo/uniseg | Unicode text segmentation | MIT |
| mattn/go-runewidth | Rune display width calculation | MIT |
| bubbletea-overlay | Overlay rendering for confirm dialogs | MIT |
| golang.org/x/sys, x/text | Go extended standard library | BSD-3-Clause |
All Charmbracelet libraries (bubbletea, bubbles, lipgloss, huh, wish, ssh) are copyright © Charmbracelet, Inc., licensed under the MIT License.
fsnotify and golang.org/x/* are BSD-3-Clause; their copyright notices are retained as required.
The [w] desktop agent is backed by zpit-desktop-mcp, our fork of @zavora-ai/computer-use-mcp (forked at v6.1.0). The original computer-use-mcp package — including the Rust NAPI native backend, the MCP server scaffolding, and the full macOS + Windows toolset — is the work of James Karanja Maina / zavora.ai, released under the MIT License. Zpit's fork adds Windows AUMID launch support, the stdio-entrypoint backslash fix, and the SDK Zod 4 bump; everything else is upstream. Sincere thanks to the zavora.ai team for making this layer of zpit possible.
| Package | Purpose | License |
|---|---|---|
zpit-desktop-mcp (zpit fork) |
Desktop-control MCP server for the [w] agent |
MIT |
@zavora-ai/computer-use-mcp (upstream) |
Original MCP server + Rust NAPI native backend | MIT |
MIT