{"id":51317364,"url":"https://github.com/jamesgober/cli-forge","last_synced_at":"2026-07-01T09:00:33.541Z","repository":{"id":368559229,"uuid":"1284677074","full_name":"jamesgober/cli-forge","owner":"jamesgober","description":"Unified CLI framework: runtime command registration with styled output through one API.","archived":false,"fork":false,"pushed_at":"2026-07-01T05:23:55.000Z","size":185,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-07-01T07:10:25.436Z","etag":null,"topics":["cli","command-line","framework","reps","rust","terminal"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesgober.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["jamesgober"]}},"created_at":"2026-06-30T05:12:17.000Z","updated_at":"2026-07-01T05:11:18.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jamesgober/cli-forge","commit_stats":null,"previous_names":["jamesgober/cli-forge"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/jamesgober/cli-forge","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fcli-forge","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fcli-forge/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fcli-forge/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fcli-forge/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesgober","download_url":"https://codeload.github.com/jamesgober/cli-forge/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesgober%2Fcli-forge/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34999792,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","command-line","framework","reps","rust","terminal"],"created_at":"2026-07-01T09:00:25.408Z","updated_at":"2026-07-01T09:00:33.495Z","avatar_url":"https://github.com/jamesgober.png","language":"Rust","funding_links":["https://github.com/sponsors/jamesgober"],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n    \u003cimg width=\"99\" alt=\"Rust logo\" src=\"https://raw.githubusercontent.com/jamesgober/rust-collection/72baabd71f00e14aa9184efcb16fa3deddda3a0a/assets/rust-logo.svg\"\u003e\n    \u003cbr\u003e\n    \u003cb\u003ecli-forge\u003c/b\u003e\n    \u003cbr\u003e\n    \u003csub\u003e\u003csup\u003eUNIFIED CLI FRAMEWORK\u003c/sup\u003e\u003c/sub\u003e\n\u003c/h1\u003e\n\n\u003cdiv align=\"center\"\u003e\n    \u003ca href=\"https://crates.io/crates/cli-forge\"\u003e\u003cimg alt=\"Crates.io\" src=\"https://img.shields.io/crates/v/cli-forge\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://crates.io/crates/cli-forge\"\u003e\u003cimg alt=\"Downloads\" src=\"https://img.shields.io/crates/d/cli-forge?color=%230099ff\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://docs.rs/cli-forge\"\u003e\u003cimg alt=\"docs.rs\" src=\"https://img.shields.io/docsrs/cli-forge\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/jamesgober/cli-forge/actions\"\u003e\u003cimg alt=\"CI\" src=\"https://github.com/jamesgober/cli-forge/actions/workflows/ci.yml/badge.svg\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/rust-lang/rfcs/blob/master/text/2495-min-rust-version.md\"\u003e\u003cimg alt=\"MSRV\" src=\"https://img.shields.io/badge/MSRV-1.85%2B-blue\"\u003e\u003c/a\u003e\n\u003c/div\u003e\n\n\u003cbr\u003e\n\n\u003cdiv align=\"left\"\u003e\n    \u003cp\u003e\n        cli-forge is a unified command-line framework where argument parsing and styled output speak one API. Commands register at runtime - from anywhere, not just main - and can be hidden or auth-gated; output flows through a single layer (plain, tag-parsed, or builder-styled) that extensions like tables, progress, and gradients reuse seamlessly. It targets the lightness of argh with the reach of clap, without the split between parsing in one crate and styling in five.\n    \u003c/p\u003e\n    \u003cbr\u003e\n    \u003chr\u003e\n    \u003cp\u003e\n        \u003cstrong\u003eMSRV is 1.85+\u003c/strong\u003e (Rust 2024 edition).\n    \u003c/p\u003e\n    \u003cblockquote\u003e\n        \u003cstrong\u003eStatus: 1.0 \u0026mdash; stable.\u003c/strong\u003e The public API is frozen under \u003ca href=\"https://semver.org/\"\u003eSemantic Versioning\u003c/a\u003e: no breaking changes before \u003ccode\u003e2.0\u003c/code\u003e. See \u003ca href=\"./CHANGELOG.md\"\u003e\u003ccode\u003eCHANGELOG.md\u003c/code\u003e\u003c/a\u003e.\n    \u003c/blockquote\u003e\n\u003c/div\u003e\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## What's here\n\n`v1.0.0` is the **stable API freeze**. The framework is feature-complete — the\noutput layer, command layer, help engine, and auth seam are all in place, and the\npublic surface is guaranteed under SemVer:\n\n- **Plain output** — `out` / `err`: one call, no parsing, no allocation for a\n  string literal. The hot path stays cheap.\n- **Three styling paths** — a chainable `style` builder, inline `parse` tags, and\n  a named `define_tag` / `tag` registry. All three render to **identical bytes**\n  for the same intent.\n- **Full color** — the eight standard names plus any 24-bit color via hex or RGB,\n  with graceful downgrade to 256- or 16-color terminals and clean fall-back to\n  plain text on pipes, `NO_COLOR`, or the Windows console without ANSI.\n- **Command tree** — a recursive `Command` tree with a full argument model\n  (flags, **counting** flags `-vvv`, options, **repeatable** options, positionals,\n  **variadic** positionals) and **aliases**, registered into an `App` **from\n  anywhere** (not just `main`), with `.hidden()` / `.requires_auth()` flags and\n  structured, non-panicking errors.\n- **Help \u0026 version** — auto-generated `--help` / `-h` (top-level and per-command)\n  and `--version` / `-V`, rendered through the output layer with injectable\n  header/footer.\n- **Auth seam** *(feature `auth`)* — gate a command behind a consumer-supplied\n  authorization hook. cli-forge holds the seam; the login state lives in your code.\n  Fails closed, and hides unauthorized commands from help.\n\nEverything above is stable. Future `1.x` releases add only strictly-additive API,\nbug fixes, and internal optimization; nothing existing changes before `2.0`. See\nthe [`ROADMAP`](./dev/ROADMAP.md).\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Installation\n\n```toml\n[dependencies]\ncli-forge = \"1.0\"\n```\n\nColor is on by default. For a build that never emits escape sequences (the API\nstays complete; every styled value renders as its plain text):\n\n```toml\n[dependencies]\ncli-forge = { version = \"1.0\", default-features = false, features = [\"std\"] }\n```\n\n\u003cbr\u003e\n\n## Quick Start\n\n```rust\nuse cli_forge::{define_tag, err, out, parse, style, tag};\n\n// Plain output — the common case.\nout(\"building...\");\nerr(\"something went wrong\");\n\n// Styling, three ways, all rendering to the same bytes for the same intent:\nout(style(\"done\").green().bold());                          // builder\nparse(\"\u003cc=red\u003e\u003cb\u003eERROR:\u003c/b\u003e\u003c/c\u003e \u003cc=#ff8800\u003elow disk\u003c/c\u003e\");  // inline tags\ndefine_tag(\"error\", style(\"\").red().bold());                // named registry\nout(tag(\"error\").render_with(\"build failed\"));\n```\n\n\u003cbr\u003e\n\n## The three styling paths\n\nThe same styled line, produced three ways. The choice is ergonomic, not visual —\nthe bytes are identical.\n\n```rust\nuse cli_forge::{define_tag, out, parse, style, tag};\n\n// 1. Builder — chain methods; the result is `Display`. Best for computed/one-off.\nout(style(\"ERROR: build failed\").red().bold());\n\n// 2. Tags — one string with inline markup. Best when text and style live together.\nparse(\"\u003cc=red\u003e\u003cb\u003eERROR: build failed\u003c/b\u003e\u003c/c\u003e\");\n\n// 3. Named registry — define the look once, reuse by name across the program.\ndefine_tag(\"error\", style(\"\").red().bold());\nout(tag(\"error\").render_with(\"ERROR: build failed\"));\n```\n\n**Tag grammar:** `\u003cb\u003e…\u003c/b\u003e` (bold), `\u003cu\u003e…\u003c/u\u003e` (underline),\n`\u003cc=VALUE\u003e…\u003c/c\u003e` (color, where `VALUE` is a named color, `#rrggbb`, or `r,g,b`),\nand `\u003c/\u003e` to close the innermost tag. Tags nest; unrecognized tags print\nliterally, so `parse` never rejects input.\n\n\u003cbr\u003e\n\n## Colors and terminals\n\nColors are the eight standard names, plus any 24-bit value:\n\n```rust\nuse cli_forge::{out, style};\n\nout(style(\"amber\").hex(\"#ff8800\"));\nout(style(\"teal\").rgb(0, 200, 120));\nout(style(\"link\").hex(\"#3b82f6\").underline());\n```\n\nThe terminal's capability is detected once. A 24-bit color renders exactly on a\ntrue-color terminal, downgrades to the nearest 256- or 16-color value where that\nis all the terminal supports, and falls away to plain text when output is a pipe,\n`NO_COLOR` is set, or the crate is built without `color`. `CLICOLOR_FORCE`\noverrides detection and forces color on. The Windows console is handled behind the\nsame API — virtual-terminal mode is enabled automatically, with a plain-text\nfall-back if it cannot be.\n\n\u003cbr\u003e\n\n## Commands\n\nBuild a recursive command tree, register commands into an `App` from anywhere, and\nlet `parse` resolve the invocation, parse arguments, and run the selected\ncommand's handler:\n\n```rust\nuse cli_forge::{App, Arg, Command, out};\n\nlet mut app = App::new(\"forge\");\n\napp.register(\n    Command::new(\"build\")\n        .about(\"compile the project\")\n        .arg(Arg::flag(\"release\").short('r'))\n        .arg(Arg::option(\"jobs\").short('j').default(\"1\"))\n        .run(|m| out(format!(\n            \"release={} jobs={}\",\n            m.flag(\"release\"),\n            m.value(\"jobs\").unwrap_or(\"?\"),\n        ))),\n);\n\napp.register(\n    Command::new(\"remote\").subcommand(\n        Command::new(\"add\")\n            .arg(Arg::positional(\"url\").required(true))\n            .run(|m| out(format!(\"added {}\", m.value(\"url\").unwrap_or(\"?\")))),\n    ),\n);\n\nlet _ = app.parse();\n```\n\nCommands register **from anywhere** — a command built in a non-`main` module is\nreachable and behaves identically, the limitation that made the predecessor\nunusable. Give a command extra names with `.alias(\"rm\")` / `.aliases([\"rm\", \"del\"])`;\naliases resolve to the canonical command. Arguments cover flags, counting flags\n(`Arg::count`, read with `count()`), options, repeatable options and variadic\npositionals (`.multiple(true)`, read with `values()`), and positionals — parsed\nfrom all the standard forms (`--long`, `--long=value`, `-s`, `-svalue`, bundled\n`-abc`, `-vvv`, `--`). Malformed input becomes a structured `ParseError`: `parse`\nprints it and exits `2`, never panicking; `try_parse_from` returns it instead.\n`.hidden()` keeps a command invokable but out of help; `.requires_auth()` gates it\nbehind the auth hook (feature `auth`).\n\n**Help and version come for free.** `-h` / `--help` renders styled help for the\napp or any command (with your `help_header` / `help_footer`); `-V` / `--version`\nprints `App::version(...)`. `App::help()` renders the top-level help as a string\nwhenever you want it. Both exit `0` under `parse`; `try_parse_from` returns them\nas `ParseError::HelpRequested` / `VersionRequested` control signals.\n\n\u003cbr\u003e\n\n## Feature flags\n\n| Feature | Default | Description |\n|---------|---------|-------------|\n| `std` | yes | Standard library: terminal detection, the stdout/stderr writers, and the command layer. |\n| `color` | yes | ANSI / styled output. Implies `std`. Disable for plain output (still complete). |\n| `auth` | no | The auth seam: `App::auth`, `AuthRequest`, and enforcement of `requires_auth`. Implies `std`; adds no dependencies. |\n\n\u003cbr\u003e\n\n## Examples\n\nRunnable examples live in [`examples/`](./examples):\n\n```bash\ncargo run --example quick_start     # plain output and one styled line\ncargo run --example three_paths     # the same line, three ways\ncargo run --example colors          # named, hex, and rgb color\ncargo run --example status_report   # a realistic deploy-style status report\ncargo run --example commands -- build --release -j 8   # the command tree\ncargo run --example arguments -- build -vv -D A -D B a.rs b.rs   # every arg kind\n```\n\nForce color when output is captured, or disable it, to see both paths:\n\n```bash\nCLICOLOR_FORCE=1 cargo run --example status_report\nNO_COLOR=1       cargo run --example status_report\n```\n\n\u003cbr\u003e\n\n## Performance\n\nThe plain path is allocation-free for a string literal — proven by a\ncounting-allocator test (`tests/allocation.rs`), not asserted. Local Criterion\nmeans (Windows x86_64, release build):\n\n| Operation | ns/op |\n|-----------|------:|\n| `out` plain write (`\u0026str`) | ~10 |\n| builder render, named color + bold | ~50 |\n| builder render, 24-bit color | ~75 |\n| named-registry render | ~43 |\n\nStyling costs more than the plain path because it builds an owned `String` and\nencodes escape sequences — a cost paid only when you opt into color. Reproduce\nwith `cargo bench --bench bench`.\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Status\n\n`v1.0.0` is the **stable API freeze**: the public surface is guaranteed under\nSemantic Versioning — no breaking changes before `2.0`. `1.x` releases add only\nstrictly-additive API, fixes, and internal optimization. See the SemVer promise in\n[`docs/API.md`](./docs/API.md#stability) and the [`ROADMAP`](./dev/ROADMAP.md).\n\n\u003chr\u003e\n\u003cbr\u003e\n\n## Contributing\n\nSee \u003ca href=\"./dev/DIRECTIVES.md\"\u003e\u003ccode\u003edev/DIRECTIVES.md\u003c/code\u003e\u003c/a\u003e for engineering standards and the definition of done. Before a PR: `cargo fmt --all`, `cargo clippy --all-targets --all-features -- -D warnings`, and `cargo test --all-features` must be clean.\n\n\u003cbr\u003e\n\n\u003cdiv id=\"license\"\u003e\n    \u003ch2\u003eLicense\u003c/h2\u003e\n    \u003cp\u003eLicensed under either of\u003c/p\u003e\n    \u003cul\u003e\n        \u003cli\u003e\u003cb\u003eApache License, Version 2.0\u003c/b\u003e \u0026mdash; \u003ca href=\"./LICENSE-APACHE\"\u003eLICENSE-APACHE\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003cb\u003eMIT License\u003c/b\u003e \u0026mdash; \u003ca href=\"./LICENSE-MIT\"\u003eLICENSE-MIT\u003c/a\u003e\u003c/li\u003e\n    \u003c/ul\u003e\n    \u003cp\u003eat your option.\u003c/p\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \u003ch2\u003e\u003c/h2\u003e\n  \u003csup\u003eCOPYRIGHT \u003csmall\u003e\u0026copy;\u003c/small\u003e 2026 \u003cstrong\u003eJames Gober \u003cme@jamesgober.com\u003e.\u003c/strong\u003e\u003c/sup\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Fcli-forge","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesgober%2Fcli-forge","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesgober%2Fcli-forge/lists"}