Skip to main content

Scripting and CI

abacusai -p runs one turn, prints the answer and exits. There is no interface, nothing to press, and everything a script needs is on stdout.

abacusai -p "summarize the changes on this branch"

Input​

The prompt can be an argument, piped in, or both.

# Prompt as an argument
abacusai -p "list the TODOs in src/"

# Piped input is appended to the prompt
git diff | abacusai -p "review this diff"

# No argument: stdin is the prompt
echo "explain this error" | abacusai -p

With a prompt argument, piped input is appended as a fenced Piped stdin: block, so a diff or a Markdown file cannot break out of it. The CLI waits up to three seconds for the first byte. If nothing is written to the pipe it carries on with a warning rather than hanging, so a producer that takes longer than that should be captured to a file first:

slow-command > out.txt
abacusai -p "what failed here?" < out.txt

In a job where nothing writes to stdin, skip the wait with < /dev/null (< NUL in cmd, $null | abacusai ... in PowerShell).

Output​

FlagWhat stdout carries
--output-format textThe final answer. The default.
--output-format jsonOne result object after the turn
--output-format stream-jsonOne JSON object per line as the turn runs; the last line of a turn is always result

stdout carries the chosen format and nothing else, ever. Diagnostics (the model, the working directory, the conversation id, warnings and non-fatal errors) go to stderr, so abacusai -p ... > answer.txt never picks them up.

Two flags shape a stream-json run: --include-partial-messages streams text and thinking as it arrives instead of one block per message, and --replay-user-messages echoes each accepted input back so a host can confirm what was received.

--verbose is for the text and json formats: the reply is written to stderr as the turn runs, leaving stdout clean.

Asking for structured output​

--json-schema appends the schema to your prompt and parses the answer back out of the reply:

abacusai -p "classify the risk of this PR" \
--json-schema '{"type":"object","properties":{"risk":{"enum":["low","high"]}},"required":["risk"]}' \
--output-format json

The parsed object lands in structured_output on the result. If nothing parses or it fails validation, the result says error_structured_output and the raw reply is still there.

Exit codes​

CodeMeaning
0The turn finished. Warnings may still have been printed to stderr.
1Fatal. The message is on stderr, and for json and stream-json the last stdout line is a result with is_error: true.
2A UserPromptSubmit hook blocked the prompt, so it was never sent. The hook's reason is on stderr and the result is prompt_blocked.
143Terminated by SIGTERM

The last stderr line of a fatal is a single JSON object so a script can classify the failure without matching prose:

{"type":"fatal","reason":"not_authenticated","message":"..."}

reason is one of not_authenticated, access_denied, no_models, invalid_config, network_unreachable, startup_timeout or internal. On startup_timeout a step field names the step that never answered.

Authentication​

Set ABACUS_API_KEY in the environment and the CLI is signed in without /login:

export ABACUS_API_KEY=$ABACUS_API_KEY # from your secret store, never committed
abacusai -p "run the tests and tell me what broke"

Permissions in a script​

Nobody is there to answer a prompt, so print mode resolves permissions up front.

With no mode named (no --permission-mode, no permissionMode setting, no ABACUS_PERMISSION_MODE), every call is approved except what a deny or ask rule catches. Name a mode when the job is not meant to have a free hand. A named mode decides as it would in a session, and a call that would still have asked is declined rather than left hanging.

# Read-only: explore and report, change nothing
abacusai -p "where is retry logic implemented?" --permission-mode plan

# Let it edit, but nothing else, and never touch secrets
abacusai -p "fix the failing test" \
--permission-mode accept-edits \
--disallowed-tools 'Read(.env),Bash(git push *)'

# Allow exactly what the job needs and decline everything else
abacusai -p "update the changelog" \
--permission-mode dont-ask \
--allowed-tools 'Bash(git log *),Bash(git diff *),Edit(CHANGELOG.md)'

--allowed-tools pre-approves calls in any mode, and --disallowed-tools denies them in any mode, Unsupervised included. The rule syntax is covered in Permissions.

Hooks run in print mode too. Blocks, failures and hook notes go to stderr, and with stream-json each hook run is reported on stdout. See Hooks in print mode.

A repository you check out in CI​

A repository's own files can start processes and widen what the agent may do, so they are gated behind workspace trust. Print mode never asks (nobody is there to answer), so by default a freshly cloned repository runs without its .abacusai/mcp.json servers, its permissionMode, its permissions.allow and strictness, its additionalDirectories and baseUrl, its hooks, and the allowed-tools of its skills. Its deny and ask rules apply either way, and so do the skills themselves. One stderr line names what was ignored; stdout and the exit code are unchanged.

When the job is meant to use them, trust the folder for that run:

ABACUS_TRUST_WORKSPACE=1 abacusai -p "run the tests and fix what breaks"

That records nothing and covers one process. Your own --settings, --mcp-config and flags are the caller's and are never gated.

Continuing a conversation​

abacusai -p -c "and now write the tests" # continue the most recent conversation
abacusai -p --resume <id> "what did we decide?" # continue a specific one

The conversation id of each run is printed to stderr.

Other useful flags​

FlagEffect
--model <id>Model for this run
--add-dir <path>Let the agent work in another directory too. Repeatable.
--settings <file>Merge a JSON settings file. Repeatable.
--mcp-config <file>Load an MCP server config file. Repeatable.
--no-agents-mdSkip AGENTS.md
--debugLog at debug level and print the log file path

A two-way protocol​

--input-format stream-json turns the same process into a bidirectional protocol: one JSON object per line on stdin, one per line on stdout. The agent can then ask for permission mid-turn and your host answers, which is how the Desktop app and the SDK drive the agent.

abacusai -p --input-format stream-json --output-format stream-json

--input-format stream-json requires --output-format stream-json; any other output format exits 1. The process stays up until stdin closes.

Sending shell typos to the agent​

abacusai shell-integration

This hooks your shell so that a command it does not recognize is sent to abacusai -p instead of failing with "command not found".

Example: a pull request reviewer​

#!/usr/bin/env bash
set -euo pipefail

git diff "origin/${BASE_BRANCH}...HEAD" > /tmp/pr.diff

abacusai -p "Review this diff. Only report real bugs." \
--permission-mode plan \
--output-format text \
< /tmp/pr.diff > /tmp/review.md

gh pr comment "${PR_NUMBER}" --body-file /tmp/review.md

--permission-mode plan keeps the job read-only, and the review lands in a file the next step can use.

For further help, reach out at support@abacus.ai.