CLI JSONL
gitdown-chat is the optional Rust adapter for agent harnesses and shell
automation. It calls the supported Chat API directly; it does not read Chat or
core storage and it does not adapt any particular agent runtime.
Configure credentials
Section titled “Configure credentials”Build the chat-cli package and place the binary on PATH. Prefer an
owner-readable token file:
printf '%s\n' '<BOT_BEARER_TOKEN>' > ~/.config/gitdown/chat-tokenchmod 600 ~/.config/gitdown/chat-tokenexport GITDOWN_CHAT_API_ORIGIN='https://gitdown-chat-dev-api-53ipddnrrq-uc.a.run.app'export GITDOWN_CHAT_TOKEN_FILE="$HOME/.config/gitdown/chat-token"Unix token files must be regular files with no group or other permission bits.
GITDOWN_CHAT_TOKEN is available for ephemeral runners. A token is never a CLI
argument, query parameter, stdout value, or diagnostic value.
Room addressing and commands
Section titled “Room addressing and commands”Every command requires a canonical repository ULID. Add a canonical branch ULID to select branch chat; omit it for global repository chat.
gitdown-chat read --repository "$REPOSITORY_ID" --limit 100gitdown-chat read --repository "$REPOSITORY_ID" \ --branch "$BRANCH_ID" --cursor '<OPAQUE_CURSOR>'
printf '%s' 'Tests are green.' | gitdown-chat send \ --repository "$REPOSITORY_ID" --branch "$BRANCH_ID"
gitdown-chat watch --repository "$REPOSITORY_ID" \ --cursor-file ~/.local/state/gitdown/chat/repository.cursor.json
gitdown-chat inbox --repository "$REPOSITORY_ID"gitdown-chat watch-inbox --repository "$REPOSITORY_ID" \ --cursor-file ~/.local/state/gitdown/chat/inbox.cursor.jsongitdown-chat acknowledge-inbox --repository "$REPOSITORY_ID" \ --through-cursor '<OPAQUE_NOTIFICATION_CURSOR>'read accepts limits 1–200 and emits a terminal page record even when the page
is empty. send accepts 1–16384 UTF-8 bytes from stdin or --body-file. Supply
--idempotency-key <ULID> when a surrounding workflow owns the logical
operation; otherwise the CLI mints one. It reuses both that key and the request
ULID through four bounded attempts on network errors, 429, and 5xx responses.
Structured mentions come only from an explicit bounded --mentions-file; raw
@text is never inferred as identity.
watch stores a v1 room-bound cursor document at the required --cursor-file,
holds an exclusive sidecar lock, and reconnects with Last-Event-ID. It rejects
concurrent use and scope/version mismatch. Normal reconnect suppresses the
repeated boundary event. Each message is flushed to stdout before the atomic,
synced cursor advance, so there is no skip window. A process failure between
those operations may replay one record after restart; deduplicate durable side
effects by message.messageId.
inbox and watch-inbox do not acknowledge by reading. After successfully
displaying or processing notifications, advance the durable checkpoint with
acknowledge-inbox. Supply --expected-cursor when coordinating concurrent
consumers; a stale compare-and-set fails instead of skipping unseen entries.
JSONL v1
Section titled “JSONL v1”Stdout is compact JSONL only: one object per line, each with version: 1 and a
closed type. Diagnostics and retry notices go only to stderr.
{"version":1,"type":"page","nextCursor":"<OPAQUE_CURSOR>"}{"version":1,"type":"heartbeat","serverTime":"2026-07-20T03:00:15Z"}{"version":1,"type":"handoff","reason":"stream_rotation"}{"version":1,"type":"reauthorize","reason":"permission_changed"}The record shapes are:
| Type | Command/source | Additional fields |
|---|---|---|
message |
read, watch |
source, nullable cursor, message |
page |
read |
nullable nextCursor |
sent |
send |
replayed, idempotencyKey, message |
heartbeat |
watch |
serverTime |
handoff |
watch |
stream_rotation |
reauthorize |
watch |
closed reason |
notification |
inbox, watch-inbox |
source, nullable cursor, notification |
inboxPage |
inbox |
nullable nextCursor, acknowledgedCursor |
inboxAcknowledged |
acknowledge-inbox |
acknowledgedCursor, replayed |
The embedded message is the strict public API projection. The CLI validates
its IDs, timestamp, byte count, render policy, sender attribution, and selected
room before emitting it. Unknown fields, unsupported events, SSE frames over
256 KiB, and cursors over 4096 bytes fail closed. The repository’s byte-exact
golden fixture is apps/chat/cli/fixtures/jsonl-v1.golden.
Exit codes and signals
Section titled “Exit codes and signals”| Code | Meaning |
|---|---|
| 0 | success or clean watch interrupt |
| 2 | usage/configuration/scope/body error |
| 3 | authentication, authorization, or reauthorization |
| 4 | not found |
| 5 | conflict or sealed room |
| 6 | rate limit or retry budget exhausted |
| 7 | network, I/O, or service unavailable |
| 8 | invalid API or SSE response |
| 9 | cursor state/lock failure |
watch handles the platform interrupt signal and exits 0 after the last fully
processed event. A reauthorize control record is emitted and flushed before
the process exits 3, allowing the harness to renew credentials deliberately.
Direct API alternative
Section titled “Direct API alternative”The CLI is not required. Harnesses may use the rooms REST API and SSE resume contract directly. Direct clients inherit the same API-only boundary, secret redaction, strict decoding, idempotency, and cursor obligations.