# cli-forge v0.6.0 — Argument Conveniences

**Two small things a real CLI is judged on, added within the freeze.** v0.6.0
brings counting flags (`-vvv`) and multiple values (repeatable options and
variadic positionals) — the "give me N of these" patterns you reach for the moment
a tool does anything real. Both are strictly additive: nothing that shipped in
0.5.0 changed or moved, so the frozen surface holds.

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

### Counting flags — `-vvv`

[`Arg::count`](https://docs.rs/cli-forge/latest/cli_forge/struct.Arg.html) defines
a repeatable switch whose occurrences are tallied — the near-universal verbosity /
log-level idiom. `-v`, `-vv`, `-vvv`, `-v -v -v`, and `--verbose --verbose` all
count. Read the total with `Matches::count`.

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

let mut app = App::new("demo");
app.register(Command::new("run").arg(Arg::count("verbose").short('v')));

let m = app.try_parse_from(["run", "-vvv"]).unwrap();
assert_eq!(m.subcommand().unwrap().1.count("verbose"), 3);
```

`Matches::flag` still works on a counting flag (true once counted), so it doubles
as a boolean.

### Multiple values — repeatable options & variadic positionals

`Arg::multiple(true)` collects every occurrence into a list instead of keeping one
value. On an option it makes it repeatable; on a positional it makes it variadic,
absorbing every remaining bare value (put it last). Read them with
`Matches::values`, which yields the values in order.

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

let mut app = App::new("cc");
app.register(
    Command::new("build")
        .arg(Arg::option("define").short('D').multiple(true))       // -D A -D B
        .arg(Arg::positional("sources").multiple(true).required(true)), // a b c
);

let m = app.try_parse_from(["build", "-D", "A", "-D", "B", "x.c", "y.c"]).unwrap();
let (_, build) = m.subcommand().unwrap();
assert_eq!(build.values("define").collect::<Vec<_>>(), ["A", "B"]);
assert_eq!(build.values("sources").collect::<Vec<_>>(), ["x.c", "y.c"]);
```

`values` returns an iterator (zero-allocation; `.collect()` for a `Vec`).
`value` still returns a single value — the first for a `multiple` argument — so
the common case is unchanged. A single option given twice remains last-wins unless
marked `multiple`.

Help reflects the new kinds: a variadic positional renders as `<sources>...`, a
repeatable option as `-D, --define <DEFINE>...`, a counting flag as a plain
`-v, --verbose`.

### Minimal surface, by design

Four new items, nothing more: `Arg::count`, `Arg::multiple`, `Matches::count`,
`Matches::values`. No new dependencies. The internal storage change (per-key value
lists) is entirely behind `pub(crate)`.

## Breaking changes

**None.** The additions are strictly additive; `value`, `flag`, `option`,
`positional`, and single-value behavior are unchanged. This is the kind of
additive step the frozen surface explicitly allows.

## 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
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
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`):

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

Doc is now verified in **both** feature configurations (default and
`--all-features`) so cross-feature links can't regress.

## What's next

- **1.0.0 — API freeze.** No new public API; documentation, tests, and internal
  optimization only. The frozen surface becomes the SemVer promise the sibling
  crates (`cli-table`, `cli-progress`, gradients, layouts, shell) build on.

## Installation

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

# With the auth seam:
cli-forge = { version = "0.6", features = ["auth"] }

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