- name
- optique
- description
- Use this skill when writing any code that builds a command-line interface with Optique in TypeScript or JavaScript. Covers the combinatorial parser model, choosing @optique/core vs @optique/run, value parsers, structured messages, optional()/withDefault()/multiple(), subcommands with command() and or(), shell completion, async parsing, the integration packages, and common mistakes to avoid. Trigger whenever the user is parsing command-line arguments, building a CLI, or adding options or subcommands to a tool.
- license
- MIT
Start at <https://optique.dev/llms.txt> online; these rules also work offline.
Core rules
----------
- Use `run()` from *@optique/run* for apps, `parse()`/`runParser()` to embed.
- With `runParser()`, `onError(exitCode, error)` supplies a structured
`Message`; `help.onShow(exitCode, page)` supplies the final `DocPage`. Both
follow output. Supply `stdout: () => {}` 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, and `createCliRunner()` from *@optique/testing/cli* for real CLIs.
Pin `colors`/`maxWidth` in 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.
- For command/option help headings, set the runner's
`helpSections: { commands: "Commands", options: "Options" }`.
It groups untitled entries only on pages with visible commands. See
<https://optique.dev/cookbook.md#command-and-option-headings-in-help>.
- Let TypeScript infer results unless another API needs a separate interface.
- Parsers usually require input. `optional(p)` yields `undefined`; use
`withDefault(p, value)` or `withDefault(flag("-v"), false)` for fallbacks.
- Use semantic `message` helpers for descriptions and errors. Since 1.3.0,
customize terminal output with `theme` or `messageFormatter`, preserving
`initialWidth`, quoting, and width options. Keep canonical errors unthemed.
`flag()` supports static/callback `errors.unexpectedValue` since 1.4.0.
- Use value parsers such as `integer()`, `choice()`, `biject()`, `regExp()`,
`url()`, `origin()`, and `uuid()` instead of validating raw strings after
parsing. Use `regExp({ flags })` for user-supplied sources, `biject()` for
one-to-one string-to-value choices, and `transform()` when an existing
value parser describes the accepted CLI spelling but your app needs a
different result type. Use `path()` from `@optique/run/valueparser` for
file-system paths. Write a custom `{ mode, metavar, parse, format }` value
parser only when the catalog does not cover the domain.
- Since 1.4.0, `-p8080`/`-vp8080` accept attached values. Values consume
the literal suffix (`-p=5` gives `"=5"`); full single-dash names win.
- Async value parsers like *@optique/git* make containing parsers async.
Await `run()`/`parse()`/`runParser()` or, for `bindKeyring()`, `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. Pass a
lone resolver for fetched choices, forwarding its `signal` to the fetch.
- Pass `{ validate, maxAttempts, signal }` as a generated prompt wrapper's
third argument, including `prompt()` from *@optique/inquirer* and
*@optique/clack*. The validator returns `undefined` to accept the prompted
value or a structured `Message` to retry, synchronously or asynchronously.
Attempt limits must be positive integers and default to unlimited retries.
Selection prompts keep their config and use the shared `validate` option.
- Implement a custom adapter's `execute(config, context)` so retries can show
`context.previousValidationMessage`, and forward `context.signal` when the
prompt library supports aborting active work. Adapter-native validation
remains separate and completes inside one shared attempt.
- Build subcommands with `command()` combined by `or()`. Put a literal field
such as `command: constant("serve")` in each branch when you want a
discriminated union.
- Use `run(parser, { completion: "both" })` for completion, or the object form
with `completion.errors` for custom shell errors. Do not hand-write scripts.
- Use `usageLine: [{ type: "ellipsis" }]` in runner options when a large root
synopsis should become a compact `Usage: myapp ...` line. This applies only
to root full help; use `command()`'s `usageLine` for subcommand help.
- Use `showUsage: false` in runner options when full help should show the
brief and command or option sections without the `Usage:` synopsis.
For deeply nested command trees, add `commandList: "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
-------------------
~~~~ typescript
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.
~~~~ typescript
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:
~~~~ typescript
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.
~~~~ typescript
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
-------------------------
- Pass parsers to `run()` for apps; use explicit argument arrays with
`parse()` in tests and embedded use. Do not pre-parse `process.argv`.
- Do not treat `or(a, b)` as “zero or more alternatives.” It requires one
matching branch unless the whole `or()` is wrapped in `optional()` or
`withDefault()`.
- Do not use `object()` for mutually exclusive subcommands. Use
`or(command(...), command(...))`.
- Do not forget that `flag("--x")` is required. Wrap it in `optional()` or
`withDefault(..., false)` for ordinary optional flags.
- Do not expect `multiple(p)` to fail when absent; it returns `[]`. Wrap with
`nonEmpty()` when at least one value is required.
- Do not confuse free-order parsing with `seq()`. Most constructs let child
parsers compete by priority; use `seq()` only for truly ordered grammars.
- Use structured `message` values for errors and descriptions.
- Register contexts for `bindEnv()`, `bindConfig()`, `bindDerivedDefault()`,
and `bindKeyring()` in the runner's `contexts` option.
- Enable `showEnvironment` for env-only help; set `documentation.description`.
- 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
`when` field and provide a typed `otherwise` value. 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 |
| --------------------------- | ------------------------------------------- | ----------------------------------------------------- |
View on GitHub