Skip to main content
The Jam CLI runs every Jam read and write from your terminal, and records new Jams from a window or a display. Authenticate once, then pipe Jam data into shell scripts, CI jobs, or AI coding agents. It is built for agents as much as for people. A coding agent already lives in a terminal, which is the one place the Chrome extension cannot reach.

What you can do with it

Hand a bug report to your agent

A Jam is a bug report with the evidence still attached. Your agent reads the same recording a person watches: the failed requests, the console errors, the clicks that led there, and the frames of what was on screen.
It starts from what happened instead of from a paraphrased ticket. Set up your coding agent installs the skills that teach it this loop.

Turn a Playwright failure into a watchable Jam

Playwright already writes a video and a trace for every test. Point the CLI at both, and the Jam plays that video with the run’s console logs and network requests lined up against the timeline. Scrub to the moment the test failed and read the 500 that caused it.
The trace also carries what the test typed, so the Jam shows each fill as a typing action, with passwords and other sensitive fields masked. Post the link on the CI job and reviewers watch the failure instead of reading a stack trace. See Create and update Jams.

Prove a change works

jam record run records the screen while a command runs, then uploads and prints a link. Wrap a test, a script, or the steps an agent drives. It runs on macOS and Linux.
The pull request carries a video of the fix working, not a claim that it does. The wrapped command keeps its own exit code, so a failing test still fails the pipeline and still leaves a Jam behind. See Record a window or the desktop.
The CLI runs on macOS, Linux, and Windows (x64 and arm64). Windows publishes an x64 binary, and Windows on ARM runs it under the built-in x64 emulation. You can also run the CLI under Windows Subsystem for Linux (WSL). On WSL, authenticate with a personal access token (see Authenticate).

Install

Run the installer:
The script detects your OS and architecture, downloads the matching binary into ~/.local/bin/jam, and adds that directory to your shell PATH. Open a new shell or source your rc file, then confirm the install:

Creating video Jams needs ffmpeg

jam create jam extracts the poster image and probes the audio track with ffmpeg/ffprobe when you create a video Jam. Every other command works without it. Install ffmpeg if you plan to create video Jams from the CLI:
Or skip ffmpeg entirely by passing posterImagePath, durationMs, width, height, and micEnabled explicitly on the create payload.

Recording with jam record

jam record captures a window or the whole desktop and uploads it as a video Jam. Nothing has to be a browser. A desktop app, a terminal, a simulator, an editor: they all record the same way. Use it from a script, a CI job, or a coding agent that needs to show its work. The Chrome extension remains the capture path in the browser. jam doctor reports recording readiness and names any missing packages.
jam record runs on macOS and Linux. On Windows the command exits with a message that the platform is unsupported. Everything else the CLI does still works there.
On macOS, a window is captured on its own (it can sit behind other windows). The CLI draws a lime outline around the recorded window on your screen. That outline is not in the video. On Linux, keep the target window uncovered.

Authenticate

Every read and write command needs an authenticated session. The CLI supports two modes.
On WSL, use a personal access token. The browser login flow expects your browser and the CLI to share the same local address, which WSL splits between Windows and Linux, so token auth is the reliable path for now.
Run:
The CLI opens an OAuth flow in your default browser, exchanges the authorization code for access and refresh tokens, and stores them in ~/.config/jam/credentials.json.

Check auth status

Prints the authenticated user, workspace, and auth method. Pass --json to consume the same data from a script.

Log out

Revokes tokens server-side where supported and clears the local credential store.

Where credentials live

The CLI stores credentials at ~/.config/jam/credentials.json with 0600 permissions, the same model used by gh, aws, gcloud, and other major developer CLIs. Bypass the credential file entirely by setting JAM_TOKEN in your shell. The CLI uses the env-var token for the lifetime of the process and never writes it to disk.

First steps

After install and auth, run this short loop to confirm the CLI talks to your workspace:
auth status confirms the CLI can read the stored token. list jams returns a page of Jams from your workspace. get jam walks a single Jam by ID. From there, set up your coding agent, or scan the command reference for the command you need.

