Scripting & Automation
trove is built to be a good citizen in a shell pipeline: machine-readable output, honest exit codes, and a clean split between data and chrome.
Output formats
Section titled “Output formats”| Mode | Trigger | Shape |
|---|---|---|
| Human | default at a TTY | Tables for lists, aligned key/value for single records, [doc:ID] handles, color. |
--json | flag, or stdout is not a TTY (auto) | The GraphQL data payload for the operation, pretty-printed. |
--jsonl | flag | One JSON object per line, for the streamable list within a result. |
Precedence is --jsonl > --json > --human. With no flag, the CLI picks human at a TTY and auto-selects json when stdout is not a TTY — the “do the right thing in a pipe” behavior. Force tables into a pipe with --human.
--jsonl emits the streamable part of a result, one object per line:
search/discover→ eachresults[]entryrecent/list→ each documentsources→ each sourceingest→ eacherrors[]entry
For commands whose payload is a single object (get of one id, source, stats, whoami), --jsonl and --json produce the same pretty-printed object.
trove search "vector search" --json # full SearchResults objecttrove search "vector search" --jsonl # one result per lineExit codes
Section titled “Exit codes”Pipelines can branch on the failure class without parsing stderr:
| Code | Meaning |
|---|---|
0 | Success. |
2 | Usage / validation error — bad flags, missing required argument, or a client-side rejection. |
4 | Auth error — not logged in, expired token (HTTP 401/403), or a server-side authorization rejection. |
5 | Not found — document(id) or source(id) returned null. |
7 | Transport / server error — 5xx, network failure, malformed response, or a GraphQL errors[] not otherwise classified. Also the gql exit when the envelope carries errors[]. |
8 | Retryable conflict — ingest cursor compare-and-swap rejection. Re-read IngestResult.cursor and retry. |
GraphQL errors[] are classified into these codes by inspecting extensions.code and the message text: unauthenticated/forbidden/admin/unauthorized → 4; a cursor + conflict/cas/mismatch → 8; not found → 5; bad_user_input/validation/invalid → 2; everything else → 7.
trove get d_missingcase $? in 0) echo "ok" ;; 5) echo "no such document" ;; 4) echo "log in first" ;; *) echo "other failure" ;;esacIdempotent reads retry transport (5xx/network) failures with capped backoff before surfacing exit 7. Mutations do not auto-retry, except the CAS-guarded ingest, which is safe to re-run.
stdout vs stderr
Section titled “stdout vs stderr”Data goes to stdout; diagnostics and chrome go to stderr. Spinners, the Bundling … line, ✓ saved/✓ deployed/✓ migrated confirmations, the whoami failure hints, and warnings are all written to stderr. This is why trove search … --json | jq is never polluted — jq only ever sees the data payload.
A useful consequence: for save, deploy, and the lifecycle mutations, the success confirmation is on stderr while the structured result (or the namespaced tool names, for deploy) is on stdout, so you can capture one without the other:
trove save --url https://example.com/post 2>/dev/null # record only, no ✓ linetrove mcp deploy >/tmp/tools.txt # tool names captured; ✓ on screen--quiet silences the stderr chrome while keeping real errors.
Color and TTY
Section titled “Color and TTY”Color is enabled only when all of these hold: stdout is a TTY, the format is human, --no-color is absent, and NO_COLOR is unset. The CLI honors the NO_COLOR convention. In a pipe (non-TTY), output is json and uncolored by default, so nothing needs stripping.
NO_COLOR=1 trove stats # plain text, no ANSItrove list --human --no-color # forced table, no color, into a filePiping recipes
Section titled “Piping recipes”# Titles of the top hitstrove search "database indexing" --json | jq -r '.results[].document.title'
# Score + title, sorted, fuzzy-pick with fzftrove search "distributed systems" --jsonl \ | jq -r '[.relevanceScore, .document.title] | @tsv' \ | sort -rn | fzf
# Open the top hit's full text in your editortrove search "kafka rebalance" --json \ | jq -r '.results[0].document.id' \ | xargs trove get | $EDITOR -
# Count everything from one source (exhaustive — documents(), not search)trove list --source "The Guardian" --json | jq '.totalCount'
# Page through a large sourcetrove list --source "arXiv Papers" --limit 100 --offset 200 --jsonl \ | jq -r '.id + "\t" + (.title // "(untitled)")'
# Bulk-ingest a JSONL stream produced by some script (CAS-guarded, retryable)generate-docs | trove ingest --source src_123 --feed feed_456 --documents -
# Capture from the clipboardpbpaste | trove save --text - --title "Clipboard note" --tag scratchProfiles and tokens in scripts
Section titled “Profiles and tokens in scripts”Set TROVE_TOKEN to skip interactive login entirely — it overrides any stored token, which is the CI path. Select a non-default environment with TROVE_PROFILE or the global --profile flag, and override just the endpoint with --endpoint. See Install & Authentication for the full precedence.
TROVE_TOKEN="$CLERK_JWT" trove list --json | jq '.totalCount'trove --profile dev search "kafka" --jsonThe raw escape hatch
Section titled “The raw escape hatch”When a script needs an operation the wrapped commands do not cover, trove gql runs a raw GraphQL document over the same endpoint and token and emits the full { data, errors } envelope, exiting 7 when errors[] is present:
echo '{ stats { totalDocuments totalSources } }' | trove gql - | jq '.data.stats'