std/cli/argparse
Declarative argument parser for .harn CLI subcommand scripts dispatched
via the harn-cli wedge (harn#2293 epic, harn#2295). Each ported
subcommand declares a parser spec and calls parse(spec, argv) against
the global argv the dispatch wedge installs.
The library is pure .harn — no new Rust builtins — and intentionally
covers only the subset of flag parsing that today's ported commands
need:
- Positional arguments, required by default.
- Long flags (
--model,--model=val,--model val) and short flags (-m val,-mval). - Boolean switches (
--json). - Repeated flags collected into a list (
multi: true). - A
--separator routing everything after it intoparsed.rest. - Structured error envelopes for the four kinds of parse failures callers can usefully react to.
Out of scope by design:
- Nested subcommand parsers — each subcommand is its own embedded script and top-level dispatch has already picked it.
- Shell completion generation — clap covers that for the top-level CLI; the per-subcommand scripts don't ship completions.
- Validators beyond "value required" — the caller decides whether a parsed value is a valid integer / path / model id / etc.
Surface
import { parser, parse, render_help } from "std/cli/argparse"
let spec = parser({
name: "eval-prompt",
about: "Render and run a .harn.prompt across a fleet of models.",
args: [
{ name: "prompt", kind: "positional", required: true,
help: "Path to the .harn.prompt file." },
{ name: "model", kind: "flag", short: "-m", long: "--model",
multi: true, value_name: "ID",
help: "Model id; repeat for multi-model fanout." },
{ name: "fixture", kind: "flag", long: "--fixture", value_name: "PATH",
help: "Replay LLM responses from this JSONL fixture." },
{ name: "json", kind: "switch", long: "--json",
help: "Emit a JSON envelope instead of human output." },
],
examples: [
"eval-prompt prompts/summarize.harn.prompt",
"eval-prompt -m claude-opus-4-7 -m claude-haiku-4-5 prompts/summarize.harn.prompt",
],
})
let result = parse(spec, argv)
let err = result.err
if err != nil {
__io_eprintln(render_help(spec))
__io_eprintln("error: " + err.hint)
exit(2)
}
let parsed = result.ok
// parsed.prompt: string, parsed.model: list<string>, parsed.fixture: string?,
// parsed.json: bool, parsed.rest: list<string>
Argument kinds
| Kind | Required by default | Notes |
|---|---|---|
positional | Yes (override with required: false) | Set variadic: true to greedily collect the rest. |
flag | No | Takes a value. multi: true collects repeats into a list; default value becomes [] when unspecified. |
switch | No | Boolean; presence sets true. Default value becomes false when unspecified. |
Error envelopes
parse returns either { ok: dict } or { err: ParseError }. Error
kind is one of:
kind | When |
|---|---|
missing_required | A required positional or flag was absent. |
unknown_flag | A --foo or -f not registered in the spec. |
unknown_arg | An extra positional beyond what's registered. |
value_required | A flag was the last argv entry with no value. |
bad_value | A switch received a value (--json=true). |
Each error carries the offending arg (the original argv string) and a
short hint suitable for one-line stderr output.
--help rendering
render_help(spec) returns a stable, snapshot-tested layout:
{about, if present}
USAGE:
{name} [OPTIONS] <positional>...
ARGS:
<positional> help text
OPTIONS:
-s, --long <VALUE> help text
--switch help text
-h, --help Print help
EXAMPLES:
{example 1}
{example 2}
The layout is intentionally clap-flavored without trying to match
byte-for-byte. Snapshot tests under conformance/tests/cli/ pin both
the structure and the column alignment.