Skip to content

Definition API Reference

The Definition layer provides functions to define CLI structure using plain objects and Zod schemas.

Functions

defineCli(definition)

Creates a CLI definition.

Parameters:

  • definition: CliDefinition - The CLI definition object

Returns: CliDefinition

Example (basic):

typescript
import { defineCli } from "cheloni";

const cli = defineCli({
  name: "my-cli",
  version: "1.0.0",
  description: "My CLI tool"
});

Example (with plugins and a plugin kit):

typescript
import { defineCli, definePlugin } from "cheloni";
import { basicPluginKit } from "cheloni/std/core";

const loggingPlugin = definePlugin({ name: "logging" });

const monitoringKit = [
  definePlugin({ name: "analytics" }),
  definePlugin({ name: "tracing" }),
];

const cli = defineCli({
  name: "my-cli",
  plugins: [loggingPlugin, ...basicPluginKit, ...monitoringKit],
});

When you call createCli() with this definition, plugins is a single list. onInit hooks run in array order. Reusable bundles are plain arrays (for example basicPluginKit from the standard library, or a local src/plugin-kits/ module you maintain).

Default metadata from package.json: You can omit name, version, and/or description when you pass metaUrl: import.meta.url (from your CLI entry module). createCli() walks up from that file’s directory, reads the nearest package.json, and fills any of those fields you left out. Values you set on the definition always win. If name is still missing after resolution, createCli() throws. See createCli().

defineCommand(definition)

Creates a command definition.

Parameters:

  • definition: CommandDefinition<TPositionalDefinition, TOptionsDefinition> - The command definition

Returns: CommandDefinition<TPositionalDefinition, TOptionsDefinition>

Example:

typescript
import { defineCommand } from "cheloni";
import { z } from "zod";

const command = defineCommand({
  name: "build",
  description: "Build the project",
  options: z.object({
    verbose: z.boolean().optional()
  }),
  handler: ({ options }) => {
    console.log("Building...", options);
  }
});

defineRootCommand(definition)

Creates a root command definition (command without a name).

Parameters:

  • definition: RootCommandDefinition<TPositionalDefinition, TOptionsDefinition> - The root command definition

Returns: CommandDefinition<TPositionalDefinition, TOptionsDefinition>

Example:

typescript
import { defineRootCommand } from "cheloni";

const rootCommand = defineRootCommand({
  handler: ({ options }) => {
    console.log("Root command");
  }
});

defineOption(schema)

Creates an inline option schema for use inside a z.object().

Parameters:

  • schema: OptionSchema - A Zod schema

Returns: OptionSchema

Example:

typescript
import { defineOption } from "cheloni";
import { z } from "zod";

const force = defineOption(z.boolean().optional());
defineCommand({ options: z.object({ force }) });

defineOption(definition) (named option)

Creates a reusable, named option definition that can be passed to bequeathOptions so subcommands inherit it automatically.

Parameters:

  • definition: OptionDefinition<TSchema> - The option definition

Returns: OptionDefinition<TSchema>

Example:

typescript
import { defineOption } from "cheloni";
import { z } from "zod";

const dryRun = defineOption({
  name: "dry-run",
  schema: z.boolean().default(false),
  handler: async ({ value, next }) => {
    if (value) console.log("Dry run mode");
    return next();
  },
});

definePositional(definition)

Creates a positional argument definition (typically used as a Zod schema).

Parameters:

  • definition: PositionalDefinition - A Zod schema or undefined

Returns: PositionalDefinition

Example:

typescript
import { definePositional } from "cheloni";
import { z } from "zod";

const positional = definePositional(z.string());

defineMiddleware(definition)

Creates a middleware definition.

Parameters:

  • definition: MiddlewareDefinition - The middleware function

Returns: MiddlewareDefinition

Example:

typescript
import { defineMiddleware } from "cheloni";

const middleware = defineMiddleware(async ({ next }) => {
  const start = Date.now();
  const result = await next();
  console.log("Duration (ms):", Date.now() - start);
  return result;
});

definePlugin(definition)

Creates a plugin definition.

Parameters:

  • definition: PluginDefinition - The plugin definition

Returns: PluginDefinition

Example:

typescript
import { definePlugin } from "cheloni";

const plugin = definePlugin({
  name: "my-plugin",
  onInit: ({ cli }) => {
    console.log("Plugin initialized");
  }
});

Plugin Hooks

onInit

Runs once during CLI creation, before any commands are executed. This is the only hook that can mutate the CLI structure.

When it runs: During createCli(), for each global plugin in order.

What you can do:

  • Modify cli.command (replace the root command, add bequeathOptions)
  • Modify cli.plugins
  • Access cli.manifest

Error handling: If onInit throws, createCli() fails immediately.

Example:

typescript
import { definePlugin, createCommand, createOption, defineOption } from "cheloni";
import z from "zod";

