@habemus-papadum/aiui 0.3.0 → 0.6.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.
Files changed (41) hide show
  1. package/README.md +6 -1
  2. package/dist/cli.js +1121 -1149
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/browser.d.ts +6 -4
  5. package/dist/commands/clean.d.ts +5 -4
  6. package/dist/commands/debug.d.ts +1 -9
  7. package/dist/commands/env.d.ts +36 -0
  8. package/dist/commands/extension.d.ts +46 -0
  9. package/dist/commands/mcp.d.ts +10 -0
  10. package/dist/commands/native-host.d.ts +16 -0
  11. package/dist/commands/pencil-url.d.ts +18 -0
  12. package/dist/commands/vite.d.ts +17 -29
  13. package/dist/config-xLcoLiLT.js +315 -0
  14. package/dist/config-xLcoLiLT.js.map +1 -0
  15. package/dist/index.d.ts +4 -6
  16. package/dist/index.js +4 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/util/aiui-args.d.ts +0 -14
  19. package/dist/util/channel-launch.d.ts +67 -0
  20. package/dist/util/channel-target.d.ts +33 -0
  21. package/dist/util/chrome.d.ts +70 -27
  22. package/dist/util/config-schema.d.ts +23 -1
  23. package/dist/util/config.d.ts +21 -11
  24. package/dist/util/gemini-preflight.d.ts +56 -0
  25. package/dist/util/managed-browser.d.ts +109 -0
  26. package/dist/util/openai-preflight.d.ts +1 -1
  27. package/package.json +9 -9
  28. package/dist/commands/demo.d.ts +0 -28
  29. package/dist/commands/paint.d.ts +0 -3
  30. package/dist/sidecars-BKlhUA0V.js +0 -48
  31. package/dist/sidecars-BKlhUA0V.js.map +0 -1
  32. package/dist/util/cft.d.ts +0 -79
  33. package/dist/util/sidecars.d.ts +0 -62
  34. package/templates/demo/CLAUDE.md +0 -16
  35. package/templates/demo/README.md +0 -36
  36. package/templates/demo/gitignore +0 -4
  37. package/templates/demo/index.html +0 -11
  38. package/templates/demo/package.json +0 -23
  39. package/templates/demo/src/main.ts +0 -51
  40. package/templates/demo/tsconfig.json +0 -11
  41. package/templates/demo/vite.config.ts +0 -17
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-xLcoLiLT.js","sources":["../src/util/config-schema.ts","../src/util/config.ts"],"sourcesContent":["/**\n * The declarative schema for aiui's `config.json` — one table that drives\n * everything config-shaped:\n *\n * - validation (util/config.ts walks these sections instead of hand-rolled\n * per-key checks),\n * - the `aiui config` commands (show/get/set/unset use it to resolve keys,\n * parse CLI values, and report defaults),\n * - the `aiui config tui` browser (docs, defaults, and enum choices all\n * render from here).\n *\n * Because docs, defaults, and validation come from the same rows, they cannot\n * drift apart. `docs/guide/config.md` remains the long-form narrative; the\n * `doc` strings here are the terminal-sized versions.\n *\n * **How a component participates.** `CONFIG_SECTIONS` is the registry: a\n * section is plain data, so a future component that grows file-backed settings\n * (say, an `intent` section for the pipeline) joins by contributing one\n * `ConfigSectionSchema` object here and a matching optional section on\n * `AiuiConfig`. Nothing else — validation, show/get/set, and the TUI pick it\n * up from the table. (The intent pipeline's current config is deliberately\n * *not* here: it is per-app, passed as `aiuiDevOverlay({ intent: { … } })` in\n * the app's Vite config — see docs/guide/intent-overlay.md.)\n */\n\n// The channel list lives with the launch code (aiui-util's browser module,\n// whose RELEASE_CHANNELS map must stay exhaustive over it); re-exported here\n// so the config table keeps being the one import surface for config shapes.\nimport { CHROME_CHANNELS } from \"@habemus-papadum/aiui-util\";\n\nexport { CHROME_CHANNELS, type ChromeChannel } from \"@habemus-papadum/aiui-util\";\n\n/**\n * Which browser aiui downloads and manages for you when config names none\n * explicitly. Both honor `--load-extension` (so the intent client auto-loads)\n * and take the media auto-accept flags; the trade-off is Chromium's open-source\n * build dodges the \"verify you're human\" reCAPTCHA that Google serves to the\n * Chrome-for-Testing automation build, at the cost of Widevine/DRM + proprietary\n * codecs and Google account sign-in. Chromium is the default; flip the global\n * default with `chrome.managed`.\n */\nexport const MANAGED_FLAVORS = [\"chromium\", \"chrome-for-testing\"] as const;\nexport type ManagedFlavor = (typeof MANAGED_FLAVORS)[number];\n\n/** The preferred managed browser when config doesn't say otherwise. */\nexport const DEFAULT_MANAGED_FLAVOR: ManagedFlavor = \"chromium\";\n\n/**\n * How aggressively `aiui claude` keeps the managed browser installed/current.\n * (Formerly `chrome.forTesting`, back when Chrome for Testing was the only\n * managed flavor — that key still works as a deprecated alias.)\n */\nexport const MANAGE_MODES = [\"prompt\", \"auto\", \"off\"] as const;\nexport type ManageMode = (typeof MANAGE_MODES)[number];\n\n/** @deprecated Use {@link MANAGE_MODES}. Kept for the `chrome.forTesting` alias. */\nexport const FOR_TESTING_MODES = MANAGE_MODES;\n/** @deprecated Use {@link ManageMode}. */\nexport type ForTestingMode = ManageMode;\n\nexport const CHROME_MODES = [\"attach\", \"launch\"] as const;\nexport type ChromeMode = (typeof CHROME_MODES)[number];\n\nexport const CHANNEL_BINDS = [\"loopback\", \"host\"] as const;\nexport type ChannelBind = (typeof CHANNEL_BINDS)[number];\n\n/** Every config leaf is a JSON scalar; sections never nest further. */\nexport type ConfigValue = boolean | number | string;\n\nexport interface ConfigFieldSchema {\n /** Leaf key within its section, e.g. `\"skipPermissions\"`. */\n key: string;\n /** `\"enum\"` is a string constrained to {@link values}. */\n type: \"boolean\" | \"number\" | \"string\" | \"enum\";\n /** The allowed values when {@link type} is `\"enum\"`. */\n values?: readonly string[];\n /** The built-in default, when the fallback is a plain value. */\n default?: ConfigValue;\n /**\n * Human phrasing of the default when a bare value would mislead (dynamic\n * fallbacks, first-run prompts). Shown instead of {@link default}.\n */\n defaultText?: string;\n /** One-line summary — the TUI's list row. */\n summary: string;\n /** The rest of the documentation (terminal-sized; optional). */\n doc?: string;\n /**\n * Constraint beyond type/enum. Returns the \"expected …\" tail of the error\n * message, or undefined when the value is fine.\n */\n validate?: (value: ConfigValue) => string | undefined;\n}\n\nexport interface ConfigSectionSchema {\n /** Top-level key in config.json, e.g. `\"chrome\"`. */\n name: string;\n /** One-line summary of what the section configures. */\n summary: string;\n fields: ConfigFieldSchema[];\n}\n\nexport const CONFIG_SECTIONS: ConfigSectionSchema[] = [\n {\n name: \"claude\",\n summary: \"how `aiui claude` launches Claude Code\",\n fields: [\n {\n key: \"skipPermissions\",\n type: \"boolean\",\n default: true,\n defaultText: \"true (unset: the first interactive launch asks, then persists the answer)\",\n summary: \"Launch Claude Code with --dangerously-skip-permissions.\",\n doc:\n \"A personal preference with real consequences (docs/guide/warning): every agent \" +\n \"action — shell commands, file writes, network, the browser — runs without asking \" +\n \"first. aiui works fine either way. The first interactive launch asks and persists \" +\n \"the answer at the user level; when unset, non-interactive sessions fall back to true.\",\n },\n {\n key: \"enterNudge\",\n type: \"boolean\",\n default: true,\n defaultText: \"true (unset: the first interactive launch asks, then persists the answer)\",\n summary: \"Auto-dismiss Claude Code's development-channel acknowledgement prompt.\",\n doc:\n \"aiui loads a custom development channel, so Claude Code shows a one-key \" +\n \"acknowledgement at every startup; this injects a single Enter keystroke into the \" +\n \"terminal to dismiss it (best-effort TIOCSTI on /dev/tty — platforms that forbid it \" +\n \"harmlessly do nothing). Saying no just means pressing Enter yourself each launch.\",\n },\n ],\n },\n {\n name: \"channel\",\n summary: \"the channel server's web backend\",\n fields: [\n {\n key: \"bind\",\n type: \"enum\",\n values: CHANNEL_BINDS,\n default: \"loopback\",\n defaultText:\n '\"loopback\" (unset: the first interactive launch asks, then persists the answer)',\n summary: \"Which interface the channel web server binds: loopback, or host (LAN).\",\n doc:\n '\"host\" (0.0.0.0) makes the session\\'s whole web surface — the iPad paint page, but ' +\n \"also prompt injection, /debug, and every sidecar — reachable by anyone on your \" +\n \"network, UNAUTHENTICATED. That is the trusted-LAN posture (docs/guide/warning): \" +\n 'right on a network that is yours alone, wrong on shared Wi-Fi. \"loopback\" keeps ' +\n \"everything this-machine-only; reaching the paint page from an iPad is then up to \" +\n \"you — tunnel the channel port however you like (Tailscale, `ssh -L`). The first \" +\n \"interactive launch asks and persists the answer at the user level. Per-launch \" +\n \"flag: --aiui-bind.\",\n },\n ],\n },\n {\n name: \"chrome\",\n summary: \"the agent's browser and the Chrome DevTools MCP\",\n fields: [\n {\n key: \"enabled\",\n type: \"boolean\",\n default: true,\n summary: \"Attach the Chrome DevTools MCP.\",\n doc:\n \"false turns it off everywhere; true restates the default and does NOT override the \" +\n \"CI default-off — only the --aiui-chrome flag forces it on under CI.\",\n },\n {\n key: \"mode\",\n type: \"enum\",\n values: CHROME_MODES,\n default: \"attach\",\n summary: \"How the MCP reaches a browser: shared session browser, or its own.\",\n doc:\n '\"attach\" shares a user-visible session browser: an already-running one is ' +\n 'discovered by profile, or an interactive launch starts one eagerly. \"launch\" is ' +\n \"the hands-off mode: chrome-devtools-mcp launches its own private browser lazily, on \" +\n \"the agent's first browser tool call.\",\n },\n {\n key: \"browserUrl\",\n type: \"string\",\n defaultText: \"unset (manage a browser locally)\",\n summary: \"Attach to this DevTools endpoint instead of managing a browser at all.\",\n doc:\n \"The remote-development key (docs/guide/remote): the browser runs on another machine \" +\n \"(started there with `aiui browser`) and its debug port is tunneled over. Setting it \" +\n 'implies mode: \"attach\" and makes every local-browser setting (profile, ' +\n \"executablePath, channel, forTesting…) irrelevant. Per-launch flag: --aiui-browser-url.\",\n validate: (value) =>\n isHttpUrl(String(value))\n ? undefined\n : 'expected an http(s) URL like \"http://127.0.0.1:9222\"',\n },\n {\n key: \"debugPort\",\n type: \"number\",\n default: 0,\n defaultText: \"0 (an OS-assigned free port)\",\n summary: \"Fixed DevTools debug port for session browsers aiui launches.\",\n doc:\n \"Pin it (e.g. 9222) when something external must find the port — an ssh tunnel, a \" +\n \"VS Code attach-to-Chrome launch config. 0 means an OS-assigned free port.\",\n validate: (value) =>\n Number.isInteger(value) && (value as number) >= 0 && (value as number) <= 65535\n ? undefined\n : \"expected 0..65535\",\n },\n {\n key: \"profile\",\n type: \"string\",\n default: \"default\",\n summary: \"Named profile under .aiui-cache/chrome/.\",\n doc: \"Per-launch flag: --aiui-chrome-profile.\",\n },\n {\n key: \"dataDir\",\n type: \"string\",\n defaultText: \"unset (derived from chrome.profile)\",\n summary: \"Explicit Chrome user data dir; takes precedence over chrome.profile.\",\n doc: \"Per-launch flag: --aiui-chrome-data-dir.\",\n },\n {\n key: \"executablePath\",\n type: \"string\",\n defaultText: \"unset (the managed browser — see chrome.managed — else installed Chrome)\",\n summary: \"Explicit browser binary to launch — e.g. a Chrome for Testing install.\",\n doc:\n \"Overrides chrome.managed. Chrome for Testing and Chromium both honor \" +\n \"--load-extension, so the intent client auto-loads. Mutually exclusive with \" +\n \"chrome.channel.\",\n },\n {\n key: \"channel\",\n type: \"enum\",\n values: CHROME_CHANNELS,\n defaultText: 'unset (\"stable\" when launching an installed Chrome)',\n summary: \"Installed Chrome release channel to launch.\",\n doc: \"Overrides chrome.managed. Mutually exclusive with chrome.executablePath.\",\n },\n {\n key: \"managed\",\n type: \"enum\",\n values: MANAGED_FLAVORS,\n default: DEFAULT_MANAGED_FLAVOR,\n summary: \"Which browser aiui downloads and manages: Chromium, or Chrome for Testing.\",\n doc:\n '\"chromium\" (default) is the open-source build — it dodges the \"verify you\\'re ' +\n 'human\" reCAPTCHA that Google serves to the Chrome-for-Testing automation build, ' +\n \"at the cost of Widevine DRM, some proprietary codecs, and Google account sign-in. \" +\n '\"chrome-for-testing\" is Google\\'s branded automation build. Both auto-load the ' +\n \"intent client and take the media auto-accept flags. Ignored when executablePath or \" +\n \"channel names a browser explicitly. Each flavor keeps its own project profile \" +\n \"under .aiui-cache/chrome/<flavor>/.\",\n },\n {\n key: \"manage\",\n type: \"enum\",\n values: MANAGE_MODES,\n default: \"prompt\",\n summary: \"How `aiui claude` keeps the managed browser (chrome.managed) installed/current.\",\n doc:\n '\"prompt\" asks before installing or updating it — interactive sessions only, never ' +\n 'under CI; \"auto\" installs/updates without asking; \"off\" never checks. Prompt ' +\n 'answers (\"automatically\", \"never ask again\") persist here at the user level. ' +\n \"Skipped entirely when executablePath or channel picks a browser explicitly. \" +\n \"(Was chrome.forTesting.)\",\n },\n {\n key: \"forTesting\",\n type: \"enum\",\n values: MANAGE_MODES,\n defaultText: \"unset (deprecated alias for chrome.manage)\",\n summary: \"DEPRECATED — old name for chrome.manage; still honored when manage is unset.\",\n doc:\n \"Renamed to chrome.manage now that Chromium is also a managed flavor. Old configs \" +\n \"keep working: when chrome.manage is unset, this value is used.\",\n },\n {\n key: \"headless\",\n type: \"boolean\",\n default: false,\n summary: \"Launch Chrome with no UI.\",\n },\n // NOTE: `chrome.buildExtension` and `chrome.autoCapture` were removed here\n // (owner, 2026-07-17) — both had long been parsed-and-ignored (the DevTools\n // extension is deleted; page-side getDisplayMedia capture is gone). A config\n // still carrying either is tolerated and dropped by validation, not a hard\n // error — see DEPRECATED_FIELDS in util/config.ts.\n ],\n },\n];\n\n/** A field paired with its section — what key-resolution hands back. */\nexport interface ResolvedField {\n section: ConfigSectionSchema;\n field: ConfigFieldSchema;\n /** The dotted path, e.g. `\"chrome.mode\"`. */\n path: string;\n}\n\n/** Every field in schema order, with dotted paths. */\nexport function allConfigFields(): ResolvedField[] {\n return CONFIG_SECTIONS.flatMap((section) =>\n section.fields.map((field) => ({ section, field, path: `${section.name}.${field.key}` })),\n );\n}\n\n/** Resolve a dotted path like `\"chrome.mode\"`; undefined when unknown. */\nexport function findConfigField(path: string): ResolvedField | undefined {\n return allConfigFields().find((entry) => entry.path === path);\n}\n\n/** The `typeof` a field's values (enums are strings). */\nexport function fieldRuntimeType(field: ConfigFieldSchema): \"boolean\" | \"number\" | \"string\" {\n return field.type === \"enum\" ? \"string\" : field.type;\n}\n\n/**\n * Constraint check beyond the runtime type: enum membership, then the field's\n * own `validate`. Returns the \"expected …\" tail for the error, or undefined.\n */\nexport function invalidReason(field: ConfigFieldSchema, value: ConfigValue): string | undefined {\n if (field.type === \"enum\" && !(field.values ?? []).includes(String(value))) {\n return `expected one of: ${(field.values ?? []).join(\", \")}`;\n }\n return field.validate?.(value);\n}\n\n/**\n * Parse a CLI-provided string into the field's type and validate it — how\n * `aiui config set` and the TUI turn text into a config value.\n */\nexport function parseFieldValue(\n field: ConfigFieldSchema,\n raw: string,\n): { value: ConfigValue } | { error: string } {\n let value: ConfigValue;\n switch (fieldRuntimeType(field)) {\n case \"boolean\": {\n if (raw !== \"true\" && raw !== \"false\") {\n return { error: \"expected true or false\" };\n }\n value = raw === \"true\";\n break;\n }\n case \"number\": {\n value = Number(raw);\n if (raw.trim() === \"\" || Number.isNaN(value)) {\n return { error: \"expected a number\" };\n }\n break;\n }\n default:\n value = raw;\n }\n const reason = invalidReason(field, value);\n return reason ? { error: reason } : { value };\n}\n\n/** Render a value the way config.json would hold it (strings quoted). */\nexport function formatConfigValue(value: ConfigValue): string {\n return typeof value === \"string\" ? JSON.stringify(value) : String(value);\n}\n\n/** The default, phrased for humans: `defaultText` wins, then the value, then \"unset\". */\nexport function describeDefault(field: ConfigFieldSchema): string {\n if (field.defaultText) {\n return field.defaultText;\n }\n return field.default === undefined ? \"unset\" : formatConfigValue(field.default);\n}\n\nfunction isHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n","/**\n * aiui's two-level configuration file.\n *\n * Settings live in `config.json` at up to two places, merged per-key with the\n * more specific one winning:\n *\n * <user cache>/config.json e.g. ~/.cache/aiui/config.json (respects\n * AIUI_CACHE / XDG_CACHE_HOME)\n * <project>/.aiui-cache/config.json next to the traces and Chrome profiles\n *\n * CLI flags override both. Everything is optional; a missing file is an empty\n * config. A *malformed* file is a hard error, not a warning — these settings\n * gate security-relevant behavior (`claude.skipPermissions`), and a typo that\n * silently reverts to the dangerous default is worse than a failed launch. The\n * same reasoning rejects unknown keys.\n *\n * What the keys are — types, enums, defaults, and documentation — lives in one\n * declarative table, `config-schema.ts`. Validation here walks that schema, and\n * the `aiui config` commands (show/get/set/tui) render from it, so the checks\n * and the docs cannot drift apart.\n */\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { projectCacheDir } from \"@habemus-papadum/aiui-claude-channel\";\nimport { cacheDir } from \"@habemus-papadum/aiui-util\";\nimport {\n type ChannelBind,\n type ChromeChannel,\n type ChromeMode,\n CONFIG_SECTIONS,\n type ConfigValue,\n DEFAULT_MANAGED_FLAVOR,\n fieldRuntimeType,\n formatConfigValue,\n invalidReason,\n type ManagedFlavor,\n type ManageMode,\n} from \"./config-schema\";\n\nexport {\n CHANNEL_BINDS,\n CHROME_CHANNELS,\n CHROME_MODES,\n type ChannelBind,\n type ChromeChannel,\n type ChromeMode,\n DEFAULT_MANAGED_FLAVOR,\n FOR_TESTING_MODES,\n type ForTestingMode,\n MANAGE_MODES,\n MANAGED_FLAVORS,\n type ManagedFlavor,\n type ManageMode,\n} from \"./config-schema\";\n\nexport const CONFIG_FILENAME = \"config.json\";\n\n/**\n * The typed shape of a config file. Must mirror `CONFIG_SECTIONS` in\n * config-schema.ts — the schema rows carry the full per-key documentation and\n * defaults; the comments here are just orientation.\n */\nexport interface AiuiConfig {\n claude?: {\n /** Launch Claude Code with `--dangerously-skip-permissions`. */\n skipPermissions?: boolean;\n /** Auto-dismiss Claude Code's development-channel prompt (util/enter-nudge.ts). */\n enterNudge?: boolean;\n };\n channel?: {\n /** Which interface the channel web server binds: loopback (default) or host (LAN). */\n bind?: ChannelBind;\n };\n chrome?: {\n /** Attach the Chrome DevTools MCP (default: true). */\n enabled?: boolean;\n /** How the MCP reaches a browser: shared session browser, or its own. */\n mode?: ChromeMode;\n /** Attach to this DevTools endpoint instead of managing a browser at all. */\n browserUrl?: string;\n /** Fixed DevTools debug port for session browsers aiui launches (0 = OS-assigned). */\n debugPort?: number;\n /** Named profile under `.aiui-cache/chrome/` (default: \"default\"). */\n profile?: string;\n /** Explicit Chrome user data dir; takes precedence over `profile`. */\n dataDir?: string;\n /** Explicit browser binary to launch. Mutually exclusive with `channel`. */\n executablePath?: string;\n /** Installed Chrome release channel to launch. */\n channel?: ChromeChannel;\n /** Which browser aiui downloads and manages (default: chromium). */\n managed?: ManagedFlavor;\n /** How `aiui claude` keeps the managed browser installed/current. */\n manage?: ManageMode;\n /** @deprecated Old name for `manage`; still honored when `manage` is unset. */\n forTesting?: ManageMode;\n /** Launch Chrome headless (default: false). */\n headless?: boolean;\n };\n}\n\n/** The untyped view validation and merging work in: section → leaf values. */\ntype SectionValues = Record<string, ConfigValue>;\n\n/**\n * Top-level sections that USED to be valid and are now gone. A config file that\n * still carries one must not hard-fail — that would break every existing\n * config on upgrade — so it is accepted and ignored, distinct from a\n * genuinely-unknown key (a typo), which still throws. `sidecars.*` retired when\n * the channel began hosting its whole standard set unconditionally (paint,\n * intent, bar, pencil): there is nothing left to toggle, so the key is inert and\n * safe to delete.\n */\nconst DEPRECATED_SECTIONS = new Set([\"sidecars\"]);\n\n/**\n * Leaf keys that USED to be valid within a section and are now GONE — the\n * field-level twin of {@link DEPRECATED_SECTIONS}. A config still carrying one\n * is accepted and dropped (never copied into the loaded config), not a hard\n * error on upgrade. `chrome.buildExtension` / `chrome.autoCapture` retired with\n * the DevTools extension and page-side getDisplayMedia capture — both were long\n * parsed-and-ignored, and nothing reads them (owner, 2026-07-17).\n */\nconst DEPRECATED_FIELDS: Record<string, readonly string[]> = {\n chrome: [\"buildExtension\", \"autoCapture\"],\n};\n\n/** The `config.json` paths consulted, user-level first (base: the project dir). */\nexport function configPaths(base: string = process.cwd()): { user: string; project: string } {\n return {\n user: join(cacheDir(undefined, { create: false }), CONFIG_FILENAME),\n project: join(projectCacheDir(base), CONFIG_FILENAME),\n };\n}\n\n/** Load and merge the user- and project-level configs (project wins per key). */\nexport function loadAiuiConfig(base: string = process.cwd()): AiuiConfig {\n const paths = configPaths(base);\n return mergeAiuiConfig(readConfigFile(paths.user) ?? {}, readConfigFile(paths.project) ?? {});\n}\n\n/** Merge two configs section-by-section; `override`'s keys win within a section. */\nexport function mergeAiuiConfig(base: AiuiConfig, override: AiuiConfig): AiuiConfig {\n const merged: Record<string, SectionValues> = {};\n for (const section of CONFIG_SECTIONS) {\n merged[section.name] = {\n ...(base as Record<string, SectionValues | undefined>)[section.name],\n ...(override as Record<string, SectionValues | undefined>)[section.name],\n };\n }\n return merged as AiuiConfig;\n}\n\n/**\n * Read one config file. Missing → undefined; unreadable JSON or an unexpected\n * shape → an error naming the file, so the fix is obvious.\n */\nexport function readConfigFile(file: string): AiuiConfig | undefined {\n let text: string;\n try {\n text = readFileSync(file, \"utf8\");\n } catch {\n return undefined;\n }\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error) {\n throw new Error(\n `invalid JSON in ${file}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n return validateConfig(raw, file);\n}\n\n/** Walk `CONFIG_SECTIONS`, rejecting unknown keys, wrong types, and bad values. */\nfunction validateConfig(raw: unknown, file: string): AiuiConfig {\n const root = asSection(raw, file, \"the top level\");\n const knownSections = CONFIG_SECTIONS.map((s) => s.name);\n for (const key of Object.keys(root)) {\n // A retired section is tolerated (accepted, then ignored below); a truly\n // unknown one is a typo and still throws — see DEPRECATED_SECTIONS.\n if (knownSections.includes(key) || DEPRECATED_SECTIONS.has(key)) {\n continue;\n }\n throw new Error(\n `unknown key \"${key}\" at the top level of ${file} — known keys: ${knownSections.join(\", \")}`,\n );\n }\n\n const config: Record<string, SectionValues> = {};\n for (const section of CONFIG_SECTIONS) {\n if (root[section.name] === undefined) {\n continue;\n }\n const values = asSection(root[section.name], file, `\"${section.name}\"`);\n rejectUnknownKeys(\n values,\n // Known leaf keys PLUS any retired-but-tolerated ones for this section;\n // the copy loop below only carries `section.fields`, so a deprecated key\n // is accepted here and then dropped.\n [...section.fields.map((f) => f.key), ...(DEPRECATED_FIELDS[section.name] ?? [])],\n file,\n `\"${section.name}\"`,\n );\n // Absent keys stay absent (never `undefined`), or merging would let them\n // clobber a value from the other config level.\n const out: SectionValues = {};\n for (const field of section.fields) {\n const value = values[field.key];\n if (value === undefined) {\n continue;\n }\n const path = `${section.name}.${field.key}`;\n const type = fieldRuntimeType(field);\n if (typeof value !== type) {\n throw new Error(`expected a ${type} for ${path} in ${file}`);\n }\n const reason = invalidReason(field, value as ConfigValue);\n if (reason) {\n throw new Error(\n `invalid ${path} ${formatConfigValue(value as ConfigValue)} in ${file} — ${reason}`,\n );\n }\n out[field.key] = value as ConfigValue;\n }\n config[section.name] = out;\n }\n return config as AiuiConfig;\n}\n\n/**\n * Persist a change to the **user-level** config (creating the file if needed).\n *\n * This is how interactive prompt answers like \"never ask again\" become\n * durable: they are per-user decisions, so they land in the user cache — never\n * in the project file, which may be shared/committed by a team.\n */\nexport function updateUserConfig(mutate: (config: AiuiConfig) => void): string {\n return updateConfigFile(configPaths().user, mutate);\n}\n\n/**\n * Persist a change to a specific config file — the general form behind\n * {@link updateUserConfig}, and how `aiui config set --project` writes the\n * project level deliberately.\n */\nexport function updateConfigFile(file: string, mutate: (config: AiuiConfig) => void): string {\n const config = readConfigFile(file) ?? {};\n mutate(config);\n mkdirSync(dirname(file), { recursive: true });\n writeFileSync(file, `${JSON.stringify(config, null, 2)}\\n`);\n return file;\n}\n\n/**\n * The managed browser this config prefers when nothing names a browser\n * explicitly — {@link DEFAULT_MANAGED_FLAVOR} (chromium) unless `chrome.managed`\n * overrides it. Flip the global default with\n * `aiui config set chrome.managed chrome-for-testing`.\n */\nexport function resolveManagedFlavor(chrome: AiuiConfig[\"chrome\"] = {}): ManagedFlavor {\n return chrome.managed ?? DEFAULT_MANAGED_FLAVOR;\n}\n\n/**\n * The manage mode, honoring the deprecated `chrome.forTesting` alias: an\n * explicit `chrome.manage` wins, else the old key, else \"prompt\".\n */\nexport function resolveManageMode(chrome: AiuiConfig[\"chrome\"] = {}): ManageMode {\n return chrome.manage ?? chrome.forTesting ?? \"prompt\";\n}\n\nfunction asSection(value: unknown, file: string, where: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(`expected an object at ${where} of ${file}`);\n }\n return value as Record<string, unknown>;\n}\n\nfunction rejectUnknownKeys(\n section: Record<string, unknown>,\n known: string[],\n file: string,\n where: string,\n): void {\n for (const key of Object.keys(section)) {\n if (!known.includes(key)) {\n throw new Error(\n `unknown key \"${key}\" at ${where} of ${file} — known keys: ${known.join(\", \")}`,\n );\n }\n }\n}\n"],"names":["MANAGED_FLAVORS","DEFAULT_MANAGED_FLAVOR","MANAGE_MODES","CHROME_MODES","CHANNEL_BINDS","CONFIG_SECTIONS","value","isHttpUrl","CHROME_CHANNELS","allConfigFields","section","field","fieldRuntimeType","invalidReason","_a","parseFieldValue","raw","reason","formatConfigValue","describeDefault","url","CONFIG_FILENAME","DEPRECATED_SECTIONS","DEPRECATED_FIELDS","configPaths","base","join","cacheDir","projectCacheDir","loadAiuiConfig","paths","mergeAiuiConfig","readConfigFile","override","merged","file","text","readFileSync","error","validateConfig","root","asSection","knownSections","s","key","config","values","rejectUnknownKeys","f","out","path","type","updateUserConfig","mutate","updateConfigFile","mkdirSync","dirname","writeFileSync","resolveManagedFlavor","chrome","resolveManageMode","where","known"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,15 +2,13 @@
2
2
  * ai ui frontends
