Start at https://optique.dev/llms.txt when online. These rules cover the combinatorial parser model and common pitfalls offline.
Core rules
- Use
run()from @optique/run for apps,parse()/runParser()to embed. - With
runParser(),onError(exitCode, error)supplies a structuredMessage;help.onShow(exitCode, page)supplies the finalDocPage. Both follow output. Supplystdout: () => {}for custom help rendering. See https://optique.dev/concepts/runners.md#structured-help-callbacks. - In tests, use
parseArgs()/parseArgsSync()from @optique/testing/parser for parser results,captureRun()from @optique/testing/run for runner output/exits,captureProgramRun()from @optique/testing/discover for dispatch, andcreateCliRunner()from @optique/testing/cli for real CLIs. Pincolors/maxWidthin tests; defaults use terminal and environment. - Compose parsers with
object(),tuple(),seq(),or(),merge(), and modifiers. Do not hand-write argument scanners around Optique parsers. - Let TypeScript infer the parsed value type from the parser. Do not hand-maintain a separate interface for the result unless another API boundary requires it.
- Most parsers are required until you wrap them.
optional(p)yieldsundefined;withDefault(p, value)yields a fallback value. For Boolean flags, usewithDefault(flag("--name"), false)when absence should meanfalse. - Use semantic
messagehelpers for descriptions and errors. Since 1.3.0, customize terminal output withthemeormessageFormatter, preservinginitialWidth, quoting, and width options. Keep canonical errors unthemed. - Use value parsers such as
integer(),choice(),biject(),regExp(),url(), anduuid()instead of validating raw strings after parsing. UseregExp({ flags })for user-supplied regular expression sources,biject()for one-to-one string-to-value choices, andtransform()when an existing value parser describes the accepted CLI spelling but your app needs a different result type. Usepath()from@optique/run/valueparserfor file-system paths. Write a custom{ mode, metavar, parse, format }value parser only when the catalog does not cover the domain. - Async value parsers like @optique/git make containing parsers async.
Await
run()/parse()/runParser()or, forbindKeyring(),runAsync(). - Use
dependency()when one value parser controls another's valid values. For a multi-level chain, wrap the middle derivation too:dependency(source.deriveSync(...)). Optique resolves such chains by dependency order, independently of object/tuple field order. - Use
derivePromptConfig(source, resolver)(from @optique/prompt, re-exported by @optique/inquirer and @optique/clack) when a prompt's choices or message depend on another parsed value. The resolver may be async and runs only at the real prompt fallback, after the named sources resolve; pass[sourceA, sourceB]when it reads several sources. The resolver's prompt kind must return the wrapped parser's value type. Derive the wrapped parser separately when the CLI domain should change, and make it a dependency source only if another consumer needs its answer. Keep static prompt configs for everything else. - Pass
{ validate, maxAttempts, signal }as a generated prompt wrapper's third argument, includingprompt()from @optique/inquirer and @optique/clack. The validator returnsundefinedto accept the prompted value or a structuredMessageto retry, synchronously or asynchronously. Attempt limits must be positive integers and default to unlimited retries. Selection prompts keep their config and use the sharedvalidateoption. - Implement a custom adapter's
execute(config, context)so retries can showcontext.previousValidationMessage, and forwardcontext.signalwhen the prompt library supports aborting active work. Adapter-native validation remains separate and completes inside one shared attempt. - Build subcommands with
command()combined byor(). Put a literal field such ascommand: constant("serve")in each branch when you want a discriminated union. - Enable completion through
run(parser, { completion: "both" })for CLI apps. Do not hand-write completion scripts from parser metadata. - Use
usageLine: [{ type: "ellipsis" }]in runner options when a large root synopsis should become a compactUsage: myapp ...line. This applies only to root full help; usecommand()'susageLinefor subcommand help. - Use
showUsage: falsein runner options when full help should show the brief and command or option sections without theUsage:synopsis. For deeply nested command trees, addcommandList: "top-level"when root help should list only first-level command groups. - Use
termWidth: "auto"in runner options when descriptions should align after the widest visible help term. Optique measures terminal display width after adding built-in help/version/completion entries.
Canonical app shape
import { object } from "@optique/core/constructs";
import { message } from "@optique/core/message";
import { withDefault } from "@optique/core/modifiers";
import { argument, flag, option } from "@optique/core/primitives";
import { integer, string } from "@optique/core/valueparser";
import { run } from "@optique/run";
const parser = object({
input: argument(string({ metavar: "FILE" }), {
description: message`Input file to process.`,
}),
port: withDefault(
option("--port", integer({ min: 1, max: 65535 }), {
description: message`Port to listen on.`,
}),
3000,
),
verbose: withDefault(
flag("-v", "--verbose", { description: message`Enable verbose logging.` }),
false,
),
});
const config = run(parser, {
brief: message`Process a file.`,
completion: "both",
showDefault: true,
termWidth: "auto",
});
console.log(`Processing ${config.input} on port ${config.port}.`);
Subcommands
Use command() for each branch and or() to require exactly one matching
subcommand. Use optional(or(...)) only when no subcommand is valid.
import { object, or } from "@optique/core/constructs";
import { withDefault } from "@optique/core/modifiers";
import { parse } from "@optique/core/parser";
import { command, constant, flag, option } from "@optique/core/primitives";
import { integer } from "@optique/core/valueparser";
const parser = or(
command("build", object({
command: constant("build"),
watch: withDefault(flag("--watch"), false),
})),
command("serve", object({
command: constant("serve"),
port: withDefault(option("--port", integer({ min: 1 })), 3000),
})),
);
const result = parse(parser, ["serve", "--port", "8080"]);
if (result.success) {
switch (result.value.command) {
case "build":
result.value.watch;
break;
case "serve":
result.value.port;
break;
}
}
Custom value parsers
Prefer the built-in catalog first. If a one-to-one dictionary can describe the
input tokens and domain values, use biject(). If an existing parser already
accepts the right input syntax, wrap it with transform() before writing a
custom parser:
import { biject, choice, transform } from "@optique/core/valueparser";
const exitCode = biject({
ok: 0,
warning: 1,
error: 2,
});
const logLevel = transform(choice(["debug", "info", "warn", "error"] as const), {
map(value) {
return value.toUpperCase() as "DEBUG" | "INFO" | "WARN" | "ERROR";
},
unmap(value) {
return value.toLowerCase() as "debug" | "info" | "warn" | "error";
},
});
When a custom domain is needed, keep the validation in a value parser so help, errors, defaults, prompts, and completion all see the same typed value.
import { message } from "@optique/core/message";
import type { ValueParser, ValueParserResult } from "@optique/core/valueparser";
const levels = ["debug", "info", "warn", "error"] as const;
type Level = typeof levels[number];
function isLevel(input: string): input is Level {
return (levels as readonly string[]).includes(input);
}
function logLevel(): ValueParser<"sync", Level> {
return {
mode: "sync",
metavar: "LEVEL",
placeholder: "info",
parse(input: string): ValueParserResult<Level> {
if (isLevel(input)) return { success: true, value: input };
return { success: false, error: message`Invalid log level: ${input}.` };
},
format(value: Level): string {
return value;
},
};
}
const parser = logLevel();
Common mistakes checklist
- Do not parse
process.argvmanually before calling Optique. Pass the parser torun()for applications, or pass explicit argument arrays toparse()in tests and embedded use. - Do not treat
or(a, b)as “zero or more alternatives.” It requires one matching branch unless the wholeor()is wrapped inoptional()orwithDefault(). - Do not use
object()for mutually exclusive subcommands. Useor(command(...), command(...)). - Do not forget that
flag("--x")is required. Wrap it inoptional()orwithDefault(..., false)for ordinary optional flags. - Do not expect
multiple(p)to fail when absent; it returns[]. Wrap withnonEmpty()when at least one value is required. - Do not confuse free-order parsing with
seq(). Most constructs let child parsers compete by priority; useseq()only for truly ordered grammars. - Do not concatenate plain strings for errors or descriptions. Use structured
messagevalues. - Register contexts for
bindEnv(),bindConfig(),bindDerivedDefault(), andbindKeyring()in the runner'scontextsoption. - Do not flatten a multi-level dependency graph into duplicated one-level
factories. Wrap each derived value that becomes a later source with
dependency()and derive the next parser from it. - Do not probe runtime capabilities eagerly before constructing a prompt
parser. Put synchronous or asynchronous checks in the prompt config's
whenfield and provide a typedotherwisevalue. The check then runs only if parsing reaches the prompt fallback.
For the detailed maintained guide, use https://optique.dev/pitfalls.md.
Reference links
- Documentation index for agents: https://optique.dev/llms.txt
- Runners and entry points: https://optique.dev/concepts/runners.md
- Primitive parsers: https://optique.dev/concepts/primitives.md
- Construct combinators: https://optique.dev/concepts/constructs.md
- Modifiers: https://optique.dev/concepts/modifiers.md
- Value parser catalog: https://optique.dev/concepts/valueparsers.md
- Inter-option dependencies: https://optique.dev/concepts/dependencies.md
- Structured messages: https://optique.dev/concepts/messages.md
- Shell completion: https://optique.dev/concepts/completion.md
- Command discovery: https://optique.dev/concepts/discover.md
- Man pages: https://optique.dev/concepts/man.md
Integration packages
| Package | Use for | Docs |
|---|---|---|
@optique/env |
Environment variable fallbacks | https://optique.dev/integrations/env.md |
@optique/keyring |
Async OS credential-store password fallback | https://optique.dev/integrations/keyring.md |
@optique/config |
Configuration file fallbacks | https://optique.dev/integrations/config.md |
@optique/derived-defaults |
Defaults computed from first-pass results | https://optique.dev/concepts/derived-defaults.md |
@optique/prompt |
Generic prompt adapter foundation | https://optique.dev/integrations/prompt.md |
@optique/clack |
Clack interactive fallback prompts | https://optique.dev/integrations/clack.md |
@optique/inquirer |
Inquirer.js interactive fallback prompts | https://optique.dev/integrations/inquirer.md |
@optique/standard-schema |
Portable schema-backed value parsing | https://optique.dev/integrations/standard-schema.md |
@optique/zod |
Zod-backed value parsing | https://optique.dev/integrations/zod.md |
@optique/valibot |
Valibot-backed value parsing | https://optique.dev/integrations/valibot.md |
@optique/temporal |
Temporal date and time parsers | https://optique.dev/integrations/temporal.md |
@optique/git |
Async Git reference validation | https://optique.dev/integrations/git.md |
@optique/logtape |
LogTape levels and formatter/output options | https://optique.dev/integrations/logtape.md |
@optique/man |
Man page generation | https://optique.dev/concepts/man.md |
@optique/discover |
File-based command discovery | https://optique.dev/concepts/discover.md |
@optique/testing |
Layered CLI testing at a chosen boundary | https://optique.dev/concepts/testing.md |