/** * BRB_COMMANDS — single UX metadata source for the `brb` slash command * (verdict #16). The switch in index.ts stays the EXECUTION authority; this * metadata drives argument completion. A structural test asserts the main * `switch (sub)` case set equals this set — the count is answered by code. * * BRB_COMMANDS below is the authority; structural tests assert dispatch * parity, so the count is never written here by hand (it drifted once: * said 15 while open made it 16). The control-namespace words (handoff round * execute report new conversations confirm cancel) live under the `>>` * control surface and the stall actions (press-stop reload-tab * reopen-conversation relaunch-browser) are internal recovery actions — none * of them are /brb subcommands, and they must not be offered as such. * * Completion contract (frozen): * - level 1: `brb ` -> subcommand candidates, value WITHOUT the * command name, label is display-only; * - level 2: candidates return the FULL argument string (e.g. "mode auto"), * because pi refills the whole args; * - completers are PURE/read-only: no browser launch, attach, bind, fresh * resolve, capability, consent, config write, conversation create, message * send. They answer "what am I allowed to fill here", nothing else. */ export interface BrbCommandMeta { name: string; /** Short description shown in the completion list. English by default; the Chinese * display lives in the @yedan/zh-slash-menu extension (one global switch). */ label: string; /** Fixed workflow priority (muscle memory must not drift). */ order: number; /** What the argument position allows. */ argKind: | "none" // no arguments; completion returns null once the command is fixed | "enum" // finite set, derived from the parser's own accepted values | "skeleton" // free text; completion offers a syntax skeleton only | "json-patch"; // config accepts a JSON patch; skeleton only, no fake enum /** Enum values for argKind "enum" — must equal the parser's accepted set. */ enumValues?: readonly string[]; /** Extra skeleton help shown for "skeleton" kinds. */ usage?: string; } export const BRB_COMMANDS: readonly BrbCommandMeta[] = [ { name: "status", label: "Show bridge status", order: 1, argKind: "none" }, { name: "setup", label: "First-run setup and connection wizard", order: 2, argKind: "none" }, { name: "doctor", label: "Check runtime environment and connection", order: 3, argKind: "none" }, { name: "launch", label: "Launch or connect the browser", order: 4, argKind: "none" }, { name: "browser", label: "View or choose the automation browser (Chrome or Edge only)", order: 20, argKind: "enum", enumValues: ["auto", "chrome", "edge"] }, { name: "auto-create", label: "View or set unbound auto-create policy", order: 19, argKind: "enum", enumValues: ["ask", "on", "off"], usage: "auto-create ask|on|off" }, { name: "bind", label: "Bind a ChatGPT conversation", order: 5, argKind: "skeleton", usage: "bind " }, { name: "unbind", label: "Release this Pi thread's BRB binding; does not delete the remote ChatGPT conversation", order: 18, argKind: "none" }, { name: "ask", label: "Send a plain request", order: 6, argKind: "skeleton", usage: "ask " }, { name: "plan", label: "Send a planning request", order: 7, argKind: "skeleton", usage: "plan " }, { name: "copy", label: "Copy the current reply", order: 8, argKind: "none" }, { name: "start", label: "Start auto-relay", order: 9, argKind: "skeleton", usage: "start " }, { name: "stop", label: "Stop auto-relay", order: 10, argKind: "none" }, { name: "resume", label: "Resume a stopped run", order: 11, argKind: "none" }, { name: "kill", label: "Terminate the bridge browser (kill ≠ unbind)", order: 12, argKind: "none" }, { name: "config", label: "View or patch configuration", order: 13, argKind: "json-patch", usage: "config " }, { name: "mode", label: "View or switch run mode", order: 14, argKind: "enum", enumValues: ["dedicated", "existing"], usage: "mode dedicated|existing" }, { name: "sources", label: "Inspect reply sources", order: 15, argKind: "none" }, { name: "open", label: "Open the currently bound ChatGPT conversation", order: 16, argKind: "skeleton", usage: "open [conversation-url] (no arg = current binding)" }, { name: "orphan", label: "Clear a local blocker left by creation", order: 17, argKind: "skeleton", usage: "orphan clear " }, ]; export type BrbCompletion = { value: string; label: string }; /** Level 1 + level 2 completion, pure (verdict #16 Q3/Q4). */ export function brbArgumentCompletions(prefix: string): BrbCompletion[] | null { // Phase A: no space yet -> subcommand prefix match. const space = prefix.indexOf(" "); if (space === -1) { const matches = BRB_COMMANDS.filter((c) => c.name.startsWith(prefix)); if (matches.length === 0) return null; return matches .sort((a, b) => a.order - b.order) .map((c) => ({ value: c.name, label: `${c.name} — ${c.label}` })); } // Phase B: command fixed; route to its argument completer. const command = prefix.slice(0, space); const rest = prefix.slice(space + 1); const meta = BRB_COMMANDS.find((c) => c.name === command); if (!meta) return null; switch (meta.argKind) { case "none": // No arguments: do not keep suggesting the command itself. return null; case "enum": { const values = (meta.enumValues ?? []).filter((v) => v.startsWith(rest)); if (values.length === 0) return null; return values.map((v) => ({ value: `${command} ${v}`, label: `${v} — ${meta.label}` })); } case "skeleton": { // A skeleton is only useful at the empty/partial position; once real // free text is being typed, hand back to pi's default input. if (rest.length === 0) { return [{ value: `${command} `, label: meta.usage ?? `${command} <...>` }]; } return null; } case "json-patch": { if (rest.length === 0) { return [{ value: `${command} {`, label: `${command} — 例如 {"maxRounds": 4}` }]; } return null; } } }