Command Book includes a CLI that lets you run saved commands, start them in the background, read back what they printed, and list, create, edit, delete, and monitor them directly from your terminal. Define a command once in Command Book and drive it from either the GUI or the CLI.
Every subcommand is scriptable. new and edit prompt when you run them bare, but pass them flags and they never touch stdin — so a setup script or an AI agent can create and maintain your command list, not just run it.
# Run a saved command in the foreground
$ commandbook run talk-python-dev
# Start it in the background and get your prompt back
$ commandbook start talk-python-dev
# See what a command printed — running or long finished
$ commandbook logs talk-python-dev --tail 50
# List all saved commands
$ commandbook list
# Show one command's full config + status
$ commandbook details talk-python-dev
# Run an arbitrary command without saving it
$ commandbook run --command "npm run dev" --name web --dir .
# Create a new command interactively
$ commandbook new
# Create one non-interactively (scriptable — never reads stdin)
$ commandbook new --name "Talk Python (dev)" --command "python app.py" --dir .
# Edit an existing command
$ commandbook edit talk-python-dev
# Change just one field, non-interactively
$ commandbook edit talk-python-dev --command "python -m flask run"
# Delete a command
$ commandbook delete talk-python-dev
# Check whether a command is running
$ commandbook status talk-python-dev
# Block until it is actually ready to serve
$ commandbook wait talk-python-dev --port 5000
# Stop a running command
$ commandbook stop talk-python-dev
# Open the GUI app
$ commandbook open
Why a CLI?
Command Book's GUI lets you configure commands with precision: working directories, pre-commands, environment variables, auto-restart behavior, and more. The CLI brings all of that to your terminal with zero extra setup.
When you run commandbook run <command-name>, the CLI executes the command exactly as if you clicked Run in the GUI. It reads the same saved configuration and replicates every detail:
- Working directory --
cds to the configured path before execution - Pre-commands -- Runs setup steps (like
git pull) before the main command - Environment variables -- Applies any configured env vars to the process
- Auto-restart -- Restarts on crash with the same delay you set in the GUI
- Login shell -- Uses your shell's PATH, aliases, and environment
Instead of remembering cd ~/projects/talk-python && git pull && python app.py --reload, you run:
$ commandbook run talk-python-dev
One command, fully configured, every time. Define it once in Command Book, run it from wherever you prefer -- the GUI or the terminal.
The CLI also opens the door to automation and integration. Shell scripts, CI pipelines, Claude Code, and other agentic coding tools can all invoke your saved commands by name: commandbook start to launch one in the background, commandbook wait to block until it is ready, commandbook logs to read what it printed, and commandbook stop to shut it down. Your carefully configured commands become accessible to any tool that can call a shell command.
To help your AI agent use the CLI effectively, add https://commandbookapp.com/docs/ai-guide.md to your project's agent instructions (e.g. CLAUDE.md, .cursorrules, or equivalent) -- it's a concise reference written specifically for LLMs. See the AI Agents guide for full setup with Claude Code, Codex, and Cursor.
Installation
The CLI ships inside the Command Book app bundle. Install it from the GUI:
From Settings
- Open Command Book
- Go to Settings (⌘,)
- Select the CLI Tools tab
- Click Install CLI Tools
From the Menu Bar
- Open Command Book
- Click File → Install CLI Tools
What Happens
The installer creates a symlink at ~/.local/bin/commandbook pointing to the CLI binary inside the app bundle. No admin privileges required.
If ~/.local/bin is not in your PATH, Command Book shows instructions to add it:
export PATH="$HOME/.local/bin:$PATH"
Add that line to your ~/.zshrc (or ~/.bashrc), then restart your terminal or run source ~/.zshrc.
Uninstalling
Use Settings → CLI Tools → Remove CLI Tools or File → Remove CLI Tools to remove the symlink.
Commands
commandbook list
Display all saved commands in a formatted table.
$ commandbook list
SLUG NAME COMMAND
────────────────────────────────────────────────────────────────────────────────
api-server API Server npm run dev
docker-postgres Docker Postgres docker compose up db
valkey-cache Valkey Cache valkey-server
talk-python-dev Talk Python (dev) python app.py
Commands are sorted alphabetically by name. If no commands exist, you'll see a message suggesting commandbook new.
commandbook run [slug]
Run a saved command in the foreground, attached to your terminal.
With a slug:
$ commandbook run talk-python-dev
Talk Python (dev)
────────────────────
Pre-command: git pull
Command: python app.py --reload
Directory: ~/projects/talk-python
────────────────────
▶ Running pre-command: git pull
Already up to date.
▶ Running: python app.py
* Serving Flask app 'app'
* Running on http://127.0.0.1:5000
^C
Interrupted.
Without a slug (interactive picker):
$ commandbook run
Select a command to run:
1) API Server npm run dev
2) Docker Postgres docker compose up db
3) Redis Cache redis-server
4) Talk Python (dev) python app.py
Enter number (1-4): 4
Without a saved command (ad-hoc):
Run an arbitrary command without saving it first by passing --command:
$ commandbook run --command "python -m http.server 8000" --name file-server --dir .
The process is fully managed — commandbook status file-server and commandbook stop file-server work just like a saved command — but it is not added to your saved command list, so one-off runs don't clutter the GUI sidebar. This is ideal for automation and AI agents that need to launch a throwaway process. Once it exits, it leaves no trace.
Options:
| Flag | Description |
|---|---|
--dir <path> |
Override the working directory (defaults to the current directory for ad-hoc runs) |
--command "<cmd>" |
Run an arbitrary command ad-hoc instead of a saved slug (not saved) |
--name <handle> |
Name/handle for an ad-hoc run, used by status/stop (derived from the command if omitted) |
--restart |
Stop the running instance first, then start a fresh one |
--allow-multiple |
Deliberately start another instance alongside the running one |
--detach, -d |
Run it in the background and return immediately — identical to commandbook start |
--detach-timeout <seconds> |
How long a --detach launch may take to come up (default 10) |
--detach-settle <seconds> |
How long a --detach launch must stay up before counting as started (default 0.75) |
--json |
Emit the launch result as JSON (with --detach) |
Already running? If the command is already up, run refuses rather than quietly starting a second copy:
$ commandbook run api-server
✖ api-server is already running (pid 71688, up 4m).
Use --restart to stop it first, or --allow-multiple to run another instance.
It exits 47, so a script or agent can branch on "already up" instead of ending up with two servers fighting over a port. Use --restart to replace it, or --allow-multiple when running several really is what you want — each instance is tracked separately, and a single stop reaps them all.
Behavior:
- Prints a header with the command name, working directory, and configuration
- Streams output to your terminal in real-time with full ANSI color support — and captures it, so
commandbook logscan show it to you again later - Runs through your login shell, so your PATH, aliases, and environment are available
- If the command has auto-restart enabled and crashes (non-zero exit), the CLI restarts it automatically after the configured delay
- Between restart attempts the command reports
restarting— it's still managed, still listed, and still stoppable - Ctrl+C sends SIGINT to the child process (intentional stop, no auto-restart)
SIGTERM(a plainkill) is an intentional stop too, and won't trigger a restart- Exit code matches the child process exit code
- Exits
404if the slug is unknown, or47if it's already running
Pre-commands run before the main command. If a pre-command fails (non-zero exit), the main command does not start.
commandbook start [slug] [--json]
Start a command in the background and get your prompt back. Same supervisor as run — pre-command, login shell, auto-restart, tracking — but it runs in its own session, so it outlives the terminal (or script, or AI agent) that started it. No &, no nohup, nothing to orphan.
$ commandbook start talk-python-dev
✓ Started Talk Python (dev) — pid 81234 (detached).
Follow output: commandbook logs talk-python-dev --follow
Check status: commandbook status talk-python-dev
Stop: commandbook stop talk-python-dev
commandbook run <slug> --detach does exactly the same thing, if you prefer to keep one launch verb.
It shows up in the app, too. A command started this way lights up its row in Command Book within half a second — live status, uptime, and its output streaming into the detail pane — so the window and the terminal always agree about what is running. Stopping it from either side works.
"Started" means started. start doesn't return until it has actually seen the process register and stay up for a moment. A command that dies on the way up — a port already taken, a missing environment variable — is reported as a failure, with its exit code and the tail of what it printed:
$ commandbook start api-server
✖ Error: API Server exited with code 3 before it finished starting.
booting...
fatal: port 5000 already in use
Full output: commandbook logs api-server
That's a liveness check, not a readiness check. To wait until the server actually answers, follow it with commandbook wait api-server --port 5000.
A command that simply finishes quickly and cleanly isn't an error — start reports ran and exited cleanly (code 0) and exits 0.
Options:
| Flag | Description |
|---|---|
--command "<cmd>" |
Start an arbitrary command ad-hoc instead of a saved slug (not saved) |
--name <handle> |
Name/handle for an ad-hoc run, used by logs/status/stop |
--dir <path> |
Override the working directory |
--restart |
Stop the running instance first, then start a fresh one |
--allow-multiple |
Deliberately start another instance alongside the running one |
--timeout <seconds> |
How long to wait for it to come up before giving up (default 10) |
--settle <seconds> |
How long it must stay up before counting as started (default 0.75) |
--json |
Emit { slug, name, state, pid, runId, exitCode, startedInSeconds } |
Unlike run, a bare commandbook start with no slug is rejected rather than opening a picker — it hands your prompt straight back, so there's nobody to answer it.
Exit codes: 0 started (or ran and exited cleanly) · 47 already running · 124 timed out waiting for it to come up · 44 slug not found · otherwise the command's own exit code if it died while starting.
commandbook details <slug> [--json]
Show a single saved command's full configuration — the same fields as the app's edit page — together with its current run status. Use it to inspect exactly what a slug will run before launching it; list only shows a truncated command, and status only shows whether it's running.
$ commandbook details talk-python-dev
Talk Python (dev) ● running
────────────────────────────
Slug talk-python-dev
Command python app.py --reload
Pre-command git pull
Working dir ~/projects/talk-python
Environment 2 vars: NODE_ENV, PORT
Auto-restart on (5s delay)
Created 2026-05-01 14:22
Updated 2026-06-10 09:03
Last run 2026-06-18 08:40
PID UPTIME SOURCE
───────────────────────
81234 12m app
With --json, emits the full configuration plus a nested status block (the same shape as status --json):
$ commandbook details talk-python-dev --json
{
"slug" : "talk-python-dev",
"name" : "Talk Python (dev)",
"command" : "python app.py --reload",
"preCommand" : "git pull",
"workingDirectory" : "~/projects/talk-python",
"environmentKeys" : [ "NODE_ENV", "PORT" ],
"autoRestart" : true,
"restartDelaySeconds" : 5,
"customIconType" : "python",
"createdAt" : "2026-05-01T14:22:00Z",
"updatedAt" : "2026-06-10T09:03:00Z",
"lastRunAt" : "2026-06-18T08:40:00Z",
"status" : { "state" : "running", "instances" : [ { "pid" : 81234, "startedAt" : "2026-06-18T08:40:00Z", "source" : "app", "uptimeSeconds" : 720 } ] }
}
Privacy: environment variable values are never printed — only their names (environmentKeys). To view or change a value, open the command in the GUI editor.
Exit codes: 0 when the command exists (running or stopped) · 404 if the slug is not found.
commandbook new
Create a new command. It runs interactively when you pass no flags, and fully non-interactively when you pass --name and --command — in flag mode it never reads stdin, so it's safe to run from a script, a Makefile, or an AI agent with stdin closed.
Non-interactive (flag mode)
$ commandbook new --name "Talk Python (dev)" --command "python app.py" \
--dir ~/projects/talk-python --pre-command "git pull" --icon python --auto-restart
✓ Command 'talk-python-dev' created.
| Flag | Meaning |
|---|---|
--name <string> |
Required. Display name; the slug is derived from it |
--command "<cmd>" |
Required. Stored verbatim — it runs through your login shell, so pipes and && are fine |
--dir <path> |
Working directory. Defaults to the current directory |
--pre-command "<cmd>" |
Runs before the main command each time; a non-zero exit aborts the run |
--env KEY=VALUE |
Repeatable. Values are stored but never printed back |
--auto-restart / --no-auto-restart |
Restart after a crash. Defaults to off |
--restart-delay <seconds> |
Delay before an auto-restart (default 5) |
--icon <type> |
Custom sidebar icon, e.g. python, docker, granian. See Icon types below |
--if-not-exists |
Exit 0 as a no-op if the slug already exists, instead of failing |
--json |
Emit the created command as JSON — the same shape as details --json |
Everything is validated before anything is written, so a rejected command never leaves a half-created entry behind.
Already exists? If a command already owns that slug, new refuses and exits 45 rather than creating a confusing duplicate:
$ commandbook new --name "Talk Python (dev)" --command "python app.py"
Error: Command 'talk-python-dev' already exists. Pass --if-not-exists to make this a
no-op, or choose a different --name.
Add --if-not-exists to make setup scripts idempotent — running them twice is then harmless:
$ commandbook new --name "Talk Python (dev)" --command "python app.py" --if-not-exists
Command 'talk-python-dev' already exists — nothing to do.
Chaining with --json. The JSON is the same object details --json returns, so you can create a command and immediately run it without a second lookup:
$ SLUG=$(commandbook new --name "API Server" --command "npm run dev" --json | jq -r .slug)
$ commandbook run "$SLUG" &
$ commandbook wait "$SLUG" --port 3000
Where it shows up. A new command is saved to your command list right away — it appears in commandbook list and in the app's ⌘K palette immediately, even while Command Book is running. It joins the sidebar the first time you actually run it, since the sidebar tracks processes rather than saved commands.
Exit codes: 0 created (or a no-op with --if-not-exists) · 45 a command already owns that slug · 64 invalid arguments (a missing --name/--command, or a bad --dir, --icon, or --env).
Icon types
--icon takes a command type, not an SF Symbol name — Command Book maps the type to the right symbol and color. Pass an invalid value and the CLI lists every accepted one. The available types cover languages and runtimes (python, node, ruby, go, rust, java, php, swift, bun, deno, …), package managers (npm, yarn, pnpm, pip, uv, poetry, cargo, …), containers and infrastructure (docker, podman, kubectl, terraform, ansible), cloud CLIs (aws, gcloud, azure, digitalocean), servers and frameworks (nginx, hugo, vite, granian, uvicorn, gunicorn, flask, django, fastapi, next, …), databases (postgres, mysql, redis, mongodb, sqlite, valkey), version control (git, gh), shells (bash, zsh, fish, make), testing (pytest, jest, mocha, rspec), and generic.
Leave --icon off and Command Book picks an icon automatically from the command text, which is usually what you want.
Interactive
$ commandbook new
Create a new command
────────────────────
Name: Talk Python (dev)
Command: python app.py
Working directory [/Users/you/projects/talk-python]: ~/projects/talk-python
Pre-command (optional): git pull
Auto-restart on crash? (y/N): y
✓ Command 'talk-python-dev' created successfully.
Prompts:
- Name (required) -- Display name for the command
- Command (required) -- The shell command to run
- Working directory (optional) -- Defaults to current directory
- Pre-command (optional) -- Runs before the main command each time
- Auto-restart on crash (optional) -- Defaults to No
- Auto-restart delay (if auto-restart is yes) -- Seconds, defaults to 5
The CLI validates that command executables exist in your PATH and warns (but still allows saving) if they're not found:
Command: assetbuilder --run build_assets.py && python app.py --reload
⚠ Warning: 'assetbuilder' not found in PATH. The command may fail at runtime.
commandbook edit <slug>
Change a saved command. Like new, it prompts when you pass no flags and is fully non-interactive when you pass any.
Non-interactive (flag mode)
Every flag from new works here, all of them optional. Only the fields you actually pass change — everything else is left exactly as it was:
$ commandbook edit talk-python-dev --command "python -m flask run" --restart-delay 3
✓ Command 'talk-python-dev' updated.
A few flags behave slightly differently than on new:
--env KEY=VALUEmerges into the existing variables rather than replacing them, so adding one variable doesn't wipe the rest. Repeat the flag to set several at once.--clear-envremoves all existing variables. Combine it with--envto replace the whole set outright:--clear-env --env API_KEY=new.--pre-command ""— an explicit empty string — clears the pre-command.
Renaming changes the slug, since slugs are derived from names. In flag mode there's no confirmation prompt; the new slug is reported instead:
$ commandbook edit talk-python-dev --name "Talk Python (development)"
✓ Command updated. Slug changed: talk-python-dev → talk-python-development
As with new, all arguments are validated before anything is written, so a rejected edit leaves the command untouched. Edits show up in a running Command Book immediately.
Exit codes: 0 updated · 404 slug not found · 64 invalid arguments.
Interactive
Current values are shown in brackets -- press Enter to keep them.
$ commandbook edit talk-python-dev
Edit command: Talk Python (dev)
────────────────────────────────
Name [Talk Python (dev)]: Talk Python (development)
Command [python app.py]: python -m flask run
Working directory [~/projects/talk-python]:
Pre-command [git pull]:
Auto-restart on crash? (y/N) [n]: y
Auto-restart delay (seconds) [5]: 3
✓ Command 'talk-python-development' updated successfully.
If the name changes and the slug would change, the CLI confirms:
⚠ Slug will change: talk-python-dev → talk-python-development
Proceed? (Y/n):
commandbook delete <slug> [--force] [--json]
Remove a saved command from your command list.
$ commandbook delete talk-python-dev
✓ Command 'talk-python-dev' deleted.
delete never asks for confirmation, on a terminal or otherwise — it's designed to behave identically whether a person or a script runs it. Instead of prompting, it refuses the one case that would actually be dangerous.
It won't delete a command that's running. Deleting the record while its process is still alive would leave you with a server holding a port and no name to stop it by, so delete refuses and exits 46:
$ commandbook delete talk-python-dev
Error: 'talk-python-dev' is running (1 instance). Stop it first, or pass --force to
stop and delete.
Either stop it yourself first, or let --force do both — it stops the process the same way commandbook stop does (SIGINT, then SIGTERM, then SIGKILL) and then deletes:
$ commandbook delete talk-python-dev --force --json
{
"slug" : "talk-python-dev",
"name" : "Talk Python (dev)",
"deleted" : true,
"stoppedInstances" : 1
}
Deleting removes the saved command only; it does not touch your ad-hoc command history. The command disappears from a running Command Book's ⌘K palette immediately.
Exit codes: 0 deleted · 404 slug not found · 46 the command is running and --force wasn't passed.
commandbook status [slug] [--json]
Show whether a saved command is currently running. Without a slug, lists every process Command Book manages.
With a slug:
$ commandbook status talk-python-dev
Talk Python (dev) ● running
PID UPTIME STATE SOURCE
───────────────────────────────────
81234 12m running app
Without a slug (list all):
$ commandbook status
NAME STATE PID UPTIME SOURCE
────────────────────────────────────────────────────────
API Server running 81234 12m app
Talk Python (dev) restarting 82345 3m cli
Machine-readable output (--json):
$ commandbook status talk-python-dev --json
{
"slug" : "talk-python-dev",
"state" : "running",
"instances" : [
{
"pid" : 81234,
"startedAt" : "2026-06-17T10:02:00Z",
"source" : "app",
"uptimeSeconds" : 720,
"state" : "running"
}
]
}
state is running, restarting, or stopped. restarting is not stopped: it means the process is momentarily down but its supervisor is alive and about to start it again, so the command is still managed and still stoppable. It exits 0, like running. If you need the server actually answering rather than merely alive, use commandbook wait --port or --http.
Options:
| Flag | Description |
|---|---|
--json |
Emit structured JSON instead of a human-readable table |
Exit codes (scriptable, HTTP-flavored):
| Code | Meaning |
|---|---|
0 |
Running or restarting — at least one live instance found |
204 |
Known but not running (the command exists, nothing is live) |
44 |
Slug not found (printed as 404 in the message/JSON) |
244 |
Runtime registry error (printed as 500) |
For the no-arg form (commandbook status), exit code is always 0 — it's a listing, not a predicate.
Framing for agents:
Command Book is the front door for starting and stopping long-running servers. If all processes are launched through Command Book (CLI run or the GUI app), then status always has a truthful answer. AI agents (Claude, etc.) can interrogate the environment cleanly:
if commandbook status django-project; then
echo "Server is up"
else
echo "Server is not running"
fi
commandbook wait <slug> [--for running|stopped] [--port n] [--http url] [--timeout s] [--json]
Blocks until a command reaches a state, then returns the instant it does. The timeout is only a ceiling — never a fixed delay. This is the clean replacement for until commandbook status <slug>; do sleep 1; done, which has no timeout and can't distinguish "still booting" from "already crashed."
$ commandbook start talk-python-dev
$ commandbook wait talk-python-dev --http http://127.0.0.1:5000
● talk-python-dev — ready (http://127.0.0.1:5000) in 0.4s
What it waits for:
| Form | Returns when |
|---|---|
commandbook wait <slug> |
the process is registered and alive (or has already exited cleanly) |
… --port <n> |
a TCP connection to 127.0.0.1:<n> succeeds |
… --http <url> |
the URL returns a 2xx/3xx response |
… --for stopped |
the command is no longer running (returns its exit code) |
Options:
| Option | Description |
|---|---|
--for <running\|stopped> |
Condition to wait for (default running) |
--port <n> |
Ready only once this TCP port on 127.0.0.1 accepts a connection |
--http <url> |
Ready only once this URL returns 2xx/3xx (e.g. http://127.0.0.1:5000) |
--timeout <seconds> |
Ceiling before giving up (default 30) |
--interval <seconds> |
Poll cadence (default 0.1) |
--json |
Emit structured JSON instead of a human-readable line |
--port and --http make running mean ready: wait returns only once the process is alive and the endpoint answers — so you can drop a separate curl-retry loop. If the process exits before the endpoint comes up, wait fails fast rather than waiting out the timeout. (For http:// localhost, wait speaks raw HTTP so it isn't subject to App Transport Security.)
A plain commandbook wait <slug> also returns 0 if the command has already exited cleanly — handy for short commands. A non-zero exit is propagated as wait's own exit code. Use --for stopped to block until a one-shot command finishes and read its exit code.
Exit codes:
| Code | Meaning |
|---|---|
0 |
Condition met (running / ready / clean exit / stopped) |
124 |
Timed out before the condition was met |
205 |
--http/--port target exited before becoming ready |
44 |
Slug not found (printed as 404) |
244 |
Runtime registry error (printed as 500) |
JSON output (--json) carries state (running · ready · exited · stopped · timeout), waitedSeconds, an exitCode once the command has ended, and the live instances:
{ "slug": "talk-python-dev", "state": "ready", "waitedSeconds": 0.34,
"instances": [ { "pid": 81234, "startedAt": "2026-06-18T08:40:00Z", "source": "cli", "uptimeSeconds": 0 } ] }
commandbook logs <slug> [--tail n] [--follow] [--grep regex] [--since d] [--run n] [--runs] [--json]
Show the output Command Book captured for a command. It works on a process that's still running, on one that finished hours ago, and on one you started in the app — captured output is written to a shared store by every run Command Book manages, so it outlives both the process and the app itself.
$ commandbook logs talk-python-dev --tail 3
10:02:01 * Running on http://127.0.0.1:5000
10:02:07 127.0.0.1 - "GET / HTTP/1.1" 200
10:02:09 127.0.0.1 - "GET /api/episodes HTTP/1.1" 200
This is what closes the loop for background work: start a server, go do something else, and come back to find out what it said.
$ commandbook start api-server
$ commandbook logs api-server --follow # stream it live, Ctrl+C to detach
$ commandbook logs api-server --grep "ERROR" # just the bad news
$ commandbook logs api-server --since 5m # just the last five minutes
Options:
| Option | Description |
|---|---|
--tail <n> |
Show the last N lines (default 200) |
--all |
Show the entire retained buffer for the run |
--follow, -f |
Print the tail, then stream new lines as they arrive |
--since <duration> |
Only lines newer than 30s, 5m, 2h, 1d, or an ISO-8601 timestamp |
--grep <regex> |
Only lines matching a regular expression (case-insensitive) |
--match-case |
Make --grep case-sensitive |
--run <n> |
Which run to read: 1 is the current/most recent, 2 the one before it |
--runs |
List the retained runs instead of their output |
--no-timestamps |
Drop the per-line time prefix |
--json |
Emit structured line records (NDJSON under --follow) |
JSON output. --json emits an array of { seq, ts, stream, text } records — one object per line under --follow (NDJSON), flushed as each line lands. Combined with --runs, it emits run metadata instead: { run, runId, name, source, state, startedAt, endedAt, exitCode, lineCount }.
Earlier runs. Command Book keeps the last several runs of each command, so you can look back at the one before the restart:
$ commandbook logs talk-python-dev --runs
RUN STARTED ENDED EXIT LINES SOURCE
──────────────────────────────────────────────────────────────
1 Jun 28 10:02 — (running) — 1240 cli
2 Jun 28 09:41 Jun 28 09:58 1 8821 app
$ commandbook logs talk-python-dev --run 2 --grep Traceback
SOURCE tells you where a run came from: cli for run/start, app for one you launched in the Command Book window. It works in both directions: the app can read a CLI run's output, and logs can read the output of something you started in the app.
Streams. Each line is tagged out (stdout), err (stderr), or sys (Command Book's own markers — the launch header, restart notices, and the closing exit line). On a terminal, stderr is shown in red and ANSI colors from the process are preserved; piped to a file or a script, ANSI is stripped so the text stays clean.
How much is kept. By default the last 5 runs per command, each capped at 100,000 lines. Both the capture itself and the number of runs are configurable in Settings → General → Output History. Turn capture off and nothing is recorded — logs then exits 204 for a known command, which means "nothing captured", not "it printed nothing".
Exit codes:
| Code | Meaning |
|---|---|
0 |
Output returned (or followed until the run ended) |
204 |
The command is known, but nothing was captured |
44 |
Slug not found, or --run n is out of range (printed as 404) |
244 |
Output store error (printed as 500) |
commandbook stop [slug] [--all] [--force]
Stop running command(s) that Command Book manages.
With a slug:
$ commandbook stop talk-python-dev
Stopped Talk Python (dev) (pid 81234).
Stop everything:
$ commandbook stop --all
Stopped API Server (pid 82345).
Stopped Talk Python (dev) (pid 81234).
2 processes stopped.
Force-kill immediately:
$ commandbook stop talk-python-dev --force
Stopped Talk Python (dev) (pid 81234).
Machine-readable output (--json):
$ commandbook stop talk-python-dev --json
{
"slug" : "talk-python-dev",
"requested" : 1,
"stopped" : [ { "pid" : 81234, "name" : "Talk Python (dev)", "signal" : "SIGINT" } ],
"failed" : [],
"exitCode" : 0
}
signal is the signal that actually stopped each process (SIGINT, SIGTERM, or SIGKILL); anything that survived is listed under failed. For stop --all --json, slug is null and the arrays aggregate every target.
Options:
| Flag | Description |
|---|---|
--all |
Stop every running Command Book process |
--force |
Skip the graceful signal sequence and send SIGKILL immediately |
--json |
Emit a machine-readable result (slug, requested count, stopped/failed, exit code) |
Behavior:
- Sends
SIGINT → SIGTERM → SIGKILLto the process group (dev-server-friendly: Django, Flask, and similar tools do their cleanest shutdown on Ctrl+C) - Sets a stop-requested flag in the runtime registry before signaling, so auto-restart is suppressed — both in the CLI
runloop and in the GUI app - Works whether or not the GUI app is running (reads the registry and signals directly)
- Stops ad-hoc processes (started with
run --command) by their--nameslug, too
Exit codes:
| Code | Meaning |
|---|---|
0 |
Stopped at least one instance |
1 |
Matched live processes but none could be killed |
204 |
Known but not running (nothing to stop) |
44 |
Slug not found (printed as 404) |
244 |
Runtime registry error (printed as 500) |
commandbook open
Open the Command Book GUI application.
$ commandbook open
Opening Command Book...
If Command Book is already running, it brings the window to the foreground.
commandbook --help
Show help for all commands. Every subcommand has its own detailed help too — commandbook help <subcommand> (or commandbook <subcommand> --help) prints its full usage, flags, and exit codes.
$ commandbook --help
OVERVIEW: Command Book CLI - Run, start, inspect, read the output of, and stop saved commands from
your terminal
USAGE: commandbook <subcommand>
OPTIONS:
--version Show the version.
-h, --help Show help information.
SUBCOMMANDS:
list List all saved commands
run Run a saved command, or an arbitrary one with --command (interactive
picker if no slug)
start Start a command in the background and return as soon as it is up
new Create a new saved command (interactive, or non-interactive with flags)
edit Edit a saved command (interactive, or non-interactive with flags)
delete Delete a saved command
open Open the GUI app
status Show whether a saved command is running (no slug = list all)
wait Block until a command is running, ready, or stopped — returning the
moment it is
logs Show the captured output of a command — running or already finished
stop Stop running command(s) Command Book manages
details Show a saved command's full configuration and current status
See 'commandbook help <subcommand>' for detailed help.
commandbook --version
Show the version number (matches the GUI app version).
$ commandbook --version
Command Book 1.1.54
Slugs
Slugs are URL-style identifiers generated from command names. They're how you reference commands in the CLI.
| Name | Slug |
|---|---|
| Talk Python (dev) | talk-python-dev |
| API Server | api-server |
| Docker - Postgres DB | docker-postgres-db |
| My App v2.0 | my-app-v2.0 |
Slugs are always unique. If two commands would produce the same slug, a numeric suffix is appended: talk-python-dev, talk-python-dev-2, talk-python-dev-3.
Slugs aren't stored — they're derived from the command's name every time it's read, numeric suffix and all. That's why new and edit have no --slug flag: to control the slug, choose the --name. It also means renaming a command changes its slug, so scripts that hard-code a slug should be updated after a rename (edit prints the new one).
Use commandbook list to see all slugs.
Shared Database
The CLI and GUI share the same SQLite database. Commands you create in the GUI appear in commandbook list, and commands you create with commandbook new appear in the GUI. Changes in either are immediately visible to the other — you don't need to restart Command Book after a new, edit, or delete from the terminal. The app picks the change up as it happens, so a command you script into existence shows up in the ⌘K palette right away, and a running process whose command you edited updates its name, working directory, and icon to match.
The database location follows the GUI's storage settings. The storage location is shown in Settings → Storage, the CLI uses the same path.
Troubleshooting
PATH: commandbook: command not found
The CLI is installed at ~/.local/bin/commandbook. If your shell doesn't find it, add ~/.local/bin to your PATH:
# Add to ~/.zshrc or ~/.bashrc
export PATH="$HOME/.local/bin:$PATH"
Then restart your terminal or run source ~/.zshrc.
You can verify the install in Settings → CLI Tools, which shows the PATH status.
Database Not Found
Error: Command Book database not found. Please run the app first to initialize.
This means the CLI can't find the SQLite database. Open Command Book at least once to create it. If you've moved the database via Settings → Storage, the CLI reads the same setting automatically.
Command Not Found (Slug)
Error: Command 'foo' not found. Run 'commandbook list' to see available commands.
Check the exact slug with commandbook list. Slugs are generated from command names and may differ from what you expect (e.g., "My API Server" becomes my-api-server).
Working Directory Doesn't Exist
Error: Working directory '/path/to/dir' does not exist.
The saved working directory has been moved or deleted. Update it with commandbook edit <slug> or use the --dir flag to override:
commandbook run my-command --dir ~/new/path
CLI Not Working After Moving the App
The CLI symlink points to the binary inside the app bundle. If you move Command Book.app to a different location, reinstall the CLI tools from Settings → CLI Tools or File → Install CLI Tools.