Skip to content

Runners and execution

Once you've built a parser using combinators, you need to execute it against command-line arguments. Optique provides three different approaches with varying levels of automation and control: the low-level parse() function, the mid-level run() function from @optique/core/facade, and the high-level run() function from @optique/run with full process integration.

Each approach serves different use cases, from fine-grained control over parsing results to completely automated CLI applications that handle everything from argument extraction to process exit codes.

Low-level parsing with parse()

The parse() function from @optique/core/parser provides the most basic parsing operation. It takes a parser and an array of string arguments, returning a result object that you must handle manually.

typescript
import { 
object
} from "@optique/core/constructs";
import {
parse
} from "@optique/core/parser";
import {
option
} from "@optique/core/primitives";
import {
string
,
integer
} from "@optique/core/valueparser";
import {
formatMessage
} from "@optique/core/message";
const
parser
=
object
({
name
:
option
("-n", "--name",
string
()),
port
:
option
("-p", "--port",
integer
({
min
: 1000 })),
}); const
result
=
parse
(
parser
, ["--name", "server", "--port", "8080"]);
if (
result
.
success
) {
console
.
log
(`Starting ${
result
.
value
.
name
} on port ${
result
.
value
.
port
}.`);
} else {
console
.
error
(`Parse error: ${
formatMessage
(
result
.
error
)}.`);
process
.
exit
(1);
}

The parse() function returns a discriminated union type that indicates success or failure:

typescript
import { 
object
} from "@optique/core/constructs";
import {
parse
} from "@optique/core/parser";
import {
option
} from "@optique/core/primitives";
import {
string
} from "@optique/core/valueparser";
import {
formatMessage
} from "@optique/core/message";
const
parser
=
object
({
name
:
option
("-n", "--name",
string
()) });
const
result
=
parse
(
parser
, ["--name", "test"]);
// Result type is: { success: true, value: { name: string } } | // { success: false, error: Message } if (
result
.
success
) {
// TypeScript knows this is the success case
result
.
value
.
name
; // string
} else { // TypeScript knows this is the error case
formatMessage
(
result
.
error
); // string
}

Use parse() when you need complete control over error handling, want to integrate parsing into a larger application flow, or need to handle multiple parsing attempts.

Mid-level execution with @optique/core/facade

The run() function from @optique/core/facade adds automatic help generation and formatted error messages while still giving you control over program behavior through callbacks.

typescript
import { 
object
} from "@optique/core/constructs";
import {
run
} from "@optique/core/facade";
import {
option
} from "@optique/core/primitives";
import {
string
,
integer
} from "@optique/core/valueparser";
const
parser
=
object
({
name
:
option
("-n", "--name",
string
()),
port
:
option
("-p", "--port",
integer
({
min
: 1000 })),
}); // Manual process integration (Node.js example) const
config
=
run
(
parser
,
"myserver", // program name
process
.
argv
.
slice
(2), // arguments
{
help
: { // New grouped API
mode
: "both", // Enable --help and help command
onShow
:
process
.
exit
, // Exit after showing help
},
version
: { // Version functionality
mode
: "option", // Enable --version flag
value
: "1.0.0", // Version string to display
onShow
:
process
.
exit
, // Exit after showing version
},
colors
:
process
.
stdout
.
isTTY
, // Auto-detect color support
onError
:
process
.
exit
, // Exit with error code
} );
config
// Its result type is:
console
.
log
(`Starting ${
config
.
name
} on port ${
config
.
port
}.`);

This approach automatically handles:

  • Help generation: Creates formatted help text from parser structure
  • Version display: Shows version information via --version or version command
  • Error formatting: Shows clear error messages with usage information
  • Option parsing: Recognizes --help/--version flags and help/version subcommands
  • Usage display: Shows command syntax when errors occur

The RunOptions interface provides extensive customization:

