# 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).