Deep dive
The CLI is an agent interface again
Coding agents already understand shells. An agent-ready CLI adds predictable discovery, JSON, retries, and exit behavior without becoming a second control plane.

I used to think of a command-line interface as the expert entrance to a product. It was quick if you already knew the vocabulary and fairly hostile if you did not. The friendly work belonged in the web app.
Coding agents have complicated that view. They are comfortable in a shell. They can inspect help, pass flags, redirect JSON, and leave an exact command in a pull request. A CLI may now be the first interface an agent reaches for.
That does not mean an old human-oriented CLI automatically works well for an agent. Many commands assume somebody is watching. They open a browser, ask a question halfway through a job, animate a spinner over the output, or silently retry a write. Those habits become failure modes in unattended work.
SproutPad's CLI follows the contract below.

Treat JSON as a public interface
This sentence is pleasant enough for a person:
Great news! Your deployment appears to be ready at example.com.
It is an awkward result for software. An agent should receive stable fields:
{
"taskId": "tsk_123",
"status": "succeeded",
"url": "https://example.com",
"receiptId": "rcpt_456",
"actualCostCents": 300
}
We still want readable tables at a terminal. They should be rendered from the same typed result rather than implemented as the command's only output. JSON Lines works for a bounded stream. Diagnostics go to standard error. The actual result stays intact on standard output.
Give exit codes useful meanings
A shell needs more information than success or failure when it waits on a governed task. Invalid local input, failed authentication, a policy refusal, a human approval, a remote task failure, and a local timeout each call for a different next step.
The structured error explains the details. The exit code gives a script enough information to stop, retry, or return control to the agent.
Timeouts deserve particular care. If sproutpad task wait reaches its local deadline, the remote task may still be running. Reporting that state as a task failure encourages a caller to replay a mutation that may already have succeeded.
Make retries explicit
Agents retry often. A network can drop after the server accepted a request but before the client saved its response. A process can disappear at the same moment.
For that reason, every consequential command needs an idempotency key in noninteractive use. A retry of the same intended action keeps the key. Changing the payload while retaining the key is an error. Choosing a new key creates a new action, regardless of whether the surrounding prompt calls it a retry.
The CLI passes that key to the API. It does not maintain its own private record of completed work, because the server must remain the authority after the local process is gone.
Keep the client thin
There is always a temptation to improve a CLI by giving it provider SDKs and a few local business rules. That can make an awkward workflow look smooth. It can also create a second implementation of policy, approvals, budgets, retries, and task state.
SproutPad's CLI is a client of the public REST API. Generated bindings cover every executable operation under canonical invoke <operationId> paths. Static discovery and bounded task-observation commands remain explicit, but there are no curated product-operation aliases that could drift from the generated contract or call a provider directly.
The same rule applies to multi-operation work. The CLI may submit a bounded workflow draft, inspect the server-attested plan, and explicitly apply it. It does not compile a private provider workflow or treat the plan signature as permission. A local timeout still leaves the durable task as the source of truth.
This is also a security property. If MCP and the CLI disagree about what an operation is allowed to do, an agent will eventually encounter the more permissive route.
Make discovery local and specific
Agents can use --help, though a 900-line help page recreates the same flat catalog problem we see with large MCP servers.
The CLI provides machine-readable operation search and local schemas. An agent can search for “cost,” inspect one matching command, and then invoke it. Shell completions can come from the same manifest for human users. They are a projection of the contract rather than a separate source of truth.
This form of discovery remains available when an MCP host is unavailable. That makes the CLI a portable way to reach the same SproutPad operations, not an emergency back door.
The checklist we use
For our purposes, an agent-ready CLI needs:
- commands that never require an interactive prompt;
- stable
--jsonoutput, plus documented JSON Lines behavior where useful; - stable exit codes paired with structured errors;
- explicit profiles and an explicit development base URL;
- safe input channels for secrets, rather than arguments visible in process listings;
- required idempotency keys for noninteractive mutations;
- bounded wait commands that distinguish a local timeout from remote failure;
- generated operation coverage with collision checks;
- shell completions derived from the operation manifest;
- no provider credentials or policy rules in the client;
- and packaging tests against the artifact users actually install.
The shell's grammar was already familiar to coding agents. Our work is mostly less glamorous: make each command predictable, make failures distinguishable, and ensure the CLI cannot obtain authority that the underlying API would deny.