Set up your coding agent

After you install the CLI and authenticate, install both bundled skills:
jam skills install writes skill files into the place your agent runtime reads them from (Claude Code, Cursor, Codex, OpenCode). A skill is a short Markdown guide the agent loads when the work matches it. It tells the agent which command to run, what the output means, and when to stop. Without it, an agent handed a Jam link guesses at flags. With it, the agent knows to read the console errors first, to look at the frames when the logs are not enough, and to page with --after. The CLI detects the runtime from environment variables, falls back to project marker directories, and defaults to Claude Code. Preview the destination with jam skills path, or name one with --target.

What the skills teach

jam-cli covers reading and writing. Pick the right jam get, filter to the events that matter, paginate, leave a comment, interpret an exit code. jam-proof covers recording. Pick a window or a display, wrap the verification command, hand back the link. It also carries the habits that make a recording worth watching, such as starting from a known state, keeping it under a minute, and titling the Jam with the claim it proves.

The loop

With both skills installed, a Jam link in your prompt is enough to start:
  1. You paste a Jam link. The agent runs jam get jam <id>, then jam get console <id> --level error and jam get network <id> --status 5xx to find the failure.
  2. It runs jam get frames <id> --overview when it needs to see the screen and not only read logs.
  3. It fixes the code.
  4. It chooses a dedicated browser window from jam record windows.
  5. It records that window while a verification command drives it: jam record run --window-id <id> --title "Checkout completes" -- <verification command>.
The CLI uploads the recording and prints a Jam link. The agent returns the link and comments it on the original Jam with jam create comment. This loop needs no browser extension or manual keyboard input. For a bug fix, ask for two Jams: one of the bug, one of the fix.
Jam also has a Model Context Protocol (MCP) server, which gives an agent Jam context without installing the Jam CLI. Use MCP when your agent only needs the bug context. Use the CLI when it also needs to record proof, run in CI, or drive Jam from a shell script.

Wrapping the CLI yourself

Skip the skills and write your own tool wrappers if you prefer. jam agent-context prints the full command surface as JSON: argument types, flag enums, default limits, and output shapes. Pair it with --json on every call so output stays parseable wherever the command runs.
The shape is locked by a snapshot test, so the JSON stays stable across releases inside the same schema_version.

Command reference

Every command supports --help. The machine-readable surface (argument types, flags, output shapes) lives at jam agent-context.

Read Jam data

Three commands return different views of the same Jam:
  • jam get jam <id> returns the top-level record (title, author, URL, dates, folder, and kind-specific data).
  • jam get metadata <id> returns structured metadata events emitted by the page via the jam.metadata() SDK call.
  • jam get intents <id> returns the structured summary (what the user was trying to do, observed issues, impact). It returns { "status": "not_requested", "value": null } when no summary is available. Treat that as absence, not an error.
Three commands return slices of the captured event stream:
  • jam get console <id> [--level error|warn|info|debug|log]
  • jam get network <id> [--status 5xx|<code>] [--method GET|POST|...] [--host <substring>] [--content-type <ct>]
  • jam get events <id> returns the unfiltered event stream.
Every filter accepts a comma-separated list, for example --level error,warn or --status 401,5xx. All three accept --limit (default 50, max 500) and --after <cursor> for pagination. Three media reads:
  • jam get transcript <id> returns { status, vtt }. vtt is null while generation is pending.
  • jam get chapters <id> returns { status, chapters, language }. chapters is null unless status is ready, and status is not_requested when generation was never queued. Chapters are text only, so to see what one looked like on screen run jam get frames <id> --at <ms> at a timestamp inside its span.
  • jam get screenshots <id> --out <dir> downloads the Jam’s images into <dir>. For screenshot Jams that’s the primary and secondary screenshots, for video Jams it’s the poster image, and for Instant Replay Jams it’s the captured screenshot.

Video frames

