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.
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 *)'

# Pre-approve exactly what the job needs
abacusai -p "update the changelog" \
--allowed-tools 'Bash(git log *)' '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 the same as in Settings.

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