import { ArgBuilder, ArgConfig } from "../schema/arg.mjs"; import { FlagBuilder, FlagConfig } from "../schema/flag.mjs"; import { BeforeParseParams, CLIPlugin, CLIPluginHooks, PluginCommandContext, ResolvedCommandParams, plugin } from "./plugin.mjs"; import { HelpThemeFactory } from "../help/theme.mjs"; import "../help/index.mjs"; import { RunOptions, RunResult } from "../schema/run.mjs"; import { CommandBuilder, ErasedCommand } from "../schema/command.mjs"; import { ParseOptions } from "../parse/index.mjs"; import { RuntimeAdapter } from "../../runtime/adapter.mjs"; import { PackageJsonData } from "../config/package-json.mjs"; import { HelpLinks } from "./help-links.mjs"; import { CompletionOptions } from "../completion/shells/shared.mjs"; import { Shell } from "../completion/index.mjs"; import { FormatLoader } from "../config/index.mjs"; import { formatRootHelp } from "./root-help.mjs"; import { Colors } from "ansispeck"; //#region src/core/cli/index.d.ts /** * Runtime descriptor for the CLI program. * * Stores the program name, version, description, and registered commands.\ * Built incrementally by {@linkcode CLIBuilder}. */ interface CLISchema { /** Program name (used in help text, usage lines, and completion scripts). */ readonly name: string; /** * Whether `.run()` should replace `name` with the invoked program name. * * Set via the `cli({ inherit: true })` factory form. */ readonly inheritName: boolean; /** Program version (shown by `--version`). */ readonly version: string | undefined; /** Program description (shown in root help). */ readonly description: string | undefined; /** Registered commands (type-erased for heterogeneous storage). */ readonly commands: readonly ErasedCommand[]; /** * Default command dispatched when no subcommand matches. * * When set, the CLI root behaves like a hybrid command group: subcommands * dispatch by name as usual, but empty argv or flags-only argv falls * through to this command instead of showing root help. * * Set via the {@linkcode CLIBuilder.default | .default()} builder method. */ readonly defaultCommand: ErasedCommand | undefined; /** * Whether the default command is also exposed as a named top-level route. * * Set by `.default(cmd, { route: true })`. When `true`, `mycli ` * dispatches to the default command (in addition to the bare/flags-only root * surface behavior) and the command is listed in the root `Commands:` * section. When `false` (the default), the default is the root surface only. */ readonly defaultCommandRouted: boolean; /** * Config discovery settings. * * When defined, {@linkcode CLIBuilder.run | .run()} auto-discovers and loads a config file before command dispatch. * * Set via the {@linkcode CLIBuilder.config | .config()} builder method. */ readonly configSettings: ConfigSettings | undefined; /** * Manifest auto-discovery settings. When defined, `.run()` discovers the * nearest manifest (`package.json`, `deno.json`, `jsr.json`, …) and merges * metadata before dispatch. * * Set via the {@linkcode CLIBuilder.manifest | .manifest()} builder method * (or the {@linkcode CLIBuilder.packageJson | .packageJson()} / * {@linkcode CLIBuilder.denoJson | .denoJson()} presets). */ readonly packageJsonSettings: ResolvedManifestSettings | undefined; /** * OSC 8 hyperlink targets for the root-help header (name/version). * * Set via the {@linkcode CLIBuilder.links | .links()} builder method. * Fields left `undefined` are derived from manifest metadata * (`repository` / `homepage`) when manifest discovery ({@linkcode * CLIBuilder.manifest | .manifest()}) is active. */ readonly helpLinks: HelpLinks | undefined; /** Whether built-in `.completions()` registration (command or flag) is active. */ readonly hasBuiltInCompletions: boolean; /** * Eager `--completions ` flag configuration. * * Set when `.completions({ as: 'flag' })` is used instead of the default * `completions` subcommand. When defined, the planner intercepts * `--completions ` before dispatch, prints the script, and exits; * root help advertises the flag in its `Flags:` section. */ readonly completionsFlag: CompletionsFlagConfig | undefined; /** * Consumer-configured root-help defaults. * * Set via the {@linkcode CLIBuilder.help | .help()} builder method and merged * under runtime `options.help` (runtime wins) before rendering. */ readonly helpConfig: HelpConfig | undefined; /** * Flag-parsing behavior settings ({@link ParseOptions}). * * Set via the `cli(name, { flags })` / `cli({ flags })` factory forms. */ readonly flagSettings: ParseOptions | undefined; /** Registered CLI plugins. */ readonly plugins: readonly CLIPlugin[]; } /** * Configuration for the eager `--completions ` flag. * * Stored in {@link CLISchema} when `.completions({ as: 'flag' })` is used. * * @internal */ interface CompletionsFlagConfig { /** Shell targets the flag accepts (mirrors {@link SHELLS}). */ readonly shells: readonly Shell[]; /** Generator options captured at build time (e.g. `functionPrefix`, `rootMode`). */ readonly options: CompletionOptions | undefined; } /** * Consumer-facing root-help configuration set via {@linkcode CLIBuilder.help}. * * Every field is optional; unset fields fall back to built-in defaults and may * be overridden per call through runtime `options.help`. */ interface HelpConfig { /** * Render the default command's arguments and flags inline at the root. * * @defaultValue `true` */ readonly inlineDefault?: boolean; /** * Also list the default command in the root `Commands:` table. * * By default the default command is the root surface and is omitted from the * command list (its args/flags render inline instead). * * @defaultValue `false` */ readonly showDefaultInCommands?: boolean; /** * Show the `Run ' --help' for more information.` hint. * * Defaults to showing the hint only when visible subcommands exist. */ readonly footer?: boolean; /** Maximum line width (columns). */ readonly width?: number; /** Emit OSC 8 hyperlinks in the header when supported. */ readonly hyperlinks?: boolean; /** * Order of flags in the `Flags:` table. * * - `'alphabetical'` — short-aliased flags first, then alphabetical by name. * - `'declaration'` — the order `.flag()` was called. * * Ignored when {@link HelpConfig.sortFlags} is set. * * @defaultValue `'alphabetical'` */ readonly flagOrder?: 'alphabetical' | 'declaration'; /** * Custom comparator over flag long names for the `Flags:` table. When set, * it wins over {@link HelpConfig.flagOrder}. * * @defaultValue `undefined` (use `flagOrder`) */ readonly sortFlags?: (a: string, b: string) => number; /** * Theme overrides for help output, merged over the built-in theme. * * The factory receives the gated ansispeck palette (same instance as * `out.color`), so overrides follow the output channel's color policy. * It is never invoked when color is off — themed help cannot leak escapes * into piped, `--json`, or `NO_COLOR` output. * * @defaultValue `undefined` (built-in theme) */ readonly theme?: HelpThemeFactory; } /** * Config discovery settings for automatic config file loading. * * Stored in {@link CLISchema} and consumed by {@linkcode CLIBuilder.run()} to call * {@link discoverConfig} before dispatching to a command. */ interface ConfigSettings { /** * Application name used to build config search paths. * * Search paths: `.{appName}.json` (cwd), `{appName}.config.json` (cwd), * and `{configDir}/{appName}/config.json` where `configDir` is * `$XDG_CONFIG_HOME` / `~/.config` on Unix or `%APPDATA%` / * `%USERPROFILE%\\AppData\\Roaming` on Windows. */ readonly appName: string; /** Additional format loaders beyond the built-in JSON loader. */ readonly loaders: readonly FormatLoader[] | undefined; } /** * Manifest auto-discovery settings — the resolved/normalized shape stored in * the schema. * * Stored in {@link CLISchema} and consumed by `CLIBuilder.run()` to * call {@link discoverManifest} before dispatching to a command. * * Note: the schema FIELD name (`packageJsonSettings`) keeps its `packageJson` * prefix for backward compatibility — renaming it would break consumers reading * `app.schema.packageJsonSettings`. The type itself is now generically named * (it holds discovery config for any manifest — `package.json`, `deno.json`, * `jsr.json`); the legacy {@link PackageJsonSettings} alias remains exported for * backward compatibility. */ interface ResolvedManifestSettings { /** * Infer CLI name from manifest `bin` keys or `name` field. * * When `true`, the discovered name replaces the `cli(name)` value. * Explicit `.version()`/`.description()` calls still take precedence * over discovered values. * * @defaultValue `false` */ readonly inferName: boolean; /** * Strip a leading `@scope/` from the inferred `name` fallback. * * Only consulted when {@link ResolvedManifestSettings.inferName | `inferName`} * is `true` and the name comes from the manifest `name` field (not a `bin` * key, which is never scoped). * * @defaultValue `true` */ readonly stripScope: boolean; /** * Explicit filesystem anchor for discovery; overrides `adapter.cwd`. * * Resolved to a string path before storage. When set, `discoverManifest` * walks up from here instead of the runtime cwd. Required for installable * CLIs whose version should reflect THEIR OWN package, not the consumer's * working directory. */ readonly from: string | undefined; /** * Candidate manifest filenames, in per-directory priority order * (e.g. `['deno.json', 'deno.jsonc', 'jsr.json']` for `.denoJson()`). */ readonly files: readonly string[]; /** * Pre-loaded data; skips filesystem discovery, uses values verbatim. * * @example * ```ts * import pkg from './package.json' with { type: 'json' }; * * cli('mycli').manifest(pkg); * ``` */ readonly data: PackageJsonData | undefined; } /** * Resolved manifest discovery settings stored in {@link CLISchema}. * * @deprecated Renamed to {@link ResolvedManifestSettings}. The stored shape * holds generic manifest discovery config (`package.json`, `deno.json`, * `jsr.json`), not just `package.json`; the misleading name is kept only for * backward compatibility. */ type PackageJsonSettings = ResolvedManifestSettings; /** * Options for {@linkcode CLIBuilder.execute | .execute()} and {@linkcode CLIBuilder.run | .run()}. * * Derives from {@linkcode RunOptions} while excluding command-execution internals * (`meta`, `mergedSchema`) and adding the CLI-level runtime adapter. */ interface CLIRunOptions extends Omit { /** * Runtime adapter providing platform-specific I/O, argv, env, etc. * * When provided to `.run()`, replaces the default Node adapter. * Ignored by `.execute()` (which is process-free by design). */ readonly adapter?: RuntimeAdapter; } /** * Inputs for {@linkcode resolveRenderContext}. Pass the host facts (TTY * status, environment) and any explicit overrides; the resolver applies the * same gating `.execute()`/`.run()` feed into the output channel. */ interface RenderContextOptions { /** * Whether stdout is connected to a TTY (e.g. `process.stdout.isTTY`). * * @defaultValue `false` */ readonly isTTY?: boolean; /** * Force JSON mode on regardless of argv. * * @defaultValue detected from a pre-separator `--json` in `argv` */ readonly jsonMode?: boolean; /** * Explicitly enable or disable colors, winning over the auto-gate. * * @defaultValue auto — `isTTY && !jsonMode` and environment support */ readonly color?: boolean; /** * Environment variables consulted for `NO_HYPERLINKS`/`FORCE_HYPERLINKS` * (e.g. `process.env`). * * @defaultValue `{}` */ readonly env?: Readonly>; } /** * The output decisions the framework will make for a given argv, resolved * before `.run()`. */ interface RenderContext { /** Whether a pre-separator `--json` puts the run in JSON mode. */ readonly jsonMode: boolean; /** The TTY status the output channel will carry. */ readonly isTTY: boolean; /** * The gated ANSI palette the output channel will expose as `out.color` — * identity formatters when color is off; `color.isColorSupported` is the * boolean gate. */ readonly color: Colors; /** Whether OSC 8 hyperlinks should be emitted (see `out.isHyperlinkSupported`). */ readonly isHyperlinkSupported: boolean; } /** * Resolve the render context for content built before `.run()`. * * Content styled ahead of execution — hand-rendered banners, custom help, or * anything else emitted outside an action handler — has no `out` to consult, * which pushes consumers into re-deriving the framework's decisions from raw * argv (`argv.includes('--json')` misreads a post-`--` literal). This probe * runs the same composition `.execute()`/`.run()` feed into the output * channel — `--`-aware `--json` detection, the color gate, and the hyperlink * override — so pre-run styling matches the channel that will render. * * @param argv - Raw argv tokens (NOT including the binary/script path, * i.e. equivalent to `process.argv.slice(2)`). * @param options - Host facts and overrides. * @returns The resolved output decisions. * * @example * ```ts * const ctx = resolveRenderContext(process.argv.slice(2), { * isTTY: process.stdout.isTTY === true, * env: process.env, * }); * const banner = ctx.color.bold('mycli'); * ``` */ declare function resolveRenderContext(argv: readonly string[], options?: RenderContextOptions): RenderContext; /** * Options for {@linkcode CLIBuilder.default | .default()}. */ interface DefaultCommandOptions { /** * Also expose the default command under its own name as a routable * top-level command. * * By default a default command is the root *surface* only — `mycli` (bare or * flags-only) runs it, but `mycli ` does not route to it (the token * is consumed as the default's first positional). Set `route: true` for CLIs * that intentionally expose both forms: `mycli` and `mycli ` become * the same command, and it is listed in the root `Commands:` section beside * its siblings. * * The name wins over positional interpretation: with `route: true`, a * positional value equal to the command's own name is consumed as the route, * so pass such a value after `--` (`mycli -- `). * * @defaultValue `false` */ readonly route?: boolean; } /** * Immutable CLI program builder. * * Registers commands, handles root-level `--help`/`--version`, and * dispatches to the matched command based on argv. * * Two execution paths: * - `.execute(argv, options?)` — testable, returns {@linkcode RunResult} * - `.run(options?)` — production entry, reads `process.argv`, exits process * * @example * ```ts * import { cli, command, flag, arg } from '@kjanat/dreamcli'; * * const deploy = command('deploy') * .arg('target', arg.string()) * .flag('force', flag.boolean().alias('f')) * .action(({ args, flags, out }) => { * out.log(`Deploying ${args.target}...`); * }); * * cli('mycli') * .version('1.0.0') * .command(deploy) * .run(); * ``` */ declare class CLIBuilder { /** @internal Runtime schema descriptor. */ readonly schema: CLISchema; /** Build a CLIBuilder from a pre-constructed schema descriptor. */ constructor(schema: CLISchema); /** * Set the program version (shown by `--version`). * * @param v - Semantic version string. * @returns The builder (for chaining). */ version(v: string): CLIBuilder; /** * Set the program description (shown in root help). * * @param text - Short description displayed in root help output. * @returns The builder (for chaining). */ description(text: string): CLIBuilder; /** * Make the root-help header clickable with OSC 8 hyperlinks. * * Links the program name and version on the first line of root `--help` * output in terminals that support * [OSC 8 hyperlinks](https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda). * The escapes are only emitted when stdout is a TTY (overridable via * `options.help.hyperlinks`), and only in the header — usage lines, the * `--help` hint, the commands table, and completion scripts stay plain. * * URLs not provided here are derived from manifest metadata when * `.manifest()` is active (works with both filesystem discovery and * pre-loaded data): * - **name** → normalized `repository` URL, falling back to `homepage` * - **version** → forge release tag (`{repo}/releases/tag/v{version}` on * GitHub, `{repo}/-/releases/v{version}` on GitLab) * * @param links - Explicit URLs; omit to derive everything from the manifest. * @returns The builder (for chaining). * * @example * ```ts * // Derive both links from the manifest repository/homepage: * cli('mycli') * .manifest() * .links() * .run(); * * // Explicit URLs (no package.json required): * cli('mycli') * .version('1.0.0') * .links({ * name: 'https://github.com/me/mycli', * version: 'https://github.com/me/mycli/releases/tag/v1.0.0', * }) * .run(); * ``` */ links(links?: { readonly name?: string | URL; readonly version?: string | URL; }): CLIBuilder; /** * Configure root-help rendering defaults. * * Stored on the schema and merged **under** any runtime `options.help` * (runtime wins). Call multiple times to set fields incrementally. * * @param config - Help rendering options (see {@link HelpConfig}). * @returns The builder (for chaining). * * @example * ```ts * // Echo the default command under Commands and never show the footer: * cli('mycli') * .help({ showDefaultInCommands: true, footer: false }) * .default(serve) * .run(); * ``` */ help(config: HelpConfig): CLIBuilder; /** * Enable automatic config file discovery. * * When enabled, `.run()` probes standard paths before dispatching, * first match wins, no merging: * 1. Project scope — for `$CWD` and each ancestor directory up to the * filesystem root, nearest first: * `.{appName}.json`, `{appName}.config.json`, `.config/{appName}.json` * 2. User scope — `{dir}/{appName}/config.json` for each user config * root (`$XDG_CONFIG_HOME` / `~/.config` on Unix, plus * `~/Library/Application Support` on macOS, * `%APPDATA%` / `%USERPROFILE%\\AppData\\Roaming` on Windows) * 3. System scope — `/etc/{appName}/config.json` on Linux and macOS * * The user can override the path via `--config ` or `--config=`. * * Loaded config feeds into the resolution chain * (CLI → env → **config** → prompt → default) for flags that * declare `.config('dotted.path')`. * * Has no effect in `.execute()` (which receives config via * `options.config` directly). * * @param appName - Name used to build search paths. * @param loaders - Additional {@link FormatLoader}s (JSON is built-in). * @returns The builder (for chaining). */ config(appName: string, loaders?: readonly FormatLoader[]): CLIBuilder; /** * Register a custom config format loader. * * Adds a {@link FormatLoader} incrementally — call multiple times to * register multiple formats. Loaders registered later for the same * extension override earlier ones. * * Requires `.config()` to have been called first (sets the app name). * * @param loader - Format loader (or extensions + parse function). * @returns The builder (for chaining). * * @example Bun built-in parsers * ```ts * import { configFormat } from '@kjanat/dreamcli'; * * cli('myapp') * .config('myapp') * .configLoader(configFormat(['yaml', 'yml'], Bun.YAML.parse)) * .configLoader(configFormat(['toml'], Bun.TOML.parse)) * .run(); * ``` * * @example npm package parsers * ```ts * import { configFormat } from '@kjanat/dreamcli'; * import { parse as parseYaml } from 'yaml'; * import { parse as parseTOML } from '@iarna/toml'; * * cli('myapp') * .config('myapp') * .configLoader(configFormat(['yaml', 'yml'], parseYaml)) * .configLoader(configFormat(['toml'], parseTOML)) * .run(); * ``` */ configLoader(loader: FormatLoader): CLIBuilder; /** * Enable manifest metadata from pre-loaded data. * * Pass an already-imported manifest (`package.json`, `deno.json`, `jsr.json`) * to skip filesystem discovery entirely — useful for bundled or installable * CLIs where the version should be locked at build time. The data's * `version`/`description` are merged into the CLI schema immediately, so this * form works in **both** `.run()` and `.execute()` (the filesystem-free path). * Explicit `.version()`/`.description()` calls still take precedence, and the * data form does not infer the CLI name — `inferName` / `{ scope }` are * available only on the discovery (settings) form. For a pre-loaded manifest * whose scoped `name` should drive the CLI name, set the name explicitly via * `cli(name)` (or call {@link inferCliName} on the data yourself). * * Detected by field shape: an object carrying at least one of * `name`/`version`/`description`/`bin`/`homepage`/`repository`. An empty `{}` * or a settings-shaped object falls through to the settings overload. * * @param data - Pre-loaded manifest metadata. * * @example * ```ts * import denoCfg from './deno.json' with { type: 'json' }; * * // No filesystem; works in .run() AND .execute(): * cli('mycli') * .manifest(denoCfg) * .command(deploy) * .run(); * ``` */ manifest(data: PackageJsonData): CLIBuilder; /** * Enable automatic manifest metadata discovery. * * When enabled, `.run()` walks up from `cwd` (or {@link ManifestSettings.from}) * to find the nearest manifest among {@link ManifestSettings.files} and merges * its `version`/`description` into the CLI schema. Explicit `.version()` / * `.description()` calls always take precedence. * * Files are parsed as JSON with a JSONC fallback — `package.json`, `deno.json`, * `jsr.json`, and `deno.jsonc` all qualify, including files with `//` / block * comments or trailing commas. A file that fails both parses is skipped * (discovery keeps walking). * * Has no effect in `.execute()` (filesystem-free) — use the data overload. * * @param settings - Optional settings: * - `files`: candidate manifest filenames in priority order * (default `['package.json']`). * - `inferName`: infer the CLI name from `bin` keys or `name`. Pass * `{ scope: 'keep' }` to keep a leading `@scope/` (stripped by default). * - `from`: anchor discovery to a file/URL/path instead of `cwd`. Pass * `import.meta.url` for installable CLIs that must report THEIR OWN * version. If the consumer's ambient `ImportMeta` omits `url`, pass * `import.meta` instead. Also accepts string paths, `file:` URL strings, * or `URL` instances. * * @example * ```ts * // Deno CLI: discover OUR deno.json (then jsr.json), keep the scope in name: * cli('mycli') * .manifest({ * files: ['deno.json', 'jsr.json'], * from: import.meta.url, * inferName: { scope: 'keep' }, * }) * .command(deploy) * .run(); * ``` */ manifest(settings?: ManifestSettings): CLIBuilder; /** * Discover metadata from `package.json` (preset for {@link CLIBuilder.manifest}). * * @deprecated Use {@link CLIBuilder.manifest} — `.manifest()` defaults to * `package.json` and also supports `deno.json` / `jsr.json` via `files`. * * @param data - Pre-loaded `package.json` metadata. */ packageJson(data: PackageJsonData): CLIBuilder; /** * Discover metadata from `package.json` (preset for {@link CLIBuilder.manifest}). * * @deprecated Use {@link CLIBuilder.manifest}. * * @param settings - `inferName` / `from` (see {@link CLIBuilder.manifest}). */ packageJson(settings?: ManifestPresetSettings): CLIBuilder; /** * Discover metadata from `deno.json`, `deno.jsonc`, then `jsr.json` (preset for * {@link CLIBuilder.manifest}). * * @deprecated Use `.manifest({ files: ['deno.json', 'deno.jsonc', 'jsr.json'] })`. * * @param data - Pre-loaded `deno.json` / `jsr.json` metadata. */ denoJson(data: PackageJsonData): CLIBuilder; /** * Discover metadata from `deno.json`, `deno.jsonc`, then `jsr.json` (preset for * {@link CLIBuilder.manifest}). * * @deprecated Use `.manifest({ files: ['deno.json', 'deno.jsonc', 'jsr.json'] })`. * * @param settings - `inferName` / `from` (see {@link CLIBuilder.manifest}). * `deno.json` / `jsr.json` have no `bin` field, so `inferName` resolves * from `name`; pass `{ scope: 'keep' }` to retain a leading `@scope/`. * * @example * ```ts * cli('mycli') * .denoJson({ from: import.meta.url }) * .command(deploy) * .run(); * ``` */ denoJson(settings?: ManifestPresetSettings): CLIBuilder; /** * Register a command with the CLI program. * * The command's type parameters are erased for heterogeneous storage. * Type safety is preserved inside the closure that delegates to * {@linkcode runCommand | runCommand()}. * * @param cmd - {@link CommandBuilder} to register. * @returns The builder (for chaining). */ command>, A extends Record>, C extends Record>(cmd: CommandBuilder): CLIBuilder; /** * Register a command as the default — dispatched when no subcommand is given. * * The CLI root behaves like a hybrid command group: named subcommands * dispatch normally, but empty argv or flags-only argv falls through to * this command instead of showing root help. * * The default command is the CLI's root *surface*: its flags and arguments * are rendered inline in root help. By default it is **not** a named * subcommand — it cannot be invoked by its own name (`mycli mycmd` does not * route to it) and it is omitted from the root `Commands:` list (re-enable * listing with `.help({ showDefaultInCommands: true })`). Only one default is * allowed. * * Pass `{ route: true }` to *also* expose the command under its own name as a * routable top-level command: `mycli` and `mycli ` then run the same * command object, and it is listed in `Commands:` beside its siblings. This * avoids duplicating the command just to keep both forms equivalent during a * v3 migration. * * @example * ```ts * // Single-command CLI — no subcommand name needed: * // mytool --force production * cli('mytool') * .default(deploy) * .run(); * * // Multi-command CLI with a default: * // mytool production → runs deploy * // mytool status → runs status * cli('mytool') * .default(deploy) * .command(status) * .run(); * * // Default that is also a named route — `mytool` and `mytool status` are * // the same surface, and `status` is listed beside `logs`: * cli('mytool') * .default(status, { route: true }) * .command(logs) * .run(); * ``` * * @param cmd - {@link CommandBuilder} to register as the default. * @param options - Default-command options. `{ route: true }` also exposes * the command under its own name (see {@link DefaultCommandOptions}). * @returns The builder (for chaining). */ default>, A extends Record>, C extends Record>(cmd: CommandBuilder, options?: DefaultCommandOptions): CLIBuilder; /** * Register a CLI plugin. * * Plugins run in registration order. At each lifecycle stage, all hooks for * the first plugin run before hooks for the second plugin, and so on. * * @param definition - A frozen {@link CLIPlugin} created by {@link plugin}. * @returns The builder (for chaining). * @see {@link plugin} to construct plugin definitions. */ plugin(definition: CLIPlugin): CLIBuilder; /** * Register built-in shell completion. * * By default (`{ as: 'command' }`) this registers a `completions` subcommand * that accepts a `shell` argument and writes the script to stdout. With * `{ as: 'flag' }` it instead exposes an eager `--completions ` flag on * the CLI root — no subcommand is registered, so a default command stays the * lone root surface. The flag may be given without a value to auto-detect the * shell from `$SHELL` / `$PSModulePath`. * * In subcommand mode, call this **after** registering all other commands so * the completion script includes the full command set: that path snapshots the * schema (and completion options) at call time, so commands registered * afterwards will not appear in that subcommand's generated script. In flag * mode, generation runs at execution time from the final builder schema, so * registration order does not matter. * * @example * ```ts * // Subcommand form (default): * cli('mycli') * .version('1.0.0') * .command(deploy) * .completions({ rootMode: 'surface' }) * .run(); * * // Eager-flag form, ideal for single-command CLIs: * cli('mycli') * .completions({ as: 'flag' }) * .default(serve) * .run(); * ``` */ completions(options?: CompletionOptions): CLIBuilder; /** * Execute the CLI program against explicit argv. * * This is the testable execution path — no process state is touched. * Returns a structured {@linkcode RunResult} with exit code and captured output. * * @param argv - Raw argv tokens (NOT including the binary/script path, * i.e. equivalent to `process.argv.slice(2)`). * @param options - Injectable runtime state. * @returns Structured result with exit code and captured output. */ execute(argv: readonly string[], options?: CLIRunOptions): Promise; /** * Run the CLI program as a production entry point. * * Reads argv from the runtime adapter, dispatches to the matched * command, writes output to real streams, and exits the process. * * This is the **only** place that touches process state (via the * adapter). For testing, use `.execute()` instead — or provide a * test adapter via `options.adapter`. * * Defaults to {@linkcode createAdapter | createAdapter()} when no adapter is provided, * which auto-detects the runtime (Node.js, Bun) and creates * the appropriate adapter. * * @param options - Optional runtime configuration including adapter. */ run(options?: CLIRunOptions): Promise; } /** * CLI-name inference control for {@link ManifestSettings.inferName}. * * - `false` (or omitted): do not infer the name. * - `true`: infer, stripping a leading `@scope/` from the `name` fallback. * - `{ scope: 'keep' }`: infer, keeping the full scoped name. * - `{ scope: 'strip' }`: infer, stripping the scope (explicit form of `true`). * * `scope` is required in the object form: an empty `{}` is rejected so that an * options object can never silently enable inference (use `true` for that). */ type InferNameOption = boolean | { readonly scope: 'keep' | 'strip'; }; /** Discovery settings accepted by {@link CLIBuilder.manifest}. */ interface ManifestSettings { /** Infer the CLI name from `bin` keys or `name`. @defaultValue `false` */ readonly inferName?: InferNameOption; /** * Anchor discovery to a file/URL/path instead of `cwd`. Normally, pass * `import.meta.url`. Pass `import.meta` whole as a compatibility form when a * project `tsconfig.json` `lib` override drops the runtime's ambient * `ImportMeta` extras and direct `.url` access does not type-check. */ readonly from?: string | URL | ImportMeta; /** * Candidate manifest filenames in priority order — NOT npm's `files` publish * globs. Discovery probes each name per directory and takes the first that * parses. * * @defaultValue `['package.json']` */ readonly files?: readonly string[]; } /** * Discovery settings for the `.packageJson()` / `.denoJson()` presets (no `files`). * * @deprecated Both presets are deprecated; use {@link ManifestSettings} with * {@link CLIBuilder.manifest}. */ type ManifestPresetSettings = Omit; /** * Options for the `cli({...})` factory form. * * This form is useful when the displayed CLI name should be inferred from the * current runtime invocation instead of always being hard-coded. */ interface CLIOptions { /** * Explicit fallback CLI name. * * Used by `.execute()`, and by `.run()` when runtime name inheritance is * disabled or the invocation name cannot be inferred. * * @defaultValue `'cli'` */ readonly name?: string; /** * Replace `name` with the invoked program basename during `.run()`. * * Examples: * - `node ./bin/mycli.ts` → `mycli.ts` * - `/usr/local/bin/mycli` → `mycli` * * @defaultValue `false` */ readonly inherit?: boolean; /** * Flag-parsing behavior settings (e.g. `{ caseParity: false }` to accept * only declared flag spellings). Shares the {@link ParseOptions} contract * used by `parse()` and `RunOptions.flags`. */ readonly flags?: ParseOptions; } /** * Create a new CLI program builder. * * The CLI name is used in help text, usage lines, and generated completion scripts. * * @example * ```ts * cli('mycli') * .version('1.0.0') * .description('My awesome tool') * .command(deploy) * .command(login) * .run(); * * cli({ inherit: true }) * .command(deploy) * .run(); * ``` */ declare function cli(name: string, options?: Omit): CLIBuilder; /** Create a new CLI program builder from an options object. */ declare function cli(options: CLIOptions): CLIBuilder; /** * Report whether the calling module is the process entrypoint, cross-runtime. * * Node, Bun, and Deno set `import.meta.main` on the module invoked directly; * projects with the runtime's ambient types can read that property directly. * This helper is a compatibility form for projects whose `tsconfig.json` `lib` * override drops those `ImportMeta` extras: passing `import.meta` whole avoids * a direct `.main` access without requiring global augmentation. * * @param meta - The calling module's `import.meta`. * @returns `true` when the module was run as the entrypoint. * * @example * ```ts * if (isMainModule(import.meta)) cli('mycli').command(deploy).run(); * ``` */ declare function isMainModule(meta: ImportMeta): boolean; //#endregion export { type BeforeParseParams, CLIBuilder, type CLIOptions, type CLIPlugin, type CLIPluginHooks, type CLIRunOptions, type CLISchema, type CompletionsFlagConfig, type ConfigSettings, type DefaultCommandOptions, type HelpConfig, type HelpLinks, type InferNameOption, type ManifestPresetSettings, type ManifestSettings, type PackageJsonSettings, type PluginCommandContext, type RenderContext, type RenderContextOptions, type ResolvedCommandParams, type ResolvedManifestSettings, cli, formatRootHelp, isMainModule, plugin, resolveRenderContext };