typescript
import { 
object
} from "@optique/core/constructs";
import {
run
} from "@optique/core/facade";
import {
option
} from "@optique/core/primitives";
import {
string
} from "@optique/core/valueparser";
import {
message
} from "@optique/core/message";
const
parser
=
object
({
name
:
option
("-n", "--name",
string
()) });
const
result
=
run
(
parser
, "myapp", ["--name", "test"], {
colors
: true, // Force colored output
maxWidth
: 80, // Wrap text at 80 columns
showDefault
: true, // Show default values in help text
brief
:
message
`A powerful CLI tool`, // Brief description at top
description
:
message
`This tool processes data efficiently.`, // Detailed description
footer
:
message
`Visit https://example.com for more info`, // Footer at bottom
help
: { // New grouped API
mode
: "option", // Only --help option, no help command
},
version
: { // Version functionality
mode
: "both", // Both --version option and version command
value
: "2.1.0", // Version string to display
},
completion
: { // Shell completion functionality
mode
: "both", // "command" | "option" | "both"
},
aboveError
: "help", // Show full help before error messages
stderr
: (
text
) => { // Custom error output handler
console
.
error
(`ERROR: ${
text
}`);
},
stdout
:
console
.
log
, // Custom help output handler
});

Use this approach when you need automatic help and error handling but want control over process behavior, or when integrating with frameworks that manage process lifecycle.

High-level execution with @optique/run

The run() function from @optique/run provides complete process integration with zero configuration required. It automatically handles argument extraction, terminal detection, and process exit.

typescript
import { 
object
} from "@optique/core/constructs";
import {
option
} from "@optique/core/primitives";
import {
string
,
integer
} from "@optique/core/valueparser";
import {
message
} from "@optique/core/message";
import {
run
,
print
} from "@optique/run";
const
parser
=
object
({
name
:
option
("-n", "--name",
string
()),
port
:
option
("-p", "--port",
integer
({
min
: 1000 })),
}); // Completely automated - just run the parser const
config
=
run
(
parser
);
// If we reach this point, parsing succeeded
print
(
message
`Starting ${
config
.
name
} on port ${
config
.
port
.
toString
()}.`);

The function automatically:

  • Extracts arguments from process.argv.slice(2)
  • Detects program name from process.argv[1]
  • Auto-detects colors from process.stdout.isTTY
  • Auto-detects width from process.stdout.columns
  • Exits on help with code 0
  • Exits on version with code 0
  • Exits on error with code 1

You can still customize behavior when needed:

typescript
import { 
object
} from "@optique/core/constructs";
import {
option
} from "@optique/core/primitives";
import {
string
} from "@optique/core/valueparser";
import {
message
} from "@optique/core/message";
import {
run
} from "@optique/run";
const
parser
=
object
({
name
:
option
("-n", "--name",
string
()) });
const
config
=
run
(
parser
, {
programName
: "custom-name", // Override detected program name
brief
:
message
`Custom CLI Tool`, // Brief description
description
:
message
`A tool for processing files.`, // Detailed description
footer
:
message
`Report bugs at github.com/user/repo`, // Footer information
help
: "both", // Enable both --help and help command
version
: "1.2.0", // Simple version string (uses default "option" mode)
colors
: true, // Force colors even for non-TTY
errorExitCode
: 2, // Exit with code 2 on errors
});

Use this approach for standalone CLI applications where you want maximum convenience and standard CLI behavior.

Configuration options

@optique/run's run() function provides several configuration options for fine-tuning behavior:

typescript
import { 
object
} from "@optique/core/constructs";
import {
option
} from "@optique/core/primitives";
import {
string
} from "@optique/core/valueparser";
import {
message
} from "@optique/core/message";
import {
run
} from "@optique/run";
const
parser
=
object
({
name
:
option
("-n", "--name",
string
()),
debug
:
option
("--debug")
}); const
config
=
run
(
parser
, {
programName
: "my-tool", // Override detected program name
args
: ["custom", "args"], // Override process.argv
colors
: true, // Force colored output
maxWidth
: 100, // Set output width
showDefault
: true, // Show default values in help text
brief
:
message
`My CLI Tool`, // Brief description
description
:
message
`Processes files efficiently`, // Detailed description
footer
:
message
`Visit example.com for help`, // Footer information
help
: "both", // Enable both --help and help command
version
: { // Advanced version configuration
value
: "2.0.0", // Version string
mode
: "command" // Only version command, no --version option
},
aboveError
: "usage", // Show usage on errors
errorExitCode
: 2 // Exit code for errors
});

Help system options

Enable built-in help functionality with different modes:

