# cli-forge v0.5.0 — Auth Seam & Feature Freeze
**The last feature, and the line in the sand.** v0.5.0 adds the auth seam — a hook
that gates commands behind the consumer's own login state — and **declares the
public surface frozen**. The framework is now feature-complete: output, commands,
help, and auth. The remaining 0.x releases are tests, docs, and optimization; this
surface becomes the 1.0 contract.
## 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.5.0
### The auth seam (feature `auth`)
cli-forge holds the *seam*, not the logic. A command marked
`.requires_auth(true)` runs — and appears in help — only when the app's
authorization hook allows it. The hook, supplied by you (or a sibling `cli-auth`
crate), is where login/logout state actually lives; the core just asks.
```rust
# #[cfg(feature = "auth")]
# {
use cli_forge::{App, Command, ParseError};
// The consumer decides what "authorized" means. Keep the hook pure — it also
// runs while generating help.
let logged_in = check_session();
app.register(Command::new("publish").requires_auth(true).run(|_| { /* gated */ }));
let err = app.try_parse_from(["publish"]).unwrap_err();
assert!(matches!(err, ParseError::Unauthorized { .. })); // refused when logged out
fn check_session() -> bool { false }
# }
```
- The hook is `Fn(&AuthRequest) -> bool`. `AuthRequest` names the command being
authorized (`command()`, `path()`) and is `#[non_exhaustive]` for future context.
- **Fails closed:** with the feature on and no hook set, auth-gated commands are
never authorized — they neither run nor appear in help.
- A refusal is `ParseError::Unauthorized { command }`; the handler does not run.
Under `parse` it prints to standard error and exits `2`.
- Unauthorized commands are **omitted from help**, so a locked feature isn't even
advertised until you're allowed to use it.
- **Without the `auth` feature**, `requires_auth` is inert and `App::auth` /
`AuthRequest` are absent — zero cost for the CLIs that don't need it.
The seam adds **no dependencies**.
### Public surface frozen
`docs/API.md` gains a **Stability** section: the surface is feature-complete and
frozen. The rest of the 0.x line adds tests, documentation, and internal
optimization — plus strictly-additive conveniences — but does not change or remove
existing API. This is what 1.0 will promise under strict SemVer.
The frozen surface: `out`, `err`, `parse`, `style` / `Style`, `define_tag` /
`tag` / `Tag`, `App`, `Command`, `Arg`, `Matches`, `ParseError`, and — behind
`auth` — `App::auth` / `AuthRequest`.
## Breaking changes
**None to the default build.** `App::auth`, `AuthRequest`, and
`ParseError::Unauthorized` are additive. With the `auth` feature enabled, one
behavior changes on purpose: `requires_auth` is now enforced, so an auth-gated
command requires a hook that authorizes it (previously the flag was inert). The
`auth` feature — previously a reserved no-op — now enables the seam and implies
`std`.
## 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 --features auth -- -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`):
- 68 unit tests (including 7 property tests).
- 4 integration tests (allocation proof + non-`main` registration).
- 45 documentation tests.
The default build (`auth` off) runs the inert-`requires_auth` path; the
`--all-features` build runs the enforced path — both are covered.
## What's next
- **1.0.0 — API freeze.** No new public API; documentation, tests, and internal
optimization only. The frozen surface here becomes the SemVer promise the
sibling crates (`cli-table`, `cli-progress`, gradients, layouts, shell) build
on.
## Installation
```toml
[dependencies]
cli-forge = "0.5"
# With the auth seam:
cli-forge = { version = "0.5", features = ["auth"] }
# Plain output only (no escape sequences; the API stays complete):
cli-forge = { version = "0.5", 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.4.0...v0.5.0`](https://github.com/jamesgober/cli-forge/compare/v0.4.0...v0.5.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/cli-forge/blob/main/CHANGELOG.md#050---2026-07-01).