# cli-forge v0.2.5 — Output Tower

**The load-bearing release.** v0.2.5 builds the output layer that every other
piece of cli-forge — and every sibling crate in the cli collection — depends on:
plain `out`/`err`, three styling paths that render to identical bytes, and a
single cross-platform terminal backend. The roadmap front-loaded this on purpose;
it is the hard part, and it is not deferred. The plain path is proven
allocation-free by test rather than by claim. This is the first substantive
release under the `cli-forge` name — 0.2.0 was a name claim on crates.io — and it
carries zero breaking changes against the 0.1.0 scaffold's frozen interface.

## 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 — unlike either — keeps output styling in the same system
as parsing, so extensions (tables, progress, gradients, layouts, shells) all build
on one output layer.

## What's new in 0.2.5

### `out` / `err` — the plain path

The common case is one call, and it stays cheap: no tag parsing, no styling work,
and no heap allocation for a string literal. The value is formatted straight to
the stream and followed by a newline.

```rust
use cli_forge::{err, out};

out("building...");
out(format!("compiled {} targets", 3));
err("error: missing argument `--input`");
```

`out`/`err` take anything `Display`, so a styled value drops straight in and
renders on the way out. A failed write — a closed pipe — is silently ignored
rather than panicking; a fire-and-forget print must never abort the program.

### Three styling paths, one set of bytes

The same styled line, produced three ways. The choice is ergonomic, not visual:
all three render to identical bytes for the same intent, verified across every
color level.

```rust
use cli_forge::{define_tag, out, parse, style, tag};

// 1. Builder — chain methods; the result is `Display`.
out(style("ERROR: build failed").red().bold());

// 2. Inline tags — markup parsed only here, never in `out`.
parse("<c=red><b>ERROR: build failed</b></c>");

// 3. Named registry — define the look once, reuse by name anywhere.
define_tag("error", style("").red().bold());
out(tag("error").render_with("ERROR: build failed"));
```

The builder exposes the eight standard named colors plus 24-bit `hex` / `rgb`,
`bold`, `underline`, and `render`. The tag grammar is deliberately small — `<b>`,
`<u>`, `<c=VALUE>` (named / `#rrggbb` / `r,g,b`), and `</>` — nests freely, and
prints unrecognized markup verbatim, so `parse` never rejects input. The registry
captures a style under a name and recalls it anywhere, even across modules.

Parameters render in a fixed canonical order (bold, underline, color), so the same
intent yields the same bytes regardless of how a builder chain is ordered or which
path produced it.

### One cross-platform terminal backend

Color capability is resolved once, from standard output, and applied to all styled
rendering:

- A 24-bit color is emitted exactly on a true-color terminal, and **degrades** to
  the nearest 256-color cube entry or the nearest of the 16 standard colors on
  terminals that cannot render it.
- Styling is dropped entirely — values render as plain text — when output is a
  pipe or a file, when `NO_COLOR` is set, when `TERM=dumb`, or when the crate is
  built without the `color` feature. `CLICOLOR_FORCE` overrides detection.
- The **Windows console** is driven through the same ANSI backend as Unix; virtual
  terminal processing is enabled automatically on first use, with a plain-text
  fall-back if it cannot be enabled. The enable call is delegated to a tiny
  Windows-only dependency so the crate root stays `#![forbid(unsafe_code)]`.

```rust
use cli_forge::{out, style};

out(style("amber").hex("#ff8800"));      // 24-bit where supported
out(style("teal").rgb(0, 200, 120));     // downgrades on 256/16-color terminals
out(style("link").hex("#3b82f6").underline());
```

### Allocation-free plain path, proven by measurement

`tests/allocation.rs` installs a counting global allocator and asserts that the
exact formatting operation `out` performs on a `&str` makes **zero** heap
allocations. A sibling test guards the harness itself by confirming a real
allocation is observed. This is the roadmap's "proven by benchmark, not by claim"
exit criterion, satisfied by an executable proof.

### Examples, benchmarks, and property tests

Four runnable examples — `quick_start`, `three_paths`, `colors`, and
`status_report` — cover plain output, the three paths, the color model, and a
realistic deploy-style status report. Criterion benchmarks measure the plain and
styled render paths. `proptest` properties fuzz the tag parser (it never panics on
arbitrary input; tagless text passes through unchanged) and the color downgrades
(every 24-bit color maps to a valid 256-cube index and basic SGR code; hex
round-trips).

### Scaffold fixes folded in

- `Cargo.toml` features now match the documented surface (`std`, `color`, `auth`);
  the undocumented `serde` dependency was removed.
- `rust-toolchain.toml` added (REPS reproducibility); the CI matrix overrides it
  per-job via `RUSTUP_TOOLCHAIN` so the 1.85 MSRV is still exercised.
- `clippy.toml` MSRV corrected to `1.85`; `deny.toml` header corrected.

## Breaking changes

**None.** The 0.1.0 release froze the public interface in `docs/API.md`; v0.2.5
fills in the output portion of that contract without changing a signature. The
0.2.0 name-claim release carried no public API to break.

## 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 bench --bench bench
cargo deny check
cargo audit
```

All green. Counts at this tag (`--all-features`):

- 44 unit tests (including 6 property tests).
- 2 allocation-proof integration tests.
- 15 documentation tests.

## What's next

- **0.3.0 — Command tree + runtime registration.** The recursive `Command` tree
  with args/flags, and an `App` registry that accepts commands registered from
  anywhere — not just `main` — the limitation that made the predecessor unusable.
  Hidden and auth-gated command flags. Rendered through this release's output
  layer.

## Installation

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

# Plain output only (no escape sequences; the API stays complete):
cli-forge = { version = "0.2", 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.2.0...v0.2.5`](https://github.com/jamesgober/cli-forge/compare/v0.2.0...v0.2.5).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/cli-forge/blob/main/CHANGELOG.md#025---2026-06-30).