typescript
import { 
object
} from "@optique/core/constructs";
import {
option
} from "@optique/core/primitives";
import {
string
} from "@optique/core/valueparser";
import {
run
} from "@optique/run";
const
parser
=
object
({
name
:
option
("-n", "--name",
string
()) });
// Simple string-based API const
result1
=
run
(
parser
, {
help
: "option", // Adds --help option only
}); const
result2
=
run
(
parser
, {
help
: "command", // Adds help subcommand only
}); const
result3
=
run
(
parser
, {
help
: "both", // Adds both --help and help command
}); // No help (default) - simply omit the help option const
result4
=
run
(
parser
, {});

Version system options

Enable built-in version functionality with flexible configuration:

typescript
import { 
object
} from "@optique/core/constructs";
import {
option
} from "@optique/core/primitives";
import {
string
} from "@optique/core/valueparser";
import {
run
} from "@optique/run";
const
parser
=
object
({
name
:
option
("-n", "--name",
string
()) });
// Simple string-based API (uses default "option" mode) const
result1
=
run
(
parser
, {
version
: "1.0.0", // Adds --version option only
}); // Advanced object-based API with mode selection const
result2
=
run
(
parser
, {
version
: {
value
: "1.0.0",
mode
: "option", // Adds --version option only
} }); const
result3
=
run
(
parser
, {
version
: {
value
: "1.0.0",
mode
: "command", // Adds version subcommand only
} }); const
result4
=
run
(
parser
, {
version
: {
value
: "1.0.0",
mode
: "both", // Adds both --version and version command
} }); // No version (default) - simply omit the version option const
result5
=
run
(
parser
, {});

Shell completion

This API is available since Optique 0.6.0.

Enable shell completion support for Bash, zsh, fish, PowerShell, and Nushell with simple configuration. The run() function automatically handles completion script generation and runtime completion requests:

typescript
import { 
object
} from "@optique/core/constructs";
import {
option
,
argument
} from "@optique/core/primitives";
import {
string
,
choice
} from "@optique/core/valueparser";
import {
run
} from "@optique/run";
const
parser
=
object
({
format
:
option
("-f", "--format",
choice
(["json", "yaml"])),
input
:
argument
(
string
()),
}); const
config
=
run
(
parser
, {
completion
: "both", // "command" | "option" | "both"
});

The completion modes control how completion is triggered:

"command"
Completion via subcommand (myapp completion bash)
"option"
Completion via option (myapp --completion bash)
"both"
Both patterns supported

Users can generate and install completion scripts:

bash
myapp completion bash > ~/.bashrc.d/myapp.bash
source ~/.bashrc.d/myapp.bash
zsh
myapp completion zsh > ~/.zsh/completions/_myapp
fish
myapp completion fish > ~/.config/fish/completions/myapp.fish
powershell
myapp completion pwsh > myapp-completion.ps1
nushell
myapp completion nu | save myapp-completion.nu
source myapp-completion.nu

Shell completion works automatically with all parser types and value parsers, providing intelligent suggestions based on your parser structure. For detailed information, see the Shell completion section.

Default value display

Both runner functions support displaying default values in help text when options or arguments are created with withDefault():

