incurs 0.7.2

A declarative CLI framework for Rust with typed commands, agent discovery, HTTP, and MCP
Documentation
# incurs

The CLI framework for humans and agents.

Define a command once. incurs derives the argument parsing, help, validation, output
formatting, JSON Schemas, and every transport from that single definition — so a
command you wrote for a terminal is already an MCP tool, an HTTP route, an OpenAPI
operation, a skill file an agent can read, a shell completion, and a window.

```toml
[dependencies]
incurs = "0.6"
schemars = "1"
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
```

Requires Rust 1.88 or newer.

## Quick start

```rust
use incurs::cli::Cli;
use incurs::command::{CommandDef, TypedContext, TypedResult};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(Deserialize, incurs::Args)]
struct GreetArgs {
    /// Name to greet.
    name: String,
}

#[derive(Deserialize, incurs::Options)]
struct GreetOptions {
    /// Add an exclamation mark.
    excited: bool,
}

#[derive(JsonSchema, Serialize)]
struct GreetOutput {
    message: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let greet = CommandDef::typed::<GreetArgs, GreetOptions, (), GreetOutput, _, _>(
        "greet",
        |ctx: TypedContext<GreetArgs, GreetOptions, ()>| async move {
            TypedResult::ok(GreetOutput {
                message: format!(
                    "Hello, {}{}",
                    ctx.args.name,
                    if ctx.options.excited { "!" } else { "." },
                ),
            })
        },
    )
    .description("Greet someone")
    .done();

    Cli::create("greet")
        .version("1.0.0")
        .command("greet", greet)
        .serve()
        .await
}
```

```console
$ greet greet Ada --excited --json
{
  "message": "Hello, Ada!"
}
```

Doc comments become descriptions everywhere. `CommandDef::typed` derives the input
schemas from `GreetArgs` and `GreetOptions`, and the output schema from `GreetOutput`.
The handler receives validated values whether the call arrived from a terminal, HTTP,
or an agent.

## What that one definition already gives you

Nothing below needs extra code.

| Run this | You get |
| --- | --- |
| `greet --help` | Help for the CLI and every command |
| `greet greet --schema` | The command's JSON Schema |
| `greet --llms` / `--llms-full` | A manifest written for an agent to read |
| `greet --format json\|yaml\|toon\|jsonl\|md` | The same result in any output format |
| `greet --mcp` | An MCP server over stdio |
| `greet mcp add` | Registration with detected MCP clients |
| `greet skills add` | Agent Skills installed for detected agents |
| `greet completions bash\|zsh\|fish` | A shell completion script |
| `greet plugin build --output ./dist` | A portable Agent Plugins 1.0 package |
| `greet --config-schema` | A schema for the config file format |

## Output

A command returns `TypedResult::ok(value)`, and the serialized value *is* the output.
Pass `--full-output` for the `{ok, data, meta}` envelope when a caller wants timing and
command metadata. `TypedResult::error(code, message)` produces the structured error
envelope and a non-zero exit; `ok_with_exit_code` passes a wrapped subprocess's status
through without turning success into a failure; `ok_with_cta` attaches follow-up
commands.

