# cli-forge v0.4.0 — Help Engine

**Help, and the little things a base CLI is expected to have.** v0.4.0 adds
auto-generated help rendered through the same output layer as everything else,
plus three conveniences that a real command-line tool is judged on: command
**aliases**, **`--help` / `-h`**, and **`--version` / `-V`**. Small features,
deliberately — no animation, no tables — but the ones a decent share of users
reach for, at close to zero cost to the base.

## What is cli-forge?

A unified command-line framework: argument parsing and styled output through one
API, with commands that register at runtime. It targets the lightness of argh with
the reach of clap, and keeps output styling in the same system as parsing.

## What's new in 0.4.0

### Auto-generated help

`-h` / `--help` at any level renders that level's help — the application's, or a
specific command's — through the output layer, so section headers are styled and
degrade to plain text on a pipe or under `NO_COLOR`. The injectable
`help_header` / `help_footer` wrap every page.

```text
demo — a cli-forge example

USAGE: demo remote <command>

OPTIONS:
  -h, --help       show this help

COMMANDS:
  add              add a remote
  remove, rm, del  remove a remote

see https://github.com/jamesgober/cli-forge
```

`App::help()` renders the top-level help as a `String` on demand — handy for a
no-command fallback or your own `help` command:

```rust
use cli_forge::{App, Command, out};

let mut app = App::new("demo");
app.register(Command::new("build").about("compile the project"));

let matches = app.try_parse_from(Vec::<String>::new()).unwrap();
if matches.subcommand().is_none() {
    out(app.help());
}
```

Hidden and auth-gated commands are omitted from the listings (auth-gated commands
surface once the auth seam lands in v0.5.0).

### Command aliases

Give a command extra invocation names. Aliases resolve to the canonical command —
`matches.subcommand()` reports the real name no matter which alias was typed — and
they appear next to the name in help.

```rust
use cli_forge::{App, Command};

let mut app = App::new("demo");
app.register(Command::new("remove").aliases(["rm", "del"]));

let matches = app.try_parse_from(["rm"]).unwrap();
assert_eq!(matches.subcommand().unwrap().0, "remove"); // canonical
```

### `--version` / `-V`

Set a version and the flags print it; leave it unset and they stay ordinary
unknown flags. The idiom is the crate version:

```rust
use cli_forge::App;
let app = App::new("forge").version(env!("CARGO_PKG_VERSION"));
```

### One path for help, version, and errors

`-h`/`--help` and `-V`/`--version` are not failures, but they need to travel the
same channel as parse errors so the exiting `parse` and the non-exiting
`try_parse_from` agree. They come back as `ParseError::HelpRequested(String)` and
`ParseError::VersionRequested(String)` (carrying the rendered text). `parse` prints
them to standard output and exits `0`; real errors go to standard error and exit
`2`. The `#[non_exhaustive]` marker on `ParseError` — added in 0.3.0 for exactly
this — kept the additions non-breaking. A command can opt out of the built-ins by
declaring its own `help` / `h` argument.

## Breaking changes

**None.** Everything is additive: `App::version` / `App::help`, `Command::alias` /
`Command::aliases`, and two new `#[non_exhaustive]` `ParseError` variants. Existing
0.3.0 code compiles and behaves the same — except that `-h`/`--help` and, when a
version is set, `-V`/`--version` are now handled instead of reported as unknown
flags.

## Verification

Run on Windows x86_64, Rust stable 1.95.x; the same commands pass on Linux (WSL2
Ubuntu) and via the CI matrix (Linux/macOS/Windows × stable/1.85):

```bash
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo test --all-features
cargo build --no-default-features            # lib compiles plain (no_std-capable)
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
RUSTUP_TOOLCHAIN=1.85 cargo test --all-features
cargo deny check
cargo audit
```

All green, with no new dependencies. Counts at this tag (`--all-features`):

- 63 unit tests (including 7 property tests).
- 4 integration tests (allocation proof + non-`main` registration).
- 43 documentation tests.

## What's next

- **0.5.0 — Auth seam, feature freeze.** The hook that enforces `requires_auth`
  (login/logout state supplied by the consumer or a sibling `cli-auth` crate —
  core holds the seam, not the logic). An auth-gated command does not run — or
  appear in help — unless the hook authorizes it. Public surface declared frozen.

## Installation

```toml
[dependencies]
cli-forge = "0.4"

# Plain output only (no escape sequences; the API stays complete):
cli-forge = { version = "0.4", default-features = false, features = ["std"] }
```

MSRV: Rust 1.85.

## Documentation

- [README](https://github.com/jamesgober/cli-forge/blob/main/README.md)
- [API Reference](https://github.com/jamesgober/cli-forge/blob/main/docs/API.md)
- [Roadmap](https://github.com/jamesgober/cli-forge/blob/main/dev/ROADMAP.md)
- [CHANGELOG](https://github.com/jamesgober/cli-forge/blob/main/CHANGELOG.md)

---

**Full diff:** [`v0.3.0...v0.4.0`](https://github.com/jamesgober/cli-forge/compare/v0.3.0...v0.4.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/cli-forge/blob/main/CHANGELOG.md#040---2026-06-30).
