A tool's voice: an optional line of character after the facts, for a person at a terminal, never where an agent parses. The kit prints a voice line only when stdout is a terminal, not under --json or --quiet, and not when the CI environment variable is set; never on stderr and never in an error. Each line is one line of plain ASCII, at most 72 characters, and comes after the fact it follows. Variants rotate, and a process celebrates (done) at most once.
npx shadcn@latest add ourostack/ouro-ui/ax-voice#v1.3.10It installs lib/ouro/ax/voice.ts, with ax-outcome. Its tests are ax-voice-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
checkVoice
Throws at definition time when a fixed voice line breaks the rules; done lines are checked as they are made.
function checkVoice(tool: string, voice: VoiceDef): voidcheckVoiceLine
What is wrong with a voice line, or null when it is one line of printable ASCII, at most VOICE_MAX characters.
function checkVoiceLine(line: string): string | nullcreateVoice
One process's voice: it rotates the variants of a slot and gives at most one done line.
function createVoice(voice: VoiceDef, options: VoiceOptions): VoiceisCi
Whether the CI environment variable says this is a CI run: set, and not empty, "0" or "false".
function isCi(env: Readonly<Record<string, string | undefined>>): booleanVoice
interface Voice {
readonly on: boolean;
line(slot: VoiceSlot, outcome?: Outcome): string | null;
}VOICE_MAX
The longest voice line, in characters.
const VOICE_MAX: 72VoiceDef
interface VoiceDef {
banner?: readonly string[];
done?: (outcome: Outcome) => readonly string[];
summary?: readonly string[];
empty?: readonly string[];
healthy?: readonly string[];
}voiceLines
The fixed voice lines (banner, summary, empty and healthy), as help --json lists them for a checker.
function voiceLines(voice: VoiceDef): string[]VoiceMemory
Remembers the variant a slot last used, so a tool that keeps it between runs rotates across runs too.
interface VoiceMemory {
last(slot: VoiceSlot): number | null;
remember(slot: VoiceSlot, index: number): void;
}voiceOn
Whether a voice line may print: stdout is a terminal, and not --json, --quiet or CI.
function voiceOn(options: Pick<VoiceOptions, "tty" | "json" | "quiet" | "env">): booleanVoiceOptions
interface VoiceOptions {
tty: boolean;
json: boolean;
quiet: boolean;
env: Readonly<Record<string, string | undefined>>;
memory?: VoiceMemory;
start?: number;
}VoiceSlot
A tool's voice: an optional line of character after the facts, for a person at a terminal, never where an agent parses. The kit prints a voice line only when stdout is a terminal, not under --json or --quiet, and not when the CI environment variable is set; never on stderr and never in an error. Each line is one line of plain ASCII, at most 72 characters, and comes after the fact it follows. Variants rotate, and a process celebrates (done) at most once.
type VoiceSlot = "banner" | "done" | "summary" | "empty" | "healthy";