Optique 1.2.0: fluent modifiers, deferred values, new integrations, and a redesigned site #877
dahlia
announced in
Announcements
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
Optique 1.2.0 adds a method-call style for modifiers, a
deferredValue()parser for fallbacks that should only resolve inside a handler, and four new packages: @optique/prompt, @optique/clack, @optique/standard-schema, and @optique/derived-defaults. @optique/discover gains lifecycle hooks and a generator for bundler-friendly command trees, and the documentation site got a visual pass.Optique is a type-safe combinatorial CLI parser for TypeScript built from composable parser functions. Parser definitions drive runtime parsing, help and completion output, and the inferred value types that handlers receive.
A redesigned optique.dev
optique.dev's front page now opens with a live, type-checked parser instead of a feature list, and the rest of the site follows the same visual language built around Optique's prism mark.
The biggest new section is a set of comparison pages that walk Commander.js, Yargs, Cliffy, Gunshi, Cleye, cmd-ts, Stricli, oclif, and Clipanion through the same three scenarios Optique handles differently: mutually exclusive option groups, option groups shared across subcommands, and resolving a value from CLI args, then env vars, then a config file, then an interactive prompt.
Fluent modifier style
Modifier functions such as
map(),optional(),withDefault(),multiple(), andnonEmpty()can now also be called as methods on parsers built by Optique's own factories:The standalone functions still work exactly as before and remain the only option when a parser's type is only known as the base
Parserinterface. Custom parsers that want the same method surface can opt in through thefluent()helper in the new @optique/core/fluent subpath (#842, #843).deferredValue()for handler-time fallbackswithDefault()resolves its fallback while parsing runs, which is too early for a value that should come from an interactive prompt, a network call, or a lookup that only one command branch ever needs. The newdeferredValue()modifier keeps the value the wrapped parser produced but leaves the fallback unresolved until the handler asks for it:Calling
apiToken({ serviceName })returns the parsed value if one was given, or runs the fallback resolver with that context otherwise; the resolver's own parameter annotation becomes the argument type TypeScript enforces at the call site. Because the fallback runs at handler time, a failed prompt is a handler error rather than a parse error, and a branch that never asks for the token never pays the cost of resolving it. Asourceproperty reports whether the value was"specified"or came from the"fallback", and an optionalmemoizeflag caches a successful resolution while still retrying after a rejection (#846, #848).New integration packages
@optique/clack: Clack prompts
@optique/clack wraps any parser with an interactive Clack prompt that fires only when a CLI value is missing, joining @optique/inquirer as Optique's second ready-made prompt integration:
It supports
text,password,confirm,number,select, andmultiselectprompts, and a cancelled prompt becomes aPrompt cancelled.parse failure instead of an unhandled rejection.@optique/prompt: the adapter both build on
Both prompt integrations share their wrapper machinery through the new @optique/prompt package: CLI values take priority, source bindings such as
bindEnv()/bindConfig()can satisfy a value before prompting, usage stays marked optional, and completion behavior is preserved. @optique/inquirer was refactored onto the same foundation without changing its public API. Reach for @optique/prompt directly if you want to wire up a prompt library other than Inquirer.js or Clack (#837, #844).@optique/standard-schema: any spec-compliant validator
@optique/standard-schema turns any Standard Schema-compatible validator, Zod, Valibot, ArkType, Yup, Joi, or anything else implementing the spec, into a value parser:
The
placeholderoption is required since Optique needs a type-appropriate stand-in during deferred prompt resolution, andstandardSchemaAsync()covers validators whose~standard.validate()returns a promise. Reach for @optique/zod or @optique/valibot instead when a schema library already has a dedicated adapter with richer CLI behavior such as inferred choices and suggestions (#862, #863).@optique/derived-defaults: fallbacks computed from earlier options
The new @optique/derived-defaults package computes a fallback value from a first-pass parse result, so one option's default can depend on another option the user already typed while still letting the user override it explicitly:
The fallback priority is CLI argument, then derived default, then static default, then error, and the derived value still goes through the wrapped parser's own validation before it is accepted (#845, #847).
Compact help for large command trees
Two new runner options make root help pages easier to scan once a CLI has grown past a handful of commands.
showUsage: falseomits theUsage:synopsis from full help pages, so the root page can act as a plain command menu, and this also applies whenaboveError: "help"renders help above a parse error (#856, #860).commandList: "top-level"limits the root command list to first-level commands instead of the fully flattened tree, leaving nested commands discoverable through<command> --help; the previous flattened behavior stays available as"recursive", the default (#864, #865). Both options are available on the core runner and onrun()/runSync()/runAsync()in @optique/run; @optique/discover'srunProgram()forwards both, andcreateProgramParser()additionally acceptscommandList.@optique/discover: static discovery, parent commands, and lifecycle hooks
Bundlers that can't scan a directory at runtime now have a supported path:
commandsFromModules()turns a static module map, such as an eagerimport.meta.glob()result, into the same command entries file-system discovery would have produced, and the newoptique-discovercommand generates that module map as a TypeScript file, with--watchto regenerate it as command files are added, removed, or renamed (#830, #840, #835, #841, #854, #858).An entry file such as stash/index.ts now defines an executable parent command at
stashthat can coexist with nested commands likestash list, instead of only ever being a namespace with no handler of its own. TheentryFileNameoption customizes or disables this rule (#838, #839).A
hooksoption onrunProgram()wraps every handler withbeforeEach,afterEach, andonError, so log scopes, tracing spans, lazy resource setup, and failure reporting can live in one place instead of inside every command:beforeEach's returned resource is threaded to the handler's new second argument and toafterEach/onError.defineCommand()accepts the samehooksfield for a per-command preflight that nests inside the program-level one (#851, #875, #876).File-system discovery,
commandsFromModules(), andoptique-discovernow skip co-located test files by default, so hello.test.ts or hello.spec.js next to a command module is ignored instead of registering as a ghost command or failing thedefineCommand()check (#857, #861 by @ho991217).New value parser tools
transform()wraps a value parser and maps its result to another type in both directions, soformat(), fallback validation, and default handling keep working on the mapped value:biject()covers the common case where the mapping is a plain one-to-one dictionary:biject({ ok: 0, warning: 1, error: 2 })accepts one of the keys and returns the mapped value, rejecting duplicate values or an empty mapping at construction time (#866).choice()can now append a “Did you mean?” hint to its error message.suggest: "nearest"uses Levenshtein distance with tunable thresholds, and a custom(input, choices) => string[] | undefinedfunction covers domain-specific logic; the default stays"never"(#849).@optique/logtape: pluggable text formatters
A new
textFormatter()value parser accepts"jsonl","logfmt","color", or"plain"and maps them to LogTape's built-in formatter functions, andConsoleSinkOptions.formatter,logOutput(), andloggingOptions()all accept either a parsed formatter option name or a fixed LogTape formatter directly (#867).An official Agent Skill
Optique now ships an Agent Skill under @optique/core's skills/optique/ directory, giving AI coding agents concise, version-matched instructions for using the library correctly instead of relying on whatever they picked up in training. Install it from the repository with the skills CLI, keep it in sync with your installed npm version through skills-npm, or add it through Optique's Claude Code plugin marketplace (#850, #852).
Installation
For the new integration packages:
If you have ideas for future improvements or encounter any issues, please let us know through GitHub Issues. For more information about Optique and its features, visit the documentation or check out the full changelog.
Beta Was this translation helpful? Give feedback.
All reactions