cappy.

Cappy CLI

cappy <command> [options]

Run from a project directory containing cappy.config.json, or pass -C <dir>. The public npm package is @uppercut-labs/cappy. Install it with npm install --global @uppercut-labs/cappy and run cappy, or use npx @uppercut-labs/cappy. Contributors should build the checkout with npm run build and invoke node packages/cli/bin/cappy.js <command> from the repository root.

Global options

Option Meaning
-j, --json Print exactly one JSON document on stdout. Progress text is suppressed; nothing contains ANSI escapes.
-C, --project <dir> Project directory (default: the current directory).
-c, --config <path> Configuration file, relative to the project (default: cappy.config.json).
-h, --help Show help.
-v, --version Show the Cappy version.

Shorthands

Every flag has a shorthand, and each shorthand is one whole token:

Shorthands never group: -nc is not -n -c, and -jh is an error. A value follows as the next argument (-pa difficulty=3) and is never translated, even when it starts with -. An unknown shorthand fails with USAGE_INVALID (exit 2).

cappy run boss_intro -pa difficulty=3 -p trailer -t 4 -j

Result envelope

Every command returns the same envelope, printed as JSON with --json:

{
  "schemaVersion": 1,
  "command": "run",
  "correlationId": "op_…",
  "ok": true,
  "data": { },
  "warnings": []
}

On failure, ok is false and error carries code, message, operation, bounded details, and retryable when known. data may still carry partial results, such as doctor checks or the state a capture reached.

Exit code Meaning
0 success
1 operation failed
2 invalid usage (bad flags, unknown session, existing take, invalid parameters)
3 invalid or missing configuration
4 missing or unusable external dependency (game command, FFmpeg, OBS)
70 internal error
130 cancelled

cappy doctor

Checks configuration, the managed workspace, the game command, FFmpeg/ffprobe, and OBS without launching the game or recording. Each check reports pass, warn, fail, or skip:

config, workspace.root, workspace, workspace.gitignore, game, ffmpeg, ffprobe, obs, obs.scenes

Warnings (such as an un-ignored .cappy/) do not fail doctor. OBS is only read (GetVersion, GetSceneList).

cappy scenarios

Launches the game, completes the handshake, and lists registered scenarios with their parameter schemas and required capabilities, plus the adapter's identity, build, and capabilities. Nothing is recorded. The game is stopped afterwards.

cappy run <scenario>

Captures an authored scenario.

Option Meaning
-pa, --param key=value Scenario parameter; repeatable. Parsed and validated against the adapter's schema; defaults are applied.
-p, --preset <name> Capture preset (default: defaultPreset, or a master-only preset). See presets.md for derivatives and event anchors.
-t, --take <n> Explicit take number. Fails with TAKE_EXISTS if that take already succeeded.

Pipeline: preflight (config, preset options, FFmpeg/ffprobe, workspace, OBS scene and idle state) before the game launches; scenario and parameter validation; take allocation; scenario session record; ready; confirmed OBS start; scenario start; events; completion; confirmed OBS stop; verified master moved into captures/<capture-id>/; ffprobe; derivatives; manifest. data.state is succeeded only when everything passed. Ctrl+C cancels (exit 130) and stops the recording Cappy started.

cappy record

Records a freeform, replayable session while you play.

Option Meaning
-d, --duration <seconds> Stop automatically.
-ca, --capture Also record an OBS master into the session.
-p, --preset <name> Preset whose scene --capture uses.

Press Enter to stop and keep the session; Ctrl+C cancels it (status cancelled, exit 130). The adapter's replay payload is stored byte-for-byte in sessions/<session-id>/replay.bin.

cappy replay <session-id>

Captures a stored session's replay through the same pipeline as run, with a manifest whose source is the replayed session. Options: -p, --preset, -t, --take, and:

Option Meaning
-nc, --no-capture Play the session back without OBS or FFmpeg, to verify it.

Before anything launches, the stored payload must still match its SHA-256 and the session must belong to this project. After the handshake, the adapter must match the recording adapter and advertise the capabilities the replay needs (replay, plus deterministic_replay when the recording had it). A different game build only warns.

cappy compare <capture-a> <capture-b>

Compares two successful captures of the same source. Usually these are replay captures of one session, made with two game builds. A is the reference.

cappy replay ses_…                       # with build 1
cappy replay ses_…                       # with build 2
cappy compare cap_A cap_B --min-ssim 0.97
Option Meaning
-ms, --min-ssim <score> Gate: the mean SSIM must be at least this score (0 to 1).
-mfs, --min-frame-ssim <score> Gate: every frame's SSIM must be at least this score, which catches a glitch lasting a few frames.
-mdm, --max-drift-ms <ms> Gate: no matched game event may drift more than ms either way.
-rse, --require-same-events Gate: every game event type must occur equally often in A and B.

Without gates, scores and the timeline diff are informational, and a completed comparison exits 0. If any gate fails, the comparison is recorded as regressed and exits 1 with COMPARISON_REGRESSED, whose details list the failed gates. The manifest (comparisonVersion: 2) records the gates given and the failedGates. A value out of range fails with USAGE_INVALID (exit 2) before any media work.

What compare does:

data carries:

Named builds and --build

builds in cappy.config.json names alternative launch commands for the game, for example two exports of it, or the same project with different arguments:

"game": { "command": "godot", "args": ["--path", "/games/demo"] },
"builds": {
  "v1": { "game": { "args": ["--path", "/builds/v1"] } },
  "v2": { "game": { "command": "/builds/v2/Demo.x86_64", "args": [] } }
}

cappy compare-builds <session | scenario> <build-a> <build-b>

Captures the same moment with two builds, then compares them:

cappy compare-builds ses_… v1 v2 --min-ssim 0.97 --require-same-events
cappy compare-builds boss_intro base v2 -pa difficulty=3

cappy timeline export <capture-id | session-id>

Exports a timeline for other tools:

cappy timeline export cap_… -fo vtt -o events.vtt   # captions over the capture's video
cappy timeline export cap_… -fo csv > events.csv
cappy timeline export ses_…                          # JSON on standard output
Option Meaning
-fo, --format <format> json (the default), csv, or vtt.
-o, --out <path> Write a new file (relative to the project) instead of standard output. An existing file is never overwritten (EXPORT_TARGET_EXISTS).

The formats:

Export files belong to you: Cappy never registers, overwrites, or deletes them. With --json, data carries the format, source, event count, and either the content or the path.

cappy clean [<id>...]

Deletes captures, sessions, comparisons, and logs that Cappy created, at once. See storage.md for exactly what each item covers and what is protected.

Option Meaning
<id>... Capture (cap_…), session (ses_…), or comparison (cmp_…) IDs.
-f, --failed Failed, cancelled, and interrupted captures, sessions, and comparisons.
-l, --logs Every command log.
-a, --all Every capture, session, comparison, and log.
-ot, --older-than <age> Only bulk-selected items older than an age such as 90m, 12h, 7d, or 2w. Alone, it applies to --all. It cannot be combined with IDs.
-dr, --dry-run Report what would be removed and change nothing.

data reports items (ID, kind, files, bytes), removed, missing, refused, kept, skipped, retiredTakes, and bytesFreed. With --dry-run, removed and bytesFreed describe what would happen. A refused file or ID makes the command exit 1 with CLEAN_INCOMPLETE, after everything else selected has been removed. A command with no selector, or with --older-than and IDs, exits 2.

Logs and correlation

record, run, and replay write logs/<correlation-id>.jsonl in the managed workspace. The same correlation ID is in the JSON result, the session, the manifest, and every timeline event.

Setup prompt

Read the guide