Skip to main content
The CLI is built to be scripted: --json gives a single JSON object on stdout, errors are a JSON envelope on stderr, and exit codes are a stable contract.

Branch on exit codes, parse only JSON

#!/usr/bin/env bash
set -u

caesar-search search "zero-downtime postgres schema migration" \
  --format compact --json -o /tmp/results.json
code=$?

case "$code" in
  0) jq -r '.results[].doc_id' /tmp/results.json ;;
  3) echo "auth error: set CAESAR_API_KEY or run caesar-search auth login" >&2; exit 1 ;;
  4) echo "API error after retries; see the stderr envelope" >&2; exit 1 ;;
  5) echo "timeout: retry or raise --timeout" >&2; exit 1 ;;
  *) echo "bad input (exit $code): fix the command" >&2; exit 1 ;;
esac
This example bakes in the three scripting rules:
  • --json on every call. Human output is not a stable interface — its wording and layout can change between releases. Errors arrive on stderr as {"error": {"code": "...", "message": "...", "hint": "..."}} (hint only when present); all fields are snake_case.
  • Exit codes are the contract, not output text: 0 success, 2 bad input, 3 auth, 4 API error, 5 timeout. The full table is on CLI usage.
  • -o for artifact capture. It writes the payload to the file and suppresses stdout entirely, so nothing competes with the file and output limits can’t truncate JSON mid-parse. Upload the file as a CI artifact, parse it with jq.

Configuration precedence

Resolution order, highest first:
SettingOrder
API key--key flag → CAESAR_API_KEY env → config file api_key → anonymous (lower rate limit)
Base URL--base-url flag → CAESAR_BASE_URL env → config file base_url → default https://search-api-staging-779189860552.europe-west1.run.app
The general rule is flag over env over config file over built-in default. In CI, prefer a masked CAESAR_API_KEY secret over the config file — nothing to provision on disk, and caesar-search auth status --json will confirm key_source is env.

Let one layer own retries

By default the CLI retries HTTP 429 and 5xx up to 3 times (4 attempts total) with exponential backoff capped at 8 seconds, honoring a Retry-After header when present. If your scheduler or workflow engine already handles retries, disable the CLI’s so attempts aren’t multiplied:
caesar-search search "site reliability postmortem templates" --json --no-retry
With --no-retry, a 429 or 5xx fails immediately with exit 4 and the error envelope on stderr — your outer retry policy takes it from there.

The api escape hatch

caesar-search api <method> <path> makes an authenticated raw call against the API — same key resolution, retries, and timeout — for endpoints the CLI doesn’t wrap:
echo '{"query":"hello","max_results":3}' | caesar-search api POST /v1/search --input -
caesar-search api GET /skills
Methods: GET, POST, PUT, PATCH, DELETE. The path must start with /. --input <file> (or --input - for stdin) supplies a JSON body. Output is always JSON — api ignores --json — and still honors -o. The endpoint catalog lives in the API reference.

Pin versions; check for drift

Auto-updating mid-pipeline makes builds unreproducible. Instead:
  • Pin the CLI in CI: npm install -g caesar-search-cli@0.1.4, or fetch a specific release archive and verify it against checksums.txt.
  • Detect drift with the read-only check and surface it — don’t act on it automatically:
caesar-search update --check --json
# {"current": "0.1.4", "latest": "0.1.5", "update_available": true, "channel": "npm"}
  • Upgrade deliberately with caesar-search update; it picks the right channel (npm, brew, standalone). Never npm update -g or brew upgrade by hand.
update --check works on every channel, including source (dev) builds. Only the actual update refuses on dev builds (exit 2).

For agents

  • Always pass --json; parse stdout only. Errors are a JSON envelope on stderr.
  • Branch on exit codes (0/2/3/4/5), never on output text.
  • Prefer -o /tmp/<name>.json then cat the file — -o suppresses stdout so harness output limits can’t truncate JSON.
  • Exit 3 means auth: set CAESAR_API_KEY or run caesar-search auth login. Anonymous still works at a lower rate limit, so don’t block on it.
  • Pass --no-retry only when your own loop handles retries; otherwise let the CLI back off.
Full common-mistakes table: CLI usage.