typer-agentic
A Typer extension that makes CLIs friendly to coding agents.
When an LLM agent misspells a flag or omits an argument, Typer prints a Rich panel and exits 2. The agent then either burns turns re-reading --help, or, worse, gives up on the tool and starts editing files by hand. typer-agentic intercepts usage errors and, only when an agent is driving, replaces the panel with a compact block: what went wrong, the valid parameters with types and choices, exactly one corrected example, and calm instructions that keep the agent on the retry path. Humans keep Typer's stock output, byte for byte.
Quickstart
from typer_agentic import agent_errors
app = typer.Typer()
...
main = agent_errors(app) # use `main` as your [project.scripts] entry point
Nothing else changes. agent_errors(app) returns a zero-argument callable, never mutates the app, and never monkeypatches Typer or Click.
Before / after
myapp sync --verbos, Typer 0.27 (stock):
Usage: myapp sync [OPTIONS] {path}
Try 'myapp sync --help' for help.
╭─ Error ────────────────────────────────────────────────────────────╮
│ No such option: --verbos (Possible options: --verbose) │
╰────────────────────────────────────────────────────────────────────╯
Same command with CLAUDECODE=1 in the environment (Claude Code sets it for you):
✗ Usage error in `myapp sync`: No such option: --verbos
This is a recoverable input mistake, not a bug in the tool. Do not switch tools, edit files or state to work around it, or invent flags; apply the one change below and re-run the command.
Did you mean: --verbose
Fix and retry (one change): Replace '--verbos' with '--verbose'. Run the corrected example.
myapp sync ./PATH --verbose
Valid options:
--verbose, -v flag Enable verbose output.
--count INTEGER How many.
--env CHOICE[dev|prod] Target environment.
Required arguments: PATH
Full reference: myapp sync --help
Honest note: Typer already prints a did-you-mean hint. What this adds is the typed inventory, the runnable example, and the de-escalation copy. If you only want the box-drawing gone, TYPER_USE_RICH=0 does that with no dependency.
Mode resolution
mode="auto" (the default) picks agent or human output per invocation. Highest precedence first:
| # | Signal | Result |
|---|---|---|
| 1 | AgentErrorsConfig(mode="agent" | "human") |
forced |
| 2 | --agent-errors / --human-errors on the command line |
agent / human |
| 3 | AGENT_ERRORS env var (1/true/yes/on vs 0/false/no/off/empty) |
agent / human |
| 4 | Any of CLAUDECODE, CLAUDE_CODE, CLAUDE_CODE_ENTRYPOINT, CODEX, CODEX_CLI, CURSOR, CURSOR_SESSION_ID, OPENCODE, AGENT set and non-empty |
agent |
| 5 | tty_heuristic=True and stderr is not a TTY |
agent |
| 6 | default | human |
The sentinel flags are consumed by the wrapper before Typer parses anything, so they work even when the rest of the command line is broken. They do not appear in --help.
Format
Markdown (default) is written for a model to read: stable first line ✗ Usage error in \`: …, fixed section order, no ANSI. JSON is for harnesses that parse stderr and retry automatically: select it with AgentErrorsConfig(format="json")orAGENT_ERRORS_FORMAT=json`. The JSON document is the only thing written to the stream. See SCHEMA.md.
For agent-harness authors: grep the first line for ✗ Usage error, or set AGENT_ERRORS_FORMAT=json and json.loads(stderr).
Configuration
from typer_agentic import AgentErrorsConfig
main = agent_errors(
app,
config=AgentErrorsConfig(
mode="auto", # "agent" | "human" to force
format="markdown", # or "json"
stream="stderr", # or "stdout"
env_var="AGENT_ERRORS",
format_env_var="AGENT_ERRORS_FORMAT",
flag="--agent-errors", # None disables
human_flag="--human-errors", # None disables
skill_flag="--agent-skill", # None disables
max_suggestions=3,
include_hidden=False, # list hidden params too
tty_heuristic=False,
agent_detect_env_vars=(...), # see table above
intercept_click_exceptions=False,
repeat_detection=False, # see below
repeat_state_dir=None, # defaults to $TMPDIR
),
)
SKILL.md for your CLI
Errors are the reactive channel. myapp --agent-skill prints an Agent Skill describing every visible command, argument, option and one example per command, so the agent has the inventory before it guesses:
myapp --agent-skill > .claude/skills/myapp/SKILL.md
render_skill(app) returns the same text as a string. The library never writes files itself.
Repeat-failure escalation (opt-in)
Each CLI run is a fresh process, so "the agent is looping" needs a little state. With repeat_detection=True the wrapper writes a small JSON record ($TMPDIR/typer-agentic-<uid>/<prog>.json, 10-minute TTL, atomic replace) and on the second identical failure adds: stop retrying variations, read --help, rebuild from the example. On the third it adds: if that does not resolve it, report the exact error to the user instead of working around it. Any I/O problem silently disables the feature.
The copy
All agent-facing text lives in typer_agentic/wording.py. If you customise it, keep the rules the defaults follow: one sentence classifying the failure as a recoverable input mistake; one sentence forbidding the panic behaviours (switching tools, editing state around the CLI, inventing flags); exactly one next action; calm, imperative, no exclamation marks, no apologies, no "please"; identical wording across error types.
Compatibility
- Python 3.11+,
typer>=0.24, no other runtime dependency.clickis never imported unconditionally. - Typer 0.26+ bundles its own Click under
typer._click; its exception classes are unrelated toclick.exceptions.typer_agentic.compatresolves whichever hierarchies are present and catches all of them. - If resolution fails on some future Typer,
agent_errors(app)becomes a transparent passthrough and emits oneRuntimeWarningper process (only in agent mode). Your CLI keeps working; only the feature degrades. - Agent mode drives Click's
make_context/invokeloop directly (notmain(standalone_mode=False), whose return value cannot distinguishtyper.Exit(n)from a command returningn), so exit codes match stock Typer:Exit(n)→n, normal return → 0,Abort→ 1, Ctrl-C → 130. Shell completion is delegated to Typer untouched. - Not intercepted, by design: runtime exceptions inside command bodies (they propagate with Typer's pretty-exception hook applied),
NoArgsIsHelpError(help is printed as usual),--help/--version/ completion. - The built-in
--help,--install-completionand--show-completionoptions are omitted from the inventory.
The test suite runs against Typer 0.25 (external Click) and the current release: uv run poe test-compat.
Development
uv sync
uv run poe setup # install git hooks
uv run poe check # lint, typecheck, dead code, deps, clones, tests
uv run poe fix # auto-format + fix lint
uv run poe test # tests with coverage
uv run poe test-compat
Golden files under tests/golden/ are regenerated with UPDATE_GOLDEN=1 uv run pytest.
Release: uv run poe release (or level=minor uv run poe release) bumps the version, tags, and pushes. The tag triggers the PyPI publish workflow.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file typer_agentic-0.1.3.tar.gz.
File metadata
- Download URL: typer_agentic-0.1.3.tar.gz
- Upload date:
- Size: 88.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c20818217b267740a6a874d075b4d31bb28303926da5272b0d52c6275ed8a6fb
|
|
| MD5 |
21f3af95f8ecd07c15546def856acfac
|
|
| BLAKE2b-256 |
b00a4d510c6047e90e04858af945666c3c878af989ba368edef0e0a85814f847
|
Provenance
The following attestation bundles were made for typer_agentic-0.1.3.tar.gz:
Publisher:
release.yml on mojzis/typer-agentic
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
typer_agentic-0.1.3.tar.gz -
Subject digest:
c20818217b267740a6a874d075b4d31bb28303926da5272b0d52c6275ed8a6fb - Sigstore transparency entry: 2754079071
- Sigstore integration time:
-
Permalink:
mojzis/typer-agentic@3b4d5b16908a0cc1c8849bec7b433e09d10eeb56 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/mojzis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3b4d5b16908a0cc1c8849bec7b433e09d10eeb56 -
Trigger Event:
push
-
Statement type:
File details
Details for the file typer_agentic-0.1.3-py3-none-any.whl.
File metadata
- Download URL: typer_agentic-0.1.3-py3-none-any.whl
- Upload date:
- Size: 28.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6acc3aa8f51d0eb5c26b8042e7430c49b6ec589d86f5d63c9b7857005b6f1e8f
|
|
| MD5 |
9568af9378993a9a81e14a55f3af838e
|
|
| BLAKE2b-256 |
b273e37460b3584e85dc2ae66b5460693a456503e4b07844edcef244d65b52f6
|
Provenance
The following attestation bundles were made for typer_agentic-0.1.3-py3-none-any.whl:
Publisher:
release.yml on mojzis/typer-agentic
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
typer_agentic-0.1.3-py3-none-any.whl -
Subject digest:
6acc3aa8f51d0eb5c26b8042e7430c49b6ec589d86f5d63c9b7857005b6f1e8f - Sigstore transparency entry: 2754079073
- Sigstore integration time:
-
Permalink:
mojzis/typer-agentic@3b4d5b16908a0cc1c8849bec7b433e09d10eeb56 -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/mojzis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3b4d5b16908a0cc1c8849bec7b433e09d10eeb56 -
Trigger Event:
push
-
Statement type: