Getting started
botctl is a Rust CLI for keeping Claude Code, Codex CLI, OpenCode, Pi, and Antigravity sessions visible and controlled inside tmux.
Claude Code panes support guarded automation. Codex CLI panes are classified from captured terminal screens and can use YOLO for command permission approval. OpenCode panes are discovered passively from their SQLite database. Pi panes are discovered passively from ~/.pi/agent/sessions for dashboard visibility, recent-message context, state classification, and tmux window status. Antigravity (agy) panes are discovered passively for dashboard visibility, state classification, and pane-scrape last-message extraction.
For end-to-end operator flows, see workflows. For command details, see command reference.
Learn these first
Most users only need a few commands:
runtimefor the single local coordinator that owns live observation and automationdashboardfor a live control surface across Claude Code panes plus screen-detected Codex CLI and passive OpenCode/Pi/Antigravity visibilitypromptfor one-shot Claude prompts that still run through an inspectable tmux TUIyolofor centralized babysitting policy and recoveryservefor a runtime-backed observation stream and HTTP facademcpfor persistent Claude sessions exposed through a small JSON-RPC tool APIlast-messagewhen you need the full latest assistant reply instead of the visible terminal excerpt
For direct blocker recovery, use the canonical names approve, reject, and dismiss-survey. The longer approve-permission and reject-permission names still work as compatibility aliases.
If you just want to get value quickly, install it, open dashboard, then try yolo, serve, prompt, or last-message. dashboard, yolo, and serve auto-start the managed runtime when needed. Everything else supports setup, diagnostics, prompt handoff, and lower-level recovery.
Requirements
tmuxclaudeonPATHfor Claude Code automationcodexpanes for Codex CLI screen classification and command permission approvalopencodepanes withOC | <session title>pane titles for passive OpenCode dashboard visibilityagyonPATH(optional) for Antigravity dashboard visibility; the state directory defaults to~/.gemini/antigravity-cliand can be overridden withANTIGRAVITY_STATE_DIRbotctlinstalled
Install
cargo install botctl
Verify the binary is on your PATH:
botctl --version
botctl help
Then open the dashboard, which starts the managed runtime automatically when needed:
botctl dashboard
First useful run
-
Open at least one Claude Code, Codex CLI, OpenCode, Pi, or Antigravity pane inside
tmux. -
Open the dashboard:
botctl dashboard -
Select a pane in the dashboard and press
Enterto jump to it. -
If the pane is a Claude Code or Codex pane blocked on a supported permission dialog, enable YOLO from the dashboard or target the pane directly:
botctl yolo --pane 0:6.0 -
If you need machine-readable observation for a tmux session, run:
botctl serve --session demo --format jsonl
Use botctl help <command> when you need terminal-local syntax.
For a one-shot prompt that launches Claude in a new tmux window, submits through the guarded TUI path, and prints only the assistant answer to stdout:
botctl prompt --text "Summarize this repo"
printf 'Say exactly hello' | botctl prompt --stdin --cwd /path/to/project
botctl prompt --text hi -- --model sonnet --name "Just testing"
By default, stdout contains only the assistant answer. prompt uses the shared botctl tmux session unless --session NAME overrides it, creates that session automatically when needed, creates one new window per run, and kills only that captured window after loading a fresh assistant answer. Failed prompt windows remain available for inspection. Pass --verbose to write launch/wait progress to stderr. Arguments after -- pass through to Claude.
The runtime owns live tmux observation, pane classification, yolo supervision, and guarded actions. dashboard, yolo, and serve connect to the same local socket at <state-dir>/runtime.sock, and in managed mode they auto-start a hidden tmux-backed runtime when one is not already running.
Managed versus unmanaged
- managed is the default for
dashboard,yolo, andserve - managed mode auto-starts a hidden runtime session when needed
- managed clients share runtime leases so the last managed listener can keep the runtime alive instead of losing it when an earlier client exits
--unmanageddisables auto-start and requires an existing runtime
Direct runtime commands:
botctl runtime
botctl runtime stop
botctl runtime --foreground
First session
Use this path when you want botctl to launch Claude Code for you.
-
Start a managed Claude session:
botctl start --session demo --cwd /path/to/project -
Check what
botctlsees:botctl listbotctl doctor --session demobotctl status --pane %19 -
Attach with tmux when you want the full terminal UI:
tmux attach -t demo
Use --plain on attach, list, status, and doctor when scripts need the line-oriented output contract.
For scripts, use JSON on one-shot inspection commands:
botctl list --json
botctl status --pane %19 --json
botctl doctor --session demo --json
Pane-targeted commands also accept tmux's explicit pane syntax:
botctl status --pane 0:2.3
If Claude is already running in tmux
If Claude was already started inside tmux, resolve and verify the pane explicitly before acting:
botctl list --all
botctl attach --pane %19
If you already know the tmux pane target, you can use it directly:
botctl attach --pane 0:2.3
Pane targets
Prefer raw tmux pane IDs such as %19 for automation. They identify one pane directly.
botctl also accepts tmux's explicit session:window.pane syntax, such as 0:2.3, when that target identifies exactly one pane. Session names are useful for setup and inspection, but guarded workflows are safest when you pass an explicit pane target.
Dashboard and YOLO
Open the cross-workspace dashboard TUI:
botctl dashboard
The dashboard uses one emoji per pane to show the current classified state at a glance. It also shows pane PID, process-tree average CPU, memory, and observed active Cook time so long-running or resource-heavy panes stay visible.
| Emoji | State | Meaning | Automation |
|---|---|---|---|
💤 | ChatReady | Ready for input | submit-prompt can run |
💬 | PromptEditing | Ready with unsubmitted input | manual submit or clear |
🤔 | UserQuestionPrompt | Waiting for an operator decision or follow-up | manual review |
⚙️ | BusyResponding | Still working | wait |
🔐 | PermissionDialog | Waiting on permission approval or rejection | approve, reject, or YOLO when safe |
❓ | PlanApprovalPrompt | Waiting on plan approval | manual review |
📁 | FolderTrustPrompt | Waiting on folder trust confirmation | approve sends raw Enter |
📝 | SurveyPrompt | Waiting on a feedback prompt | dismiss-survey or YOLO |
✏️ | ExternalEditorActive | Waiting on an external editor flow | manual review |
🧾 | DiffDialog | Waiting on a diff review choice | manual review |
❔ | Unknown | Not classified safely | inspect before acting |
OpenCode panes can appear in the same dashboard without terminal capture or keypress automation. They are included only when the tmux command is opencode, the pane title is OC | <session title>, and OpenCode's SQLite database has exactly one session row whose directory matches the pane cwd and whose title matches the stripped pane title. If OpenCode truncates the pane title with ..., botctl accepts that title as a prefix only when it is still unique within the same cwd. If that match is missing, duplicated, ambiguous, or unreadable, the pane is ignored rather than guessed. Resolved OpenCode panes show a bounded recent-message excerpt from SQLite message/part rows and can show PermissionDialog when a running file/glob tool targets an absolute path outside the session directory. OpenCode and Pi panes are navigable and contribute window-title emojis, but YOLO and recovery actions are not enabled for them.
Codex CLI panes can also appear in the same dashboard. They are included only after botctl captures a likely Codex pane and sees Codex screen text such as the OpenAI Codex header, Codex approval prompt language, or a /statusline with run-state. With that statusline enabled, Ready maps to ChatReady, while Working and Thinking map to BusyResponding. Codex panes are navigable and classified from their screen, and Codex YOLO can approve command permission dialogs by sending y for Yes, proceed. Broader recovery actions remain Claude-only.
Antigravity (agy) panes appear in the same dashboard when the pane command is agy and the secondary signal passes. The dashboard shows the ⚛ glyph, the current classified state (ChatReady/BusyResponding/AgyCommandPermissionPrompt/AgyFolderTrustPrompt/AgySettingsPersistPrompt/Unknown), and cook time derived from BusyResponding. Antigravity panes are navigable; YOLO command-permission auto-approve is opt-in per pane with botctl yolo start --pane <agy-pane> and fires only on the captured > 1. Yes default. Folder-trust and settings-persist prompts are detected separately but require manual review. Direct approve/reject/auto-unstick actions are not enabled for Antigravity panes.
Dump the latest assistant message
Use last-message when you need the full latest assistant text as Markdown instead of a bounded screen capture or dashboard excerpt:
botctl last-message --pane 0:4.1
botctl last-message --pane 0:4.1 --out last-agent-message.md
botctl last-message --pane 0:4.1 --out -
Without --out, botctl writes MESSAGE_<provider-session-id>.md in the current directory and prints:
Dumped last message of 951 lines out to file:
MESSAGE_91399d1d-79e5-48a2-b355-c8b751fe9333.md
Use --out - to stream the raw Markdown body to stdout instead of writing a file. The command supports Claude, Codex, OpenCode, Pi, and Antigravity transcript sources. Claude is resolved from ~/.claude/projects, Codex from ~/.codex/sessions, OpenCode from its SQLite message/part rows after the same pane title plus cwd resolution used by the dashboard, Pi from JSONL sessions under ~/.pi/agent/sessions (or PI_CODING_AGENT_SESSION_DIR), and Antigravity from pane-scrape extraction (requires three horizontal-rule lines to be visible in the pane scrollback: one above the last assistant turn, plus the two that bracket the live input box). It writes assistant-visible text only; tool calls and provider metadata are not included.
If you use tmux, this is the recommended way to launch the dashboard as a popup and keep it alive:
bind-key C-c display-popup -E -w 80% -h 40% botctl dashboard --persistent
That uses --persistent, which keeps the dashboard alive in a dedicated tmux-backed popup session:
botctl dashboard --persistent
When opened from tmux, the dashboard selects the row for the pane that launched the popup when that pane is visible. That makes the fast path prefix C-c, then y, to toggle YOLO for the current Claude pane.
Enable YOLO for one explicit pane:
botctl yolo --pane 0:6.0
Enable YOLO for all panes in one workspace:
botctl yolo --all --workspace .
Tail the shared runtime events for that yolo policy:
botctl yolo --pane 0:6.0 --follow
Observe versus serve
observeis bounded and returns a fixed snapshot/event sample.runtimeis the long-lived owner of observation and automation.serveis the runtime-backed facade for one tmux session.servedoes not run its own observer loop anymore.
If you are not sure which one you want, you probably want managed serve, not observe.
Run serve against one tmux session:
botctl serve --session demo
Watch one specific pane only:
botctl serve --session demo --pane %19
Machine-readable stream:
botctl serve --session demo --format jsonl
Safety rules
- Prefer explicit pane IDs for automation.
session:window.paneis also accepted when it identifies one pane exactly.- Never automate an ambiguous target.
- Claude ownership is validated before automation runs.
Unknownis a refusal state, not a guess.- Folder trust approval is special and uses raw
Enter. - Codex YOLO is limited to command permission dialogs.
- OpenCode support is passive dashboard and status visibility.
- Antigravity support is dashboard/status visibility plus opt-in YOLO command-permission auto-approve. Folder-trust, settings-persist, direct
approve/reject, andauto-unstickremain manual.
For blocker recovery, prompt handoff, and recovery examples, see workflows and prompt handoff.
Current limits
- live classification is still built around
capture-pane, with the central runtime using a best-effort merged stream model when that helps breakUnknownstates - the classifier is conservative and keyword-based
serveis a runtime-backed foreground facade, not the full daemon/SSE control plane yet- the HTTP surface is still polling-based; there is no SSE endpoint yet