@habemus-papadum/aiui 0.1.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 He } from "commander";
3
- import { join as c, dirname as U, resolve as $, delimiter as Ve, relative as Be } from "node:path";
4
- import { cacheDir as G } from "@habemus-papadum/aiui-util";
5
- import { execa as w } from "execa";
6
- import { mkdirSync as T, rmSync as qe, writeFileSync as k, readFileSync as D, existsSync as g, realpathSync as pe, readdirSync as we, accessSync as Ge, constants as Je, cpSync as We, renameSync as Ye } from "node:fs";
7
- import { computeSystemExecutablePath as Ke, ChromeReleaseChannel as O, Browser as x, detectBrowserPlatform as be, resolveBuildId as ge, getInstalledBrowsers as ye, install as ze, uninstall as Xe } from "@puppeteer/browsers";
8
- import { projectCacheDir as ve, listMcpServers as Ze, selectMcpServer as Qe } from "@habemus-papadum/aiui-claude-channel";
9
- import { createInterface as et } from "node:readline/promises";
10
- import b from "chalk";
11
- import { createRequire as tt } from "node:module";
12
- import { spawn as ot } from "node:child_process";
13
- import { fileURLToPath as rt } from "node:url";
14
- const xe = "DevToolsActivePort", ie = 2e4;
15
- async function I(e) {
16
- const t = nt(e);
17
- if (t !== void 0 && await it(t))
18
- return { browserUrl: `http://127.0.0.1:${t}`, port: t };
19
- }
20
- function nt(e) {
21
- try {
22
- const [t] = D(c(e, xe), "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 it(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 Ce(e) {
39
- return e.executablePath ? e.executablePath : Ke({
40
- browser: x.CHROME,
41
- channel: at[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 at = {
45
- stable: O.STABLE,
46
- beta: O.BETA,
47
- dev: O.DEV,
48
- canary: O.CANARY
49
- };
50
- async function $e(e) {
51
- T(e.userDataDir, { recursive: !0 }), qe(c(e.userDataDir, xe), { 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 = w(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() + ie;
71
- for (; Date.now() < r; ) {
72
- const i = await I(e.userDataDir);
73
- if (i) {
74
- try {
75
- k(
76
- c(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 st(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 ${ie / 1e3}s`
92
- );
187
+ const o = Xe(e, n);
188
+ return o ? { error: o } : { value: n };
93
189
  }
94
- async function Ee(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 st(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);
104
195
  }
105
- const ae = "config.json", se = ["stable", "beta", "dev", "canary"], ce = ["prompt", "auto", "off"], le = ["attach", "launch"];
106
- function De(e = process.cwd()) {
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
+ }
203
+ }
204
+ const Ee = "config.json";
205
+ function he(e = process.cwd()) {
107
206
  return {
108
- user: c(G(void 0, { create: !1 }), ae),
109
- project: c(ve(e), ae)
207
+ user: f(Y(void 0, { create: !1 }), Ee),
208
+ project: f(de(e), Ee)
110
209
  };
111
210
  }
112
- function R(e = process.cwd()) {
113
- const t = De(e);
114
- return ct(q(t.user) ?? {}, q(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 ct(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 q(e) {
224
+ function O(e) {
123
225
  let t;
124
226
  try {
125
- t = D(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 lt(o, e);
138
- }
139
- function lt(e, t) {
140
- const o = F(e, t, "the top level");
141
- H(o, ["claude", "chrome"], t, "the top level");
142
- const n = {};
143
- if (o.claude !== void 0) {
144
- const r = F(o.claude, t, '"claude"');
145
- H(r, ["skipPermissions", "enterNudge"], t, '"claude"'), n.claude = ue({
146
- skipPermissions: p(
147
- r.skipPermissions,
148
- "boolean",
149
- t,
150
- "claude.skipPermissions"
151
- ),
152
- enterNudge: p(r.enterNudge, "boolean", t, "claude.enterNudge")
153
- });
154
- }
155
- if (o.chrome !== void 0) {
156
- const r = F(o.chrome, t, '"chrome"');
157
- H(
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 = p(r.channel, "string", t, "chrome.channel");
176
- if (i !== void 0 && !se.includes(i))
177
- throw new Error(
178
- `invalid chrome.channel "${i}" in ${t} — expected one of: ${se.join(", ")}`
179
- );
180
- const a = p(r.forTesting, "string", t, "chrome.forTesting");
181
- if (a !== void 0 && !ce.includes(a))
182
- throw new Error(
183
- `invalid chrome.forTesting "${a}" in ${t} — expected one of: ${ce.join(", ")}`
184
- );
185
- const s = p(r.mode, "string", t, "chrome.mode");
186
- if (s !== void 0 && !le.includes(s))
187
- throw new Error(
188
- `invalid chrome.mode "${s}" in ${t} — expected one of: ${le.join(", ")}`
189
- );
190
- const l = p(r.browserUrl, "string", t, "chrome.browserUrl");
191
- if (l !== void 0 && !ut(l))
192
- throw new Error(
193
- `invalid chrome.browserUrl "${l}" in ${t} — expected an http(s) URL like "http://127.0.0.1:9222"`
194
- );
195
- const u = p(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 = ue({
199
- enabled: p(r.enabled, "boolean", t, "chrome.enabled"),
200
- mode: s,
201
- browserUrl: l,
202
- debugPort: u,
203
- profile: p(r.profile, "string", t, "chrome.profile"),
204
- dataDir: p(r.dataDir, "string", t, "chrome.dataDir"),
205
- executablePath: p(r.executablePath, "string", t, "chrome.executablePath"),
206
- channel: i,
207
- forTesting: a,
208
- headless: p(r.headless, "boolean", t, "chrome.headless"),
209
- buildExtension: p(r.buildExtension, "boolean", t, "chrome.buildExtension")
210
- });
211
- }
212
- return n;
213
- }
214
- function ut(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 A(e) {
223
- const t = De().user, o = q(t) ?? {};
224
- return e(o), T(U(t), { recursive: !0 }), k(t, `${JSON.stringify(o, null, 2)}
225
- `), t;
279
+ function B(e) {
280
+ return fe(he().user, e);
226
281
  }
227
- function ue(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 F(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 H(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 p(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 J(e, t, o) {
250
- const n = et({ 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) => ` ${b.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(`${b.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((l) => l.key === a) ?? t.find((l) => l.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(`${b.bgRed.white.bold(" ERROR ")} ${b.red.bold(e)}`), t && console.error(b.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 Pe(e, t) {
277
- console.error(`${b.bgYellow.black.bold(" WARN ")} ${b.yellow.bold(e)}`), t && console.error(b.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
329
  function y(e, t) {
280
- console.error(`${b.bgCyan.black.bold(" NOTE ")} ${b.cyan(e)}`), t && console.error(b.dim(t));
330
+ console.error(`${l.bgCyan.black.bold(" NOTE ")} ${l.cyan(e)}`), t && console.error(l.dim(t));
281
331
  }
282
- const Te = 1440 * 60 * 1e3, dt = 4e3;
283
- function S(e = !0) {
284
- return G("chrome", { create: e });
332
+ const Ze = 1440 * 60 * 1e3, Ht = 4e3;
333
+ function A(e = !0) {
334
+ return Y("chrome", { create: e });
285
335
  }
286
- const ke = "update-state.json";
287
- function N() {
336
+ const et = "update-state.json";
337
+ function X() {
288
338
  try {
289
- return JSON.parse(D(c(S(!1), ke), "utf8"));
339
+ return JSON.parse(U(f(A(!1), et), "utf8"));
290
340
  } catch {
291
341
  return {};
292
342
  }
293
343
  }
294
- function _(e) {
295
- const t = S();
296
- T(t, { recursive: !0 }), k(c(t, ke), `${JSON.stringify({ ...N(), ...e })}
344
+ function Q(e) {
345
+ const t = A();
346
+ W(t, { recursive: !0 }), q(f(t, et), `${JSON.stringify({ ...X(), ...e })}
297
347
  `);
298
348
  }
299
- function W(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 Y() {
309
- const e = S(!1);
310
- if (!g(e))
358
+ async function me() {
359
+ const e = A(!1);
360
+ if (!v(e))
311
361
  return;
312
- const n = (await ye({ cacheDir: e })).filter((r) => r.browser === x.CHROME).sort((r, i) => W(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 K(e = {}) {
316
- const { maxAgeMs: t = Te, timeoutMs: o = dt, now: n = Date.now() } = e, r = N();
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 pt(
323
- ge(x.CHROME, i, "stable"),
324
- o
372
+ const s = await qt(
373
+ Ye(I.CHROME, i, "stable"),
374
+ n
325
375
  );
326
- return _({ 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 = S(), o = await ze({
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 ye({ 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 Xe({ 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 ht(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 ge(x.CHROME, t, "stable");
349
- _({ checkedAt: Date.now(), latestBuildId: o });
350
- const n = await Y();
351
- if (n && W(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 Y();
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 ft(t, n);
365
- const i = await K({ now: n });
366
- return !i || W(i, r.buildId) <= 0 ? r.executablePath : mt(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 ft(e, t) {
369
- const o = await K({ 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 y(`installing Chrome for Testing ${o} (chrome.forTesting: "auto")…`), (await C(o)).executablePath;
374
- const n = N();
375
- if (n.installDeclinedAt && t - n.installDeclinedAt < Te)
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 J(
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 ${S(!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,161 +434,162 @@ async function ft(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 = A((a) => {
390
- a.chrome = { ...a.chrome, forTesting: "off" };
439
+ const i = B((s) => {
440
+ s.chrome = { ...s.chrome, forTesting: "off" };
391
441
  });
392
442
  y(`wrote chrome.forTesting: "off" to ${i}`);
393
443
  } else
394
- _({ installDeclinedAt: t });
444
+ Q({ installDeclinedAt: t });
395
445
  }
396
- async function mt(e, t, o) {
446
+ async function Wt(e, t, n) {
397
447
  if (e === "auto")
398
448
  return y(
399
- `updating Chrome for Testing ${t.buildId} → ${o} (chrome.forTesting: "auto")…`
400
- ), (await C(o)).executablePath;
401
- if (N().skippedBuildId === o)
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 J(
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 = A((i) => {
469
+ const r = B((i) => {
420
470
  i.chrome = { ...i.chrome, forTesting: "auto" };
421
471
  });
422
- return y(`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 = A((i) => {
475
+ const r = B((i) => {
426
476
  i.chrome = { ...i.chrome, forTesting: "off" };
427
477
  });
428
478
  return y(`wrote chrome.forTesting: "off" to ${r}`), t.executablePath;
429
479
  }
430
480
  default:
431
- return _({ skippedBuildId: o }), t.executablePath;
481
+ return Q({ skippedBuildId: n }), t.executablePath;
432
482
  }
433
483
  }
434
- async function pt(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);
445
- }
446
- }
447
- const wt = tt(import.meta.url);
448
- function P(e) {
449
- const t = e.split("/");
450
- for (const o of wt.resolve.paths(e) ?? []) {
451
- const n = c(o, ...t, "package.json");
452
- if (g(n))
453
- return U(n);
494
+ clearTimeout(n);
454
495
  }
455
- throw new Error(`could not locate the "${e}" package (is it installed?)`);
456
496
  }
457
- function z(e, t) {
458
- const o = P(e), n = JSON.parse(D(c(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 (g(c(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", $(o, i)] };
505
+ return { command: process.execPath, args: ["--import", "tsx", P(n, i)] };
466
506
  }
467
- return { command: process.execPath, args: [$(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 gt = "chrome-devtools", Se = "@habemus-papadum/aiui-devtools-extension", yt = "default", vt = /^[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 : !X(o);
475
- }
476
- function X(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(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: Ue({ 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 && $(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 Ue(e, t = process.cwd()) {
535
+ function rt(e, t = process.cwd()) {
500
536
  if (e.dataDir)
501
- return $(t, e.dataDir);
502
- const o = e.profile ?? yt;
503
- if (!vt.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 c(ve(t), "chrome", o);
543
+ return f(de(t), "chrome", n);
508
544
  }
509
- function j() {
545
+ function Z() {
510
546
  let e;
511
547
  try {
512
- e = pe(c(P(Se), "extension"));
548
+ e = He(f(L(nt), "extension"));
513
549
  } catch {
514
550
  return;
515
551
  }
516
- return g(c(e, "js")) ? e : void 0;
552
+ return v(f(e, "js")) ? e : void 0;
517
553
  }
518
- async function Z() {
554
+ async function be() {
519
555
  let e, t;
520
556
  try {
521
- e = pe(P(Se)), t = c(P("typescript"), "bin", "tsc");
557
+ e = He(L(nt)), t = f(L("typescript"), "bin", "tsc");
522
558
  } catch {
523
559
  return;
524
560
  }
525
- if (!g(c(e, "src")))
561
+ if (!v(f(e, "src")))
526
562
  return;
527
- const o = await w(process.execPath, [t, "-p", c(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
- o.exitCode && Pe(
533
- "aiui-devtools-extension failed to compile — the DevTools panel will be stale or missing",
534
- o.all || o.message
568
+ if (n.exitCode) {
569
+ T(
570
+ "aiui-devtools-extension failed to compile — the DevTools panel will be stale or missing",
571
+ n.all || n.message
572
+ );
573
+ return;
574
+ }
575
+ const o = f(e, "build-debug-ui.mjs");
576
+ if (!v(o))
577
+ return;
578
+ const r = await k(process.execPath, [o], {
579
+ cwd: e,
580
+ reject: !1,
581
+ all: !0
582
+ });
583
+ r.exitCode && T(
584
+ "aiui-devtools-extension debug-ui bundle failed — the Intent pane will be degraded",
585
+ r.all || r.message
535
586
  );
536
587
  }
537
- function Ae(e, t) {
588
+ function it(e, t) {
538
589
  if (!t || e.executablePath)
539
590
  return;
540
- const o = c(e.userDataDir, "aiui-devtools-extension-hint");
541
- if (!g(o)) {
591
+ const n = f(e.userDataDir, "aiui-devtools-extension-hint");
592
+ if (!v(n)) {
542
593
  y(
543
594
  "the aiui DevTools panel can't auto-load into regular Chrome (≥ 137 ignores --load-extension)",
544
595
  `Load it once in the launched Chrome — chrome://extensions → Developer mode → Load unpacked →
@@ -547,101 +598,120 @@ ${t}
547
598
  which auto-loads it. This note won't repeat for this profile.`
548
599
  );
549
600
  try {
550
- k(o, `${(/* @__PURE__ */ new Date()).toISOString()}
601
+ q(n, `${(/* @__PURE__ */ new Date()).toISOString()}
551
602
  `);
552
603
  } catch {
553
604
  }
554
605
  }
555
606
  }
556
- function V(e) {
607
+ function te(e) {
557
608
  return {
558
609
  command: "npx",
559
610
  args: ["-y", "chrome-devtools-mcp@latest", "--browser-url", e]
560
611
  };
561
612
  }
562
- function xt(e, t) {
563
- const o = [
613
+ function Qt(e, t) {
614
+ const n = [
564
615
  "-y",
565
616
  "chrome-devtools-mcp@latest",
566
617
  "--userDataDir",
567
618
  e.userDataDir,
568
619
  "--ignoreDefaultChromeArg=--disable-extensions"
569
620
  ];
570
- 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 };
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 };
571
622
  }
572
- const Ct = 9222;
573
- async function $t(e) {
574
- const o = { ...R().chrome };
575
- if (o.browserUrl) {
623
+ const Zt = 9222;
624
+ async function en(e) {
625
+ const n = { ...V().chrome };
626
+ if (n.browserUrl) {
576
627
  y(
577
- `config pins chrome.browserUrl to ${o.browserUrl} — the browser is managed elsewhere`,
628
+ `config pins chrome.browserUrl to ${n.browserUrl} — the browser is managed elsewhere`,
578
629
  "Run `aiui browser` on the machine that should host it (and drop browserUrl there)."
579
630
  );
580
631
  return;
581
632
  }
582
- let n;
633
+ let o, r;
583
634
  try {
584
- n = Ne(e.remotePort, "--remote-port") ?? Ct;
585
- } catch (l) {
586
- m(l instanceof Error ? l.message : String(l)), process.exitCode = 1;
635
+ o = Se(e.remotePort, "--remote-port") ?? Zt, r = Se(e.port, "--port");
636
+ } catch (c) {
637
+ g(c instanceof Error ? c.message : String(c)), process.exitCode = 1;
587
638
  return;
588
639
  }
589
- let r = o;
590
640
  const i = {
591
641
  chromeProfile: e.tunnel ? void 0 : e.profile,
592
- chromeDataDir: e.dataDir ?? (e.tunnel ? Et(e.tunnel, e.profile) : void 0)
642
+ chromeDataDir: e.dataDir ?? (e.tunnel ? tn(e.tunnel, e.profile) : void 0)
593
643
  };
594
- let a = de(E(i, r), e), s = await I(a.userDataDir);
595
- if (s)
596
- he("session browser already running", a, s), e.open && (await Ee(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}`));
597
649
  else {
598
- const l = !!process.stdin.isTTY && !!process.stdout.isTTY && !X();
599
- if (!r.executablePath && !r.channel) {
600
- const h = await Ie({ mode: r.forTesting ?? "prompt", interactive: l });
601
- h && (r = { ...r, executablePath: h }, a = de(E(i, r), e));
602
- }
603
- a.buildExtension && await Z();
604
- const u = j();
605
- l && Ae(a, u);
606
- let f;
607
- try {
608
- f = Ce(a);
609
- } catch (h) {
610
- m(
611
- "no browser to launch",
612
- `${h instanceof Error ? h.message : String(h)}
613
- Install Chrome for Testing with \`aiui chrome install\`, or set chrome.executablePath.`
614
- ), process.exitCode = 1;
615
- return;
616
- }
650
+ const c = !!process.stdin.isTTY && !!process.stdout.isTTY && !ue();
617
651
  try {
618
- s = await $e({
619
- binary: f,
620
- userDataDir: a.userDataDir,
621
- debugPort: a.debugPort,
622
- extensionDir: u,
623
- 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,
624
658
  startUrl: e.open
625
659
  });
626
- } catch (h) {
627
- m(
660
+ a = d.session, s = d.settings;
661
+ } catch (d) {
662
+ g(
628
663
  "the session browser failed to start",
629
- h instanceof Error ? h.message : String(h)
664
+ d instanceof Error ? d.message : String(d)
630
665
  ), process.exitCode = 1;
631
666
  return;
632
667
  }
633
- he("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
+ );
634
697
  }
635
- e.tunnel ? await Tt(e.tunnel, n, s.port) : kt(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 };
636
706
  }
637
- function Et(e, t) {
638
- const o = t ?? Dt(e);
639
- return c(G("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);
640
710
  }
641
- function Dt(e) {
711
+ function nn(e) {
642
712
  return (e.includes("@") ? e.slice(e.indexOf("@") + 1) : e).replace(/[^A-Za-z0-9._-]/g, "-") || "remote";
643
713
  }
644
- function Pt(e, t, o) {
714
+ function on(e, t, n) {
645
715
  return [
646
716
  // No remote command — the connection exists only to carry the forward...
647
717
  "-N",
@@ -650,114 +720,110 @@ function Pt(e, t, o) {
650
720
  "-o",
651
721
  "ExitOnForwardFailure=yes",
652
722
  "-R",
653
- `${t}:localhost:${o}`,
723
+ `${t}:localhost:${n}`,
654
724
  e
655
725
  ];
656
726
  }
657
- function Re(e) {
727
+ function at(e) {
658
728
  return `aiui claude --aiui-browser-url http://127.0.0.1:${e}`;
659
729
  }
660
- async function Tt(e, t, o) {
730
+ async function rn(e, t, n) {
661
731
  console.log(
662
732
  `
663
- tunneling ${e}:${t} → localhost:${o} — on ${e}, run:
733
+ tunneling ${e}:${t} → localhost:${n} — on ${e}, run:
664
734
 
665
- ${Re(t)}
735
+ ${at(t)}
666
736
 
667
737
  (Ctrl-C closes the tunnel; the browser stays running.)`
668
738
  );
669
- const n = await w("ssh", Pt(e, t, o), {
739
+ const o = await k("ssh", on(e, t, n), {
670
740
  stdio: "inherit",
671
741
  reject: !1
672
742
  });
673
- n.failed && !n.isTerminated && (m(
674
- `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})`,
675
745
  "A taken remote port exits immediately (ExitOnForwardFailure) — try another --remote-port. The browser is still running; rerun `aiui browser --tunnel …` to reconnect."
676
746
  ), process.exitCode = 1);
677
747
  }
678
- function Ne(e, t) {
748
+ function Se(e, t) {
679
749
  if (e === void 0)
680
750
  return;
681
- const o = Number(e);
682
- if (!(Number.isInteger(o) && o >= 0 && o <= 65535))
751
+ const n = Number(e);
752
+ if (!(Number.isInteger(n) && n >= 0 && n <= 65535))
683
753
  throw new Error(`invalid ${t} ${e} — expected 0..65535`);
684
- return o;
685
- }
686
- function de(e, t) {
687
- const o = Ne(t.port, "--port");
688
- return o === void 0 ? e : { ...e, debugPort: o };
754
+ return n;
689
755
  }
690
- function he(e, t, o) {
691
- 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}`);
692
758
  }
693
- function kt(e, t) {
759
+ function sn(e, t) {
694
760
  console.log(
695
761
  `
696
762
  An \`aiui claude\` in this profile's project attaches automatically. For a *remote*
697
763
  session, rerun with \`--tunnel <[user@]host>\` — or do it by hand:
698
764
  ssh -N -o ExitOnForwardFailure=yes -R ${t}:localhost:${e.port} <host>
699
- then, on the remote: ${Re(t)}`
765
+ then, on the remote: ${at(t)}`
700
766
  );
701
767
  }
702
- async function It(e, t) {
703
- var i, a;
704
- const o = R(), n = E(
768
+ async function an(e, t) {
769
+ var i, s;
770
+ const n = V(), o = D(
705
771
  { chromeProfile: t.profile, chromeDataDir: t.dataDir },
706
- o.chrome ?? {}
707
- ), 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);
708
774
  if (!r) {
709
- m(
775
+ g(
710
776
  "no session browser is running for this profile",
711
- `Start one with \`aiui browser\` (profile: ${n.userDataDir}).`
777
+ `Start one with \`aiui browser\` (profile: ${o.userDataDir}).`
712
778
  ), process.exitCode = 1;
713
779
  return;
714
780
  }
715
781
  try {
716
- await Ee(r, e), console.log(`opened ${e}`);
717
- } catch (s) {
718
- 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;
719
785
  }
720
786
  }
721
- async function St(e) {
787
+ async function cn(e) {
722
788
  const [t] = e;
723
789
  switch (t) {
724
790
  case "install":
725
791
  case "update":
726
- await ht((o) => console.log(o));
792
+ await Kt((n) => console.log(n));
727
793
  return;
728
794
  case "status":
729
- await Ot();
795
+ await ln();
730
796
  return;
731
797
  case "extension": {
732
- await Z();
733
- const o = j();
734
- if (!o) {
735
- m(
798
+ await be();
799
+ const n = Z();
800
+ if (!n) {
801
+ g(
736
802
  "the aiui-devtools-extension is not available in this install",
737
803
  "In a dev checkout, build it first: pnpm --filter @habemus-papadum/aiui-devtools-extension build"
738
804
  ), process.exitCode = 1;
739
805
  return;
740
806
  }
741
- console.log(o);
807
+ console.log(n);
742
808
  return;
743
809
  }
744
810
  default:
745
- m(
811
+ g(
746
812
  t ? `unknown aiui chrome action: ${t}` : "aiui chrome needs an action",
747
813
  "Usage: aiui chrome <install | update | status | extension>"
748
814
  ), process.exitCode = 1;
749
815
  return;
750
816
  }
751
817
  }
752
- async function Ot() {
753
- const t = R().chrome ?? {}, o = { chrome: !1, noChrome: !1 }, n = await Y(), r = await K();
754
- if (console.log("Chrome for Testing (managed):"), n) {
755
- const v = r === void 0 ? "(latest stable unknown — offline?)" : r === n.buildId ? "(latest stable)" : `(latest stable is ${r} — run \`aiui chrome update\`)`;
756
- console.log(` installed ${n.buildId} ${v}`), 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}`);
757
823
  } else
758
824
  console.log(" not installed — `aiui chrome install` (recommended; auto-loads the panel)");
759
825
  if (console.log(` startup checks (chrome.forTesting): ${t.forTesting ?? "prompt"}`), console.log(`
760
- This directory would launch:`), !Oe(o, t)) {
826
+ This directory would launch:`), !ot(n, t)) {
761
827
  console.log(" nothing — the Chrome DevTools MCP is disabled here");
762
828
  return;
763
829
  }
@@ -766,224 +832,361 @@ This directory would launch:`), !Oe(o, t)) {
766
832
  return;
767
833
  }
768
834
  const i = { ...t };
769
- !i.executablePath && !i.channel && n && (i.executablePath = n.executablePath);
770
- const a = E({}, i), s = await I(a.userDataDir), l = 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";
771
- console.log(` connection: ${l}`);
772
- 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)";
773
- console.log(` browser: ${u}${a.headless ? " — headless" : ""}`), console.log(` user data dir: ${a.userDataDir}`);
774
- const f = c(Ue({}, process.cwd()), "..");
775
- if (g(f)) {
776
- const v = we(f, { withFileTypes: !0 }).filter((d) => d.isDirectory()).map((d) => d.name);
777
- v.length && console.log(` profiles here: ${v.join(", ")}`);
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";
837
+ console.log(` connection: ${c}`);
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(", ")}`);
778
844
  }
779
845
  console.log(`
780
846
  aiui DevTools panel:`);
781
- const h = j();
847
+ const h = Z();
782
848
  if (!h) {
783
849
  console.log(" not available (unbuilt dev checkout? run `aiui chrome extension` for help)");
784
850
  return;
785
851
  }
786
- 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(
787
853
  " (chrome://extensions → Developer mode → Load unpacked), or `aiui chrome install`"
788
854
  ));
789
855
  }
790
- const Ut = "--aiui-";
791
- function _e(e) {
856
+ const un = "--aiui-";
857
+ function ct(e) {
792
858
  if (e.includes("--help") || e.includes("-h"))
793
859
  return "help";
794
860
  if (e.includes("--version") || e.includes("-v"))
795
861
  return "version";
796
862
  }
797
- function je(e) {
798
- let t, o, n = !1, r = !1, i, a, s;
799
- const l = [];
800
- for (let u = 0; u < e.length; u++) {
801
- const f = e[u];
802
- if (!f.startsWith(Ut)) {
803
- l.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);
804
872
  continue;
805
873
  }
806
- const h = f.indexOf("="), v = h === -1 ? f : f.slice(0, h);
807
- let d = h === -1 ? void 0 : f.slice(h + 1);
808
- switch (v) {
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) {
809
877
  case "--aiui-tag": {
810
- if (d === void 0 && (d = e[++u]), !d)
878
+ if (u === void 0 && (u = e[++w]), !u)
811
879
  throw new Error("--aiui-tag requires a non-empty value");
812
- t = d;
880
+ t = u;
813
881
  break;
814
882
  }
815
883
  case "--aiui-mcp": {
816
- if (d === void 0 && (d = e[++u]), !d)
884
+ if (u === void 0 && (u = e[++w]), !u)
817
885
  throw new Error("--aiui-mcp requires a non-empty value");
818
- o = d;
886
+ n = u;
819
887
  break;
820
888
  }
821
889
  case "--aiui-chrome": {
822
- if (d !== void 0)
890
+ if (u !== void 0)
823
891
  throw new Error("--aiui-chrome takes no value");
824
- n = !0;
892
+ o = !0;
825
893
  break;
826
894
  }
827
895
  case "--aiui-no-chrome": {
828
- if (d !== void 0)
896
+ if (u !== void 0)
829
897
  throw new Error("--aiui-no-chrome takes no value");
830
898
  r = !0;
831
899
  break;
832
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
+ }
833
913
  case "--aiui-chrome-profile": {
834
- if (d === void 0 && (d = e[++u]), !d)
914
+ if (u === void 0 && (u = e[++w]), !u)
835
915
  throw new Error("--aiui-chrome-profile requires a non-empty value");
836
- i = d;
916
+ a = u;
837
917
  break;
838
918
  }
839
919
  case "--aiui-chrome-data-dir": {
840
- if (d === void 0 && (d = e[++u]), !d)
920
+ if (u === void 0 && (u = e[++w]), !u)
841
921
  throw new Error("--aiui-chrome-data-dir requires a non-empty value");
842
- a = d;
922
+ c = u;
843
923
  break;
844
924
  }
845
925
  case "--aiui-browser-url": {
846
- if (d === void 0 && (d = e[++u]), !d)
926
+ if (u === void 0 && (u = e[++w]), !u)
847
927
  throw new Error("--aiui-browser-url requires a non-empty value");
848
- 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;
849
947
  break;
850
948
  }
851
949
  default:
852
- throw new Error(`unknown aiui option: ${v}`);
950
+ throw new Error(`unknown aiui option: ${N}`);
853
951
  }
854
952
  }
855
- if (n && r)
953
+ if (o && r)
856
954
  throw new Error("--aiui-chrome and --aiui-no-chrome are mutually exclusive");
857
- 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)
858
958
  throw new Error("--aiui-chrome-profile and --aiui-chrome-data-dir are mutually exclusive");
859
- if (s !== void 0 && (i !== void 0 || a !== void 0))
959
+ if (d !== void 0 && (a !== void 0 || c !== void 0))
860
960
  throw new Error(
861
961
  "--aiui-browser-url means the browser is managed elsewhere — it can't be combined with --aiui-chrome-profile or --aiui-chrome-data-dir"
862
962
  );
863
- return { tag: t, mcp: o, chrome: n, noChrome: r, chromeProfile: i, chromeDataDir: a, browserUrl: s, passthrough: l };
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
+ };
864
978
  }
865
- const At = {
979
+ const dn = {
866
980
  darwin: 2147578994,
867
981
  linux: 21522
868
- }, Rt = 'open(my $t,"+<","/dev/tty") or exit 0; my $c="\\r"; ioctl($t,$ARGV[0]+0,$c);', Nt = [250, 750];
869
- function _t(e = Nt) {
870
- const t = At[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];
871
985
  if (t !== void 0)
872
- for (const o of e)
986
+ for (const n of e)
873
987
  setTimeout(() => {
874
988
  try {
875
- ot("perl", ["-e", Rt, String(t)], { stdio: "ignore" }).on("error", () => {
989
+ jt("perl", ["-e", hn, String(t)], { stdio: "ignore" }).on("error", () => {
876
990
  });
877
991
  } catch {
878
992
  }
879
- }, o).unref();
993
+ }, n).unref();
880
994
  }
881
- const jt = `One-time setup — how should aiui launch Claude Code?
995
+ const mn = `One-time setup — how should aiui launch Claude Code?
882
996
  With --dangerously-skip-permissions, every agent action (shell commands, file writes,
883
997
  network, the browser) runs without asking you first. Fast, and dangerous. It's a personal
884
998
  preference — aiui works fine either way. Saved as claude.skipPermissions in your user
885
- config; edit or delete it there to change your mind.`, Mt = `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?
886
1007
  aiui loads a custom development channel, so Claude Code shows a one-key acknowledgement
887
1008
  prompt at every startup. aiui can dismiss it for you: shortly after launch it injects a
888
1009
  single Enter keystroke into this terminal (a best-effort TIOCSTI ioctl on /dev/tty — it
889
1010
  literally "types" the Enter for you; on platforms that forbid that, nothing happens and
890
1011
  you press it yourself). Saying no just means pressing Enter once per launch. Saved as
891
1012
  claude.enterNudge in your user config.`;
892
- async function Lt(e, t = J) {
893
- var n, r;
894
- let o = e;
895
- if (((n = o.claude) == null ? void 0 : n.skipPermissions) === void 0) {
896
- const i = await t(jt, [
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, [
897
1018
  { key: "y", label: "yes — skip permissions; nothing asks before acting" },
898
1019
  { key: "n", label: "no — keep Claude Code's own permission prompts" }
899
1020
  ]);
900
- o = fe(o, "skipPermissions", i === "y");
1021
+ n = je(n, "skipPermissions", s === "y");
901
1022
  }
902
- if (((r = o.claude) == null ? void 0 : r.enterNudge) === void 0) {
903
- const i = await t(Mt, [
1023
+ if (((r = n.claude) == null ? void 0 : r.enterNudge) === void 0) {
1024
+ const s = await t(gn, [
904
1025
  { key: "y", label: "yes — press Enter for me at startup" },
905
1026
  { key: "n", label: "no — I'll press it myself each launch" }
906
1027
  ]);
907
- o = fe(o, "enterNudge", i === "y");
1028
+ n = je(n, "enterNudge", s === "y");
908
1029
  }
909
- 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;
910
1038
  }
911
- function fe(e, t, o) {
912
- const n = A((r) => {
913
- r.claude = { ...r.claude, [t]: o };
1039
+ function je(e, t, n) {
1040
+ const o = B((r) => {
1041
+ r.claude = { ...r.claude, [t]: n };
914
1042
  });
915
- return y(`wrote claude.${t}: ${o} to ${n}`), { ...e, claude: { ...e.claude, [t]: o } };
1043
+ return y(`wrote claude.${t}: ${n} to ${o}`), { ...e, claude: { ...e.claude, [t]: n } };
916
1044
  }
917
- const M = "0.1.0";
918
- function Q(e) {
919
- const t = (process.env.PATH ?? "").split(Ve).filter(Boolean), o = process.platform === "win32" ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";") : [""];
920
- for (const n of t)
921
- for (const r of o)
1045
+ function yn(e, t) {
1046
+ const n = B((o) => {
1047
+ o.channel = { ...o.channel, bind: t };
1048
+ });
1049
+ return y(`wrote channel.bind: ${t} to ${n}`), { ...e, channel: { ...e.channel, bind: t } };
1050
+ }
1051
+ const vn = "https://api.openai.com/v1/models", kn = 3e3;
1052
+ async function Cn(e = {}) {
1053
+ var c;
1054
+ const {
1055
+ verify: t = !0,
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();
1060
+ if (!i)
1061
+ return "missing";
1062
+ if (!t)
1063
+ return "unverified";
1064
+ const s = new AbortController(), a = setTimeout(() => s.abort(), r);
1065
+ try {
1066
+ const d = await o(vn, {
1067
+ headers: { authorization: `Bearer ${i}` },
1068
+ signal: s.signal
1069
+ });
1070
+ return d.ok ? "valid" : d.status === 401 || d.status === 403 ? "invalid" : "unverified";
1071
+ } catch {
1072
+ return "unverified";
1073
+ } finally {
1074
+ clearTimeout(a);
1075
+ }
1076
+ }
1077
+ function $n(e) {
1078
+ switch (e) {
1079
+ case "valid":
1080
+ return null;
1081
+ case "missing":
1082
+ return {
1083
+ level: "warn",
1084
+ title: "OPENAI_API_KEY is not set — the intent pipeline will run degraded",
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.)"
1086
+ };
1087
+ case "invalid":
1088
+ return {
1089
+ level: "warn",
1090
+ title: "OPENAI_API_KEY was rejected by OpenAI (401) — the intent pipeline will run degraded",
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):
1092
+ echo $OPENAI_API_KEY | head -c 12
1093
+ and compare that against the start of your real key. Until it's fixed, transcription and correction are unavailable.`
1094
+ };
1095
+ case "unverified":
1096
+ return {
1097
+ level: "note",
1098
+ title: "couldn't verify OPENAI_API_KEY with OpenAI — continuing",
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."
1100
+ };
1101
+ }
1102
+ }
1103
+ function xn(e) {
1104
+ const t = $n(e);
1105
+ t && (t.level === "warn" ? T(t.title, t.detail) : y(t.title, t.detail));
1106
+ }
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)
922
1112
  try {
923
- return Ge(c(n, e + r), Je.X_OK), !0;
1113
+ return $t(f(o, e + r), xt.X_OK), !0;
924
1114
  } catch {
925
1115
  }
926
1116
  return !1;
927
1117
  }
928
- const Ft = "@habemus-papadum/aiui-claude-channel", Ht = "@habemus-papadum/aiui-claude-plugin", me = "aiui";
929
- async function Vt(e = []) {
930
- var oe, re;
931
- const t = je(e), { tag: o, passthrough: n } = t, r = _e(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);
932
1122
  if (r) {
933
- r === "help" ? qt() : console.log(`aiui ${M}`), await Bt(n);
1123
+ r === "help" ? In() : console.log(`aiui ${ee}`), await Dn(o);
934
1124
  return;
935
1125
  }
936
- let i = R();
937
- const a = Jt(n) && !X();
938
- if (a && (i = await Lt(i)), !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())
939
1131
  return;
940
- const s = $(P(Ht), "marketplace", "plugins"), l = z(Ft), u = [...l.args, "mcp"];
941
- o && u.push("--tag", o);
942
- const f = {
943
- [me]: { command: l.command, args: u }
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 }
944
1138
  };
945
- let h = { enabled: !1 };
946
- if (Oe(t, i.chrome)) {
947
- const L = {
1139
+ let b = { enabled: !1 };
1140
+ if (ot(t, i.chrome)) {
1141
+ const E = {
948
1142
  ...i.chrome,
949
1143
  ...t.browserUrl ? { browserUrl: t.browserUrl } : {}
950
- }, ne = await Gt(t, L, a);
951
- f[gt] = ne.entry, h = ne.info;
952
- }
953
- const v = { launcher: "aiui claude", chromeDevtools: h };
954
- u.push("--launch-info", JSON.stringify(v));
955
- const d = JSON.stringify({ mcpServers: f }), ee = [c(s, "aiui"), c(s, "frontend-design")];
956
- h.enabled && ee.push(c(s, "session-browser"));
957
- const Fe = [
958
- ...((oe = i.claude) == null ? void 0 : oe.skipPermissions) ?? !0 ? ["--dangerously-skip-permissions"] : [],
1144
+ }, _ = await Sn(t, E, s);
1145
+ m[Jt] = _.entry, b = _.info;
1146
+ }
1147
+ const w = {
1148
+ launcher: "aiui claude",
1149
+ chromeDevtools: b,
1150
+ openaiKey: a
1151
+ };
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"] : [],
959
1162
  "--mcp-config",
960
- d,
961
- ...ee.flatMap((L) => ["--plugin-dir", L]),
1163
+ u,
1164
+ ...Ce.flatMap((E) => ["--plugin-dir", E]),
962
1165
  // Custom channels are a research preview and not on the approved allowlist,
963
1166
  // so opt this session into loading ours as a development channel.
964
1167
  "--dangerously-load-development-channels",
965
- `server:${me}`
1168
+ `server:${Ue}`
966
1169
  ];
967
- a && (((re = i.claude) == null ? void 0 : re.enterNudge) ?? !0) && _t();
968
- const te = await w("claude", [...Fe, ...n], {
1170
+ s && (((Pe = i.claude) == null ? void 0 : Pe.enterNudge) ?? !0) && pn();
1171
+ const $e = await k("claude", [...wt, ...o], {
969
1172
  stdio: "inherit",
970
1173
  reject: !1
971
1174
  });
972
- te.exitCode && (process.exitCode = te.exitCode);
1175
+ $e.exitCode && (process.exitCode = $e.exitCode);
973
1176
  }
974
- function Me() {
975
- return Q("claude") ? !0 : (m(
1177
+ function ut() {
1178
+ return ye("claude") ? !0 : (g(
976
1179
  "`claude` was not found on your PATH",
977
1180
  "Install Claude Code and make sure the `claude` command is available, then try again."
978
1181
  ), process.exitCode = 1, !1);
979
1182
  }
980
- async function Bt(e) {
981
- if (!Me())
1183
+ async function Dn(e) {
1184
+ if (!ut())
982
1185
  return;
983
- const t = await w("claude", e, { stdio: "inherit", reject: !1 });
1186
+ const t = await k("claude", e, { stdio: "inherit", reject: !1 });
984
1187
  t.exitCode && (process.exitCode = t.exitCode);
985
1188
  }
986
- function qt() {
1189
+ function In() {
987
1190
  console.log(`aiui claude — launch Claude Code wired with the aiui channel, plugin, and browser MCP
988
1191
 
989
1192
  aiui's own flags (everything else forwards to claude verbatim):
@@ -994,167 +1197,773 @@ aiui's own flags (everything else forwards to claude verbatim):
994
1197
  --aiui-chrome-data-dir <path> explicit browser user data dir
995
1198
  --aiui-browser-url <url> attach to a browser at this DevTools endpoint
996
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)
997
1208
 
998
1209
  Durable settings live in config.json (project .aiui-cache/ + user cache) — see the
999
1210
  Configuration guide. What follows is claude's own --help:
1000
1211
  `);
1001
1212
  }
1002
- async function Gt(e, t, o) {
1213
+ async function Sn(e, t, n) {
1003
1214
  if (t.browserUrl)
1004
1215
  return {
1005
- entry: V(t.browserUrl),
1216
+ entry: te(t.browserUrl),
1006
1217
  info: { enabled: !0, connection: "attach", browserUrl: t.browserUrl }
1007
1218
  };
1008
- let n = { ...t }, r = E(e, n);
1219
+ let o = { ...t }, r = D(e, o);
1009
1220
  if (r.mode === "attach") {
1010
- const s = await I(r.userDataDir);
1011
- if (s)
1221
+ const a = await F(r.userDataDir);
1222
+ if (a)
1012
1223
  return {
1013
- entry: V(s.browserUrl),
1224
+ entry: te(a.browserUrl),
1014
1225
  info: {
1015
1226
  enabled: !0,
1016
1227
  connection: "attach",
1017
- browserUrl: s.browserUrl,
1228
+ browserUrl: a.browserUrl,
1018
1229
  userDataDir: r.userDataDir
1019
1230
  }
1020
1231
  };
1021
1232
  }
1022
- if (!n.executablePath && !n.channel) {
1023
- const s = await Ie({ mode: n.forTesting ?? "prompt", interactive: o });
1024
- s && (n = { ...n, executablePath: s }, r = E(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));
1025
1236
  }
1026
- T(r.userDataDir, { recursive: !0 }), r.buildExtension && await Z();
1027
- const i = j();
1028
- o && Ae(r, i);
1029
- const a = {
1237
+ W(r.userDataDir, { recursive: !0 }), r.buildExtension && await be();
1238
+ const i = Z();
1239
+ n && it(r, i);
1240
+ const s = {
1030
1241
  userDataDir: r.userDataDir,
1031
1242
  executablePath: r.executablePath,
1032
1243
  channel: r.channel,
1033
1244
  headless: r.headless,
1034
1245
  extensionDir: i
1035
1246
  };
1036
- if (r.mode === "attach" && o)
1247
+ if (r.mode === "attach" && n)
1037
1248
  try {
1038
- const s = await $e({
1039
- binary: Ce(r),
1249
+ const a = await Ve({
1250
+ binary: Fe(r),
1040
1251
  userDataDir: r.userDataDir,
1041
1252
  debugPort: r.debugPort,
1042
1253
  extensionDir: i,
1043
1254
  headless: r.headless
1044
1255
  });
1045
1256
  return {
1046
- entry: V(s.browserUrl),
1257
+ entry: te(a.browserUrl),
1047
1258
  info: {
1048
1259
  enabled: !0,
1049
1260
  connection: "attach",
1050
- browserUrl: s.browserUrl,
1051
- ...a
1261
+ browserUrl: a.browserUrl,
1262
+ ...s
1052
1263
  }
1053
1264
  };
1054
- } catch (s) {
1055
- Pe(
1265
+ } catch (a) {
1266
+ T(
1056
1267
  "couldn't start the session browser — falling back to a browser private to the MCP",
1057
- s instanceof Error ? s.message : String(s)
1268
+ a instanceof Error ? a.message : String(a)
1058
1269
  );
1059
1270
  }
1060
1271
  return {
1061
- entry: xt(r, i),
1062
- info: { enabled: !0, connection: "launch", ...a }
1272
+ entry: Qt(r, i),
1273
+ info: { enabled: !0, connection: "launch", ...s }
1063
1274
  };
1064
1275
  }
1065
- function Jt(e) {
1276
+ function On(e) {
1066
1277
  return !process.stdin.isTTY || !process.stdout.isTTY ? !1 : !e.some((t) => t === "-p" || t === "--print");
1067
1278
  }
1068
- async function Wt(e, t = {}) {
1069
- const o = $(process.cwd(), e ?? "aiui-demo");
1070
- switch (Yt(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)) {
1071
1880
  case "occupied":
1072
- m(
1073
- `${o} already exists and isn't an aiui demo`,
1881
+ g(
1882
+ `${n} already exists and isn't an aiui demo`,
1074
1883
  "Pick an empty (or new) directory — the scaffold never overwrites existing content."
1075
1884
  ), process.exitCode = 1;
1076
1885
  return;
1077
1886
  case "existing-demo":
1078
- y(`existing demo found at ${o} — continuing where it left off`);
1887
+ y(`existing demo found at ${n} — continuing where it left off`);
1079
1888
  break;
1080
1889
  case "new": {
1081
- const n = Zt();
1082
- if (!n) {
1083
- 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;
1084
1893
  return;
1085
1894
  }
1086
- zt(n, o), console.log(`scaffolded the demo playground at ${o}`), await Xt(o);
1895
+ ro(o, n), console.log(`scaffolded the demo playground at ${n}`), await io(n);
1087
1896
  break;
1088
1897
  }
1089
1898
  }
1090
- if (!t.skipInstall && !g(c(o, "node_modules")))
1091
- if (!Q("npm"))
1899
+ if (!t.skipInstall && !v(f(n, "node_modules")))
1900
+ if (!ye("npm"))
1092
1901
  y("npm not found on PATH — run your package manager's install in the demo yourself");
1093
1902
  else {
1094
1903
  console.log("installing dependencies (one time)…");
1095
- const n = await w("npm", ["install", "--no-audit", "--no-fund"], {
1096
- cwd: o,
1904
+ const o = await k("npm", ["install", "--no-audit", "--no-fund"], {
1905
+ cwd: n,
1097
1906
  stdio: "inherit",
1098
1907
  reject: !1
1099
1908
  });
1100
- if (n.exitCode) {
1101
- 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;
1102
1911
  return;
1103
1912
  }
1104
1913
  }
1105
- Qt(o);
1914
+ ao(n);
1106
1915
  }
1107
- function Yt(e) {
1108
- var o;
1109
- if (!g(e))
1916
+ function no(e) {
1917
+ var n;
1918
+ if (!v(e))
1110
1919
  return "new";
1111
1920
  let t;
1112
1921
  try {
1113
- t = we(e);
1922
+ t = G(e);
1114
1923
  } catch {
1115
1924
  return "occupied";
1116
1925
  }
1117
1926
  if (t.length === 0)
1118
1927
  return "new";
1119
1928
  try {
1120
- if (((o = JSON.parse(D(c(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)
1121
1930
  return "existing-demo";
1122
1931
  } catch {
1123
1932
  }
1124
1933
  return "occupied";
1125
1934
  }
1126
- function Kt(e) {
1935
+ function oo(e) {
1127
1936
  return /^\d+\.\d+\.\d+$/.test(e) ? `^${e}` : "latest";
1128
1937
  }
1129
- function zt(e, t) {
1130
- T(t, { recursive: !0 }), We(e, t, { recursive: !0 }), g(c(t, "gitignore")) && Ye(c(t, "gitignore"), c(t, ".gitignore"));
1131
- const o = c(t, "package.json");
1132
- k(
1133
- o,
1134
- D(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(
1135
1944
  "__AIUI_VERSION_RANGE__",
1136
- Kt(M)
1945
+ oo(ee)
1137
1946
  )
1138
1947
  );
1139
1948
  }
1140
- async function Xt(e) {
1141
- !Q("git") || (await w("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"], {
1142
1951
  reject: !1
1143
- })).exitCode === 0 || (await w("git", ["-C", e, "init", "--quiet"], { reject: !1 })).exitCode !== 0 || (await w("git", ["-C", e, "add", "-A"], { reject: !1 }), await w("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"], {
1144
1953
  reject: !1
1145
1954
  }));
1146
1955
  }
1147
- function Zt() {
1148
- let e = U(rt(import.meta.url));
1956
+ function so() {
1957
+ let e = R(Ge(import.meta.url));
1149
1958
  for (let t = 0; t < 4; t++) {
1150
- const o = c(e, "templates", "demo");
1151
- if (g(c(o, "package.json")))
1152
- return o;
1153
- e = U(e);
1959
+ const n = f(e, "templates", "demo");
1960
+ if (v(f(n, "package.json")))
1961
+ return n;
1962
+ e = R(e);
1154
1963
  }
1155
1964
  }
1156
- function Qt(e) {
1157
- const t = Be(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}`;
1158
1967
  console.log(`
1159
1968
  demo ready. Run the loop:
1160
1969
 
@@ -1167,104 +1976,90 @@ then open the app in the session browser (the window you share with the agent):
1167
1976
  npx aiui open http://localhost:5173
1168
1977
 
1169
1978
  Click the ✳ aiui button on the page and type an intent — it lands in the session
1170
- as a prompt. Re-run \`${o}\` anytime to continue this sandbox.`);
1979
+ as a prompt. Re-run \`${n}\` anytime to continue this sandbox.`);
1171
1980
  }
1172
- const eo = "@habemus-papadum/aiui-claude-channel";
1173
- async function to(e = []) {
1981
+ const co = "@habemus-papadum/aiui-claude-channel";
1982
+ async function lo(e = []) {
1174
1983
  let t;
1175
1984
  try {
1176
- t = z(eo);
1985
+ t = ge(co);
1177
1986
  } catch {
1178
- m(
1987
+ g(
1179
1988
  "The aiui Claude channel CLI is not available",
1180
1989
  "`@habemus-papadum/aiui-claude-channel` should be installed as a dependency of aiui — try reinstalling."
1181
1990
  ), process.exitCode = 1;
1182
1991
  return;
1183
1992
  }
1184
- const o = await w(t.command, [...t.args, ...e], {
1993
+ const n = await k(t.command, [...t.args, ...e], {
1185
1994
  stdio: "inherit",
1186
1995
  reject: !1
1187
1996
  });
1188
- o.exitCode && (process.exitCode = o.exitCode);
1997
+ n.exitCode && (process.exitCode = n.exitCode);
1189
1998
  }
1190
- const oo = "vite", B = "VITE_AIUI_PORT";
1191
- function ro(e, t) {
1192
- if (t !== void 0) {
1193
- const o = e.find((n) => n.tag === t);
1194
- if (!o) {
1195
- const n = e.length > 0 ? e.map((r) => r.tag).join(", ") : "(none running)";
1196
- return {
1197
- error: `no running aiui channel with tag "${t}" — running tags: ${n}`
1198
- };
1199
- }
1200
- return { server: o };
1201
- }
1202
- return e.length > 0 ? { select: e } : {};
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;
1203
2005
  }
1204
- async function no(e = []) {
1205
- const { mcp: t, tag: o, passthrough: n } = je(e), r = _e(n);
1206
- if (r) {
1207
- r === "help" ? ao() : console.log(`aiui ${M}`), await io(n);
2006
+ async function Be(e, t) {
2007
+ try {
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;
2012
+ } catch {
1208
2013
  return;
1209
2014
  }
1210
- const i = t ?? o, a = ro(Ze(), i);
1211
- if (a.error) {
1212
- m("Could not resolve an aiui channel", a.error), process.exitCode = 1;
2015
+ }
2016
+ async function ho(e) {
2017
+ const t = await Be(
2018
+ e,
2019
+ "/paint/info"
2020
+ );
2021
+ if (!(t != null && t.ok))
1213
2022
  return;
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
+ };
2030
+ }
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 });
1214
2036
  }
1215
- const s = a.select ? await Qe(a.select) : a.server;
1216
- let l;
1217
- s ? (l = String(s.port), console.error(
1218
- b.dim(
1219
- `aiui: connecting vite to channel "${s.tag}" (${s.cwd}) on port ${l} via ${B}`
1220
- )
1221
- )) : console.error(b.dim(`aiui: no running channel found — ${B} left unset`));
1222
- const u = Le();
1223
- if (!u)
2037
+ if (e.json) {
2038
+ console.log(JSON.stringify({ targets: n }, null, 2));
1224
2039
  return;
1225
- const f = await w(u.command, [...u.args, ...n], {
1226
- stdio: "inherit",
1227
- reject: !1,
1228
- ...l ? { env: { [B]: l } } : {}
1229
- });
1230
- f.exitCode && (process.exitCode = f.exitCode);
1231
- }
1232
- function Le() {
1233
- try {
1234
- return z(oo);
1235
- } catch {
1236
- m(
1237
- "Vite is not available",
1238
- "`vite` should be installed as a dependency of aiui — try reinstalling."
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.`
1239
2046
  ), process.exitCode = 1;
1240
2047
  return;
1241
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("");
1242
2059
  }
1243
- async function io(e) {
1244
- const t = Le();
1245
- if (!t)
1246
- return;
1247
- const o = await w(t.command, [...t.args, ...e], {
1248
- stdio: "inherit",
1249
- reject: !1
1250
- });
1251
- o.exitCode && (process.exitCode = o.exitCode);
1252
- }
1253
- function ao() {
1254
- console.log(`aiui vite — launch Vite connected to the running aiui channel
1255
-
1256
- aiui's own flags (everything else forwards to vite verbatim):
1257
- --aiui-mcp <tag> connect to the channel server with this tag
1258
- --aiui-tag <tag> accepted alias for --aiui-mcp
1259
-
1260
- The chosen channel's port is exported as VITE_AIUI_PORT; the aiuiDevOverlay()
1261
- Vite plugin picks it up there and wires the intent tool to it. What follows is
1262
- vite's own --help:
1263
- `);
1264
- }
1265
- function so() {
1266
- const e = new He();
1267
- return e.name("aiui").description("ai ui frontends — thin launchers for Claude, Vite, and the channel CLI").version(M).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) => Vt(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) => no(t)), e.command("chrome").description("manage the agent's browser: install | update | status | extension").argument("<action>", "install | update | status | extension").action((t) => St([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(
1268
2063
  "start (or find) the shared session browser; --tunnel does the whole remote-dev local half"
1269
2064
  ).option(
1270
2065
  "--profile <name>",
@@ -1272,9 +2067,13 @@ function so() {
1272
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(
1273
2068
  "--tunnel <[user@]host>",
1274
2069
  "reverse-tunnel the debug port to this host (Ctrl-C closes it)"
1275
- ).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) => Wt(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) => It(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) => to(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;
1276
2075
  }
1277
- so().parseAsync().catch((e) => {
2076
+ po().parseAsync().catch((e) => {
1278
2077
  console.error(e instanceof Error ? e.message : e), process.exitCode = 1;
1279
2078
  });
1280
2079
  //# sourceMappingURL=cli.js.map