One definition of a command: its words, effect, arguments and flags, examples and output. From it come the parser (whose refusals name the exact fix), the usage and help, help --json and the MCP tool's input schema, so the CLI and the MCP server can never disagree.
npx shadcn@latest add ourostack/ouro-ui/ax-command#v1.3.10It installs lib/ouro/ax/command.ts, with ax-effects, ax-exit-codes, ax-outcome, ax-problem, ax-voice, ax-wait. Its tests are ax-command-tests. Every item runs on Node 22.18 or later as it is copied: the files import each other as ./<file>.ts. The Ouro AX guide shows how they fit together.
Exports
AGENT_FLAG
The flag a self-scoped command takes: who is calling. The kit compares it with the item's owner, so an agent acts on its own item only.
const AGENT_FLAG: FlagSpecArgSpec
interface ArgSpec {
name: string;
description: string;
required?: boolean;
rest?: boolean | "list";
stdin?: boolean;
unless?: string | readonly string[];
source?: string;
missing?: string;
values?: readonly string[];
}argUsage
<name>, [<name>] or <name...>, as usage shows an argument.
function argUsage(a: ArgSpec): stringBUILTIN_FLAGS
const BUILTIN_FLAGS: Readonly<Record<string, FlagSpec>>checkInput
Checks an MCP call's arguments against the command, as the parser checks argv: an unknown property names the nearest, a wrong type or a missing value names it. The fix is the arguments to send instead, as JSON.
function checkInput(cli: Cli<RunContext>, cmd: CommandDef<unknown, RunContext>, raw: unknown, thing?: string): Readonly<Record<string, InputValue>>checkOwner
Refuses a self-scoped call on an item that is not the calling agent's (not-yours, the usage exit): it names whose item it is and who called, and gives the command for the caller's own item (self.instead) or says to ask the person. It passes a command without self, and the owner's own call. runCli, dispatch and the MCP server call it before plan() or run(); a tool that runs commands its own way calls it too.
function checkOwner<C extends RunContext>(cli: Cli<C>, cmd: CommandDef<unknown, C>, input: Readonly<Record<string, InputValue>>, ctx: C) => Promise<void>Cli
interface Cli<C extends RunContext = RunContext> {
readonly name: string;
readonly about: readonly string[];
readonly commands: readonly CommandDef<unknown, C>[];
readonly exits: ExitTable;
readonly headings: EffectHeadings;
readonly globalFlags: Readonly<Record<string, FlagSpec>>;
readonly ambiguous: Readonly<Record<string, readonly Meaning[]>>;
readonly personOnly: Readonly<Record<string, PersonOnly>>;
readonly groups: Readonly<Record<string, string>>;
readonly outcomeExits: OutcomeExits;
readonly waitExits: WaitExits;
readonly voice: VoiceDef;
}CliDef
interface CliDef<C extends RunContext = RunContext> {
name: string;
about: readonly string[];
commands: readonly CommandDef<unknown, C>[];
exits?: ExitTable;
headings?: EffectHeadings;
globalFlags?: Readonly<Record<string, FlagSpec>>;
ambiguous?: Readonly<Record<string, readonly Meaning[]>>;
personOnly?: Readonly<Record<string, PersonOnly>>;
groups?: Readonly<Record<string, string>>;
outcomeExits?: OutcomeExits;
waitExits?: WaitExits;
voice?: VoiceDef;
}CommandDef
A command's definition. C is the context run() and plan() get: RunContext, or an adopter's own that extends it.
interface CommandDef<R = unknown, C extends RunContext = RunContext> {
name: string;
aliases?: readonly string[];
summary: string;
effect: Effect;
readOnly?: boolean;
why?: "required" | "optional" | "none";
args?: readonly ArgSpec[];
flags?: Readonly<Record<string, FlagSpec>>;
examples?: readonly string[];
output?: string;
hidden?: boolean;
summarizes?: boolean;
loose?: boolean;
self?: SelfScope<C>;
run?(input: Input, ctx: C): R | Promise<R>;
plan?(input: Input, ctx: C): Outcome | Promise<Outcome>;
text?(result: R, input: Input): string;
}defineCli
Builds the tool and checks its definitions: unique names, a valid exit table, examples that parse.
function defineCli<C extends RunContext = RunContext>(def: CliDef<C>) => Cli<C>defineCommand
function defineCommand<R, C extends RunContext = RunContext>(def: CommandDef<R, C>) => CommandDef<R, C>exampleArgv
An example's words after the tool's name, up to a pipe; null when the tool is not in it.
function exampleArgv(tool: string, example: string): string[] | nullfindCommand
The command the leading words name (the longest match), and how many words it took.
function findCommand(cli: Pick<Cli<RunContext>, "commands">, words: readonly string[]): { command: CommandDef<unknown, RunContext>; used: number; } | nullflagsOf
The flags a command takes: the built-ins, the tool's, --why where asked for, --dry-run on an "others" command, then its own; a command's own flag replaces a generated one of the same name, except a self-scoped command's --agent, which the owner check needs.
function flagsOf(cli: Pick<Cli<RunContext>, "globalFlags">, cmd: CommandDef<unknown, RunContext>): Record<string, FlagSpec>FlagSpec
interface FlagSpec {
type: FlagType;
description: string;
required?: boolean;
placeholder?: string;
values?: readonly string[];
min?: number;
max?: number;
stdin?: boolean;
unless?: string | readonly string[];
sensitive?: boolean;
}FlagType
One definition of a command: its words, effect, arguments and flags, examples and output. From it come the parser (whose refusals name the exact fix), the usage and help, help --json and the MCP tool's input schema, so the CLI and the MCP server can never disagree.
type FlagType = "boolean" | "string" | "number" | "enum" | "list";flagUsage
--name <value>, or --name for a boolean.
function flagUsage(name: string, spec: FlagSpec): stringHelpAsked
interface HelpAsked {
kind: "help";
topic: string | undefined;
json: boolean;
}Input
type Input = Readonly<Record<string, InputValue>>;inputSchema
The command's input as a JSON Schema: its arguments and flags as properties (the MCP tool's inputSchema).
function inputSchema(cli: Pick<Cli<RunContext>, "globalFlags">, cmd: CommandDef<unknown, RunContext>): JsonSchema & { properties: Record<string, JsonSchema & { type: string; }>; required: string[]; }InputValue
type InputValue = string | number | boolean | readonly string[];JsonSchema
A JSON Schema object, as an MCP tool's inputSchema takes.
interface JsonSchema {
type?: string;
description?: string;
enum?: readonly string[];
items?: JsonSchema;
minimum?: number;
maximum?: number;
properties?: Record<string, JsonSchema>;
required?: string[];
additionalProperties?: boolean;
}Meaning
One meaning of an ambiguous word, and the command for it. use may hold {args}, the words the agent gave after the ambiguous word in that command's argument slots (a slot with none shows its placeholder), and {flags}, the flags the agent gave that the command takes (a sensitive value as its placeholder). A flag use writes out itself, such as --why <why>, gives way to the same flag given.
interface Meaning {
means: string;
use: string;
}parse
Parses argv against the definitions. Throws an AxError (exit "usage") whose fix is the corrected command line: an unknown flag names the nearest one, a missing argument shows where it goes and where to get it, a missing --why is appended, an ambiguous word lists each meaning's command.
function parse(cli: Cli<RunContext>, argv: readonly string[]): Parsed | HelpAskedParsed
interface Parsed {
kind: "run";
command: CommandDef;
input: Input;
json: boolean;
unchecked: Readonly<Record<string, string | true>>;
}PersonOnly
Words only the person acts on, such as unmute: the reason, one sentence to you, the agent ("You can't turn on the person's microphone; only they can."), and how the person does it (with the microphone button in the call's controls). The words refuse, refused, with both in one line, and help --json lists them under personOnly, so an agent learns them before it tries.
interface PersonOnly {
reason: string;
how: string;
}RunContext
interface RunContext {
stdin(): Promise<string>;
json: boolean;
}SelfScope
A command an agent may run only on its own item, such as its own row in the person's list of listening agents (the twin of a component action declared scope: "self"). The command takes --agent <you>, and the kit asks owner whose item the call names, before plan() or run(): when it is not the calling agent's, the call is refused (not-yours) and nothing runs. help --json marks the command scope: "self".
interface SelfScope<C extends RunContext = RunContext> {
owner(input: Input, ctx: C): string | null | Promise<string | null>;
instead?: string;
}toArgv
The argv that runs a command with this input, the way an agent would type it; the first stdin-capable argument's text goes on stdin ("-"), so text that starts with "-" or spans lines passes whole.
function toArgv(cli: Pick<Cli<RunContext>, "globalFlags">, cmd: CommandDef<unknown, RunContext>, input: Readonly<Record<string, InputValue>>): { argv: string[]; stdin: string | null; }usageOf
The command's usage line from its definition: words, arguments, required flags, then the optional ones.
function usageOf(cli: Pick<Cli<RunContext>, "globalFlags">, cmd: CommandDef<unknown, RunContext>): string