import { dirname, resolve } from "node:path"; import { cwd, exit } from "node:process"; import { existsSync } from "node:fs"; import type { CommandContext } from "./types.ts"; export function usage(): never { console.log(`Usage: bin/lbs [--root ] list bin/lbs [--root ] validate bin/lbs [--root ] index [--check] bin/lbs [--root ] new-skill [--description ] bin/lbs [--root ] new-ref bin/lbs [--root ] env show [--json] bin/lbs [--root ] env doctor bin/lbs [--root ] fixture list [skill] [--json] bin/lbs [--root ] fixture check [skill] [--json] bin/lbs [--root ] log scan [--json] [--output ] [--backend-log ] [--frontend-log ] [--console-log ] [--case ] [--success-pattern ] [--failure-pattern ] [--expected-failure ] [--since ] [--until ] [--tail-lines ] [--no-auto-log] [--strict] bin/lbs [--root ] log watch [--json] [--backend-log ] [--case ] [--success-pattern ] [--failure-pattern ] [--expected-failure ] [--interval-ms ] [--duration-ms ] [--from-start] [--strict] bin/lbs [--root ] log guard start [--run-id ] [--output-dir ] [--backend-log ] [--case ] [--json] bin/lbs [--root ] log guard stop --run-id [--output-dir ] [--session ] [--output ] [--case ] [--backend-log ] [--since ] [--until ] [--json] [--no-strict] bin/lbs [--root ] case new --title [--skill langbot-testing] [--mode agent-browser|probe] [--area ] [--type smoke] bin/lbs [--root ] case list [skill] [--json] [--type ] [--area ] [--tag ] [--priority p0|p1|p2] [--risk low|medium|high] [--automation] [--ci] [--ready] [--machine-ready] bin/lbs [--root ] case show [skill] bin/lbs [--root ] suite new --title [--skill langbot-testing] [--description ] [--type smoke] [--priority p2] bin/lbs [--root ] suite list [skill] [--json] [--type ] [--priority p0|p1|p2] bin/lbs [--root ] suite show [skill] bin/lbs [--root ] suite plan [skill] [--json] bin/lbs [--root ] suite start [skill] [--run-id ] [--evidence-dir ] [--output ] [--json] bin/lbs [--root ] suite run [skill] [--run-id ] [--evidence-dir ] [--output ] [--headed] [--dry-run] [--include-manual-check] [--include-not-ready] [--json] bin/lbs [--root ] suite report [skill] [--run-id ] [--evidence-dir ] [--output ] [--json] bin/lbs [--root ] test plan [skill] [--json] bin/lbs [--root ] test recommend [--file ] [--json] bin/lbs [--root ] test start [skill] [--output ] [--json] bin/lbs [--root ] test run [skill] [--output ] [--run-id ] [--headed] [--dry-run] [--json] bin/lbs [--root ] test report [skill] [--output ] [--json] [--backend-log ] [--frontend-log ] [--console-log ] [--evidence-dir ] [--since ] [--until ] [--tail-lines ] [--no-auto-log] bin/lbs [--root ] test result [skill] --result --reason --evidence-dir [--evidence ui,console,backend_log] [--started-at ] [--finished-at ] [--run-id ] [--url ] [--browser-path ] [--report ] [--notes ] [--json] bin/lbs [--root ] trouble list [skill] bin/lbs [--root ] trouble show [skill] bin/lbs [--root ] trouble search bin/lbs [--root ] trouble add --title --symptom --cause --fix [--id ] [--verify ] `); exit(2); } export function fail(message: string): never { console.error(`ERROR: ${message}`); exit(1); } export function repoRoot(start: string): string { let current = resolve(start); while (true) { // The skills assets root is identified by skills.index.json (present at the // root of this assets tree). Check it first so that when the tree lives // inside a larger repo (e.g. LangBot/skills/), we stop at the assets root // and not at the outer repo's .git/README.md. if (existsSync(`${current}/skills.index.json`) && existsSync(`${current}/schemas/case.schema.json`)) { return current; } if (existsSync(`${current}/.git`) && existsSync(`${current}/README.md`)) { return current; } const parent = dirname(current); if (parent === current) return resolve(start); current = parent; } } export function parseGlobalArgs(rawArgs: string[]): CommandContext { let root = repoRoot(cwd()); const args = [...rawArgs]; for (let i = 0; i < args.length; ) { if (args[i] === "--root") { const value = args[i + 1]; if (!value) fail("--root requires a path"); root = resolve(value); args.splice(i, 2); continue; } i += 1; } return { root, args }; } export function parseOptions(args: string[]): { positional: string[]; options: Record } { const positional: string[] = []; const options: Record = {}; for (let i = 0; i < args.length; i += 1) { const arg = args[i]; if (arg.startsWith("--")) { const key = arg.slice(2); const value = args[i + 1]; if (!value || value.startsWith("--")) { options[key] = true; } else { options[key] = value; i += 1; } } else { positional.push(arg); } } return { positional, options }; } export function optionString(options: Record, key: string): string | undefined { const value = options[key]; return typeof value === "string" ? value : undefined; }