tmux-ide — Claude Code Skill
SkillDev toolsTurn any project into a tmux-powered terminal IDE with a simple ide.yml
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the tmux-ide — Claude Code Skill skill
What this skill tells your AI
The instructions your AI receives, as published by wavyrai/tmux-ide in skill/SKILL.md and read by ahel’s review.
tmux-ide is a dock around tmux: one command adds a native chrome to any tmux
session — a fleet of tabs with live agent-status glyphs, ground-truth
working/blocked/done detection, notifications when an agent needs a human, and a
crash-proof restore. It's built around tmux (adopt is additive tmux config, no
wrapper process), and the whole UI is one keystroke away — one interaction
grammar, one theme file. .tmux-ide/workspace.yml is optional; adopt works on any session.
When to use
- User mentions tmux, a dock/status bar over sessions, an agent fleet, or session status
- User wants live working/blocked/done status across multiple agents or panes
- You are an agent and want to report your own status so the dock/fleet reflects it (the agent contract, below)
- Post-crash recovery — a tmux server died and the user wants their fleet + Claude conversations back
- User wants a git worktree (plus an adopted session) per branch
- User wants to set up a multi-pane dev workspace with
.tmux-ide/workspace.yml
The agent contract
This is the core of tmux-ide. Detection is two-layer, and an agent that reports its own state is the authoritative layer — the dock trusts it over any screen-scraping. If you are an agent running in a tmux pane, self-report by setting a pane-local tmux option:
tmux set-option -p @agent_state "<state>:$(date +%s)" # state = working | blocked | done | idle
The value is <state>:<unix-epoch>. A working/blocked report older than ~10
minutes is treated as stale (the detector falls back to Layer 2), so long-running
agents should re-stamp periodically. Two optional companions:
tmux set-option -p @agent_session_id "<id>" # your own session id — powers restore --resume-agents
tmux set-option -p @agent_hint claude # force which agent manifest Layer 2 uses for this pane
Display metadata — say WHAT you're doing and WHO you are, right in the fleet UI. Two more optional pane-local options:
tmux set-option -p @agent_status_text "refactoring auth" # one-liner, ≤32 chars — shows in the pane chip ("● claude · refactoring auth")
tmux set-option -p @agent_display_name "reviewer" # your name — replaces the detected kind in sidebar rows & chips
Plain text only (control characters are stripped, tabs break the line for your
pane — don't stamp them; anything past 32 chars is ellipsized). Both surface in
tmux-ide team --json (per-pane statusText / displayName), the unified
app's pane chips, and the sidebar agent rows. They follow the SAME staleness
rules as @agent_state: they only show while your state stamp is fresh, so
re-stamp @agent_state alongside — update the text whenever your focus
changes, and it disappears with a stale/cleared state instead of lying.
Claude Code users get this for free — tmux-ide integration install claude
writes a POSIX hook into ~/.claude/settings.json that stamps @agent_state on
every lifecycle event (UserPromptSubmit/PreToolUse → working, Notification →
blocked, Stop → done, SessionEnd → idle) and records @agent_session_id. It
takes effect for new Claude Code sessions; the merge is reversible
(integration uninstall claude).
Session-id capture for other kinds (what restore --resume-agents resumes
from): codex and cursor-agent panes are stamped automatically — the chrome
updater reads each CLI's own on-disk session state; opencode gets a plugin via
tmux-ide integration install opencode. tmux-ide integration status shows
what's active. Kinds without a verified resume story (gemini, aider, copilot, …)
can self-report the id as above.
How detection layers work: Layer 1 is the authority above — a fresh
@agent_state option is ground truth. When none is present, Layer 2 resolves the
agent from the pane's process tree and reads the visible screen against
evidence-tuned per-agent manifests to infer working/blocked/done. Run
tmux-ide agent explain <pane> to see exactly which layer fired for a pane and why.
Coordinating with other agents
The status bus is shared, so you can work as part of a team — and the teammates don't have to be Claude Code. As an agent, you can:
tmux-ide team --json # fleet rollup: each session's + window's agent status
tmux-ide agent explain %2 --json # one specific pane's status + why (per-pane read)
tmux-ide send %2 "do X, then run tests" # task another pane's agent (by %id, title, role, or @ide_name)
tmux-ide wait output %2 --match "done" # block until that pane prints something (exit 0 match / 1 timeout)
tmux-ide wait agent-status api --status done # block until a whole session finishes
tmux-ide events --follow # subscribe to the live session-status transition stream
send types straight into the target agent's prompt (use --no-enter to stage
text; pipe stdin for long input — messages over ~150 chars auto-route through a
.tasks/dispatch/ file). Report your own status with the @agent_state contract
above so teammates coordinating on you see the truth. This works across
Claude Code, codex, cursor-agent, aider, or any CLI agent in a pane.
Fleet control from the CLI
Every command takes --json for structured output.
tmux-ide team --json # whole-fleet state: sessions, panes, agent statuses
tmux-ide events --follow # stream agent-status transitions (needs an adopted session)
tmux-ide events --json # recent transitions as JSON
tmux-ide wait agent-status <session> --status blocked --timeout 60000 # block until a session hits a status
tmux-ide wait output <pane|session> --match "<regex>" --timeout 60000 # block until a pane's output matches
tmux-ide send <target> "<message>" # send text to a pane (by name/title/role/ID); --to <name>, --no-enter
tmux-ide agent explain <pane> --json # debug how a pane's agent state was detected
tmux-ide adopt <session> # add the dock to an existing session (additive tmux config)
tmux-ide adopt --all # adopt every live session
tmux-ide unadopt <session> # remove the dock — sessions keep running as plain tmux
tmux-ide restore --dry-run --json # preview rebuilding the fleet from the last snapshot
tmux-ide restore --resume-agents # rebuild after a tmux crash; revive agent convos (claude/codex/cursor/opencode)
tmux-ide worktree create <branch> --from <ref> # git worktree on a new branch + a session in it
tmux-ide worktree open <branch> # open/switch to an existing worktree's session
tmux-ide worktree list --json # worktrees joined with their session status
tmux-ide worktree remove <branch> --force # kill the session + remove the worktree
tmux-ide update --dry-run # detect install method (dev checkout vs npm/pnpm/bun) and show/run the update
tmux-ide doctor # system + integration health (tmux version, TUI surfaces, skill freshness)
Drive tmux-ide over the socket (agent loops)
The CLI above spawns a process per call. If you are driving the fleet in a loop — polling status, waiting on agents, reacting to transitions — start the control server once and keep ONE connection open instead:
tmux-ide serve & # local Unix socket at ~/.tmux-ide/control.sock (0600, this user only)
Frame format: newline-delimited JSON, one object per line. Send
{"v":1,"id":<any>,"verb":"<verb>","params":{…}}; you get back
{"v":1,"id":<same>,"ok":true,"data":…} or
{"v":1,"id":<same>,"ok":false,"error":{"code","message"}}. Responses
correlate by id (they may arrive out of order — a long wait doesn't block
other verbs on the same connection). After the subscribe verb the server
also PUSHES unsolicited {"v":1,"event":"agent-status","data":{ts,session,from,to}}
frames the moment its detection tick sees a session change state — no polling.
Verbs: fleet (the team --json payload) · agents (per-pane entries,
optional {session}) · send ({session,target,message,noEnter?,dir?}) ·
wait ({kind:"agent-status",session,status,timeoutMs?} or
{kind:"output",target,match,timeoutMs?}; a timeout is an error response with
code timeout) · spawn ({kind|command, session?|sessionName, dir?, placement?, paneId?} → the new paneId) · restart-agent / stop-agent
({paneId, kind|command}) · explain ({target}) · subscribe.
One-shot from a shell (nc keeps the pipe open for the response):
printf '{"v":1,"id":1,"verb":"fleet"}\n' | nc -U ~/.tmux-ide/control.sock | head -1
A subscribe loop from node:
const net = require("node:net");
const os = require("node:os");
const sock = net.connect(`${os.homedir()}/.tmux-ide/control.sock`);
let buf = "";
sock.on("data", (chunk) => {
buf += chunk;
const lines = buf.split("\n");
buf = lines.pop();
for (const line of lines.filter(Boolean)) {
const frame = JSON.parse(line);
if (frame.event === "agent-status") console.log(frame.data); // react here
}
});
sock.write('{"v":1,"id":1,"verb":"subscribe"}\n');
Right after subscribe, the first tick reports every session once with
from:null (a snapshot of where the fleet stands); real transitions follow.
Socket vs CLI: prefer the socket for anything event-driven or repeated
(subscribe replaces an events --follow poll; a server-held wait costs no
spawn-per-poll). Prefer the CLI for one-shot reads and anything a human might
re-run — it needs no server. With a server running, tmux-ide events --follow --socket and tmux-ide wait … --socket use it automatically and fall back to
polling silently when it's gone. The server is local-only by design: no
network listener, no tokens — filesystem permissions are the auth.
Keys & surfaces to tell USERS about
Once a session is adopted, the whole UI is a keystroke away. Lead with the
prefix — an agent pane can temporarily change key encoding and swallow a
root-table Alt bind, but the tmux prefix always reaches tmux. Every surface has
a prefix twin and an ⌥ fast-path (single keystroke when the terminal allows it).
Right-click any pane or the bar opens the actions menu at the pointer.
| Surface | Prefix (always works) | ⌥ fast-path |
|---|---|---|
| Home cockpit — fleet tree, detail, preview | prefix h | ⌥h |
| Switch session | prefix j | ⌥p |
| Cheat sheet — every key on one page | prefix k | ⌥k |
| Actions menu (or right-click) | prefix u | ⌥m |
| Sidebar — fleet nav column | prefix b | ⌥b |
| Panels — explorer / changes / config | prefix e g v | ⌥e ⌥g ⌥, |
One interaction grammar everywhere: j/k move, enter opens, / filters,
esc backs out, ? asks. Bare tmux-ide with no project config opens the home
cockpit (the fleet home screen). tmux-ide cheatsheet prints the full sheet.
The app — tmux-ide app (the terminal IDE)
v2.7 adds a full-screen unified app: tmux stays the engine (PTYs, agents,
persistence); the app is the IDE around it. Launch tmux-ide app (bare = home
screen) or tmux-ide app <session>. Needs bun, or a downloaded binary:
tmux-ide update --tui-binary.
- Tabs
F1–F4: Home (fleet, pick a session = set the workspace) · Terminal (the session mirrored live — it keeps streaming while you're on other tabs) · Files (tree + built-in editor:^ssave,^zundo, click to place the cursor) · Diff (colored working-tree changes,^eopens the file in the editor).F5= command palette (fuzzy everything). - Mouse-native: hover highlights; right-click = context menus (split/zoom/ kill panes, layouts, synchronize-panes, kill/rename sessions & windows — destructive actions confirm); drag pane borders to resize; drag-select text → clipboard via OSC52 (works through ssh); scrollbars; clickable buttons.
- tmux parity:
[⛶]zoom, window verbs, layout presets,/scrollback search withn/N, paste-buffer picker.^qquits — the session is untouched, like you were never there. - State persists across launches (~/.tmux-ide/app-state.json): last tab, session, open file.
.tmux-ide/workspace.yml (optional)
Adopt works on any session. If you'd rather have tmux-ide build the layout, describe
it in .tmux-ide/workspace.yml (sessions launched from a config are adopted automatically).
Setup workflow for a user's project:
-
Check state:
tmux-ide status --json -
Detect the stack:
tmux-ide detect --json -
Present 2-3 layout options as ASCII diagrams before writing config:
Option A — Claude + Dev (recommended)
┌─────────────────────────────────────┐ │ Claude │ 70% ├──────────┬──────────┬──────────────┤ │ Dev Srv │ Tests │ Shell │ 30% └──────────┴──────────┴──────────────┘Option B — Dual Claude
┌─────────────────┬─────────────────┐ │ Claude 1 │ Claude 2 │ 70% ├────────┬────────┴───────┬─────────┤ │Dev Srv │ Tests │ Shell │ 30% └────────┴────────────────┴─────────┘Option C — Explorer + Claude + Changes (widget panes)
┌──────────┬───────────────┬─────────┐ │ Explorer │ Claude │ Changes │ 100% │ (widget) │ │ (widget)│ └──────────┴───────────────┴─────────┘Adapt pane names/commands to the detected stack (
pnpm dev,cargo watch, …). -
Write it — quick path
tmux-ide detect --write, or build with the config CLI:tmux-ide config add-row --size 70% tmux-ide config add-pane --row 0 --title Claude --command claude tmux-ide config add-row --size 30% tmux-ide config add-pane --row 1 --title "Dev Server" --command "pnpm dev" tmux-ide config add-pane --row 1 --title Shell tmux-ide validate --json # always validate after mutations
Schema:
version: 1
name: my-app # tmux session name
before: pnpm install # optional pre-launch shell hook
terminal:
theme: # optional per-session pane colors
accent: colour75
border: colour238
rows:
- size: 70% # row height percent (rows split evenly if omitted)
panes:
- title: Claude # pane border label
command: claude # command to run (optional)
size: 50% # pane width percent (optional)
dir: apps/web # per-pane working directory (optional)
focus: true # initial focus (optional)
env: # environment variables (optional)
PORT: "3000"
- panes:
- title: Explorer
type: explorer # widget pane: explorer | changes | preview | config
target: src/ # optional widget target path
- title: Shell
Read config with tmux-ide config --json; mutate with config set <dot.path> <value>,
add-pane, remove-pane, add-row; apply changes to a running session with
tmux-ide restart.
Mission runtime wiring is future work for the workspace config model. Do not add
mission or orchestrator runtime fields to .tmux-ide/workspace.yml yet.
Config — ~/.tmux-ide/config.json
The one product-wide config (override path with TMUX_IDE_CONFIG). A deep
partial merge over defaults — any block or field you omit falls back:
{
"keys": {
"home": "M-h",
"popup": "M-p",
"cheatsheet": "M-k",
"menu": "M-m",
"sidebar": "M-b",
"panels": { "explorer": "M-e", "changes": "M-g", "config": "M-," },
},
"theme": {
"accent": "colour75",
"muted": "colour240",
"fg": "colour250",
"status": {
"blocked": "colour203",
"working": "colour221",
"done": "colour111",
"idle": "colour114",
"unknown": "colour244",
},
"glyphs": { "active": "●", "inactive": "○" },
},
"notifications": { "toast": true, "macos": false },
"restore": { "resumeAgents": false },
"updates": { "check": true },
"integrations": { "offer": true },
}
One palette + one keymap drive every surface (status bar, chips, menu, cheat sheet, and the OpenTUI widgets), so re-theming the whole product is a one-file edit plus a re-adopt.
Keeping this skill current
This file is managed — installs and tmux-ide update (dev checkouts) refresh the
copy under ~/.claude/skills/tmux-ide. To refresh it manually at any time, run
tmux-ide skill-sync. tmux-ide doctor reports when the installed copy is stale.
Signals
- GitHub stars
- 544
- Forks
- 30
- Last commit
- Sep 2026
ahel review
K1binfo
installs-packages
Automated review, not a security audit. Ruleset v1+k2.
Advanced
- Catalog kind
- skill
- Gateway key
skill-wavyrai- Source
- github.com/wavyrai/tmux-ide