@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.
package/dist/cli.js CHANGED
@@ -1,381 +1,431 @@
1
1
  #!/usr/bin/env node
2
- import { Command as Be } from "commander";
3
- import { join as l, dirname as S, resolve as E, delimiter as Ve, relative as Ke } from "node:path";
4
- import { cacheDir as q } from "@habemus-papadum/aiui-util";
5
- import { execa as p } from "execa";
6
- import { mkdirSync as k, rmSync as Ye, writeFileSync as T, readFileSync as P, existsSync as b, realpathSync as we, readdirSync as ge, accessSync as qe, constants as Ge, cpSync as Je, renameSync as We } from "node:fs";
7
- import { computeSystemExecutablePath as ze, ChromeReleaseChannel as O, Browser as x, detectBrowserPlatform as be, resolveBuildId as ye, getInstalledBrowsers as ve, install as Xe, uninstall as Ze } from "@puppeteer/browsers";
8
- import { projectCacheDir as xe, listMcpServers as Qe, selectMcpServer as et } from "@habemus-papadum/aiui-claude-channel";
9
- import { createInterface as tt } from "node:readline/promises";
10
- import g from "chalk";
11
- import { createRequire as ot } from "node:module";
12
- import { spawn as rt } from "node:child_process";
13
- import { fileURLToPath as nt } from "node:url";
14
- const Ce = "DevToolsActivePort", ae = 2e4;
15
- async function I(e) {
16
- const t = it(e);
17
- if (t !== void 0 && await at(t))
18
- return { browserUrl: `http://127.0.0.1:${t}`, port: t };
19
- }
20
- function it(e) {
21
- try {
22
- const [t] = P(l(e, Ce), "utf8").split(`
23
- `), o = Number(t);
24
- return Number.isInteger(o) && o > 0 ? o : void 0;
25
- } catch {
26
- return;
2
+ import { Command as gt } from "commander";
3
+ import { join as f, dirname as R, resolve as P, delimiter as bt, relative as yt } from "node:path";
4
+ import { CHROME_CHANNELS as vt, cacheDir as Y, packageRoot as L, runningFromSource as kt, isCi as ue, discoverSessionBrowser as F, openInSessionBrowser as H, sessionBrowserBinary as Fe, launchSessionBrowser as Ve, decideBrowserAction as Ct } from "@habemus-papadum/aiui-util";
5
+ import { execa as k } from "execa";
6
+ import { readFileSync as U, mkdirSync as W, writeFileSync as q, existsSync as v, realpathSync as He, readdirSync as G, accessSync as $t, constants as xt, rmSync as Tt, lstatSync as Pt, cpSync as Et, renameSync as Dt } from "node:fs";
7
+ import { detectBrowserPlatform as Ke, resolveBuildId as Ye, Browser as I, getInstalledBrowsers as We, install as It, uninstall as St } from "@puppeteer/browsers";
8
+ import { projectCacheDir as de, listMcpServers as J, selectMcpServer as qe } from "@habemus-papadum/aiui-claude-channel";
9
+ import { createInterface as Ot } from "node:readline/promises";
10
+ import l from "chalk";
11
+ import { spawn as jt } from "node:child_process";
12
+ import { r as Ut } from "./sidecars-BKlhUA0V.js";
13
+ import { homedir as At, networkInterfaces as Nt } from "node:os";
14
+ import { Separator as ie, select as K, input as _t } from "@inquirer/prompts";
15
+ import { fileURLToPath as Ge } from "node:url";
16
+ import { aiuiDevOverlay as Rt } from "@habemus-papadum/aiui-dev-overlay/vite";
17
+ import { createServer as Lt } from "vite";
18
+ const Mt = ["prompt", "auto", "off"], Bt = ["attach", "launch"], se = ["loopback", "host"], M = [
19
+ {
20
+ name: "claude",
21
+ summary: "how `aiui claude` launches Claude Code",
22
+ fields: [
23
+ {
24
+ key: "skipPermissions",
25
+ type: "boolean",
26
+ default: !0,
27
+ defaultText: "true (unset: the first interactive launch asks, then persists the answer)",
28
+ summary: "Launch Claude Code with --dangerously-skip-permissions.",
29
+ doc: "A personal preference with real consequences (docs/guide/warning): every agent action — shell commands, file writes, network, the browser — runs without asking first. aiui works fine either way. The first interactive launch asks and persists the answer at the user level; when unset, non-interactive sessions fall back to true."
30
+ },
31
+ {
32
+ key: "enterNudge",
33
+ type: "boolean",
34
+ default: !0,
35
+ defaultText: "true (unset: the first interactive launch asks, then persists the answer)",
36
+ summary: "Auto-dismiss Claude Code's development-channel acknowledgement prompt.",
37
+ doc: "aiui loads a custom development channel, so Claude Code shows a one-key acknowledgement at every startup; this injects a single Enter keystroke into the terminal to dismiss it (best-effort TIOCSTI on /dev/tty — platforms that forbid it harmlessly do nothing). Saying no just means pressing Enter yourself each launch."
38
+ }
39
+ ]
40
+ },
41
+ {
42
+ name: "channel",
43
+ summary: "the channel server's web backend",
44
+ fields: [
45
+ {
46
+ key: "bind",
47
+ type: "enum",
48
+ values: se,
49
+ default: "loopback",
50
+ defaultText: '"loopback" (unset: the first interactive launch asks, then persists the answer)',
51
+ summary: "Which interface the channel web server binds: loopback, or host (LAN).",
52
+ doc: '"host" (0.0.0.0) makes the session\'s whole web surface — the iPad paint page, but also prompt injection, /debug, and every sidecar — reachable by anyone on your network, UNAUTHENTICATED. That is the trusted-LAN posture (docs/guide/warning): right on a network that is yours alone, wrong on shared Wi-Fi. "loopback" keeps everything this-machine-only; reaching the paint page from an iPad is then up to you — tunnel the channel port however you like (Tailscale, `ssh -L`). The first interactive launch asks and persists the answer at the user level. Per-launch flag: --aiui-bind.'
53
+ }
54
+ ]
55
+ },
56
+ {
57
+ name: "sidecars",
58
+ summary: "which session sidecars `aiui claude` asks the channel to host",
59
+ fields: [
60
+ {
61
+ key: "paint",
62
+ type: "boolean",
63
+ default: !0,
64
+ summary: "Host the iPad paint sidecar (on the channel's own port).",
65
+ doc: "The iPad paint stream (docs/guide/paint-stream) rides the channel's one port — no extra process, no extra listener — so it is on by default; false turns it off. Whether an iPad can actually reach it is channel.bind's call (host, or a tunnel you own). Per-launch flags win: --aiui-sidecar paint / --aiui-no-sidecar paint. `aiui paint url` prints the URL to open on the iPad."
66
+ }
67
+ ]
68
+ },
69
+ {
70
+ name: "chrome",
71
+ summary: "the agent's browser and the Chrome DevTools MCP",
72
+ fields: [
73
+ {
74
+ key: "enabled",
75
+ type: "boolean",
76
+ default: !0,
77
+ summary: "Attach the Chrome DevTools MCP.",
78
+ doc: "false turns it off everywhere; true restates the default and does NOT override the CI default-off — only the --aiui-chrome flag forces it on under CI."
79
+ },
80
+ {
81
+ key: "mode",
82
+ type: "enum",
83
+ values: Bt,
84
+ default: "attach",
85
+ summary: "How the MCP reaches a browser: shared session browser, or its own.",
86
+ doc: `"attach" shares a user-visible session browser: an already-running one is discovered by profile, or an interactive launch starts one eagerly. "launch" is the hands-off mode: chrome-devtools-mcp launches its own private browser lazily, on the agent's first browser tool call.`
87
+ },
88
+ {
89
+ key: "browserUrl",
90
+ type: "string",
91
+ defaultText: "unset (manage a browser locally)",
92
+ summary: "Attach to this DevTools endpoint instead of managing a browser at all.",
93
+ doc: 'The remote-development key (docs/guide/remote): the browser runs on another machine (started there with `aiui browser`) and its debug port is tunneled over. Setting it implies mode: "attach" and makes every local-browser setting (profile, executablePath, channel, forTesting…) irrelevant. Per-launch flag: --aiui-browser-url.',
94
+ validate: (e) => Ft(String(e)) ? void 0 : 'expected an http(s) URL like "http://127.0.0.1:9222"'
95
+ },
96
+ {
97
+ key: "debugPort",
98
+ type: "number",
99
+ default: 0,
100
+ defaultText: "0 (an OS-assigned free port)",
101
+ summary: "Fixed DevTools debug port for session browsers aiui launches.",
102
+ doc: "Pin it (e.g. 9222) when something external must find the port — an ssh tunnel, a VS Code attach-to-Chrome launch config. 0 means an OS-assigned free port.",
103
+ validate: (e) => Number.isInteger(e) && e >= 0 && e <= 65535 ? void 0 : "expected 0..65535"
104
+ },
105
+ {
106
+ key: "profile",
107
+ type: "string",
108
+ default: "default",
109
+ summary: "Named profile under .aiui-cache/chrome/.",
110
+ doc: "Per-launch flag: --aiui-chrome-profile."
111
+ },
112
+ {
113
+ key: "dataDir",
114
+ type: "string",
115
+ defaultText: "unset (derived from chrome.profile)",
116
+ summary: "Explicit Chrome user data dir; takes precedence over chrome.profile.",
117
+ doc: "Per-launch flag: --aiui-chrome-data-dir."
118
+ },
119
+ {
120
+ key: "executablePath",
121
+ type: "string",
122
+ defaultText: "unset (managed Chrome for Testing when installed, else installed Chrome)",
123
+ summary: "Chrome binary to launch — e.g. a Chrome for Testing install.",
124
+ doc: "Chrome for Testing still honors --load-extension, so the aiui DevTools panel can auto-load. Mutually exclusive with chrome.channel."
125
+ },
126
+ {
127
+ key: "channel",
128
+ type: "enum",
129
+ values: vt,
130
+ defaultText: 'unset ("stable" when launching an installed Chrome)',
131
+ summary: "Installed Chrome release channel to launch.",
132
+ doc: "Mutually exclusive with chrome.executablePath."
133
+ },
134
+ {
135
+ key: "forTesting",
136
+ type: "enum",
137
+ values: Mt,
138
+ default: "prompt",
139
+ summary: "How `aiui claude` manages the recommended Chrome for Testing install.",
140
+ doc: '"prompt" asks before installing or updating it — interactive sessions only, never under CI; "auto" installs/updates without asking; "off" never checks. Prompt answers ("automatically", "never ask again") persist here at the user level. Skipped entirely when executablePath or channel picks a browser explicitly.'
141
+ },
142
+ {
143
+ key: "headless",
144
+ type: "boolean",
145
+ default: !1,
146
+ summary: "Launch Chrome with no UI."
147
+ },
148
+ {
149
+ key: "buildExtension",
150
+ type: "boolean",
151
+ default: !0,
152
+ summary: "Rebuild the aiui-devtools-extension whenever a browser starts in a dev checkout.",
153
+ doc: "~0.3s of tsc so the auto-loaded DevTools panel is never stale. Only relevant in a dev checkout of pdum_aiui."
154
+ }
155
+ ]
27
156
  }
157
+ ];
158
+ function Je() {
159
+ return M.flatMap(
160
+ (e) => e.fields.map((t) => ({ section: e, field: t, path: `${e.name}.${t.key}` }))
161
+ );
28
162
  }
29
- async function at(e) {
30
- try {
31
- return (await fetch(`http://127.0.0.1:${e}/json/version`, {
32
- signal: AbortSignal.timeout(1e3)
33
- })).ok;
34
- } catch {
35
- return !1;
36
- }
163
+ function ze(e) {
164
+ return e.type === "enum" ? "string" : e.type;
37
165
  }
38
- function Ee(e) {
39
- return e.executablePath ? e.executablePath : ze({
40
- browser: x.CHROME,
41
- channel: st[e.channel ?? "stable"]
42
- });
166
+ function Xe(e, t) {
167
+ var n;
168
+ return e.type === "enum" && !(e.values ?? []).includes(String(t)) ? `expected one of: ${(e.values ?? []).join(", ")}` : (n = e.validate) == null ? void 0 : n.call(e, t);
43
169
  }
44
- const st = {
45
- stable: O.STABLE,
46
- beta: O.BETA,
47
- dev: O.DEV,
48
- canary: O.CANARY
49
- };
50
- async function $e(e) {
51
- k(e.userDataDir, { recursive: !0 }), Ye(l(e.userDataDir, Ce), { force: !0 });
52
- const t = [
53
- `--remote-debugging-port=${e.debugPort ?? 0}`,
54
- `--user-data-dir=${e.userDataDir}`,
55
- "--no-first-run",
56
- "--no-default-browser-check"
57
- ];
58
- e.extensionDir && t.push(`--load-extension=${e.extensionDir}`), e.headless && t.push("--headless"), t.push(e.startUrl ?? "about:blank");
59
- const o = p(e.binary, t, {
60
- detached: !0,
61
- stdio: "ignore",
62
- reject: !1,
63
- cleanup: !1
64
- });
65
- o.unref();
66
- let n = !1;
67
- o.then(() => {
68
- n = !0;
69
- });
70
- const r = Date.now() + ae;
71
- for (; Date.now() < r; ) {
72
- const i = await I(e.userDataDir);
73
- if (i) {
74
- try {
75
- T(
76
- l(e.userDataDir, "aiui-browser.json"),
77
- `${JSON.stringify({ pid: o.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() })}
78
- `
79
- );
80
- } catch {
81
- }
82
- return i;
170
+ function ae(e, t) {
171
+ let n;
172
+ switch (ze(e)) {
173
+ case "boolean": {
174
+ if (t !== "true" && t !== "false")
175
+ return { error: "expected true or false" };
176
+ n = t === "true";
177
+ break;
83
178
  }
84
- if (n)
85
- throw new Error(
86
- "the browser exited before exposing its DevTools endpoint — is another Chrome already running on this profile without a debug port? Close it and retry."
87
- );
88
- await ct(250);
179
+ case "number": {
180
+ if (n = Number(t), t.trim() === "" || Number.isNaN(n))
181
+ return { error: "expected a number" };
182
+ break;
183
+ }
184
+ default:
185
+ n = t;
89
186
  }
90
- throw new Error(
91
- `the browser did not expose its DevTools endpoint within ${ae / 1e3}s`
92
- );
187
+ const o = Xe(e, n);
188
+ return o ? { error: o } : { value: n };
93
189
  }
94
- async function Pe(e, t) {
95
- const o = e.replace(/\/+$/, ""), n = await fetch(`${o}/json/new?${encodeURI(t)}`, {
96
- method: "PUT",
97
- signal: AbortSignal.timeout(3e3)
98
- });
99
- if (!n.ok)
100
- throw new Error(`the browser refused to open the tab (${n.status} ${n.statusText})`);
190
+ function $(e) {
191
+ return typeof e == "string" ? JSON.stringify(e) : String(e);
101
192
  }
102
- function ct(e) {
103
- return new Promise((t) => setTimeout(t, e));
193
+ function ce(e) {
194
+ return e.defaultText ? e.defaultText : e.default === void 0 ? "unset" : $(e.default);
195
+ }
196
+ function Ft(e) {
197
+ try {
198
+ const t = new URL(e);
199
+ return t.protocol === "http:" || t.protocol === "https:";
200
+ } catch {
201
+ return !1;
202
+ }
104
203
  }
105
- const se = "config.json", ce = ["stable", "beta", "dev", "canary"], le = ["prompt", "auto", "off"], ue = ["attach", "launch"];
106
- function De(e = process.cwd()) {
204
+ const Ee = "config.json";
205
+ function he(e = process.cwd()) {
107
206
  return {
108
- user: l(q(void 0, { create: !1 }), se),
109
- project: l(xe(e), se)
207
+ user: f(Y(void 0, { create: !1 }), Ee),
208
+ project: f(de(e), Ee)
110
209
  };
111
210
  }
112
- function N(e = process.cwd()) {
113
- const t = De(e);
114
- return lt(Y(t.user) ?? {}, Y(t.project) ?? {});
211
+ function V(e = process.cwd()) {
212
+ const t = he(e);
213
+ return Qe(O(t.user) ?? {}, O(t.project) ?? {});
115
214
  }
116
- function lt(e, t) {
117
- return {
118
- claude: { ...e.claude, ...t.claude },
119
- chrome: { ...e.chrome, ...t.chrome }
120
- };
215
+ function Qe(e, t) {
216
+ const n = {};
217
+ for (const o of M)
218
+ n[o.name] = {
219
+ ...e[o.name],
220
+ ...t[o.name]
221
+ };
222
+ return n;
121
223
  }
122
- function Y(e) {
224
+ function O(e) {
123
225
  let t;
124
226
  try {
125
- t = P(e, "utf8");
227
+ t = U(e, "utf8");
126
228
  } catch {
127
229
  return;
128
230
  }
129
- let o;
231
+ let n;
130
232
  try {
131
- o = JSON.parse(t);
132
- } catch (n) {
233
+ n = JSON.parse(t);
234
+ } catch (o) {
133
235
  throw new Error(
134
- `invalid JSON in ${e}: ${n instanceof Error ? n.message : String(n)}`
236
+ `invalid JSON in ${e}: ${o instanceof Error ? o.message : String(o)}`
135
237
  );
136
238
  }
137
- return ut(o, e);
138
- }
139
- function ut(e, t) {
140
- const o = H(e, t, "the top level");
141
- B(o, ["claude", "chrome"], t, "the top level");
142
- const n = {};
143
- if (o.claude !== void 0) {
144
- const r = H(o.claude, t, '"claude"');
145
- B(r, ["skipPermissions", "enterNudge"], t, '"claude"'), n.claude = de({
146
- skipPermissions: w(
147
- r.skipPermissions,
148
- "boolean",
149
- t,
150
- "claude.skipPermissions"
151
- ),
152
- enterNudge: w(r.enterNudge, "boolean", t, "claude.enterNudge")
153
- });
154
- }
155
- if (o.chrome !== void 0) {
156
- const r = H(o.chrome, t, '"chrome"');
157
- B(
158
- r,
159
- [
160
- "enabled",
161
- "mode",
162
- "browserUrl",
163
- "debugPort",
164
- "profile",
165
- "dataDir",
166
- "executablePath",
167
- "channel",
168
- "forTesting",
169
- "headless",
170
- "buildExtension"
171
- ],
239
+ return Vt(n, e);
240
+ }
241
+ function Vt(e, t) {
242
+ const n = De(e, t, "the top level");
243
+ Ie(
244
+ n,
245
+ M.map((r) => r.name),
246
+ t,
247
+ "the top level"
248
+ );
249
+ const o = {};
250
+ for (const r of M) {
251
+ if (n[r.name] === void 0)
252
+ continue;
253
+ const i = De(n[r.name], t, `"${r.name}"`);
254
+ Ie(
255
+ i,
256
+ r.fields.map((a) => a.key),
172
257
  t,
173
- '"chrome"'
258
+ `"${r.name}"`
174
259
  );
175
- const i = w(r.channel, "string", t, "chrome.channel");
176
- if (i !== void 0 && !ce.includes(i))
177
- throw new Error(
178
- `invalid chrome.channel "${i}" in ${t} — expected one of: ${ce.join(", ")}`
179
- );
180
- const a = w(r.forTesting, "string", t, "chrome.forTesting");
181
- if (a !== void 0 && !le.includes(a))
182
- throw new Error(
183
- `invalid chrome.forTesting "${a}" in ${t} — expected one of: ${le.join(", ")}`
184
- );
185
- const s = w(r.mode, "string", t, "chrome.mode");
186
- if (s !== void 0 && !ue.includes(s))
187
- throw new Error(
188
- `invalid chrome.mode "${s}" in ${t} — expected one of: ${ue.join(", ")}`
189
- );
190
- const c = w(r.browserUrl, "string", t, "chrome.browserUrl");
191
- if (c !== void 0 && !dt(c))
192
- throw new Error(
193
- `invalid chrome.browserUrl "${c}" in ${t} — expected an http(s) URL like "http://127.0.0.1:9222"`
194
- );
195
- const u = w(r.debugPort, "number", t, "chrome.debugPort");
196
- if (u !== void 0 && !(Number.isInteger(u) && u >= 0 && u <= 65535))
197
- throw new Error(`invalid chrome.debugPort ${u} in ${t} — expected 0..65535`);
198
- n.chrome = de({
199
- enabled: w(r.enabled, "boolean", t, "chrome.enabled"),
200
- mode: s,
201
- browserUrl: c,
202
- debugPort: u,
203
- profile: w(r.profile, "string", t, "chrome.profile"),
204
- dataDir: w(r.dataDir, "string", t, "chrome.dataDir"),
205
- executablePath: w(r.executablePath, "string", t, "chrome.executablePath"),
206
- channel: i,
207
- forTesting: a,
208
- headless: w(r.headless, "boolean", t, "chrome.headless"),
209
- buildExtension: w(r.buildExtension, "boolean", t, "chrome.buildExtension")
210
- });
211
- }
212
- return n;
213
- }
214
- function dt(e) {
215
- try {
216
- const t = new URL(e);
217
- return t.protocol === "http:" || t.protocol === "https:";
218
- } catch {
219
- return !1;
260
+ const s = {};
261
+ for (const a of r.fields) {
262
+ const c = i[a.key];
263
+ if (c === void 0)
264
+ continue;
265
+ const d = `${r.name}.${a.key}`, p = ze(a);
266
+ if (typeof c !== p)
267
+ throw new Error(`expected a ${p} for ${d} in ${t}`);
268
+ const h = Xe(a, c);
269
+ if (h)
270
+ throw new Error(
271
+ `invalid ${d} ${$(c)} in ${t} ${h}`
272
+ );
273
+ s[a.key] = c;
274
+ }
275
+ o[r.name] = s;
220
276
  }
277
+ return o;
221
278
  }
222
- function U(e) {
223
- const t = De().user, o = Y(t) ?? {};
224
- return e(o), k(S(t), { recursive: !0 }), T(t, `${JSON.stringify(o, null, 2)}
225
- `), t;
279
+ function B(e) {
280
+ return fe(he().user, e);
226
281
  }
227
- function de(e) {
228
- return Object.fromEntries(Object.entries(e).filter(([, t]) => t !== void 0));
282
+ function fe(e, t) {
283
+ const n = O(e) ?? {};
284
+ return t(n), W(R(e), { recursive: !0 }), q(e, `${JSON.stringify(n, null, 2)}
285
+ `), e;
229
286
  }
230
- function H(e, t, o) {
287
+ function De(e, t, n) {
231
288
  if (typeof e != "object" || e === null || Array.isArray(e))
232
- throw new Error(`expected an object at ${o} of ${t}`);
289
+ throw new Error(`expected an object at ${n} of ${t}`);
233
290
  return e;
234
291
  }
235
- function B(e, t, o, n) {
292
+ function Ie(e, t, n, o) {
236
293
  for (const r of Object.keys(e))
237
294
  if (!t.includes(r))
238
295
  throw new Error(
239
- `unknown key "${r}" at ${n} of ${o} — known keys: ${t.join(", ")}`
296
+ `unknown key "${r}" at ${o} of ${n} — known keys: ${t.join(", ")}`
240
297
  );
241
298
  }
242
- function w(e, t, o, n) {
243
- if (e !== void 0) {
244
- if (typeof e !== t)
245
- throw new Error(`expected a ${t} for ${n} in ${o}`);
246
- return e;
247
- }
248
- }
249
- async function G(e, t, o) {
250
- const n = tt({ input: process.stdin, output: process.stderr });
299
+ async function z(e, t, n) {
300
+ const o = Ot({ input: process.stdin, output: process.stderr });
251
301
  try {
252
302
  const r = t.map(
253
- (i) => ` ${g.bold(`[${i.key === o ? i.key.toUpperCase() : i.key}]`)} ${i.label}`
303
+ (i) => ` ${l.bold(`[${i.key === n ? i.key.toUpperCase() : i.key}]`)} ${i.label}`
254
304
  ).join(`
255
305
  `);
256
306
  for (; ; ) {
257
- const a = (await n.question(`${g.cyan(e)}
307
+ const s = (await o.question(`${l.cyan(e)}
258
308
  ${r}
259
309
  > `)).trim().toLowerCase();
260
- if (!a) {
261
- if (o !== void 0)
262
- return o;
310
+ if (!s) {
311
+ if (n !== void 0)
312
+ return n;
263
313
  continue;
264
314
  }
265
- const s = t.find((c) => c.key === a) ?? t.find((c) => c.label.toLowerCase().startsWith(a));
266
- if (s)
267
- return s.key;
315
+ const a = t.find((c) => c.key === s) ?? t.find((c) => c.label.toLowerCase().startsWith(s));
316
+ if (a)
317
+ return a.key;
268
318
  }
269
319
  } finally {
270
- n.close();
320
+ o.close();
271
321
  }
272
322
  }
273
- function m(e, t) {
274
- console.error(`${g.bgRed.white.bold(" ERROR ")} ${g.red.bold(e)}`), t && console.error(g.dim(t));
323
+ function g(e, t) {
324
+ console.error(`${l.bgRed.white.bold(" ERROR ")} ${l.red.bold(e)}`), t && console.error(l.dim(t));
275
325
  }
276
- function _(e, t) {
277
- console.error(`${g.bgYellow.black.bold(" WARN ")} ${g.yellow.bold(e)}`), t && console.error(g.dim(t));
326
+ function T(e, t) {
327
+ console.error(`${l.bgYellow.black.bold(" WARN ")} ${l.yellow.bold(e)}`), t && console.error(l.dim(t));
278
328
  }
279
- function v(e, t) {
280
- console.error(`${g.bgCyan.black.bold(" NOTE ")} ${g.cyan(e)}`), t && console.error(g.dim(t));
329
+ function y(e, t) {
330
+ console.error(`${l.bgCyan.black.bold(" NOTE ")} ${l.cyan(e)}`), t && console.error(l.dim(t));
281
331
  }
282
- const ke = 1440 * 60 * 1e3, ht = 4e3;
332
+ const Ze = 1440 * 60 * 1e3, Ht = 4e3;
283
333
  function A(e = !0) {
284
- return q("chrome", { create: e });
334
+ return Y("chrome", { create: e });
285
335
  }
286
- const Te = "update-state.json";
287
- function R() {
336
+ const et = "update-state.json";
337
+ function X() {
288
338
  try {
289
- return JSON.parse(P(l(A(!1), Te), "utf8"));
339
+ return JSON.parse(U(f(A(!1), et), "utf8"));
290
340
  } catch {
291
341
  return {};
292
342
  }
293
343
  }
294
- function j(e) {
344
+ function Q(e) {
295
345
  const t = A();
296
- k(t, { recursive: !0 }), T(l(t, Te), `${JSON.stringify({ ...R(), ...e })}
346
+ W(t, { recursive: !0 }), q(f(t, et), `${JSON.stringify({ ...X(), ...e })}
297
347
  `);
298
348
  }
299
- function J(e, t) {
300
- const o = e.split(".").map(Number), n = t.split(".").map(Number);
301
- for (let r = 0; r < Math.max(o.length, n.length); r++) {
302
- const i = (o[r] ?? 0) - (n[r] ?? 0);
349
+ function pe(e, t) {
350
+ const n = e.split(".").map(Number), o = t.split(".").map(Number);
351
+ for (let r = 0; r < Math.max(n.length, o.length); r++) {
352
+ const i = (n[r] ?? 0) - (o[r] ?? 0);
303
353
  if (i)
304
354
  return i < 0 ? -1 : 1;
305
355
  }
306
356
  return 0;
307
357
  }
308
- async function W() {
358
+ async function me() {
309
359
  const e = A(!1);
310
- if (!b(e))
360
+ if (!v(e))
311
361
  return;
312
- const n = (await ve({ cacheDir: e })).filter((r) => r.browser === x.CHROME).sort((r, i) => J(r.buildId, i.buildId)).at(-1);
313
- return n && { buildId: n.buildId, executablePath: n.executablePath };
362
+ const o = (await We({ cacheDir: e })).filter((r) => r.browser === I.CHROME).sort((r, i) => pe(r.buildId, i.buildId)).at(-1);
363
+ return o && { buildId: o.buildId, executablePath: o.executablePath };
314
364
  }
315
- async function z(e = {}) {
316
- const { maxAgeMs: t = ke, timeoutMs: o = ht, now: n = Date.now() } = e, r = R();
317
- if (r.latestBuildId && r.checkedAt && n - r.checkedAt < t)
365
+ async function we(e = {}) {
366
+ const { maxAgeMs: t = Ze, timeoutMs: n = Ht, now: o = Date.now() } = e, r = X();
367
+ if (r.latestBuildId && r.checkedAt && o - r.checkedAt < t)
318
368
  return r.latestBuildId;
319
- const i = be();
369
+ const i = Ke();
320
370
  if (i)
321
371
  try {
322
- const a = await wt(
323
- ye(x.CHROME, i, "stable"),
324
- o
372
+ const s = await qt(
373
+ Ye(I.CHROME, i, "stable"),
374
+ n
325
375
  );
326
- return j({ checkedAt: n, latestBuildId: a }), a;
376
+ return Q({ checkedAt: o, latestBuildId: s }), s;
327
377
  } catch {
328
378
  return r.latestBuildId;
329
379
  }
330
380
  }
331
- async function C(e) {
332
- const t = A(), o = await Xe({
333
- browser: x.CHROME,
381
+ async function S(e) {
382
+ const t = A(), n = await It({
383
+ browser: I.CHROME,
334
384
  buildId: e,
335
385
  cacheDir: t,
336
386
  downloadProgressCallback: "default"
337
- }), n = (await ve({ cacheDir: t })).filter(
338
- (r) => r.browser === x.CHROME && r.buildId !== e
387
+ }), o = (await We({ cacheDir: t })).filter(
388
+ (r) => r.browser === I.CHROME && r.buildId !== e
339
389
  );
340
- for (const r of n)
341
- await Ze({ browser: x.CHROME, buildId: r.buildId, cacheDir: t });
342
- return { buildId: e, executablePath: o.executablePath };
390
+ for (const r of o)
391
+ await St({ browser: I.CHROME, buildId: r.buildId, cacheDir: t });
392
+ return { buildId: e, executablePath: n.executablePath };
343
393
  }
344
- async function ft(e) {
345
- const t = be();
394
+ async function Kt(e) {
395
+ const t = Ke();
346
396
  if (!t)
347
397
  throw new Error("could not detect a supported platform for Chrome for Testing");
348
- const o = await ye(x.CHROME, t, "stable");
349
- j({ checkedAt: Date.now(), latestBuildId: o });
350
- const n = await W();
351
- if (n && J(n.buildId, o) >= 0)
352
- return e(`Chrome for Testing ${n.buildId} is up to date`), { ...n, outcome: "current" };
398
+ const n = await Ye(I.CHROME, t, "stable");
399
+ Q({ checkedAt: Date.now(), latestBuildId: n });
400
+ const o = await me();
401
+ if (o && pe(o.buildId, n) >= 0)
402
+ return e(`Chrome for Testing ${o.buildId} is up to date`), { ...o, outcome: "current" };
353
403
  e(
354
- n ? `updating Chrome for Testing ${n.buildId} → ${o}…` : `installing Chrome for Testing ${o}…`
404
+ o ? `updating Chrome for Testing ${o.buildId} → ${n}…` : `installing Chrome for Testing ${n}…`
355
405
  );
356
- const r = await C(o);
357
- return e(`Chrome for Testing ${o} installed at ${r.executablePath}`), { ...r, outcome: n ? "updated" : "installed" };
406
+ const r = await S(n);
407
+ return e(`Chrome for Testing ${n} installed at ${r.executablePath}`), { ...r, outcome: o ? "updated" : "installed" };
358
408
  }
359
- async function Ie(e) {
360
- const { mode: t, interactive: o, now: n = Date.now() } = e, r = await W();
361
- if (t === "off" || !o)
409
+ async function tt(e) {
410
+ const { mode: t, interactive: n, now: o = Date.now() } = e, r = await me();
411
+ if (t === "off" || !n)
362
412
  return r == null ? void 0 : r.executablePath;
363
413
  if (!r)
364
- return mt(t, n);
365
- const i = await z({ now: n });
366
- return !i || J(i, r.buildId) <= 0 ? r.executablePath : pt(t, r, i);
414
+ return Yt(t, o);
415
+ const i = await we({ now: o });
416
+ return !i || pe(i, r.buildId) <= 0 ? r.executablePath : Wt(t, r, i);
367
417
  }
368
- async function mt(e, t) {
369
- const o = await z({ now: t });
370
- if (!o)
418
+ async function Yt(e, t) {
419
+ const n = await we({ now: t });
420
+ if (!n)
371
421
  return;
372
422
  if (e === "auto")
373
- return v(`installing Chrome for Testing ${o} (chrome.forTesting: "auto")…`), (await C(o)).executablePath;
374
- const n = R();
375
- if (n.installDeclinedAt && t - n.installDeclinedAt < ke)
423
+ return y(`installing Chrome for Testing ${n} (chrome.forTesting: "auto")…`), (await S(n)).executablePath;
424
+ const o = X();
425
+ if (o.installDeclinedAt && t - o.installDeclinedAt < Ze)
376
426
  return;
377
- const r = await G(
378
- `Chrome for Testing isn't installed. It's the recommended browser for aiui — version-pinned, separate from your real Chrome, and it auto-loads the aiui DevTools panel (branded Chrome can't). Download ${o} (~160 MB) to ${A(!1)}?`,
427
+ const r = await z(
428
+ `Chrome for Testing isn't installed. It's the recommended browser for aiui — version-pinned, separate from your real Chrome, and it auto-loads the aiui DevTools panel (branded Chrome can't). Download ${n} (~160 MB) to ${A(!1)}?`,
379
429
  [
380
430
  { key: "y", label: "yes, install it" },
381
431
  { key: "n", label: "not now — use the regular Chrome (asks again tomorrow)" },
@@ -384,177 +434,163 @@ async function mt(e, t) {
384
434
  "y"
385
435
  );
386
436
  if (r === "y")
387
- return (await C(o)).executablePath;
437
+ return (await S(n)).executablePath;
388
438
  if (r === "never") {
389
- const i = U((a) => {
390
- a.chrome = { ...a.chrome, forTesting: "off" };
439
+ const i = B((s) => {
440
+ s.chrome = { ...s.chrome, forTesting: "off" };
391
441
  });
392
- v(`wrote chrome.forTesting: "off" to ${i}`);
442
+ y(`wrote chrome.forTesting: "off" to ${i}`);
393
443
  } else
394
- j({ installDeclinedAt: t });
444
+ Q({ installDeclinedAt: t });
395
445
  }
396
- async function pt(e, t, o) {
446
+ async function Wt(e, t, n) {
397
447
  if (e === "auto")
398
- return v(
399
- `updating Chrome for Testing ${t.buildId} → ${o} (chrome.forTesting: "auto")…`
400
- ), (await C(o)).executablePath;
401
- if (R().skippedBuildId === o)
448
+ return y(
449
+ `updating Chrome for Testing ${t.buildId} → ${n} (chrome.forTesting: "auto")…`
450
+ ), (await S(n)).executablePath;
451
+ if (X().skippedBuildId === n)
402
452
  return t.executablePath;
403
- switch (await G(
404
- `Your Chrome for Testing (${t.buildId}) is out of date — latest stable is ${o}. Update?`,
453
+ switch (await z(
454
+ `Your Chrome for Testing (${t.buildId}) is out of date — latest stable is ${n}. Update?`,
405
455
  [
406
456
  { key: "y", label: "yes, just this once" },
407
457
  { key: "a", label: 'automatically, now and from here on (writes chrome.forTesting: "auto")' },
408
458
  {
409
459
  key: "s",
410
- label: `skip ${o} — keep ${t.buildId}, don't ask again for this version`
460
+ label: `skip ${n} — keep ${t.buildId}, don't ask again for this version`
411
461
  },
412
462
  { key: "never", label: 'never ask again (writes chrome.forTesting: "off")' }
413
463
  ],
414
464
  "y"
415
465
  )) {
416
466
  case "y":
417
- return (await C(o)).executablePath;
467
+ return (await S(n)).executablePath;
418
468
  case "a": {
419
- const r = U((i) => {
469
+ const r = B((i) => {
420
470
  i.chrome = { ...i.chrome, forTesting: "auto" };
421
471
  });
422
- return v(`wrote chrome.forTesting: "auto" to ${r}`), (await C(o)).executablePath;
472
+ return y(`wrote chrome.forTesting: "auto" to ${r}`), (await S(n)).executablePath;
423
473
  }
424
474
  case "never": {
425
- const r = U((i) => {
475
+ const r = B((i) => {
426
476
  i.chrome = { ...i.chrome, forTesting: "off" };
427
477
  });
428
- return v(`wrote chrome.forTesting: "off" to ${r}`), t.executablePath;
478
+ return y(`wrote chrome.forTesting: "off" to ${r}`), t.executablePath;
429
479
  }
430
480
  default:
431
- return j({ skippedBuildId: o }), t.executablePath;
481
+ return Q({ skippedBuildId: n }), t.executablePath;
432
482
  }
433
483
  }
434
- async function wt(e, t) {
435
- let o;
484
+ async function qt(e, t) {
485
+ let n;
436
486
  try {
437
487
  return await Promise.race([
438
488
  e,
439
- new Promise((n, r) => {
440
- o = setTimeout(() => r(new Error(`timed out after ${t}ms`)), t);
489
+ new Promise((o, r) => {
490
+ n = setTimeout(() => r(new Error(`timed out after ${t}ms`)), t);
441
491
  })
442
492
  ]);
443
493
  } finally {
444
- clearTimeout(o);
494
+ clearTimeout(n);
445
495
  }
446
496
  }
447
- const gt = ot(import.meta.url);
448
- function D(e) {
449
- const t = e.split("/");
450
- for (const o of gt.resolve.paths(e) ?? []) {
451
- const n = l(o, ...t, "package.json");
452
- if (b(n))
453
- return S(n);
454
- }
455
- throw new Error(`could not locate the "${e}" package (is it installed?)`);
456
- }
457
- function X(e, t) {
458
- const o = D(e), n = JSON.parse(P(l(o, "package.json"), "utf8")), r = typeof n.bin == "string" ? n.bin : bt(n.bin);
497
+ function ge(e, t) {
498
+ const n = L(e), o = JSON.parse(U(f(n, "package.json"), "utf8")), r = typeof o.bin == "string" ? o.bin : Gt(o.bin);
459
499
  if (!r)
460
500
  throw new Error(
461
501
  `package ${e} declares no bin`
462
502
  );
463
- if (b(l(o, "src"))) {
503
+ if (kt(n)) {
464
504
  const i = r.replace(/^\.?\/?dist\//, "src/").replace(/\.js$/, ".ts");
465
- return { command: process.execPath, args: ["--import", "tsx", E(o, i)] };
505
+ return { command: process.execPath, args: ["--import", "tsx", P(n, i)] };
466
506
  }
467
- return { command: process.execPath, args: [E(o, r)] };
507
+ return { command: process.execPath, args: [P(n, r)] };
468
508
  }
469
- function bt(e) {
509
+ function Gt(e) {
470
510
  return e ? Object.values(e)[0] : void 0;
471
511
  }
472
- const yt = "chrome-devtools", Ae = "@habemus-papadum/aiui-devtools-extension", vt = "default", xt = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
473
- function Oe(e, t = {}, o = process.env) {
474
- return e.noChrome ? !1 : e.chrome ? !0 : t.enabled === !1 ? !1 : !Z(o);
475
- }
476
- function Z(e = process.env) {
477
- const t = e.CI;
478
- return t !== void 0 && t !== "" && t !== "0" && t.toLowerCase() !== "false";
512
+ const Jt = "chrome-devtools", nt = "@habemus-papadum/aiui-devtools-extension", zt = "default", Xt = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
513
+ function ot(e, t = {}, n = process.env) {
514
+ return e.noChrome ? !1 : e.chrome ? !0 : t.enabled === !1 ? !1 : !ue(n);
479
515
  }
480
- function $(e, t = {}, o = process.cwd()) {
516
+ function D(e, t = {}, n = process.cwd()) {
481
517
  if (t.executablePath && t.channel)
482
518
  throw new Error(
483
519
  "config sets both chrome.executablePath and chrome.channel — they pick the browser two different ways; keep exactly one"
484
520
  );
485
- const n = e.chromeProfile !== void 0 || e.chromeDataDir !== void 0, r = e.chromeDataDir ?? (n ? void 0 : t.dataDir), i = e.chromeProfile ?? (n ? void 0 : t.profile);
521
+ const o = e.chromeProfile !== void 0 || e.chromeDataDir !== void 0, r = e.chromeDataDir ?? (o ? void 0 : t.dataDir), i = e.chromeProfile ?? (o ? void 0 : t.profile);
486
522
  return {
487
- userDataDir: Se({ dataDir: r, profile: i }, o),
523
+ userDataDir: rt({ dataDir: r, profile: i }, n),
488
524
  // A configured endpoint means the browser is managed elsewhere (usually
489
525
  // another machine) — that's always attach, whatever `mode` says.
490
526
  mode: t.browserUrl ? "attach" : t.mode ?? "attach",
491
527
  browserUrl: t.browserUrl,
492
528
  debugPort: t.debugPort ?? 0,
493
- executablePath: t.executablePath && E(o, t.executablePath),
529
+ executablePath: t.executablePath && P(n, t.executablePath),
494
530
  channel: t.channel,
495
531
  headless: t.headless ?? !1,
496
532
  buildExtension: t.buildExtension ?? !0
497
533
  };
498
534
  }
499
- function Se(e, t = process.cwd()) {
535
+ function rt(e, t = process.cwd()) {
500
536
  if (e.dataDir)
501
- return E(t, e.dataDir);
502
- const o = e.profile ?? vt;
503
- if (!xt.test(o))
537
+ return P(t, e.dataDir);
538
+ const n = e.profile ?? zt;
539
+ if (!Xt.test(n))
504
540
  throw new Error(
505
- `invalid chrome profile name "${o}" — use letters, digits, ".", "_", "-" (or --aiui-chrome-data-dir for an arbitrary path)`
541
+ `invalid chrome profile name "${n}" — use letters, digits, ".", "_", "-" (or --aiui-chrome-data-dir for an arbitrary path)`
506
542
  );
507
- return l(xe(t), "chrome", o);
543
+ return f(de(t), "chrome", n);
508
544
  }
509
- function M() {
545
+ function Z() {
510
546
  let e;
511
547
  try {
512
- e = we(l(D(Ae), "extension"));
548
+ e = He(f(L(nt), "extension"));
513
549
  } catch {
514
550
  return;
515
551
  }
516
- return b(l(e, "js")) ? e : void 0;
552
+ return v(f(e, "js")) ? e : void 0;
517
553
  }
518
- async function Q() {
554
+ async function be() {
519
555
  let e, t;
520
556
  try {
521
- e = we(D(Ae)), t = l(D("typescript"), "bin", "tsc");
557
+ e = He(L(nt)), t = f(L("typescript"), "bin", "tsc");
522
558
  } catch {
523
559
  return;
524
560
  }
525
- if (!b(l(e, "src")))
561
+ if (!v(f(e, "src")))
526
562
  return;
527
- const o = await p(process.execPath, [t, "-p", l(e, "tsconfig.json")], {
563
+ const n = await k(process.execPath, [t, "-p", f(e, "tsconfig.json")], {
528
564
  cwd: e,
529
565
  reject: !1,
530
566
  all: !0
531
567
  });
532
- if (o.exitCode) {
533
- _(
568
+ if (n.exitCode) {
569
+ T(
534
570
  "aiui-devtools-extension failed to compile — the DevTools panel will be stale or missing",
535
- o.all || o.message
571
+ n.all || n.message
536
572
  );
537
573
  return;
538
574
  }
539
- const n = l(e, "build-debug-ui.mjs");
540
- if (!b(n))
575
+ const o = f(e, "build-debug-ui.mjs");
576
+ if (!v(o))
541
577
  return;
542
- const r = await p(process.execPath, [n], {
578
+ const r = await k(process.execPath, [o], {
543
579
  cwd: e,
544
580
  reject: !1,
545
581
  all: !0
546
582
  });
547
- r.exitCode && _(
583
+ r.exitCode && T(
548
584
  "aiui-devtools-extension debug-ui bundle failed — the Intent pane will be degraded",
549
585
  r.all || r.message
550
586
  );
551
587
  }
552
- function Ue(e, t) {
588
+ function it(e, t) {
553
589
  if (!t || e.executablePath)
554
590
  return;
555
- const o = l(e.userDataDir, "aiui-devtools-extension-hint");
556
- if (!b(o)) {
557
- v(
591
+ const n = f(e.userDataDir, "aiui-devtools-extension-hint");
592
+ if (!v(n)) {
593
+ y(
558
594
  "the aiui DevTools panel can't auto-load into regular Chrome (≥ 137 ignores --load-extension)",
559
595
  `Load it once in the launched Chrome — chrome://extensions → Developer mode → Load unpacked →
560
596
  ${t}
@@ -562,101 +598,120 @@ ${t}
562
598
  which auto-loads it. This note won't repeat for this profile.`
563
599
  );
564
600
  try {
565
- T(o, `${(/* @__PURE__ */ new Date()).toISOString()}
601
+ q(n, `${(/* @__PURE__ */ new Date()).toISOString()}
566
602
  `);
567
603
  } catch {
568
604
  }
569
605
  }
570
606
  }
571
- function V(e) {
607
+ function te(e) {
572
608
  return {
573
609
  command: "npx",
574
610
  args: ["-y", "chrome-devtools-mcp@latest", "--browser-url", e]
575
611
  };
576
612
  }
577
- function Ct(e, t) {
578
- const o = [
613
+ function Qt(e, t) {
614
+ const n = [
579
615
  "-y",
580
616
  "chrome-devtools-mcp@latest",
581
617
  "--userDataDir",
582
618
  e.userDataDir,
583
619
  "--ignoreDefaultChromeArg=--disable-extensions"
584
620
  ];
585
- return e.executablePath && o.push("--executablePath", e.executablePath), e.channel && o.push("--channel", e.channel), e.headless && o.push("--headless"), t && o.push(`--chromeArg=--load-extension=${t}`), { command: "npx", args: o };
586
- }
587
- const Et = 9222;
588
- async function $t(e) {
589
- const o = { ...N().chrome };
590
- if (o.browserUrl) {
591
- v(
592
- `config pins chrome.browserUrl to ${o.browserUrl} — the browser is managed elsewhere`,
621
+ return e.executablePath && n.push("--executablePath", e.executablePath), e.channel && n.push("--channel", e.channel), e.headless && n.push("--headless"), t && n.push(`--chromeArg=--load-extension=${t}`), { command: "npx", args: n };
622
+ }
623
+ const Zt = 9222;
624
+ async function en(e) {
625
+ const n = { ...V().chrome };
626
+ if (n.browserUrl) {
627
+ y(
628
+ `config pins chrome.browserUrl to ${n.browserUrl} — the browser is managed elsewhere`,
593
629
  "Run `aiui browser` on the machine that should host it (and drop browserUrl there)."
594
630
  );
595
631
  return;
596
632
  }
597
- let n;
633
+ let o, r;
598
634
  try {
599
- n = Ne(e.remotePort, "--remote-port") ?? Et;
635
+ o = Se(e.remotePort, "--remote-port") ?? Zt, r = Se(e.port, "--port");
600
636
  } catch (c) {
601
- m(c instanceof Error ? c.message : String(c)), process.exitCode = 1;
637
+ g(c instanceof Error ? c.message : String(c)), process.exitCode = 1;
602
638
  return;
603
639
  }
604
- let r = o;
605
640
  const i = {
606
641
  chromeProfile: e.tunnel ? void 0 : e.profile,
607
- chromeDataDir: e.dataDir ?? (e.tunnel ? Pt(e.tunnel, e.profile) : void 0)
642
+ chromeDataDir: e.dataDir ?? (e.tunnel ? tn(e.tunnel, e.profile) : void 0)
608
643
  };
609
- let a = he($(i, r), e), s = await I(a.userDataDir);
610
- if (s)
611
- fe("session browser already running", a, s), e.open && (await Pe(s.browserUrl, e.open), console.log(`opened ${e.open}`));
644
+ let s = D(i, n);
645
+ r !== void 0 && (s = { ...s, debugPort: r });
646
+ let a = await F(s.userDataDir);
647
+ if (a)
648
+ Oe("session browser already running", s, a), e.open && (await H(a.browserUrl, e.open), console.log(`opened ${e.open}`));
612
649
  else {
613
- const c = !!process.stdin.isTTY && !!process.stdout.isTTY && !Z();
614
- if (!r.executablePath && !r.channel) {
615
- const h = await Ie({ mode: r.forTesting ?? "prompt", interactive: c });
616
- h && (r = { ...r, executablePath: h }, a = he($(i, r), e));
617
- }
618
- a.buildExtension && await Q();
619
- const u = M();
620
- c && Ue(a, u);
621
- let f;
622
- try {
623
- f = Ee(a);
624
- } catch (h) {
625
- m(
626
- "no browser to launch",
627
- `${h instanceof Error ? h.message : String(h)}
628
- Install Chrome for Testing with \`aiui chrome install\`, or set chrome.executablePath.`
629
- ), process.exitCode = 1;
630
- return;
631
- }
650
+ const c = !!process.stdin.isTTY && !!process.stdout.isTTY && !ue();
632
651
  try {
633
- s = await $e({
634
- binary: f,
635
- userDataDir: a.userDataDir,
636
- debugPort: a.debugPort,
637
- extensionDir: u,
638
- headless: a.headless || e.headless,
652
+ const d = await st({
653
+ flags: i,
654
+ config: n,
655
+ interactive: c,
656
+ debugPort: r,
657
+ headless: e.headless,
639
658
  startUrl: e.open
640
659
  });
641
- } catch (h) {
642
- m(
660
+ a = d.session, s = d.settings;
661
+ } catch (d) {
662
+ g(
643
663
  "the session browser failed to start",
644
- h instanceof Error ? h.message : String(h)
664
+ d instanceof Error ? d.message : String(d)
645
665
  ), process.exitCode = 1;
646
666
  return;
647
667
  }
648
- fe("session browser started", a, s);
668
+ Oe("session browser started", s, a);
669
+ }
670
+ e.tunnel ? await rn(e.tunnel, o, a.port) : sn(a, o);
671
+ }
672
+ async function st(e) {
673
+ let t = e.config ?? {};
674
+ const n = e.flags ?? {}, o = () => {
675
+ const c = D(n, t);
676
+ return e.debugPort === void 0 ? c : { ...c, debugPort: e.debugPort };
677
+ };
678
+ let r = o();
679
+ if (!t.executablePath && !t.channel) {
680
+ const c = await tt({
681
+ mode: t.forTesting ?? "prompt",
682
+ interactive: e.interactive
683
+ });
684
+ c && (t = { ...t, executablePath: c }, r = o());
685
+ }
686
+ r.buildExtension && await be();
687
+ const i = Z();
688
+ e.interactive && it(r, i);
689
+ let s;
690
+ try {
691
+ s = Fe(r);
692
+ } catch (c) {
693
+ throw new Error(
694
+ `${c instanceof Error ? c.message : String(c)}
695
+ Install Chrome for Testing with \`aiui chrome install\`, or set chrome.executablePath.`
696
+ );
649
697
  }
650
- e.tunnel ? await Tt(e.tunnel, n, s.port) : It(s, n);
698
+ return { session: await Ve({
699
+ binary: s,
700
+ userDataDir: r.userDataDir,
701
+ debugPort: r.debugPort,
702
+ extensionDir: i,
703
+ headless: r.headless || e.headless,
704
+ startUrl: e.startUrl
705
+ }), settings: r };
651
706
  }
652
- function Pt(e, t) {
653
- const o = t ?? Dt(e);
654
- return l(q("browser-profiles", { create: !1 }), o);
707
+ function tn(e, t) {
708
+ const n = t ?? nn(e);
709
+ return f(Y("browser-profiles", { create: !1 }), n);
655
710
  }
656
- function Dt(e) {
711
+ function nn(e) {
657
712
  return (e.includes("@") ? e.slice(e.indexOf("@") + 1) : e).replace(/[^A-Za-z0-9._-]/g, "-") || "remote";
658
713
  }
659
- function kt(e, t, o) {
714
+ function on(e, t, n) {
660
715
  return [
661
716
  // No remote command — the connection exists only to carry the forward...
662
717
  "-N",
@@ -665,114 +720,110 @@ function kt(e, t, o) {
665
720
  "-o",
666
721
  "ExitOnForwardFailure=yes",
667
722
  "-R",
668
- `${t}:localhost:${o}`,
723
+ `${t}:localhost:${n}`,
669
724
  e
670
725
  ];
671
726
  }
672
- function _e(e) {
727
+ function at(e) {
673
728
  return `aiui claude --aiui-browser-url http://127.0.0.1:${e}`;
674
729
  }
675
- async function Tt(e, t, o) {
730
+ async function rn(e, t, n) {
676
731
  console.log(
677
732
  `
678
- tunneling ${e}:${t} → localhost:${o} — on ${e}, run:
733
+ tunneling ${e}:${t} → localhost:${n} — on ${e}, run:
679
734
 
680
- ${_e(t)}
735
+ ${at(t)}
681
736
 
682
737
  (Ctrl-C closes the tunnel; the browser stays running.)`
683
738
  );
684
- const n = await p("ssh", kt(e, t, o), {
739
+ const o = await k("ssh", on(e, t, n), {
685
740
  stdio: "inherit",
686
741
  reject: !1
687
742
  });
688
- n.failed && !n.isTerminated && (m(
689
- `the ssh tunnel to ${e} exited (code ${n.exitCode})`,
743
+ o.failed && !o.isTerminated && (g(
744
+ `the ssh tunnel to ${e} exited (code ${o.exitCode})`,
690
745
  "A taken remote port exits immediately (ExitOnForwardFailure) — try another --remote-port. The browser is still running; rerun `aiui browser --tunnel …` to reconnect."
691
746
  ), process.exitCode = 1);
692
747
  }
693
- function Ne(e, t) {
748
+ function Se(e, t) {
694
749
  if (e === void 0)
695
750
  return;
696
- const o = Number(e);
697
- if (!(Number.isInteger(o) && o >= 0 && o <= 65535))
751
+ const n = Number(e);
752
+ if (!(Number.isInteger(n) && n >= 0 && n <= 65535))
698
753
  throw new Error(`invalid ${t} ${e} — expected 0..65535`);
699
- return o;
700
- }
701
- function he(e, t) {
702
- const o = Ne(t.port, "--port");
703
- return o === void 0 ? e : { ...e, debugPort: o };
754
+ return n;
704
755
  }
705
- function fe(e, t, o) {
706
- console.log(e), console.log(` profile: ${t.userDataDir}`), console.log(` debug endpoint: ${o.browserUrl}`);
756
+ function Oe(e, t, n) {
757
+ console.log(e), console.log(` profile: ${t.userDataDir}`), console.log(` debug endpoint: ${n.browserUrl}`);
707
758
  }
708
- function It(e, t) {
759
+ function sn(e, t) {
709
760
  console.log(
710
761
  `
711
762
  An \`aiui claude\` in this profile's project attaches automatically. For a *remote*
712
763
  session, rerun with \`--tunnel <[user@]host>\` — or do it by hand:
713
764
  ssh -N -o ExitOnForwardFailure=yes -R ${t}:localhost:${e.port} <host>
714
- then, on the remote: ${_e(t)}`
765
+ then, on the remote: ${at(t)}`
715
766
  );
716
767
  }
717
- async function At(e, t) {
718
- var i, a;
719
- const o = N(), n = $(
768
+ async function an(e, t) {
769
+ var i, s;
770
+ const n = V(), o = D(
720
771
  { chromeProfile: t.profile, chromeDataDir: t.dataDir },
721
- o.chrome ?? {}
722
- ), r = ((i = o.chrome) == null ? void 0 : i.browserUrl) ?? ((a = await I(n.userDataDir)) == null ? void 0 : a.browserUrl);
772
+ n.chrome ?? {}
773
+ ), r = ((i = n.chrome) == null ? void 0 : i.browserUrl) ?? ((s = await F(o.userDataDir)) == null ? void 0 : s.browserUrl);
723
774
  if (!r) {
724
- m(
775
+ g(
725
776
  "no session browser is running for this profile",
726
- `Start one with \`aiui browser\` (profile: ${n.userDataDir}).`
777
+ `Start one with \`aiui browser\` (profile: ${o.userDataDir}).`
727
778
  ), process.exitCode = 1;
728
779
  return;
729
780
  }
730
781
  try {
731
- await Pe(r, e), console.log(`opened ${e}`);
732
- } catch (s) {
733
- m(`couldn't open ${e}`, s instanceof Error ? s.message : String(s)), process.exitCode = 1;
782
+ await H(r, e), console.log(`opened ${e}`);
783
+ } catch (a) {
784
+ g(`couldn't open ${e}`, a instanceof Error ? a.message : String(a)), process.exitCode = 1;
734
785
  }
735
786
  }
736
- async function Ot(e) {
787
+ async function cn(e) {
737
788
  const [t] = e;
738
789
  switch (t) {
739
790
  case "install":
740
791
  case "update":
741
- await ft((o) => console.log(o));
792
+ await Kt((n) => console.log(n));
742
793
  return;
743
794
  case "status":
744
- await St();
795
+ await ln();
745
796
  return;
746
797
  case "extension": {
747
- await Q();
748
- const o = M();
749
- if (!o) {
750
- m(
798
+ await be();
799
+ const n = Z();
800
+ if (!n) {
801
+ g(
751
802
  "the aiui-devtools-extension is not available in this install",
752
803
  "In a dev checkout, build it first: pnpm --filter @habemus-papadum/aiui-devtools-extension build"
753
804
  ), process.exitCode = 1;
754
805
  return;
755
806
  }
756
- console.log(o);
807
+ console.log(n);
757
808
  return;
758
809
  }
759
810
  default:
760
- m(
811
+ g(
761
812
  t ? `unknown aiui chrome action: ${t}` : "aiui chrome needs an action",
762
813
  "Usage: aiui chrome <install | update | status | extension>"
763
814
  ), process.exitCode = 1;
764
815
  return;
765
816
  }
766
817
  }
767
- async function St() {
768
- const t = N().chrome ?? {}, o = { chrome: !1, noChrome: !1 }, n = await W(), r = await z();
769
- if (console.log("Chrome for Testing (managed):"), n) {
770
- const y = r === void 0 ? "(latest stable unknown — offline?)" : r === n.buildId ? "(latest stable)" : `(latest stable is ${r} — run \`aiui chrome update\`)`;
771
- console.log(` installed ${n.buildId} ${y}`), console.log(` ${n.executablePath}`);
818
+ async function ln() {
819
+ const t = V().chrome ?? {}, n = { chrome: !1, noChrome: !1 }, o = await me(), r = await we();
820
+ if (console.log("Chrome for Testing (managed):"), o) {
821
+ const m = r === void 0 ? "(latest stable unknown — offline?)" : r === o.buildId ? "(latest stable)" : `(latest stable is ${r} — run \`aiui chrome update\`)`;
822
+ console.log(` installed ${o.buildId} ${m}`), console.log(` ${o.executablePath}`);
772
823
  } else
773
824
  console.log(" not installed — `aiui chrome install` (recommended; auto-loads the panel)");
774
825
  if (console.log(` startup checks (chrome.forTesting): ${t.forTesting ?? "prompt"}`), console.log(`
775
- This directory would launch:`), !Oe(o, t)) {
826
+ This directory would launch:`), !ot(n, t)) {
776
827
  console.log(" nothing — the Chrome DevTools MCP is disabled here");
777
828
  return;
778
829
  }
@@ -781,181 +832,249 @@ This directory would launch:`), !Oe(o, t)) {
781
832
  return;
782
833
  }
783
834
  const i = { ...t };
784
- !i.executablePath && !i.channel && n && (i.executablePath = n.executablePath);
785
- const a = $({}, i), s = await I(a.userDataDir), c = a.mode === "attach" ? s ? `attach to the running session browser at ${s.browserUrl}` : "attach — a session browser starts with the next interactive launch (or `aiui browser`)" : "launch — chrome-devtools-mcp starts a private browser on the agent's first tool use";
835
+ !i.executablePath && !i.channel && o && (i.executablePath = o.executablePath);
836
+ const s = D({}, i), a = await F(s.userDataDir), c = s.mode === "attach" ? a ? `attach to the running session browser at ${a.browserUrl}` : "attach — a session browser starts with the next interactive launch (or `aiui browser`)" : "launch — chrome-devtools-mcp starts a private browser on the agent's first tool use";
786
837
  console.log(` connection: ${c}`);
787
- const u = a.executablePath ? a.executablePath === (n == null ? void 0 : n.executablePath) ? `Chrome for Testing ${n.buildId}` : a.executablePath : a.channel ? `installed Chrome (${a.channel} channel)` : "installed Chrome (stable)";
788
- console.log(` browser: ${u}${a.headless ? " — headless" : ""}`), console.log(` user data dir: ${a.userDataDir}`);
789
- const f = l(Se({}, process.cwd()), "..");
790
- if (b(f)) {
791
- const y = ge(f, { withFileTypes: !0 }).filter((d) => d.isDirectory()).map((d) => d.name);
792
- y.length && console.log(` profiles here: ${y.join(", ")}`);
838
+ const d = s.executablePath ? s.executablePath === (o == null ? void 0 : o.executablePath) ? `Chrome for Testing ${o.buildId}` : s.executablePath : s.channel ? `installed Chrome (${s.channel} channel)` : "installed Chrome (stable)";
839
+ console.log(` browser: ${d}${s.headless ? " — headless" : ""}`), console.log(` user data dir: ${s.userDataDir}`);
840
+ const p = f(rt({}, process.cwd()), "..");
841
+ if (v(p)) {
842
+ const m = G(p, { withFileTypes: !0 }).filter((b) => b.isDirectory()).map((b) => b.name);
843
+ m.length && console.log(` profiles here: ${m.join(", ")}`);
793
844
  }
794
845
  console.log(`
795
846
  aiui DevTools panel:`);
796
- const h = M();
847
+ const h = Z();
797
848
  if (!h) {
798
849
  console.log(" not available (unbuilt dev checkout? run `aiui chrome extension` for help)");
799
850
  return;
800
851
  }
801
- console.log(` ${h}`), a.executablePath ? console.log(" auto-loads via --load-extension (honored by Chrome for Testing/Chromium)") : (console.log(" can NOT auto-load into branded Chrome ≥ 137 — load it unpacked once"), console.log(
852
+ console.log(` ${h}`), s.executablePath ? console.log(" auto-loads via --load-extension (honored by Chrome for Testing/Chromium)") : (console.log(" can NOT auto-load into branded Chrome ≥ 137 — load it unpacked once"), console.log(
802
853
  " (chrome://extensions → Developer mode → Load unpacked), or `aiui chrome install`"
803
854
  ));
804
855
  }
805
- const Ut = "--aiui-";
806
- function Re(e) {
856
+ const un = "--aiui-";
857
+ function ct(e) {
807
858
  if (e.includes("--help") || e.includes("-h"))
808
859
  return "help";
809
860
  if (e.includes("--version") || e.includes("-v"))
810
861
  return "version";
811
862
  }
812
- function je(e) {
813
- let t, o, n = !1, r = !1, i, a, s;
814
- const c = [];
815
- for (let u = 0; u < e.length; u++) {
816
- const f = e[u];
817
- if (!f.startsWith(Ut)) {
818
- c.push(f);
863
+ function lt(e) {
864
+ let t, n, o = !1, r = !1, i = !1, s = !1, a, c, d;
865
+ const p = [], h = [];
866
+ let m;
867
+ const b = [];
868
+ for (let w = 0; w < e.length; w++) {
869
+ const C = e[w];
870
+ if (!C.startsWith(un)) {
871
+ b.push(C);
819
872
  continue;
820
873
  }
821
- const h = f.indexOf("="), y = h === -1 ? f : f.slice(0, h);
822
- let d = h === -1 ? void 0 : f.slice(h + 1);
823
- switch (y) {
874
+ const x = C.indexOf("="), N = x === -1 ? C : C.slice(0, x);
875
+ let u = x === -1 ? void 0 : C.slice(x + 1);
876
+ switch (N) {
824
877
  case "--aiui-tag": {
825
- if (d === void 0 && (d = e[++u]), !d)
878
+ if (u === void 0 && (u = e[++w]), !u)
826
879
  throw new Error("--aiui-tag requires a non-empty value");
827
- t = d;
880
+ t = u;
828
881
  break;
829
882
  }
830
883
  case "--aiui-mcp": {
831
- if (d === void 0 && (d = e[++u]), !d)
884
+ if (u === void 0 && (u = e[++w]), !u)
832
885
  throw new Error("--aiui-mcp requires a non-empty value");
833
- o = d;
886
+ n = u;
834
887
  break;
835
888
  }
836
889
  case "--aiui-chrome": {
837
- if (d !== void 0)
890
+ if (u !== void 0)
838
891
  throw new Error("--aiui-chrome takes no value");
839
- n = !0;
892
+ o = !0;
840
893
  break;
841
894
  }
842
895
  case "--aiui-no-chrome": {
843
- if (d !== void 0)
896
+ if (u !== void 0)
844
897
  throw new Error("--aiui-no-chrome takes no value");
845
898
  r = !0;
846
899
  break;
847
900
  }
901
+ case "--aiui-browser": {
902
+ if (u !== void 0)
903
+ throw new Error("--aiui-browser takes no value");
904
+ i = !0;
905
+ break;
906
+ }
907
+ case "--aiui-no-browser": {
908
+ if (u !== void 0)
909
+ throw new Error("--aiui-no-browser takes no value");
910
+ s = !0;
911
+ break;
912
+ }
848
913
  case "--aiui-chrome-profile": {
849
- if (d === void 0 && (d = e[++u]), !d)
914
+ if (u === void 0 && (u = e[++w]), !u)
850
915
  throw new Error("--aiui-chrome-profile requires a non-empty value");
851
- i = d;
916
+ a = u;
852
917
  break;
853
918
  }
854
919
  case "--aiui-chrome-data-dir": {
855
- if (d === void 0 && (d = e[++u]), !d)
920
+ if (u === void 0 && (u = e[++w]), !u)
856
921
  throw new Error("--aiui-chrome-data-dir requires a non-empty value");
857
- a = d;
922
+ c = u;
858
923
  break;
859
924
  }
860
925
  case "--aiui-browser-url": {
861
- if (d === void 0 && (d = e[++u]), !d)
926
+ if (u === void 0 && (u = e[++w]), !u)
862
927
  throw new Error("--aiui-browser-url requires a non-empty value");
863
- s = d;
928
+ d = u;
929
+ break;
930
+ }
931
+ case "--aiui-sidecar": {
932
+ if (u === void 0 && (u = e[++w]), !u)
933
+ throw new Error("--aiui-sidecar requires a non-empty value");
934
+ p.push(u);
935
+ break;
936
+ }
937
+ case "--aiui-no-sidecar": {
938
+ if (u === void 0 && (u = e[++w]), !u)
939
+ throw new Error("--aiui-no-sidecar requires a non-empty value");
940
+ h.push(u);
941
+ break;
942
+ }
943
+ case "--aiui-bind": {
944
+ if (u === void 0 && (u = e[++w]), !u || !se.includes(u))
945
+ throw new Error(`--aiui-bind requires one of: ${se.join(", ")}`);
946
+ m = u;
864
947
  break;
865
948
  }
866
949
  default:
867
- throw new Error(`unknown aiui option: ${y}`);
950
+ throw new Error(`unknown aiui option: ${N}`);
868
951
  }
869
952
  }
870
- if (n && r)
953
+ if (o && r)
871
954
  throw new Error("--aiui-chrome and --aiui-no-chrome are mutually exclusive");
872
- if (i !== void 0 && a !== void 0)
955
+ if (i && s)
956
+ throw new Error("--aiui-browser and --aiui-no-browser are mutually exclusive");
957
+ if (a !== void 0 && c !== void 0)
873
958
  throw new Error("--aiui-chrome-profile and --aiui-chrome-data-dir are mutually exclusive");
874
- if (s !== void 0 && (i !== void 0 || a !== void 0))
959
+ if (d !== void 0 && (a !== void 0 || c !== void 0))
875
960
  throw new Error(
876
961
  "--aiui-browser-url means the browser is managed elsewhere — it can't be combined with --aiui-chrome-profile or --aiui-chrome-data-dir"
877
962
  );
878
- return { tag: t, mcp: o, chrome: n, noChrome: r, chromeProfile: i, chromeDataDir: a, browserUrl: s, passthrough: c };
963
+ return {
964
+ tag: t,
965
+ mcp: n,
966
+ chrome: o,
967
+ noChrome: r,
968
+ browser: i,
969
+ noBrowser: s,
970
+ chromeProfile: a,
971
+ chromeDataDir: c,
972
+ browserUrl: d,
973
+ sidecar: p,
974
+ noSidecar: h,
975
+ bind: m,
976
+ passthrough: b
977
+ };
879
978
  }
880
- const _t = {
979
+ const dn = {
881
980
  darwin: 2147578994,
882
981
  linux: 21522
883
- }, Nt = 'open(my $t,"+<","/dev/tty") or exit 0; my $c="\\r"; ioctl($t,$ARGV[0]+0,$c);', Rt = [250, 750];
884
- function jt(e = Rt) {
885
- const t = _t[process.platform];
982
+ }, hn = 'open(my $t,"+<","/dev/tty") or exit 0; my $c="\\r"; ioctl($t,$ARGV[0]+0,$c);', fn = [250, 750];
983
+ function pn(e = fn) {
984
+ const t = dn[process.platform];
886
985
  if (t !== void 0)
887
- for (const o of e)
986
+ for (const n of e)
888
987
  setTimeout(() => {
889
988
  try {
890
- rt("perl", ["-e", Nt, String(t)], { stdio: "ignore" }).on("error", () => {
989
+ jt("perl", ["-e", hn, String(t)], { stdio: "ignore" }).on("error", () => {
891
990
  });
892
991
  } catch {
893
992
  }
894
- }, o).unref();
993
+ }, n).unref();
895
994
  }
896
- const Mt = `One-time setup — how should aiui launch Claude Code?
995
+ const mn = `One-time setup — how should aiui launch Claude Code?
897
996
  With --dangerously-skip-permissions, every agent action (shell commands, file writes,
898
997
  network, the browser) runs without asking you first. Fast, and dangerous. It's a personal
899
998
  preference — aiui works fine either way. Saved as claude.skipPermissions in your user
900
- config; edit or delete it there to change your mind.`, Lt = `One-time setup — auto-dismiss Claude Code's channel prompt?
999
+ config; edit or delete it there to change your mind.`, wn = `One-time setup — where should the channel's web server bind?
1000
+ Binding the HOST interface puts the session's whole web surface on your network,
1001
+ UNAUTHENTICATED — the iPad paint page (\`aiui paint url\` prints its URL), but also prompt
1002
+ injection, /debug, and every sidecar. That's the simple, single-port way to use the iPad —
1003
+ on a network that is yours alone (a home LAN), not on café Wi-Fi. LOOPBACK keeps everything
1004
+ this-machine-only; reaching it from an iPad is then up to you — tunnel the channel port
1005
+ however you like (Tailscale, \`ssh -L\`). Saved as channel.bind in your user config;
1006
+ --aiui-bind wins per launch.`, gn = `One-time setup — auto-dismiss Claude Code's channel prompt?
901
1007
  aiui loads a custom development channel, so Claude Code shows a one-key acknowledgement
902
1008
  prompt at every startup. aiui can dismiss it for you: shortly after launch it injects a
903
1009
  single Enter keystroke into this terminal (a best-effort TIOCSTI ioctl on /dev/tty — it
904
1010
  literally "types" the Enter for you; on platforms that forbid that, nothing happens and
905
1011
  you press it yourself). Saying no just means pressing Enter once per launch. Saved as
906
1012
  claude.enterNudge in your user config.`;
907
- async function Ft(e, t = G) {
908
- var n, r;
909
- let o = e;
910
- if (((n = o.claude) == null ? void 0 : n.skipPermissions) === void 0) {
911
- const i = await t(Mt, [
1013
+ async function bn(e, t = z) {
1014
+ var o, r, i;
1015
+ let n = e;
1016
+ if (((o = n.claude) == null ? void 0 : o.skipPermissions) === void 0) {
1017
+ const s = await t(mn, [
912
1018
  { key: "y", label: "yes — skip permissions; nothing asks before acting" },
913
1019
  { key: "n", label: "no — keep Claude Code's own permission prompts" }
914
1020
  ]);
915
- o = me(o, "skipPermissions", i === "y");
1021
+ n = je(n, "skipPermissions", s === "y");
916
1022
  }
917
- if (((r = o.claude) == null ? void 0 : r.enterNudge) === void 0) {
918
- const i = await t(Lt, [
1023
+ if (((r = n.claude) == null ? void 0 : r.enterNudge) === void 0) {
1024
+ const s = await t(gn, [
919
1025
  { key: "y", label: "yes — press Enter for me at startup" },
920
1026
  { key: "n", label: "no — I'll press it myself each launch" }
921
1027
  ]);
922
- o = me(o, "enterNudge", i === "y");
1028
+ n = je(n, "enterNudge", s === "y");
923
1029
  }
924
- return o;
1030
+ if (((i = n.channel) == null ? void 0 : i.bind) === void 0) {
1031
+ const s = await t(wn, [
1032
+ { key: "h", label: "host — reachable on my (trusted) network; the iPad just works" },
1033
+ { key: "l", label: "loopback — this machine only; I'll tunnel when I want the iPad" }
1034
+ ]);
1035
+ n = yn(n, s === "h" ? "host" : "loopback");
1036
+ }
1037
+ return n;
1038
+ }
1039
+ function je(e, t, n) {
1040
+ const o = B((r) => {
1041
+ r.claude = { ...r.claude, [t]: n };
1042
+ });
1043
+ return y(`wrote claude.${t}: ${n} to ${o}`), { ...e, claude: { ...e.claude, [t]: n } };
925
1044
  }
926
- function me(e, t, o) {
927
- const n = U((r) => {
928
- r.claude = { ...r.claude, [t]: o };
1045
+ function yn(e, t) {
1046
+ const n = B((o) => {
1047
+ o.channel = { ...o.channel, bind: t };
929
1048
  });
930
- return v(`wrote claude.${t}: ${o} to ${n}`), { ...e, claude: { ...e.claude, [t]: o } };
1049
+ return y(`wrote channel.bind: ${t} to ${n}`), { ...e, channel: { ...e.channel, bind: t } };
931
1050
  }
932
- const Ht = "https://api.openai.com/v1/models", Bt = 3e3;
933
- async function Vt(e = {}) {
1051
+ const vn = "https://api.openai.com/v1/models", kn = 3e3;
1052
+ async function Cn(e = {}) {
934
1053
  var c;
935
1054
  const {
936
1055
  verify: t = !0,
937
- env: o = process.env,
938
- fetchImpl: n = fetch,
939
- timeoutMs: r = Bt
940
- } = e, i = (c = o.OPENAI_API_KEY) == null ? void 0 : c.trim();
1056
+ env: n = process.env,
1057
+ fetchImpl: o = fetch,
1058
+ timeoutMs: r = kn
1059
+ } = e, i = (c = n.OPENAI_API_KEY) == null ? void 0 : c.trim();
941
1060
  if (!i)
942
1061
  return "missing";
943
1062
  if (!t)
944
1063
  return "unverified";
945
- const a = new AbortController(), s = setTimeout(() => a.abort(), r);
1064
+ const s = new AbortController(), a = setTimeout(() => s.abort(), r);
946
1065
  try {
947
- const u = await n(Ht, {
1066
+ const d = await o(vn, {
948
1067
  headers: { authorization: `Bearer ${i}` },
949
- signal: a.signal
1068
+ signal: s.signal
950
1069
  });
951
- return u.ok ? "valid" : u.status === 401 || u.status === 403 ? "invalid" : "unverified";
1070
+ return d.ok ? "valid" : d.status === 401 || d.status === 403 ? "invalid" : "unverified";
952
1071
  } catch {
953
1072
  return "unverified";
954
1073
  } finally {
955
- clearTimeout(s);
1074
+ clearTimeout(a);
956
1075
  }
957
1076
  }
958
- function Kt(e) {
1077
+ function $n(e) {
959
1078
  switch (e) {
960
1079
  case "valid":
961
1080
  return null;
@@ -963,7 +1082,7 @@ function Kt(e) {
963
1082
  return {
964
1083
  level: "warn",
965
1084
  title: "OPENAI_API_KEY is not set — the intent pipeline will run degraded",
966
- detail: "Speech transcription and dictation correction fall back to mock/off mode. To enable them, export the key in the shell you run `aiui claude` from:\n export OPENAI_API_KEY=sk-…\nIt flows through to the channel process, which is where those calls happen."
1085
+ detail: "Speech transcription and dictation correction are unavailable — the overlay says so when you try to dictate. To enable them, export the key in the shell you run `aiui claude` from:\n export OPENAI_API_KEY=sk-…\nIt flows through to the channel process, which is where those calls happen. (For offline work, switch the overlay to the mock backends — see the intent-overlay guide.)"
967
1086
  };
968
1087
  case "invalid":
969
1088
  return {
@@ -971,96 +1090,103 @@ function Kt(e) {
971
1090
  title: "OPENAI_API_KEY was rejected by OpenAI (401) — the intent pipeline will run degraded",
972
1091
  detail: `The key in your environment isn't valid. The usual cause is a stale shell export shadowing your real key — check what's actually set (this prints only a short prefix, not the whole secret):
973
1092
  echo $OPENAI_API_KEY | head -c 12
974
- and compare that against the start of your real key. Until it's fixed, transcription and correction run in mock/off mode.`
1093
+ and compare that against the start of your real key. Until it's fixed, transcription and correction are unavailable.`
975
1094
  };
976
1095
  case "unverified":
977
1096
  return {
978
1097
  level: "note",
979
1098
  title: "couldn't verify OPENAI_API_KEY with OpenAI — continuing",
980
- detail: "The check didn't complete (offline, a timeout, or a transient OpenAI error), so the key is unverified — not known-bad. Launch continues; if the intent pipeline degrades to mock/off, an unreachable or invalid key may be why."
1099
+ detail: "The check didn't complete (offline, a timeout, or a transient OpenAI error), so the key is unverified — not known-bad. Launch continues; if transcription and correction turn out unavailable, an unreachable or invalid key may be why."
981
1100
  };
982
1101
  }
983
1102
  }
984
- function Yt(e) {
985
- const t = Kt(e);
986
- t && (t.level === "warn" ? _(t.title, t.detail) : v(t.title, t.detail));
1103
+ function xn(e) {
1104
+ const t = $n(e);
1105
+ t && (t.level === "warn" ? T(t.title, t.detail) : y(t.title, t.detail));
987
1106
  }
988
- const L = "0.2.0";
989
- function ee(e) {
990
- const t = (process.env.PATH ?? "").split(Ve).filter(Boolean), o = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
991
- for (const n of t)
992
- for (const r of o)
1107
+ const ee = "0.3.0";
1108
+ function ye(e) {
1109
+ const t = (process.env.PATH ?? "").split(bt).filter(Boolean), n = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
1110
+ for (const o of t)
1111
+ for (const r of n)
993
1112
  try {
994
- return qe(l(n, e + r), Ge.X_OK), !0;
1113
+ return $t(f(o, e + r), xt.X_OK), !0;
995
1114
  } catch {
996
1115
  }
997
1116
  return !1;
998
1117
  }
999
- const qt = "@habemus-papadum/aiui-claude-channel", Gt = "@habemus-papadum/aiui-claude-plugin", pe = "aiui";
1000
- async function Jt(e = []) {
1001
- var re, ne;
1002
- const t = je(e), { tag: o, passthrough: n } = t, r = Re(n);
1118
+ const Tn = "@habemus-papadum/aiui-claude-channel", Pn = "@habemus-papadum/aiui-claude-plugin", Ue = "aiui";
1119
+ async function En(e = []) {
1120
+ var xe, Te, Pe;
1121
+ const t = lt(e), { tag: n, passthrough: o } = t, r = ct(o);
1003
1122
  if (r) {
1004
- r === "help" ? zt() : console.log(`aiui ${L}`), await Wt(n);
1123
+ r === "help" ? In() : console.log(`aiui ${ee}`), await Dn(o);
1005
1124
  return;
1006
1125
  }
1007
- let i = N();
1008
- const a = Zt(n) && !Z();
1009
- a && (i = await Ft(i));
1010
- const s = await Vt({ verify: a });
1011
- if (a && Yt(s), !Me())
1126
+ let i = V();
1127
+ const s = On(o) && !ue();
1128
+ s && (i = await bn(i));
1129
+ const a = await Cn({ verify: s });
1130
+ if (s && xn(a), !ut())
1012
1131
  return;
1013
- const c = E(D(Gt), "marketplace", "plugins"), u = X(qt), f = [...u.args, "mcp"];
1014
- o && f.push("--tag", o);
1015
- const h = {
1016
- [pe]: { command: u.command, args: f }
1132
+ const c = P(L(Pn), "marketplace", "plugins"), d = ge(Tn), p = [...d.args, "mcp"];
1133
+ n && p.push("--tag", n);
1134
+ const h = t.bind ?? ((xe = i.channel) == null ? void 0 : xe.bind) ?? "loopback";
1135
+ p.push("--bind", h);
1136
+ const m = {
1137
+ [Ue]: { command: d.command, args: p }
1017
1138
  };
1018
- let y = { enabled: !1 };
1019
- if (Oe(t, i.chrome)) {
1020
- const F = {
1139
+ let b = { enabled: !1 };
1140
+ if (ot(t, i.chrome)) {
1141
+ const E = {
1021
1142
  ...i.chrome,
1022
1143
  ...t.browserUrl ? { browserUrl: t.browserUrl } : {}
1023
- }, ie = await Xt(t, F, a);
1024
- h[yt] = ie.entry, y = ie.info;
1144
+ }, _ = await Sn(t, E, s);
1145
+ m[Jt] = _.entry, b = _.info;
1025
1146
  }
1026
- const d = {
1147
+ const w = {
1027
1148
  launcher: "aiui claude",
1028
- chromeDevtools: y,
1029
- openaiKey: s
1149
+ chromeDevtools: b,
1150
+ openaiKey: a
1030
1151
  };
1031
- f.push("--launch-info", JSON.stringify(d));
1032
- const Fe = JSON.stringify({ mcpServers: h }), te = [l(c, "aiui"), l(c, "frontend-design")];
1033
- y.enabled && te.push(l(c, "session-browser"));
1034
- const He = [
1035
- ...((re = i.claude) == null ? void 0 : re.skipPermissions) ?? !0 ? ["--dangerously-skip-permissions"] : [],
1152
+ p.push("--launch-info", JSON.stringify(w));
1153
+ const C = [...t.sidecar], x = [...t.noSidecar];
1154
+ for (const [E, _] of Object.entries(i.sidecars ?? {}))
1155
+ _ === void 0 || C.includes(E) || x.includes(E) || (_ ? C : x).push(E);
1156
+ const N = Ut(process.cwd(), { enable: C, disable: x });
1157
+ N.length > 0 && p.push("--sidecars", JSON.stringify(N));
1158
+ const u = JSON.stringify({ mcpServers: m }), Ce = [f(c, "aiui"), f(c, "frontend-design")];
1159
+ b.enabled && Ce.push(f(c, "session-browser"));
1160
+ const wt = [
1161
+ ...((Te = i.claude) == null ? void 0 : Te.skipPermissions) ?? !0 ? ["--dangerously-skip-permissions"] : [],
1036
1162
  "--mcp-config",
1037
- Fe,
1038
- ...te.flatMap((F) => ["--plugin-dir", F]),
1163
+ u,
1164
+ ...Ce.flatMap((E) => ["--plugin-dir", E]),
1039
1165
  // Custom channels are a research preview and not on the approved allowlist,
1040
1166
  // so opt this session into loading ours as a development channel.
1041
1167
  "--dangerously-load-development-channels",
1042
- `server:${pe}`
1168
+ `server:${Ue}`
1043
1169
  ];
1044
- a && (((ne = i.claude) == null ? void 0 : ne.enterNudge) ?? !0) && jt();
1045
- const oe = await p("claude", [...He, ...n], {
1170
+ s && (((Pe = i.claude) == null ? void 0 : Pe.enterNudge) ?? !0) && pn();
1171
+ const $e = await k("claude", [...wt, ...o], {
1046
1172
  stdio: "inherit",
1047
1173
  reject: !1
1048
1174
  });
1049
- oe.exitCode && (process.exitCode = oe.exitCode);
1175
+ $e.exitCode && (process.exitCode = $e.exitCode);
1050
1176
  }
1051
- function Me() {
1052
- return ee("claude") ? !0 : (m(
1177
+ function ut() {
1178
+ return ye("claude") ? !0 : (g(
1053
1179
  "`claude` was not found on your PATH",
1054
1180
  "Install Claude Code and make sure the `claude` command is available, then try again."
1055
1181
  ), process.exitCode = 1, !1);
1056
1182
  }
1057
- async function Wt(e) {
1058
- if (!Me())
1183
+ async function Dn(e) {
1184
+ if (!ut())
1059
1185
  return;
1060
- const t = await p("claude", e, { stdio: "inherit", reject: !1 });
1186
+ const t = await k("claude", e, { stdio: "inherit", reject: !1 });
1061
1187
  t.exitCode && (process.exitCode = t.exitCode);
1062
1188
  }
1063
- function zt() {
1189
+ function In() {
1064
1190
  console.log(`aiui claude — launch Claude Code wired with the aiui channel, plugin, and browser MCP
1065
1191
 
1066
1192
  aiui's own flags (everything else forwards to claude verbatim):
@@ -1071,167 +1197,773 @@ aiui's own flags (everything else forwards to claude verbatim):
1071
1197
  --aiui-chrome-data-dir <path> explicit browser user data dir
1072
1198
  --aiui-browser-url <url> attach to a browser at this DevTools endpoint
1073
1199
  (e.g. a tunnel from \`aiui browser --tunnel\`)
1200
+ --aiui-bind <loopback|host> where the channel's web server binds: loopback
1201
+ (this machine only, the default) or host (your
1202
+ whole network can reach the session's web
1203
+ surface — the iPad paint page included;
1204
+ trusted networks only)
1205
+ --aiui-sidecar <name> host this session sidecar (repeatable);
1206
+ \`paint\` (iPad ink) is always on
1207
+ --aiui-no-sidecar <name> don't host this session sidecar (repeatable)
1074
1208
 
1075
1209
  Durable settings live in config.json (project .aiui-cache/ + user cache) — see the
1076
1210
  Configuration guide. What follows is claude's own --help:
1077
1211
  `);
1078
1212
  }
1079
- async function Xt(e, t, o) {
1213
+ async function Sn(e, t, n) {
1080
1214
  if (t.browserUrl)
1081
1215
  return {
1082
- entry: V(t.browserUrl),
1216
+ entry: te(t.browserUrl),
1083
1217
  info: { enabled: !0, connection: "attach", browserUrl: t.browserUrl }
1084
1218
  };
1085
- let n = { ...t }, r = $(e, n);
1219
+ let o = { ...t }, r = D(e, o);
1086
1220
  if (r.mode === "attach") {
1087
- const s = await I(r.userDataDir);
1088
- if (s)
1221
+ const a = await F(r.userDataDir);
1222
+ if (a)
1089
1223
  return {
1090
- entry: V(s.browserUrl),
1224
+ entry: te(a.browserUrl),
1091
1225
  info: {
1092
1226
  enabled: !0,
1093
1227
  connection: "attach",
1094
- browserUrl: s.browserUrl,
1228
+ browserUrl: a.browserUrl,
1095
1229
  userDataDir: r.userDataDir
1096
1230
  }
1097
1231
  };
1098
1232
  }
1099
- if (!n.executablePath && !n.channel) {
1100
- const s = await Ie({ mode: n.forTesting ?? "prompt", interactive: o });
1101
- s && (n = { ...n, executablePath: s }, r = $(e, n));
1233
+ if (!o.executablePath && !o.channel) {
1234
+ const a = await tt({ mode: o.forTesting ?? "prompt", interactive: n });
1235
+ a && (o = { ...o, executablePath: a }, r = D(e, o));
1102
1236
  }
1103
- k(r.userDataDir, { recursive: !0 }), r.buildExtension && await Q();
1104
- const i = M();
1105
- o && Ue(r, i);
1106
- const a = {
1237
+ W(r.userDataDir, { recursive: !0 }), r.buildExtension && await be();
1238
+ const i = Z();
1239
+ n && it(r, i);
1240
+ const s = {
1107
1241
  userDataDir: r.userDataDir,
1108
1242
  executablePath: r.executablePath,
1109
1243
  channel: r.channel,
1110
1244
  headless: r.headless,
1111
1245
  extensionDir: i
1112
1246
  };
1113
- if (r.mode === "attach" && o)
1247
+ if (r.mode === "attach" && n)
1114
1248
  try {
1115
- const s = await $e({
1116
- binary: Ee(r),
1249
+ const a = await Ve({
1250
+ binary: Fe(r),
1117
1251
  userDataDir: r.userDataDir,
1118
1252
  debugPort: r.debugPort,
1119
1253
  extensionDir: i,
1120
1254
  headless: r.headless
1121
1255
  });
1122
1256
  return {
1123
- entry: V(s.browserUrl),
1257
+ entry: te(a.browserUrl),
1124
1258
  info: {
1125
1259
  enabled: !0,
1126
1260
  connection: "attach",
1127
- browserUrl: s.browserUrl,
1128
- ...a
1261
+ browserUrl: a.browserUrl,
1262
+ ...s
1129
1263
  }
1130
1264
  };
1131
- } catch (s) {
1132
- _(
1265
+ } catch (a) {
1266
+ T(
1133
1267
  "couldn't start the session browser — falling back to a browser private to the MCP",
1134
- s instanceof Error ? s.message : String(s)
1268
+ a instanceof Error ? a.message : String(a)
1135
1269
  );
1136
1270
  }
1137
1271
  return {
1138
- entry: Ct(r, i),
1139
- info: { enabled: !0, connection: "launch", ...a }
1272
+ entry: Qt(r, i),
1273
+ info: { enabled: !0, connection: "launch", ...s }
1140
1274
  };
1141
1275
  }
1142
- function Zt(e) {
1276
+ function On(e) {
1143
1277
  return !process.stdin.isTTY || !process.stdout.isTTY ? !1 : !e.some((t) => t === "-p" || t === "--print");
1144
1278
  }
1145
- async function Qt(e, t = {}) {
1146
- const o = E(process.cwd(), e ?? "aiui-demo");
1147
- switch (eo(o)) {
1279
+ function jn(e = process.cwd()) {
1280
+ return {
1281
+ project: de(e),
1282
+ user: Y(void 0, { create: !1 }),
1283
+ browser: A(!1)
1284
+ };
1285
+ }
1286
+ function Un(e, t) {
1287
+ const n = [];
1288
+ return e.userOnly || n.push({ label: "project cache", path: t.project }), e.projectOnly || n.push(
1289
+ e.keepBrowser ? {
1290
+ label: "user cache (keeping Chrome for Testing)",
1291
+ path: t.user,
1292
+ keep: [t.browser]
1293
+ } : { label: "user cache", path: t.user }
1294
+ ), n;
1295
+ }
1296
+ function An(e) {
1297
+ var o;
1298
+ if (!v(e.path))
1299
+ return [];
1300
+ if (!((o = e.keep) != null && o.length))
1301
+ return [e.path];
1302
+ const t = new Set(e.keep.map((r) => P(r)));
1303
+ let n;
1304
+ try {
1305
+ n = G(e.path);
1306
+ } catch {
1307
+ return [];
1308
+ }
1309
+ return n.map((r) => P(f(e.path, r))).filter((r) => !t.has(r));
1310
+ }
1311
+ async function Nn(e = {}) {
1312
+ if (e.projectOnly && e.userOnly) {
1313
+ g(
1314
+ "pass at most one of --project-only / --user-only",
1315
+ "with neither, `aiui clean` removes both"
1316
+ ), process.exitCode = 1;
1317
+ return;
1318
+ }
1319
+ const t = process.cwd(), o = Un(e, jn(t)).map((h) => ({ target: h, paths: An(h) })).filter((h) => h.paths.length > 0);
1320
+ if (o.length === 0) {
1321
+ console.log("nothing to clean — no aiui state here or in the user cache.");
1322
+ return;
1323
+ }
1324
+ let r = 0;
1325
+ console.log(`aiui clean will remove:
1326
+ `);
1327
+ for (const { target: h, paths: m } of o) {
1328
+ const b = m.reduce((w, C) => w + le(C), 0);
1329
+ r += b, console.log(` ${ne(b).padStart(9)} ${h.label}`), console.log(` ${" ".repeat(9)} ${l.dim(h.path)}`);
1330
+ }
1331
+ console.log(`
1332
+ ${ne(r).padStart(9)} total
1333
+ `);
1334
+ const i = !e.projectOnly, s = i && !e.keepBrowser, a = [
1335
+ "traces and session-browser logins are cleared",
1336
+ i && "the Claude permission prompt (claude.skipPermissions) returns on next launch",
1337
+ s && "Chrome for Testing re-downloads (~160 MB) on the next `aiui claude`"
1338
+ ].filter((h) => !!h);
1339
+ console.log("This resets aiui toward a fresh install:");
1340
+ for (const h of a)
1341
+ console.log(` • ${h}`);
1342
+ console.log("");
1343
+ const c = J(t);
1344
+ if (c.length && T(
1345
+ `${c.length} aiui session${c.length === 1 ? "" : "s"} still running`,
1346
+ "stop them first — a live session rewrites its registry entry as it exits, and an open browser can lock files being deleted"
1347
+ ), e.dryRun) {
1348
+ y("dry run — nothing was deleted");
1349
+ return;
1350
+ }
1351
+ if (!e.yes) {
1352
+ if (!process.stdin.isTTY) {
1353
+ g(
1354
+ "refusing to delete without confirmation",
1355
+ "re-run with --yes for a non-interactive clean, or from a terminal"
1356
+ ), process.exitCode = 1;
1357
+ return;
1358
+ }
1359
+ if (await z(
1360
+ "Delete these now?",
1361
+ [
1362
+ { key: "y", label: "yes, delete" },
1363
+ { key: "n", label: "no, cancel" }
1364
+ ],
1365
+ "n"
1366
+ ) !== "y") {
1367
+ console.log("cancelled — nothing was deleted.");
1368
+ return;
1369
+ }
1370
+ }
1371
+ let d = 0, p = 0;
1372
+ for (const { paths: h } of o)
1373
+ for (const m of h) {
1374
+ const b = le(m);
1375
+ try {
1376
+ Tt(m, { recursive: !0, force: !0 }), d += b;
1377
+ } catch (w) {
1378
+ p++, T(
1379
+ `could not remove ${m}`,
1380
+ w instanceof Error ? w.message : String(w)
1381
+ );
1382
+ }
1383
+ }
1384
+ p && T(
1385
+ `${p} path${p === 1 ? "" : "s"} could not be deleted`,
1386
+ "a running browser can lock its files — close the session browser and re-run"
1387
+ ), console.log(`clean complete — freed ~${ne(d)}.`);
1388
+ }
1389
+ function le(e) {
1390
+ let t;
1391
+ try {
1392
+ t = Pt(e);
1393
+ } catch {
1394
+ return 0;
1395
+ }
1396
+ if (t.isSymbolicLink() || !t.isDirectory())
1397
+ return t.size;
1398
+ let n;
1399
+ try {
1400
+ n = G(e);
1401
+ } catch {
1402
+ return 0;
1403
+ }
1404
+ let o = 0;
1405
+ for (const r of n)
1406
+ o += le(f(e, r));
1407
+ return o;
1408
+ }
1409
+ function ne(e) {
1410
+ if (e < 1024)
1411
+ return `${e} B`;
1412
+ const t = ["KB", "MB", "GB", "TB"];
1413
+ let n = e / 1024, o = 0;
1414
+ for (; n >= 1024 && o < t.length - 1; )
1415
+ n /= 1024, o++;
1416
+ return `${n.toFixed(1)} ${t[o]}`;
1417
+ }
1418
+ function j(e = process.cwd()) {
1419
+ const t = he(e);
1420
+ return {
1421
+ paths: t,
1422
+ user: O(t.user) ?? {},
1423
+ project: O(t.project) ?? {}
1424
+ };
1425
+ }
1426
+ function ve(e) {
1427
+ return Je().map((t) => {
1428
+ const n = Ae(e.user, t), o = Ae(e.project, t), r = o ?? n, i = o !== void 0 ? "project" : n !== void 0 ? "user" : t.field.default !== void 0 ? "default" : "unset";
1429
+ return { ...t, userValue: n, projectValue: o, effective: r, source: i };
1430
+ });
1431
+ }
1432
+ function Ae(e, t) {
1433
+ var o;
1434
+ return (o = e[t.section.name]) == null ? void 0 : o[t.field.key];
1435
+ }
1436
+ function ke(e, t) {
1437
+ const n = ve(t).find((o) => o.path === e);
1438
+ return n || (g(
1439
+ `unknown config key: ${e}`,
1440
+ `Known keys:
1441
+ ${Je().map((o) => o.path).join(`
1442
+ `)}`
1443
+ ), process.exitCode = 1), n;
1444
+ }
1445
+ function _n(e = {}, t = process.cwd()) {
1446
+ const n = j(t), o = ve(n);
1447
+ if (e.json) {
1448
+ console.log(
1449
+ JSON.stringify(
1450
+ {
1451
+ files: {
1452
+ user: { path: n.paths.user, exists: v(n.paths.user) },
1453
+ project: { path: n.paths.project, exists: v(n.paths.project) }
1454
+ },
1455
+ user: n.user,
1456
+ project: n.project,
1457
+ effective: Qe(n.user, n.project)
1458
+ },
1459
+ null,
1460
+ 2
1461
+ )
1462
+ );
1463
+ return;
1464
+ }
1465
+ console.log(`user: ${n.paths.user}${Ne(n.paths.user)}`), console.log(`project: ${n.paths.project}${Ne(n.paths.project)}`);
1466
+ const r = Math.max(...o.map((s) => s.path.length));
1467
+ let i = "";
1468
+ for (const s of o)
1469
+ s.section.name !== i && (i = s.section.name, console.log(`
1470
+ ${l.bold(i)} ${l.dim(`— ${s.section.summary}`)}`)), console.log(` ${s.path.padEnd(r + 2)}${dt(s)}`);
1471
+ console.log(
1472
+ l.dim(
1473
+ "\nProject beats user per key; CLI flags beat both. `aiui config` browses the docs interactively."
1474
+ )
1475
+ );
1476
+ }
1477
+ function Ne(e) {
1478
+ return v(e) ? "" : l.dim(" (not present)");
1479
+ }
1480
+ function dt(e) {
1481
+ return e.effective !== void 0 ? `${l.cyan($(e.effective))} ${l.dim(`(${e.source})`)}` : l.dim(
1482
+ e.field.default !== void 0 ? `default: ${$(e.field.default)}` : "unset"
1483
+ );
1484
+ }
1485
+ function Rn(e, t = process.cwd()) {
1486
+ const n = j(t), o = ke(e, n);
1487
+ if (o) {
1488
+ if (o.effective !== void 0) {
1489
+ console.log(String(o.effective));
1490
+ const r = o.source === "project" ? n.paths.project : n.paths.user;
1491
+ console.error(l.dim(`# set in the ${o.source} config: ${r}`));
1492
+ return;
1493
+ }
1494
+ if (o.field.default !== void 0) {
1495
+ console.log(String(o.field.default)), console.error(l.dim(`# built-in default — ${ce(o.field)}`));
1496
+ return;
1497
+ }
1498
+ console.error(
1499
+ l.dim(`# not set in any config file — default: ${ce(o.field)}`)
1500
+ );
1501
+ }
1502
+ }
1503
+ function Ln(e, t, n = {}, o = process.cwd()) {
1504
+ const r = j(o), i = ke(e, r);
1505
+ if (!i)
1506
+ return;
1507
+ const s = ae(i.field, t);
1508
+ if ("error" in s) {
1509
+ g(`invalid value for ${i.path}: ${t}`, s.error), process.exitCode = 1;
1510
+ return;
1511
+ }
1512
+ ht(r, i, s.value, n.project ? "project" : "user");
1513
+ }
1514
+ function ht(e, t, n, o) {
1515
+ const r = fe(e.paths[o], (i) => {
1516
+ const s = i;
1517
+ s[t.section.name] = { ...s[t.section.name], [t.field.key]: n };
1518
+ });
1519
+ y(`wrote ${t.path}: ${$(n)} to ${r}`);
1520
+ }
1521
+ function Mn(e, t = {}, n = process.cwd()) {
1522
+ const o = j(n), r = ke(e, o);
1523
+ r && ft(o, r, t.project ? "project" : "user");
1524
+ }
1525
+ function ft(e, t, n) {
1526
+ var a;
1527
+ const o = e.paths[n];
1528
+ if (((a = (O(o) ?? {})[t.section.name]) == null ? void 0 : a[t.field.key]) === void 0) {
1529
+ y(`${t.path} is not set in the ${n} config (${o})`);
1530
+ return;
1531
+ }
1532
+ const s = fe(o, (c) => {
1533
+ const d = c, p = { ...d[t.section.name] };
1534
+ delete p[t.field.key], Object.keys(p).length === 0 ? delete d[t.section.name] : d[t.section.name] = p;
1535
+ });
1536
+ y(`removed ${t.path} from ${s}`);
1537
+ }
1538
+ async function _e(e = process.cwd()) {
1539
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
1540
+ g(
1541
+ "aiui config tui needs an interactive terminal",
1542
+ "In scripts and non-TTY sessions use `aiui config show` (or `show --json`)."
1543
+ ), process.exitCode = 1;
1544
+ return;
1545
+ }
1546
+ const t = j(e).paths;
1547
+ console.log(l.dim(`user: ${Le(t.user)}`)), console.log(l.dim(`project: ${Le(t.project)}`));
1548
+ try {
1549
+ for (; ; ) {
1550
+ const n = j(e), o = await Bn(n);
1551
+ if (!o)
1552
+ return;
1553
+ await Vn(n, o);
1554
+ }
1555
+ } catch (n) {
1556
+ if (n instanceof Error && n.name === "ExitPromptError")
1557
+ return;
1558
+ throw n;
1559
+ }
1560
+ }
1561
+ async function Bn(e) {
1562
+ const t = ve(e), n = Math.max(...t.map((r) => r.path.length)) + 2, o = [];
1563
+ for (const r of M) {
1564
+ o.push(new ie(l.dim(`── ${r.name} — ${r.summary}`)));
1565
+ for (const i of t.filter((s) => s.section.name === r.name))
1566
+ o.push({
1567
+ name: `${i.path.padEnd(n)}${dt(i)}`,
1568
+ value: i,
1569
+ description: Fn(i)
1570
+ });
1571
+ }
1572
+ return o.push(new ie(" ")), o.push({ name: "exit", value: void 0, description: "Leave the config browser." }), K({
1573
+ message: "aiui config",
1574
+ choices: o,
1575
+ pageSize: o.length,
1576
+ loop: !1
1577
+ });
1578
+ }
1579
+ function Fn(e) {
1580
+ const t = [l.bold(e.field.summary)];
1581
+ return e.field.doc && t.push(Kn(e.field.doc, 76)), t.push(""), e.field.type === "enum" && t.push(`allowed: ${(e.field.values ?? []).join(" | ")}`), t.push(`default: ${ce(e.field)}`), t.push(`user: ${Re(e.userValue)}`), t.push(`project: ${Re(e.projectValue)}`), t.push(
1582
+ `effective: ${e.effective !== void 0 ? `${$(e.effective)} (from the ${e.source} config)` : "the built-in default"}`
1583
+ ), t.join(`
1584
+ `);
1585
+ }
1586
+ function Re(e) {
1587
+ return e === void 0 ? l.dim("(not set)") : $(e);
1588
+ }
1589
+ async function Vn(e, t) {
1590
+ const n = [
1591
+ {
1592
+ name: "set in the user config",
1593
+ value: "set-user",
1594
+ description: `${e.paths.user}
1595
+ Personal preference — applies to every project.`
1596
+ },
1597
+ {
1598
+ name: "set in the project config",
1599
+ value: "set-project",
1600
+ description: `${e.paths.project}
1601
+ This project only; the file may be shared or committed by a team.`
1602
+ }
1603
+ ];
1604
+ t.userValue !== void 0 && n.push({
1605
+ name: `unset in the user config (currently ${$(t.userValue)})`,
1606
+ value: "unset-user"
1607
+ }), t.projectValue !== void 0 && n.push({
1608
+ name: `unset in the project config (currently ${$(t.projectValue)})`,
1609
+ value: "unset-project"
1610
+ }), n.push(new ie()), n.push({ name: "back", value: "back" });
1611
+ const o = await K({
1612
+ message: `${t.path} — ${t.field.summary}`,
1613
+ choices: n
1614
+ });
1615
+ if (o === "back")
1616
+ return;
1617
+ const r = o.endsWith("project") ? "project" : "user";
1618
+ if (o.startsWith("unset")) {
1619
+ ft(e, t, r);
1620
+ return;
1621
+ }
1622
+ const i = await Hn(t);
1623
+ i !== void 0 && ht(e, t, i, r);
1624
+ }
1625
+ async function Hn(e) {
1626
+ const t = (r) => r === e.effective ? " (current)" : r === e.field.default ? " (default)" : "";
1627
+ if (e.field.type === "boolean")
1628
+ return K({
1629
+ message: `${e.path} =`,
1630
+ choices: [!0, !1].map((r) => ({
1631
+ name: `${r}${l.dim(t(r))}`,
1632
+ value: r
1633
+ }))
1634
+ });
1635
+ if (e.field.type === "enum")
1636
+ return K({
1637
+ message: `${e.path} =`,
1638
+ choices: (e.field.values ?? []).map((r) => ({
1639
+ name: `${r}${l.dim(t(r))}`,
1640
+ value: r
1641
+ }))
1642
+ });
1643
+ const n = await _t({
1644
+ message: `${e.path} = `,
1645
+ default: e.effective !== void 0 ? String(e.effective) : void 0,
1646
+ validate: (r) => {
1647
+ const i = ae(e.field, r);
1648
+ return "error" in i ? i.error : !0;
1649
+ }
1650
+ }), o = ae(e.field, n);
1651
+ return "error" in o ? void 0 : o.value;
1652
+ }
1653
+ function Le(e) {
1654
+ const t = At();
1655
+ return e.startsWith(t) ? `~${e.slice(t.length)}` : e;
1656
+ }
1657
+ function Kn(e, t) {
1658
+ const n = [];
1659
+ let o = "";
1660
+ for (const r of e.split(/\s+/))
1661
+ o && o.length + 1 + r.length > t ? (n.push(o), o = r) : o = o ? `${o} ${r}` : r;
1662
+ return o && n.push(o), n.join(`
1663
+ `);
1664
+ }
1665
+ const Yn = "vite", oe = "VITE_AIUI_PORT";
1666
+ function pt(e, t) {
1667
+ if (t !== void 0) {
1668
+ const n = e.find((o) => o.tag === t);
1669
+ if (!n) {
1670
+ const o = e.length > 0 ? e.map((r) => r.tag).join(", ") : "(none running)";
1671
+ return {
1672
+ error: `no running aiui channel with tag "${t}" — running tags: ${o}`
1673
+ };
1674
+ }
1675
+ return { server: n };
1676
+ }
1677
+ return e.length > 0 ? { select: e } : {};
1678
+ }
1679
+ async function Wn(e = []) {
1680
+ const t = lt(e), { mcp: n, tag: o, passthrough: r } = t, i = ct(r);
1681
+ if (i) {
1682
+ i === "help" ? Qn() : console.log(`aiui ${ee}`), await Xn(r);
1683
+ return;
1684
+ }
1685
+ const s = n ?? o, a = pt(J(), s);
1686
+ if (a.error) {
1687
+ g("Could not resolve an aiui channel", a.error), process.exitCode = 1;
1688
+ return;
1689
+ }
1690
+ const c = a.select ? await qe(a.select) : a.server;
1691
+ let d;
1692
+ c ? (d = String(c.port), console.error(
1693
+ l.dim(
1694
+ `aiui: connecting vite to channel "${c.tag}" (${c.cwd}) on port ${d} via ${oe}`
1695
+ )
1696
+ )) : console.error(l.dim(`aiui: no running channel found — ${oe} left unset`));
1697
+ const p = mt();
1698
+ if (!p)
1699
+ return;
1700
+ const h = {};
1701
+ d && (h[oe] = d), process.stdout.isTTY && !("FORCE_COLOR" in process.env) && !("NO_COLOR" in process.env) && (h.FORCE_COLOR = "1");
1702
+ const m = k(p.command, [...p.args, ...r], {
1703
+ stdio: ["inherit", "pipe", "inherit"],
1704
+ buffer: !1,
1705
+ reject: !1,
1706
+ env: h
1707
+ });
1708
+ m.stdout && Jn(m.stdout, process.stdout, (w) => {
1709
+ zn(w, t);
1710
+ });
1711
+ const b = await m;
1712
+ b.exitCode && (process.exitCode = b.exitCode);
1713
+ }
1714
+ const qn = /\x1b\[[0-9;?]*[ -/]*[@-~]/g;
1715
+ function Gn(e) {
1716
+ const n = e.replace(qn, "").match(
1717
+ /\bLocal:\s+(https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\])(?::\d+)?\/?\S*)/
1718
+ );
1719
+ return n == null ? void 0 : n[1];
1720
+ }
1721
+ function Jn(e, t, n) {
1722
+ let o = "", r = !1;
1723
+ e.on("data", (i) => {
1724
+ if (t.write(i), !r) {
1725
+ for (o += i.toString(); ; ) {
1726
+ const s = o.indexOf(`
1727
+ `);
1728
+ if (s === -1)
1729
+ break;
1730
+ const a = o.slice(0, s);
1731
+ o = o.slice(s + 1);
1732
+ const c = Gn(a);
1733
+ if (c) {
1734
+ r = !0, o = "", n(c);
1735
+ return;
1736
+ }
1737
+ }
1738
+ o.length > 8192 && (o = o.slice(-8192));
1739
+ }
1740
+ });
1741
+ }
1742
+ async function zn(e, t) {
1743
+ try {
1744
+ const n = {
1745
+ ...V().chrome,
1746
+ ...t.browserUrl ? { browserUrl: t.browserUrl } : {}
1747
+ }, o = Ct(t, n);
1748
+ if (o.kind === "skip")
1749
+ return;
1750
+ if (o.kind === "hint") {
1751
+ y(
1752
+ `detected a headless environment (${o.reason}) — not opening a browser`,
1753
+ `Assuming the dev server's port is already forwarded, open ${e} in the browser
1754
+ on your local machine. (Pass --aiui-browser to open one here anyway.)`
1755
+ );
1756
+ return;
1757
+ }
1758
+ if (n.browserUrl) {
1759
+ await H(n.browserUrl, e), console.error(l.dim(`aiui: opened ${e} in the browser at ${n.browserUrl}`));
1760
+ return;
1761
+ }
1762
+ const r = D(t, n), i = await F(r.userDataDir);
1763
+ i ? (await H(i.browserUrl, e), console.error(l.dim(`aiui: opened ${e} in the session browser`))) : (await st({
1764
+ flags: t,
1765
+ config: n,
1766
+ interactive: !1,
1767
+ startUrl: e
1768
+ }), console.error(l.dim(`aiui: opened ${e} in a new session browser`)));
1769
+ } catch (n) {
1770
+ T(
1771
+ "couldn't open the app in the session browser — the dev server is unaffected",
1772
+ n instanceof Error ? n.message : String(n)
1773
+ );
1774
+ }
1775
+ }
1776
+ function mt() {
1777
+ try {
1778
+ return ge(Yn);
1779
+ } catch {
1780
+ g(
1781
+ "Vite is not available",
1782
+ "`vite` should be installed as a dependency of aiui — try reinstalling."
1783
+ ), process.exitCode = 1;
1784
+ return;
1785
+ }
1786
+ }
1787
+ async function Xn(e) {
1788
+ const t = mt();
1789
+ if (!t)
1790
+ return;
1791
+ const n = await k(t.command, [...t.args, ...e], {
1792
+ stdio: "inherit",
1793
+ reject: !1
1794
+ });
1795
+ n.exitCode && (process.exitCode = n.exitCode);
1796
+ }
1797
+ function Qn() {
1798
+ console.log(`aiui vite — launch Vite connected to the running aiui channel
1799
+
1800
+ aiui's own flags (everything else forwards to vite verbatim):
1801
+ --aiui-mcp <tag> connect to the channel server with this tag
1802
+ --aiui-tag <tag> accepted alias for --aiui-mcp
1803
+ --aiui-browser open the app in the session browser even when
1804
+ the environment looks headless (CI, SSH, no display)
1805
+ --aiui-no-browser never open a browser for this run
1806
+ --aiui-chrome-profile <name> browser profile at .aiui-cache/chrome/<name>
1807
+ --aiui-chrome-data-dir <path> explicit browser user data dir
1808
+
1809
+ The chosen channel's port is exported as VITE_AIUI_PORT; the aiuiDevOverlay()
1810
+ Vite plugin picks it up there and wires the intent tool to it. When Vite prints
1811
+ its Local: URL, aiui opens it in the shared session browser (the one \`aiui
1812
+ claude\` and \`aiui open\` use); in headless environments it prints the URL to
1813
+ open on your own machine instead. What follows is vite's own --help:
1814
+ `);
1815
+ }
1816
+ const re = "/__aiui/debug", Me = 4747;
1817
+ function Zn() {
1818
+ let e = R(Ge(import.meta.url));
1819
+ for (let t = 0; t < 5; t++) {
1820
+ const n = f(e, "package.json");
1821
+ if (v(n))
1822
+ try {
1823
+ if (JSON.parse(U(n, "utf8")).name === "@habemus-papadum/aiui")
1824
+ return e;
1825
+ } catch {
1826
+ }
1827
+ e = R(e);
1828
+ }
1829
+ }
1830
+ async function eo(e = {}) {
1831
+ var d;
1832
+ const t = pt(J(), e.mcp);
1833
+ if (t.error) {
1834
+ g("Could not resolve an aiui channel", t.error), process.exitCode = 1;
1835
+ return;
1836
+ }
1837
+ const n = t.select ? await qe(t.select) : t.server;
1838
+ if (!n) {
1839
+ console.log("No running aiui channel to debug — start one with `aiui claude`."), process.exitCode = 1;
1840
+ return;
1841
+ }
1842
+ const o = Zn();
1843
+ if (!o) {
1844
+ g("Could not locate the aiui package root to serve the viewer from"), process.exitCode = 1;
1845
+ return;
1846
+ }
1847
+ const r = {
1848
+ name: "aiui:debug-home",
1849
+ configureServer(p) {
1850
+ p.middlewares.use((h, m, b) => {
1851
+ if ((h.url ?? "/").split("?")[0] === "/") {
1852
+ m.statusCode = 302, m.setHeader("location", re), m.end();
1853
+ return;
1854
+ }
1855
+ b();
1856
+ });
1857
+ }
1858
+ }, i = Number(e.port), s = await Lt({
1859
+ root: o,
1860
+ configFile: !1,
1861
+ // `mount: false`: this server hosts the viewer, not an app — nothing to
1862
+ // arm, so the intent tool stays out of it. The plugin still serves the
1863
+ // DEBUG_ROUTE page and seeds the picked channel's port into it.
1864
+ plugins: [r, Rt({ port: n.port, mount: !1 })],
1865
+ server: {
1866
+ port: Number.isInteger(i) && i > 0 ? i : Me,
1867
+ open: e.open === !1 ? !1 : re
1868
+ },
1869
+ logLevel: "warn"
1870
+ });
1871
+ await s.listen();
1872
+ const c = `${(((d = s.resolvedUrls) == null ? void 0 : d.local[0]) ?? `http://localhost:${Me}/`).replace(/\/$/, "")}${re}`;
1873
+ console.log(`${l.cyan("aiui debug")} — the lowering-trace viewer`), console.log(` viewing ${l.bold(c)}`), console.log(
1874
+ l.dim(` channel "${n.tag}" (${n.cwd}) on port ${n.port}`) + l.dim(" — switch channels from the page's header. Ctrl-C to stop.")
1875
+ );
1876
+ }
1877
+ async function to(e, t = {}) {
1878
+ const n = P(process.cwd(), e ?? "aiui-demo");
1879
+ switch (no(n)) {
1148
1880
  case "occupied":
1149
- m(
1150
- `${o} already exists and isn't an aiui demo`,
1881
+ g(
1882
+ `${n} already exists and isn't an aiui demo`,
1151
1883
  "Pick an empty (or new) directory — the scaffold never overwrites existing content."
1152
1884
  ), process.exitCode = 1;
1153
1885
  return;
1154
1886
  case "existing-demo":
1155
- v(`existing demo found at ${o} — continuing where it left off`);
1887
+ y(`existing demo found at ${n} — continuing where it left off`);
1156
1888
  break;
1157
1889
  case "new": {
1158
- const n = no();
1159
- if (!n) {
1160
- m("the demo template did not ship with this aiui install"), process.exitCode = 1;
1890
+ const o = so();
1891
+ if (!o) {
1892
+ g("the demo template did not ship with this aiui install"), process.exitCode = 1;
1161
1893
  return;
1162
1894
  }
1163
- oo(n, o), console.log(`scaffolded the demo playground at ${o}`), await ro(o);
1895
+ ro(o, n), console.log(`scaffolded the demo playground at ${n}`), await io(n);
1164
1896
  break;
1165
1897
  }
1166
1898
  }
1167
- if (!t.skipInstall && !b(l(o, "node_modules")))
1168
- if (!ee("npm"))
1169
- v("npm not found on PATH — run your package manager's install in the demo yourself");
1899
+ if (!t.skipInstall && !v(f(n, "node_modules")))
1900
+ if (!ye("npm"))
1901
+ y("npm not found on PATH — run your package manager's install in the demo yourself");
1170
1902
  else {
1171
1903
  console.log("installing dependencies (one time)…");
1172
- const n = await p("npm", ["install", "--no-audit", "--no-fund"], {
1173
- cwd: o,
1904
+ const o = await k("npm", ["install", "--no-audit", "--no-fund"], {
1905
+ cwd: n,
1174
1906
  stdio: "inherit",
1175
1907
  reject: !1
1176
1908
  });
1177
- if (n.exitCode) {
1178
- m("npm install failed — fix that, then re-run this command to continue"), process.exitCode = n.exitCode;
1909
+ if (o.exitCode) {
1910
+ g("npm install failed — fix that, then re-run this command to continue"), process.exitCode = o.exitCode;
1179
1911
  return;
1180
1912
  }
1181
1913
  }
1182
- io(o);
1914
+ ao(n);
1183
1915
  }
1184
- function eo(e) {
1185
- var o;
1186
- if (!b(e))
1916
+ function no(e) {
1917
+ var n;
1918
+ if (!v(e))
1187
1919
  return "new";
1188
1920
  let t;
1189
1921
  try {
1190
- t = ge(e);
1922
+ t = G(e);
1191
1923
  } catch {
1192
1924
  return "occupied";
1193
1925
  }
1194
1926
  if (t.length === 0)
1195
1927
  return "new";
1196
1928
  try {
1197
- if (((o = JSON.parse(P(l(e, "package.json"), "utf8")).aiui) == null ? void 0 : o.demo) === !0)
1929
+ if (((n = JSON.parse(U(f(e, "package.json"), "utf8")).aiui) == null ? void 0 : n.demo) === !0)
1198
1930
  return "existing-demo";
1199
1931
  } catch {
1200
1932
  }
1201
1933
  return "occupied";
1202
1934
  }
1203
- function to(e) {
1935
+ function oo(e) {
1204
1936
  return /^\d+\.\d+\.\d+$/.test(e) ? `^${e}` : "latest";
1205
1937
  }
1206
- function oo(e, t) {
1207
- k(t, { recursive: !0 }), Je(e, t, { recursive: !0 }), b(l(t, "gitignore")) && We(l(t, "gitignore"), l(t, ".gitignore"));
1208
- const o = l(t, "package.json");
1209
- T(
1210
- o,
1211
- P(o, "utf8").replaceAll(
1938
+ function ro(e, t) {
1939
+ W(t, { recursive: !0 }), Et(e, t, { recursive: !0 }), v(f(t, "gitignore")) && Dt(f(t, "gitignore"), f(t, ".gitignore"));
1940
+ const n = f(t, "package.json");
1941
+ q(
1942
+ n,
1943
+ U(n, "utf8").replaceAll(
1212
1944
  "__AIUI_VERSION_RANGE__",
1213
- to(L)
1945
+ oo(ee)
1214
1946
  )
1215
1947
  );
1216
1948
  }
1217
- async function ro(e) {
1218
- !ee("git") || (await p("git", ["-C", e, "rev-parse", "--is-inside-work-tree"], {
1949
+ async function io(e) {
1950
+ !ye("git") || (await k("git", ["-C", e, "rev-parse", "--is-inside-work-tree"], {
1219
1951
  reject: !1
1220
- })).exitCode === 0 || (await p("git", ["-C", e, "init", "--quiet"], { reject: !1 })).exitCode !== 0 || (await p("git", ["-C", e, "add", "-A"], { reject: !1 }), await p("git", ["-C", e, "commit", "--quiet", "-m", "aiui demo scaffold"], {
1952
+ })).exitCode === 0 || (await k("git", ["-C", e, "init", "--quiet"], { reject: !1 })).exitCode !== 0 || (await k("git", ["-C", e, "add", "-A"], { reject: !1 }), await k("git", ["-C", e, "commit", "--quiet", "-m", "aiui demo scaffold"], {
1221
1953
  reject: !1
1222
1954
  }));
1223
1955
  }
1224
- function no() {
1225
- let e = S(nt(import.meta.url));
1956
+ function so() {
1957
+ let e = R(Ge(import.meta.url));
1226
1958
  for (let t = 0; t < 4; t++) {
1227
- const o = l(e, "templates", "demo");
1228
- if (b(l(o, "package.json")))
1229
- return o;
1230
- e = S(e);
1959
+ const n = f(e, "templates", "demo");
1960
+ if (v(f(n, "package.json")))
1961
+ return n;
1962
+ e = R(e);
1231
1963
  }
1232
1964
  }
1233
- function io(e) {
1234
- const t = Ke(process.cwd(), e) || ".", o = t === "aiui-demo" ? "aiui demo" : `aiui demo ${t}`;
1965
+ function ao(e) {
1966
+ const t = yt(process.cwd(), e) || ".", n = t === "aiui-demo" ? "aiui demo" : `aiui demo ${t}`;
1235
1967
  console.log(`
1236
1968
  demo ready. Run the loop:
1237
1969
 
@@ -1244,104 +1976,90 @@ then open the app in the session browser (the window you share with the agent):
1244
1976
  npx aiui open http://localhost:5173
1245
1977
 
1246
1978
  Click the ✳ aiui button on the page and type an intent — it lands in the session
1247
- as a prompt. Re-run \`${o}\` anytime to continue this sandbox.`);
1979
+ as a prompt. Re-run \`${n}\` anytime to continue this sandbox.`);
1248
1980
  }
1249
- const ao = "@habemus-papadum/aiui-claude-channel";
1250
- async function so(e = []) {
1981
+ const co = "@habemus-papadum/aiui-claude-channel";
1982
+ async function lo(e = []) {
1251
1983
  let t;
1252
1984
  try {
1253
- t = X(ao);
1985
+ t = ge(co);
1254
1986
  } catch {
1255
- m(
1987
+ g(
1256
1988
  "The aiui Claude channel CLI is not available",
1257
1989
  "`@habemus-papadum/aiui-claude-channel` should be installed as a dependency of aiui — try reinstalling."
1258
1990
  ), process.exitCode = 1;
1259
1991
  return;
1260
1992
  }
1261
- const o = await p(t.command, [...t.args, ...e], {
1993
+ const n = await k(t.command, [...t.args, ...e], {
1262
1994
  stdio: "inherit",
1263
1995
  reject: !1
1264
1996
  });
1265
- o.exitCode && (process.exitCode = o.exitCode);
1266
- }
1267
- const co = "vite", K = "VITE_AIUI_PORT";
1268
- function lo(e, t) {
1269
- if (t !== void 0) {
1270
- const o = e.find((n) => n.tag === t);
1271
- if (!o) {
1272
- const n = e.length > 0 ? e.map((r) => r.tag).join(", ") : "(none running)";
1273
- return {
1274
- error: `no running aiui channel with tag "${t}" — running tags: ${n}`
1275
- };
1276
- }
1277
- return { server: o };
1278
- }
1279
- return e.length > 0 ? { select: e } : {};
1997
+ n.exitCode && (process.exitCode = n.exitCode);
1280
1998
  }
1281
- async function uo(e = []) {
1282
- const { mcp: t, tag: o, passthrough: n } = je(e), r = Re(n);
1283
- if (r) {
1284
- r === "help" ? fo() : console.log(`aiui ${L}`), await ho(n);
1285
- return;
1286
- }
1287
- const i = t ?? o, a = lo(Qe(), i);
1288
- if (a.error) {
1289
- m("Could not resolve an aiui channel", a.error), process.exitCode = 1;
1290
- return;
1291
- }
1292
- const s = a.select ? await et(a.select) : a.server;
1293
- let c;
1294
- s ? (c = String(s.port), console.error(
1295
- g.dim(
1296
- `aiui: connecting vite to channel "${s.tag}" (${s.cwd}) on port ${c} via ${K}`
1297
- )
1298
- )) : console.error(g.dim(`aiui: no running channel found — ${K} left unset`));
1299
- const u = Le();
1300
- if (!u)
1301
- return;
1302
- const f = await p(u.command, [...u.args, ...n], {
1303
- stdio: "inherit",
1304
- reject: !1,
1305
- ...c ? { env: { [K]: c } } : {}
1306
- });
1307
- f.exitCode && (process.exitCode = f.exitCode);
1999
+ function uo() {
2000
+ const e = [];
2001
+ for (const t of Object.values(Nt()))
2002
+ for (const n of t ?? [])
2003
+ n.family === "IPv4" && !n.internal && e.push(n.address);
2004
+ return e;
1308
2005
  }
1309
- function Le() {
2006
+ async function Be(e, t) {
1310
2007
  try {
1311
- return X(co);
2008
+ const n = await fetch(`http://127.0.0.1:${e}${t}`, {
2009
+ signal: AbortSignal.timeout(1500)
2010
+ });
2011
+ return n.ok ? await n.json() : void 0;
1312
2012
  } catch {
1313
- m(
1314
- "Vite is not available",
1315
- "`vite` should be installed as a dependency of aiui — try reinstalling."
1316
- ), process.exitCode = 1;
1317
2013
  return;
1318
2014
  }
1319
2015
  }
1320
2016
  async function ho(e) {
1321
- const t = Le();
1322
- if (!t)
2017
+ const t = await Be(
2018
+ e,
2019
+ "/paint/info"
2020
+ );
2021
+ if (!(t != null && t.ok))
1323
2022
  return;
1324
- const o = await p(t.command, [...t.args, ...e], {
1325
- stdio: "inherit",
1326
- reject: !1
1327
- });
1328
- o.exitCode && (process.exitCode = o.exitCode);
2023
+ const n = await Be(e, "/health"), o = (n == null ? void 0 : n.host) !== void 0 && n.host !== "127.0.0.1";
2024
+ return {
2025
+ lan: o,
2026
+ urls: o ? uo().map((r) => `http://${r}:${e}/paint/`) : [`http://127.0.0.1:${e}/paint/`],
2027
+ hosts: t.hosts ?? 0,
2028
+ clients: t.clients ?? 0
2029
+ };
1329
2030
  }
1330
- function fo() {
1331
- console.log(`aiui vite launch Vite connected to the running aiui channel
1332
-
1333
- aiui's own flags (everything else forwards to vite verbatim):
1334
- --aiui-mcp <tag> connect to the channel server with this tag
1335
- --aiui-tag <tag> accepted alias for --aiui-mcp
1336
-
1337
- The chosen channel's port is exported as VITE_AIUI_PORT; the aiuiDevOverlay()
1338
- Vite plugin picks it up there and wires the intent tool to it. What follows is
1339
- vite's own --help:
1340
- `);
2031
+ async function fo(e = {}) {
2032
+ const t = J(), n = [];
2033
+ for (const o of t) {
2034
+ const r = await ho(o.port);
2035
+ r && n.push({ cwd: o.cwd, pid: o.pid, port: o.port, ...r });
2036
+ }
2037
+ if (e.json) {
2038
+ console.log(JSON.stringify({ targets: n }, null, 2));
2039
+ return;
2040
+ }
2041
+ if (n.length === 0) {
2042
+ console.log("No running channel is hosting the paint surface."), console.log(""), console.log(
2043
+ `It is on by default — start a session with ${l.cyan("aiui claude")}. If it's off here,`
2044
+ ), console.log(
2045
+ `check for ${l.cyan("sidecars.paint false")} in config or a --aiui-no-sidecar flag.`
2046
+ ), process.exitCode = 1;
2047
+ return;
2048
+ }
2049
+ for (const o of n) {
2050
+ console.log(""), console.log(`${l.bold(o.cwd)} ${l.dim(`(channel :${o.port})`)}`);
2051
+ for (const i of o.urls.length ? o.urls : ["(no LAN address found)"])
2052
+ console.log(` ${l.cyan(i)}`);
2053
+ const r = o.lan ? "open the URL on the iPad (same Wi-Fi)" : "loopback-only — the iPad needs a tunnel to this port (Tailscale, ssh -L), or relaunch with --aiui-bind host";
2054
+ console.log(
2055
+ l.dim(` ${o.hosts} browser host(s), ${o.clients} viewer(s) — ${r}`)
2056
+ );
2057
+ }
2058
+ console.log("");
1341
2059
  }
1342
- function mo() {
1343
- const e = new Be();
1344
- return e.name("aiui").description("ai ui frontends — thin launchers for Claude, Vite, and the channel CLI").version(L).enablePositionalOptions(), e.command("claude").description("launch Claude (extra args are forwarded, e.g. `aiui claude --resume`)").allowUnknownOption().allowExcessArguments().helpOption(!1).argument("[args...]", "arguments forwarded to claude").action((t) => Jt(t)), e.command("vite").description("launch Vite (extra args are forwarded, e.g. `aiui vite dev`)").allowUnknownOption().allowExcessArguments().helpOption(!1).argument("[args...]", "arguments forwarded to vite").action((t) => uo(t)), e.command("chrome").description("manage the agent's browser: install | update | status | extension").argument("<action>", "install | update | status | extension").action((t) => Ot([t])), e.command("browser").description(
2060
+ function po() {
2061
+ const e = new gt();
2062
+ e.name("aiui").description("ai ui frontends — thin launchers for Claude, Vite, and the channel CLI").version(ee).enablePositionalOptions(), e.command("claude").description("launch Claude (extra args are forwarded, e.g. `aiui claude --resume`)").allowUnknownOption().allowExcessArguments().helpOption(!1).argument("[args...]", "arguments forwarded to claude").action((o) => En(o)), e.command("vite").description("launch Vite (extra args are forwarded, e.g. `aiui vite dev`)").allowUnknownOption().allowExcessArguments().helpOption(!1).argument("[args...]", "arguments forwarded to vite").action((o) => Wn(o)), e.command("debug").description("open the lowering-trace viewer for a running channel (switchable in-page)").option("--mcp <tag>", "target a channel by registry tag (skips the selector)").option("--port <port>", "UI port for the viewer's dev server (default 4747)").option("--no-open", "don't open the browser").action((o) => eo(o)), e.command("chrome").description("manage the agent's browser: install | update | status | extension").argument("<action>", "install | update | status | extension").action((o) => cn([o])), e.command("browser").description(
1345
2063
  "start (or find) the shared session browser; --tunnel does the whole remote-dev local half"
1346
2064
  ).option(
1347
2065
  "--profile <name>",
@@ -1349,9 +2067,13 @@ function mo() {
1349
2067
  ).option("--data-dir <path>", "explicit Chrome user data dir").option("--port <port>", "fixed local DevTools debug port (default: OS-assigned)").option("--headless", "launch with no UI").option("--open <url>", "also open this URL in it").option(
1350
2068
  "--tunnel <[user@]host>",
1351
2069
  "reverse-tunnel the debug port to this host (Ctrl-C closes it)"
1352
- ).option("--remote-port <port>", "fixed port on the tunnel's remote side (default: 9222)").action((t) => $t(t)), e.command("demo").description("scaffold a runnable demo playground (safe to re-run; default dir: aiui-demo)").argument("[dir]", "target directory (default: aiui-demo)").option("--skip-install", "scaffold only — don't run npm install").action((t, o) => Qt(t, o)), e.command("open").description("open a URL in the session browser, e.g. `aiui open http://localhost:5173`").argument("<url>", "the URL to open").option("--profile <name>", "named profile under .aiui-cache/chrome/").option("--data-dir <path>", "explicit Chrome user data dir").action((t, o) => At(t, o)), e.command("mcp").description("run a channel command (forwards to aiui-claude-channel), e.g. `aiui mcp quick`").allowUnknownOption().allowExcessArguments().helpOption(!1).argument("[args...]", "arguments forwarded to the aiui-claude-channel CLI").action((t) => so(t)), e;
2070
+ ).option("--remote-port <port>", "fixed port on the tunnel's remote side (default: 9222)").action((o) => en(o)), e.command("demo").description("scaffold a runnable demo playground (safe to re-run; default dir: aiui-demo)").argument("[dir]", "target directory (default: aiui-demo)").option("--skip-install", "scaffold only — don't run npm install").action((o, r) => to(o, r)), e.command("clean").description(
2071
+ "reset aiui state (project + user cache, incl. Chrome for Testing) for a clean-slate demo"
2072
+ ).option("--project-only", "only this repo's .aiui-cache/").option("--user-only", "only the user cache (~/.cache/aiui)").option("--keep-browser", "keep Chrome for Testing (skip the ~160 MB re-download)").option("-n, --dry-run", "print what would be deleted, then stop").option("-y, --yes", "delete without the confirmation prompt").action((o) => Nn(o)), e.command("open").description("open a URL in the session browser, e.g. `aiui open http://localhost:5173`").argument("<url>", "the URL to open").option("--profile <name>", "named profile under .aiui-cache/chrome/").option("--data-dir <path>", "explicit Chrome user data dir").action((o, r) => an(o, r));
2073
+ const t = e.command("config").description("inspect and edit aiui's config.json — tui | show | get | set | unset").action(() => _e());
2074
+ return t.command("tui").description("browse every setting interactively: docs, defaults, current values, editing").action(() => _e()), t.command("show").description("every key with its effective value and which file set it").option("--json", "machine-readable: file paths, per-level values, effective merge").action((o) => _n(o)), t.command("get").description("print a key's effective value (provenance goes to stderr)").argument("<key>", 'dotted key, e.g. "chrome.mode"').action((o) => Rn(o)), t.command("set").description("set a key in the user config (or the project's with --project)").argument("<key>", 'dotted key, e.g. "chrome.mode"').argument("<value>", "the new value, validated against the schema").option("--project", "write .aiui-cache/config.json here instead of the user config").action((o, r, i) => Ln(o, r, i)), t.command("unset").description("remove a key from the user config (or the project's with --project)").argument("<key>", 'dotted key, e.g. "claude.skipPermissions"').option("--project", "remove from .aiui-cache/config.json here instead of the user config").action((o, r) => Mn(o, r)), e.command("mcp").description("run a channel command (forwards to aiui-claude-channel), e.g. `aiui mcp quick`").allowUnknownOption().allowExcessArguments().helpOption(!1).argument("[args...]", "arguments forwarded to the aiui-claude-channel CLI").action((o) => lo(o)), e.command("paint").description("the iPad paint stream — url (where the iPad should connect)").command("url").description("print the URL(s) an iPad should open, per running paint-enabled channel").option("--json", "machine-readable targets").action((o) => fo(o)), e;
1353
2075
  }
1354
- mo().parseAsync().catch((e) => {
2076
+ po().parseAsync().catch((e) => {
1355
2077
  console.error(e instanceof Error ? e.message : e), process.exitCode = 1;
1356
2078
  });
1357
2079
  //# sourceMappingURL=cli.js.map