Agent-first Python CLI library — Typer-compatible, built on argparse + pydantic
Project description
agentyper
Agent-first Python CLI library — built on argparse + pydantic, Typer-compatible.
Typer was built for the era when type hints changed Python.
agentyper is built for the era when AI agents changed how software is operated.
Why Agentyper?
Typer was built for humans. Agents need something different.
Typer revolutionized CLI development by elegantly using type hints. However, because it is built on Click and Rich, it is fundamentally optimized for human eyeballs. When interacting with standard CLIs, AI Agents struggle: they cannot organically discover the tool's schema, they get blocked by interactive prompts ([y/N]), they hallucinate when parsing ANSI-colored tables, and they fail to self-correct when errors are returned as flat strings.
Agentyper keeps the beloved Developer Experience (DX) of Typer but replaces the execution engine with argparse and Pydantic.
With a one-line switch (import agentyper as typer), your CLI instantly yields OpenAPI/JSON schemas, strict Pydantic structured errors, and deterministic programmatic overrides for interactive features.
📖 Learn more in our docs:
- For Agents: How to use an agentyper CLI — schema discovery, format flags, error JSON, bypass flags
- For Developers: Building agent-friendly CLIs — best practices, output patterns, testing
- Agent Requirements for CLI Tools
- Why Agentyper? (Alternatives & Comparison Matrix)
agentyper implements the CLI Agent Spec — a formal specification for agent-ergonomic CLI tools.
Install
pip install agentyper
Quick Start
Absolute minimum:
Create a main.py:
def main(name: str):
print(f"Hello {name}")
Run it without importing anything:
agentyper main.py run Camila
Single function (like typer.run()):
import agentyper
def search(ticker: str, limit: int = 10):
"""Search securities by ticker."""
results = service.search(ticker, limit)
agentyper.output(results) # routes via --format automatically
agentyper.run(search)
Multi-command app:
import agentyper
app = agentyper.Agentyper(name="my-tool", version="1.0.0")
@app.command()
def search(ticker: str, limit: int = agentyper.Option(10, help="Max results")):
"""Search securities."""
agentyper.output(service.search(ticker, limit))
@app.command()
def delete(name: str):
"""Delete a record."""
if agentyper.confirm(f"Delete '{name}'?"):
service.delete(name)
app()
Typer migration — one line:
# import typer ← before
import agentyper as typer # ← after; everything else stays identical
What Agents Get for Free
Every parser gets the core machine-facing flags:
my-tool --schema # full JSON Schema of the entire app
my-tool search --schema # JSON Schema for this command's params
my-tool search AAPL --format csv # 4× cheaper output than table
my-tool search AAPL --format json # structured JSON output
Interaction flags are always accepted, but they are shown in --help only when the app or command declares them explicitly or when agentyper can tell they are relevant. Timeout remains capability-based:
my-tool delete alice --yes # skip confirm() in agent mode
my-tool wizard --answers '{"confirms":[true],"prompts":["Alice","admin"]}'
my-tool sync --timeout 5000 # available when timeout support is enabled
This keeps --help output compact for simple non-interactive CLIs while still letting callers pass prompt-bypass flags defensively.
If you are an agent consuming an agentyper CLI, read docs/for-agents.md for schema discovery, error handling, and the full flag reference.
If you are building a CLI with agentyper, read docs/for-developers.md for patterns, best practices, and a pre-ship checklist.
Agent Ergonomics
| Feature | agentyper | Typer |
|---|---|---|
--schema on every command |
✅ automatic | ❌ manual |
--format json/csv/table |
✅ automatic | ❌ manual |
| Structured JSON errors | ✅ automatic | ❌ free text |
| Exit code taxonomy (0/1/2) | ✅ | ❌ 0 or 1 |
| Interactive features in agent mode | ✅ bypass flags when interactive | ❌ blocks |
isatty() auto-format detection |
✅ | ❌ |
| Dependencies | argparse + pydantic | Click + Typer |
Typer Compatibility Matrix
agentyper implements Typer's core API. For most CLIs, import agentyper as typer works flawlessly. Because it is built from scratch on argparse + Pydantic (for LLM reliability) instead of Click, there are some differences.
| Feature / API | Status | Notes |
|---|---|---|
@app.command(), @app.callback() |
✅ Supported | Core app routing works identically. |
Option(), Argument() |
✅ Supported | Core args mapping (default, help, etc.) |
| Type Hint Extraction | ✅ Supported | Uses Pydantic for robust validation. |
Interactive Prompts (confirm()) |
✅ Supported | Enhanced with non-blocking agent overrides. |
typer.Context (ctx) |
✅ Supported | Invocation-scoped context exposes ctx.format, ctx.runtime, ctx.root, ctx.params, and ctx.obj. click-specific methods (ctx.forward(), ctx.meta) still do not exist. |
click Parameter Types |
❌ Unsupported | Fully replaced by Pydantic. Use Literal["A"] instead of click.Choice. |
typer.style(), colors |
❌ Unsupported | Removed. Agents prefer plain text or structured JSON. |
Custom Click logic |
❌ Unsupported | Executed purely via standard argparse. |
Exit Codes
agentyper.EXIT_SUCCESS = 0 # success
agentyper.EXIT_VALIDATION = 1 # bad input — agent should retry with correction
agentyper.EXIT_SYSTEM = 2 # system error — agent should abort
Interactive Features
All interactive features from Typer work identically in a terminal.
In agent/non-TTY mode, they resolve without blocking. The interaction flags may be hidden from --help on non-interactive commands, but they are still accepted by the parser:
# Human terminal: asks interactively
my-tool delete alice
# Agent: auto-confirm via flag
my-tool delete alice --yes
# Agent: pre-supply all answers
my-tool wizard --answers '{"confirms":[true,false],"prompts":["Alice","admin"]}'
# Agent: pipe answers from stdin
echo '{"confirms":[true]}' | my-tool delete alice --answers -
If static detection is not enough for your app shape, expose the flags explicitly:
app = agentyper.Agentyper(name="my-tool", interactive=True)
@app.command(interactive=True)
def wizard() -> None:
...
Timeout Support
--timeout MS is also opt-in. It appears when timeout support is declared at the app or command level.
app = agentyper.Agentyper(name="my-tool", default_timeout_ms=30_000)
@app.command(timeout_ms=5_000)
def sync() -> None:
...
Use enable_timeout=True if you want the flag available even when the timeout value is supplied elsewhere.
Invocation Context
Commands and callbacks can accept ctx: agentyper.Context to inspect the resolved invocation state:
@app.command()
def sync(ctx: agentyper.Context, target: str):
client = build_client(ctx)
agentyper.output({"target": target, "format": ctx.format})
Use this rule of thumb:
- Pass
ctxexplicitly when the function is part of the command flow and invocation state is an important dependency. - Call
agentyper.get_current_context()in deep helper code that needs invocation-scoped state without forcingctxthrough unrelated layers. - If helper code may run outside an active CLI invocation, either accept
ctxexplicitly or handle theRuntimeErrorraised byget_current_context().
agentyper.Context exposes:
ctx.runtimefor resolved framework state such as format, verbosity, answers, and timeout.ctx.paramsfor parsed command parameters.ctx.rootfor resolved global/root flags.ctx.objfor app-owned mutable shared state.
Helper code can also read the active invocation context without manually threading it through every call:
def build_client(ctx: agentyper.Context | None = None) -> Client:
ctx = ctx or agentyper.get_current_context()
return Client(timeout_ms=ctx.runtime.timeout_ms, verbose=ctx.runtime.verbosity > 0)
This hybrid pattern works well for reusable helpers and tests:
def emit_success(
payload: dict,
ctx: agentyper.Context | None = None,
) -> None:
ctx = ctx or agentyper.get_current_context()
agentyper.output(payload, format_=ctx.format_)
@app.command()
def run(ctx: agentyper.Context) -> None:
emit_success({"status": "ok"}, ctx=ctx)
License
MIT
Project details
Release history Release notifications | RSS feed
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 agentyper-0.1.12.tar.gz.
File metadata
- Download URL: agentyper-0.1.12.tar.gz
- Upload date:
- Size: 42.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a65b81472702095b2f66fb8f857332aa64deed3dc9b195692bf35eab0c4539f0
|
|
| MD5 |
11abaff0fda45dd87191efe621eae80f
|
|
| BLAKE2b-256 |
a487d32445ec3a52eccfbba8af4b8df769e5280c3bf12878990aa044b5806732
|
File details
Details for the file agentyper-0.1.12-py3-none-any.whl.
File metadata
- Download URL: agentyper-0.1.12-py3-none-any.whl
- Upload date:
- Size: 39.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
585d37b8dbd51b481fdc62d48433c995dc535257baec9649772c1c5814cc7f79
|
|
| MD5 |
97290d9ba363abbd39c3e0ee509a82ed
|
|
| BLAKE2b-256 |
72832765a22f8aad388675b5108d49a0d0aea7cc8446458e40b6f075e704c0e7
|