Skip to main content

Ouro AX

ax-command

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.10

It 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: FlagSpec

ArgSpec

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): string

BUILTIN_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[] | null

findCommand

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; } | null

flagsOf

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): string

HelpAsked

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 | HelpAsked

Parsed

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