3
3
  *
4
4
  * The `aiui` CLI's library surface. Launch *policy* the CLI applies lives here
5
- * too, so sibling supervisors can reuse it instead of re-deriving it — the
6
- * first case is {@link resolveSidecars}, the `aiui claude` logic deciding
7
- * which session sidecars (e.g. the paint stream) a channel server should host for a
8
- * project root. The workbench imports it to give its debug channel the same
9
- * sidecar set a real session would get.
5
+ * too, so sibling supervisors can reuse it instead of re-deriving it — e.g. the
6
+ * layered config loader below, so a debug-channel supervisor binds the channel
7
+ * the same way a real `aiui claude` launch would.
10
8
  *
11
9
  * @packageDocumentation
12
10
  */
13
- export { type ResolveSidecarsDeps, resolveSidecars, type SidecarDescriptor, } from "./util/sidecars";
11
+ export { type AiuiConfig, loadAiuiConfig } from "./util/config";
14
12
  /** The published package name — handy for smoke tests. */
15
13
  export declare const name = "@habemus-papadum/aiui";
16
14
  /** Greet someone. Replace with your library's real API. */
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { r as n } from "./sidecars-BKlhUA0V.js";
2
- const r = "@habemus-papadum/aiui";
1
+ import { l as r } from "./config-xLcoLiLT.js";
2
+ const o = "@habemus-papadum/aiui";
3
3
  function a(e) {
4
4
  return `Hello, ${e}!`;
5
5
  }
