Install
Cockpit ships for macOS, Windows, and Linux. macOS builds are signed and notarized, and every platform that supports it gets in-app updates. Grab a build from the download page — .dmg, .exe, .deb and .rpm (x64 and arm64), each with a published SHA-256.
Cockpit is a terminal first: it needs no account, no cloud, and no Remote Pi setup to be useful. The mesh, pairing, and daemon features light up only once you install the remote-pi extension described in the main docs.
The cockpit CLI
Cockpit materializes a small binary at ~/.cockpit/bin/cockpit and puts that folder on the PATH of the terminals it spawns — and only those. So the CLI exists for anything running inside Cockpit (you, a script, an agent) and does not leak into the rest of your shell environment. It talks to the app over a local socket: a Unix socket on macOS and Linux, a loopback TCP port plus a token on Windows.
This is what makes Cockpit an agentic multiplexer: an agent in one tab can open another tab, type into it, read what it printed, run a project task, or query a database — the same verbs a human uses, with no screen scraping.
Targets & ids
The unit the CLI addresses is a tab (one terminal or agent session). A pane is the split leaf that groups tabs and is not addressable — list-panes and read-pane survive as legacy aliases.
| Flag | What it does |
|---|---|
--tab-id <id> | Target another tab. Defaults to $COCKPIT_TAB_ID (the current tab; legacy fallback $COCKPIT_PANE_ID). |
--focused | Target whatever tab you are looking at, resolved by the app. Works from outside a Cockpit terminal too (dictation tools, scripts): with no env inherited the CLI finds the app through ~/.cockpit/status.sock. Wins over --tab-id. |
--enter | (send only) press Enter right after the text, as a separate keystroke — a send plus a send-key Enter in one call. |
Ids (t0, t1…) are assigned per app boot, so never hardcode one. Discover them with cockpit list-tabs, or give a tab a stable label (double-click the tab, or new-tab --title) and address it by name. Labels persist across boots; workspace ids are opaque UUIDs, so use workspacePath / path when you need the folder on disk.
Command reference
| Command | What it does |
|---|---|
send [--tab-id id] [--enter] <text> | Type text into a tab. |
send-key [--tab-id id] <Key>… | Press named keys: Enter Tab Escape Space BSpace Up Down Left Right Home End PageUp PageDown Delete and C-<letter> (e.g. C-c). |
open <file> | Open a file in the app's viewer. Bare cockpit <file>is a shortcut for it, relative to the tab's cwd. |
new-tab [--cwd dir] [--title name] [--split h|v] | Open a terminal tab and print its id. h/right splits side by side, v/down stacks; omit to open as a tab in the same pane. Anchored at the emitting tab's pane. |
read-tab [label|tab-id] | Read a tab's rendered output. No target = the current tab. Alias: read-pane. |
read-task <task-id> | Read a task's output, even with no tab open for it. |
list-tabs [--json] | List active tabs (alias: list-panes). The JSON carries label, workspacePath, working and, for task output tabs, taskId. |
list-workspaces [--json] | List workspaces (projects) and their paths. |
list-tasks [--json] | List this workspace's tasks. [output] marks tasks whose output read-task can read (ran this boot); ● marks tasks running right now. |
db <list|schema|query|run|execute> | SQL databases registered in the workspace — see Databases. |
redis [browse] --db conn | Run a Redis command, or open the key table for a human. |
mongo [browse] --db conn [--database name] | Run a MongoDB command, or open the collection browser. |
orchestrate <file.ckp> [--json] | Apply a pane layout — see .ckp layouts. |
install-skill [--force] | Install the Claude Code skill that teaches this CLI. |
# open a worker tab beside you, then drive it id=$(cockpit new-tab --cwd ~/proj --title Worker --split h) cockpit send --tab-id "$id" --enter "npm test" # read what it printed cockpit read-tab Worker --lines 50 # run and follow a project task cockpit list-tasks cockpit read-task npm:dev --lines 80 # open a file in the viewer, query a database cockpit open ~/.gitconfig cockpit db query --db dev-local --sql "SELECT * FROM orders LIMIT 5"
Reading output
read-tab and read-task share a windowing model. Output is always chronological (top to bottom); the flags only pick which window you get.
| Flag | Default | What it does |
|---|---|---|
--lines N | 100 | How many lines to return (server cap: 2000). |
--offset N | 0 | Skip N lines from the anchor — this is your pagination. |
--from-start | off | Anchor at the start of the buffer instead of the tail. |
Task ids are stable per workspace: npm:<script> from package.json, flutter:run / flutter:test, and json:<label> from .cockpit/tasks.json.
.ckp pane layouts
A .ckp file is a versionable YAML that describes the terminals to open in a workspace — the equivalent of a tmuxinator layout. One file is one layout, and the file name is the layout name (dev.ckp→ layout “dev”). Commit it, and a teammate gets your working geometry on clone.
There are three ways to apply one:
- GUI — right-click the
.ckpfile in the tree → Open layout. - CLI —
cockpit orchestrate dev.ckpfrom inside a tab. - Worktree autorun —
autorun: worktreein the file: the layout is applied by itself whenever you create a worktree of the workspace. The worktree is born empty, so the geometry comes out exact.
# dev.ckp — anywhere in the project; cwd is relative to this file
autorun: worktree # optional
panes:
- name: Frontend # required, unique — becomes the tab's stable label
cwd: frontend # relative to this file, always with "/"
command: claude # optional: typed into the shell after the tab opens
- name: Backend
cwd: backend
split: right # tab (default) | right (side by side) | down (stacked)
command: npm run dev
- name: Sign
cwd: .
command: ./sign.sh
platforms: [macos] # optional: macos | windows | linux (string or list)Fields
Root: panes (required) is the list of panes in creation order. autorun (optional) accepts only worktree; with two or more autorun files at the root, none of them runs — ambiguity is never guessed.
| Field | Required | Default | Description |
|---|---|---|---|
name | yes | — | Unique (case-insensitive). Becomes the tab's manual label and the merge key. |
cwd | no | . | Relative to the file's folder, forward slashes only. Absolute paths and \ are rejected for portability. |
split | no | tab | Where it is born, relative to the previously created pane: tab, right, down. |
command | no | — | Typed into the terminal and run by the tab's shell (resolved through the machine's PATH). |
platforms | no | all | macos / windows / linux, string or list — same semantics as in tasks.json. |
Merge semantics
- A pane whose
namealready exists as a tab label or title in the workspace is skipped — applying a layout twice is a no-op. Nothing is ever closed. splitanchors on the pane created previously in this run; if that one was skipped by the merge, the next opens as a plain tab. Perfect geometry is guaranteed only in an empty workspace — which is exactly the worktree autorun case.- A missing
cwdor invalid YAML gives a readable error (dialog in the GUI, stderr in the CLI); nothing is applied halfway from the failing pane on. commandis typed ~700 ms after the tab opens, so the shell has time to finish booting, with Enter at the end.
Cockpit treats .ckp as YAML for highlighting and shows the Cockpit logo as the file icon in the tree.
Task Run
Task Run executes your project's build and dev commands (npm run dev, flutter run, go run, make…) with streamed output, a visual lifecycle (play / stop / restart), interactive keys, and reload-on-save. Two sources coexist:
- Auto-detected — on opening a project, Cockpit reads the manifests (
package.jsonscripts,pubspec.yaml) and shows tasks with no config at all. - Declared —
.cockpit/tasks.json, for customizing, adding tasks, or describing a monorepo. JSON tasks take precedence over a detected task with the same id.
It knows only command, args, and env. There are no stack-specific keys (flavor, dart-define, NODE_ENV) — all of that is expressed as args and env.
Where the file lives
At the root of the workspace you open. Discovery is literal — Cockpit does not walk up the tree. For a single package, open the package folder. For a monorepo, open the root and let one .cockpit/tasks.json drive the subpackages through a per-task cwd.
The file is JSONC: comments (// and /* */) and trailing commas are allowed, just like VSCode's tasks.json. Point $schema at docs/tasks.schema.json for editor autocomplete; Cockpit ignores the field when running.
{
"tasks": [
{
"label": "run",
"cwd": "app", // relative to the tasks.json folder
"command": "flutter",
"args": ["run"],
"kind": "watch",
"interactiveKeys": [
{ "key": "r", "label": "Hot reload", "icon": "refresh", "primary": true },
{ "key": "R", "label": "Hot restart", "icon": "restart", "primary": true },
{ "key": "q", "label": "Quit", "icon": "stop" }
],
"watch": {
"paths": ["lib", "assets"],
"ignore": ["build", ".dart_tool"],
"onChange": "Hot reload", // an interactiveKey label, or "__restart__"
"debounceMs": 300
},
"progressPatterns": [
{ "begin": "Performing hot reload", "end": "Reloaded .* in .*ms" }
],
"profiles": [
{ "name": "default" },
{ "name": "web", "args": ["-d", "chrome"] }
]
},
{
"label": "api",
"cwd": "backend", // monorepo: another subfolder
"command": "dart",
"args": ["run", "bin/server.dart"],
"kind": "watch"
}
]
}Fields
Root: tasks (required) and cwd (optional) — a default cwd for every task, which each task may override.
| Field | Required | Default | Description |
|---|---|---|---|
label | yes | — | Short display name. The task id is derived from it: json:<label>. |
command | yes | — | Base executable (npm, flutter, dart…). |
args | no | [] | Base args, placed before the profile's args. |
cwd | no | root | Run folder, relative to the tasks.json folder (absolute also accepted). Falls back to the top-level cwd, then the root. |
platforms | no | all | OSes where the task is visible: macos, windows, linux. |
kind | no | oneShot | watch (long-lived process, e.g. a dev server) or oneShot (runs and exits). |
interactiveKeys | no | [] | Buttons that write to the process' stdin: key, label, optional icon (refresh, restart, stop, bolt) and primary. |
watch | no | null | Reload on save: paths, ignore, onChange (required — an interactiveKeys label or "__restart__") and debounceMs (300). Leave it out for tools that already watch (Vite, Next). |
progressPatterns | no | [] | begin / end regexes that swing the badge between building and running. |
profiles | no | [] | Named run variants (launch configs): name, extra argsappended after the task's, and env merged into the process environment. A chip cycles them before you hit play. |
For an argument value containing spaces, use separate items in args (["--dart-define", "MSG=hello world"]). And the output tab does not survive an app restart — the task dies with it.
Databases
Connections live per workspace in .cockpit/databases.json, plus any SQLite files Cockpit auto-detects in the project. SQLite, Postgres, MySQL, SQL Server, Redis, and MongoDB open as tabs: a table view for SQL and Redis, a collection browser for MongoDB. A .dbq file is a saved query you can commit and re-run.
The same connections are reachable from the CLI, and the output is one JSON line — built to be parsed by an agent, not read by a human.
cockpit db list
cockpit db schema --db dev-local orders
cockpit db query --db dev-local --sql "SELECT * FROM orders LIMIT 5"
cockpit db run reports/daily.dbq
# non-SQL engines have their own verbs
cockpit redis --db cache --command "SCAN 0 COUNT 20"
cockpit mongo --db atlas --database shop --command '{"find":"orders","limit":5}'
# open the same thing visually for a human
cockpit redis browse --db cache
cockpit mongo browse --db atlas --database shopEach connection carries an access level (read — the default, including for connections created before the field existed — or readwrite) and an agents flag. A connection with agents: false is invisible to the CLI. The gates are enforced on the CLI surface: what you do by hand in the GUI is never blocked, but what an agent can reach is yours to decide. Run cockpit db --help for the full surface.
Themes
Cockpit ships nine built-in themes — cockpit, cockpit.2, violet, violet.2, midnight, rose, sun, flexoki and pantera — each with a light and a dark variant. Beyond those, a theme is a single JSON filethat paints all three layers at once: the app UI, the code viewer's syntax highlighting, and the terminal palette.
The theme file
- Import with Settings → Appearance → Theme → Import…
- Themes live in
<data folder>/themes/(the same root as the “Storage” setting), one file per theme, named after itsid. Copying a.jsonin there installs it too. - Export produces a complete file (every token, no
extends) — a good starting point for hand editing.
{
"$schema": "https://raw.githubusercontent.com/jacobaraujo7/remote_pi/main/cockpit/docs/theme.schema.json",
"id": "acme.aurora",
"name": "Aurora",
"author": "Acme",
"version": "1.0.0",
"extends": "cockpit",
"variants": {
"dark": { "ui": {}, "syntax": {}, "terminal": {} },
"light": { "ui": {}, "syntax": {}, "terminal": {} }
}
}| Field | Required | What it is |
|---|---|---|
id | yes | Stable, namespaced identity (publisher.name). It is what gets stored in preferences, so renaming namenever loses the user's choice. Cannot collide with a built-in id. |
name | yes | What shows up in the picker. |
author | no | Metadata. |
version | no | Metadata. |
extends | no | Id of a built-in theme to inherit from. Absent = inherits from cockpit. |
variants | yes | At least one of dark / light. A dark-only theme is applied in light mode too — better than mixing half a light theme with half a dark one. |
Inheritance is the point. Every token you do not declare comes from the base, so a useful theme can be five lines long:
{
"$schema": "https://raw.githubusercontent.com/jacobaraujo7/remote_pi/main/cockpit/docs/theme.schema.json",
"id": "acme.violet",
"name": "Violet",
"variants": {
"dark": { "ui": { "accent": "#8B5CF6", "accentSoft": "#8B5CF633", "accentText": "#C4B5FD" } },
"light": { "ui": { "accent": "#7C3AED", "accentSoft": "#7C3AED22", "accentText": "#5B21B6" } }
}
}Colors are CSS-style hex — #RGB, #RRGGBB or #RRGGBBAA, alpha last. It is not Dart's 0xAARRGGBB. On a bad import the parser points at the field path that broke (variants.dark.ui.accent), and validation runs before the copy, so an invalid file never reaches the themes folder.
Tokens
| Group | Tokens |
|---|---|
ui (25) | Surfaces bg panel panel2 panel3 · strokes border border2 · text text text2 text3 text4 · brand accent accentSoft accentText · state online ok error warn · editing edited editedBg · git gitStaged gitUntracked gitDeleted gitConflict · overlay scrim shadow |
syntax (12) | background base comment keyword string number class builtin function variable meta deletion |
terminal (23) | cursor selection foreground background, the 8 normal ANSI colors, their 8 bright* counterparts, and searchHitBackground searchHitBackgroundCurrent searchHitForeground |
Text drawn on top of accent and erroris not a token: it is derived from the color's luminance, so a light accent automatically gets dark text.
syntax.background has a special default. The code viewer, the editor, and the terminal are all content inside a tab, so they share the field: when a theme declares neither syntax.background nor terminal.background, the three follow ui.panel of this theme, not of the base. Declare the field if your code palette needs a surface of its own.
The $schema URL lives in the repository, versioned next to the code that implements it, so the app and the schema can never drift apart. Cockpit ignores the field when reading a theme — it only serves your editor. See theme.example.json for a commented file with every group filled in.
Agent turn status
When an agent runs in a tab, Cockpit shows whether it is working, waiting for you, or idle — as a spinner, a badge, a chime, and an OS notification when the window is unfocused. Supported harnesses today: Claude Code and Codex CLI (0.147+).
How it works:
- At boot, Cockpit materializes the internal CLI at
~/.cockpit/bin/cockpitand registerscockpit hookon the harness' lifecycle events. - On each event the harness runs the hook, passing a JSON payload on stdin.
- The hook translates it into a status and sends it to the app over the local socket. (A socket rather than an escape sequence on the PTY: harnesses run hooks with no controlling terminal, and writing to
/dev/ttyfails withENXIO.) - Routing is by the
COCKPIT_PANE_IDenv var, which the app injects into the tab's PTY. An agent session started outside Cockpit does not have it, so the hook is a no-op there. Nothing to configure, nothing to disable.
| Harness | File | Format |
|---|---|---|
| Claude Code | ~/.claude/settings.json | hooks.<Event>[], each item {matcher, hooks:[{type, command}]} |
| Codex CLI | ~/.codex/hooks.json | Same shape, plus a trust block in ~/.codex/config.toml (Codex silently ignores an untrusted hook, so Cockpit computes and writes the trust hash for you, between # >>> cockpit hooks delimiters). |
Both installers do an idempotent append of a marked entry (_cockpit: v1): re-running removes our old entry and re-adds it, never rewriting the list — your own hooks, and those of plugins or iTerm2, survive untouched.
Event mapping
| Event | Claude | Codex | Status |
|---|---|---|---|
UserPromptSubmit | ✓ | ✓ | working (turn starts) |
PreToolUse | ✓ | ✓ | working— except for Claude's blocking tools (below) |
PostToolUse | ✓ | ✓ | working |
Notification | ✓ | — | waiting / idle (heuristic on the text) |
PermissionRequest | — | ✓ | waiting |
Stop | ✓ | ✓ | idle |
SessionStart / SessionEnd | ✓ | ✓ | idle |
SubagentStart/Stop, PreCompact/PostCompact | — | ✓ | ignored |
Two asymmetries matter. On Claude, tools that block waiting for the user (AskUserQuestion, ExitPlanMode) emit no Notification; the last hook before the block is PreToolUse, so that one maps to waiting for those two — otherwise the tab would spin forever with no chime. On Codex, approval has its own event, so there is no text heuristic and no detour.
Resuming a session
Cockpit persists the session_id that arrived through the hook, and on restoring the tab it types the command that reattaches the conversation. A session id alone does not say which harness it belongs to, and the commands differ — claude --resume <id> versus codex resume <id> — so the installer registers the hook as cockpit hook --harness <name> and the layout stores the harness next to the id. Entries written by older versions pass no flag and are assumed to be Claude, which is what they all were.
The Codex trust key includes the hook's group index. If you add a hook of your own before ours on the same event — or edit hooks.json by hand — the hash stops matching and the hook silently stops running. The installer repairs it on the next boot, because it recomputes the indices from the final file.
Sounds & notifications
Turn status drives audio too. Under Settings → Notifications you can bind a sound per event — turn done, action needed, error — pick a custom audio file for each, and set the volume. With the window focused you get the chime; unfocused, an OS notification. Since an agent started outside Cockpit never reports status, nothing fires for sessions the app is not hosting.
Language
Cockpit's interface is fully localized in English, Portuguese (Brazil) and Spanish, down to the native application menu. Switch it in Settings → General → Language; the choice is independent of the OS locale.
Links
- Cockpit product page — the tour.
- Tutorial: layouts and tasks — build a
.ckpand atasks.jsonfrom scratch. - Tutorial: an agent team in Cockpit.
- Remote Pi docs — mesh, relay, daemons, pairing.
- Source docs & JSON schemas in the repository.
- GitHub — issues and source.