@dylanrussell/agent-router 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/errors.ts","../src/core/schema.ts","../src/core/atomic-write.ts","../src/core/state.ts","../src/cli.ts","../src/core/backups.ts","../src/core/config.ts","../src/core/paths.ts","../src/core/frontmatter.ts","../src/core/history.ts","../src/core/opencode-config.ts","../src/core/stack-manager.ts","../src/core/validator.ts","../src/version.ts"],"sourcesContent":["/**\n * Typed errors used across the agent-router core.\n *\n * Why typed errors instead of plain `Error`? The CLI maps each subclass to a\n * specific exit code (see `src/cli.ts`); the plugin tools map them to\n * structured tool-error responses opencode can render. Untyped Errors would\n * force string-matching at the boundary, which is brittle.\n *\n * All errors below carry an `exitCode` static so the CLI can do\n * `if (err instanceof RouterError) process.exit(err.constructor.exitCode)`.\n */\n\nexport abstract class RouterError extends Error {\n /** CLI exit code for this error class. Subclasses override. */\n static readonly exitCode: number = 1;\n override readonly name: string = \"RouterError\";\n}\n\n/** User passed a stack name that doesn't exist on disk. */\nexport class StackNotFoundError extends RouterError {\n static override readonly exitCode = 2;\n override readonly name = \"StackNotFoundError\";\n constructor(\n public readonly stackName: string,\n public readonly available: ReadonlyArray<string>,\n ) {\n super(\n `Stack \"${stackName}\" not found. Available: ${available.length ? available.join(\", \") : \"(none)\"}.`,\n );\n }\n}\n\n/**\n * An agent `.md` file a stack references is missing, or exists but has no\n * frontmatter `model:` line to rewrite. `apply` is strict: it fails before\n * writing anything rather than leaving the suite half-switched.\n */\nexport class AgentFileError extends RouterError {\n static override readonly exitCode = 2;\n override readonly name = \"AgentFileError\";\n constructor(\n public readonly agentName: string,\n public readonly filePath: string,\n reason: string,\n ) {\n super(`Agent \"${agentName}\" (${filePath}): ${reason}`);\n }\n}\n\n/** state.json missing or corrupt and the operation needs it. */\nexport class NoActiveStackError extends RouterError {\n static override readonly exitCode = 1;\n override readonly name = \"NoActiveStackError\";\n constructor(message = \"No active stack. Run `agent-router init` first.\") {\n super(message);\n }\n}\n\n/** A JSON file on disk failed schema validation. */\nexport class ValidationError extends RouterError {\n static override readonly exitCode = 4;\n override readonly name = \"ValidationError\";\n constructor(\n message: string,\n public readonly path?: string,\n public readonly issues?: ReadonlyArray<string>,\n ) {\n super(message);\n }\n}\n\n/** Model IDs in a stack are not reachable through current opencode auth. */\nexport class ModelValidationError extends RouterError {\n static override readonly exitCode = 4;\n override readonly name = \"ModelValidationError\";\n constructor(\n public readonly stackName: string,\n public readonly missing: ReadonlyArray<{ path: string; modelId: string }>,\n ) {\n super(\n `Stack \"${stackName}\" references ${missing.length} unreachable model ID${missing.length === 1 ? \"\" : \"s\"}.`,\n );\n }\n}\n\n/** Filesystem op failed (read/write/permission/etc.). */\nexport class IOError extends RouterError {\n static override readonly exitCode = 3;\n override readonly name = \"IOError\";\n readonly causedBy: unknown;\n constructor(message: string, causedBy?: unknown) {\n super(message);\n this.causedBy = causedBy;\n }\n}\n\n/** User passed bad arguments or attempted a refused operation (e.g. rm active without --force). */\nexport class UserError extends RouterError {\n static override readonly exitCode = 1;\n override readonly name = \"UserError\";\n}\n","/**\n * Zod schemas for agent-router's own files.\n *\n * Two philosophies:\n *\n * 1. `state.json` and `config.json` are OURS — strict schema, fail closed.\n * We control every byte; if malformed something corrupted it and we\n * should refuse to proceed rather than silently rebuild and lose pointers.\n *\n * 2. `stacks/*.json` are user-authored. We validate only the shape we care\n * about (an `agents` record whose entries carry a `model` string) and\n * pass unknown keys through verbatim so future additions survive\n * round-trips.\n */\n\nimport { z } from \"zod\";\n\n/* ------------------------------------------------------------------------- *\n * state.json *\n * ------------------------------------------------------------------------- */\n\nexport const StateFileSchema = z\n .object({\n version: z.literal(1),\n active: z.string().min(1),\n previousActive: z.string().min(1).nullable(),\n lastSwitchedAt: z.string().min(1),\n })\n .strict();\n\nexport type StateFile = z.infer<typeof StateFileSchema>;\n\n/* ------------------------------------------------------------------------- *\n * stack files *\n * ------------------------------------------------------------------------- */\n\n/**\n * The smallest commitment a stack entry makes: it must have a `model` string.\n * Unknown keys ride along unchanged.\n */\nexport const AgentEntrySchema = z\n .object({\n model: z.string().min(1),\n })\n .passthrough();\n\nexport type AgentEntry = z.infer<typeof AgentEntrySchema>;\n\n/**\n * A stack maps agent names (the `<name>.md` files in the agents dir) to the\n * model each should run on. `apply` rewrites each file's frontmatter `model:`\n * line to match; `capture` builds one of these from the current frontmatter.\n */\nexport const StackFileSchema = z\n .object({\n agents: z.record(z.string(), AgentEntrySchema),\n })\n .passthrough()\n .refine((s) => Object.keys(s.agents).length > 0, {\n message: \"Stack file must map at least one agent in `agents`.\",\n });\n\nexport type StackFile = z.infer<typeof StackFileSchema>;\n\n/* ------------------------------------------------------------------------- *\n * config.json (agent-router's own settings — strict, fail closed) *\n * ------------------------------------------------------------------------- */\n\nexport const ConfigFileSchema = z\n .object({\n /** Where the agent .md files live. Default: `${opencodeConfigDir}/agents`. */\n agentsDir: z.string().min(1).optional(),\n /** Where named stacks live. Default: `${routerHome}/stacks`. */\n stacksDir: z.string().min(1).optional(),\n })\n .strict();\n\nexport type ConfigFile = z.infer<typeof ConfigFileSchema>;\n\n/**\n * Loose schema for `opencode.json` / `tui.json`. We only read/edit `plugin`\n * (array). Everything else is preserved.\n */\nexport const OpencodeJsonSchema = z\n .object({\n plugin: z.array(z.string()).optional(),\n })\n .passthrough();\n\nexport type OpencodeJson = z.infer<typeof OpencodeJsonSchema>;\n","/**\n * Atomic file writes via tmp-then-rename.\n *\n * Why atomic? The user's agent `.md` files may be read concurrently by:\n * - opencode loading agents during startup,\n * - a parallel `agent-router` invocation in another terminal.\n *\n * A non-atomic `writeFile` truncates first, so a crash mid-write leaves an\n * empty / partial file and the next opencode startup blows up. Tmp-then-rename\n * guarantees readers see either the old contents or the new contents — never\n * a half-written file. POSIX rename(2) is atomic on the same filesystem.\n *\n * The tmp file lives next to the destination (same dir = same FS) and has a\n * `.artmp-<pid>-<rand>` suffix so concurrent writers don't collide.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { mkdir, realpath, rename, unlink, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { IOError } from \"./errors.js\";\n\n/**\n * Resolve symlinks before writing. rename(2) onto a symlink replaces the\n * link itself, not its target — which silently destroys dotfile-manager\n * setups (e.g. chezmoi's `~/.config/opencode/tui.json -> ~/.agents/tui.json`).\n * Writing to the resolved real path keeps the link intact and also keeps the\n * tmp file on the same filesystem as the actual destination, which rename's\n * atomicity requires.\n */\nasync function resolveDestination(destPath: string): Promise<string> {\n try {\n return await realpath(destPath);\n } catch {\n try {\n const dir = await realpath(path.dirname(destPath));\n return path.join(dir, path.basename(destPath));\n } catch {\n return destPath;\n }\n }\n}\n\n/**\n * Write `contents` to `destPath` atomically. Creates parent directories if\n * missing. Follows symlinks (writes through to the target). On failure cleans\n * up the tmp file (best-effort).\n *\n * @param destPath Absolute destination path.\n * @param contents UTF-8 string to write. Use `JSON.stringify` for JSON.\n */\nexport async function atomicWriteFile(destPath: string, contents: string): Promise<void> {\n await mkdir(path.dirname(destPath), { recursive: true });\n const dest = await resolveDestination(destPath);\n const dir = path.dirname(dest);\n\n const tmp = path.join(\n dir,\n `.${path.basename(dest)}.artmp-${process.pid}-${randomBytes(4).toString(\"hex\")}`,\n );\n\n try {\n await writeFile(tmp, contents, { encoding: \"utf8\", mode: 0o644 });\n await rename(tmp, dest);\n } catch (cause) {\n await unlink(tmp).catch(() => {\n // Best-effort cleanup. The tmp suffix means leftover files are\n // recognizable (`.artmp-*`) and harmless if we miss one.\n });\n throw new IOError(`Atomic write failed for ${destPath}: ${(cause as Error).message}`, cause);\n }\n}\n\n/**\n * Convenience: stringify with 2-space indent + trailing newline (POSIX\n * text-file convention; also keeps `git diff` clean), then atomic-write.\n */\nexport async function atomicWriteJson(destPath: string, value: unknown): Promise<void> {\n const text = `${JSON.stringify(value, null, 2)}\\n`;\n await atomicWriteFile(destPath, text);\n}\n","/**\n * state.json read/write.\n *\n * `state.json` is the single pointer that says which stack is currently\n * \"active\" (i.e. its models were most recently applied to the agent files'\n * frontmatter). Every CLI command that switches stacks updates this file\n * atomically so concurrent CLIs and the plugin agree on what's active.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { atomicWriteJson } from \"./atomic-write.js\";\nimport { IOError, ValidationError } from \"./errors.js\";\nimport { type StateFile, StateFileSchema } from \"./schema.js\";\n\n/**\n * Read state.json. Returns `null` if the file doesn't exist (uninitialized).\n * Throws `ValidationError` if the file exists but is malformed.\n */\nexport async function readState(statePath: string): Promise<StateFile | null> {\n if (!existsSync(statePath)) return null;\n let raw: string;\n try {\n raw = await readFile(statePath, \"utf8\");\n } catch (cause) {\n throw new IOError(`Failed to read ${statePath}: ${(cause as Error).message}`, cause);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new ValidationError(\n `state.json is not valid JSON: ${(cause as Error).message}`,\n statePath,\n );\n }\n const result = StateFileSchema.safeParse(parsed);\n if (!result.success) {\n throw new ValidationError(\n `state.json failed validation: ${result.error.issues.map((i) => i.message).join(\"; \")}`,\n statePath,\n result.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`),\n );\n }\n return result.data;\n}\n\n/** Atomic write of `state.json`. Validates before writing — defensive. */\nexport async function writeState(statePath: string, state: StateFile): Promise<void> {\n const result = StateFileSchema.safeParse(state);\n if (!result.success) {\n throw new ValidationError(\n `Refusing to write invalid state.json: ${result.error.issues.map((i) => i.message).join(\"; \")}`,\n statePath,\n );\n }\n await atomicWriteJson(statePath, result.data);\n}\n","/**\n * `agent-router` CLI entry point.\n *\n * One file, one tool — uses `cac` for arg parsing because it's tiny (~6kB\n * minified) and we don't need the bells and whistles of yargs/commander.\n *\n * Architecture:\n * - Each subcommand is a small async function that calls into `core/`.\n * - All stdout/stderr formatting lives here (the core throws typed errors;\n * the CLI maps them to exit codes + human-readable output).\n * - We never call `process.exit()` directly — we throw and let `main()`\n * handle it. This makes the CLI testable via dynamic import.\n *\n * Exit codes (see `errors.ts`):\n * 0 success\n * 1 user error (bad args, refused destructive op)\n * 2 stack or agent file not found\n * 3 IO error\n * 4 schema or model-validation failed\n */\n\nimport { spawnSync } from \"node:child_process\";\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { cac } from \"cac\";\nimport { backupFiles } from \"./core/backups.js\";\nimport { resolvePathsWithConfig } from \"./core/config.js\";\nimport {\n IOError,\n ModelValidationError,\n RouterError,\n StackNotFoundError,\n UserError,\n} from \"./core/errors.js\";\nimport { readAgentModels } from \"./core/frontmatter.js\";\nimport { listHistory } from \"./core/history.js\";\nimport {\n PLUGIN_REGISTRY_ENTRY,\n ensurePluginEntry,\n ensureTuiJsonPluginEntry,\n readOpencodeJson,\n removePluginEntry,\n writeOpencodeJson,\n} from \"./core/opencode-config.js\";\nimport type { RouterPaths } from \"./core/paths.js\";\nimport { StackFileSchema } from \"./core/schema.js\";\nimport {\n applyStack,\n back,\n captureStack,\n exportStack,\n getActiveStackName,\n importStack,\n listStacks,\n readStack,\n readStackRaw,\n removeStack,\n stackPath,\n} from \"./core/stack-manager.js\";\nimport { validateStack } from \"./core/validator.js\";\nimport { VERSION } from \"./version.js\";\n\n/* ------------------------------------------------------------------------- *\n * helpers *\n * ------------------------------------------------------------------------- */\n\nconst log = (...args: unknown[]): void => console.log(...args);\nconst err = (...args: unknown[]): void => console.error(...args);\n\n/**\n * Format `[*] premium` style line for `list`. Uses an asterisk because\n * unicode bullet glyphs render inconsistently across terminal fonts.\n */\nfunction formatStackListLine(name: string, isActive: boolean): string {\n return `${isActive ? \"*\" : \" \"} ${name}`;\n}\n\n/* ------------------------------------------------------------------------- *\n * subcommand implementations *\n * ------------------------------------------------------------------------- */\n\ninterface InitOptions {\n force?: boolean;\n noEditOpencodeJson?: boolean;\n}\n\nconst INIT_CAPTURE_NAME = \"default\";\n\nasync function cmdInit(paths: RouterPaths, opts: InitOptions): Promise<void> {\n await mkdir(paths.stacksDir, { recursive: true });\n await mkdir(paths.historyDir, { recursive: true });\n\n const { readState, writeState } = await import(\"./core/state.js\");\n const existing = await readState(paths.statePath);\n const stacks = await listStacks(paths);\n\n if ((!existing || opts.force) && stacks.length === 0) {\n try {\n const captured = await captureStack(paths, INIT_CAPTURE_NAME, { force: !!opts.force });\n await writeState(paths.statePath, {\n version: 1,\n active: INIT_CAPTURE_NAME,\n previousActive: null,\n lastSwitchedAt: new Date().toISOString(),\n });\n log(\n `agent-router: captured ${captured.agents} agent${captured.agents === 1 ? \"\" : \"s\"} from ${paths.agentsDir} → stacks/${INIT_CAPTURE_NAME}.json (active)`,\n );\n } catch (e) {\n if (e instanceof UserError) {\n log(`agent-router: no agents captured (${e.message})`);\n log(\n \" create agent .md files with a frontmatter `model:` line, then run `agent-router capture <name>`.\",\n );\n } else {\n throw e;\n }\n }\n } else if (existing) {\n log(\n `agent-router: state already initialized (active=\"${existing.active}\"); use --force to reset`,\n );\n }\n\n if (opts.noEditOpencodeJson) {\n log(\"agent-router: --no-edit-opencode-json passed; not editing opencode.json or tui.json\");\n log(` add this to opencode.json plugin[]: \"${PLUGIN_REGISTRY_ENTRY}\"`);\n log(` add this to tui.json plugin[] (sidebar): \"${PLUGIN_REGISTRY_ENTRY}\"`);\n return;\n }\n\n await editOpencodeJson(paths);\n await editTuiJson(paths);\n}\n\n/**\n * Register the TUI half in tui.json — opencode >= 1.17 loads TUI plugins\n * (sidebar, commands) from tui.json's `plugin` array, not opencode.json's.\n */\nasync function editTuiJson(paths: RouterPaths): Promise<void> {\n const exists = existsSync(paths.tuiJsonPath);\n if (exists) {\n await backupFiles(paths.opencodeBackupsDir, [paths.tuiJsonPath]);\n }\n const result = await ensureTuiJsonPluginEntry(paths.tuiJsonPath);\n if (result.added) {\n log(`agent-router: added \"${PLUGIN_REGISTRY_ENTRY}\" to tui.json plugin[] (sidebar support)`);\n return;\n }\n log(\"agent-router: tui.json already up to date\");\n}\n\n/**\n * Ensure our plugin entry is in opencode.json (and drop the legacy\n * `@dylanrussell/omo-router` entry when found).\n */\nasync function editOpencodeJson(paths: RouterPaths): Promise<void> {\n const cfg = await readOpencodeJson(paths.opencodeJsonPath);\n if (!cfg) {\n log(`agent-router: ${paths.opencodeJsonPath} missing — skipping plugin edits.`);\n return;\n }\n\n const removed = removePluginEntry(cfg);\n const ensured = ensurePluginEntry(removed.config);\n\n if (!ensured.result.added && removed.result.removed.length === 0) {\n log(\"agent-router: opencode.json already up to date\");\n return;\n }\n\n await backupFiles(paths.opencodeBackupsDir, [paths.opencodeJsonPath]);\n await writeOpencodeJson(paths.opencodeJsonPath, ensured.config);\n\n if (removed.result.removed.length) {\n log(\n `agent-router: removed legacy plugin entr${removed.result.removed.length === 1 ? \"y\" : \"ies\"}: ${removed.result.removed.join(\", \")}`,\n );\n }\n if (ensured.result.added) {\n log(`agent-router: added \"${PLUGIN_REGISTRY_ENTRY}\" to opencode.json plugin[]`);\n }\n}\n\nasync function cmdList(paths: RouterPaths): Promise<void> {\n const stacks = await listStacks(paths);\n if (stacks.length === 0) {\n log(\"(no stacks; run `agent-router init` or `agent-router capture <name>`)\");\n return;\n }\n const active = await getActiveStackName(paths);\n for (const s of stacks) log(formatStackListLine(s, s === active));\n}\n\nasync function cmdStatus(paths: RouterPaths): Promise<void> {\n const active = await getActiveStackName(paths);\n log(active ?? \"(none)\");\n}\n\nasync function cmdShow(paths: RouterPaths, name: string): Promise<void> {\n const raw = await readStackRaw(paths, name);\n try {\n log(JSON.stringify(JSON.parse(raw), null, 2));\n } catch {\n process.stdout.write(raw);\n }\n}\n\nasync function cmdCurrent(paths: RouterPaths): Promise<void> {\n const models = await readAgentModels(paths.agentsDir);\n const names = Object.keys(models).sort();\n if (names.length === 0) {\n log(`(no agent .md files with a \\`model:\\` line in ${paths.agentsDir})`);\n return;\n }\n const width = Math.max(...names.map((n) => n.length));\n for (const n of names) log(`${n.padEnd(width)} ${models[n]}`);\n}\n\ninterface UseOptions {\n noValidate?: boolean;\n forceInvalid?: boolean;\n}\n\nasync function cmdUse(paths: RouterPaths, name: string, opts: UseOptions): Promise<void> {\n const r = await applyStack(paths, name, {\n validate: !opts.noValidate,\n forceInvalid: opts.forceInvalid ?? false,\n });\n log(\n `Switched: ${r.previous ?? \"(none)\"} → ${r.current} (${r.changed.length} agent${r.changed.length === 1 ? \"\" : \"s\"} updated). Restart opencode for change to take effect.`,\n );\n}\n\nasync function cmdBack(paths: RouterPaths, n: number, opts: UseOptions): Promise<void> {\n const r = await back(paths, n, {\n validate: !opts.noValidate,\n forceInvalid: opts.forceInvalid ?? false,\n });\n log(\n `Switched: ${r.previous ?? \"(none)\"} → ${r.current} (${r.changed.length} agent${r.changed.length === 1 ? \"\" : \"s\"} updated). Restart opencode for change to take effect.`,\n );\n}\n\nasync function cmdCapture(paths: RouterPaths, name: string, force: boolean): Promise<void> {\n const r = await captureStack(paths, name, { force });\n log(`Captured ${r.agents} agent${r.agents === 1 ? \"\" : \"s\"} → ${r.path}`);\n}\n\nasync function cmdHistory(paths: RouterPaths, limit: number): Promise<void> {\n const entries = await listHistory(paths.historyDir);\n if (entries.length === 0) {\n log(\"(no history)\");\n return;\n }\n for (const e of entries.slice(0, limit)) {\n log(`${e.id} ${e.fromStack} → ${e.toStack}`);\n }\n}\n\ninterface ValidateCmdOptions {\n all?: boolean;\n active?: boolean;\n}\n\nasync function cmdValidate(\n paths: RouterPaths,\n name: string | undefined,\n opts: ValidateCmdOptions,\n): Promise<void> {\n const targets: Array<{ readonly name: string; readonly load: () => Promise<unknown> }> = [];\n if (opts.active) {\n targets.push({\n name: \"(current frontmatter)\",\n load: async () => {\n const models = await readAgentModels(paths.agentsDir);\n const agents = Object.fromEntries(\n Object.entries(models).map(([k, model]) => [k, { model }]),\n );\n return { agents };\n },\n });\n } else if (opts.all) {\n for (const n of await listStacks(paths)) {\n targets.push({ name: n, load: () => readStack(paths, n) });\n }\n } else {\n if (!name) throw new UserError(\"Specify a stack name, or pass --all or --active.\");\n targets.push({ name, load: () => readStack(paths, name) });\n }\n\n let anyMissing = false;\n for (const t of targets) {\n const parsed = StackFileSchema.parse(await t.load());\n const r = await validateStack(parsed);\n if (r.ok) {\n log(`${t.name}: OK (${r.checked} model id${r.checked === 1 ? \"\" : \"s\"} checked)`);\n } else {\n anyMissing = true;\n err(`${t.name}: MISSING ${r.missing.length} model id${r.missing.length === 1 ? \"\" : \"s\"}:`);\n for (const m of r.missing) err(` ${m.path.padEnd(48)} ${m.modelId}`);\n }\n }\n if (anyMissing) {\n err(\"\");\n err(\n \"Run `opencode auth list` to confirm provider auth, or `opencode models` to see the full reachable list.\",\n );\n throw new ModelValidationError(\"(validation gate)\", []);\n }\n}\n\nasync function cmdRm(paths: RouterPaths, name: string, force: boolean): Promise<void> {\n await removeStack(paths, name, { force });\n log(`Removed stack \"${name}\"`);\n}\n\nasync function cmdEdit(paths: RouterPaths, name: string): Promise<void> {\n const filePath = stackPath(paths, name);\n if (!existsSync(filePath)) {\n throw new StackNotFoundError(name, await listStacks(paths));\n }\n const editor = process.env.EDITOR && process.env.EDITOR.length > 0 ? process.env.EDITOR : \"vi\";\n const result = spawnSync(editor, [filePath], { stdio: \"inherit\" });\n if (result.status !== 0) {\n throw new UserError(`Editor exited with status ${result.status}.`);\n }\n // Re-validate: warn if the user broke the schema, but don't block — they may\n // be mid-edit and want to fix it on a follow-up run.\n try {\n await readStack(paths, name);\n } catch (e) {\n err(`Warning: ${(e as Error).message}`);\n }\n}\n\nasync function cmdImport(\n paths: RouterPaths,\n name: string,\n file: string,\n force: boolean,\n): Promise<void> {\n await importStack(paths, name, path.resolve(file), { force });\n log(`Imported \"${file}\" → stacks/${name}.json`);\n}\n\nasync function cmdExport(paths: RouterPaths, name: string, file: string): Promise<void> {\n await exportStack(paths, name, path.resolve(file));\n log(`Exported stacks/${name}.json → \"${file}\"`);\n}\n\nfunction cmdPath(paths: RouterPaths): void {\n for (const [k, v] of Object.entries(paths)) log(`${k.padEnd(22)} ${v as string}`);\n}\n\n/* ------------------------------------------------------------------------- *\n * cac wiring *\n * ------------------------------------------------------------------------- */\n\nasync function main(argv: ReadonlyArray<string>): Promise<number> {\n const cli = cac(\"agent-router\");\n const paths = await resolvePathsWithConfig();\n\n cli\n .command(\"init\", \"create state dirs, capture current models, register the plugin\")\n .option(\"--force\", \"overwrite existing state\")\n .option(\"--no-edit-opencode-json\", \"do not modify opencode.json or tui.json\")\n .action(async (opts: { force?: boolean; editOpencodeJson?: boolean }) => {\n const passthrough: InitOptions = {};\n if (opts.force !== undefined) passthrough.force = opts.force;\n if (opts.editOpencodeJson === false) passthrough.noEditOpencodeJson = true;\n await cmdInit(paths, passthrough);\n });\n\n cli.command(\"list\", \"list available stacks (* marks active)\").action(() => cmdList(paths));\n cli.command(\"status\", \"print active stack name\").action(() => cmdStatus(paths));\n cli\n .command(\"current\", \"print the agent → model mapping currently in frontmatter\")\n .action(() => cmdCurrent(paths));\n cli\n .command(\"show <name>\", \"print a stack file as pretty JSON\")\n .action((n: string) => cmdShow(paths, n));\n\n cli\n .command(\"use <name>\", \"apply a stack to the agent files (validates first)\")\n .alias(\"apply\")\n .option(\"--no-validate\", \"skip the pre-apply model validation\")\n .option(\"--force-invalid\", \"apply even if validation finds missing models\")\n .action(async (n: string, opts: { validate?: boolean; forceInvalid?: boolean }) => {\n const passthrough: UseOptions = {};\n if (opts.validate === false) passthrough.noValidate = true;\n if (opts.forceInvalid !== undefined) passthrough.forceInvalid = opts.forceInvalid;\n await cmdUse(paths, n, passthrough);\n });\n\n cli\n .command(\"capture <name>\", \"snapshot current frontmatter models into a new stack\")\n .option(\"--force\", \"overwrite an existing stack\")\n .action((n: string, opts: { force?: boolean }) => cmdCapture(paths, n, !!opts.force));\n\n cli\n .command(\"back\", \"undo the last N switches\")\n .option(\"-n <n>\", \"how many switches to undo\", { default: 1 })\n .option(\"--no-validate\", \"skip the pre-apply model validation\")\n .option(\"--force-invalid\", \"apply even if validation finds missing models\")\n .action(async (opts: { n: number; validate?: boolean; forceInvalid?: boolean }) => {\n const passthrough: UseOptions = {};\n if (opts.validate === false) passthrough.noValidate = true;\n if (opts.forceInvalid !== undefined) passthrough.forceInvalid = opts.forceInvalid;\n await cmdBack(paths, opts.n ?? 1, passthrough);\n });\n\n cli\n .command(\"history\", \"list recent switches (newest first)\")\n .option(\"--limit <n>\", \"max entries to show\", { default: 20 })\n .action((opts: { limit: number }) => cmdHistory(paths, opts.limit ?? 20));\n\n cli\n .command(\n \"validate [name]\",\n \"verify model IDs in a stack are reachable via current opencode auth\",\n )\n .option(\"--all\", \"validate every stack\")\n .option(\"--active\", \"validate the models currently in agent frontmatter\")\n .action((name: string | undefined, opts: { all?: boolean; active?: boolean }) =>\n cmdValidate(paths, name, opts),\n );\n\n cli\n .command(\"rm <name>\", \"delete a stack\")\n .option(\"--force\", \"delete even if active\")\n .action((n: string, opts: { force?: boolean }) => cmdRm(paths, n, !!opts.force));\n\n cli\n .command(\"edit <name>\", \"open a stack in $EDITOR (fallback `vi`)\")\n .action((n: string) => cmdEdit(paths, n));\n\n cli\n .command(\"import <name> <file>\", \"copy <file> into stacks/<name>.json\")\n .option(\"--force\", \"overwrite an existing stack\")\n .action((n: string, f: string, opts: { force?: boolean }) =>\n cmdImport(paths, n, f, !!opts.force),\n );\n\n cli\n .command(\"export <name> <file>\", \"copy stacks/<name>.json to <file>\")\n .action((n: string, f: string) => cmdExport(paths, n, f));\n\n cli.command(\"path\", \"print all paths used by agent-router\").action(() => cmdPath(paths));\n\n cli.command(\"completion\", \"print instructions for installing shell autocomplete\").action(() => {\n log(\"To install autocomplete for agent-router, run one of the following:\\n\");\n log(\n \"ZSH:\\n agent-router completion-script zsh > ~/.agent-router-completion.zsh\\n echo 'source ~/.agent-router-completion.zsh' >> ~/.zshrc\\n\",\n );\n log(\n \"BASH:\\n agent-router completion-script bash > ~/.agent-router-completion.bash\\n echo 'source ~/.agent-router-completion.bash' >> ~/.bashrc\\n\",\n );\n log(\n \"FISH:\\n agent-router completion-script fish > ~/.config/fish/completions/agent-router.fish\\n\",\n );\n });\n\n cli\n .command(\"completion-script <shell>\", \"Generate the completion script for your shell\")\n .action((shell: string) => {\n if (shell === \"zsh\") {\n log(\n `\n#compdef agent-router\n\n_agent_router() {\n local -a commands\n commands=(\n 'init:create state dirs, capture current models, register the plugin'\n 'list:list available stacks'\n 'status:print active stack name'\n 'current:print the current agent → model mapping'\n 'show:print a stack file as pretty JSON'\n 'use:apply a stack to the agent files'\n 'capture:snapshot current models into a new stack'\n 'back:undo the last N switches'\n 'history:list recent switches'\n 'validate:verify model IDs'\n 'rm:delete a stack'\n 'edit:open a stack in $EDITOR'\n 'import:copy file into stacks'\n 'export:copy stack to file'\n 'path:print all paths'\n )\n\n if (( CURRENT == 2 )); then\n _describe -t commands 'commands' commands\n else\n local cmd=\\${words[2]}\n case $cmd in\n show|use|validate|rm|edit|export)\n local -a stacks\n stacks=($(agent-router completion-resolve))\n _describe -t stacks 'stacks' stacks\n ;;\n esac\n fi\n}\n\ncompdef _agent_router agent-router\n `.trim(),\n );\n } else if (shell === \"bash\") {\n log(\n `\n_agent_router() {\n local cur prev commands stacks\n COMPREPLY=()\n cur=\"\\${COMP_WORDS[COMP_CWORD]}\"\n prev=\"\\${COMP_WORDS[COMP_CWORD-1]}\"\n commands=\"init list status current show use capture back history validate rm edit import export path completion\"\n\n if [[ \\${COMP_CWORD} == 1 ]]; then\n COMPREPLY=( $(compgen -W \"\\${commands}\" -- \\${cur}) )\n return 0\n fi\n\n case \"\\${prev}\" in\n show|use|validate|rm|edit|export)\n stacks=$(agent-router completion-resolve 2>/dev/null)\n COMPREPLY=( $(compgen -W \"\\${stacks}\" -- \\${cur}) )\n ;;\n esac\n return 0\n}\n\ncomplete -F _agent_router agent-router\n `.trim(),\n );\n } else if (shell === \"fish\") {\n log(\n `\nfunction _agent_router_needs_command\n set cmd (commandline -opc)\n if test (count $cmd) -eq 1\n return 0\n end\n return 1\nend\n\nfunction _agent_router_using_command\n set cmd (commandline -opc)\n if test (count $cmd) -gt 1\n if test $cmd[2] = $argv[1]\n return 0\n end\n end\n return 1\nend\n\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a init -d 'create state dirs, capture current models, register the plugin'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a list -d 'list available stacks'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a status -d 'print active stack name'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a current -d 'print the current agent → model mapping'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a show -d 'print a stack file as pretty JSON'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a use -d 'apply a stack to the agent files'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a capture -d 'snapshot current models into a new stack'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a back -d 'undo the last N switches'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a history -d 'list recent switches'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a validate -d 'verify model IDs'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a rm -d 'delete a stack'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a edit -d 'open a stack in $EDITOR'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a import -d 'copy file into stacks'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a export -d 'copy stack to file'\ncomplete -f -c agent-router -n '_agent_router_needs_command' -a path -d 'print all paths'\n\ncomplete -f -c agent-router -n '_agent_router_using_command show' -a '(agent-router completion-resolve)'\ncomplete -f -c agent-router -n '_agent_router_using_command use' -a '(agent-router completion-resolve)'\ncomplete -f -c agent-router -n '_agent_router_using_command validate' -a '(agent-router completion-resolve)'\ncomplete -f -c agent-router -n '_agent_router_using_command rm' -a '(agent-router completion-resolve)'\ncomplete -f -c agent-router -n '_agent_router_using_command edit' -a '(agent-router completion-resolve)'\ncomplete -f -c agent-router -n '_agent_router_using_command export' -a '(agent-router completion-resolve)'\n `.trim(),\n );\n } else {\n err(\"Unsupported shell. Supported: bash, zsh, fish\");\n process.exit(1);\n }\n });\n\n cli\n .command(\"completion-resolve\", \"Internal command used by shell autocomplete\")\n .action(async () => {\n try {\n const stacks = await listStacks(paths);\n log(stacks.join(\"\\n\"));\n } catch {\n // Silently fail during completion\n }\n });\n\n cli.help();\n cli.version(VERSION);\n\n try {\n cli.parse([process.argv[0] ?? \"node\", process.argv[1] ?? \"agent-router\", ...argv], {\n run: false,\n });\n if (cli.matchedCommand) {\n await cli.runMatchedCommand();\n } else if (\n argv.length > 0 &&\n !argv.includes(\"--help\") &&\n !argv.includes(\"-h\") &&\n !argv.includes(\"--version\") &&\n !argv.includes(\"-v\")\n ) {\n cli.outputHelp();\n return 1;\n }\n return 0;\n } catch (e) {\n if (e instanceof RouterError) {\n err(`error: ${e.message}`);\n if (e instanceof ModelValidationError && e.missing.length > 0) {\n err(\"\");\n for (const m of e.missing) err(` ${m.path.padEnd(48)} ${m.modelId}`);\n }\n const ctor = e.constructor as { exitCode?: number };\n return ctor.exitCode ?? 1;\n }\n err(`unexpected error: ${(e as Error).message}`);\n return 1;\n }\n}\n\n// Detect direct invocation. We can't simply compare `import.meta.url` to\n// `process.argv[1]` — npm-installed bins live behind a symlink so the two\n// paths differ after Node loads the file. Compare via `realpathSync` on both\n// sides; both should resolve to the same dist/cli.js.\nfunction isInvokedAsScript(): boolean {\n if (!import.meta.url.startsWith(\"file:\")) return false;\n const argv1 = process.argv[1];\n if (!argv1) return false;\n try {\n return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(path.resolve(argv1));\n } catch {\n return false;\n }\n}\n\nif (isInvokedAsScript()) {\n main(process.argv.slice(2)).then(\n (code) => process.exit(code),\n (e) => {\n err(e);\n process.exit(1);\n },\n );\n}\n\nexport { main };\n","/**\n * One-shot timestamped backups of opencode config files.\n *\n * Backups land under `~/.config/opencode/.backups/<stamp>/` — the same\n * convention other opencode installers use, so files dropped here are\n * recognizable to the user.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { copyFile, mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { IOError } from \"./errors.js\";\n\n/** Format: `2026-05-04T13-22-01-000Z` — ISO with `:` and `.` swapped to `-` so it's path-safe. */\nexport function timestampStamp(date: Date = new Date()): string {\n return date.toISOString().replace(/[:.]/g, \"-\");\n}\n\n/**\n * Copy `files` (those that exist) into `${backupsRoot}/<stamp>/`.\n *\n * @param backupsRoot Typically `${opencodeConfigDir}/.backups`.\n * @param files Absolute paths to back up. Missing files are skipped silently.\n * @returns Absolute path of the timestamped backup directory.\n */\nexport async function backupFiles(\n backupsRoot: string,\n files: ReadonlyArray<string>,\n): Promise<string> {\n const stamp = timestampStamp();\n const dir = path.join(backupsRoot, stamp);\n await mkdir(dir, { recursive: true });\n\n for (const f of files) {\n if (!existsSync(f)) continue;\n try {\n await copyFile(f, path.join(dir, path.basename(f)));\n } catch (cause) {\n throw new IOError(`Backup copy failed for ${f}: ${(cause as Error).message}`, cause);\n }\n }\n return dir;\n}\n","/**\n * Read `${routerHome}/config.json` — agent-router's own settings file.\n *\n * Why a file when env vars already exist? `agentsDir`/`stacksDir` must agree\n * across two execution contexts: the CLI (your shell) and the plugin (inside\n * opencode). An env var has to be exported in BOTH or the contexts disagree\n * and drift returns. A file in a fixed location is read identically by both,\n * so a single declaration wins everywhere.\n *\n * Precedence (highest first): explicit `resolvePaths` option → this file →\n * env var → built-in default. The file beats env because it is the more\n * deliberate, version-controllable declaration.\n *\n * The schema is strict (fail closed) — this file is ours, like state.json.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport { IOError, ValidationError } from \"./errors.js\";\nimport { type ResolvePathsOptions, type RouterPaths, resolvePaths } from \"./paths.js\";\nimport { type ConfigFile, ConfigFileSchema } from \"./schema.js\";\n\nexport const CONFIG_FILE_NAME = \"config.json\";\n\n/** Expand a leading `~` or `~/` to the user's home directory. */\nexport function expandHome(p: string): string {\n if (p === \"~\") return homedir();\n if (p.startsWith(\"~/\")) return path.join(homedir(), p.slice(2));\n return p;\n}\n\n/** Read `${routerHome}/config.json`. Returns null if absent. Throws on parse/schema error. */\nexport async function readConfigFile(routerHome: string): Promise<ConfigFile | null> {\n const configPath = path.join(routerHome, CONFIG_FILE_NAME);\n if (!existsSync(configPath)) return null;\n\n let raw: string;\n try {\n raw = await readFile(configPath, \"utf8\");\n } catch (cause) {\n throw new IOError(`Failed to read ${configPath}: ${(cause as Error).message}`, cause);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new ValidationError(\n `agent-router config.json is not valid JSON: ${(cause as Error).message}`,\n configPath,\n );\n }\n\n const result = ConfigFileSchema.safeParse(parsed);\n if (!result.success) {\n throw new ValidationError(\n `agent-router config.json failed validation: ${result.error.issues.map((i) => i.message).join(\"; \")}`,\n configPath,\n result.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`),\n );\n }\n return result.data;\n}\n\n/** Expand `~` and anchor relative paths at `routerHome`. */\nfunction normalizeConfigPath(routerHome: string, p: string): string {\n const expanded = expandHome(p);\n return path.isAbsolute(expanded) ? expanded : path.resolve(routerHome, expanded);\n}\n\n/**\n * Like `resolvePaths`, but first reads `config.json` so its `agentsDir` /\n * `stacksDir` take effect. This is what the CLI and plugin call —\n * `resolvePaths` stays a pure, I/O-free function for tests and callers that\n * supply their own paths.\n *\n * Explicit `options.agentsDir` / `options.stacksDir` still win over the file.\n */\nexport async function resolvePathsWithConfig(\n options: ResolvePathsOptions = {},\n): Promise<RouterPaths> {\n const base = resolvePaths(options);\n\n const config = await readConfigFile(base.routerHome);\n if (!config) return base;\n\n const next: ResolvePathsOptions = { ...options };\n if (options.agentsDir === undefined && config.agentsDir) {\n (next as { agentsDir?: string }).agentsDir = normalizeConfigPath(\n base.routerHome,\n config.agentsDir,\n );\n }\n if (options.stacksDir === undefined && config.stacksDir) {\n (next as { stacksDir?: string }).stacksDir = normalizeConfigPath(\n base.routerHome,\n config.stacksDir,\n );\n }\n return resolvePaths(next);\n}\n","/**\n * Path resolution for agent-router.\n *\n * Three roots matter:\n * 1. `opencodeConfigDir` — where `opencode.json`, `tui.json`, and the\n * native `agents/` directory live.\n * Default: `${XDG_CONFIG_HOME:-~/.config}/opencode`.\n *\n * 2. `agentsDir` — the directory of agent `.md` files whose frontmatter\n * `model:` lines agent-router rewrites.\n * Default: `${opencodeConfigDir}/agents`.\n * Override: `AGENT_ROUTER_AGENTS_DIR` env var, or `agentsDir` in\n * config.json (see config.ts).\n *\n * 3. `routerHome` — machine-local state (state.json, history).\n * Default: `${opencodeConfigDir}/agent-router`.\n * Override: `AGENT_ROUTER_HOME` env var (`OMO_ROUTER_HOME` is honored as\n * a legacy fallback for one release).\n *\n * Stacks are config, history is state — so `stacksDir` is separately\n * overridable (`AGENT_ROUTER_STACKS_DIR` env / `stacksDir` in config.json),\n * letting users keep stacks in a dotfiles-managed location while history\n * stays machine-local. Default: `${routerHome}/stacks`.\n *\n * Tests rely on the env overrides to fully isolate from the user's real\n * config. Never hard-code paths elsewhere — go through `resolvePaths()`.\n */\n\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nexport interface RouterPaths {\n /** Directory housing `opencode.json`, `tui.json`, and `agents/`. */\n readonly opencodeConfigDir: string;\n /** `${opencodeConfigDir}/opencode.json`. */\n readonly opencodeJsonPath: string;\n /**\n * `${opencodeConfigDir}/tui.json` — opencode's TUI config. Since opencode\n * 1.17 the TUI loads its plugins from THIS file's `plugin` array, not from\n * `opencode.json`. `init` patches both so the sidebar half of agent-router\n * loads alongside the server half.\n */\n readonly tuiJsonPath: string;\n /** Directory of agent `.md` files (frontmatter `model:` lines are the live target). */\n readonly agentsDir: string;\n /** Directory `${opencodeConfigDir}/.backups` for installer-style backups. */\n readonly opencodeBackupsDir: string;\n /** Root for agent-router state (overridable via `AGENT_ROUTER_HOME`). */\n readonly routerHome: string;\n /** `${routerHome}/state.json`. */\n readonly statePath: string;\n /** Directory of named stack files (overridable via `AGENT_ROUTER_STACKS_DIR`). */\n readonly stacksDir: string;\n /** `${routerHome}/history` — rolling switch history. */\n readonly historyDir: string;\n}\n\nexport interface ResolvePathsOptions {\n /**\n * Override `opencodeConfigDir`. Used by tests that don't want to touch\n * `~/.config/opencode`.\n */\n readonly opencodeConfigDir?: string;\n /** Direct override for `routerHome`. Wins over both default and env. */\n readonly routerHome?: string;\n /** Direct override for `agentsDir`. Wins over both default and env. */\n readonly agentsDir?: string;\n /** Direct override for `stacksDir`. Wins over both default and env. */\n readonly stacksDir?: string;\n /**\n * Optional environment map for testability. Production callers omit this and\n * we read from `process.env`.\n */\n readonly env?: Readonly<Record<string, string | undefined>>;\n}\n\n/**\n * Compute all paths used by agent-router. Pure — no filesystem I/O.\n *\n * Resolution order (each): explicit option → env var → default.\n * - `routerHome`: `AGENT_ROUTER_HOME` (legacy `OMO_ROUTER_HOME`) → `${opencodeConfigDir}/agent-router`\n * - `agentsDir`: `AGENT_ROUTER_AGENTS_DIR` → `${opencodeConfigDir}/agents`\n * - `stacksDir`: `AGENT_ROUTER_STACKS_DIR` → `${routerHome}/stacks`\n */\nexport function resolvePaths(options: ResolvePathsOptions = {}): RouterPaths {\n const env = options.env ?? process.env;\n\n const opencodeConfigDir =\n options.opencodeConfigDir ??\n path.join(env.XDG_CONFIG_HOME ?? path.join(homedir(), \".config\"), \"opencode\");\n\n const routerHome =\n options.routerHome ??\n env.AGENT_ROUTER_HOME ??\n env.OMO_ROUTER_HOME ??\n path.join(opencodeConfigDir, \"agent-router\");\n\n const agentsDir =\n options.agentsDir ?? env.AGENT_ROUTER_AGENTS_DIR ?? path.join(opencodeConfigDir, \"agents\");\n\n const stacksDir =\n options.stacksDir ?? env.AGENT_ROUTER_STACKS_DIR ?? path.join(routerHome, \"stacks\");\n\n return {\n opencodeConfigDir,\n opencodeJsonPath: path.join(opencodeConfigDir, \"opencode.json\"),\n tuiJsonPath: path.join(opencodeConfigDir, \"tui.json\"),\n agentsDir,\n opencodeBackupsDir: path.join(opencodeConfigDir, \".backups\"),\n routerHome,\n statePath: path.join(routerHome, \"state.json\"),\n stacksDir,\n historyDir: path.join(routerHome, \"history\"),\n };\n}\n","/**\n * Frontmatter `model:` line handling — the read/write layer between stacks\n * and the agent `.md` files opencode loads.\n *\n * Contract (see README): an agent file starts with a YAML frontmatter block\n * delimited by `---` lines, containing exactly one top-level `model:` key.\n * agent-router rewrites ONLY that line; the prompt body and every other\n * frontmatter key are owned by the user and never touched.\n *\n * Parsing is deliberately line-based rather than a full YAML round-trip:\n * a YAML parser would re-serialize the whole block and clobber the user's\n * comments, key order, and formatting. A targeted line replacement cannot.\n * Only top-level (column-0) `model:` keys match, so nested keys like\n * `options.model` are never confused for the agent model.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { readFile, readdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { AgentFileError, IOError } from \"./errors.js\";\n\n/** Matches the frontmatter block at the very start of the file. */\nconst FRONTMATTER_RE = /^---\\r?\\n([\\s\\S]*?)\\r?\\n---(?:\\r?\\n|$)/;\n\n/** Matches a top-level `model:` line inside frontmatter (multiline mode). */\nconst MODEL_LINE_RE = /^model:[ \\t]*(.*)$/m;\n\n/** Strip a trailing YAML comment (` # ...`) and surrounding quotes/space. */\nfunction cleanModelValue(raw: string): string {\n let v = raw;\n const hash = v.search(/[ \\t]#/);\n if (hash >= 0) v = v.slice(0, hash);\n v = v.trim();\n if (\n (v.startsWith('\"') && v.endsWith('\"') && v.length >= 2) ||\n (v.startsWith(\"'\") && v.endsWith(\"'\") && v.length >= 2)\n ) {\n v = v.slice(1, -1);\n }\n return v;\n}\n\n/**\n * Extract the frontmatter `model:` value from agent file content.\n * Returns null when the file has no frontmatter or no model line.\n */\nexport function getFrontmatterModel(content: string): string | null {\n const fm = FRONTMATTER_RE.exec(content);\n if (!fm?.[1]) return null;\n const line = MODEL_LINE_RE.exec(fm[1]);\n if (!line) return null;\n const value = cleanModelValue(line[1] ?? \"\");\n return value.length > 0 ? value : null;\n}\n\n/**\n * Return new content with the frontmatter `model:` line replaced by\n * `model: <model>`. Throws (plain Error — callers wrap with context) when\n * there is no frontmatter or no model line to replace.\n */\nexport function setFrontmatterModel(content: string, model: string): string {\n const fm = FRONTMATTER_RE.exec(content);\n if (!fm?.[1]) throw new Error(\"no frontmatter block\");\n const block = fm[1];\n if (!MODEL_LINE_RE.test(block)) throw new Error(\"no `model:` line in frontmatter\");\n // Replacement callback sidesteps `$`-pattern interpretation in the model id.\n const nextBlock = block.replace(MODEL_LINE_RE, () => `model: ${model}`);\n // Splice the edited block back into the matched frontmatter by index so no\n // string in the file is ever treated as a pattern.\n const blockStart = fm[0].indexOf(block);\n const nextFm = fm[0].slice(0, blockStart) + nextBlock + fm[0].slice(blockStart + block.length);\n return nextFm + content.slice(fm[0].length);\n}\n\n/** Absolute path of `<agentsDir>/<name>.md`. */\nexport function agentFilePath(agentsDir: string, name: string): string {\n return path.join(agentsDir, `${name}.md`);\n}\n\n/** List agent names (`.md` basenames) in the agents dir, sorted. */\nexport async function listAgentFiles(agentsDir: string): Promise<string[]> {\n if (!existsSync(agentsDir)) return [];\n let names: string[];\n try {\n names = await readdir(agentsDir);\n } catch (cause) {\n throw new IOError(`Failed to read agents dir: ${(cause as Error).message}`, cause);\n }\n return names\n .filter((n) => n.endsWith(\".md\"))\n .map((n) => n.slice(0, -\".md\".length))\n .sort();\n}\n\n/**\n * Read the current agent → model mapping from every `.md` file in the agents\n * dir. Files without a frontmatter `model:` line are skipped (they aren't\n * routable agents — e.g. docs accidentally living there).\n */\nexport async function readAgentModels(agentsDir: string): Promise<Record<string, string>> {\n const out: Record<string, string> = {};\n for (const name of await listAgentFiles(agentsDir)) {\n const filePath = agentFilePath(agentsDir, name);\n let content: string;\n try {\n content = await readFile(filePath, \"utf8\");\n } catch (cause) {\n throw new IOError(`Failed to read ${filePath}: ${(cause as Error).message}`, cause);\n }\n const model = getFrontmatterModel(content);\n if (model !== null) out[name] = model;\n }\n return out;\n}\n\n/**\n * Read one agent file strictly: throws `AgentFileError` when the file is\n * missing or has no rewritable `model:` line. Returns the raw content plus\n * the current model, ready for `setFrontmatterModel`.\n */\nexport async function readAgentFileStrict(\n agentsDir: string,\n name: string,\n): Promise<{ filePath: string; content: string; model: string }> {\n const filePath = agentFilePath(agentsDir, name);\n if (!existsSync(filePath)) {\n throw new AgentFileError(name, filePath, \"file does not exist\");\n }\n let content: string;\n try {\n content = await readFile(filePath, \"utf8\");\n } catch (cause) {\n throw new IOError(`Failed to read ${filePath}: ${(cause as Error).message}`, cause);\n }\n const model = getFrontmatterModel(content);\n if (model === null) {\n throw new AgentFileError(name, filePath, \"no frontmatter `model:` line to rewrite\");\n }\n return { filePath, content, model };\n}\n","/**\n * Switch history: rolling log of the agent→model mappings each apply\n * displaced.\n *\n * Every `agent-router use <new>` writes the *current* (about-to-be-displaced)\n * mapping here — capture-shaped JSON — under a filename that encodes the\n * timestamp and the from→to transition. `agent-router back` walks this\n * directory's names to compute multi-step reverts.\n *\n * Why filenames carry the metadata (vs. a separate index.json):\n * - `ls -t history/` already gives chronological order.\n * - One-file-per-event means concurrent CLIs can't corrupt an index.\n * - The user can delete history entries manually if they care to.\n */\n\nimport { mkdir, readdir, unlink } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { atomicWriteFile } from \"./atomic-write.js\";\nimport { timestampStamp } from \"./backups.js\";\nimport { IOError } from \"./errors.js\";\n\nexport interface HistoryEntry {\n /** Filename without `.json` extension. Sortable: lex order = chronological. */\n readonly id: string;\n /** Absolute path to the JSON content file. */\n readonly path: string;\n /** Timestamp parsed from filename. */\n readonly timestamp: string;\n /** Stack name that *was* active when this snapshot was taken. May be a sentinel. */\n readonly fromStack: string;\n /** Stack name being switched *to* when this snapshot was taken. */\n readonly toStack: string;\n}\n\n/**\n * Filename format: `<isoStamp>__<from>-to-<to>.json`.\n *\n * `<isoStamp>` uses dashes instead of `:`/`.` so it's path-safe (see\n * `backups.timestampStamp`). `__` separates timestamp from labels so the\n * parser can find it unambiguously even if stack names contain `-`.\n */\nfunction filename(fromStack: string, toStack: string, when: Date = new Date()): string {\n const stamp = timestampStamp(when);\n // Stack names are user-supplied. Sanitize lightly so filenames stay\n // reasonable. We allow alphanumerics, dash, underscore, dot, and\n // parentheses (for the `(none)` label). Everything else collapses to `_`.\n const safe = (s: string) => s.replace(/[^A-Za-z0-9._()-]/g, \"_\");\n return `${stamp}__${safe(fromStack)}-to-${safe(toStack)}.json`;\n}\n\n/** Parse a filename back into structured metadata. Returns null on mismatch. */\nexport function parseHistoryFilename(name: string): Omit<HistoryEntry, \"path\"> | null {\n if (!name.endsWith(\".json\")) return null;\n const stem = name.slice(0, -\".json\".length);\n const sep = stem.indexOf(\"__\");\n if (sep < 0) return null;\n const stamp = stem.slice(0, sep);\n const rest = stem.slice(sep + 2);\n const toIdx = rest.lastIndexOf(\"-to-\");\n if (toIdx < 0) return null;\n return {\n id: stem,\n timestamp: stamp,\n fromStack: rest.slice(0, toIdx),\n toStack: rest.slice(toIdx + \"-to-\".length),\n };\n}\n\n/**\n * Append a snapshot of `displacedContent` to history under a deterministic\n * filename derived from `fromStack`/`toStack` and the current time.\n *\n * @returns The id (filename stem) of the created entry.\n */\nexport async function appendHistory(\n historyDir: string,\n fromStack: string,\n toStack: string,\n displacedContent: string,\n): Promise<string> {\n await mkdir(historyDir, { recursive: true });\n const name = filename(fromStack, toStack);\n await atomicWriteFile(path.join(historyDir, name), displacedContent);\n return name.slice(0, -\".json\".length);\n}\n\n/** List history entries newest-first. Returns empty array if dir missing. */\nexport async function listHistory(historyDir: string): Promise<HistoryEntry[]> {\n let names: string[];\n try {\n names = await readdir(historyDir);\n } catch (cause) {\n if ((cause as NodeJS.ErrnoException).code === \"ENOENT\") return [];\n throw new IOError(`Failed to read history dir: ${(cause as Error).message}`, cause);\n }\n const entries: HistoryEntry[] = [];\n for (const n of names) {\n const parsed = parseHistoryFilename(n);\n if (parsed) entries.push({ ...parsed, path: path.join(historyDir, n) });\n }\n // Sort descending by timestamp (which is in lex-sortable ISO form).\n entries.sort((a, b) => (a.timestamp < b.timestamp ? 1 : a.timestamp > b.timestamp ? -1 : 0));\n return entries;\n}\n\n/**\n * Trim history to at most `keep` entries, deleting oldest files first.\n * Default 20 mirrors the plan. Returns the ids of deleted entries.\n */\nexport async function trimHistory(historyDir: string, keep = 20): Promise<string[]> {\n const all = await listHistory(historyDir);\n if (all.length <= keep) return [];\n const toDelete = all.slice(keep);\n const deleted: string[] = [];\n for (const e of toDelete) {\n try {\n await unlink(e.path);\n deleted.push(e.id);\n } catch (cause) {\n // Best-effort: log silently and keep going. A leftover history file is\n // harmless beyond a bit of disk; failing the whole switch over a stale\n // unlink would be worse UX.\n void cause;\n }\n }\n return deleted;\n}\n","/**\n * Read and edit `~/.config/opencode/opencode.json` and `tui.json`.\n *\n * `agent-router init` is the only place that writes these files. The mutation\n * is deliberately minimal: ensure each `plugin` array contains\n * `@dylanrussell/agent-router@latest` (and drop the legacy\n * `@dylanrussell/omo-router` entry when present).\n *\n * All ops are idempotent: re-running `init` on an already-configured file\n * leaves it unchanged. We always back up before writing — see `backups.ts`.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { atomicWriteJson } from \"./atomic-write.js\";\nimport { IOError, ValidationError } from \"./errors.js\";\nimport { type OpencodeJson, OpencodeJsonSchema } from \"./schema.js\";\n\nexport const PLUGIN_NPM_NAME = \"@dylanrussell/agent-router\";\n/** What we add to plugin[] arrays — name@latest for auto-updates. */\nexport const PLUGIN_REGISTRY_ENTRY = `${PLUGIN_NPM_NAME}@latest`;\n/** The predecessor package `init` removes when found. */\nexport const LEGACY_PLUGIN_NPM_NAME = \"@dylanrussell/omo-router\";\n\n/** Read opencode.json. Returns null if absent. Throws on parse/schema error. */\nexport async function readOpencodeJson(opencodeJsonPath: string): Promise<OpencodeJson | null> {\n return readPluginConfigFile(opencodeJsonPath, \"opencode.json\");\n}\n\n/**\n * Read tui.json — opencode's TUI config. Same loose schema as opencode.json;\n * the TUI (opencode >= 1.17) loads its plugins from THIS file's `plugin`\n * array, so agent-router's sidebar half must be registered here.\n */\nexport async function readTuiJson(tuiJsonPath: string): Promise<OpencodeJson | null> {\n return readPluginConfigFile(tuiJsonPath, \"tui.json\");\n}\n\nasync function readPluginConfigFile(filePath: string, label: string): Promise<OpencodeJson | null> {\n if (!existsSync(filePath)) return null;\n let raw: string;\n try {\n raw = await readFile(filePath, \"utf8\");\n } catch (cause) {\n throw new IOError(`Failed to read ${filePath}: ${(cause as Error).message}`, cause);\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new ValidationError(`${label} is not valid JSON: ${(cause as Error).message}`, filePath);\n }\n const result = OpencodeJsonSchema.safeParse(parsed);\n if (!result.success) {\n throw new ValidationError(\n `${label} failed validation: ${result.error.issues.map((i) => i.message).join(\"; \")}`,\n filePath,\n result.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`),\n );\n }\n return result.data;\n}\n\nexport interface EnsurePluginEntryResult {\n /** True if we actually had to add the entry. */\n readonly added: boolean;\n /** The plugin array after the operation (always defined). */\n readonly plugin: string[];\n}\n\n/**\n * Pure transform: ensure `config.plugin` contains `entry`.\n * Returns the new config (does NOT write). Idempotent.\n */\nexport function ensurePluginEntry(\n config: OpencodeJson,\n entry: string = PLUGIN_REGISTRY_ENTRY,\n): { config: OpencodeJson; result: EnsurePluginEntryResult } {\n const existing = config.plugin ?? [];\n const already = existing.some(\n (p) => p === entry || stripVersionTag(p) === stripVersionTag(entry),\n );\n if (already) {\n return { config, result: { added: false, plugin: existing } };\n }\n const next = [...existing, entry];\n return {\n config: { ...config, plugin: next },\n result: { added: true, plugin: next },\n };\n}\n\nexport interface RemovePluginEntryResult {\n /** The entries actually removed. */\n readonly removed: ReadonlyArray<string>;\n readonly plugin: string[];\n}\n\n/**\n * Pure transform: remove every plugin entry whose package name (version tag\n * stripped) equals `npmName`. Idempotent.\n */\nexport function removePluginEntry(\n config: OpencodeJson,\n npmName: string = LEGACY_PLUGIN_NPM_NAME,\n): { config: OpencodeJson; result: RemovePluginEntryResult } {\n const existing = config.plugin ?? [];\n const removed = existing.filter((p) => stripVersionTag(p) === npmName);\n if (removed.length === 0) {\n return { config, result: { removed: [], plugin: existing } };\n }\n const next = existing.filter((p) => stripVersionTag(p) !== npmName);\n return {\n config: { ...config, plugin: next },\n result: { removed, plugin: next },\n };\n}\n\n/** Strip `@version` from a plugin entry. `foo@1.2.3` → `foo`; scoped names handled. */\nfunction stripVersionTag(entry: string): string {\n // For scoped packages `@org/name@ver`, the `@` we want is the LAST one.\n // For unscoped `name@ver`, also the last @.\n const lastAt = entry.lastIndexOf(\"@\");\n // If `lastAt` is 0 it's the leading `@` of a scoped name with no version.\n if (lastAt <= 0) return entry;\n return entry.slice(0, lastAt);\n}\n\n/** Write opencode.json. Caller is responsible for backing up first. */\nexport async function writeOpencodeJson(\n opencodeJsonPath: string,\n config: OpencodeJson,\n): Promise<void> {\n await atomicWriteJson(opencodeJsonPath, config);\n}\n\nconst TUI_JSON_SCHEMA_URL = \"https://opencode.ai/config.json\";\n\n/**\n * Ensure tui.json exists and lists `entry` in its `plugin` array so the TUI\n * half of agent-router loads (and drop the legacy omo-router entry). Creates\n * the file when absent. Idempotent. Returns whether an entry was added\n * (callers back up before calling when the file already exists).\n */\nexport async function ensureTuiJsonPluginEntry(\n tuiJsonPath: string,\n entry: string = PLUGIN_REGISTRY_ENTRY,\n): Promise<EnsurePluginEntryResult> {\n const existing = (await readTuiJson(tuiJsonPath)) ?? { $schema: TUI_JSON_SCHEMA_URL };\n const removed = removePluginEntry(existing);\n const { config, result } = ensurePluginEntry(removed.config, entry);\n if (result.added || removed.result.removed.length > 0) {\n await atomicWriteJson(tuiJsonPath, config);\n }\n return result;\n}\n","/**\n * The orchestration layer. Composes paths + state + history + validator +\n * frontmatter I/O into the operations the CLI and plugin tools call.\n *\n * Public surface:\n * - `listStacks` list available stack names\n * - `readStack` parse a stack file\n * - `getActiveStackName` read state.json\n * - `applyStack` the big one: validate + preflight + history + rewrite frontmatter + update state\n * - `back` compute target from state.previousActive and call applyStack\n * - `captureStack` read current frontmatter models into a new stack\n * - `removeStack` / `importStack` / `exportStack`\n *\n * Every write goes through `atomicWriteFile` / `atomicWriteJson` so concurrent\n * readers see consistent state, and symlinked agent files (dotfile setups)\n * survive intact.\n */\n\nimport { existsSync } from \"node:fs\";\nimport { copyFile, mkdir, readFile, readdir, unlink } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { atomicWriteFile, atomicWriteJson } from \"./atomic-write.js\";\nimport {\n IOError,\n type RouterError,\n StackNotFoundError,\n UserError,\n ValidationError,\n} from \"./errors.js\";\nimport { readAgentFileStrict, readAgentModels, setFrontmatterModel } from \"./frontmatter.js\";\nimport { appendHistory, listHistory, trimHistory } from \"./history.js\";\nimport type { RouterPaths } from \"./paths.js\";\nimport { type StackFile, StackFileSchema } from \"./schema.js\";\nimport { readState, writeState } from \"./state.js\";\nimport { type ValidateOptions, validateStackOrThrow } from \"./validator.js\";\n\n/* ------------------------------------------------------------------------- *\n * read-only helpers *\n * ------------------------------------------------------------------------- */\n\nexport async function listStacks(paths: RouterPaths): Promise<string[]> {\n if (!existsSync(paths.stacksDir)) return [];\n let names: string[];\n try {\n names = await readdir(paths.stacksDir);\n } catch (cause) {\n throw new IOError(`Failed to read stacks dir: ${(cause as Error).message}`, cause);\n }\n return names\n .filter((n) => n.endsWith(\".json\"))\n .map((n) => n.slice(0, -\".json\".length))\n .sort();\n}\n\nexport function stackPath(paths: RouterPaths, name: string): string {\n return path.join(paths.stacksDir, `${name}.json`);\n}\n\n/** Read and validate a stack file. Throws `StackNotFoundError` or `ValidationError`. */\nexport async function readStack(paths: RouterPaths, name: string): Promise<StackFile> {\n const filePath = stackPath(paths, name);\n if (!existsSync(filePath)) {\n throw new StackNotFoundError(name, await listStacks(paths));\n }\n const raw = await readFile(filePath, \"utf8\").catch((cause: Error) => {\n throw new IOError(`Failed to read ${filePath}: ${cause.message}`, cause);\n });\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (cause) {\n throw new ValidationError(\n `Stack \"${name}\" is not valid JSON: ${(cause as Error).message}`,\n filePath,\n );\n }\n const result = StackFileSchema.safeParse(parsed);\n if (!result.success) {\n throw new ValidationError(\n `Stack \"${name}\" failed validation: ${result.error.issues.map((i) => i.message).join(\"; \")}`,\n filePath,\n result.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`),\n );\n }\n return result.data;\n}\n\n/** Raw file contents (string) — used when we want to forward bytes verbatim. */\nexport async function readStackRaw(paths: RouterPaths, name: string): Promise<string> {\n const filePath = stackPath(paths, name);\n if (!existsSync(filePath)) {\n throw new StackNotFoundError(name, await listStacks(paths));\n }\n return readFile(filePath, \"utf8\");\n}\n\nexport async function getActiveStackName(paths: RouterPaths): Promise<string | null> {\n const state = await readState(paths.statePath);\n return state?.active ?? null;\n}\n\n/* ------------------------------------------------------------------------- *\n * applyStack — the central operation *\n * ------------------------------------------------------------------------- */\n\nexport interface ApplyOptions {\n /** Default true. Set false to skip the pre-apply model-validation gate. */\n readonly validate?: boolean;\n /** Default false. When true, apply even if validation finds missing models. */\n readonly forceInvalid?: boolean;\n /** Plumbed to validator for tests / mocks. */\n readonly validateOptions?: ValidateOptions;\n}\n\nexport interface ApplyResult {\n readonly previous: string | null;\n readonly current: string;\n /** Agents whose frontmatter model actually changed. */\n readonly changed: ReadonlyArray<string>;\n readonly historyId: string;\n readonly restartRequired: true;\n}\n\n/**\n * Apply stack `name` to the agent files. The algorithm:\n *\n * 1. Read + schema-validate the target stack (throws if missing).\n * 2. Validate the stack's model IDs against `opencode models` (unless\n * --no-validate).\n * 3. STRICT PRE-FLIGHT: read every agent file the stack references. Any\n * missing file or missing `model:` line aborts BEFORE any write — the\n * suite is never left half-switched.\n * 4. Append the displaced mapping (current models of ALL agent files, as a\n * capture-shaped JSON) to history.\n * 5. Atomic-write each agent file whose model differs from the target.\n * 6. Update state.json.\n * 7. Trim history to 20.\n */\nexport async function applyStack(\n paths: RouterPaths,\n name: string,\n options: ApplyOptions = {},\n): Promise<ApplyResult> {\n const validate = options.validate ?? true;\n const forceInvalid = options.forceInvalid ?? false;\n\n const target = await readStack(paths, name);\n\n if (validate && !forceInvalid) {\n await validateStackOrThrow(name, target, options.validateOptions);\n }\n\n const pending: Array<{ agent: string; filePath: string; next: string | null }> = [];\n for (const [agent, entry] of Object.entries(target.agents)) {\n const { filePath, content, model } = await readAgentFileStrict(paths.agentsDir, agent);\n pending.push({\n agent,\n filePath,\n next: model === entry.model ? null : setFrontmatterModel(content, entry.model),\n });\n }\n\n const prevState = await readState(paths.statePath);\n const prevActive = prevState?.active ?? null;\n\n const displaced = { agents: modelsToStackAgents(await readAgentModels(paths.agentsDir)) };\n const historyId = await appendHistory(\n paths.historyDir,\n prevActive ?? \"(none)\",\n name,\n `${JSON.stringify(displaced, null, 2)}\\n`,\n );\n\n const changed: string[] = [];\n for (const p of pending) {\n if (p.next === null) continue;\n await atomicWriteFile(p.filePath, p.next);\n changed.push(p.agent);\n }\n\n await writeState(paths.statePath, {\n version: 1,\n active: name,\n previousActive: prevActive,\n lastSwitchedAt: new Date().toISOString(),\n });\n\n await trimHistory(paths.historyDir).catch(() => {});\n\n return {\n previous: prevActive,\n current: name,\n changed,\n historyId,\n restartRequired: true,\n };\n}\n\nfunction modelsToStackAgents(models: Record<string, string>): Record<string, { model: string }> {\n const out: Record<string, { model: string }> = {};\n for (const k of Object.keys(models).sort()) {\n const model = models[k];\n if (model !== undefined) out[k] = { model };\n }\n return out;\n}\n\n/* ------------------------------------------------------------------------- *\n * back *\n * ------------------------------------------------------------------------- */\n\n/**\n * Undo last N switches (default 1). Implemented in terms of `applyStack` so\n * each step records a fresh history entry — invariants stay consistent.\n *\n * For N=1 we simply target `state.previousActive`. For N>1 we walk history\n * backward to find the n-th previous fromStack (best-effort: history can\n * truncate, in which case we stop at whatever we have).\n */\nexport async function back(\n paths: RouterPaths,\n n = 1,\n options: ApplyOptions = {},\n): Promise<ApplyResult> {\n if (n < 1) throw new UserError(\"`back -n` must be at least 1.\");\n const state = await readState(paths.statePath);\n if (!state || !state.previousActive) {\n throw new UserError(\"No previous stack to revert to.\");\n }\n if (n === 1) return applyStack(paths, state.previousActive, options);\n const entries = await listHistory(paths.historyDir);\n if (entries.length < n) {\n throw new UserError(\n `Cannot go back ${n} steps; only ${entries.length} switch${entries.length === 1 ? \"\" : \"es\"} in history.`,\n );\n }\n // entries[0] is the newest = the snapshot taken right before the LAST switch.\n // entries[n-1].fromStack is the stack that was active before the n-th-most-recent switch.\n const target = entries[n - 1]?.fromStack;\n if (!target || target === \"(none)\") {\n throw new UserError(`Cannot go back ${n} steps to a non-stack target (\"${target}\").`);\n }\n return applyStack(paths, target, options);\n}\n\n/* ------------------------------------------------------------------------- *\n * capture / remove / import / export *\n * ------------------------------------------------------------------------- */\n\nconst STACK_NAME_RE = /^[A-Za-z0-9._-]+$/;\n\nfunction assertValidStackName(name: string): void {\n if (!STACK_NAME_RE.test(name)) {\n throw new UserError(\n `Stack name \"${name}\" contains invalid characters. Allowed: A-Z a-z 0-9 . _ -`,\n );\n }\n}\n\nexport interface CaptureOptions {\n /** When true, overwrite an existing stack of the same name. */\n readonly force?: boolean;\n}\n\nexport interface CaptureResult {\n readonly name: string;\n readonly path: string;\n /** Number of agents captured. */\n readonly agents: number;\n}\n\n/**\n * Snapshot the current frontmatter models of every agent file into a new\n * stack. This replaces both the old snapshot-back concept and seed stacks —\n * your real, working setup is always one `capture` away from being a stack.\n */\nexport async function captureStack(\n paths: RouterPaths,\n name: string,\n options: CaptureOptions = {},\n): Promise<CaptureResult> {\n assertValidStackName(name);\n const dest = stackPath(paths, name);\n if (existsSync(dest) && !options.force) {\n throw new UserError(`Stack \"${name}\" already exists. Use --force to overwrite.`);\n }\n const models = await readAgentModels(paths.agentsDir);\n const agents = modelsToStackAgents(models);\n if (Object.keys(agents).length === 0) {\n throw new UserError(\n `No agent .md files with a frontmatter \\`model:\\` line found in ${paths.agentsDir}.`,\n );\n }\n await mkdir(paths.stacksDir, { recursive: true });\n await atomicWriteJson(dest, { agents });\n return { name, path: dest, agents: Object.keys(agents).length };\n}\n\nexport async function removeStack(\n paths: RouterPaths,\n name: string,\n options: { readonly force?: boolean } = {},\n): Promise<void> {\n const filePath = stackPath(paths, name);\n if (!existsSync(filePath)) {\n throw new StackNotFoundError(name, await listStacks(paths));\n }\n const active = await getActiveStackName(paths);\n if (active === name && !options.force) {\n throw new UserError(`\"${name}\" is the active stack. Use --force to remove anyway.`);\n }\n await unlink(filePath);\n}\n\nexport async function importStack(\n paths: RouterPaths,\n name: string,\n fromFile: string,\n options: { readonly force?: boolean } = {},\n): Promise<void> {\n assertValidStackName(name);\n const dest = stackPath(paths, name);\n if (existsSync(dest) && !options.force) {\n throw new UserError(`Stack \"${name}\" already exists. Use --force to overwrite.`);\n }\n if (!existsSync(fromFile)) {\n throw new UserError(`Import file does not exist: ${fromFile}`);\n }\n await mkdir(paths.stacksDir, { recursive: true });\n await copyFile(fromFile, dest);\n}\n\nexport async function exportStack(paths: RouterPaths, name: string, toFile: string): Promise<void> {\n const filePath = stackPath(paths, name);\n if (!existsSync(filePath)) {\n throw new StackNotFoundError(name, await listStacks(paths));\n }\n await mkdir(path.dirname(toFile), { recursive: true });\n await copyFile(filePath, toFile);\n}\n\n/** Helper used by callers to test for any of our typed errors uniformly. */\nexport function isRouterError(e: unknown): e is RouterError {\n return (\n e instanceof Error &&\n typeof (e as { name?: string }).name === \"string\" &&\n /Error$/.test((e as Error).name)\n );\n}\n","/**\n * Validate that every model ID referenced by a stack file is reachable\n * through the user's current opencode auth.\n *\n * Strategy: shell out to `opencode models`, capture the line-per-id list,\n * compare to the IDs found in the stack. The model catalogue is\n * auth-state-dependent — a user without an Anthropic key won't see\n * `anthropic/...` IDs even though they exist on the registry — so this gives\n * us a real \"will this work\" answer instead of a registry sniff.\n *\n * For tests, callers can inject a fake `runOpencodeModels` that returns\n * canned text. See `tests/fixtures/opencode-models-output.txt`.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { ModelValidationError } from \"./errors.js\";\nimport type { StackFile } from \"./schema.js\";\n\nconst execFileAsync = promisify(execFile);\n\nexport interface ValidateOptions {\n /**\n * Override the model lister. Defaults to running `opencode models` via\n * `execFile`. Tests pass a static string.\n */\n readonly runOpencodeModels?: () => Promise<string>;\n}\n\nexport interface MissingModel {\n /** Dotted location inside the stack — e.g., `agents.oracle.model`. */\n readonly path: string;\n /** The unreachable model ID. */\n readonly modelId: string;\n}\n\nexport interface ValidateResult {\n readonly ok: boolean;\n readonly checked: number;\n readonly missing: ReadonlyArray<MissingModel>;\n /** Models reachable through current auth, parsed from `opencode models`. */\n readonly available: ReadonlySet<string>;\n}\n\nconst DEFAULT_RUNNER = async (): Promise<string> => {\n const { stdout } = await execFileAsync(\"opencode\", [\"models\"], {\n encoding: \"utf8\",\n timeout: 30_000,\n maxBuffer: 4 * 1024 * 1024,\n });\n return stdout;\n};\n\n/** Parse `opencode models` stdout into a Set of model IDs. */\nexport function parseModelList(stdout: string): Set<string> {\n const out = new Set<string>();\n for (const raw of stdout.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (!line) continue;\n if (line.startsWith(\"#\")) continue;\n out.add(line);\n }\n return out;\n}\n\n/** Collect every `(path, modelId)` pair from a stack's `agents` record. */\nexport function collectModelRefs(stack: StackFile): Array<MissingModel> {\n const refs: Array<MissingModel> = [];\n for (const [k, v] of Object.entries(stack.agents)) {\n refs.push({ path: `agents.${k}.model`, modelId: v.model });\n }\n return refs;\n}\n\n/**\n * Validate `stack` against the current opencode model catalogue.\n *\n * Returns a structured result; never throws on validation failure (for use\n * in CLI/plugin contexts that want to format the output themselves).\n */\nexport async function validateStack(\n stack: StackFile,\n options: ValidateOptions = {},\n): Promise<ValidateResult> {\n const runner = options.runOpencodeModels ?? DEFAULT_RUNNER;\n const stdout = await runner();\n const available = parseModelList(stdout);\n const refs = collectModelRefs(stack);\n const missing = refs.filter((r) => !available.has(r.modelId));\n return { ok: missing.length === 0, checked: refs.length, missing, available };\n}\n\n/**\n * Convenience: validate and throw `ModelValidationError` on failure. Used by\n * the pre-apply gate in `stack-manager.applyStack`.\n */\nexport async function validateStackOrThrow(\n stackName: string,\n stack: StackFile,\n options: ValidateOptions = {},\n): Promise<ValidateResult> {\n const result = await validateStack(stack, options);\n if (!result.ok) throw new ModelValidationError(stackName, result.missing);\n return result;\n}\n","/**\n * Hard-coded version string. Kept in sync with package.json by the publish\n * pipeline (`npm version` bumps both). We don't read package.json at runtime\n * because that would either pull a Node-specific JSON import flag or force a\n * filesystem read on plugin init in opencode's Bun runtime.\n */\nexport const VERSION = \"1.0.0\";\n"],"mappings":";;;;;;;;;;;AAAA,IAYsB,aAOT,oBAkBA,gBAsBA,iBAaA,sBAcA,SAWA;AAjGb;AAAA;AAAA;AAYO,IAAe,cAAf,cAAmC,MAAM;AAAA;AAAA,MAE9C,OAAgB,WAAmB;AAAA,MACjB,OAAe;AAAA,IACnC;AAGO,IAAM,qBAAN,cAAiC,YAAY;AAAA,MAGlD,YACkB,WACA,WAChB;AACA;AAAA,UACE,UAAU,SAAS,2BAA2B,UAAU,SAAS,UAAU,KAAK,IAAI,IAAI,QAAQ;AAAA,QAClG;AALgB;AACA;AAAA,MAKlB;AAAA,MANkB;AAAA,MACA;AAAA,MAJlB,OAAyB,WAAW;AAAA,MAClB,OAAO;AAAA,IAS3B;AAOO,IAAM,iBAAN,cAA6B,YAAY;AAAA,MAG9C,YACkB,WACA,UAChB,QACA;AACA,cAAM,UAAU,SAAS,MAAM,QAAQ,MAAM,MAAM,EAAE;AAJrC;AACA;AAAA,MAIlB;AAAA,MALkB;AAAA,MACA;AAAA,MAJlB,OAAyB,WAAW;AAAA,MAClB,OAAO;AAAA,IAQ3B;AAYO,IAAM,kBAAN,cAA8B,YAAY;AAAA,MAG/C,YACE,SACgBA,OACA,QAChB;AACA,cAAM,OAAO;AAHG,oBAAAA;AACA;AAAA,MAGlB;AAAA,MAJkB;AAAA,MACA;AAAA,MALlB,OAAyB,WAAW;AAAA,MAClB,OAAO;AAAA,IAQ3B;AAGO,IAAM,uBAAN,cAAmC,YAAY;AAAA,MAGpD,YACkB,WACA,SAChB;AACA;AAAA,UACE,UAAU,SAAS,gBAAgB,QAAQ,MAAM,wBAAwB,QAAQ,WAAW,IAAI,KAAK,GAAG;AAAA,QAC1G;AALgB;AACA;AAAA,MAKlB;AAAA,MANkB;AAAA,MACA;AAAA,MAJlB,OAAyB,WAAW;AAAA,MAClB,OAAO;AAAA,IAS3B;AAGO,IAAM,UAAN,cAAsB,YAAY;AAAA,MACvC,OAAyB,WAAW;AAAA,MAClB,OAAO;AAAA,MAChB;AAAA,MACT,YAAY,SAAiB,UAAoB;AAC/C,cAAM,OAAO;AACb,aAAK,WAAW;AAAA,MAClB;AAAA,IACF;AAGO,IAAM,YAAN,cAAwB,YAAY;AAAA,MACzC,OAAyB,WAAW;AAAA,MAClB,OAAO;AAAA,IAC3B;AAAA;AAAA;;;ACrFA,SAAS,SAAS;AAflB,IAqBa,iBAmBA,kBAaA,iBAeA,kBAeA;AAnFb;AAAA;AAAA;AAqBO,IAAM,kBAAkB,EAC5B,OAAO;AAAA,MACN,SAAS,EAAE,QAAQ,CAAC;AAAA,MACpB,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MACxB,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,MAC3C,gBAAgB,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,IAClC,CAAC,EACA,OAAO;AAYH,IAAM,mBAAmB,EAC7B,OAAO;AAAA,MACN,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,IACzB,CAAC,EACA,YAAY;AASR,IAAM,kBAAkB,EAC5B,OAAO;AAAA,MACN,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,gBAAgB;AAAA,IAC/C,CAAC,EACA,YAAY,EACZ,OAAO,CAAC,MAAM,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,GAAG;AAAA,MAC/C,SAAS;AAAA,IACX,CAAC;AAQI,IAAM,mBAAmB,EAC7B,OAAO;AAAA;AAAA,MAEN,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA;AAAA,MAEtC,WAAW,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,IACxC,CAAC,EACA,OAAO;AAQH,IAAM,qBAAqB,EAC/B,OAAO;AAAA,MACN,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,IACvC,CAAC,EACA,YAAY;AAAA;AAAA;;;ACvEf,SAAS,mBAAmB;AAC5B,SAAS,SAAAC,QAAO,UAAU,QAAQ,QAAQ,iBAAiB;AAC3D,OAAOC,WAAU;AAWjB,eAAe,mBAAmB,UAAmC;AACnE,MAAI;AACF,WAAO,MAAM,SAAS,QAAQ;AAAA,EAChC,QAAQ;AACN,QAAI;AACF,YAAM,MAAM,MAAM,SAASA,MAAK,QAAQ,QAAQ,CAAC;AACjD,aAAOA,MAAK,KAAK,KAAKA,MAAK,SAAS,QAAQ,CAAC;AAAA,IAC/C,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAUA,eAAsB,gBAAgB,UAAkB,UAAiC;AACvF,QAAMD,OAAMC,MAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AACvD,QAAM,OAAO,MAAM,mBAAmB,QAAQ;AAC9C,QAAM,MAAMA,MAAK,QAAQ,IAAI;AAE7B,QAAM,MAAMA,MAAK;AAAA,IACf;AAAA,IACA,IAAIA,MAAK,SAAS,IAAI,CAAC,UAAU,QAAQ,GAAG,IAAI,YAAY,CAAC,EAAE,SAAS,KAAK,CAAC;AAAA,EAChF;AAEA,MAAI;AACF,UAAM,UAAU,KAAK,UAAU,EAAE,UAAU,QAAQ,MAAM,IAAM,CAAC;AAChE,UAAM,OAAO,KAAK,IAAI;AAAA,EACxB,SAAS,OAAO;AACd,UAAM,OAAO,GAAG,EAAE,MAAM,MAAM;AAAA,IAG9B,CAAC;AACD,UAAM,IAAI,QAAQ,2BAA2B,QAAQ,KAAM,MAAgB,OAAO,IAAI,KAAK;AAAA,EAC7F;AACF;AAMA,eAAsB,gBAAgB,UAAkB,OAA+B;AACrF,QAAM,OAAO,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA;AAC9C,QAAM,gBAAgB,UAAU,IAAI;AACtC;AA/EA;AAAA;AAAA;AAmBA;AAAA;AAAA;;;ACnBA;AAAA;AAAA;AAAA;AAAA;AASA,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,YAAAC,iBAAgB;AASzB,eAAsB,UAAU,WAA8C;AAC5E,MAAI,CAACD,YAAW,SAAS,EAAG,QAAO;AACnC,MAAI;AACJ,MAAI;AACF,UAAM,MAAMC,UAAS,WAAW,MAAM;AAAA,EACxC,SAAS,OAAO;AACd,UAAM,IAAI,QAAQ,kBAAkB,SAAS,KAAM,MAAgB,OAAO,IAAI,KAAK;AAAA,EACrF;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,iCAAkC,MAAgB,OAAO;AAAA,MACzD;AAAA,IACF;AAAA,EACF;AACA,QAAM,SAAS,gBAAgB,UAAU,MAAM;AAC/C,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,iCAAiC,OAAO,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACrF;AAAA,MACA,OAAO,MAAM,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE;AAAA,IACpE;AAAA,EACF;AACA,SAAO,OAAO;AAChB;AAGA,eAAsB,WAAW,WAAmB,OAAiC;AACnF,QAAM,SAAS,gBAAgB,UAAU,KAAK;AAC9C,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,yCAAyC,OAAO,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MAC7F;AAAA,IACF;AAAA,EACF;AACA,QAAM,gBAAgB,WAAW,OAAO,IAAI;AAC9C;AAzDA;AAAA;AAAA;AAWA;AACA;AACA;AAAA;AAAA;;;ACQA,SAAS,iBAAiB;AAC1B,SAAS,cAAAC,aAAY,oBAAoB;AACzC,SAAS,SAAAC,cAAa;AACtB,OAAOC,WAAU;AACjB,SAAS,qBAAqB;AAC9B,SAAS,WAAW;;;ACfpB;AAHA,SAAS,kBAAkB;AAC3B,SAAS,UAAU,aAAa;AAChC,OAAO,UAAU;AAIV,SAAS,eAAe,OAAa,oBAAI,KAAK,GAAW;AAC9D,SAAO,KAAK,YAAY,EAAE,QAAQ,SAAS,GAAG;AAChD;AASA,eAAsB,YACpB,aACA,OACiB;AACjB,QAAM,QAAQ,eAAe;AAC7B,QAAM,MAAM,KAAK,KAAK,aAAa,KAAK;AACxC,QAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AAEpC,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,WAAW,CAAC,EAAG;AACpB,QAAI;AACF,YAAM,SAAS,GAAG,KAAK,KAAK,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC;AAAA,IACpD,SAAS,OAAO;AACd,YAAM,IAAI,QAAQ,0BAA0B,CAAC,KAAM,MAAgB,OAAO,IAAI,KAAK;AAAA,IACrF;AAAA,EACF;AACA,SAAO;AACT;;;ACtBA;AAJA,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,gBAAgB;AACzB,SAAS,WAAAC,gBAAe;AACxB,OAAOC,WAAU;;;ACSjB,SAAS,eAAe;AACxB,OAAOC,WAAU;AAuDV,SAAS,aAAa,UAA+B,CAAC,GAAgB;AAC3E,QAAM,MAAM,QAAQ,OAAO,QAAQ;AAEnC,QAAM,oBACJ,QAAQ,qBACRA,MAAK,KAAK,IAAI,mBAAmBA,MAAK,KAAK,QAAQ,GAAG,SAAS,GAAG,UAAU;AAE9E,QAAM,aACJ,QAAQ,cACR,IAAI,qBACJ,IAAI,mBACJA,MAAK,KAAK,mBAAmB,cAAc;AAE7C,QAAM,YACJ,QAAQ,aAAa,IAAI,2BAA2BA,MAAK,KAAK,mBAAmB,QAAQ;AAE3F,QAAM,YACJ,QAAQ,aAAa,IAAI,2BAA2BA,MAAK,KAAK,YAAY,QAAQ;AAEpF,SAAO;AAAA,IACL;AAAA,IACA,kBAAkBA,MAAK,KAAK,mBAAmB,eAAe;AAAA,IAC9D,aAAaA,MAAK,KAAK,mBAAmB,UAAU;AAAA,IACpD;AAAA,IACA,oBAAoBA,MAAK,KAAK,mBAAmB,UAAU;AAAA,IAC3D;AAAA,IACA,WAAWA,MAAK,KAAK,YAAY,YAAY;AAAA,IAC7C;AAAA,IACA,YAAYA,MAAK,KAAK,YAAY,SAAS;AAAA,EAC7C;AACF;;;AD5FA;AAEO,IAAM,mBAAmB;AAGzB,SAAS,WAAW,GAAmB;AAC5C,MAAI,MAAM,IAAK,QAAOC,SAAQ;AAC9B,MAAI,EAAE,WAAW,IAAI,EAAG,QAAOC,MAAK,KAAKD,SAAQ,GAAG,EAAE,MAAM,CAAC,CAAC;AAC9D,SAAO;AACT;AAGA,eAAsB,eAAe,YAAgD;AACnF,QAAM,aAAaC,MAAK,KAAK,YAAY,gBAAgB;AACzD,MAAI,CAACC,YAAW,UAAU,EAAG,QAAO;AAEpC,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,SAAS,YAAY,MAAM;AAAA,EACzC,SAAS,OAAO;AACd,UAAM,IAAI,QAAQ,kBAAkB,UAAU,KAAM,MAAgB,OAAO,IAAI,KAAK;AAAA,EACtF;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,+CAAgD,MAAgB,OAAO;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,iBAAiB,UAAU,MAAM;AAChD,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,+CAA+C,OAAO,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACnG;AAAA,MACA,OAAO,MAAM,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE;AAAA,IACpE;AAAA,EACF;AACA,SAAO,OAAO;AAChB;AAGA,SAAS,oBAAoB,YAAoB,GAAmB;AAClE,QAAM,WAAW,WAAW,CAAC;AAC7B,SAAOD,MAAK,WAAW,QAAQ,IAAI,WAAWA,MAAK,QAAQ,YAAY,QAAQ;AACjF;AAUA,eAAsB,uBACpB,UAA+B,CAAC,GACV;AACtB,QAAM,OAAO,aAAa,OAAO;AAEjC,QAAM,SAAS,MAAM,eAAe,KAAK,UAAU;AACnD,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,OAA4B,EAAE,GAAG,QAAQ;AAC/C,MAAI,QAAQ,cAAc,UAAa,OAAO,WAAW;AACvD,IAAC,KAAgC,YAAY;AAAA,MAC3C,KAAK;AAAA,MACL,OAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI,QAAQ,cAAc,UAAa,OAAO,WAAW;AACvD,IAAC,KAAgC,YAAY;AAAA,MAC3C,KAAK;AAAA,MACL,OAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,aAAa,IAAI;AAC1B;;;AFzEA;;;AIVA;AAHA,SAAS,cAAAE,mBAAkB;AAC3B,SAAS,YAAAC,WAAU,eAAe;AAClC,OAAOC,WAAU;AAIjB,IAAM,iBAAiB;AAGvB,IAAM,gBAAgB;AAGtB,SAAS,gBAAgB,KAAqB;AAC5C,MAAI,IAAI;AACR,QAAM,OAAO,EAAE,OAAO,QAAQ;AAC9B,MAAI,QAAQ,EAAG,KAAI,EAAE,MAAM,GAAG,IAAI;AAClC,MAAI,EAAE,KAAK;AACX,MACG,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,GAAG,KAAK,EAAE,UAAU,KACpD,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,GAAG,KAAK,EAAE,UAAU,GACrD;AACA,QAAI,EAAE,MAAM,GAAG,EAAE;AAAA,EACnB;AACA,SAAO;AACT;AAMO,SAAS,oBAAoB,SAAgC;AAClE,QAAM,KAAK,eAAe,KAAK,OAAO;AACtC,MAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,QAAM,OAAO,cAAc,KAAK,GAAG,CAAC,CAAC;AACrC,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,gBAAgB,KAAK,CAAC,KAAK,EAAE;AAC3C,SAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;AAOO,SAAS,oBAAoB,SAAiB,OAAuB;AAC1E,QAAM,KAAK,eAAe,KAAK,OAAO;AACtC,MAAI,CAAC,KAAK,CAAC,EAAG,OAAM,IAAI,MAAM,sBAAsB;AACpD,QAAM,QAAQ,GAAG,CAAC;AAClB,MAAI,CAAC,cAAc,KAAK,KAAK,EAAG,OAAM,IAAI,MAAM,iCAAiC;AAEjF,QAAM,YAAY,MAAM,QAAQ,eAAe,MAAM,UAAU,KAAK,EAAE;AAGtE,QAAM,aAAa,GAAG,CAAC,EAAE,QAAQ,KAAK;AACtC,QAAM,SAAS,GAAG,CAAC,EAAE,MAAM,GAAG,UAAU,IAAI,YAAY,GAAG,CAAC,EAAE,MAAM,aAAa,MAAM,MAAM;AAC7F,SAAO,SAAS,QAAQ,MAAM,GAAG,CAAC,EAAE,MAAM;AAC5C;AAGO,SAAS,cAAc,WAAmB,MAAsB;AACrE,SAAOA,MAAK,KAAK,WAAW,GAAG,IAAI,KAAK;AAC1C;AAGA,eAAsB,eAAe,WAAsC;AACzE,MAAI,CAACF,YAAW,SAAS,EAAG,QAAO,CAAC;AACpC,MAAI;AACJ,MAAI;AACF,YAAQ,MAAM,QAAQ,SAAS;AAAA,EACjC,SAAS,OAAO;AACd,UAAM,IAAI,QAAQ,8BAA+B,MAAgB,OAAO,IAAI,KAAK;AAAA,EACnF;AACA,SAAO,MACJ,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,EAC/B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,EACpC,KAAK;AACV;AAOA,eAAsB,gBAAgB,WAAoD;AACxF,QAAM,MAA8B,CAAC;AACrC,aAAW,QAAQ,MAAM,eAAe,SAAS,GAAG;AAClD,UAAM,WAAW,cAAc,WAAW,IAAI;AAC9C,QAAI;AACJ,QAAI;AACF,gBAAU,MAAMC,UAAS,UAAU,MAAM;AAAA,IAC3C,SAAS,OAAO;AACd,YAAM,IAAI,QAAQ,kBAAkB,QAAQ,KAAM,MAAgB,OAAO,IAAI,KAAK;AAAA,IACpF;AACA,UAAM,QAAQ,oBAAoB,OAAO;AACzC,QAAI,UAAU,KAAM,KAAI,IAAI,IAAI;AAAA,EAClC;AACA,SAAO;AACT;AAOA,eAAsB,oBACpB,WACA,MAC+D;AAC/D,QAAM,WAAW,cAAc,WAAW,IAAI;AAC9C,MAAI,CAACD,YAAW,QAAQ,GAAG;AACzB,UAAM,IAAI,eAAe,MAAM,UAAU,qBAAqB;AAAA,EAChE;AACA,MAAI;AACJ,MAAI;AACF,cAAU,MAAMC,UAAS,UAAU,MAAM;AAAA,EAC3C,SAAS,OAAO;AACd,UAAM,IAAI,QAAQ,kBAAkB,QAAQ,KAAM,MAAgB,OAAO,IAAI,KAAK;AAAA,EACpF;AACA,QAAM,QAAQ,oBAAoB,OAAO;AACzC,MAAI,UAAU,MAAM;AAClB,UAAM,IAAI,eAAe,MAAM,UAAU,yCAAyC;AAAA,EACpF;AACA,SAAO,EAAE,UAAU,SAAS,MAAM;AACpC;;;AC1HA;AAFA,SAAS,SAAAE,QAAO,WAAAC,UAAS,UAAAC,eAAc;AACvC,OAAOC,WAAU;AAGjB;AAsBA,SAAS,SAAS,WAAmB,SAAiB,OAAa,oBAAI,KAAK,GAAW;AACrF,QAAM,QAAQ,eAAe,IAAI;AAIjC,QAAM,OAAO,CAAC,MAAc,EAAE,QAAQ,sBAAsB,GAAG;AAC/D,SAAO,GAAG,KAAK,KAAK,KAAK,SAAS,CAAC,OAAO,KAAK,OAAO,CAAC;AACzD;AAGO,SAAS,qBAAqB,MAAiD;AACpF,MAAI,CAAC,KAAK,SAAS,OAAO,EAAG,QAAO;AACpC,QAAM,OAAO,KAAK,MAAM,GAAG,CAAC,QAAQ,MAAM;AAC1C,QAAM,MAAM,KAAK,QAAQ,IAAI;AAC7B,MAAI,MAAM,EAAG,QAAO;AACpB,QAAM,QAAQ,KAAK,MAAM,GAAG,GAAG;AAC/B,QAAM,OAAO,KAAK,MAAM,MAAM,CAAC;AAC/B,QAAM,QAAQ,KAAK,YAAY,MAAM;AACrC,MAAI,QAAQ,EAAG,QAAO;AACtB,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,WAAW;AAAA,IACX,WAAW,KAAK,MAAM,GAAG,KAAK;AAAA,IAC9B,SAAS,KAAK,MAAM,QAAQ,OAAO,MAAM;AAAA,EAC3C;AACF;AAQA,eAAsB,cACpB,YACA,WACA,SACA,kBACiB;AACjB,QAAMC,OAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAC3C,QAAM,OAAO,SAAS,WAAW,OAAO;AACxC,QAAM,gBAAgBC,MAAK,KAAK,YAAY,IAAI,GAAG,gBAAgB;AACnE,SAAO,KAAK,MAAM,GAAG,CAAC,QAAQ,MAAM;AACtC;AAGA,eAAsB,YAAY,YAA6C;AAC7E,MAAI;AACJ,MAAI;AACF,YAAQ,MAAMC,SAAQ,UAAU;AAAA,EAClC,SAAS,OAAO;AACd,QAAK,MAAgC,SAAS,SAAU,QAAO,CAAC;AAChE,UAAM,IAAI,QAAQ,+BAAgC,MAAgB,OAAO,IAAI,KAAK;AAAA,EACpF;AACA,QAAM,UAA0B,CAAC;AACjC,aAAW,KAAK,OAAO;AACrB,UAAM,SAAS,qBAAqB,CAAC;AACrC,QAAI,OAAQ,SAAQ,KAAK,EAAE,GAAG,QAAQ,MAAMD,MAAK,KAAK,YAAY,CAAC,EAAE,CAAC;AAAA,EACxE;AAEA,UAAQ,KAAK,CAAC,GAAG,MAAO,EAAE,YAAY,EAAE,YAAY,IAAI,EAAE,YAAY,EAAE,YAAY,KAAK,CAAE;AAC3F,SAAO;AACT;AAMA,eAAsB,YAAY,YAAoB,OAAO,IAAuB;AAClF,QAAM,MAAM,MAAM,YAAY,UAAU;AACxC,MAAI,IAAI,UAAU,KAAM,QAAO,CAAC;AAChC,QAAM,WAAW,IAAI,MAAM,IAAI;AAC/B,QAAM,UAAoB,CAAC;AAC3B,aAAW,KAAK,UAAU;AACxB,QAAI;AACF,YAAME,QAAO,EAAE,IAAI;AACnB,cAAQ,KAAK,EAAE,EAAE;AAAA,IACnB,SAAS,OAAO;AAId,WAAK;AAAA,IACP;AAAA,EACF;AACA,SAAO;AACT;;;AChHA;AACA;AACA;AAJA,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,YAAAC,iBAAgB;AAKlB,IAAM,kBAAkB;AAExB,IAAM,wBAAwB,GAAG,eAAe;AAEhD,IAAM,yBAAyB;AAGtC,eAAsB,iBAAiB,kBAAwD;AAC7F,SAAO,qBAAqB,kBAAkB,eAAe;AAC/D;AAOA,eAAsB,YAAY,aAAmD;AACnF,SAAO,qBAAqB,aAAa,UAAU;AACrD;AAEA,eAAe,qBAAqB,UAAkB,OAA6C;AACjG,MAAI,CAACD,YAAW,QAAQ,EAAG,QAAO;AAClC,MAAI;AACJ,MAAI;AACF,UAAM,MAAMC,UAAS,UAAU,MAAM;AAAA,EACvC,SAAS,OAAO;AACd,UAAM,IAAI,QAAQ,kBAAkB,QAAQ,KAAM,MAAgB,OAAO,IAAI,KAAK;AAAA,EACpF;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,OAAO;AACd,UAAM,IAAI,gBAAgB,GAAG,KAAK,uBAAwB,MAAgB,OAAO,IAAI,QAAQ;AAAA,EAC/F;AACA,QAAM,SAAS,mBAAmB,UAAU,MAAM;AAClD,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,GAAG,KAAK,uBAAuB,OAAO,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACnF;AAAA,MACA,OAAO,MAAM,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE;AAAA,IACpE;AAAA,EACF;AACA,SAAO,OAAO;AAChB;AAaO,SAAS,kBACd,QACA,QAAgB,uBAC2C;AAC3D,QAAM,WAAW,OAAO,UAAU,CAAC;AACnC,QAAM,UAAU,SAAS;AAAA,IACvB,CAAC,MAAM,MAAM,SAAS,gBAAgB,CAAC,MAAM,gBAAgB,KAAK;AAAA,EACpE;AACA,MAAI,SAAS;AACX,WAAO,EAAE,QAAQ,QAAQ,EAAE,OAAO,OAAO,QAAQ,SAAS,EAAE;AAAA,EAC9D;AACA,QAAM,OAAO,CAAC,GAAG,UAAU,KAAK;AAChC,SAAO;AAAA,IACL,QAAQ,EAAE,GAAG,QAAQ,QAAQ,KAAK;AAAA,IAClC,QAAQ,EAAE,OAAO,MAAM,QAAQ,KAAK;AAAA,EACtC;AACF;AAYO,SAAS,kBACd,QACA,UAAkB,wBACyC;AAC3D,QAAM,WAAW,OAAO,UAAU,CAAC;AACnC,QAAM,UAAU,SAAS,OAAO,CAAC,MAAM,gBAAgB,CAAC,MAAM,OAAO;AACrE,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO,EAAE,QAAQ,QAAQ,EAAE,SAAS,CAAC,GAAG,QAAQ,SAAS,EAAE;AAAA,EAC7D;AACA,QAAM,OAAO,SAAS,OAAO,CAAC,MAAM,gBAAgB,CAAC,MAAM,OAAO;AAClE,SAAO;AAAA,IACL,QAAQ,EAAE,GAAG,QAAQ,QAAQ,KAAK;AAAA,IAClC,QAAQ,EAAE,SAAS,QAAQ,KAAK;AAAA,EAClC;AACF;AAGA,SAAS,gBAAgB,OAAuB;AAG9C,QAAM,SAAS,MAAM,YAAY,GAAG;AAEpC,MAAI,UAAU,EAAG,QAAO;AACxB,SAAO,MAAM,MAAM,GAAG,MAAM;AAC9B;AAGA,eAAsB,kBACpB,kBACA,QACe;AACf,QAAM,gBAAgB,kBAAkB,MAAM;AAChD;AAEA,IAAM,sBAAsB;AAQ5B,eAAsB,yBACpB,aACA,QAAgB,uBACkB;AAClC,QAAM,WAAY,MAAM,YAAY,WAAW,KAAM,EAAE,SAAS,oBAAoB;AACpF,QAAM,UAAU,kBAAkB,QAAQ;AAC1C,QAAM,EAAE,QAAQ,OAAO,IAAI,kBAAkB,QAAQ,QAAQ,KAAK;AAClE,MAAI,OAAO,SAAS,QAAQ,OAAO,QAAQ,SAAS,GAAG;AACrD,UAAM,gBAAgB,aAAa,MAAM;AAAA,EAC3C;AACA,SAAO;AACT;;;AN5GA;;;AO1BA;AACA;AAJA,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,YAAAC,WAAU,SAAAC,QAAO,YAAAC,WAAU,WAAAC,UAAS,UAAAC,eAAc;AAC3D,OAAOC,WAAU;AAYjB;AACA;;;ACjBA;AAFA,SAAS,gBAAgB;AACzB,SAAS,iBAAiB;AAI1B,IAAM,gBAAgB,UAAU,QAAQ;AAyBxC,IAAM,iBAAiB,YAA6B;AAClD,QAAM,EAAE,OAAO,IAAI,MAAM,cAAc,YAAY,CAAC,QAAQ,GAAG;AAAA,IAC7D,UAAU;AAAA,IACV,SAAS;AAAA,IACT,WAAW,IAAI,OAAO;AAAA,EACxB,CAAC;AACD,SAAO;AACT;AAGO,SAAS,eAAe,QAA6B;AAC1D,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,OAAO,OAAO,MAAM,OAAO,GAAG;AACvC,UAAM,OAAO,IAAI,KAAK;AACtB,QAAI,CAAC,KAAM;AACX,QAAI,KAAK,WAAW,GAAG,EAAG;AAC1B,QAAI,IAAI,IAAI;AAAA,EACd;AACA,SAAO;AACT;AAGO,SAAS,iBAAiB,OAAuC;AACtE,QAAM,OAA4B,CAAC;AACnC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,MAAM,MAAM,GAAG;AACjD,SAAK,KAAK,EAAE,MAAM,UAAU,CAAC,UAAU,SAAS,EAAE,MAAM,CAAC;AAAA,EAC3D;AACA,SAAO;AACT;AAQA,eAAsB,cACpB,OACA,UAA2B,CAAC,GACH;AACzB,QAAM,SAAS,QAAQ,qBAAqB;AAC5C,QAAM,SAAS,MAAM,OAAO;AAC5B,QAAM,YAAY,eAAe,MAAM;AACvC,QAAM,OAAO,iBAAiB,KAAK;AACnC,QAAM,UAAU,KAAK,OAAO,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE,OAAO,CAAC;AAC5D,SAAO,EAAE,IAAI,QAAQ,WAAW,GAAG,SAAS,KAAK,QAAQ,SAAS,UAAU;AAC9E;AAMA,eAAsB,qBACpB,WACA,OACA,UAA2B,CAAC,GACH;AACzB,QAAM,SAAS,MAAM,cAAc,OAAO,OAAO;AACjD,MAAI,CAAC,OAAO,GAAI,OAAM,IAAI,qBAAqB,WAAW,OAAO,OAAO;AACxE,SAAO;AACT;;;ADhEA,eAAsB,WAAW,OAAuC;AACtE,MAAI,CAACC,YAAW,MAAM,SAAS,EAAG,QAAO,CAAC;AAC1C,MAAI;AACJ,MAAI;AACF,YAAQ,MAAMC,SAAQ,MAAM,SAAS;AAAA,EACvC,SAAS,OAAO;AACd,UAAM,IAAI,QAAQ,8BAA+B,MAAgB,OAAO,IAAI,KAAK;AAAA,EACnF;AACA,SAAO,MACJ,OAAO,CAAC,MAAM,EAAE,SAAS,OAAO,CAAC,EACjC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,QAAQ,MAAM,CAAC,EACtC,KAAK;AACV;AAEO,SAAS,UAAU,OAAoB,MAAsB;AAClE,SAAOC,MAAK,KAAK,MAAM,WAAW,GAAG,IAAI,OAAO;AAClD;AAGA,eAAsB,UAAU,OAAoB,MAAkC;AACpF,QAAM,WAAW,UAAU,OAAO,IAAI;AACtC,MAAI,CAACF,YAAW,QAAQ,GAAG;AACzB,UAAM,IAAI,mBAAmB,MAAM,MAAM,WAAW,KAAK,CAAC;AAAA,EAC5D;AACA,QAAM,MAAM,MAAMG,UAAS,UAAU,MAAM,EAAE,MAAM,CAAC,UAAiB;AACnE,UAAM,IAAI,QAAQ,kBAAkB,QAAQ,KAAK,MAAM,OAAO,IAAI,KAAK;AAAA,EACzE,CAAC;AACD,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,OAAO;AACd,UAAM,IAAI;AAAA,MACR,UAAU,IAAI,wBAAyB,MAAgB,OAAO;AAAA,MAC9D;AAAA,IACF;AAAA,EACF;AACA,QAAM,SAAS,gBAAgB,UAAU,MAAM;AAC/C,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI;AAAA,MACR,UAAU,IAAI,wBAAwB,OAAO,MAAM,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MAC1F;AAAA,MACA,OAAO,MAAM,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE;AAAA,IACpE;AAAA,EACF;AACA,SAAO,OAAO;AAChB;AAGA,eAAsB,aAAa,OAAoB,MAA+B;AACpF,QAAM,WAAW,UAAU,OAAO,IAAI;AACtC,MAAI,CAACH,YAAW,QAAQ,GAAG;AACzB,UAAM,IAAI,mBAAmB,MAAM,MAAM,WAAW,KAAK,CAAC;AAAA,EAC5D;AACA,SAAOG,UAAS,UAAU,MAAM;AAClC;AAEA,eAAsB,mBAAmB,OAA4C;AACnF,QAAM,QAAQ,MAAM,UAAU,MAAM,SAAS;AAC7C,SAAO,OAAO,UAAU;AAC1B;AAuCA,eAAsB,WACpB,OACA,MACA,UAAwB,CAAC,GACH;AACtB,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,eAAe,QAAQ,gBAAgB;AAE7C,QAAM,SAAS,MAAM,UAAU,OAAO,IAAI;AAE1C,MAAI,YAAY,CAAC,cAAc;AAC7B,UAAM,qBAAqB,MAAM,QAAQ,QAAQ,eAAe;AAAA,EAClE;AAEA,QAAM,UAA2E,CAAC;AAClF,aAAW,CAAC,OAAO,KAAK,KAAK,OAAO,QAAQ,OAAO,MAAM,GAAG;AAC1D,UAAM,EAAE,UAAU,SAAS,MAAM,IAAI,MAAM,oBAAoB,MAAM,WAAW,KAAK;AACrF,YAAQ,KAAK;AAAA,MACX;AAAA,MACA;AAAA,MACA,MAAM,UAAU,MAAM,QAAQ,OAAO,oBAAoB,SAAS,MAAM,KAAK;AAAA,IAC/E,CAAC;AAAA,EACH;AAEA,QAAM,YAAY,MAAM,UAAU,MAAM,SAAS;AACjD,QAAM,aAAa,WAAW,UAAU;AAExC,QAAM,YAAY,EAAE,QAAQ,oBAAoB,MAAM,gBAAgB,MAAM,SAAS,CAAC,EAAE;AACxF,QAAM,YAAY,MAAM;AAAA,IACtB,MAAM;AAAA,IACN,cAAc;AAAA,IACd;AAAA,IACA,GAAG,KAAK,UAAU,WAAW,MAAM,CAAC,CAAC;AAAA;AAAA,EACvC;AAEA,QAAM,UAAoB,CAAC;AAC3B,aAAW,KAAK,SAAS;AACvB,QAAI,EAAE,SAAS,KAAM;AACrB,UAAM,gBAAgB,EAAE,UAAU,EAAE,IAAI;AACxC,YAAQ,KAAK,EAAE,KAAK;AAAA,EACtB;AAEA,QAAM,WAAW,MAAM,WAAW;AAAA,IAChC,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,gBAAgB;AAAA,IAChB,iBAAgB,oBAAI,KAAK,GAAE,YAAY;AAAA,EACzC,CAAC;AAED,QAAM,YAAY,MAAM,UAAU,EAAE,MAAM,MAAM;AAAA,EAAC,CAAC;AAElD,SAAO;AAAA,IACL,UAAU;AAAA,IACV,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA,iBAAiB;AAAA,EACnB;AACF;AAEA,SAAS,oBAAoB,QAAmE;AAC9F,QAAM,MAAyC,CAAC;AAChD,aAAW,KAAK,OAAO,KAAK,MAAM,EAAE,KAAK,GAAG;AAC1C,UAAM,QAAQ,OAAO,CAAC;AACtB,QAAI,UAAU,OAAW,KAAI,CAAC,IAAI,EAAE,MAAM;AAAA,EAC5C;AACA,SAAO;AACT;AAcA,eAAsB,KACpB,OACA,IAAI,GACJ,UAAwB,CAAC,GACH;AACtB,MAAI,IAAI,EAAG,OAAM,IAAI,UAAU,+BAA+B;AAC9D,QAAM,QAAQ,MAAM,UAAU,MAAM,SAAS;AAC7C,MAAI,CAAC,SAAS,CAAC,MAAM,gBAAgB;AACnC,UAAM,IAAI,UAAU,iCAAiC;AAAA,EACvD;AACA,MAAI,MAAM,EAAG,QAAO,WAAW,OAAO,MAAM,gBAAgB,OAAO;AACnE,QAAM,UAAU,MAAM,YAAY,MAAM,UAAU;AAClD,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,IAAI;AAAA,MACR,kBAAkB,CAAC,gBAAgB,QAAQ,MAAM,UAAU,QAAQ,WAAW,IAAI,KAAK,IAAI;AAAA,IAC7F;AAAA,EACF;AAGA,QAAM,SAAS,QAAQ,IAAI,CAAC,GAAG;AAC/B,MAAI,CAAC,UAAU,WAAW,UAAU;AAClC,UAAM,IAAI,UAAU,kBAAkB,CAAC,kCAAkC,MAAM,KAAK;AAAA,EACtF;AACA,SAAO,WAAW,OAAO,QAAQ,OAAO;AAC1C;AAMA,IAAM,gBAAgB;AAEtB,SAAS,qBAAqB,MAAoB;AAChD,MAAI,CAAC,cAAc,KAAK,IAAI,GAAG;AAC7B,UAAM,IAAI;AAAA,MACR,eAAe,IAAI;AAAA,IACrB;AAAA,EACF;AACF;AAmBA,eAAsB,aACpB,OACA,MACA,UAA0B,CAAC,GACH;AACxB,uBAAqB,IAAI;AACzB,QAAM,OAAO,UAAU,OAAO,IAAI;AAClC,MAAIH,YAAW,IAAI,KAAK,CAAC,QAAQ,OAAO;AACtC,UAAM,IAAI,UAAU,UAAU,IAAI,6CAA6C;AAAA,EACjF;AACA,QAAM,SAAS,MAAM,gBAAgB,MAAM,SAAS;AACpD,QAAM,SAAS,oBAAoB,MAAM;AACzC,MAAI,OAAO,KAAK,MAAM,EAAE,WAAW,GAAG;AACpC,UAAM,IAAI;AAAA,MACR,kEAAkE,MAAM,SAAS;AAAA,IACnF;AAAA,EACF;AACA,QAAMI,OAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAChD,QAAM,gBAAgB,MAAM,EAAE,OAAO,CAAC;AACtC,SAAO,EAAE,MAAM,MAAM,MAAM,QAAQ,OAAO,KAAK,MAAM,EAAE,OAAO;AAChE;AAEA,eAAsB,YACpB,OACA,MACA,UAAwC,CAAC,GAC1B;AACf,QAAM,WAAW,UAAU,OAAO,IAAI;AACtC,MAAI,CAACJ,YAAW,QAAQ,GAAG;AACzB,UAAM,IAAI,mBAAmB,MAAM,MAAM,WAAW,KAAK,CAAC;AAAA,EAC5D;AACA,QAAM,SAAS,MAAM,mBAAmB,KAAK;AAC7C,MAAI,WAAW,QAAQ,CAAC,QAAQ,OAAO;AACrC,UAAM,IAAI,UAAU,IAAI,IAAI,sDAAsD;AAAA,EACpF;AACA,QAAMK,QAAO,QAAQ;AACvB;AAEA,eAAsB,YACpB,OACA,MACA,UACA,UAAwC,CAAC,GAC1B;AACf,uBAAqB,IAAI;AACzB,QAAM,OAAO,UAAU,OAAO,IAAI;AAClC,MAAIL,YAAW,IAAI,KAAK,CAAC,QAAQ,OAAO;AACtC,UAAM,IAAI,UAAU,UAAU,IAAI,6CAA6C;AAAA,EACjF;AACA,MAAI,CAACA,YAAW,QAAQ,GAAG;AACzB,UAAM,IAAI,UAAU,+BAA+B,QAAQ,EAAE;AAAA,EAC/D;AACA,QAAMI,OAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAChD,QAAME,UAAS,UAAU,IAAI;AAC/B;AAEA,eAAsB,YAAY,OAAoB,MAAc,QAA+B;AACjG,QAAM,WAAW,UAAU,OAAO,IAAI;AACtC,MAAI,CAACN,YAAW,QAAQ,GAAG;AACzB,UAAM,IAAI,mBAAmB,MAAM,MAAM,WAAW,KAAK,CAAC;AAAA,EAC5D;AACA,QAAMI,OAAMF,MAAK,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,QAAMI,UAAS,UAAU,MAAM;AACjC;;;AE7UO,IAAM,UAAU;;;AT8DvB,IAAM,MAAM,IAAI,SAA0B,QAAQ,IAAI,GAAG,IAAI;AAC7D,IAAM,MAAM,IAAI,SAA0B,QAAQ,MAAM,GAAG,IAAI;AAM/D,SAAS,oBAAoB,MAAc,UAA2B;AACpE,SAAO,GAAG,WAAW,MAAM,GAAG,IAAI,IAAI;AACxC;AAWA,IAAM,oBAAoB;AAE1B,eAAe,QAAQ,OAAoB,MAAkC;AAC3E,QAAMC,OAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;AAChD,QAAMA,OAAM,MAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAEjD,QAAM,EAAE,WAAAC,YAAW,YAAAC,YAAW,IAAI,MAAM;AACxC,QAAM,WAAW,MAAMD,WAAU,MAAM,SAAS;AAChD,QAAM,SAAS,MAAM,WAAW,KAAK;AAErC,OAAK,CAAC,YAAY,KAAK,UAAU,OAAO,WAAW,GAAG;AACpD,QAAI;AACF,YAAM,WAAW,MAAM,aAAa,OAAO,mBAAmB,EAAE,OAAO,CAAC,CAAC,KAAK,MAAM,CAAC;AACrF,YAAMC,YAAW,MAAM,WAAW;AAAA,QAChC,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,gBAAgB;AAAA,QAChB,iBAAgB,oBAAI,KAAK,GAAE,YAAY;AAAA,MACzC,CAAC;AACD;AAAA,QACE,0BAA0B,SAAS,MAAM,SAAS,SAAS,WAAW,IAAI,KAAK,GAAG,SAAS,MAAM,SAAS,kBAAa,iBAAiB;AAAA,MAC1I;AAAA,IACF,SAAS,GAAG;AACV,UAAI,aAAa,WAAW;AAC1B,YAAI,qCAAqC,EAAE,OAAO,GAAG;AACrD;AAAA,UACE;AAAA,QACF;AAAA,MACF,OAAO;AACL,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF,WAAW,UAAU;AACnB;AAAA,MACE,oDAAoD,SAAS,MAAM;AAAA,IACrE;AAAA,EACF;AAEA,MAAI,KAAK,oBAAoB;AAC3B,QAAI,qFAAqF;AACzF,QAAI,0CAA0C,qBAAqB,GAAG;AACtE,QAAI,+CAA+C,qBAAqB,GAAG;AAC3E;AAAA,EACF;AAEA,QAAM,iBAAiB,KAAK;AAC5B,QAAM,YAAY,KAAK;AACzB;AAMA,eAAe,YAAY,OAAmC;AAC5D,QAAM,SAASC,YAAW,MAAM,WAAW;AAC3C,MAAI,QAAQ;AACV,UAAM,YAAY,MAAM,oBAAoB,CAAC,MAAM,WAAW,CAAC;AAAA,EACjE;AACA,QAAM,SAAS,MAAM,yBAAyB,MAAM,WAAW;AAC/D,MAAI,OAAO,OAAO;AAChB,QAAI,wBAAwB,qBAAqB,0CAA0C;AAC3F;AAAA,EACF;AACA,MAAI,2CAA2C;AACjD;AAMA,eAAe,iBAAiB,OAAmC;AACjE,QAAM,MAAM,MAAM,iBAAiB,MAAM,gBAAgB;AACzD,MAAI,CAAC,KAAK;AACR,QAAI,iBAAiB,MAAM,gBAAgB,wCAAmC;AAC9E;AAAA,EACF;AAEA,QAAM,UAAU,kBAAkB,GAAG;AACrC,QAAM,UAAU,kBAAkB,QAAQ,MAAM;AAEhD,MAAI,CAAC,QAAQ,OAAO,SAAS,QAAQ,OAAO,QAAQ,WAAW,GAAG;AAChE,QAAI,gDAAgD;AACpD;AAAA,EACF;AAEA,QAAM,YAAY,MAAM,oBAAoB,CAAC,MAAM,gBAAgB,CAAC;AACpE,QAAM,kBAAkB,MAAM,kBAAkB,QAAQ,MAAM;AAE9D,MAAI,QAAQ,OAAO,QAAQ,QAAQ;AACjC;AAAA,MACE,2CAA2C,QAAQ,OAAO,QAAQ,WAAW,IAAI,MAAM,KAAK,KAAK,QAAQ,OAAO,QAAQ,KAAK,IAAI,CAAC;AAAA,IACpI;AAAA,EACF;AACA,MAAI,QAAQ,OAAO,OAAO;AACxB,QAAI,wBAAwB,qBAAqB,6BAA6B;AAAA,EAChF;AACF;AAEA,eAAe,QAAQ,OAAmC;AACxD,QAAM,SAAS,MAAM,WAAW,KAAK;AACrC,MAAI,OAAO,WAAW,GAAG;AACvB,QAAI,uEAAuE;AAC3E;AAAA,EACF;AACA,QAAM,SAAS,MAAM,mBAAmB,KAAK;AAC7C,aAAW,KAAK,OAAQ,KAAI,oBAAoB,GAAG,MAAM,MAAM,CAAC;AAClE;AAEA,eAAe,UAAU,OAAmC;AAC1D,QAAM,SAAS,MAAM,mBAAmB,KAAK;AAC7C,MAAI,UAAU,QAAQ;AACxB;AAEA,eAAe,QAAQ,OAAoB,MAA6B;AACtE,QAAM,MAAM,MAAM,aAAa,OAAO,IAAI;AAC1C,MAAI;AACF,QAAI,KAAK,UAAU,KAAK,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC;AAAA,EAC9C,QAAQ;AACN,YAAQ,OAAO,MAAM,GAAG;AAAA,EAC1B;AACF;AAEA,eAAe,WAAW,OAAmC;AAC3D,QAAM,SAAS,MAAM,gBAAgB,MAAM,SAAS;AACpD,QAAM,QAAQ,OAAO,KAAK,MAAM,EAAE,KAAK;AACvC,MAAI,MAAM,WAAW,GAAG;AACtB,QAAI,iDAAiD,MAAM,SAAS,GAAG;AACvE;AAAA,EACF;AACA,QAAM,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC;AACpD,aAAW,KAAK,MAAO,KAAI,GAAG,EAAE,OAAO,KAAK,CAAC,KAAK,OAAO,CAAC,CAAC,EAAE;AAC/D;AAOA,eAAe,OAAO,OAAoB,MAAc,MAAiC;AACvF,QAAM,IAAI,MAAM,WAAW,OAAO,MAAM;AAAA,IACtC,UAAU,CAAC,KAAK;AAAA,IAChB,cAAc,KAAK,gBAAgB;AAAA,EACrC,CAAC;AACD;AAAA,IACE,aAAa,EAAE,YAAY,QAAQ,WAAM,EAAE,OAAO,KAAK,EAAE,QAAQ,MAAM,SAAS,EAAE,QAAQ,WAAW,IAAI,KAAK,GAAG;AAAA,EACnH;AACF;AAEA,eAAe,QAAQ,OAAoB,GAAW,MAAiC;AACrF,QAAM,IAAI,MAAM,KAAK,OAAO,GAAG;AAAA,IAC7B,UAAU,CAAC,KAAK;AAAA,IAChB,cAAc,KAAK,gBAAgB;AAAA,EACrC,CAAC;AACD;AAAA,IACE,aAAa,EAAE,YAAY,QAAQ,WAAM,EAAE,OAAO,KAAK,EAAE,QAAQ,MAAM,SAAS,EAAE,QAAQ,WAAW,IAAI,KAAK,GAAG;AAAA,EACnH;AACF;AAEA,eAAe,WAAW,OAAoB,MAAc,OAA+B;AACzF,QAAM,IAAI,MAAM,aAAa,OAAO,MAAM,EAAE,MAAM,CAAC;AACnD,MAAI,YAAY,EAAE,MAAM,SAAS,EAAE,WAAW,IAAI,KAAK,GAAG,WAAM,EAAE,IAAI,EAAE;AAC1E;AAEA,eAAe,WAAW,OAAoB,OAA8B;AAC1E,QAAM,UAAU,MAAM,YAAY,MAAM,UAAU;AAClD,MAAI,QAAQ,WAAW,GAAG;AACxB,QAAI,cAAc;AAClB;AAAA,EACF;AACA,aAAW,KAAK,QAAQ,MAAM,GAAG,KAAK,GAAG;AACvC,QAAI,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,WAAM,EAAE,OAAO,EAAE;AAAA,EAC9C;AACF;AAOA,eAAe,YACb,OACA,MACA,MACe;AACf,QAAM,UAAmF,CAAC;AAC1F,MAAI,KAAK,QAAQ;AACf,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,MAAM,YAAY;AAChB,cAAM,SAAS,MAAM,gBAAgB,MAAM,SAAS;AACpD,cAAM,SAAS,OAAO;AAAA,UACpB,OAAO,QAAQ,MAAM,EAAE,IAAI,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AAAA,QAC3D;AACA,eAAO,EAAE,OAAO;AAAA,MAClB;AAAA,IACF,CAAC;AAAA,EACH,WAAW,KAAK,KAAK;AACnB,eAAW,KAAK,MAAM,WAAW,KAAK,GAAG;AACvC,cAAQ,KAAK,EAAE,MAAM,GAAG,MAAM,MAAM,UAAU,OAAO,CAAC,EAAE,CAAC;AAAA,IAC3D;AAAA,EACF,OAAO;AACL,QAAI,CAAC,KAAM,OAAM,IAAI,UAAU,kDAAkD;AACjF,YAAQ,KAAK,EAAE,MAAM,MAAM,MAAM,UAAU,OAAO,IAAI,EAAE,CAAC;AAAA,EAC3D;AAEA,MAAI,aAAa;AACjB,aAAW,KAAK,SAAS;AACvB,UAAM,SAAS,gBAAgB,MAAM,MAAM,EAAE,KAAK,CAAC;AACnD,UAAM,IAAI,MAAM,cAAc,MAAM;AACpC,QAAI,EAAE,IAAI;AACR,UAAI,GAAG,EAAE,IAAI,SAAS,EAAE,OAAO,YAAY,EAAE,YAAY,IAAI,KAAK,GAAG,WAAW;AAAA,IAClF,OAAO;AACL,mBAAa;AACb,UAAI,GAAG,EAAE,IAAI,aAAa,EAAE,QAAQ,MAAM,YAAY,EAAE,QAAQ,WAAW,IAAI,KAAK,GAAG,GAAG;AAC1F,iBAAW,KAAK,EAAE,QAAS,KAAI,KAAK,EAAE,KAAK,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE;AAAA,IACtE;AAAA,EACF;AACA,MAAI,YAAY;AACd,QAAI,EAAE;AACN;AAAA,MACE;AAAA,IACF;AACA,UAAM,IAAI,qBAAqB,qBAAqB,CAAC,CAAC;AAAA,EACxD;AACF;AAEA,eAAe,MAAM,OAAoB,MAAc,OAA+B;AACpF,QAAM,YAAY,OAAO,MAAM,EAAE,MAAM,CAAC;AACxC,MAAI,kBAAkB,IAAI,GAAG;AAC/B;AAEA,eAAe,QAAQ,OAAoB,MAA6B;AACtE,QAAM,WAAW,UAAU,OAAO,IAAI;AACtC,MAAI,CAACA,YAAW,QAAQ,GAAG;AACzB,UAAM,IAAI,mBAAmB,MAAM,MAAM,WAAW,KAAK,CAAC;AAAA,EAC5D;AACA,QAAM,SAAS,QAAQ,IAAI,UAAU,QAAQ,IAAI,OAAO,SAAS,IAAI,QAAQ,IAAI,SAAS;AAC1F,QAAM,SAAS,UAAU,QAAQ,CAAC,QAAQ,GAAG,EAAE,OAAO,UAAU,CAAC;AACjE,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,UAAU,6BAA6B,OAAO,MAAM,GAAG;AAAA,EACnE;AAGA,MAAI;AACF,UAAM,UAAU,OAAO,IAAI;AAAA,EAC7B,SAAS,GAAG;AACV,QAAI,YAAa,EAAY,OAAO,EAAE;AAAA,EACxC;AACF;AAEA,eAAe,UACb,OACA,MACA,MACA,OACe;AACf,QAAM,YAAY,OAAO,MAAMC,MAAK,QAAQ,IAAI,GAAG,EAAE,MAAM,CAAC;AAC5D,MAAI,aAAa,IAAI,mBAAc,IAAI,OAAO;AAChD;AAEA,eAAe,UAAU,OAAoB,MAAc,MAA6B;AACtF,QAAM,YAAY,OAAO,MAAMA,MAAK,QAAQ,IAAI,CAAC;AACjD,MAAI,mBAAmB,IAAI,iBAAY,IAAI,GAAG;AAChD;AAEA,SAAS,QAAQ,OAA0B;AACzC,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,EAAG,KAAI,GAAG,EAAE,OAAO,EAAE,CAAC,IAAI,CAAW,EAAE;AAClF;AAMA,eAAe,KAAK,MAA8C;AAChE,QAAM,MAAM,IAAI,cAAc;AAC9B,QAAM,QAAQ,MAAM,uBAAuB;AAE3C,MACG,QAAQ,QAAQ,gEAAgE,EAChF,OAAO,WAAW,0BAA0B,EAC5C,OAAO,2BAA2B,yCAAyC,EAC3E,OAAO,OAAO,SAA0D;AACvE,UAAM,cAA2B,CAAC;AAClC,QAAI,KAAK,UAAU,OAAW,aAAY,QAAQ,KAAK;AACvD,QAAI,KAAK,qBAAqB,MAAO,aAAY,qBAAqB;AACtE,UAAM,QAAQ,OAAO,WAAW;AAAA,EAClC,CAAC;AAEH,MAAI,QAAQ,QAAQ,wCAAwC,EAAE,OAAO,MAAM,QAAQ,KAAK,CAAC;AACzF,MAAI,QAAQ,UAAU,yBAAyB,EAAE,OAAO,MAAM,UAAU,KAAK,CAAC;AAC9E,MACG,QAAQ,WAAW,+DAA0D,EAC7E,OAAO,MAAM,WAAW,KAAK,CAAC;AACjC,MACG,QAAQ,eAAe,mCAAmC,EAC1D,OAAO,CAAC,MAAc,QAAQ,OAAO,CAAC,CAAC;AAE1C,MACG,QAAQ,cAAc,oDAAoD,EAC1E,MAAM,OAAO,EACb,OAAO,iBAAiB,qCAAqC,EAC7D,OAAO,mBAAmB,+CAA+C,EACzE,OAAO,OAAO,GAAW,SAAyD;AACjF,UAAM,cAA0B,CAAC;AACjC,QAAI,KAAK,aAAa,MAAO,aAAY,aAAa;AACtD,QAAI,KAAK,iBAAiB,OAAW,aAAY,eAAe,KAAK;AACrE,UAAM,OAAO,OAAO,GAAG,WAAW;AAAA,EACpC,CAAC;AAEH,MACG,QAAQ,kBAAkB,sDAAsD,EAChF,OAAO,WAAW,6BAA6B,EAC/C,OAAO,CAAC,GAAW,SAA8B,WAAW,OAAO,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC;AAEtF,MACG,QAAQ,QAAQ,0BAA0B,EAC1C,OAAO,UAAU,6BAA6B,EAAE,SAAS,EAAE,CAAC,EAC5D,OAAO,iBAAiB,qCAAqC,EAC7D,OAAO,mBAAmB,+CAA+C,EACzE,OAAO,OAAO,SAAoE;AACjF,UAAM,cAA0B,CAAC;AACjC,QAAI,KAAK,aAAa,MAAO,aAAY,aAAa;AACtD,QAAI,KAAK,iBAAiB,OAAW,aAAY,eAAe,KAAK;AACrE,UAAM,QAAQ,OAAO,KAAK,KAAK,GAAG,WAAW;AAAA,EAC/C,CAAC;AAEH,MACG,QAAQ,WAAW,qCAAqC,EACxD,OAAO,eAAe,uBAAuB,EAAE,SAAS,GAAG,CAAC,EAC5D,OAAO,CAAC,SAA4B,WAAW,OAAO,KAAK,SAAS,EAAE,CAAC;AAE1E,MACG;AAAA,IACC;AAAA,IACA;AAAA,EACF,EACC,OAAO,SAAS,sBAAsB,EACtC,OAAO,YAAY,oDAAoD,EACvE;AAAA,IAAO,CAAC,MAA0B,SACjC,YAAY,OAAO,MAAM,IAAI;AAAA,EAC/B;AAEF,MACG,QAAQ,aAAa,gBAAgB,EACrC,OAAO,WAAW,uBAAuB,EACzC,OAAO,CAAC,GAAW,SAA8B,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,KAAK,CAAC;AAEjF,MACG,QAAQ,eAAe,yCAAyC,EAChE,OAAO,CAAC,MAAc,QAAQ,OAAO,CAAC,CAAC;AAE1C,MACG,QAAQ,wBAAwB,qCAAqC,EACrE,OAAO,WAAW,6BAA6B,EAC/C;AAAA,IAAO,CAAC,GAAW,GAAW,SAC7B,UAAU,OAAO,GAAG,GAAG,CAAC,CAAC,KAAK,KAAK;AAAA,EACrC;AAEF,MACG,QAAQ,wBAAwB,mCAAmC,EACnE,OAAO,CAAC,GAAW,MAAc,UAAU,OAAO,GAAG,CAAC,CAAC;AAE1D,MAAI,QAAQ,QAAQ,sCAAsC,EAAE,OAAO,MAAM,QAAQ,KAAK,CAAC;AAEvF,MAAI,QAAQ,cAAc,sDAAsD,EAAE,OAAO,MAAM;AAC7F,QAAI,uEAAuE;AAC3E;AAAA,MACE;AAAA,IACF;AACA;AAAA,MACE;AAAA,IACF;AACA;AAAA,MACE;AAAA,IACF;AAAA,EACF,CAAC;AAED,MACG,QAAQ,6BAA6B,+CAA+C,EACpF,OAAO,CAAC,UAAkB;AACzB,QAAI,UAAU,OAAO;AACnB;AAAA,QACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAsCA,KAAK;AAAA,MACP;AAAA,IACF,WAAW,UAAU,QAAQ;AAC3B;AAAA,QACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAuBA,KAAK;AAAA,MACP;AAAA,IACF,WAAW,UAAU,QAAQ;AAC3B;AAAA,QACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAyCA,KAAK;AAAA,MACP;AAAA,IACF,OAAO;AACL,UAAI,+CAA+C;AACnD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,CAAC;AAEH,MACG,QAAQ,sBAAsB,6CAA6C,EAC3E,OAAO,YAAY;AAClB,QAAI;AACF,YAAM,SAAS,MAAM,WAAW,KAAK;AACrC,UAAI,OAAO,KAAK,IAAI,CAAC;AAAA,IACvB,QAAQ;AAAA,IAER;AAAA,EACF,CAAC;AAEH,MAAI,KAAK;AACT,MAAI,QAAQ,OAAO;AAEnB,MAAI;AACF,QAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,KAAK,QAAQ,QAAQ,KAAK,CAAC,KAAK,gBAAgB,GAAG,IAAI,GAAG;AAAA,MACjF,KAAK;AAAA,IACP,CAAC;AACD,QAAI,IAAI,gBAAgB;AACtB,YAAM,IAAI,kBAAkB;AAAA,IAC9B,WACE,KAAK,SAAS,KACd,CAAC,KAAK,SAAS,QAAQ,KACvB,CAAC,KAAK,SAAS,IAAI,KACnB,CAAC,KAAK,SAAS,WAAW,KAC1B,CAAC,KAAK,SAAS,IAAI,GACnB;AACA,UAAI,WAAW;AACf,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,SAAS,GAAG;AACV,QAAI,aAAa,aAAa;AAC5B,UAAI,UAAU,EAAE,OAAO,EAAE;AACzB,UAAI,aAAa,wBAAwB,EAAE,QAAQ,SAAS,GAAG;AAC7D,YAAI,EAAE;AACN,mBAAW,KAAK,EAAE,QAAS,KAAI,KAAK,EAAE,KAAK,OAAO,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE;AAAA,MACtE;AACA,YAAM,OAAO,EAAE;AACf,aAAO,KAAK,YAAY;AAAA,IAC1B;AACA,QAAI,qBAAsB,EAAY,OAAO,EAAE;AAC/C,WAAO;AAAA,EACT;AACF;AAMA,SAAS,oBAA6B;AACpC,MAAI,CAAC,YAAY,IAAI,WAAW,OAAO,EAAG,QAAO;AACjD,QAAM,QAAQ,QAAQ,KAAK,CAAC;AAC5B,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI;AACF,WAAO,aAAa,cAAc,YAAY,GAAG,CAAC,MAAM,aAAaA,MAAK,QAAQ,KAAK,CAAC;AAAA,EAC1F,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAI,kBAAkB,GAAG;AACvB,OAAK,QAAQ,KAAK,MAAM,CAAC,CAAC,EAAE;AAAA,IAC1B,CAAC,SAAS,QAAQ,KAAK,IAAI;AAAA,IAC3B,CAAC,MAAM;AACL,UAAI,CAAC;AACL,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF;","names":["path","mkdir","path","existsSync","readFile","existsSync","mkdir","path","existsSync","homedir","path","path","homedir","path","existsSync","existsSync","readFile","path","mkdir","readdir","unlink","path","mkdir","path","readdir","unlink","existsSync","readFile","existsSync","copyFile","mkdir","readFile","readdir","unlink","path","existsSync","readdir","path","readFile","mkdir","unlink","copyFile","mkdir","readState","writeState","existsSync","path"]}
@@ -0,0 +1,30 @@
1
+ import { Plugin } from '@opencode-ai/plugin';
2
+
3
+ /**
4
+ * opencode plugin entry for agent-router.
5
+ *
6
+ * What this exposes to opencode:
7
+ * - 6 tools the agent can call:
8
+ * router_status — read state.json
9
+ * router_list — list stacks
10
+ * router_use — apply a stack (fires TUI toast on success)
11
+ * router_capture — snapshot current frontmatter models into a stack
12
+ * router_validate — check model IDs against opencode's reachable list
13
+ * router_back — undo last switch (fires TUI toast)
14
+ *
15
+ * - One log line on init noting the active stack (via client.app.log).
16
+ *
17
+ * What this DOES NOT do:
18
+ * - We never fire `tui.toast.show` from plugin init. The toast hook is only
19
+ * reliably callable inside event/tool handler context per opencode docs.
20
+ * - We never rewrite agent files from a hook (only from tool calls).
21
+ *
22
+ * The `tool()` helper from `@opencode-ai/plugin` wraps our `args` zod shape +
23
+ * `execute()` into the shape opencode wants. `execute()` must return either a
24
+ * string or `{output: string, metadata?: object}`. We always return JSON in
25
+ * `output` so the agent can parse our tool responses programmatically.
26
+ */
27
+
28
+ declare const AgentRouterPlugin: Plugin;
29
+
30
+ export { AgentRouterPlugin, AgentRouterPlugin as default };