Metadata-Version: 2.4
Name: agentyper
Version: 0.1.13
Summary: Agent-first Python CLI library — Typer-compatible, built on argparse + pydantic
Project-URL: Homepage, https://github.com/romamo/agentyper
Project-URL: Repository, https://github.com/romamo/agentyper
Project-URL: Issues, https://github.com/romamo/agentyper/issues
Author-email: Roman Medvedev <pypi@romavm.dev>
License: MIT
License-File: LICENSE
Keywords: agent,ai,argparse,cli,pydantic,typer
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Terminals
Requires-Python: >=3.10
Requires-Dist: pydantic>=2.0.0
Requires-Dist: rich>=13.0.0
Provides-Extra: completion
Requires-Dist: argcomplete>=3.0.0; extra == 'completion'
Description-Content-Type: text/markdown

# 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](docs/for-agents.md)** — schema discovery, format flags, error JSON, bypass flags
* **[For Developers: Building agent-friendly CLIs](docs/for-developers.md)** — best practices, output patterns, testing
* **[Agent Requirements for CLI Tools](docs/agent_cli_requirements.md)**
* **[Why Agentyper? (Alternatives & Comparison Matrix)](docs/why_agentyper.md)**

> agentyper implements the **[CLI Agent Spec](https://github.com/romamo/cli-agent-spec)** — a formal specification for agent-ergonomic CLI tools.

## Install

```bash
pip install agentyper
```

## Quick Start

**Absolute minimum:**

Create a `main.py`:
```python
def main(name: str):
    print(f"Hello {name}")
```

Run it without importing anything:
```bash
agentyper main.py run Camila
```

**Single function (like `typer.run()`):**

```python
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:**

```python
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:**

```python
# 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:

```bash
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:

```bash
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](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](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

```python
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:

```bash
# 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:

```python
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.

```python
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:

```python
@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 `ctx` explicitly 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 forcing `ctx` through unrelated layers.
- If helper code may run outside an active CLI invocation, either accept `ctx` explicitly or handle the `RuntimeError` raised by `get_current_context()`.

`agentyper.Context` exposes:

- `ctx.runtime` for resolved framework state such as format, verbosity, answers, and timeout.
- `ctx.params` for parsed command parameters.
- `ctx.root` for resolved global/root flags.
- `ctx.obj` for app-owned mutable shared state.

Helper code can also read the active invocation context without manually threading it through every call:

```python
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:

```python
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
