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​
| Flag | What stdout carries |
|---|---|
--output-format text | The final answer. The default. |
--output-format json | One result object after the turn |
--output-format stream-json | One 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​
| Code | Meaning |
|---|---|
0 | The turn finished. Warnings may still have been printed to stderr. |
1 | Fatal. The message is on stderr, and for json and stream-json the last stdout line is a result with is_error: true. |
143 | Terminated 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​
| Flag | Effect |
|---|---|
--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-md | Skip AGENTS.md |
--debug | Log 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.