Skip to content

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:

argtype
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"),
  ),
)
CombinatorMeaning
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:

ConsumerWhat it derives
IDEsIntellisense and diagnostics for CLI invocations
Terminal emulatorsInline completions and argument previews
API generatorsTyped wrappers in Python, TypeScript, or any language with functions and types
AgentsA typed parameter surface a model fills in instead of guessing a command string, checkable against the grammar before it runs
Shell completersValid tokens at any cursor position
ParsersTyped parameter values extracted from argv
Documentation toolsMan pages and help text from structure and doc comments
ValidatorsWell-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:

typescript
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.