const plugin = definePlugin({
  name: "my-plugin",
  onInit: ({ cli }) => {
    console.log('Plugin initialized');
  }
});

onBeforeCommandExecution

Runs before a command handler executes, after argument parsing and validation.

When it runs: During executeCommand(), before the handler runs. Global plugins run first, then command plugins.

What you can do:

  • Access parsed command definition
  • Access cli instance
  • Throw to prevent handler execution

Error handling: If onBeforeCommandExecution throws, the handler does not run.

Example:

typescript
const authPlugin = definePlugin({
  name: "auth",
  onBeforeCommandExecution: ({ command, cli }) => {
    // Check authentication before command runs
    if (!isAuthenticated() && command.name !== "login") {
      throw new Error("Authentication required");
    }
  }
});

onAfterCommandExecution

Runs after a command handler executes, even if the handler threw an error.

When it runs: During executeCommand(), in a finally block after the handler.

What you can do:

  • Cleanup resources
  • Log execution results
  • Access command and cli

Error handling: Errors are logged but don't override the original error.

Example:

typescript
const plugin = definePlugin({
  name: "logging-plugin",
  onAfterCommandExecution: ({ command }) => {
    console.log(`Command ${command.name} completed`);
  }
});

onDestroy

Runs when the CLI execution completes, even if an error occurred.

When it runs: During executeCli(), in a finally block.

What you can do:

  • Cleanup resources
  • Close connections
  • Final logging

Error handling: Errors are logged but don't throw.

Example:

typescript
const plugin = definePlugin({
  name: "db-plugin",
  onDestroy: ({ cli }) => {
    // Close database connections
    db.close();
  }
});

Types

CliDefinition

typescript
interface CliDefinition {
  name?: string;
  version?: string;
  metaUrl?: string | URL;
  description?: string;
  details?: string;
  deprecated?: boolean | string;
  command?: RootCommandDefinition;
  plugins?: PluginDefinition[];
  onError?: CliErrorHandler;
}

name is required for a runnable CLI, but it may be omitted on the definition when metaUrl is set so createCli() can read it from package.json. The same applies to version and description.

CommandDefinition<TPositionalDefinition, TOptionsDefinition>

typescript
interface CommandDefinition<
  TPositionalDefinition extends PositionalDefinition = any,
  TOptionsDefinition extends OptionSchema = any
> {
  name: string;
  paths?: string[];
  deprecated?: boolean | string;
  description?: string;
  positional?: TPositionalDefinition;
  options?: TOptionsDefinition;
  middleware?: MiddlewareDefinition[];
  examples?: string[];
  details?: string;
  throwOnExtrageousOptions?: ExtrageousOptionsBehavior;
  plugins?: PluginDefinition[];
  commands?: CommandDefinition[];
  bequeathOptions?: OptionDefinition[];
  handler?: CommandHandler<TPositionalDefinition, TOptionsDefinition>;
}

RootCommandDefinition<TPositionalDefinition, TOptionsDefinition>

typescript
type RootCommandDefinition<
  TPositionalDefinition extends PositionalDefinition = any,
  TOptionsDefinition extends OptionSchema = any
> = Omit<CommandDefinition<TPositionalDefinition, TOptionsDefinition>, "name">;

OptionSchema

typescript
type OptionSchema = z.ZodTypeAny;

OptionDefinition<TSchema>

A reusable, named option that can be shared across commands via bequeathOptions.

typescript
interface OptionDefinition<TSchema extends OptionSchema = OptionSchema> {
  name: string;
  schema?: TSchema;
  handler?: OptionHandler<TSchema>;
}

PositionalDefinition

typescript
type PositionalDefinition = z.ZodTypeAny | undefined;

MiddlewareDefinition

typescript
type MiddlewareDefinition = Middleware;

PluginDefinition

typescript
interface PluginDefinition {
  name: string;
  onInit?: PluginHook;
  onBeforeCommandExecution?: PluginCommandHook;
  onAfterCommandExecution?: PluginCommandHook;
  onDestroy?: PluginHook;
}

PluginHook

Called during CLI initialization (onInit) or cleanup (onDestroy).

typescript
type PluginHook = (params: PluginHookParams) => Promisable<void>;

Parameters:

  • params.cli: Cli - The CLI instance
  • params.plugin: Plugin - The plugin instance

PluginCommandHook

Called before (onBeforeCommandExecution) or after (onAfterCommandExecution) command execution.

typescript
type PluginCommandHook = (params: PluginCommandHookParams) => Promisable<void>;

Parameters:

  • params.cli: Cli - The CLI instance
  • params.plugin: Plugin - The plugin instance
  • params.command: CommandDefinition - The command being executed

ExtrageousOptionsBehavior

typescript
type ExtrageousOptionsBehavior = 'throw' | 'filter-out' | 'pass-through';
  • 'throw' - Throw an error when extrageous options are found (default)
  • 'filter-out' - Silently remove extrageous options
  • 'pass-through' - Pass extrageous options through to the handler