Skip to content

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.

Build the chat-cli package and place the binary on PATH. Prefer an owner-readable token file:

Terminal window
printf '%s\n' '<BOT_BEARER_TOKEN>' > ~/.config/gitdown/chat-token
chmod 600 ~/.config/gitdown/chat-token
export 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.

Every command requires a canonical repository ULID. Add a canonical branch ULID to select branch chat; omit it for global repository chat.

Terminal window
gitdown-chat read --repository "$REPOSITORY_ID" --limit 100
gitdown-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.json
gitdown-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.

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.

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.

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.