jam get frames <id> saves still frames from a video Jam as jpgs, so you or an agent can see what was on screen instead of only reading the transcript. Frames land in --out (default ./jam-frames/<id>/) and the command prints the saved paths as JSON.
The mode depends on the flags:
  • Overview grid. --overview. Saves one grid image with frames evenly spaced across the whole video, each cell labeled with its timestamp. The frame count scales with duration (6 for short clips up to 16 for long ones). Best for orienting before you know which moment you care about.
  • Timestamps. --at <ms>, single or comma-separated. Saves one jpg per timestamp.
  • Window. --from <ms> --to <ms> --count <n>. Saves N evenly-spaced frames across the range.
--size accepts small, medium, or large (default medium) and sets the frame height. When frames aren’t available (a screenshot Jam, or a video not hosted on Cloudflare Stream), the command prints the reason to stderr and exits non-zero.

List workspace collections

--type accepts screenshot, video, replay, or unknown. --order-by accepts createdAt or updatedAt. --limit defaults to 20 (max 500). All three list commands accept --after <cursor> for pagination. See jam list jams --help for all filters.

Create and update Jams

Create a screenshot Jam from a JSON payload:
The payload requires url, screenDimensions, and exactly one screenshot source (screenshotPath, screenshotDataUrl, or screenshotMediaId). To create a video Jam, set kind to "video" and provide videoPath. If you omit posterImagePath, the CLI extracts the video’s first frame locally with ffmpeg. A video Jam can also carry the console logs and network requests from a Playwright test run. Point playwrightTracePath at the trace.zip Playwright wrote, and the CLI parses it and attaches the events to the Jam, synced to the video timeline:
Headers and request bodies are redacted before upload, the same way the browser extension redacts them during a recording. If the trace cannot be read, the CLI reports it and still creates the Jam, without events. The create payload has no HAR field. Attach console and network events from a Playwright trace.zip with playwrightTracePath. The trace also records every fill, type, pressSequentially and insertText the test ran. The Jam shows each of those as a typing action, with the text it typed. A password field, a one-time-code or credit-card field, and any field whose name, id, autocomplete or placeholder reads as sensitive show *** instead. So does text the CLI cannot attribute to a field, which includes everything typed through keyboard.insertText. To avoid escaping a large JSON blob on the command line, read the payload from a file with an @ prefix, or pipe it in on stdin:
Run jam create jam --help for both payload shapes, or jam agent-context for the full machine-readable JSON Schema (under create.jam, on the source arg). Comment on a Jam, then keep that comment current as you learn more instead of posting a second one:
<body> is Markdown. --at pins the comment to a video timestamp in milliseconds, and only create accepts it. An edit replaces the body entirely, keeps the timestamp the comment was created with, and works only on comments you authored. <commentId> is the id the create call returned, and the share URL it printed works too. React to a comment, or take the reaction back:
Reactions are one of 🐛 💜 ✅ 👀 ❓ 👏 🔥 👍, the same set the share page offers. Both commands are idempotent, and removing only clears your own reaction. Rename a Jam, rewrite its description, or move it to a folder:
Pass at least one flag. Only the fields you pass change. --folder takes a folder name, its short ID, or its UUID. Pass an empty string to remove the Jam from its current folder. --description takes Markdown, and an @mention of a teammate’s email notifies them. Editing the title or description needs an Admin or Creator role; moving folders does not.

Create and rename folders

create folder returns { id, shortId, name }, so you can file a Jam into the new folder straight away with jam update jam <id> --folder <folder>. Folder names are not unique. Run jam list folders first if you mean to reuse an existing folder rather than add another one with the same name. update folder accepts a folder name, its short ID, or its UUID. Renaming leaves the folder’s Jams and short ID untouched.

Delete Jams, comments, and folders