6
6
  export {
7
7
  a as greet,
8
- r as name,
9
- n as resolveSidecars
8
+ r as loadAiuiConfig,
9
+ o as name
10
10
  };
11
11
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * ai ui frontends\n *\n * The `aiui` CLI's library surface. Launch *policy* the CLI applies lives here\n * too, so sibling supervisors can reuse it instead of re-deriving it — the\n * first case is {@link resolveSidecars}, the `aiui claude` logic deciding\n * which session sidecars (e.g. the paint stream) a channel server should host for a\n * project root. The workbench imports it to give its debug channel the same\n * sidecar set a real session would get.\n *\n * @packageDocumentation\n */\n\nexport {\n type ResolveSidecarsDeps,\n resolveSidecars,\n type SidecarDescriptor,\n} from \"./util/sidecars\";\n\n/** The published package name — handy for smoke tests. */\nexport const name = \"@habemus-papadum/aiui\";\n\n/** Greet someone. Replace with your library's real API. */\nexport function greet(who: string): string {\n return `Hello, ${who}!`;\n}\n"],"names":["name","greet","who"],"mappings":";AAoBO,MAAMA,IAAO;AAGb,SAASC,EAAMC,GAAqB;AACzC,SAAO,UAAUA,CAAG;AACtB;"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * ai ui frontends\n *\n * The `aiui` CLI's library surface. Launch *policy* the CLI applies lives here\n * too, so sibling supervisors can reuse it instead of re-deriving it — e.g. the\n * layered config loader below, so a debug-channel supervisor binds the channel\n * the same way a real `aiui claude` launch would.\n *\n * @packageDocumentation\n */\n\n// The layered config loader (user config project config), for supervisors\n// that must obey the same settings a real `aiui claude` launch would the\n// supervisors read `channel.bind` through this so a debug channel binds the\n// way the user configured the real one.\nexport { type AiuiConfig, loadAiuiConfig } from \"./util/config\";\n\n/** The published package name — handy for smoke tests. */\nexport const name = \"@habemus-papadum/aiui\";\n\n/** Greet someone. Replace with your library's real API. */\nexport function greet(who: string): string {\n return `Hello, ${who}!`;\n}\n"],"names":["name","greet","who"],"mappings":";AAkBO,MAAMA,IAAO;AAGb,SAASC,EAAMC,GAAqB;AACzC,SAAO,UAAUA,CAAG;AACtB;"}
@@ -55,17 +55,6 @@ export interface AiuiArgs {
55
55
  * `chrome.browserUrl` in config for this launch.
56
56
  */
57
57
  browserUrl?: string;
58
- /**
59
- * Names collected from repeatable `--aiui-sidecar <name>` flags — session
60
- * sidecars to force-enable even when they wouldn't auto-detect for this
61
- * project (e.g. `--aiui-sidecar paint`).
62
- */
63
- sidecar: string[];
64
- /**
65
- * Names collected from repeatable `--aiui-no-sidecar <name>` flags — session
66
- * sidecars to disable for this run. Disable wins over enable.
67
- */
68
- noSidecar: string[];
69
58
  /**
70
59
  * The `--aiui-bind <loopback|host>` value, if provided — where the channel's
71
60
  * web backend binds for this launch, overriding `channel.bind` in config.
@@ -105,9 +94,6 @@ export declare function infoFlag(passthrough: string[]): "help" | "version" | un
105
94
  * dir instead of a named profile. Mutually exclusive with the above.
106
95
  * - `--aiui-browser-url <url>` — attach the Chrome DevTools MCP to this
107
96
  * endpoint (e.g. a tunneled remote browser) instead of managing one.
108
- * - `--aiui-sidecar <name>` / `--aiui-no-sidecar <name>` — force-enable /
109
- * disable a session sidecar by name. Both are repeatable and accumulate;
110
- * disable wins over enable when the same name appears in both.
111
97
  * - `--aiui-bind <loopback|host>` — where the channel's web backend binds for
112
98
  * this launch (see `channel.bind` in the config guide). Any other value is
113
99
  * an error.
@@ -0,0 +1,67 @@
1
+ /**
2
+ * How a channel process gets configured, in one place.
3
+ *
4
+ * There are two ways the channel server comes up, and they must agree:
5
+ *
6
+ * - **`aiui claude`** builds an argv for the channel's `mcp` subcommand and
7
+ * hands it to Claude Code inside `--mcp-config`, which spawns it.
8
+ * - **`aiui mcp serve` / `aiui mcp mcp`** run the very same channel CLI
9
+ * directly, with no Claude Code anywhere in the loop.
10
+ *
11
+ * The one durable setting they must both derive from config is where the web
12
+ * backend binds (`channel.bind`) — the trusted-LAN posture. The second path
13
+ * used to forward its arguments verbatim and so never computed it; this module
14
+ * is what makes both paths resolve the same {@link ChannelLaunch} and emit the
15
+ * same `--bind` flag, so a standalone channel binds exactly like a session's.
16
+ *
17
+ * Which sidecars a channel hosts is NOT decided here anymore. The channel
18
+ * imports and mounts its own standard set (intent, bar, pencil — see the
19
+ * channel's standard-sidecars.ts), so there is nothing to pass: every channel
20
+ * hosts all four, and `channel.bind` alone decides whether a remote device can
21
+ * reach them.
22
+ *
23
+ * What else deliberately does NOT live here: `--launch-info` (a summary of *how
24
+ * a Claude Code session was assembled* — browser wiring, key preflight — which
25
+ * a direct launch has nothing to say about, and which `serve` does not even
26
+ * accept), and `--tag` / `--name` / `--port`, which are per-invocation identity
27
+ * rather than durable configuration.
28
+ */
29
+ import type { AiuiConfig, ChannelBind } from "./config";
30
+ /** Everything a launcher knows that can influence the channel's configuration. */
31
+ export interface ChannelLaunchInput {
32
+ /** Merged user + project config (`util/config`). */
33
+ config: AiuiConfig;
34
+ /** An explicit per-launch bind (`--aiui-bind`, or `serve`'s own `--bind`). */
35
+ bind?: ChannelBind;
36
+ }
37
+ /** The resolved settings, ready to be rendered as channel CLI flags. */
38
+ export interface ChannelLaunch {
39
+ bind: ChannelBind;
40
+ }
41
+ /**
42
+ * Resolve the channel's bind for a launch. Follows the three-tier precedence
43
+ * the docs promise: per-launch flag, then durable config, then the built-in
44
+ * default (`loopback`).
45
+ */
46
+ export declare function resolveChannelLaunch(input: ChannelLaunchInput): ChannelLaunch;
47
+ /**
48
+ * Render a {@link ChannelLaunch} as flags for the channel CLI. Both the `mcp`
49
+ * and `serve` subcommands accept `--bind` (see the channel's program.ts).
50
+ */
51
+ export declare function channelLaunchFlags(launch: ChannelLaunch): string[];
52
+ /**
53
+ * Whether `aiui mcp <args...>` will start a channel process, and so wants the
54
+ * config-derived settings. Callers use this to leave the subcommands that
55
+ * merely talk to a channel someone else is running (`quick`, `config`)
56
+ * untouched.
57
+ */
58
+ export declare function isChannelLaunch(args: string[]): boolean;
59
+ /**
60
+ * Apply the config-derived channel settings to a raw `aiui mcp <args...>`
61
+ * invocation.
62
+ *
63
+ * Returns `args` untouched unless its subcommand actually launches a channel.
64
+ * A setting the caller named explicitly is never overridden — `aiui mcp serve
65
+ * --bind host` means `host` no matter what config says.
66
+ */
67
+ export declare function applyChannelLaunchArgs(args: string[], launch: ChannelLaunch): string[];
@@ -0,0 +1,33 @@
1
+ /**
2
+ * channel-target.ts — resolve which running channel a channel-CONNECTING
3
+ * command should talk to.
4
+ *
5
+ * Used by the commands that genuinely reach into a channel: `aiui debug` (the
6
+ * trace viewer, bound to one channel's port) and, in spirit, the intent
7
+ * client's standalone `pnpm dev` launcher. Deliberately NOT used by `aiui
8
+ * vite` any more: an app it serves reaches the channel through the intent
9
+ * client at `/intent/`, not a build-time port, so there is nothing to resolve.
10
+ * (This helper lived in `commands/vite.ts` while vite was that connection;
11
+ * it moved here when vite stopped connecting — owner, 2026-07-17.)
12
+ */
13
+ import type { RunningServer } from "@habemus-papadum/aiui-claude-channel";
14
+ /** The channel a command should point at, or why it couldn't be resolved. */
15
+ export interface ChannelTarget {
16
+ /** The server resolved without prompting (by tag). */
17
+ server?: RunningServer;
18
+ /** Servers to offer in the interactive selector (no tag given, ≥1 running). */
19
+ select?: RunningServer[];
20
+ /** A human-readable reason a requested server couldn't be resolved. */
21
+ error?: string;
22
+ }
23
+ /**
24
+ * Decide which running channel server a command should connect to.
25
+ *
26
+ * Pure so it can be unit-tested without spawning anything:
27
+ * - With a `targetTag`, return the server whose `tag` matches exactly; if none
28
+ * matches, return an `error` naming the tag and the tags that *are* running.
29
+ * - Without a `targetTag`, don't guess: return `{}` when nothing is running, or
30
+ * `{ select }` so the caller runs the same selector as `quick` (which
31
+ * auto-picks a lone server and prompts when there are several).
32
+ */
33
+ export declare function resolveChannelTarget(servers: RunningServer[], targetTag: string | undefined): ChannelTarget;
@@ -31,6 +31,13 @@ export declare function chromeDevtoolsEnabled(args: Pick<ChromeFlags, "chrome" |
31
31
  export interface ChromeSettings {
32
32
  /** Absolute user data dir for this launch. */
33
33
  userDataDir: string;
34
+ /**
35
+ * The browser-variant tag the profile is partitioned under (see
36
+ * {@link chromeVariant}) — `chromium`, `chrome-for-testing`, `chrome-<channel>`,
37
+ * or `custom-<hash>`. Informational; `custom-*` when an explicit `dataDir`
38
+ * escapes the convention.
39
+ */
40
+ variant: string;
34
41
  /**
35
42
  * "attach" (default): share a session browser — discover or eagerly launch
36
43
  * one and point the MCP at its debug endpoint. "launch": chrome-devtools-mcp
@@ -46,7 +53,6 @@ export interface ChromeSettings {
46
53
  /** Installed Chrome release channel to launch, if configured. */
47
54
  channel?: ChromeChannel;
48
55
  headless: boolean;
49
- buildExtension: boolean;
50
56
  }
51
57
  /**
52
58
  * Reconcile CLI flags with config into the settings for this launch.
@@ -57,55 +63,92 @@ export interface ChromeSettings {
57
63
  * one they mean. Within config alone, `dataDir` beats `profile`.
58
64
  */
59
65
  export declare function resolveChromeSettings(args: Pick<ChromeFlags, "chromeProfile" | "chromeDataDir">, config?: ChromeConfig, base?: string): ChromeSettings;
66
+ /**
67
+ * The variant tag a launch's profile is partitioned under. Distinct browser
68
+ * builds must not share a Chrome user data dir — a downgrade or a
69
+ * different-channel launch on the same profile can refuse to start ("profile
70
+ * was created by a newer version") or silently migrate state — so each variant
71
+ * gets its own directory:
72
+ *
73
+ * - an explicit `channel` → `chrome-<channel>` (e.g. `chrome-beta`)
74
+ * - an explicit `executablePath` → `custom-<hash>` (stable per binary path, so
75
+ * two hand-picked binaries don't collide)
76
+ * - otherwise the managed flavor → `chromium` or `chrome-for-testing`
77
+ *
78
+ * `channel` and `executablePath` are mutually exclusive (validated in
79
+ * {@link resolveChromeSettings}); channel is checked first only for definiteness.
80
+ */
81
+ export declare function chromeVariant(config: Pick<ChromeConfig, "channel" | "executablePath" | "managed">): string;
60
82
  /**
61
83
  * The Chrome user data dir to use (absolute).
62
84
  *
63
85
  * An explicit `dataDir` wins, resolved against `base`. Otherwise the named
64
86
  * profile (default: {@link DEFAULT_CHROME_PROFILE}) maps to
65
- * `.aiui-cache/chrome/<name>` under `base` alternate profiles are just other
66
- * names, created on first use by the caller's mkdir.
87
+ * `.aiui-cache/chrome/<variant>/<name>` under `base`, partitioned by browser
88
+ * variant (see {@link chromeVariant}) so distinct builds never share a profile.
89
+ * Alternate profiles are just other names, created on first use by the caller's
90
+ * mkdir; `variant` defaults to {@link DEFAULT_MANAGED_FLAVOR}.
67
91
  */
68
92
  export declare function chromeUserDataDir(ids: {
69
93
  dataDir?: string;
70
94
  profile?: string;
95
+ variant?: string;
71
96
  }, base?: string): string;
97
+ /** The intent client's MV3 bundle directory name (see its build-ext.ts: a
98
+ * THIRD shape of that package — dist/ is the library, dist-ext/ the unpacked
99
+ * extension). */
100
+ export declare const INTENT_CLIENT_OUT_DIR = "dist-ext";
72
101
  /**
73
- * The built aiui-devtools-extension directory, if available.
102
+ * What we found when looking for the intent client's extension the one
103
+ * launches AUTO-LOAD.
74
104
  *
75
- * The package publishes `extension/` prebuilt, so this resolves both in a dev
76
- * workspace (where `extension/js` appears once built see
77
- * {@link buildDevtoolsExtension}) and installed from npm. Without `js/` the
78
- * extension is an empty shell, so an unbuilt dev checkout returns undefined.
105
+ * Deliberately simple: the bundle is a static build with no dev server and no
106
+ * dev/prod split the client's hot-iteration surface is the channel-served
107
+ * plain page, so the extension only ever needs to be BUILT.
79
108
  */
80
- export declare function devtoolsExtensionDir(): string | undefined;
81
- /**
82
- * Rebuild the aiui-devtools-extension package so the auto-loaded panel is never stale.
83
- *
84
- * A full tsc of the extension (plus the debug-ui esbuild bundle) is well
85
- * under a second — cheap enough to run on every launch rather than tracking
86
- * staleness. Best-effort by design: outside a dev
87
- * checkout (no devtools package, no typescript) it silently does nothing, and
88
- * a failing compile warns loudly but never blocks the launch — whatever
89
- * `extension/js` already holds is what gets loaded.
90
- */
91
- export declare function buildDevtoolsExtension(): Promise<void>;
109
+ export type IntentClientExtension = {
110
+ state: "absent";
111
+ } | {
112
+ state: "unbuilt";
113
+ root: string;
114
+ } | {
115
+ state: "ready";
116
+ dir: string;
117
+ };
118
+ /** Absolute paths for the intent client's bundle, in a checkout. */
119
+ export declare function intentClientExtensionPaths(): {
120
+ root: string;
121
+ outDir: string;
122
+ } | undefined;
123
+ /** {@link findIntentClientExtension}'s decision, against explicit paths. */
124
+ export declare function resolveIntentClientExtension(paths: {
125
+ root: string;
126
+ outDir: string;
127
+ } | undefined): IntentClientExtension;
128
+ /** The intent client extension, as this checkout can load it. No build-on-
129
+ * launch (the same rule as the frozen extension's ladder): the bundle is a
130
+ * deliberate act — `pnpm -C packages/aiui-intent-client build:ext`. */
131
+ export declare function findIntentClientExtension(): IntentClientExtension;
132
+ /** One launch-time note when the client is resolvable but unbuilt — printed
133
+ * every launch while true (actionable; it disappears once fixed). */
134
+ export declare function warnIntentClientState(intent: IntentClientExtension): void;
92
135
  /**
93
- * One-time tip when the extension exists but the chosen browser won't
94
- * auto-load it (no `executablePath` means a branded Chrome — installed
136
+ * One-time tip when extensions exist but the chosen browser won't
137
+ * auto-load them (no `executablePath` means a branded Chrome — installed
95
138
  * stable or a `channel` build — and branded builds ≥ 137 ignore
96
139
  * `--load-extension`). Printed once per profile: the marker file lives in the
97
140
  * user data dir, so it survives exactly as long as the profile whose manual
98
141
  * install it recommends.
99
142
  */
100
- export declare function maybeExtensionAutoloadHint(settings: ChromeSettings, extensionDir: string | undefined): void;
143
+ export declare function maybeExtensionAutoloadHint(settings: ChromeSettings, extensionDirs: string[]): void;
101
144
  /**
102
145
  * The `mcpServers` entry that launches chrome-devtools-mcp.
103
146
  *
104
147
  * Uses the documented `npx -y chrome-devtools-mcp@latest` invocation with the
105
148
  * user data dir pinned to ours. Puppeteer's default launch args include
106
- * `--disable-extensions`, which would neuter both the auto-loaded panel and
107
- * anything installed manually into the profile — so it is always stripped.
108
- * When an extension dir is given, additionally ask Chrome to load it.
149
+ * `--disable-extensions`, which would neuter both the auto-loaded extensions
150
+ * and anything installed manually into the profile — so it is always stripped.
151
+ * When extension dirs are given, additionally ask Chrome to load them.
109
152
  */
110
153
  /**
111
154
  * The `mcpServers` entry that *attaches* chrome-devtools-mcp to an existing
@@ -117,7 +160,7 @@ export declare function chromeMcpAttachServer(browserUrl: string): {
117
160
  command: string;
118
161
  args: string[];
119
162
  };
120
- export declare function chromeMcpServer(settings: ChromeSettings, extensionDir?: string): {
163
+ export declare function chromeMcpServer(settings: ChromeSettings, extensionDirs?: string[]): {
121
164
  command: string;
122
165
  args: string[];
123
166
  };
@@ -23,8 +23,30 @@
23
23
  * the app's Vite config — see docs/guide/intent-overlay.md.)
24
24
  */
25
25
  export { CHROME_CHANNELS, type ChromeChannel } from "@habemus-papadum/aiui-util";
26
+ /**
27
+ * Which browser aiui downloads and manages for you when config names none
28
+ * explicitly. Both honor `--load-extension` (so the intent client auto-loads)
29
+ * and take the media auto-accept flags; the trade-off is Chromium's open-source
30
+ * build dodges the "verify you're human" reCAPTCHA that Google serves to the
31
+ * Chrome-for-Testing automation build, at the cost of Widevine/DRM + proprietary
32
+ * codecs and Google account sign-in. Chromium is the default; flip the global
33
+ * default with `chrome.managed`.
34
+ */
35
+ export declare const MANAGED_FLAVORS: readonly ["chromium", "chrome-for-testing"];
36
+ export type ManagedFlavor = (typeof MANAGED_FLAVORS)[number];
37
+ /** The preferred managed browser when config doesn't say otherwise. */
38
+ export declare const DEFAULT_MANAGED_FLAVOR: ManagedFlavor;
39
+ /**
40
+ * How aggressively `aiui claude` keeps the managed browser installed/current.
41
+ * (Formerly `chrome.forTesting`, back when Chrome for Testing was the only
42
+ * managed flavor — that key still works as a deprecated alias.)
43
+ */
44
+ export declare const MANAGE_MODES: readonly ["prompt", "auto", "off"];
45
+ export type ManageMode = (typeof MANAGE_MODES)[number];
46
+ /** @deprecated Use {@link MANAGE_MODES}. Kept for the `chrome.forTesting` alias. */
26
47
  export declare const FOR_TESTING_MODES: readonly ["prompt", "auto", "off"];
27
- export type ForTestingMode = (typeof FOR_TESTING_MODES)[number];
48
+ /** @deprecated Use {@link ManageMode}. */
49
+ export type ForTestingMode = ManageMode;
28
50
  export declare const CHROME_MODES: readonly ["attach", "launch"];
29
51
  export type ChromeMode = (typeof CHROME_MODES)[number];
30
52
  export declare const CHANNEL_BINDS: readonly ["loopback", "host"];
@@ -1,5 +1,5 @@
1
- import { type ChannelBind, type ChromeChannel, type ChromeMode, type ForTestingMode } from "./config-schema";
2
- export { CHANNEL_BINDS, CHROME_CHANNELS, CHROME_MODES, type ChannelBind, type ChromeChannel, type ChromeMode, FOR_TESTING_MODES, type ForTestingMode, } from "./config-schema";
1
+ import { type ChannelBind, type ChromeChannel, type ChromeMode, type ManagedFlavor, type ManageMode } from "./config-schema";
2
+ export { CHANNEL_BINDS, CHROME_CHANNELS, CHROME_MODES, type ChannelBind, type ChromeChannel, type ChromeMode, DEFAULT_MANAGED_FLAVOR, FOR_TESTING_MODES, type ForTestingMode, MANAGE_MODES, MANAGED_FLAVORS, type ManagedFlavor, type ManageMode, } from "./config-schema";
3
3
  export declare const CONFIG_FILENAME = "config.json";
4
4
  /**
5
5
  * The typed shape of a config file. Must mirror `CONFIG_SECTIONS` in
@@ -17,10 +17,6 @@ export interface AiuiConfig {
17
17
  /** Which interface the channel web server binds: loopback (default) or host (LAN). */
18
18
  bind?: ChannelBind;
19
19
  };
20
- sidecars?: {
21
- /** Host the iPad paint sidecar on the channel's port (default: true). */
22
- paint?: boolean;
23
- };
24
20
  chrome?: {
25
21
  /** Attach the Chrome DevTools MCP (default: true). */
26
22
  enabled?: boolean;
@@ -34,16 +30,18 @@ export interface AiuiConfig {
34
30
  profile?: string;
35
31
  /** Explicit Chrome user data dir; takes precedence over `profile`. */
36
32
  dataDir?: string;
37
- /** Chrome binary to launch. Mutually exclusive with `channel`. */
33
+ /** Explicit browser binary to launch. Mutually exclusive with `channel`. */
38
34
  executablePath?: string;
39
35
  /** Installed Chrome release channel to launch. */
40
36
  channel?: ChromeChannel;
41
- /** How `aiui claude` manages the Chrome for Testing install. */
42
- forTesting?: ForTestingMode;
37
+ /** Which browser aiui downloads and manages (default: chromium). */
38
+ managed?: ManagedFlavor;
39
+ /** How `aiui claude` keeps the managed browser installed/current. */
40
+ manage?: ManageMode;
41
+ /** @deprecated Old name for `manage`; still honored when `manage` is unset. */
42
+ forTesting?: ManageMode;
43
43
  /** Launch Chrome headless (default: false). */
44
44
  headless?: boolean;
45
- /** Rebuild the aiui-devtools-extension on every launch in a dev checkout. */
46
- buildExtension?: boolean;
47
45
  };
48
46
  }
49
47
  /** The `config.json` paths consulted, user-level first (base: the project dir). */
@@ -74,3 +72,15 @@ export declare function updateUserConfig(mutate: (config: AiuiConfig) => void):
74
72
  * project level deliberately.
75
73
  */
76
74
  export declare function updateConfigFile(file: string, mutate: (config: AiuiConfig) => void): string;
75
+ /**
76
+ * The managed browser this config prefers when nothing names a browser
77
+ * explicitly — {@link DEFAULT_MANAGED_FLAVOR} (chromium) unless `chrome.managed`
78
+ * overrides it. Flip the global default with
79
+ * `aiui config set chrome.managed chrome-for-testing`.
80
+ */
81
+ export declare function resolveManagedFlavor(chrome?: AiuiConfig["chrome"]): ManagedFlavor;
82
+ /**
83
+ * The manage mode, honoring the deprecated `chrome.forTesting` alias: an
84
+ * explicit `chrome.manage` wins, else the old key, else "prompt".
85
+ */
86
+ export declare function resolveManageMode(chrome?: AiuiConfig["chrome"]): ManageMode;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Gemini key preflight for `aiui claude` — the GEMINI_API_KEY twin of
3
+ * {@link ./openai-preflight}.
4
+ *
5
+ * The realtime submode's reference engine is Gemini Live, and it runs in the
6
+ * *channel* process, which inherits the environment `aiui claude` launches in.
7
+ * Same key story as OpenAI: **`GEMINI_API_KEY` in the environment**, never
8
+ * config, never a flag. And the same failure this catches: a missing or stale
9
+ * key that would otherwise surface as an opaque closed WebSocket deep in a
10
+ * live session ("gemini live session closed") rather than a clear message up
11
+ * front. Degradation, not refusal — a bad key never blocks the launch; the
12
+ * realtime tier is simply unavailable until it's fixed.
13
+ *
14
+ * We record only a status — never the key or any prefix of it — so the
15
+ * launch-info summary can explain a degraded pipeline without seeing the
16
+ * secret.
17
+ */
18
+ import type { OpenAiKeyStatus } from "@habemus-papadum/aiui-claude-channel";
19
+ export interface GeminiPreflightOptions {
20
+ /**
21
+ * Perform the authenticated network check. True only for interactive,
22
+ * non-CI launches. When false the check is skipped silently and a present
23
+ * key is reported as "unverified", never contacted.
24
+ */
25
+ verify?: boolean;
26
+ /** Injectable for tests; defaults to the process environment. */
27
+ env?: NodeJS.ProcessEnv;
28
+ /** Injectable for tests; defaults to global `fetch`. */
29
+ fetchImpl?: typeof fetch;
30
+ /** Network budget for the status check (default {@link DEFAULT_TIMEOUT_MS}). */
31
+ timeoutMs?: number;
32
+ }
33
+ /**
34
+ * Determine the status of `GEMINI_API_KEY` for this launch. Same contract as
35
+ * `preflightOpenAiKey`: never throws, never prints, never returns the key.
36
+ * The key rides the query string (Gemini's auth shape — no bearer header) and
37
+ * a rejected key answers 400/401/403 depending on how it's malformed, so all
38
+ * three read as "invalid".
39
+ */
40
+ export declare function preflightGeminiKey(opts?: GeminiPreflightOptions): Promise<OpenAiKeyStatus>;
41
+ interface PreflightMessage {
42
+ level: "warn" | "note";
43
+ title: string;
44
+ detail: string;
45
+ }
46
+ /**
47
+ * The user-facing message for a preflight status, or `null` when there's
48
+ * nothing to say. Unlike the OpenAI twin, a **missing** Gemini key is a quiet
49
+ * `note`, not a warning: the default transcription tiers don't need it — only
50
+ * the realtime (Gemini Live) submode does — so its absence degrades one
51
+ * opt-in tier, not the pipeline's default path.
52
+ */
53
+ export declare function geminiPreflightMessage(status: OpenAiKeyStatus): PreflightMessage | null;
54
+ /** Print the preflight message for `status` (nothing, for a valid key). */
55
+ export declare function reportGeminiPreflight(status: OpenAiKeyStatus): void;
56
+ export {};