Documentation · Cockpit

Cockpit reference

Last updated: 2026-08-11License: MIT

Everything the desktop app reads from your repository or writes to your machine: the internal cockpit CLI that agents use to drive tabs, the .ckp pane layouts, the .cockpit/tasks.json Task Run file, the theme format, and the harness hooks behind the turn status. For the product tour, see the Cockpit page; for the mesh, daemons, and the relay, see the Remote Pi docs.

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.

FlagWhat it does
--tab-id <id>Target another tab. Defaults to $COCKPIT_TAB_ID (the current tab; legacy fallback $COCKPIT_PANE_ID).
--focusedTarget 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.
Tab ids reset on boot

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

CommandWhat 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 connRun 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.
Cockpit terminal
$
# 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.

FlagDefaultWhat it does
--lines N100How many lines to return (server cap: 2000).
--offset N0Skip N lines from the anchor — this is your pagination.
--from-startoffAnchor 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 .ckp file in the tree → Open layout.
  • CLI cockpit orchestrate dev.ckp from inside a tab.
  • Worktree autorun autorun: worktree in 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 — yaml
# 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.

FieldRequiredDefaultDescription
nameyesUnique (case-insensitive). Becomes the tab's manual label and the merge key.
cwdno.Relative to the file's folder, forward slashes only. Absolute paths and \ are rejected for portability.
splitnotabWhere it is born, relative to the previously created pane: tab, right, down.
commandnoTyped into the terminal and run by the tab's shell (resolved through the machine's PATH).
platformsnoallmacos / windows / linux, string or list — same semantics as in tasks.json.

Merge semantics

  • A pane whose name already exists as a tab label or title in the workspace is skipped — applying a layout twice is a no-op. Nothing is ever closed.
  • split anchors 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 cwd or invalid YAML gives a readable error (dialog in the GUI, stderr in the CLI); nothing is applied halfway from the failing pane on.
  • command is 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.json scripts, 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.
The runner is generic

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.

.cockpit/tasks.json — jsonc
{
  "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.

FieldRequiredDefaultDescription
labelyesShort display name. The task id is derived from it: json:<label>.
commandyesBase executable (npm, flutter, dart…).
argsno[]Base args, placed before the profile's args.
cwdnorootRun folder, relative to the tasks.json folder (absolute also accepted). Falls back to the top-level cwd, then the root.
platformsnoallOSes where the task is visible: macos, windows, linux.
kindnooneShotwatch (long-lived process, e.g. a dev server) or oneShot (runs and exits).
interactiveKeysno[]Buttons that write to the process' stdin: key, label, optional icon (refresh, restart, stop, bolt) and primary.
watchnonullReload on save: paths, ignore, onChange (required — an interactiveKeys label or "__restart__") and debounceMs (300). Leave it out for tools that already watch (Vite, Next).
progressPatternsno[]begin / end regexes that swing the badge between building and running.
profilesno[]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.
Two known limits

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 terminal
$
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 shop
Per-connection guardrails

Each 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 its id. Copying a .json in there installs it too.
  • Export produces a complete file (every token, no extends) — a good starting point for hand editing.
theme.json — shape — json
{
  "$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": {} }
  }
}
FieldRequiredWhat it is
idyesStable, 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.
nameyesWhat shows up in the picker.
authornoMetadata.
versionnoMetadata.
extendsnoId of a built-in theme to inherit from. Absent = inherits from cockpit.
variantsyesAt 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:

acme.violet.json — json
{
  "$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

GroupTokens
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:

  1. At boot, Cockpit materializes the internal CLI at ~/.cockpit/bin/cockpit and registers cockpit hookon the harness' lifecycle events.
  2. On each event the harness runs the hook, passing a JSON payload on stdin.
  3. 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/tty fails with ENXIO.)
  4. Routing is by the COCKPIT_PANE_ID env 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.
HarnessFileFormat
Claude Code~/.claude/settings.jsonhooks.<Event>[], each item {matcher, hooks:[{type, command}]}
Codex CLI~/.codex/hooks.jsonSame 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

EventClaudeCodexStatus
UserPromptSubmitworking (turn starts)
PreToolUseworking— except for Claude's blocking tools (below)
PostToolUseworking
Notificationwaiting / idle (heuristic on the text)
PermissionRequestwaiting
Stopidle
SessionStart / SessionEndidle
SubagentStart/Stop, PreCompact/PostCompactignored

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.

Codex trust is index-keyed

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.