agent-cli-scaffold
Build CLIs an agent can actually drive. A runtime you import, a scaffold that generates a working tool, and a checklist that runs.
The command and the module are both agtcli; agent-cli-scaffold is only the
name it is published under.
The rules and the incident behind each are in PLAYBOOK.md. This file is how to use the thing.
Why a library and not a template
The same contract copied into five projects is five things that drift. orly and
mercadona-cli independently grew the same exit-code taxonomy, the same
compact/--raw split, and the same atomic-write helper, in two languages, and
the copies already disagree.
So the mechanical half is imported and tested once. agtcli check covers the
half that is still yours.
Install
pip install agent-cli-scaffold # or: uv tool install agent-cli-scaffold
agtcli --help
From a clone instead, which is what you want if you are changing the contract:
uv sync && uv tool install --editable .
Either works, and the difference shows up in what you scaffold. A generated
project depends on agent-cli-scaffold, and agtcli new pins that dependency
at wherever this copy came from: nothing to pin from an index, the local path
from a clone, the same commit from a git install. So a project scaffolded by
the released agtcli builds anywhere; one scaffolded from your clone builds
where that clone is.
Recipes
Scaffold a CLI and confirm it works
agtcli new hansard --description "Search UK Hansard debates." \
--triggers "hansard, what did parliament say about X"
cd hansard && uv sync && uv run pytest -q # 14 passing
uv tool install --editable . && hansard --help
You get a tool that already has typed exit codes, a compact default with a
--raw escape hatch, atomic 0600 state, a credential-rotation hook, a
blast-radius guard, a thin skill, and contract tests that pass. Then replace
src/hansard/api.py with the real service.
Audit one before shipping
agtcli check ./hansard # exits non-zero while anything fails
agtcli check ./hansard --json # one object per check
agtcli rules # just the checklist
Gate a commit or a CI job on the checklist
agtcli check . --json | jq -e '.failed == 0'
Find which rule a check is about
agtcli rules --json | jq -r '.[] | select(.name=="truncation-marked") | .why'
Fifteen items are checked by reading the project. Nine cannot be, so they are reported as unverified rather than counted as passes.
$ agtcli check ./hansard
✓ skill-shipped: skill/SKILL.md
✓ exit-codes-typed: inherited from agtcli.build
✓ truncation-marked: cuts are marked
✓ blast-radius-capped: costly commands consult the guard
...
? dumb-subagent-test-run: the only step that reliably finds what you missed
9 item(s) cannot be checked mechanically; verify them yourself.
What you import
import agtcli
from agtcli import Session
app = agtcli.build("mytool", help="...", notes=AGENT_NOTES, unit="EUR")
@app.command()
def get(ctx: typer.Context, id: str):
s = Session.get(ctx)
s.out.emit(record, compact=lambda r: {"id": r["id"]})
@app.command()
@agtcli.costly # cannot return without checking the cap
def submit(ctx: typer.Context, id: str):
s = Session.get(ctx)
s.guard.check(total, f"submitting {id}")
main = agtcli.main_for(app)
build / run / main_for |
a Typer app wired to the contract; one place decides every exit code |
ExitCode |
0 ok, 1 retryable, 2 usage, 3 not-found, 4 auth, 5 timeout, 6 refused. The --help table is generated from this enum, so it cannot drift |
Output |
compact by default, --raw for everything, and under --json stdout carries JSON and nothing else |
trim |
cut a value yourself; the result says how much is missing. The one to reach for |
mark_truncation / elide |
the halves of trim. mark_truncation alone is for text somebody else cut, where the last character is the only evidence; wrapping your own elide in it marks whole values as truncated |
write_atomic / write_json |
mkstemp plus os.replace, 0600, because agents run in parallel |
Guard / @costly |
a cap by flag, env or config that fails closed, and raises if a costly command never consults it |
Config |
pydantic-settings with the precedence the right way round: flag, then env, then file |
Errors are typed and carry what to do next:
$ hansard submit 123 --json ; echo "exit $?"
{
"error": "refused",
"message": "submitting 123 is irreversible and --yes was not given",
"exit_code": 6,
"retryable": false,
"remedy": "re-run with --yes once a human has agreed"
}
exit 6
The skill
skill/ is meant to be symlinked, so it versions with the code:
ln -s "$PWD/skill" ~/.claude/skills/agtcli
A CLI is invoked, not found, so the skill body is a one-liner that points at
--help. What the agent discovers progressively is the tool: tool --help for
the verbs, rules and exit codes; tool <verb> --help for one command, with the
global flags carried down so --max-cost is still visible on the command that
spends; and a next-step hint in the output itself.
Development
uv run pytest -q
Every test is a rule from the playbook or a bug that actually happened. If you change the contract, a test should fail.
Metadata
Release files for agent-cli-scaffold 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| agent_cli_scaffold-0.1.1.tar.gz | 78.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agent_cli_scaffold-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 123.9 kB
Release files / agent_cli_scaffold-0.1.1.tar.gz
| Download URL | agent_cli_scaffold-0.1.1.tar.gz |
|---|---|
| Size | 78.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8f180b7529d4472539c8a823002dbdccab97b5d44a26d19fbb889463d4ad3f3f
|
|
BLAKE2b-256 checksum How to use checksums |
5e7158c8af079baec1bcc0500647b3b1deaa343df8670d7c268e140eba846388
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.
Transparency logRelease files / agent_cli_scaffold-0.1.1-py3-none-any.whl
| Download URL | agent_cli_scaffold-0.1.1-py3-none-any.whl |
|---|---|
| Size | 45.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9f8d93a276b6337d07ef6f420a5fb6a37818045dfac4a16b7699d06583d5ccd7
|
|
BLAKE2b-256 checksum How to use checksums |
b59eac000f1120f31d56f3d16ebef3a35fadbae4953a8b7a516511394592d253
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 7, 2026.
Transparency log