@habemus-papadum/aiui 0.2.0 → 0.3.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.
@@ -1,3 +1,8 @@
1
+ import { type SessionBrowser } from "@habemus-papadum/aiui-util";
2
+ import type { AiuiArgs } from "../util/aiui-args";
3
+ import { type ChromeSettings } from "../util/chrome";
4
+ import { type AiuiConfig } from "../util/config";
5
+ type ChromeConfig = NonNullable<AiuiConfig["chrome"]>;
1
6
  /** The default *remote* port — the one worth keeping fixed (see module doc). */
2
7
  export declare const DEFAULT_REMOTE_PORT = 9222;
3
8
  export interface BrowserOptions {
@@ -12,6 +17,48 @@ export interface BrowserOptions {
12
17
  remotePort?: string;
13
18
  }
14
19
  export declare function runBrowser(opts: BrowserOptions): Promise<void>;
20
+ /** Options for {@link startSessionBrowser}. */
21
+ export interface StartSessionBrowserOptions {
22
+ /** CLI identity flags (profile / data dir), reconciled against config. */
23
+ flags?: Pick<AiuiArgs, "chromeProfile" | "chromeDataDir">;
24
+ /** The `chrome` section of the loaded config. */
25
+ config?: ChromeConfig;
26
+ /**
27
+ * Whether prompting is allowed. Interactive launches may offer to
28
+ * install/update Chrome for Testing and print the extension autoload hint;
29
+ * non-interactive ones (CI, or a sidecar whose terminal belongs to another
30
+ * process — `aiui vite`'s browser open) degrade to whatever browser is
31
+ * already installed, silently.
32
+ */
33
+ interactive: boolean;
34
+ /** Override the resolved DevTools debug port (`aiui browser --port`). */
35
+ debugPort?: number;
36
+ /** Launch headless regardless of config (`aiui browser --headless`). */
37
+ headless?: boolean;
38
+ /** Open this URL as the first tab instead of about:blank. */
39
+ startUrl?: string;
40
+ }
41
+ /**
42
+ * Launch the session browser the way `aiui browser` does — the shared
43
+ * "everything before the window exists" sequence, extracted so other commands
44
+ * (`aiui vite`'s browser sidecar) launch identically instead of re-deriving
45
+ * it:
46
+ *
47
+ * 1. Resolve settings from flags + config.
48
+ * 2. Prefer the managed Chrome for Testing unless config names a browser
49
+ * explicitly (the sync may prompt to install/update — only when
50
+ * `interactive`; otherwise it just reports what's installed).
51
+ * 3. Rebuild and load the aiui devtools extension (dev checkouts only).
52
+ * 4. Launch on the profile's user data dir and wait for the debug endpoint.
53
+ *
54
+ * Throws with a remediation-bearing message when no browser can be found or
55
+ * the launch fails; callers decide whether that's fatal (`aiui browser`) or
56
+ * merely a warning (`aiui vite`, where the dev server must keep running).
57
+ */
58
+ export declare function startSessionBrowser(opts: StartSessionBrowserOptions): Promise<{
59
+ session: SessionBrowser;
60
+ settings: ChromeSettings;
61
+ }>;
15
62
  /**
16
63
  * `~/.cache/aiui/browser-profiles/<key>` for a tunneled session browser.
17
64
  * Path-only (no mkdir) — the browser launch creates it on first use.
@@ -25,3 +72,4 @@ export declare function sshTunnelArgs(target: string, remotePort: number, localP
25
72
  export declare function remoteAttachCommand(remotePort: number): string;
26
73
  /** `aiui open <url>` — open a page in the running session browser. */
27
74
  export declare function runOpen(url: string, opts: Pick<BrowserOptions, "profile" | "dataDir">): Promise<void>;
75
+ export {};
@@ -0,0 +1,49 @@
1
+ export interface CleanOptions {
2
+ /** Limit the reset to this repo's `.aiui-cache/`. */
3
+ projectOnly?: boolean;
4
+ /** Limit the reset to the user cache (`~/.cache/aiui`). */
5
+ userOnly?: boolean;
6
+ /** Keep the managed Chrome for Testing install (skip the ~160 MB re-download). */
7
+ keepBrowser?: boolean;
8
+ /** Print what would be deleted, then stop. */
9
+ dryRun?: boolean;
10
+ /** Delete without the confirmation prompt. */
11
+ yes?: boolean;
12
+ }
13
+ /** The three cache roots `clean` reasons about (resolved, never created). */
14
+ export interface CleanRoots {
15
+ /** `<base>/.aiui-cache`. */
16
+ project: string;
17
+ /** `~/.cache/aiui` (respects `$AIUI_CACHE` / `$XDG_CACHE_HOME`). */
18
+ user: string;
19
+ /** The managed Chrome for Testing dir — a child of {@link CleanRoots.user}. */
20
+ browser: string;
21
+ }
22
+ /** One thing `clean` will delete: a root path, optionally sparing some children. */
23
+ export interface CleanTarget {
24
+ /** Short human label for the plan output. */
25
+ label: string;
26
+ /** The directory to remove. */
27
+ path: string;
28
+ /**
29
+ * Absolute child paths to preserve instead of deleting `path` wholesale. Used
30
+ * by `--keep-browser` to clear the user cache while keeping `chrome/`.
31
+ */
32
+ keep?: string[];
33
+ }
34
+ /** Resolve the cache roots for a given base dir (defaults to cwd). */
35
+ export declare function cleanRoots(base?: string): CleanRoots;
36
+ /**
37
+ * Decide which roots to remove from the flags. Pure over `roots`: the fs is only
38
+ * touched later, when the targets are resolved to concrete deletions.
39
+ */
40
+ export declare function planCleanTargets(opts: CleanOptions, roots: CleanRoots): CleanTarget[];
41
+ /**
42
+ * The concrete absolute paths a target will delete. `[path]` for a plain target,
43
+ * or its children minus the kept ones for a `--keep-browser` target; `[]` when
44
+ * nothing there exists (so an already-clean root drops out of the plan).
45
+ */
46
+ export declare function resolveDeletions(target: CleanTarget): string[];
47
+ export declare function runClean(opts?: CleanOptions): Promise<void>;
48
+ /** Human-readable byte count (binary units), e.g. `346.0 MB`. */
49
+ export declare function formatBytes(n: number): string;
@@ -0,0 +1 @@
1
+ export declare function runConfigTui(base?: string): Promise<void>;
@@ -0,0 +1,43 @@
1
+ import { type AiuiConfig } from "../util/config";
2
+ import { type ConfigValue, type ResolvedField } from "../util/config-schema";
3
+ /** Which config file a write targets. */
4
+ export type ConfigLevel = "user" | "project";
5
+ /** Both config files, read once — the raw material for every subcommand. */
6
+ export interface ConfigLevels {
7
+ paths: {
8
+ user: string;
9
+ project: string;
10
+ };
11
+ user: AiuiConfig;
12
+ project: AiuiConfig;
13
+ }
14
+ /** One field with its per-level values and resolved provenance. */
15
+ export interface FieldState extends ResolvedField {
16
+ userValue?: ConfigValue;
17
+ projectValue?: ConfigValue;
18
+ /** The value the launcher would see (project beats user); undefined = unset. */
19
+ effective?: ConfigValue;
20
+ /** Where {@link effective} comes from; "default"/"unset" mean no file sets it. */
21
+ source: ConfigLevel | "default" | "unset";
22
+ }
23
+ /** Read both config levels (missing files read as empty configs). */
24
+ export declare function readLevels(base?: string): ConfigLevels;
25
+ /** Resolve every schema field against the two levels. */
26
+ export declare function fieldStates(levels: ConfigLevels): FieldState[];
27
+ export interface ShowOptions {
28
+ json?: boolean;
29
+ }
30
+ export declare function runConfigShow(options?: ShowOptions, base?: string): void;
31
+ /** The value column of one `show`/TUI row: set value + source, or the default. */
32
+ export declare function valueCell(state: FieldState): string;
33
+ export declare function runConfigGet(key: string, base?: string): void;
34
+ export interface WriteOptions {
35
+ /** Target the project file (.aiui-cache/config.json) instead of the user file. */
36
+ project?: boolean;
37
+ }
38
+ export declare function runConfigSet(key: string, raw: string, options?: WriteOptions, base?: string): void;
39
+ /** The shared write path for `set` and the TUI. */
40
+ export declare function writeValue(levels: ConfigLevels, state: ResolvedField, value: ConfigValue, level: ConfigLevel): void;
41
+ export declare function runConfigUnset(key: string, options?: WriteOptions, base?: string): void;
42
+ /** The shared unset path for `unset` and the TUI. */
43
+ export declare function removeValue(levels: ConfigLevels, state: ResolvedField, level: ConfigLevel): void;
@@ -0,0 +1,15 @@
1
+ export interface DebugOptions {
2
+ /** Target a channel by its registry tag instead of the interactive selector. */
3
+ mcp?: string;
4
+ /** UI port for the viewer's dev server (default {@link DEFAULT_UI_PORT}). */
5
+ port?: string;
6
+ /** Open the browser at the viewer (default true; `--no-open` skips). */
7
+ open?: boolean;
8
+ }
9
+ /**
10
+ * This package's root — the Vite server root, found by walking up from this
11
+ * module to the `@habemus-papadum/aiui` package.json (two levels in the
12
+ * source tree, but the walk keeps it honest against dist layouts too).
13
+ */
14
+ export declare function packageRoot(): string | undefined;
15
+ export declare function runDebug(opts?: DebugOptions): Promise<void>;
@@ -17,7 +17,7 @@ export declare function classifyDemoTarget(target: string): DemoTargetState;
17
17
  * doesn't exist on the registry).
18
18
  */
19
19
  export declare function demoDependencyRange(version: string): string;
20
- /** Copy the template, restore `.gitignore`, and pin dependency ranges. */
20
+ /** Copy the template, restore dot-paths npm strips, and pin dependency ranges. */
21
21
  export declare function scaffoldDemo(template: string, target: string): void;
22
22
  /**
23
23
  * The template directory shipped with this install. Probed upward from this
@@ -0,0 +1,3 @@
1
+ export declare function runPaintUrl(opts?: {
2
+ json?: boolean;
3
+ }): Promise<void>;
@@ -35,5 +35,43 @@ export declare function resolveChannelTarget(servers: RunningServer[], targetTag
35
35
  * run it via the current Node with an absolute path. Resolving it also doubles
36
36
  * as the "is Vite available?" check: if it isn't installed, we fail loudly
37
37
  * rather than shelling out to nothing. See {@link resolvePackageCli}.
38
+ *
39
+ * Once the dev server is up, a *sidecar* opens it in the session browser (the
40
+ * shared window `aiui claude` attaches the agent to). We never know up front
41
+ * which port Vite will bind — the user runs many dev servers and Vite walks up
42
+ * from 5173 — so instead of guessing, Vite's stdout is teed through us and
43
+ * scanned for its own ready banner (see {@link parseViteLocalUrl}). stdin and
44
+ * stderr stay inherited: Vite still owns the terminal, its interactive
45
+ * shortcuts and Ctrl-C behave exactly as before. The sidecar is fire-and-forget
46
+ * ({@link openAppInBrowser}): whatever the browser does, the dev server runs on.
38
47
  */
39
48
  export declare function runVite(rawArgs?: string[]): Promise<void>;
49
+ /**
50
+ * Parse one line of Vite's stdout for the local dev-server URL.
51
+ *
52
+ * Vite's ready banner looks like (colored in a real terminal):
53
+ *
54
+ * ➜ Local: http://localhost:5174/
55
+ * ➜ Network: use --host to expose
56
+ *
57
+ * Only the `Local:` line counts, and only with a loopback host (`localhost`,
58
+ * `127.0.0.1`, `[::1]`) — that's the URL meaningful to open in a browser on
59
+ * this machine, or to port-forward in the headless hint. ANSI codes are
60
+ * stripped *first*: Vite colors the host and the port separately, so escape
61
+ * sequences sit in the middle of the URL text and no pattern would survive
62
+ * matching against the raw bytes. Returns undefined for anything that isn't
63
+ * the ready line — the same shape as the workbench's parseServeReadyLine, the
64
+ * house pattern for these one-line protocols.
65
+ */
66
+ export declare function parseViteLocalUrl(line: string): string | undefined;
67
+ /**
68
+ * Tee a child's stdout to ours verbatim while watching for the dev-server URL.
69
+ *
70
+ * Chunks are forwarded untouched (colors, spinners, screen clears all pass
71
+ * through); scanning happens on a parallel line-buffered copy so a URL split
72
+ * across chunk boundaries is still seen. Once found, `onUrl` fires exactly
73
+ * once and the scanner disengages — from then on this is a plain passthrough.
74
+ * Exported for tests, which drive it with in-memory streams (no child
75
+ * process).
76
+ */
77
+ export declare function teeAndDetectLocalUrl(source: NodeJS.ReadableStream, sink: NodeJS.WritableStream, onUrl: (url: string) => void): void;
package/dist/index.d.ts CHANGED
@@ -1,8 +1,16 @@
1
1
  /**
2
2
  * ai ui frontends
3
3
  *
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.
10
+ *
4
11
  * @packageDocumentation
5
12
  */
13
+ export { type ResolveSidecarsDeps, resolveSidecars, type SidecarDescriptor, } from "./util/sidecars";
6
14
  /** The published package name — handy for smoke tests. */
7
15
  export declare const name = "@habemus-papadum/aiui";
8
16
  /** Greet someone. Replace with your library's real API. */
package/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
- const a = "@habemus-papadum/aiui";
2
- function n(e) {
1
+ import { r as n } from "./sidecars-BKlhUA0V.js";
2
+ const r = "@habemus-papadum/aiui";
3
+ function a(e) {
3
4
  return `Hello, ${e}!`;
4
5
  }
5
6
  export {
6
- n as greet,
7
- a as name
7
+ a as greet,
8
+ r as name,
9
+ n as resolveSidecars
8
10
  };
9
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 * @packageDocumentation\n */\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":"AAOO,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 — 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;"}
@@ -0,0 +1,48 @@
1
+ import { createRequire as c } from "node:module";
2
+ const l = c(import.meta.url), n = [
3
+ {
4
+ // The iPad paint stream. Always on: it rides the channel's own port (no
5
+ // process, no extra listener), so hosting it costs nothing. Whether an
6
+ // iPad can actually REACH it is the channel bind's decision (`channel.bind`
7
+ // / `--aiui-bind`, asked at first run) — the security posture lives there,
8
+ // not here. `--aiui-no-sidecar paint` / `sidecars.paint: false` turn it off.
9
+ name: "paint",
10
+ autoEnable: () => !0,
11
+ descriptor: (a, o) => ({
12
+ name: "paint",
13
+ module: o("@habemus-papadum/aiui-paint/sidecar"),
14
+ export: "paintSidecar",
15
+ options: { root: a }
16
+ })
17
+ }
18
+ ];
19
+ function u(a, o, i = {}) {
20
+ const d = i.resolveModule ?? ((e) => l.resolve(e)), s = i.log ?? ((e) => process.stderr.write(`[aiui] warning: ${e}
21
+ `)), t = /* @__PURE__ */ new Set();
22
+ for (const e of n)
23
+ try {
24
+ e.autoEnable(a) && t.add(e.name);
25
+ } catch (r) {
26
+ s(
27
+ `sidecar "${e.name}" auto-detect failed: ${r instanceof Error ? r.message : String(r)}`
28
+ );
29
+ }
30
+ for (const e of o.enable)
31
+ n.some((r) => r.name === e) && t.add(e);
32
+ for (const e of o.disable)
33
+ t.delete(e);
34
+ return n.filter((e) => t.has(e.name)).map((e) => {
35
+ try {
36
+ return e.descriptor(a, d);
37
+ } catch (r) {
38
+ s(
39
+ `sidecar "${e.name}" disabled — its module failed to resolve: ${r instanceof Error ? r.message : String(r)}`
40
+ );
41
+ return;
42
+ }
43
+ }).filter((e) => e !== void 0);
44
+ }
45
+ export {
46
+ u as r
47
+ };
48
+ //# sourceMappingURL=sidecars-BKlhUA0V.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidecars-BKlhUA0V.js","sources":["../src/util/sidecars.ts"],"sourcesContent":["/**\n * Deciding which **session sidecars** the channel should host for a launch.\n *\n * A sidecar is an extra backend the channel mounts alongside the intent\n * pipeline — the concrete one today is the iPad paint stream. The channel takes\n * no dependency on any sidecar: `aiui claude` hands it a JSON array of\n * {@link SidecarDescriptor}s on `--sidecars`, and the channel dynamic-imports\n * each `module`, calls `mod[export ?? \"default\"](options)`, and mounts the\n * result (a bad descriptor is logged + skipped there).\n *\n * This module is the CLI's half: a small registry of the sidecars the launcher\n * knows how to construct, plus a pure resolver that decides which are on for a\n * given project root and set of `--aiui-sidecar` / `--aiui-no-sidecar` flags.\n */\n\nimport { createRequire } from \"node:module\";\n\nconst nodeRequire = createRequire(import.meta.url);\n\n/**\n * The contract the channel's `--sidecars` argument expects, mirrored here so\n * the CLI can emit it. The channel imports `module`, calls its `export` (or\n * `default`) with `options`, and mounts the returned sidecar.\n */\nexport interface SidecarDescriptor {\n /** Stable identifier, used for `--aiui-sidecar`/`--aiui-no-sidecar` and logs. */\n name: string;\n /** Importable specifier the channel `import()`s (e.g. a package subpath). */\n module: string;\n /** Named export to call as the factory. Defaults to `\"default\"`. */\n export?: string;\n /** Passed opaquely to the factory (e.g. `{ root: \"/proj\" }`). */\n options?: unknown;\n}\n\n/** Resolves a package specifier to an absolute path the channel can `import()`. */\ntype ResolveModule = (specifier: string) => string;\n\n/** Injectable seams, so the resolver is unit-testable without on-disk state. */\nexport interface ResolveSidecarsDeps {\n /**\n * Resolves a sidecar's package specifier to an ABSOLUTE path. Defaults to\n * `createRequire(import.meta.url).resolve`. This matters: the channel\n * dynamic-imports the descriptor's `module`, but it does NOT depend on any\n * sidecar package — so a bare specifier resolves from the channel's own\n * node_modules and fails (pnpm's isolated layout). Resolving here (from the\n * `aiui` CLI, which DOES depend on the sidecar package) to an absolute path\n * makes the import work in both the source-first workspace and an install.\n */\n resolveModule?: ResolveModule;\n /** Warning sink for a sidecar that had to be dropped (defaults to stderr). */\n log?: (message: string) => void;\n}\n\n/** A sidecar the CLI knows how to enable and construct, keyed by name. */\ninterface KnownSidecar {\n name: string;\n /** Whether this sidecar auto-enables for the given project root. */\n autoEnable: (root: string) => boolean;\n /** Build the descriptor the channel will mount for this root. */\n descriptor: (root: string, resolveModule: ResolveModule) => SidecarDescriptor;\n}\n\n/**\n * The registry of sidecars the launcher can enable. Emit order follows this\n * list, so the resolver's output is stable regardless of flag order. The paint\n * stream is always on — see its entry.\n */\nconst KNOWN_SIDECARS: KnownSidecar[] = [\n {\n // The iPad paint stream. Always on: it rides the channel's own port (no\n // process, no extra listener), so hosting it costs nothing. Whether an\n // iPad can actually REACH it is the channel bind's decision (`channel.bind`\n // / `--aiui-bind`, asked at first run) — the security posture lives there,\n // not here. `--aiui-no-sidecar paint` / `sidecars.paint: false` turn it off.\n name: \"paint\",\n autoEnable: () => true,\n descriptor: (root, resolveModule) => ({\n name: \"paint\",\n module: resolveModule(\"@habemus-papadum/aiui-paint/sidecar\"),\n export: \"paintSidecar\",\n options: { root },\n }),\n },\n];\n\n/**\n * Decide which session sidecars to host for `root`.\n *\n * Starts from the auto-detected set (each known sidecar whose `autoEnable`\n * predicate is truthy for this root), then applies the flags: `opts.enable`\n * force-adds a known sidecar by name even when it wouldn't auto-detect, and\n * `opts.disable` removes one. Disable wins over enable. Enable names the CLI\n * doesn't know how to construct are ignored (the launcher can only build the\n * sidecars in {@link KNOWN_SIDECARS}). Descriptors come back in the registry's\n * stable order.\n */\nexport function resolveSidecars(\n root: string,\n opts: { enable: string[]; disable: string[] },\n deps: ResolveSidecarsDeps = {},\n): SidecarDescriptor[] {\n const resolveModule =\n deps.resolveModule ?? ((specifier: string) => nodeRequire.resolve(specifier));\n const log =\n deps.log ?? ((message: string) => process.stderr.write(`[aiui] warning: ${message}\\n`));\n const enabled = new Set<string>();\n\n // Auto-detected sidecars. A throwing detector must not kill the launch —\n // warn and treat as not detected.\n for (const known of KNOWN_SIDECARS) {\n try {\n if (known.autoEnable(root)) {\n enabled.add(known.name);\n }\n } catch (err) {\n log(\n `sidecar \"${known.name}\" auto-detect failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n // Force-enable, but only sidecars the CLI knows how to construct.\n for (const name of opts.enable) {\n if (KNOWN_SIDECARS.some((k) => k.name === name)) {\n enabled.add(name);\n }\n }\n // Disable wins over enable.\n for (const name of opts.disable) {\n enabled.delete(name);\n }\n\n return KNOWN_SIDECARS.filter((k) => enabled.has(k.name))\n .map((k) => {\n try {\n return k.descriptor(root, resolveModule);\n } catch (err) {\n // The sidecar's package isn't resolvable (e.g. not installed) — skip it\n // rather than failing the whole launch, but say so: silently dropping it\n // would surface as \"the sidecar is mysteriously absent\".\n log(\n `sidecar \"${k.name}\" disabled — its module failed to resolve: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return undefined;\n }\n })\n .filter((d): d is SidecarDescriptor => d !== undefined);\n}\n"],"names":["nodeRequire","createRequire","KNOWN_SIDECARS","root","resolveModule","resolveSidecars","opts","deps","specifier","log","message","enabled","known","err","name","k","d"],"mappings":";AAiBA,MAAMA,IAAcC,EAAc,YAAY,GAAG,GAmD3CC,IAAiC;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAME,MAAM;AAAA,IACN,YAAY,MAAM;AAAA,IAClB,YAAY,CAACC,GAAMC,OAAmB;AAAA,MACpC,MAAM;AAAA,MACN,QAAQA,EAAc,qCAAqC;AAAA,MAC3D,QAAQ;AAAA,MACR,SAAS,EAAE,MAAAD,EAAA;AAAA,IAAK;AAAA,EAClB;AAEJ;AAaO,SAASE,EACdF,GACAG,GACAC,IAA4B,CAAA,GACP;AACrB,QAAMH,IACJG,EAAK,kBAAkB,CAACC,MAAsBR,EAAY,QAAQQ,CAAS,IACvEC,IACJF,EAAK,QAAQ,CAACG,MAAoB,QAAQ,OAAO,MAAM,mBAAmBA,CAAO;AAAA,CAAI,IACjFC,wBAAc,IAAA;AAIpB,aAAWC,KAASV;AAClB,QAAI;AACF,MAAIU,EAAM,WAAWT,CAAI,KACvBQ,EAAQ,IAAIC,EAAM,IAAI;AAAA,IAE1B,SAASC,GAAK;AACZ,MAAAJ;AAAA,QACE,YAAYG,EAAM,IAAI,yBACpBC,aAAe,QAAQA,EAAI,UAAU,OAAOA,CAAG,CACjD;AAAA,MAAA;AAAA,IAEJ;AAGF,aAAWC,KAAQR,EAAK;AACtB,IAAIJ,EAAe,KAAK,CAACa,MAAMA,EAAE,SAASD,CAAI,KAC5CH,EAAQ,IAAIG,CAAI;AAIpB,aAAWA,KAAQR,EAAK;AACtB,IAAAK,EAAQ,OAAOG,CAAI;AAGrB,SAAOZ,EAAe,OAAO,CAACa,MAAMJ,EAAQ,IAAII,EAAE,IAAI,CAAC,EACpD,IAAI,CAACA,MAAM;AACV,QAAI;AACF,aAAOA,EAAE,WAAWZ,GAAMC,CAAa;AAAA,IACzC,SAASS,GAAK;AAIZ,MAAAJ;AAAA,QACE,YAAYM,EAAE,IAAI,8CAChBF,aAAe,QAAQA,EAAI,UAAU,OAAOA,CAAG,CACjD;AAAA,MAAA;AAEF;AAAA,IACF;AAAA,EACF,CAAC,EACA,OAAO,CAACG,MAA8BA,MAAM,MAAS;AAC1D;"}
@@ -6,6 +6,7 @@
6
6
  * begin with `--aiui-` so they're unambiguously distinguishable from flags meant
7
7
  * for the wrapped command (e.g. claude's `--resume`).
8
8
  */
9
+ import { type ChannelBind } from "./config-schema";
9
10
  export interface AiuiArgs {
10
11
  /** The `--aiui-tag <tag>` value, if provided (the channel/MCP session tag). */
11
12
  tag?: string;
@@ -25,6 +26,18 @@ export interface AiuiArgs {
25
26
  * Code's built-in browser integration and forwards via passthrough.)
26
27
  */
27
28
  noChrome: boolean;
29
+ /**
30
+ * `--aiui-browser` was passed: open the wrapped tool's page in the session
31
+ * browser even where it would default off (CI / headless environments —
32
+ * e.g. `aiui vite` on a machine whose port *is* getting forwarded to one
33
+ * with a display).
34
+ */
35
+ browser: boolean;
36
+ /**
37
+ * `--aiui-no-browser` was passed: skip all session-browser activity (no
38
+ * tab opened, no browser launched) for this run.
39
+ */
40
+ noBrowser: boolean;
28
41
  /**
29
42
  * The `--aiui-chrome-profile <name>` value, if provided — which named Chrome
30
43
  * profile (user data dir under `.aiui-cache/chrome/`) to launch with.
@@ -42,6 +55,24 @@ export interface AiuiArgs {
42
55
  * `chrome.browserUrl` in config for this launch.
43
56
  */
44
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
+ /**
70
+ * The `--aiui-bind <loopback|host>` value, if provided — where the channel's
71
+ * web backend binds for this launch, overriding `channel.bind` in config.
72
+ * `host` is the trusted-LAN posture: the whole (unauthenticated) channel
73
+ * surface, iPad paint page included, becomes reachable from the network.
74
+ */
75
+ bind?: ChannelBind;
45
76
  /** Everything else, to forward verbatim to the wrapped tool. */
46
77
  passthrough: string[];
47
78
  }
@@ -65,12 +96,21 @@ export declare function infoFlag(passthrough: string[]): "help" | "version" | un
65
96
  * MCP server to target (e.g. which session `aiui vite` should connect to).
66
97
  * - `--aiui-chrome` / `--aiui-no-chrome` — force the Chrome DevTools MCP on
67
98
  * (even under CI) / leave it off. Passing both is an error.
99
+ * - `--aiui-browser` / `--aiui-no-browser` — force opening the page in the
100
+ * session browser (even in CI/headless environments) / never open one.
101
+ * Passing both is an error.
68
102
  * - `--aiui-chrome-profile <name>` — launch Chrome with the named profile
69
103
  * (created on first use under `.aiui-cache/chrome/<name>`).
70
104
  * - `--aiui-chrome-data-dir <path>` — launch Chrome with an explicit user data
71
105
  * dir instead of a named profile. Mutually exclusive with the above.
72
106
  * - `--aiui-browser-url <url>` — attach the Chrome DevTools MCP to this
73
107
  * 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
+ * - `--aiui-bind <loopback|host>` — where the channel's web backend binds for
112
+ * this launch (see `channel.bind` in the config guide). Any other value is
113
+ * an error.
74
114
  *
75
115
  * Any other `--aiui-*` flag throws, so a typo surfaces loudly instead of being
76
116
  * silently dropped or leaking into the child command.
@@ -19,10 +19,14 @@ type ChromeFlags = Pick<AiuiArgs, "chrome" | "noChrome" | "chromeProfile" | "chr
19
19
  * npx download + Chrome launch), with `--aiui-no-chrome`, or with
20
20
  * `chrome.enabled: false` in config. `--aiui-chrome` forces it on even under
21
21
  * CI; `chrome.enabled: true` merely restates the default and does not.
22
+ *
23
+ * Deliberately gated on CI alone, not the wider `isHeadless` check from
24
+ * aiui-util: on a headless-but-interactive box (SSH into a dev machine)
25
+ * the MCP still works — it just launches/attaches a headless-capable Chrome —
26
+ * whereas *opening a page for the user* would be pointless. Only commands that
27
+ * put a window in front of someone consult the broader signals.
22
28
  */
23
29
  export declare function chromeDevtoolsEnabled(args: Pick<ChromeFlags, "chrome" | "noChrome">, config?: ChromeConfig, env?: NodeJS.ProcessEnv): boolean;
24
- /** Truthy `CI` env var, with the conventional "false"/"0" escape hatches. */
25
- export declare function isCi(env?: NodeJS.ProcessEnv): boolean;
26
30
  /** The launch-relevant Chrome settings after flags and config are reconciled. */
27
31
  export interface ChromeSettings {
28
32
  /** Absolute user data dir for this launch. */
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The declarative schema for aiui's `config.json` — one table that drives
3
+ * everything config-shaped:
4
+ *
5
+ * - validation (util/config.ts walks these sections instead of hand-rolled
6
+ * per-key checks),
7
+ * - the `aiui config` commands (show/get/set/unset use it to resolve keys,
8
+ * parse CLI values, and report defaults),
9
+ * - the `aiui config tui` browser (docs, defaults, and enum choices all
10
+ * render from here).
11
+ *
12
+ * Because docs, defaults, and validation come from the same rows, they cannot
13
+ * drift apart. `docs/guide/config.md` remains the long-form narrative; the
14
+ * `doc` strings here are the terminal-sized versions.
15
+ *
16
+ * **How a component participates.** `CONFIG_SECTIONS` is the registry: a
17
+ * section is plain data, so a future component that grows file-backed settings
18
+ * (say, an `intent` section for the pipeline) joins by contributing one
19
+ * `ConfigSectionSchema` object here and a matching optional section on
20
+ * `AiuiConfig`. Nothing else — validation, show/get/set, and the TUI pick it
21
+ * up from the table. (The intent pipeline's current config is deliberately
22
+ * *not* here: it is per-app, passed as `aiuiDevOverlay({ intent: { … } })` in
23
+ * the app's Vite config — see docs/guide/intent-overlay.md.)
24
+ */
25
+ export { CHROME_CHANNELS, type ChromeChannel } from "@habemus-papadum/aiui-util";
26
+ export declare const FOR_TESTING_MODES: readonly ["prompt", "auto", "off"];
27
+ export type ForTestingMode = (typeof FOR_TESTING_MODES)[number];
28
+ export declare const CHROME_MODES: readonly ["attach", "launch"];
29
+ export type ChromeMode = (typeof CHROME_MODES)[number];
30
+ export declare const CHANNEL_BINDS: readonly ["loopback", "host"];
31
+ export type ChannelBind = (typeof CHANNEL_BINDS)[number];
32
+ /** Every config leaf is a JSON scalar; sections never nest further. */
33
+ export type ConfigValue = boolean | number | string;
34
+ export interface ConfigFieldSchema {
35
+ /** Leaf key within its section, e.g. `"skipPermissions"`. */
36
+ key: string;
37
+ /** `"enum"` is a string constrained to {@link values}. */
38
+ type: "boolean" | "number" | "string" | "enum";
39
+ /** The allowed values when {@link type} is `"enum"`. */
40
+ values?: readonly string[];
41
+ /** The built-in default, when the fallback is a plain value. */
42
+ default?: ConfigValue;
43
+ /**
44
+ * Human phrasing of the default when a bare value would mislead (dynamic
45
+ * fallbacks, first-run prompts). Shown instead of {@link default}.
46
+ */
47
+ defaultText?: string;
48
+ /** One-line summary — the TUI's list row. */
49
+ summary: string;
50
+ /** The rest of the documentation (terminal-sized; optional). */
51
+ doc?: string;
52
+ /**
53
+ * Constraint beyond type/enum. Returns the "expected …" tail of the error
54
+ * message, or undefined when the value is fine.
55
+ */
56
+ validate?: (value: ConfigValue) => string | undefined;
57
+ }
58
+ export interface ConfigSectionSchema {
59
+ /** Top-level key in config.json, e.g. `"chrome"`. */
60
+ name: string;
61
+ /** One-line summary of what the section configures. */
62
+ summary: string;
63
+ fields: ConfigFieldSchema[];
64
+ }
65
+ export declare const CONFIG_SECTIONS: ConfigSectionSchema[];
66
+ /** A field paired with its section — what key-resolution hands back. */
67
+ export interface ResolvedField {
68
+ section: ConfigSectionSchema;
69
+ field: ConfigFieldSchema;
70
+ /** The dotted path, e.g. `"chrome.mode"`. */
71
+ path: string;
72
+ }
73
+ /** Every field in schema order, with dotted paths. */
74
+ export declare function allConfigFields(): ResolvedField[];
75
+ /** Resolve a dotted path like `"chrome.mode"`; undefined when unknown. */
76
+ export declare function findConfigField(path: string): ResolvedField | undefined;
77
+ /** The `typeof` a field's values (enums are strings). */
78
+ export declare function fieldRuntimeType(field: ConfigFieldSchema): "boolean" | "number" | "string";
79
+ /**
80
+ * Constraint check beyond the runtime type: enum membership, then the field's
81
+ * own `validate`. Returns the "expected …" tail for the error, or undefined.
82
+ */
83
+ export declare function invalidReason(field: ConfigFieldSchema, value: ConfigValue): string | undefined;
84
+ /**
85
+ * Parse a CLI-provided string into the field's type and validate it — how
86
+ * `aiui config set` and the TUI turn text into a config value.
87
+ */
88
+ export declare function parseFieldValue(field: ConfigFieldSchema, raw: string): {
89
+ value: ConfigValue;
90
+ } | {
91
+ error: string;
92
+ };
93
+ /** Render a value the way config.json would hold it (strings quoted). */
94
+ export declare function formatConfigValue(value: ConfigValue): string;
95
+ /** The default, phrased for humans: `defaultText` wins, then the value, then "unset". */
96
+ export declare function describeDefault(field: ConfigFieldSchema): string;