typescript
import { 
object
} from "@optique/core/constructs";
import {
withDefault
} from "@optique/core/modifiers";
import {
option
} from "@optique/core/primitives";
import {
string
,
integer
} from "@optique/core/valueparser";
import {
run
} from "@optique/run";
const
parser
=
object
({
name
:
option
("-n", "--name",
string
()),
port
:
withDefault
(
option
("-p", "--port",
integer
()), 3000),
format
:
withDefault
(
option
("-f", "--format",
string
()), "json"),
}); const
config
=
run
(
parser
, {
showDefault
: true, // Shows: --port [3000], --format [json]
}); // Custom formatting const
config2
=
run
(
parser
, {
showDefault
: {
prefix
: " (default: ",
suffix
: ")"
} // Shows: --port (default: 3000), --format (default: json) });

Default values are automatically dimmed when colors are enabled, making them visually distinct from the main help text.

Rich documentation support

This API is available since Optique 0.4.0.

Both runner functions support adding rich documentation to help text through the brief, description, and footer options. These fields allow you to provide comprehensive documentation without modifying parser definitions:

typescript
import { 
object
} from "@optique/core/constructs";
import {
option
} from "@optique/core/primitives";
import {
string
} from "@optique/core/valueparser";
import {
message
} from "@optique/core/message";
import {
run
} from "@optique/run";
const
parser
=
object
("Options", {
input
:
option
("-i", "--input",
string
()),
output
:
option
("-o", "--output",
string
()),
}); const
config
=
run
(
parser
, {
brief
:
message
`A powerful file processing tool`,
description
:
message
`This utility processes files with various transformations.
Supports multiple input formats including JSON, YAML, and plain text. Output can be customized with different formatting options.`,
footer
:
message
`Examples:
myapp -i data.json -o result.txt myapp --input config.yaml --output processed.json For more information, visit: https://example.com/docs Report bugs at: https://github.com/user/myapp/issues`,
help
: "option",
version
: "1.0.0",
});

The documentation fields appear in the following order in help output:

A powerful file processing tool
Usage: myapp -i/--input STRING -o/--output STRING

This utility processes files with various transformations.

Supports multiple input formats including JSON, YAML, and plain text. Output can be
customized with different formatting options.

Options:
  -i, --input STRING
  -o, --output STRING

Examples:
  myapp -i data.json -o result.txt
  myapp --input config.yaml --output processed.json

For more information, visit: https://example.com/docs

Report bugs at: https://github.com/user/myapp/issues

These same fields also appear when errors are displayed with aboveError: "help", providing context even when parsing fails. The user-provided documentation takes precedence over any documentation generated from parser structure.

Error handling behavior

The @optique/run run() function automatically:

  • Prints usage information and error messages to stderr
  • Exits with code 0 for help requests
  • Exits with code 0 for version requests
  • Exits with code 1 (or custom) for parse errors
  • Never returns on errors (always calls process.exit())

Type inference with InferValue<T>

The InferValue<T> utility type extracts the result type from any parser, enabling type-safe code when working with parser results programmatically.

typescript
import { 
object
,
or
} from "@optique/core/constructs";
import type {
InferValue
,
Parser
} from "@optique/core/parser";
import {
command
,
constant
,
option
} from "@optique/core/primitives";
import {
string
} from "@optique/core/valueparser";
// Complex parser with union types const
parser
=
or
(
command
("start",
object
({
type
:
constant
("start"),
port
:
option
("-p", "--port",
string
()),
})),
command
("stop",
object
({
type
:
constant
("stop"),
force
:
option
("--force"),
})) ); // InferValue extracts the union type automatically type
Config
=
InferValue
<typeof
parser
>;
function
handleConfig
(
config
:
Config
) {
if (
config
.
type
=== "start") {
// TypeScript knows this is the start command
console
.
log
(`Starting on port ${
config
.
port
|| "default"}.`);
} else { // TypeScript knows this is the stop command
console
.
log
(`Stopping ${
config
.
force
? "forcefully" : "gracefully"}.`);
} }

InferValue<T> is particularly useful when:

  • Creating functions that work with parser results
  • Building generic utilities around parsers
  • Extracting types for external APIs or storage

When to use each approach

Choose your execution strategy based on your application's needs:

Use @optique/run when:

  • Building CLI applications for Node.js, Bun, or Deno
  • You want automatic process.argv parsing and process.exit() handling
  • You need automatic terminal capability detection (colors, width)
  • You prefer a simple, batteries-included approach

Use @optique/core instead when:

  • Building web applications or libraries
  • You need full control over argument sources and error handling
  • Working in environments without process (browsers, web workers)
  • Building reusable parser components

Use parse() when:

  • Testing parsers: You need to inspect parsing results in tests
  • Complex integration: Parsing is part of a larger application flow
  • Custom error handling: You need application-specific error recovery
  • Multiple attempts: You want to try different parsers or arguments

Use run() from @optique/core/facade when:

  • Framework integration: Working with web frameworks or custom runtimes
  • Library development: Building CLI libraries for other applications
  • Custom I/O: You need non-standard input/output handling
  • Controlled exit: The application manages its own lifecycle

Use run() from @optique/run when:

  • Standalone CLIs: Building command-line applications
  • Rapid prototyping: You want to get a CLI running quickly
  • Standard behavior: Your application follows typical CLI conventions
  • Node.js/Bun/Deno: You're running in a standard JavaScript runtime

The progression from parse() to @optique/run's run() trades control for convenience. Start with the highest-level approach that meets your needs, then move to lower-level functions only when you need the additional control.