Skip to main content

Ouro AX

ax-cli

The whole front door for a tool built on the kit: parse, help, run (or, on --dry-run, plan), print the result as text or --json, and turn every failure into the one error line and its exit code. An outcome or a wait sets the exit code from its state.

npx shadcn@latest add ourostack/ouro-ui/ax-cli#v1.3.10

It installs lib/ouro/ax/cli.ts and lib/ouro/ax/help.ts, with ax-checks, ax-command, ax-effects, ax-outcome, ax-output, ax-problem, ax-terminal, ax-voice, ax-wait. Its tests are ax-cli-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 of cli.ts

CliIo

The whole front door for a tool built on the kit: parse, help, run (or, on --dry-run, plan), print the result as text or --json, and turn every failure into the one error line and its exit code. An outcome or a wait sets the exit code from its state.

interface CliIo {
  stdout: { write(s: string): unknown; isTTY?: boolean };
  stderr: { write(s: string): unknown };
  stdin?: AsyncIterable<string | Uint8Array> | Iterable<string | Uint8Array>;
}

dispatch

Runs a parsed command as runCli does, for a tool that reads stdin and prints errors its own way: on --dry-run the command's plan(), refused ("no-preview") when it has none, else its run(). A self-scoped command is refused first when its item is not the calling agent's (checkOwner). It prints nothing and reads no stdin.

function dispatch<C extends RunContext>(cli: Cli<C>, parsed: Parsed, ctx: C) => Promise<unknown>

isEmptyResult

Whether a result is an empty list: an empty array, or a page ({ items }) whose items are empty.

function isEmptyResult(result: unknown): boolean

printResult

Prints a command's result as runCli does and returns the exit code: an outcome by its state (a refusal or failure in text is the one error line on stderr), a wait by its state, a checks report (ax-checks) by its verdict, with its text styled for stdout and every check under --verbose, anything else as its text or --json. A report whose checks all ran and passed may get the voice's healthy line. Given the process's voice, a voice line may follow the text on stdout, never before it: after a done or queued outcome, an empty list, or the summary command.

function printResult(cli: Cli<RunContext>, cmd: CommandDef<unknown, RunContext>, input: Readonly<Record<string, InputValue>>, result: unknown, json: boolean, io: Pick<CliIo, "stdout" | "stderr">, voice?: Voice | undefined, env?: Readonly<Record<string, string | undefined>>): number

quietPipes

Ends the tool quietly when whatever reads its output goes away, as in tool help | head -1: on EPIPE it exits with the exit code so far (0 when none was set) and prints nothing, instead of a stack trace. Call it once in the tool's bin, before runCli: quietPipes(process). Any other stream error is thrown as before.

function quietPipes(proc: { stdout: ErrorEvents; stderr: ErrorEvents; exitCode?: string | number | null | undefined; exit(code?: number | undefined): unknown; }): void

runCli

function runCli(cli: Cli<RunContext>, argv: readonly string[], io: CliIo, options?: RunOptions): Promise<number>

RunOptions

interface RunOptions {
  overview?: OverviewOptions;
  topics?: Readonly<Record<string, string>>;
  env?: Readonly<Record<string, string | undefined>>;
  voiceMemory?: VoiceMemory;
}

Exports of help.ts

commandManifest

One command as data.

function commandManifest(cli: Cli<RunContext>, cmd: CommandDef<unknown, RunContext>): CommandManifest

CommandManifest

interface CommandManifest {
  name: string;
  aliases: readonly string[];
  effect: "private" | "others";
  whoSees: string;
  readOnly: boolean;
  needsWhy: boolean;
  dryRun: boolean;
  runs: boolean;
  summary: string;
  usage: string;
  inputSchema: JsonSchema;
  output: string | null;
  examples: readonly string[];
  hidden: boolean;
  scope?: "self";
}

DEFAULT_WIDTH

Help from the definitions: a one-screen overview under the two effect headings, each command's details, and the same as JSON (help --json), which an agent parses instead of scraping text. Every line is wrapped to width columns.

const DEFAULT_WIDTH: 100

definitionLines

The generated part of a command's help: its usage from the definition, its arguments and its flags.

function definitionLines(cli: Cli<RunContext>, cmd: CommandDef<unknown, RunContext>, width?: number): string[]

flagLines

One line per flag: usage on the left, what it does on the right; the built-ins and the tool's own are left out.

function flagLines(cli: Cli<RunContext>, cmd: CommandDef<unknown, RunContext>, width?: number): string[]

helpJson

The help as data: the tool, its headings, the commands, the words only the person acts on (personOnly: name, reason and how), the exit codes, the tool's fixed voice lines (for a checker that looks for them in an agent's output) and the one error shape.

function helpJson(cli: Cli<RunContext>): { tool: string; about: readonly string[]; effects: { private: string; others: string; }; commands: CommandManifest[]; personOnly: { name: string; reason: string; how: string; }[]; exitCodes: readonly ExitCode[]; voice: string[]; errorShape: { error: { code: string; exit: string; thing: string; problem: string; fix: string; message: string; }; }; }

OverviewOptions

interface OverviewOptions {
  width?: number;
  othersNote?: string;
  footer?: readonly string[];
}

renderCommand

One command's whole help, from its definition alone.

function renderCommand(cli: Cli<RunContext>, cmd: CommandDef<unknown, RunContext>, width?: number): string

renderOverview

The overview: what the tool is, the commands under "Only you see these" and "Other people see these", the words only the person acts on, the footer.

function renderOverview(cli: Cli<RunContext>, options?: OverviewOptions): string

wrap

Wraps text to width columns, every line indented; a word longer than a line stays whole.

function wrap(text: string, indent: number, first?: number, width?: number): [...string[], string]