cmdkit 0.1.0

Core library for CLI tools, providing common functionality and utilities for building command-line applications.
Documentation
# CLI-Core

CLI-Core is a Rust library for implementation-first command dispatch with an instance-owned runtime.

The architecture is intentionally small and portable:

- commands are registered as a tree of implementations
- `Command` owns command metadata and an internal strategy handle
- `CommandStrategy` owns behavior for the selected command only
- routing nodes forward subcommands; leaf strategies consume the parsed invocation
- help output is generated from command metadata through a pluggable `HelpRenderer`

## Core concepts

### Command model

`Command` is the unit of registration. It contains:

- `metadata: CommandMetaData`
- an internal strategy handle

`CommandMetaData` includes required and optional help-facing fields:

- required: `name`, `description`
- optional: `usage`, `long_description`, `examples`, `options`, `aliases`

Use `CommandMetaData::new(...)` and builder-style metadata methods such as `with_usage(...)`, `with_examples(...)`, and `with_options(...)`.

### Strategy model

`CommandStrategy` defines one method:

```rust
fn execute(
    &self,
    options: Vec<String>,
    arguments: HashMap<String, String>,
    subcommands: Vec<String>,
) -> Result<(), StrategyError>
```

`CliCore` resolves `argv[1]` as the command name and parses the remaining tokens into:

- `options`: bare flags such as `--verbose`
- `arguments`: flag/value pairs such as `--path ./tmp`
- `subcommands`: the remaining command chain for nested routing

Only the final selected command strategy receives the parsed invocation.
Intermediate routing commands can ignore flags and options.

### Help rendering

Help is rendered from registered command metadata via `HelpRenderer`.

Default behavior uses `PlainTextHelpRenderer`, configured in `CoreConfig::new()`.
You can inject a custom renderer with `CoreConfig::with_help_renderer(...)`.

## Quick start

### 1. Define a strategy

```rust
use std::collections::HashMap;

use cmdkit::{CommandStrategy, StrategyError};

struct NewProject;

impl CommandStrategy for NewProject {
    fn execute(
        &self,
        options: Vec<String>,
        arguments: HashMap<String, String>,
        subcommands: Vec<String>,
    ) -> Result<(), StrategyError> {
        let project_name = arguments
            .get("name")
            .cloned()
            .ok_or_else(|| StrategyError::invalid_arguments("missing --name <project_name>"))?;

        println!("creating project: {project_name}");
        println!("options: {options:?}");
        println!("subcommands: {subcommands:?}");
        Ok(())
    }
}
```

### 2. Register commands

```rust
use cmdkit::{CliCore, Command};

let core = CliCore::new();
core.register(
    Command::new("new", "Create a new project", NewProject)
        .with_usage("new --name <project_name>"),
);
```

For nested commands, build a tree and let the runtime route to the leaf:

```rust
use std::collections::HashMap;

use cmdkit::{command, Command, CommandStrategy, StrategyError};

struct RunTask;

impl CommandStrategy for RunTask {
    fn execute(
        &self,
        options: Vec<String>,
        arguments: HashMap<String, String>,
        subcommands: Vec<String>,
    ) -> Result<(), StrategyError> {
        println!("options: {options:?}");
        println!("arguments: {arguments:?}");
        println!("subcommands: {subcommands:?}");
        Ok(())
    }
}

let app = command("app", "Application root")
    .subcommand(
        command("run", "Run tasks")
            .subcommand(Command::new("task", "Execute a task", RunTask)),
    )
    .build();
```

### 3. Run dispatch

```rust
core.run_with_commands(&[]);
```

Or use crate-level helpers:

```rust
cmdkit::run_with_commands(&[]);
```

### 4. Run with explicit args (tests/embedding)

```rust
use cmdkit::CliCoreError;

fn run_embedded(args: Vec<String>) -> Result<(), CliCoreError> {
    let core = CliCore::new();
    core.try_run_from_args(&args)
}
```

### 5. Pass parsed flags and values

```rust
let args = vec![
    "app".to_string(),
    "new".to_string(),
    "--name".to_string(),
    "my_app".to_string(),
];

core.try_run_from_args(&args)?;
```

## Configuring the runtime

`CoreConfig` is runtime-owned and immutable after `CliCore::create(config)`.

```rust
use cmdkit::{CliCore, CoreConfig, LockPoisonPolicy};

let config = CoreConfig::new()
    .with_lock_poison_policy(LockPoisonPolicy::Recover);

let core = CliCore::create(config);
```

## Custom help rendering

```rust
use cmdkit::{Command, HelpRenderer};

struct JsonHelpRenderer;

impl HelpRenderer for JsonHelpRenderer {
    fn render(&self, caller: &str, commands: &[Command]) -> String {
        format!("{{\"bin\":\"{}\",\"command_count\":{}}}", caller, commands.len())
    }
}
```

Use it with `CoreConfig::with_help_renderer(...)`.

## Proc macro (`#[cli]`)

The `#[cli]` macro lives in the separate `cmdkit-macros` crate. Add it alongside `cli-core` and import it from that package:

```rust
use std::collections::HashMap;

use cmdkit::StrategyError;
use cmdkit_macros::cli;

#[cli]
fn list_files(
    &self,
    options: Vec<String>,
    arguments: HashMap<String, String>,
    subcommands: Vec<String>,
) -> Result<(), StrategyError> {
    println!("options: {options:?}");
    println!("arguments: {arguments:?}");
    println!("subcommands: {subcommands:?}");
    Ok(())
}
```

This generates `ListFiles` with `ListFiles::new()` and a `list_files_strategy()` factory.

If you do not want the macro crate, you can still build commands directly with `Command::new(...)` or `Command::from_fn(...)`.

## Error model

- routing errors: `CliCoreError`
- strategy errors: `StrategyError` with kinds
  - `InvalidArguments`
  - `Execution`
  - `Internal`
- `CliCoreError::StrategyExecution` retains the original strategy error as source

## Notes

- command lookup is flat by command name at the runtime boundary
- help is metadata-driven and can recursively traverse registered subcommand trees
- routing commands only forward nested subcommands; leaf strategies consume parsed flags and values
- the runtime is instance-owned, so the architecture stays portable and does not depend on process-global state