The default format is [TOON](https://crates.io/crates/toon-format). Table and CSV are
deliberately opt-in, through a separate crate:

```toml
incurs-extras = "0.6"
```

```rust
use incurs_extras::{CliExtras, ExtraFormat};

let cli = cli.default_extra_format(ExtraFormat::Table);
```

## Feature flags

```toml
incurs = { version = "0.6", features = ["http", "mcp", "openapi"] }
```

| Feature | Adds |
| --- | --- |
| `cli` *(default)* | Process adapters, signals, and terminal output |
| `toon`, `tokens` *(default)* | TOON output and `--token-count` / `--token-limit` |
| `http` | Axum routes for root and nested commands, plus fetch gateways |
| `mcp` | MCP server, all five published standards at once |
| `openapi` | Import an OpenAPI 3.x document as commands, and emit one |
| `yaml` | YAML output |
| `agent-plugins`, `agent-plugins-mcp` | Load Agent Plugin packages, with or without MCP transports |

MCP clients that support discovery negotiate with `server/discover`; older clients
initialize with their exact standard. Fallback happens only on protocol evidence, never
on an authentication, transport, or server failure.

## The `incurs` command-line tool

```bash
cargo install incurs-cli     # installs the `incurs` binary
```

```bash
incurs explain               # the Rust authoring reference, by topic
incurs explain typed-commands
```

### Code generation

Point it at a project whose binary exposes `--llms-full`, and it writes typed helpers
for calling that CLI from Rust:

```bash
incurs gen --dir ./my-cli --entry my-cli --config-schema
```

- `src/incurs_generated.rs` — typed command modules, argument and option types, CTA renderers, and the embedded manifest
- `incurs.manifest.json` — the canonical command manifest
- `config.schema.json` — the config file schema, with `--config-schema`

`--output` and `--json-output` override the first two paths. `--entry` takes a Cargo
binary name or a path to an executable.

### Agent Plugin packages

Any incurs CLI can package itself:

```bash
my-cli plugin build --bundle-cli --output ./dist/my-cli-plugin
```

The package keeps its layers separate: `skills/<name>/SKILL.md` for agents to read,
`mcp.json` declaring the tool surface, and `bin/my-cli` as the executable, all declared
by a root `plugin.json`. Use `--plugin-no-mcp` for a skills-only package.

The `incurs` binary is also a client for those packages:

```bash
incurs plugin install ./dist/my-cli-plugin
incurs plugin validate ./dist/my-cli-plugin --data-dir ~/.local/share/my-cli-plugin
incurs plugin tools    ./dist/my-cli-plugin --data-dir ~/.local/share/my-cli-plugin
incurs plugin call     ./dist/my-cli-plugin my-server_my-tool \
  --arguments '{"name":"Ada"}' --data-dir ~/.local/share/my-cli-plugin
incurs plugin uninstall my-cli [--purge]
```

Installation validates the package, checks its operating system and architecture, and
installs the command into your executable directory, telling you when that directory is
not on `PATH`. Each MCP server connects independently, so one failure does not hide the
tools from the others. The data directory you name is created before launch and
persists across runs.

See [Agent Plugins compatibility](docs/agent-plugins.md) for the full behavior and
failure-boundary matrix.

## Calling commands without a CLI

`Cli::tool_catalog()` exposes every MCP-visible command as a transport-neutral Rust
API. Calls made through it use the same schemas, middleware, environment fields, config
defaults, streaming results, and structured errors as the CLI — it is the boundary MCP,
Code Mode, and the desktop application all go through. `try_tool_catalog()` reports
name collisions instead of silently replacing a command.

## Code Mode

`incurs-codemode` lets an agent write a small JavaScript program that calls your tools,
instead of making one tool call per step. It provides connector discovery, approval
policy, immutable capability snapshots, deterministic replay, cancellation, ordered
events, artifact-backed large values, rollback hooks, and bounded durable history. It
can wrap an incurs catalog, a remote MCP client, or an authenticated OpenAPI client.

`incurs-codemode-local` runs those programs in a resource-limited QuickJS runtime, and
`incurs-codemode-mcp` exposes the lifecycle to MCP clients:

| Tool | Purpose |
| --- | --- |
| `codemode_search` | Search tools and snippets, with the declarations needed to call each match |
| `codemode_execute` | Start a JavaScript execution |
| `codemode_execution` | Read execution state or an owned artifact |
| `codemode_decide` | Approve or reject one pending action |
| `codemode_cancel` | Cancel a running or paused execution |

## Terminal applications

`incurs-app-ratatui` runs a command graph full-screen in a terminal: commands on
the left, the selected command's inputs collected from its schema, results below.

```rust
use incurs_app_ratatui::TerminalApp;

TerminalApp::from_cli(&cli)?.title("Todo").run()
```

The installed `incurs` tool does this for its own commands:

```bash
incurs --tui
```

See [its README](crates/incurs-app-ratatui/README.md) for the key map and the
controls each schema type gets.

## Native desktop applications

`incurs-app-gpui` ships the same command graph as a double-clickable application, for
people who will never open a terminal. Commands are listed in a window, each command's
inputs are collected from its schema, and every call goes through the tool catalog — so
validation, middleware, config defaults, streaming, and cancellation behave exactly as
they do on the CLI.

```rust
use incurs_app_gpui::DesktopApp;

DesktopApp::from_cli(&cli)?.title("Todo").run()
```

`MacBundle` wraps the built executable in a macOS `.app` that installs by dragging.
See [its README](extensions/gpui/README.md) for the full guide and current limits.

## How it fits together

```text
              typed command definitions
                         |
                         v
            shared command graph + schemas
      ______________|____________________________
     |        |        |            |            |
    CLI     HTTP      MCP     tool catalog   generated artifacts
                                   |          |-- OpenAPI
                                   |          |-- Agent Skills
                                   |          |-- shell completions
                                   |          `-- Rust and JSON codegen
                                   |
                                   |-- Code Mode  (local QuickJS, remote executors)
                                   |
                                   `-- native desktop window
```

## Learn more

- [`incurs explain`]crates/incurs-cli — the authoring reference, also compiled into [`SKILL.md`]SKILL.md for agents
- [A worked example]crates/incurs/examples/todoapp.rs covering commands, streaming, middleware, CTAs, discovery, and output formats
- [MIGRATION.md]MIGRATION.md — upgrading between releases
- [CHANGELOG.md]CHANGELOG.md — what changed
- [CONTRIBUTING.md]CONTRIBUTING.md — working on incurs itself

## License

MIT. incurs began as a Rust port of [wevm/incur](https://github.com/wevm/incur) and
keeps its command model; the two are now independent.