Deleting a Jam takes it out of your lists and search, and there is no way to restore it. The dashboard has no trash or archive view. Deleting a folder deletes every Jam inside it and reports how many in archivedJamCount. Deleting a comment is permanent and takes its attachments with it, and only the comment’s author can do it. Each command asks you to confirm first. Pass -y (or --yes) to skip the prompt. Scripts and agents have no terminal to answer on, so they must pass -y; without it the command refuses rather than assuming an answer.

Record a window or the desktop

jam record is the CLI capture path. It is not Recording Links, which are shareable URLs that collect Jams from a browser. jam record captures pixels from a window or display. It does not attach console logs or network requests. The common use is proof of work. Wrap a test run, a script, or the steps an agent drives, and the Jam link becomes the evidence: what was on screen, in order, at the time it happened. Attach it to the pull request instead of describing the result in words. See Set up your coding agent for the agent side of that workflow. List targets first. Window IDs change when a window is reopened, so re-run jam record windows before each recording:
Then record. With no selector flag, jam record run records the whole primary display. Name a window with exactly one of --window-id, --app, or --bundle-id. Name a display with --display. Do not combine those flags. --app and --bundle-id match case-insensitively but exactly. If more than one window matches, the command fails and lists candidate IDs. Prefer --window-id from a fresh jam record windows when an app has several windows open.
Put the wrapped command after -- so its own flags reach it instead of jam. Command output goes to stderr. Stdout carries only the Jam receipt:
The wrapped form exits with the command’s code when the command fails, and with the upload’s code otherwise. A failing test still produces a Jam and still fails the pipeline. If upload fails, the mp4 is kept and its path is printed to stderr. With no wrapped command, recording runs until you stop it. Press Ctrl-C at a terminal, or send SIGINT (kill -INT <pid>) from a script that started the process in the background. Both forms upload and print the same receipt. The no-command form exits 0. A wrapped command killed by SIGINT exits 130 after the upload. --title, --description, and --folder set those fields at creation. --folder takes a folder ID. Leave them off and set the title or description later with jam update jam <id>. A recording link is a shareable URL that collects Jams: anyone who opens it can record and submit a Jam back to your workspace. A link captures console and network logs only when it records from a connected recording domain (a “recording URL”), so list your connected domains first and pass one when you create the link. See Recording Links for the dashboard workflow.
create returns the link’s public ID and shareable URL. Every other command addresses the link by that public ID.
jams lists the Jams recorded through a link. update edits its settings (name, folder, reference, Jam title, expiration, metadata). delete soft-deletes the link so it stops accepting new recordings, while the Jams it already collected stay. To connect a new domain, run jam recording-links verify <url> and open the returned link in a browser where Jam is live on that domain.

Output mode

The CLI pretty-prints when stdout is a TTY and emits compact JSON when output is piped. Force JSON output in any context with the top-level --json flag:
Machine consumers (agents, scripts) should pass --json so output stays parseable regardless of where the command runs. Exception: jam doctor always prints a human-readable report.

Pagination

Paginated commands return:
Walk every page in a shell loop:
--limit caps each page at 500. Defaults: 50 for get commands, 20 for list commands.

Exit codes

The exit code is authoritative. Branch on it, not on stderr parsing. In JSON mode, errors print to stderr as {"error":{"code":"...","message":"..."}}. valid_values is included on validation errors when applicable.

Environment variables

Update and uninstall

Install the latest CLI binary:
Install a specific version:
The CLI verifies the new binary’s checksum, runs a --version smoke test, and replaces the running binary atomically. On Windows the running .exe is locked, so each version installs into its own folder and jam upgrade re-points a jam command at the new one. A jam process you already have open keeps running the old version until you restart it. Remove the CLI and local data:
Skip the confirmation in non-interactive environments:
Uninstall removes ~/.local/bin/jam, the ~/.local/state/jam/ state directory, your stored credentials in ~/.config/jam/, and the PATH marker the installer added to your shell rc files. On Windows it removes the %LOCALAPPDATA%\Programs\Jam install folder and drops its entry from your user PATH.
jam uninstall is irreversible. Re-install via the curl one-liner to recover.