argtype
A type language for command-line argument grammars.
The problem
CLI tools describe their arguments in prose, argparse decorators, ad-hoc schemas, or not at all. There is no shared structural representation, so every consumer reimplements the same knowledge from scratch. Wrapper generators scrape --help. Autocomplete scripts are hand-written per shell. Documentation drifts from implementation.
The structural information exists. It just isn't written down in a form anything can consume.
argtype is that form: a portable description of a tool's argument grammar. It changes nothing about how the tool is invoked; argv is still a plain string array, exactly as today. It sits alongside the program the way a man page does, and anything that wants to understand the tool's interface can read it.
The language
A CLI invocation is a string[]. argtype describes the structure of those arrays using six combinators, four terminal types, and quoted literals:
convert: seq(
input: path,
output: path,
set(
/// Output image quality (1-100)
opt("-quality", quality: int = 80),
/// Generate a thumbnail
opt("-thumbnail", size: str),
/// Strip all metadata
opt("-strip"),
),
)| Combinator | Meaning |
|---|---|
seq(...) | Ordered sequence |
set(...) | Unordered collection |
opt(...) | Optional |
rep(...) | Repetition |
alt(...) | Alternative (the choice is meaningful) |
any(...) | Alternative (the choice is cosmetic, e.g. -o / --output) |
There are no "flags", "options", or "positionals". A flag is opt("-f", float). A positional is a bare path. The language describes the shape of valid string arrays, not CLI conventions.
What falls out
An argtype definition is a complete structural description of a tool's arguments. That makes it a single source the rest of the toolchain can compile from. Any consumer that reads the grammar derives what it needs:
| Consumer | What it derives |
|---|---|
| IDEs | Intellisense and diagnostics for CLI invocations |
| Terminal emulators | Inline completions and argument previews |
| API generators | Typed wrappers in Python, TypeScript, or any language with functions and types |
| Agents | A typed parameter surface a model fills in instead of guessing a command string, checkable against the grammar before it runs |
| Shell completers | Valid tokens at any cursor position |
| Parsers | Typed parameter values extracted from argv |
| Documentation tools | Man pages and help text from structure and doc comments |
| Validators | Well-formedness checks before a command runs |
Why it works
Regular language. The combinators map to concatenation, union, Kleene star, optional, and permutation. No recursion. Parsing is linear-time, autocomplete is always computable, and analysis is tractable for every consumer.
Grammar/parametrization split. The grammar describes valid argv shapes. A solver derives the minimal typed interface, the parameters a caller actually needs to specify. Backends emit both the parameter interface and the argument builder. The API surface is computed from the grammar, not hand-designed.
Two notations. A sugar DSL for hand-authoring and a chaining API that embeds in any host language. Both produce the same representation. The chaining API gets imports, generics, and composition for free from the host:
const Flagged = (flag: string, value: Node) => opt(lit(flag), value)
const convert = set(
Flagged("-quality", int().name("quality").default(80)),
path().name("input"),
).name("convert")Why now
Tool-calling models get CLI invocations wrong all the time, because they're guessing strings: assembling ffmpeg -i in.mp4 -vf scale=... out.mp4 token by token from training data and hoping it works. argtype turns that into something checkable. The model fills in the typed parameters the grammar implies, a compiler builds the command, and those parameters can be validated against the grammar before anything runs, so a bad value is caught statically instead of by the program failing partway through.
It works in both directions. A capable model reads a tool's source (the argparse calls, the Click decorators, the clap macros) or its --help and writes the grammar. The structure is already in the implementation, the translation is mechanical, and the result is short enough to review by hand before you trust it. Smaller tool-calling models then consume those grammars: the whole syntax fits in a prompt, and the typed interface is small because the solver kept only the parameters that matter. The corpus problem ("who writes all these specs?") is mostly an LLM problem now, and a tractable one.
See the spec for the full language definition.