Skip to content

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.

ModeTriggerShape
Humandefault at a TTYTables for lists, aligned key/value for single records, [doc:ID] handles, color.
--jsonflag, or stdout is not a TTY (auto)The GraphQL data payload for the operation, pretty-printed.
--jsonlflagOne 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 → each results[] entry
  • recent / list → each document
  • sources → each source
  • ingest → each errors[] 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.

Terminal window
trove search "vector search" --json # full SearchResults object
trove search "vector search" --jsonl # one result per line

Pipelines can branch on the failure class without parsing stderr:

CodeMeaning
0Success.
2Usage / validation error — bad flags, missing required argument, or a client-side rejection.
4Auth error — not logged in, expired token (HTTP 401/403), or a server-side authorization rejection.
5Not found — document(id) or source(id) returned null.
7Transport / server error — 5xx, network failure, malformed response, or a GraphQL errors[] not otherwise classified. Also the gql exit when the envelope carries errors[].
8Retryable 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/unauthorized4; a cursor + conflict/cas/mismatch8; not found5; bad_user_input/validation/invalid2; everything else → 7.

Terminal window
trove get d_missing
case $? in
0) echo "ok" ;;
5) echo "no such document" ;;
4) echo "log in first" ;;
*) echo "other failure" ;;
esac

Idempotent 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.

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:

Terminal window
trove save --url https://example.com/post 2>/dev/null # record only, no ✓ line
trove mcp deploy >/tmp/tools.txt # tool names captured; ✓ on screen

--quiet silences the stderr chrome while keeping real errors.

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.

Terminal window
NO_COLOR=1 trove stats # plain text, no ANSI
trove list --human --no-color # forced table, no color, into a file
Terminal window
# Titles of the top hits
trove search "database indexing" --json | jq -r '.results[].document.title'
# Score + title, sorted, fuzzy-pick with fzf
trove search "distributed systems" --jsonl \
| jq -r '[.relevanceScore, .document.title] | @tsv' \
| sort -rn | fzf
# Open the top hit's full text in your editor
trove 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 source
trove 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 clipboard
pbpaste | trove save --text - --title "Clipboard note" --tag scratch

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.

Terminal window
TROVE_TOKEN="$CLERK_JWT" trove list --json | jq '.totalCount'
trove --profile dev search "kafka" --json

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:

Terminal window
echo '{ stats { totalDocuments totalSources } }' | trove gql - | jq '.data.stats'