Cockpit CLI
The Cockpit CLI is a thin layer over the running Cockpit server's HTTP API. Two binaries are installed by npm install -g @surething/cockpit: cockpit (canonical name) and cock (short alias) — identical. The server itself runs continuously; sub-commands speak HTTP to localhost:3457 to inspect or drive what's open in your panels.
| Command | Purpose |
|---|---|
cockpit | Start the server (the main entry point) |
cockpit browser | Drive a Browser bubble — navigate, click, evaluate JS, capture network |
cockpit terminal | Read a Terminal bubble's output (read-only; no stdin) |
cockpit codegraph | Query the project-wide code index from a shell |
cockpit connection | List all bubbles (terminal + browser) with their titles |
cockpit update | Upgrade to the latest version |
cockpit and cock
The cockpit binary is the main entry point. Its short alias is cock — both are installed when you npm install -g @surething/cockpit.
Usage
cockpit [path] [options]
cock [path] [options]
The two commands are identical; pick whichever you prefer to type.
Common forms
Start in the current directory:
cockpit
Start in a specific project:
cockpit ~/code/my-project
Start without auto-opening the browser:
cockpit . --no-open
Start on a different port:
cockpit . --port 4000
Show the version:
cockpit -v
Options
| Flag | Description |
|---|---|
-v, --version | Print the version and exit. |
-h, --help | Show inline help. |
--port <n> | Listen on a non-default port. Default 3457. |
--no-open | Don't auto-open the browser after the server starts. |
[path] | Working directory to open. Defaults to process.cwd(). |
Default port
Cockpit listens on port 3457. You can override per-run with --port, or make it permanent by writing to ~/.cockpit/server.json:
{ "port": 4000 }
The per-run flag wins when both are set.
Sub-commands
cockpit itself starts the server. Two sub-commands are used to drive Cockpit from external scripts (CI, ChatOps, automation):
| Sub-command | Purpose |
|---|---|
cockpit browser <id> <action> | Drive a running Browser bubble (navigate, click, evaluate JS, capture network, …) |
cockpit terminal <id> <action> | Read from a running Terminal bubble (list / output / wait; read-only) |
Both target an already-running Cockpit server at localhost:3457. They're the same APIs the in-app UI uses; exposing them as CLI commands lets you script the bubbles from anywhere.
See:
cockpit browser— full action list (25+ actions: snapshot, click, type, network, perf, …)cockpit terminal— list / output / wait (read-only)
Upgrading
cockpit update
Equivalent to npm install -g @surething/cockpit@latest. See cockpit update for what gets preserved across upgrades.
Exit codes
| Code | Meaning |
|---|---|
0 | Normal exit (server stopped cleanly) |
1 | Server failed to start (port in use, permission denied, missing Node, …) |
130 | Killed by Ctrl+C (SIGINT) |
Environment variables
| Variable | Effect |
|---|---|
COCKPIT_PORT | Same as --port, used by some downstream tools (/cg curl snippets, etc.). |
PORT | Fallback if COCKPIT_PORT is unset. |
cockpit browser
cockpit browser <id> <action> (or cock browser for the short form) drives a Browser bubble in your running Cockpit instance from outside — from the AI in a chat, from a shell script, from CI, from anywhere.
The <id> is the short ID badge from the Browser bubble's title bar. Click the badge to register the bubble and copy a starter command to your clipboard.
The CLI is designed for AI-driven E2E: every command has an actionable error template, silent failures are caught and warned, and the act/wait/assert cycle composes into atomic steps. Prefer selector-based interaction (--text, --selector) over snapshot refs — refs go stale on every re-render.
Quick examples
# Diagnose where the AI is currently parked (cheap, never blocks)
cock browser xa7k2 health
cock browser xa7k2 status
# Find and interact (selector-first; refs go stale on re-render)
cock browser xa7k2 snapshot --filter 'role=button' --include-hidden-text
cock browser xa7k2 click --text "Sign in"
cock browser xa7k2 click --selector 'button[type="submit"]'
cock browser xa7k2 fill --selector 'input[name="email"]' --value "[email protected]"
cock browser xa7k2 submit --form-selector 'form#login'
# Probe the backend (inherits page auth)
cock browser xa7k2 fetch /api/users/me
cock browser xa7k2 fetch /api/items --method POST --body '{"name":"hello"}'
cock browser xa7k2 fetch /api/items --json '$.data[0].id'
# act → wait → assert (atomic E2E)
cock browser xa7k2 click --text "Save"
cock browser xa7k2 wait --network-idle --quiet-ms 500
cock browser xa7k2 assert --selector '[role="status"]' --text "Saved"
cock browser xa7k2 assert --fetch /api/items --jsonpath '$.count' --equals 5
# Test isolation
cock browser xa7k2 reset --cookies --storage --reload
cock browser xa7k2 set --type cookie --name token --value abc123 --path /
# Run arbitrary JS (escape hatch — prefer fetch/click-by-selector when possible)
cock browser xa7k2 evaluate "document.title"
cock browser xa7k2 evaluate --all-frames "await fetch('/api/x').then(r=>r.json())"
Full action list
Inspection
| Action | What it does |
|---|---|
list | List every Browser bubble currently registered |
snapshot [--filter <regex>] [--include-hidden-text] [--max-depth N] | Accessibility tree. Banner explains the format. --filter greps server-side; --include-hidden-text surfaces text collapsed inside <summary> / container nodes |
screenshot | PNG of the page, saved to /tmp; the path is printed |
url | Current URL |
title | Page title |
status | One-line summary: URL, title, last console error, last failed request, top visible buttons. Run after a long gap to re-orient |
bounds <ref> | Position and size of an element |
attrs <ref> | All HTML attributes of an element |
computed <ref> | Computed CSS for an element |
events <ref> | Event listeners attached to an element |
cookies | All cookies for the page |
storage --type local|session | localStorage or sessionStorage contents |
theme --mode dark|light | Force the bubble's theme |
Interaction
Selector-based forms are preferred — refs are valid only until the next snapshot or re-render.
| Action | What it does |
|---|---|
click [<ref>] [--text <substr>] [--selector <css>] [--nth N] [--exact] | Click by ref, by visible text/aria-label, or by CSS selector. Refs go stale on re-render; selector and --text survive |
fill [<ref>] [--selector <css>] --value <v> | Set the value via native setter + dispatch input event (works on React-controlled inputs) |
type <ref> <text> | Type into the focused input via CDP key events. May silently miss on React-controlled inputs — prefer fill --selector |
submit [--form-selector <css>] | Call form.requestSubmit(). Works where pressing Enter is ignored by onKeyDown |
hover <ref> | Hover over an element |
focus <ref> | Focus an element |
scroll --direction up|down|left|right | Scroll the page |
key <key> | Press a key (Enter, Ctrl+A, Shift+Tab, …). On React inputs, prefer submit |
Wait — synchronisation between act and assert
| Action | What it does |
|---|---|
wait --network-idle [--quiet-ms 500] [--max-request-age-ms 30000] | Wait until 0 in-flight HTTP requests for quiet-ms consecutive ms. Long-running streams (SSE / long-poll) older than max-request-age-ms are excluded |
wait --selector <css> [--state visible|hidden|attached|detached] | Wait for the matching element to reach the given state. Default state is visible |
wait --dom-stable [--quiet-ms 300] | Wait until a MutationObserver reports no DOM changes for quiet-ms (useful between an act and a snapshot) |
wait --extension-ready [--quiet-ms 500] | CLI-side poll of health. Never blocks on the page. Replaces manual until evaluate "1+1" loops when the page is busy |
wait --text <substr> / --url <pat> / --ref <ref> / --time <ms> | Classic conditions: text appears, URL matches, ref is still attached, sleep |
Assert — non-zero exit on failure
| Action | What it does |
|---|---|
assert --selector <css> [--text <substr>] [--visible <bool>] [--attr "k=v"] | Element-level assertion via selector. Refs are also accepted (--ref) but go stale; selector is preferred |
assert --network --method <M> --url <pat> --status <S> [--since <ms>] | Assert that a matching request occurred in the network buffer. --status accepts ints (200) or ranges (2xx) |
assert --fetch <url> [--fetch-method M] [--body B] [--fetch-status N] [--jsonpath <P> --equals V | --contains V | --not-contains V] | Make a fetch (inherits page auth) and assert the response status or a JSONPath value. JSONPath subset: $, .key, [N], [*] |
assert --url <pat> / --title <substr> / --console-no-errors | Page-level assertions |
Navigation
| Action | What it does |
|---|---|
navigate --url <url> | Go to a URL |
reload [--noCache] | Reload (optionally bypassing cache) |
back | Go back one history entry |
forward | Go forward one history entry |
Backend probing
| Action | What it does |
|---|---|
fetch <url> [--method <M>] [--body <B>] [--headers <JSON>] [--json <jsonpath>] | GET (or any method) against the page's auth session. --json extracts a value via the same JSONPath subset as assert --fetch. Returns { status, contentType, data } or { status, jsonpath, value } |
fetch is the preferred way to read or mutate the backend from the AI — clearer than wrapping await fetch(...).then(r => r.json()) inside evaluate.
JavaScript
| Action | What it does |
|---|---|
evaluate <js> | Run a JS expression in the page; result printed as JSON. --all-frames runs in every iframe. Large results are transparently chunked back to the CLI |
Use evaluate as an escape hatch when none of the higher-level actions fit. For plain HTTP calls, prefer fetch; for clicks, prefer click --text / --selector; for assertions, prefer assert --selector.
Lifecycle / fixtures
| Action | What it does |
|---|---|
reset [--cookies] [--storage] [--cache] [--reload] | Atomic test-isolation helper. Combine flags as needed. --cookies expires JS-visible cookies; --storage clears localStorage and sessionStorage; --cache drops Cache Storage entries; --reload reloads the page after clearing |
set --type cookie --name <K> --value <V> [--domain <D>] [--path <P>] [--secure] [--same-site Lax|Strict|None] [--expires <date>] | Write a JS-visible cookie. Returns verified: false if the browser silently rejected the value (cross-domain / SameSite mismatch / Secure over HTTP) |
set --type local-storage --name <K> --value <V> | Write to localStorage |
set --type session-storage --name <K> --value <V> | Write to sessionStorage |
Diagnostics
| Action | What it does |
|---|---|
health | Server-side snapshot of the bridge: WS status, pending command count, time since last successful command. Never round-trips to the page, so it works even when the page itself is blocked on a long-running evaluate |
health --deep | Also probes the page itself (readyState, snapshotEpoch, page-side timestamp). May block if the page is busy |
Network capture
| Action | What it does |
|---|---|
network [--status <code>] [--method <method>] [--type <type>] [--clear] | List captured network requests with filters. --status accepts comma-separated 4xx,5xx |
network_record start [--url <pat>] [--method <m>] [--status <code>] | Start recording request/response bodies |
network_record stop | Stop recording |
network_record status | Whether recording is on |
network_detail <reqId> | Full request/response detail for one request |
console [--level error|warn|info|debug] [--clear] | Console messages |
perf [--metric timing|memory|resources] | Performance metrics including Core Web Vitals |
Snapshot output
snapshot returns a plain-text accessibility tree. The first 4–5 lines are a banner explaining the format, including the current snapshot version (v=N):
# a11y tree v=3 — refs valid until next snapshot
# Text inside <details>/<summary> and unnamed container <div>/<section> is collapsed.
# Grep on role / aria-label, NOT on user-visible emoji / text.
# Tips: --include-hidden-text surfaces collapsed innerText; --filter <regex> reduces output.
body [e0#v3]
...
Each addressable element gets a ref like e5#v3. The #v3 suffix is the snapshot epoch — refs from an earlier snapshot are rejected with a clear "stale" error that points you to click --text / click --selector as the re-render-safe alternative. Most AI workflows should skip refs entirely and use --text / --selector directly.
Post-verify for click / key / submit
These actions are most prone to silent failure — CDP reports "succeeded" even when the framework didn't actually react to the event (React-controlled inputs ignoring synthetic keydown, portal-rendered buttons with no real handler, etc.). The CLI silently probes the page state before and after each action; if nothing observable changed in the verify window, it writes a warning to stderr along with actionable templates (without affecting the action's main stdout).
| Flag | What it does |
|---|---|
--verify-ms <ms> | Override the verify window. Default is 1000 ms. Lower = faster but more false positives on slow-rendering React; higher = more tolerant |
--skip-verify (or --no-verify) | Disable post-verify for the current command (e.g. legitimate clicks with no observable side-effect) |
Output format
Most actions return JSON on stdout — easy to pipe into jq, gron, or to read from the AI. url, title, and network_detail return plain text. screenshot returns a file path. snapshot returns the banner + plain-text accessibility tree. health, status, wait, and assert print a one-line human-friendly summary. fetch prints [status] (contentType) followed by the body, or [status] $.jsonpath = followed by the extracted value.
Warnings (silent-failure detection, cookie not accepted, etc.) go to stderr. The main result stays on stdout, so a > redirect captures the real data while letting the AI still see the warning.
Exit codes
0 on success, non-zero on failure (stale ref, network error, assertion failure, no matching element, etc.). See the main CLI page for the full exit code list.
When NOT to use this CLI
- Testing LLM-agent driven flows end-to-end. The agent's stochastic tool choice and
stop_reasonmake UI assertions flaky. Prefer a thin runtime script that calls the same middleware or service directly with controlled inputs. - Pages that stream or re-render for >10 s.
evaluatecalls queue behind page work and may time out (~15 s default). Runwait --extension-readybetween acts and asserts; if it stays hung, pivot to a service-level test. - Multi-tab / popup OAuth flows. Each Browser bubble tracks one tab. Open the secondary tab in its own bubble, or stub the OAuth handshake.
cockpit terminal
cockpit terminal <id> <action> (or cock terminal) reads from a Terminal bubble in your running Cockpit — pulls the buffered output or waits for a running command to settle.
Note: the terminal CLI is intentionally read-only. There is no
stdinand nofollow(live streaming). The code comment says: "read-only by design; the write side belongs to the Bash tool / web UI." For interactive control, drive the bubble inside Cockpit's UI, or use a Browser bubble +cock browserfor automation.
The <id> is the short ID badge from the Terminal bubble's title bar. Click the badge to register the bubble and copy a starter command to your clipboard.
Full action list
| Action | What it does |
|---|---|
list | List every Terminal bubble currently registered. Shows status (running / idle) and the command in each. |
output | Print the entire buffered output of the terminal — the full history from when the bubble started. |
wait | Block until the currently-running command settles. Useful in scripts that need to "wait for npm run build to finish before continuing". |
Quick examples
# Find your bubbles
cock terminal list
# Snapshot what's on screen now
cock terminal xy789 output
# Wait for npm run build to finish
cock terminal xy789 wait
# Then look at the result
cock terminal xy789 output | tail -50
When to use this
Main patterns:
- AI reads what your shell is doing through the bubble. Run
npm run devin a Terminal bubble. Handcock terminal <id>to the AI in chat — it canoutputfor recent logs andwaitfor a build to settle. - CI / scripts observe a long-running command from outside. A launcher script kicks off
npm run devin a Cockpit terminal; another script periodically runscock terminal <id> outputto scrape logs and assert.
Limits
The Terminal CLI is intentionally lighter than the Browser CLI:
- No write to stdin. You can't push commands into the terminal process from the CLI.
- No screen-scraping with structured selectors (
outputgives you the raw buffer, you parse it yourself). - No window-resize, signals, or
Ctrl+C-style interrupt actions.
For full interactive control (Ctrl+C, typing commands, etc.), interact with the bubble in Cockpit's UI directly.
cockpit codegraph
cockpit codegraph queries the project-wide code index that powers the in-product CodeGraph feature (/cg mode), but from your shell so it composes with scripts, CI, and Unix pipelines.
The Cockpit server must be running — the CLI talks to it over HTTP on the local port.
Two families of subcommands
Lookups — coordinates only (file paths + line numbers), mirroring the in-product API:
| Subcommand | What it does |
|---|---|
search <query> | Find symbols by name. Returns file + qname hits. |
callers <qname> [--file PATH] | Direct callers of a symbol. |
callees <qname> [--file PATH] | What a symbol calls. |
impact <qname> [--depth N=2] | Transitive callers, BFS. Use risk for ranked output. |
file <path> | Symbol tree (functions/classes) inside a file. |
coedit <path> [--commits N=100] | Files co-edited in git history alongside this one. |
Analytics — blended scoring across PPR / TF-IDF / Louvain communities / co-edit:
| Subcommand | What it does |
|---|---|
context --query Q [--cursor C] [--open F1,F2,…] [--top N=15] | Top-K semantically relevant coordinates for a free-form question. |
related <qname> [--top N=10] | Broader neighbours: callers + callees + PPR + co-edit + community. |
risk <qname> [--depth N=2] [--top N=20] | Risk-scored impact + suggested tests. |
affected <files…|--stdin> [--depth N=10] [--filter G] [--as-cmd RUNNER] | Test files transitively affected. CI-friendly. |
Common flags
--json # Raw JSON response (full schema; see per-cmd --help)
--help, -h # Subcommand-specific help (output format + exit codes + examples)
Output format (plain text)
TAB-separated, one row per result. Pipeable into cut, awk, fzf, etc.
search sym\t<file>:<line>\t<kind>\t<qname> or file\t<file>
callers <file>:<line>\t<qname>\t[<callLines>]
callees <file>:<line>\t<qname>\t[<callLines>]
impact d=<depth>\t<file>:<line>\t<qname>
file <kind>\t<startLine>-<endLine>\t<qname>
coedit <cooccur>/<total>\t<file> # after a '# history' comment
context <score>\t<file>:<line>\t<qname>\t[<signals>]
related <score>\t<file>:<line>\t<qname>\t<<relations>>
risk <score>\td=<depth>\t<file>:<line>\t<qname>\t[<tags>]
affected <file> # one test path per line
Pass --json to get the full structured payload instead.
Diagnostics on stderr
Designed so warnings don't break shell pipelines:
# ambiguousIn: <files…> Same qname in multiple files — pass --file
# cursor: <note> Cursor format auto-corrected ('.' → '::', etc.)
# degraded: <reason> analytics-warming / coedit-unavailable / truncated
Exit codes
| Code | Meaning |
|---|---|
0 | Output produced |
1 | Empty result (no callers, no tests, no hits) — short-circuits shell pipelines |
2 | Argument or 4xx server error |
3 | Cockpit server not reachable. Start it with cock <project-path>. |
Prerequisites
The CLI hits http://localhost:3457 by default (the same port as the main Cockpit server — not a separate codegraph port). Override via:
COCKPIT_HOST=… COCKPIT_PORT=… cock codegraph …
Examples
cock codegraph search getCodeIndex
cock codegraph related getCodeIndex --top 5
cock codegraph risk searchIndex --depth 2
# Newline-separated test paths for whatever changed:
git diff --name-only | cock codegraph affected --stdin
# Drive jest directly with the affected tests:
git diff --name-only | cock codegraph affected --stdin --as-cmd jest
# Same idea for vitest:
git diff --name-only | cock codegraph affected --stdin --as-cmd "vitest run"
See also
- CodeGraph (in-product) — the same index, but queried by the AI via
/cgslash mode. - LSP — alternative for editor-grade go-to-definition / find-references.
cockpit connection
cockpit connection list enumerates every bubble — terminal and browser — that the running Cockpit server knows about, with each bubble's user-set title (set via the ✎ button next to the bubble's short id).
The point is to give an LLM (or a human at a shell) a way to map cryptic 4-character bubble ids to human-meaningful purposes before driving them via cockpit terminal <id> … or cockpit browser <id> …. This page maps to the /cc slash mode used by the agentic flow.
Usage
cockpit connection list [--cwd PATH] [--all] [--json]
Only one subcommand exists today — list.
Flags
| Flag | Meaning |
|---|---|
--cwd PATH | Only list bubbles whose project cwd matches PATH (canonicalised). Use $PWD to scope to the current shell. |
--all | Include dead entries (exited terminals, disconnected browsers). Off by default. |
--json | Emit raw JSON instead of TAB-separated lines. |
Output (plain, TAB-separated)
<type> <shortId> <title-or-(none)> <projectCwd-or-?> <command-or-empty>
One row per bubble. <type> is term or browser.
Output (--json)
Array of:
{
"type": "term" | "browser",
"shortId": "abcd",
"title": "optional user-set label",
"projectCwd": "/abs/path",
"tabId": "…",
"command": "npm run dev",
"alive": true
}
Exit codes
| Code | Meaning |
|---|---|
0 | Bubbles found |
1 | No bubbles (after filters) |
2 | Usage / argument error |
3 | Cockpit server unreachable. Start it with cock <project-path>. |
Examples
# All live bubbles across all projects:
cockpit connection list
# Only this project's bubbles:
cockpit connection list --cwd $PWD
# Everything (including dead), as JSON for programmatic use:
cockpit connection list --all --json | jq
See also
cockpit terminal— drive a terminal bubble by id.cockpit browser— drive a browser bubble by id.
cockpit update
cockpit update upgrades Cockpit to the latest published version.
cockpit update
Equivalent to running:
npm install -g @surething/cockpit@latest
You can run either; they do the same thing.
What gets preserved
Everything in your Cockpit data folder (~/.cockpit/) is untouched by an upgrade:
- API keys and engine settings
- Sessions and pinned tabs
- Scheduled tasks
- Skills registry
- Notes
- Reviews
- The Chrome extension cache
Just the global npm package gets replaced.
After upgrading
Restart any running cockpit process to pick up the new version. If you have Cockpit open in a browser tab, refresh the page after restart.
To verify:
cockpit -v
If cockpit update fails
The most common cause is that npm install -g needs elevated permissions on your system (a globally-installed npm package is owned by root on some setups). If you see an EACCES error, run with sudo:
sudo npm install -g @surething/cockpit@latest
Or, better, fix your npm permissions once so you don't need sudo. The npm docs have a guide for this — search "resolving EACCES permissions errors npm".
Pinning to a version
To install a specific version instead of the latest:
npm install -g @surething/[email protected]
The version list is at npmjs.com/package/@surething/cockpit.
Downgrade
The same command — just give it the older version you want:
npm install -g @surething/[email protected]
Your data folder is forward and backward compatible across minor versions; downgrading is safe.