@habemus-papadum/aiui 0.2.0 → 0.5.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/README.md +6 -1
- package/dist/cli.js +1481 -832
- package/dist/cli.js.map +1 -1
- package/dist/commands/browser.d.ts +50 -0
- package/dist/commands/clean.d.ts +49 -0
- package/dist/commands/config-tui.d.ts +1 -0
- package/dist/commands/config.d.ts +43 -0
- package/dist/commands/debug.d.ts +7 -0
- package/dist/commands/env.d.ts +36 -0
- package/dist/commands/extension.d.ts +46 -0
- package/dist/commands/mcp.d.ts +10 -0
- package/dist/commands/native-host.d.ts +16 -0
- package/dist/commands/pencil-url.d.ts +18 -0
- package/dist/commands/vite.d.ts +54 -28
- package/dist/config-Cis6kCik.js +293 -0
- package/dist/config-Cis6kCik.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/util/aiui-args.d.ts +26 -0
- package/dist/util/channel-launch.d.ts +67 -0
- package/dist/util/channel-target.d.ts +33 -0
- package/dist/util/chrome.d.ts +48 -27
- package/dist/util/config-schema.d.ts +96 -0
- package/dist/util/config.d.ts +31 -59
- package/dist/util/first-run.d.ts +11 -8
- package/dist/util/gemini-preflight.d.ts +56 -0
- package/dist/util/openai-preflight.d.ts +4 -3
- package/dist/util/resolve-cli.d.ts +1 -11
- package/package.json +12 -8
- package/dist/commands/demo.d.ts +0 -28
- package/dist/util/browser.d.ts +0 -44
- package/templates/demo/CLAUDE.md +0 -16
- package/templates/demo/README.md +0 -36
- package/templates/demo/gitignore +0 -4
- package/templates/demo/index.html +0 -11
- package/templates/demo/package.json +0 -21
- package/templates/demo/src/main.ts +0 -51
- package/templates/demo/tsconfig.json +0 -11
- package/templates/demo/vite.config.ts +0 -17
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { readFileSync as C, mkdirSync as T, writeFileSync as x } from "node:fs";
|
|
2
|
+
import { join as p, dirname as S } from "node:path";
|
|
3
|
+
import { projectCacheDir as E } from "@habemus-papadum/aiui-claude-channel";
|
|
4
|
+
import { CHROME_CHANNELS as N, cacheDir as $ } from "@habemus-papadum/aiui-util";
|
|
5
|
+
const O = ["prompt", "auto", "off"], P = ["attach", "launch"], D = ["loopback", "host"], u = [
|
|
6
|
+
{
|
|
7
|
+
name: "claude",
|
|
8
|
+
summary: "how `aiui claude` launches Claude Code",
|
|
9
|
+
fields: [
|
|
10
|
+
{
|
|
11
|
+
key: "skipPermissions",
|
|
12
|
+
type: "boolean",
|
|
13
|
+
default: !0,
|
|
14
|
+
defaultText: "true (unset: the first interactive launch asks, then persists the answer)",
|
|
15
|
+
summary: "Launch Claude Code with --dangerously-skip-permissions.",
|
|
16
|
+
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."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
key: "enterNudge",
|
|
20
|
+
type: "boolean",
|
|
21
|
+
default: !0,
|
|
22
|
+
defaultText: "true (unset: the first interactive launch asks, then persists the answer)",
|
|
23
|
+
summary: "Auto-dismiss Claude Code's development-channel acknowledgement prompt.",
|
|
24
|
+
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."
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "channel",
|
|
30
|
+
summary: "the channel server's web backend",
|
|
31
|
+
fields: [
|
|
32
|
+
{
|
|
33
|
+
key: "bind",
|
|
34
|
+
type: "enum",
|
|
35
|
+
values: D,
|
|
36
|
+
default: "loopback",
|
|
37
|
+
defaultText: '"loopback" (unset: the first interactive launch asks, then persists the answer)',
|
|
38
|
+
summary: "Which interface the channel web server binds: loopback, or host (LAN).",
|
|
39
|
+
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.'
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "chrome",
|
|
45
|
+
summary: "the agent's browser and the Chrome DevTools MCP",
|
|
46
|
+
fields: [
|
|
47
|
+
{
|
|
48
|
+
key: "enabled",
|
|
49
|
+
type: "boolean",
|
|
50
|
+
default: !0,
|
|
51
|
+
summary: "Attach the Chrome DevTools MCP.",
|
|
52
|
+
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."
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: "mode",
|
|
56
|
+
type: "enum",
|
|
57
|
+
values: P,
|
|
58
|
+
default: "attach",
|
|
59
|
+
summary: "How the MCP reaches a browser: shared session browser, or its own.",
|
|
60
|
+
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.`
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
key: "browserUrl",
|
|
64
|
+
type: "string",
|
|
65
|
+
defaultText: "unset (manage a browser locally)",
|
|
66
|
+
summary: "Attach to this DevTools endpoint instead of managing a browser at all.",
|
|
67
|
+
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.',
|
|
68
|
+
validate: (e) => j(String(e)) ? void 0 : 'expected an http(s) URL like "http://127.0.0.1:9222"'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: "debugPort",
|
|
72
|
+
type: "number",
|
|
73
|
+
default: 0,
|
|
74
|
+
defaultText: "0 (an OS-assigned free port)",
|
|
75
|
+
summary: "Fixed DevTools debug port for session browsers aiui launches.",
|
|
76
|
+
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.",
|
|
77
|
+
validate: (e) => Number.isInteger(e) && e >= 0 && e <= 65535 ? void 0 : "expected 0..65535"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
key: "profile",
|
|
81
|
+
type: "string",
|
|
82
|
+
default: "default",
|
|
83
|
+
summary: "Named profile under .aiui-cache/chrome/.",
|
|
84
|
+
doc: "Per-launch flag: --aiui-chrome-profile."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
key: "dataDir",
|
|
88
|
+
type: "string",
|
|
89
|
+
defaultText: "unset (derived from chrome.profile)",
|
|
90
|
+
summary: "Explicit Chrome user data dir; takes precedence over chrome.profile.",
|
|
91
|
+
doc: "Per-launch flag: --aiui-chrome-data-dir."
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "executablePath",
|
|
95
|
+
type: "string",
|
|
96
|
+
defaultText: "unset (managed Chrome for Testing when installed, else installed Chrome)",
|
|
97
|
+
summary: "Chrome binary to launch — e.g. a Chrome for Testing install.",
|
|
98
|
+
doc: "Chrome for Testing still honors --load-extension, so the aiui DevTools panel can auto-load. Mutually exclusive with chrome.channel."
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
key: "channel",
|
|
102
|
+
type: "enum",
|
|
103
|
+
values: N,
|
|
104
|
+
defaultText: 'unset ("stable" when launching an installed Chrome)',
|
|
105
|
+
summary: "Installed Chrome release channel to launch.",
|
|
106
|
+
doc: "Mutually exclusive with chrome.executablePath."
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: "forTesting",
|
|
110
|
+
type: "enum",
|
|
111
|
+
values: O,
|
|
112
|
+
default: "prompt",
|
|
113
|
+
summary: "How `aiui claude` manages the recommended Chrome for Testing install.",
|
|
114
|
+
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.'
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
key: "headless",
|
|
118
|
+
type: "boolean",
|
|
119
|
+
default: !1,
|
|
120
|
+
summary: "Launch Chrome with no UI."
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
key: "buildExtension",
|
|
124
|
+
type: "boolean",
|
|
125
|
+
default: !0,
|
|
126
|
+
summary: "OBSOLETE — parsed and ignored (the DevTools extension is deleted).",
|
|
127
|
+
doc: "The only extension a launch auto-loads is the intent client, whose MV3 bundle is built deliberately (`pnpm -C packages/aiui-intent-client build:ext`). Kept in the schema so old configs stay valid."
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
key: "autoCapture",
|
|
131
|
+
type: "boolean",
|
|
132
|
+
default: !0,
|
|
133
|
+
summary: "OBSOLETE — parsed and ignored (page-side getDisplayMedia capture is gone).",
|
|
134
|
+
doc: "The intent client's hosts capture natively (CDP screenshots / the extension's tabCapture stream); nothing reads the page-side capture marker any more. Kept in the schema so old configs stay valid."
|
|
135
|
+
}
|
|
136
|
+
]
|
|
137
|
+
}
|
|
138
|
+
];
|
|
139
|
+
function J() {
|
|
140
|
+
return u.flatMap(
|
|
141
|
+
(e) => e.fields.map((t) => ({ section: e, field: t, path: `${e.name}.${t.key}` }))
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
function w(e) {
|
|
145
|
+
return e.type === "enum" ? "string" : e.type;
|
|
146
|
+
}
|
|
147
|
+
function b(e, t) {
|
|
148
|
+
var n;
|
|
149
|
+
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);
|
|
150
|
+
}
|
|
151
|
+
function V(e, t) {
|
|
152
|
+
let n;
|
|
153
|
+
switch (w(e)) {
|
|
154
|
+
case "boolean": {
|
|
155
|
+
if (t !== "true" && t !== "false")
|
|
156
|
+
return { error: "expected true or false" };
|
|
157
|
+
n = t === "true";
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case "number": {
|
|
161
|
+
if (n = Number(t), t.trim() === "" || Number.isNaN(n))
|
|
162
|
+
return { error: "expected a number" };
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
default:
|
|
166
|
+
n = t;
|
|
167
|
+
}
|
|
168
|
+
const a = b(e, n);
|
|
169
|
+
return a ? { error: a } : { value: n };
|
|
170
|
+
}
|
|
171
|
+
function k(e) {
|
|
172
|
+
return typeof e == "string" ? JSON.stringify(e) : String(e);
|
|
173
|
+
}
|
|
174
|
+
function B(e) {
|
|
175
|
+
return e.defaultText ? e.defaultText : e.default === void 0 ? "unset" : k(e.default);
|
|
176
|
+
}
|
|
177
|
+
function j(e) {
|
|
178
|
+
try {
|
|
179
|
+
const t = new URL(e);
|
|
180
|
+
return t.protocol === "http:" || t.protocol === "https:";
|
|
181
|
+
} catch {
|
|
182
|
+
return !1;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const y = "config.json", A = /* @__PURE__ */ new Set(["sidecars"]);
|
|
186
|
+
function v(e = process.cwd()) {
|
|
187
|
+
return {
|
|
188
|
+
user: p($(void 0, { create: !1 }), y),
|
|
189
|
+
project: p(E(e), y)
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function G(e = process.cwd()) {
|
|
193
|
+
const t = v(e);
|
|
194
|
+
return I(l(t.user) ?? {}, l(t.project) ?? {});
|
|
195
|
+
}
|
|
196
|
+
function I(e, t) {
|
|
197
|
+
const n = {};
|
|
198
|
+
for (const a of u)
|
|
199
|
+
n[a.name] = {
|
|
200
|
+
...e[a.name],
|
|
201
|
+
...t[a.name]
|
|
202
|
+
};
|
|
203
|
+
return n;
|
|
204
|
+
}
|
|
205
|
+
function l(e) {
|
|
206
|
+
let t;
|
|
207
|
+
try {
|
|
208
|
+
t = C(e, "utf8");
|
|
209
|
+
} catch {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
let n;
|
|
213
|
+
try {
|
|
214
|
+
n = JSON.parse(t);
|
|
215
|
+
} catch (a) {
|
|
216
|
+
throw new Error(
|
|
217
|
+
`invalid JSON in ${e}: ${a instanceof Error ? a.message : String(a)}`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return M(n, e);
|
|
221
|
+
}
|
|
222
|
+
function M(e, t) {
|
|
223
|
+
const n = g(e, t, "the top level"), a = u.map((r) => r.name);
|
|
224
|
+
for (const r of Object.keys(n))
|
|
225
|
+
if (!(a.includes(r) || A.has(r)))
|
|
226
|
+
throw new Error(
|
|
227
|
+
`unknown key "${r}" at the top level of ${t} — known keys: ${a.join(", ")}`
|
|
228
|
+
);
|
|
229
|
+
const s = {};
|
|
230
|
+
for (const r of u) {
|
|
231
|
+
if (n[r.name] === void 0)
|
|
232
|
+
continue;
|
|
233
|
+
const c = g(n[r.name], t, `"${r.name}"`);
|
|
234
|
+
L(
|
|
235
|
+
c,
|
|
236
|
+
r.fields.map((o) => o.key),
|
|
237
|
+
t,
|
|
238
|
+
`"${r.name}"`
|
|
239
|
+
);
|
|
240
|
+
const h = {};
|
|
241
|
+
for (const o of r.fields) {
|
|
242
|
+
const i = c[o.key];
|
|
243
|
+
if (i === void 0)
|
|
244
|
+
continue;
|
|
245
|
+
const d = `${r.name}.${o.key}`, m = w(o);
|
|
246
|
+
if (typeof i !== m)
|
|
247
|
+
throw new Error(`expected a ${m} for ${d} in ${t}`);
|
|
248
|
+
const f = b(o, i);
|
|
249
|
+
if (f)
|
|
250
|
+
throw new Error(
|
|
251
|
+
`invalid ${d} ${k(i)} in ${t} — ${f}`
|
|
252
|
+
);
|
|
253
|
+
h[o.key] = i;
|
|
254
|
+
}
|
|
255
|
+
s[r.name] = h;
|
|
256
|
+
}
|
|
257
|
+
return s;
|
|
258
|
+
}
|
|
259
|
+
function K(e) {
|
|
260
|
+
return F(v().user, e);
|
|
261
|
+
}
|
|
262
|
+
function F(e, t) {
|
|
263
|
+
const n = l(e) ?? {};
|
|
264
|
+
return t(n), T(S(e), { recursive: !0 }), x(e, `${JSON.stringify(n, null, 2)}
|
|
265
|
+
`), e;
|
|
266
|
+
}
|
|
267
|
+
function g(e, t, n) {
|
|
268
|
+
if (typeof e != "object" || e === null || Array.isArray(e))
|
|
269
|
+
throw new Error(`expected an object at ${n} of ${t}`);
|
|
270
|
+
return e;
|
|
271
|
+
}
|
|
272
|
+
function L(e, t, n, a) {
|
|
273
|
+
for (const s of Object.keys(e))
|
|
274
|
+
if (!t.includes(s))
|
|
275
|
+
throw new Error(
|
|
276
|
+
`unknown key "${s}" at ${a} of ${n} — known keys: ${t.join(", ")}`
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
export {
|
|
280
|
+
D as C,
|
|
281
|
+
J as a,
|
|
282
|
+
F as b,
|
|
283
|
+
v as c,
|
|
284
|
+
B as d,
|
|
285
|
+
u as e,
|
|
286
|
+
k as f,
|
|
287
|
+
G as l,
|
|
288
|
+
I as m,
|
|
289
|
+
V as p,
|
|
290
|
+
l as r,
|
|
291
|
+
K as u
|
|
292
|
+
};
|
|
293
|
+
//# sourceMappingURL=config-Cis6kCik.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-Cis6kCik.js","sources":["../src/util/config-schema.ts","../src/util/config.ts"],"sourcesContent":["/**\n * The declarative schema for aiui's `config.json` — one table that drives\n * everything config-shaped:\n *\n * - validation (util/config.ts walks these sections instead of hand-rolled\n * per-key checks),\n * - the `aiui config` commands (show/get/set/unset use it to resolve keys,\n * parse CLI values, and report defaults),\n * - the `aiui config tui` browser (docs, defaults, and enum choices all\n * render from here).\n *\n * Because docs, defaults, and validation come from the same rows, they cannot\n * drift apart. `docs/guide/config.md` remains the long-form narrative; the\n * `doc` strings here are the terminal-sized versions.\n *\n * **How a component participates.** `CONFIG_SECTIONS` is the registry: a\n * section is plain data, so a future component that grows file-backed settings\n * (say, an `intent` section for the pipeline) joins by contributing one\n * `ConfigSectionSchema` object here and a matching optional section on\n * `AiuiConfig`. Nothing else — validation, show/get/set, and the TUI pick it\n * up from the table. (The intent pipeline's current config is deliberately\n * *not* here: it is per-app, passed as `aiuiDevOverlay({ intent: { … } })` in\n * the app's Vite config — see docs/guide/intent-overlay.md.)\n */\n\n// The channel list lives with the launch code (aiui-util's browser module,\n// whose RELEASE_CHANNELS map must stay exhaustive over it); re-exported here\n// so the config table keeps being the one import surface for config shapes.\nimport { CHROME_CHANNELS } from \"@habemus-papadum/aiui-util\";\n\nexport { CHROME_CHANNELS, type ChromeChannel } from \"@habemus-papadum/aiui-util\";\n\nexport const FOR_TESTING_MODES = [\"prompt\", \"auto\", \"off\"] as const;\nexport type ForTestingMode = (typeof FOR_TESTING_MODES)[number];\n\nexport const CHROME_MODES = [\"attach\", \"launch\"] as const;\nexport type ChromeMode = (typeof CHROME_MODES)[number];\n\nexport const CHANNEL_BINDS = [\"loopback\", \"host\"] as const;\nexport type ChannelBind = (typeof CHANNEL_BINDS)[number];\n\n/** Every config leaf is a JSON scalar; sections never nest further. */\nexport type ConfigValue = boolean | number | string;\n\nexport interface ConfigFieldSchema {\n /** Leaf key within its section, e.g. `\"skipPermissions\"`. */\n key: string;\n /** `\"enum\"` is a string constrained to {@link values}. */\n type: \"boolean\" | \"number\" | \"string\" | \"enum\";\n /** The allowed values when {@link type} is `\"enum\"`. */\n values?: readonly string[];\n /** The built-in default, when the fallback is a plain value. */\n default?: ConfigValue;\n /**\n * Human phrasing of the default when a bare value would mislead (dynamic\n * fallbacks, first-run prompts). Shown instead of {@link default}.\n */\n defaultText?: string;\n /** One-line summary — the TUI's list row. */\n summary: string;\n /** The rest of the documentation (terminal-sized; optional). */\n doc?: string;\n /**\n * Constraint beyond type/enum. Returns the \"expected …\" tail of the error\n * message, or undefined when the value is fine.\n */\n validate?: (value: ConfigValue) => string | undefined;\n}\n\nexport interface ConfigSectionSchema {\n /** Top-level key in config.json, e.g. `\"chrome\"`. */\n name: string;\n /** One-line summary of what the section configures. */\n summary: string;\n fields: ConfigFieldSchema[];\n}\n\nexport const CONFIG_SECTIONS: ConfigSectionSchema[] = [\n {\n name: \"claude\",\n summary: \"how `aiui claude` launches Claude Code\",\n fields: [\n {\n key: \"skipPermissions\",\n type: \"boolean\",\n default: true,\n defaultText: \"true (unset: the first interactive launch asks, then persists the answer)\",\n summary: \"Launch Claude Code with --dangerously-skip-permissions.\",\n doc:\n \"A personal preference with real consequences (docs/guide/warning): every agent \" +\n \"action — shell commands, file writes, network, the browser — runs without asking \" +\n \"first. aiui works fine either way. The first interactive launch asks and persists \" +\n \"the answer at the user level; when unset, non-interactive sessions fall back to true.\",\n },\n {\n key: \"enterNudge\",\n type: \"boolean\",\n default: true,\n defaultText: \"true (unset: the first interactive launch asks, then persists the answer)\",\n summary: \"Auto-dismiss Claude Code's development-channel acknowledgement prompt.\",\n doc:\n \"aiui loads a custom development channel, so Claude Code shows a one-key \" +\n \"acknowledgement at every startup; this injects a single Enter keystroke into the \" +\n \"terminal to dismiss it (best-effort TIOCSTI on /dev/tty — platforms that forbid it \" +\n \"harmlessly do nothing). Saying no just means pressing Enter yourself each launch.\",\n },\n ],\n },\n {\n name: \"channel\",\n summary: \"the channel server's web backend\",\n fields: [\n {\n key: \"bind\",\n type: \"enum\",\n values: CHANNEL_BINDS,\n default: \"loopback\",\n defaultText:\n '\"loopback\" (unset: the first interactive launch asks, then persists the answer)',\n summary: \"Which interface the channel web server binds: loopback, or host (LAN).\",\n doc:\n '\"host\" (0.0.0.0) makes the session\\'s whole web surface — the iPad paint page, but ' +\n \"also prompt injection, /debug, and every sidecar — reachable by anyone on your \" +\n \"network, UNAUTHENTICATED. That is the trusted-LAN posture (docs/guide/warning): \" +\n 'right on a network that is yours alone, wrong on shared Wi-Fi. \"loopback\" keeps ' +\n \"everything this-machine-only; reaching the paint page from an iPad is then up to \" +\n \"you — tunnel the channel port however you like (Tailscale, `ssh -L`). The first \" +\n \"interactive launch asks and persists the answer at the user level. Per-launch \" +\n \"flag: --aiui-bind.\",\n },\n ],\n },\n {\n name: \"chrome\",\n summary: \"the agent's browser and the Chrome DevTools MCP\",\n fields: [\n {\n key: \"enabled\",\n type: \"boolean\",\n default: true,\n summary: \"Attach the Chrome DevTools MCP.\",\n doc:\n \"false turns it off everywhere; true restates the default and does NOT override the \" +\n \"CI default-off — only the --aiui-chrome flag forces it on under CI.\",\n },\n {\n key: \"mode\",\n type: \"enum\",\n values: CHROME_MODES,\n default: \"attach\",\n summary: \"How the MCP reaches a browser: shared session browser, or its own.\",\n doc:\n '\"attach\" shares a user-visible session browser: an already-running one is ' +\n 'discovered by profile, or an interactive launch starts one eagerly. \"launch\" is ' +\n \"the hands-off mode: chrome-devtools-mcp launches its own private browser lazily, on \" +\n \"the agent's first browser tool call.\",\n },\n {\n key: \"browserUrl\",\n type: \"string\",\n defaultText: \"unset (manage a browser locally)\",\n summary: \"Attach to this DevTools endpoint instead of managing a browser at all.\",\n doc:\n \"The remote-development key (docs/guide/remote): the browser runs on another machine \" +\n \"(started there with `aiui browser`) and its debug port is tunneled over. Setting it \" +\n 'implies mode: \"attach\" and makes every local-browser setting (profile, ' +\n \"executablePath, channel, forTesting…) irrelevant. Per-launch flag: --aiui-browser-url.\",\n validate: (value) =>\n isHttpUrl(String(value))\n ? undefined\n : 'expected an http(s) URL like \"http://127.0.0.1:9222\"',\n },\n {\n key: \"debugPort\",\n type: \"number\",\n default: 0,\n defaultText: \"0 (an OS-assigned free port)\",\n summary: \"Fixed DevTools debug port for session browsers aiui launches.\",\n doc:\n \"Pin it (e.g. 9222) when something external must find the port — an ssh tunnel, a \" +\n \"VS Code attach-to-Chrome launch config. 0 means an OS-assigned free port.\",\n validate: (value) =>\n Number.isInteger(value) && (value as number) >= 0 && (value as number) <= 65535\n ? undefined\n : \"expected 0..65535\",\n },\n {\n key: \"profile\",\n type: \"string\",\n default: \"default\",\n summary: \"Named profile under .aiui-cache/chrome/.\",\n doc: \"Per-launch flag: --aiui-chrome-profile.\",\n },\n {\n key: \"dataDir\",\n type: \"string\",\n defaultText: \"unset (derived from chrome.profile)\",\n summary: \"Explicit Chrome user data dir; takes precedence over chrome.profile.\",\n doc: \"Per-launch flag: --aiui-chrome-data-dir.\",\n },\n {\n key: \"executablePath\",\n type: \"string\",\n defaultText: \"unset (managed Chrome for Testing when installed, else installed Chrome)\",\n summary: \"Chrome binary to launch — e.g. a Chrome for Testing install.\",\n doc:\n \"Chrome for Testing still honors --load-extension, so the aiui DevTools panel can \" +\n \"auto-load. Mutually exclusive with chrome.channel.\",\n },\n {\n key: \"channel\",\n type: \"enum\",\n values: CHROME_CHANNELS,\n defaultText: 'unset (\"stable\" when launching an installed Chrome)',\n summary: \"Installed Chrome release channel to launch.\",\n doc: \"Mutually exclusive with chrome.executablePath.\",\n },\n {\n key: \"forTesting\",\n type: \"enum\",\n values: FOR_TESTING_MODES,\n default: \"prompt\",\n summary: \"How `aiui claude` manages the recommended Chrome for Testing install.\",\n doc:\n '\"prompt\" asks before installing or updating it — interactive sessions only, never ' +\n 'under CI; \"auto\" installs/updates without asking; \"off\" never checks. Prompt ' +\n 'answers (\"automatically\", \"never ask again\") persist here at the user level. ' +\n \"Skipped entirely when executablePath or channel picks a browser explicitly.\",\n },\n {\n key: \"headless\",\n type: \"boolean\",\n default: false,\n summary: \"Launch Chrome with no UI.\",\n },\n {\n key: \"buildExtension\",\n type: \"boolean\",\n default: true,\n summary: \"OBSOLETE — parsed and ignored (the DevTools extension is deleted).\",\n doc:\n \"The only extension a launch auto-loads is the intent client, whose MV3 bundle \" +\n \"is built deliberately (`pnpm -C packages/aiui-intent-client build:ext`). \" +\n \"Kept in the schema so old configs stay valid.\",\n },\n {\n key: \"autoCapture\",\n type: \"boolean\",\n default: true,\n summary: \"OBSOLETE — parsed and ignored (page-side getDisplayMedia capture is gone).\",\n doc:\n \"The intent client's hosts capture natively (CDP screenshots / the extension's \" +\n \"tabCapture stream); nothing reads the page-side capture marker any more. Kept in \" +\n \"the schema so old configs stay valid.\",\n },\n ],\n },\n];\n\n/** A field paired with its section — what key-resolution hands back. */\nexport interface ResolvedField {\n section: ConfigSectionSchema;\n field: ConfigFieldSchema;\n /** The dotted path, e.g. `\"chrome.mode\"`. */\n path: string;\n}\n\n/** Every field in schema order, with dotted paths. */\nexport function allConfigFields(): ResolvedField[] {\n return CONFIG_SECTIONS.flatMap((section) =>\n section.fields.map((field) => ({ section, field, path: `${section.name}.${field.key}` })),\n );\n}\n\n/** Resolve a dotted path like `\"chrome.mode\"`; undefined when unknown. */\nexport function findConfigField(path: string): ResolvedField | undefined {\n return allConfigFields().find((entry) => entry.path === path);\n}\n\n/** The `typeof` a field's values (enums are strings). */\nexport function fieldRuntimeType(field: ConfigFieldSchema): \"boolean\" | \"number\" | \"string\" {\n return field.type === \"enum\" ? \"string\" : field.type;\n}\n\n/**\n * Constraint check beyond the runtime type: enum membership, then the field's\n * own `validate`. Returns the \"expected …\" tail for the error, or undefined.\n */\nexport function invalidReason(field: ConfigFieldSchema, value: ConfigValue): string | undefined {\n if (field.type === \"enum\" && !(field.values ?? []).includes(String(value))) {\n return `expected one of: ${(field.values ?? []).join(\", \")}`;\n }\n return field.validate?.(value);\n}\n\n/**\n * Parse a CLI-provided string into the field's type and validate it — how\n * `aiui config set` and the TUI turn text into a config value.\n */\nexport function parseFieldValue(\n field: ConfigFieldSchema,\n raw: string,\n): { value: ConfigValue } | { error: string } {\n let value: ConfigValue;\n switch (fieldRuntimeType(field)) {\n case \"boolean\": {\n if (raw !== \"true\" && raw !== \"false\") {\n return { error: \"expected true or false\" };\n }\n value = raw === \"true\";\n break;\n }\n case \"number\": {\n value = Number(raw);\n if (raw.trim() === \"\" || Number.isNaN(value)) {\n return { error: \"expected a number\" };\n }\n break;\n }\n default:\n value = raw;\n }\n const reason = invalidReason(field, value);\n return reason ? { error: reason } : { value };\n}\n\n/** Render a value the way config.json would hold it (strings quoted). */\nexport function formatConfigValue(value: ConfigValue): string {\n return typeof value === \"string\" ? JSON.stringify(value) : String(value);\n}\n\n/** The default, phrased for humans: `defaultText` wins, then the value, then \"unset\". */\nexport function describeDefault(field: ConfigFieldSchema): string {\n if (field.defaultText) {\n return field.defaultText;\n }\n return field.default === undefined ? \"unset\" : formatConfigValue(field.default);\n}\n\nfunction isHttpUrl(value: string): boolean {\n try {\n const url = new URL(value);\n return url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n","/**\n * aiui's two-level configuration file.\n *\n * Settings live in `config.json` at up to two places, merged per-key with the\n * more specific one winning:\n *\n * <user cache>/config.json e.g. ~/.cache/aiui/config.json (respects\n * AIUI_CACHE / XDG_CACHE_HOME)\n * <project>/.aiui-cache/config.json next to the traces and Chrome profiles\n *\n * CLI flags override both. Everything is optional; a missing file is an empty\n * config. A *malformed* file is a hard error, not a warning — these settings\n * gate security-relevant behavior (`claude.skipPermissions`), and a typo that\n * silently reverts to the dangerous default is worse than a failed launch. The\n * same reasoning rejects unknown keys.\n *\n * What the keys are — types, enums, defaults, and documentation — lives in one\n * declarative table, `config-schema.ts`. Validation here walks that schema, and\n * the `aiui config` commands (show/get/set/tui) render from it, so the checks\n * and the docs cannot drift apart.\n */\nimport { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { projectCacheDir } from \"@habemus-papadum/aiui-claude-channel\";\nimport { cacheDir } from \"@habemus-papadum/aiui-util\";\nimport {\n type ChannelBind,\n type ChromeChannel,\n type ChromeMode,\n CONFIG_SECTIONS,\n type ConfigValue,\n type ForTestingMode,\n fieldRuntimeType,\n formatConfigValue,\n invalidReason,\n} from \"./config-schema\";\n\nexport {\n CHANNEL_BINDS,\n CHROME_CHANNELS,\n CHROME_MODES,\n type ChannelBind,\n type ChromeChannel,\n type ChromeMode,\n FOR_TESTING_MODES,\n type ForTestingMode,\n} from \"./config-schema\";\n\nexport const CONFIG_FILENAME = \"config.json\";\n\n/**\n * The typed shape of a config file. Must mirror `CONFIG_SECTIONS` in\n * config-schema.ts — the schema rows carry the full per-key documentation and\n * defaults; the comments here are just orientation.\n */\nexport interface AiuiConfig {\n claude?: {\n /** Launch Claude Code with `--dangerously-skip-permissions`. */\n skipPermissions?: boolean;\n /** Auto-dismiss Claude Code's development-channel prompt (util/enter-nudge.ts). */\n enterNudge?: boolean;\n };\n channel?: {\n /** Which interface the channel web server binds: loopback (default) or host (LAN). */\n bind?: ChannelBind;\n };\n chrome?: {\n /** Attach the Chrome DevTools MCP (default: true). */\n enabled?: boolean;\n /** How the MCP reaches a browser: shared session browser, or its own. */\n mode?: ChromeMode;\n /** Attach to this DevTools endpoint instead of managing a browser at all. */\n browserUrl?: string;\n /** Fixed DevTools debug port for session browsers aiui launches (0 = OS-assigned). */\n debugPort?: number;\n /** Named profile under `.aiui-cache/chrome/` (default: \"default\"). */\n profile?: string;\n /** Explicit Chrome user data dir; takes precedence over `profile`. */\n dataDir?: string;\n /** Chrome binary to launch. Mutually exclusive with `channel`. */\n executablePath?: string;\n /** Installed Chrome release channel to launch. */\n channel?: ChromeChannel;\n /** How `aiui claude` manages the Chrome for Testing install. */\n forTesting?: ForTestingMode;\n /** Launch Chrome headless (default: false). */\n headless?: boolean;\n /** OBSOLETE (the devtools extension is deleted): parsed and ignored so old\n * configs stay valid. */\n buildExtension?: boolean;\n /** OBSOLETE (page-side getDisplayMedia capture is gone): parsed and\n * ignored so old configs stay valid. */\n autoCapture?: boolean;\n };\n}\n\n/** The untyped view validation and merging work in: section → leaf values. */\ntype SectionValues = Record<string, ConfigValue>;\n\n/**\n * Top-level sections that USED to be valid and are now gone. A config file that\n * still carries one must not hard-fail — that would break every existing\n * config on upgrade — so it is accepted and ignored, distinct from a\n * genuinely-unknown key (a typo), which still throws. `sidecars.*` retired when\n * the channel began hosting its whole standard set unconditionally (paint,\n * intent, bar, pencil): there is nothing left to toggle, so the key is inert and\n * safe to delete.\n */\nconst DEPRECATED_SECTIONS = new Set([\"sidecars\"]);\n\n/** The `config.json` paths consulted, user-level first (base: the project dir). */\nexport function configPaths(base: string = process.cwd()): { user: string; project: string } {\n return {\n user: join(cacheDir(undefined, { create: false }), CONFIG_FILENAME),\n project: join(projectCacheDir(base), CONFIG_FILENAME),\n };\n}\n\n/** Load and merge the user- and project-level configs (project wins per key). */\nexport function loadAiuiConfig(base: string = process.cwd()): AiuiConfig {\n const paths = configPaths(base);\n return mergeAiuiConfig(readConfigFile(paths.user) ?? {}, readConfigFile(paths.project) ?? {});\n}\n\n/** Merge two configs section-by-section; `override`'s keys win within a section. */\nexport function mergeAiuiConfig(base: AiuiConfig, override: AiuiConfig): AiuiConfig {\n const merged: Record<string, SectionValues> = {};\n for (const section of CONFIG_SECTIONS) {\n merged[section.name] = {\n ...(base as Record<string, SectionValues | undefined>)[section.name],\n ...(override as Record<string, SectionValues | undefined>)[section.name],\n };\n }\n return merged as AiuiConfig;\n}\n\n/**\n * Read one config file. Missing → undefined; unreadable JSON or an unexpected\n * shape → an error naming the file, so the fix is obvious.\n */\nexport function readConfigFile(file: string): AiuiConfig | undefined {\n let text: string;\n try {\n text = readFileSync(file, \"utf8\");\n } catch {\n return undefined;\n }\n let raw: unknown;\n try {\n raw = JSON.parse(text);\n } catch (error) {\n throw new Error(\n `invalid JSON in ${file}: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n return validateConfig(raw, file);\n}\n\n/** Walk `CONFIG_SECTIONS`, rejecting unknown keys, wrong types, and bad values. */\nfunction validateConfig(raw: unknown, file: string): AiuiConfig {\n const root = asSection(raw, file, \"the top level\");\n const knownSections = CONFIG_SECTIONS.map((s) => s.name);\n for (const key of Object.keys(root)) {\n // A retired section is tolerated (accepted, then ignored below); a truly\n // unknown one is a typo and still throws — see DEPRECATED_SECTIONS.\n if (knownSections.includes(key) || DEPRECATED_SECTIONS.has(key)) {\n continue;\n }\n throw new Error(\n `unknown key \"${key}\" at the top level of ${file} — known keys: ${knownSections.join(\", \")}`,\n );\n }\n\n const config: Record<string, SectionValues> = {};\n for (const section of CONFIG_SECTIONS) {\n if (root[section.name] === undefined) {\n continue;\n }\n const values = asSection(root[section.name], file, `\"${section.name}\"`);\n rejectUnknownKeys(\n values,\n section.fields.map((f) => f.key),\n file,\n `\"${section.name}\"`,\n );\n // Absent keys stay absent (never `undefined`), or merging would let them\n // clobber a value from the other config level.\n const out: SectionValues = {};\n for (const field of section.fields) {\n const value = values[field.key];\n if (value === undefined) {\n continue;\n }\n const path = `${section.name}.${field.key}`;\n const type = fieldRuntimeType(field);\n if (typeof value !== type) {\n throw new Error(`expected a ${type} for ${path} in ${file}`);\n }\n const reason = invalidReason(field, value as ConfigValue);\n if (reason) {\n throw new Error(\n `invalid ${path} ${formatConfigValue(value as ConfigValue)} in ${file} — ${reason}`,\n );\n }\n out[field.key] = value as ConfigValue;\n }\n config[section.name] = out;\n }\n return config as AiuiConfig;\n}\n\n/**\n * Persist a change to the **user-level** config (creating the file if needed).\n *\n * This is how interactive prompt answers like \"never ask again\" become\n * durable: they are per-user decisions, so they land in the user cache — never\n * in the project file, which may be shared/committed by a team.\n */\nexport function updateUserConfig(mutate: (config: AiuiConfig) => void): string {\n return updateConfigFile(configPaths().user, mutate);\n}\n\n/**\n * Persist a change to a specific config file — the general form behind\n * {@link updateUserConfig}, and how `aiui config set --project` writes the\n * project level deliberately.\n */\nexport function updateConfigFile(file: string, mutate: (config: AiuiConfig) => void): string {\n const config = readConfigFile(file) ?? {};\n mutate(config);\n mkdirSync(dirname(file), { recursive: true });\n writeFileSync(file, `${JSON.stringify(config, null, 2)}\\n`);\n return file;\n}\n\nfunction asSection(value: unknown, file: string, where: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new Error(`expected an object at ${where} of ${file}`);\n }\n return value as Record<string, unknown>;\n}\n\nfunction rejectUnknownKeys(\n section: Record<string, unknown>,\n known: string[],\n file: string,\n where: string,\n): void {\n for (const key of Object.keys(section)) {\n if (!known.includes(key)) {\n throw new Error(\n `unknown key \"${key}\" at ${where} of ${file} — known keys: ${known.join(\", \")}`,\n );\n }\n }\n}\n"],"names":["FOR_TESTING_MODES","CHROME_MODES","CHANNEL_BINDS","CONFIG_SECTIONS","value","isHttpUrl","CHROME_CHANNELS","allConfigFields","section","field","fieldRuntimeType","invalidReason","_a","parseFieldValue","raw","reason","formatConfigValue","describeDefault","url","CONFIG_FILENAME","DEPRECATED_SECTIONS","configPaths","base","join","cacheDir","projectCacheDir","loadAiuiConfig","paths","mergeAiuiConfig","readConfigFile","override","merged","file","text","readFileSync","error","validateConfig","root","asSection","knownSections","s","key","config","values","rejectUnknownKeys","f","out","path","type","updateUserConfig","mutate","updateConfigFile","mkdirSync","dirname","writeFileSync","where","known"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ai ui frontends
|
|
3
3
|
*
|
|
4
|
+
* The `aiui` CLI's library surface. Launch *policy* the CLI applies lives here
|
|
5
|
+
* too, so sibling supervisors can reuse it instead of re-deriving it — e.g. the
|
|
6
|
+
* layered config loader below, so a debug-channel supervisor binds the channel
|
|
7
|
+
* the same way a real `aiui claude` launch would.
|
|
8
|
+
*
|
|
4
9
|
* @packageDocumentation
|
|
5
10
|
*/
|
|
11
|
+
export { type AiuiConfig, loadAiuiConfig } from "./util/config";
|
|
6
12
|
/** The published package name — handy for smoke tests. */
|
|
7
13
|
export declare const name = "@habemus-papadum/aiui";
|
|
8
14
|
/** Greet someone. Replace with your library's real API. */
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { l as r } from "./config-Cis6kCik.js";
|
|
2
|
+
const o = "@habemus-papadum/aiui";
|
|
3
|
+
function a(e) {
|
|
3
4
|
return `Hello, ${e}!`;
|
|
4
5
|
}
|
|
5
6
|
export {
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
a as greet,
|
|
8
|
+
r as loadAiuiConfig,
|
|
9
|
+
o as name
|
|
8
10
|
};
|
|
9
11
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * ai ui frontends\n *\n * @packageDocumentation\n */\n\n/** The published package name — handy for smoke tests. */\nexport const name = \"@habemus-papadum/aiui\";\n\n/** Greet someone. Replace with your library's real API. */\nexport function greet(who: string): string {\n return `Hello, ${who}!`;\n}\n"],"names":["name","greet","who"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * ai ui frontends\n *\n * The `aiui` CLI's library surface. Launch *policy* the CLI applies lives here\n * too, so sibling supervisors can reuse it instead of re-deriving it — e.g. the\n * layered config loader below, so a debug-channel supervisor binds the channel\n * the same way a real `aiui claude` launch would.\n *\n * @packageDocumentation\n */\n\n// The layered config loader (user config ← project config), for supervisors\n// that must obey the same settings a real `aiui claude` launch would — the\n// supervisors read `channel.bind` through this so a debug channel binds the\n// way the user configured the real one.\nexport { type AiuiConfig, loadAiuiConfig } from \"./util/config\";\n\n/** The published package name — handy for smoke tests. */\nexport const name = \"@habemus-papadum/aiui\";\n\n/** Greet someone. Replace with your library's real API. */\nexport function greet(who: string): string {\n return `Hello, ${who}!`;\n}\n"],"names":["name","greet","who"],"mappings":";AAkBO,MAAMA,IAAO;AAGb,SAASC,EAAMC,GAAqB;AACzC,SAAO,UAAUA,CAAG;AACtB;"}
|
package/dist/util/aiui-args.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* begin with `--aiui-` so they're unambiguously distinguishable from flags meant
|
|
7
7
|
* for the wrapped command (e.g. claude's `--resume`).
|
|
8
8
|
*/
|
|
9
|
+
import { type ChannelBind } from "./config-schema";
|
|
9
10
|
export interface AiuiArgs {
|
|
10
11
|
/** The `--aiui-tag <tag>` value, if provided (the channel/MCP session tag). */
|
|
11
12
|
tag?: string;
|
|
@@ -25,6 +26,18 @@ export interface AiuiArgs {
|
|
|
25
26
|
* Code's built-in browser integration and forwards via passthrough.)
|
|
26
27
|
*/
|
|
27
28
|
noChrome: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* `--aiui-browser` was passed: open the wrapped tool's page in the session
|
|
31
|
+
* browser even where it would default off (CI / headless environments —
|
|
32
|
+
* e.g. `aiui vite` on a machine whose port *is* getting forwarded to one
|
|
33
|
+
* with a display).
|
|
34
|
+
*/
|
|
35
|
+
browser: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* `--aiui-no-browser` was passed: skip all session-browser activity (no
|
|
38
|
+
* tab opened, no browser launched) for this run.
|
|
39
|
+
*/
|
|
40
|
+
noBrowser: boolean;
|
|
28
41
|
/**
|
|
29
42
|
* The `--aiui-chrome-profile <name>` value, if provided — which named Chrome
|
|
30
43
|
* profile (user data dir under `.aiui-cache/chrome/`) to launch with.
|
|
@@ -42,6 +55,13 @@ export interface AiuiArgs {
|
|
|
42
55
|
* `chrome.browserUrl` in config for this launch.
|
|
43
56
|
*/
|
|
44
57
|
browserUrl?: string;
|
|
58
|
+
/**
|
|
59
|
+
* The `--aiui-bind <loopback|host>` value, if provided — where the channel's
|
|
60
|
+
* web backend binds for this launch, overriding `channel.bind` in config.
|
|
61
|
+
* `host` is the trusted-LAN posture: the whole (unauthenticated) channel
|
|
62
|
+
* surface, iPad paint page included, becomes reachable from the network.
|
|
63
|
+
*/
|
|
64
|
+
bind?: ChannelBind;
|
|
45
65
|
/** Everything else, to forward verbatim to the wrapped tool. */
|
|
46
66
|
passthrough: string[];
|
|
47
67
|
}
|
|
@@ -65,12 +85,18 @@ export declare function infoFlag(passthrough: string[]): "help" | "version" | un
|
|
|
65
85
|
* MCP server to target (e.g. which session `aiui vite` should connect to).
|
|
66
86
|
* - `--aiui-chrome` / `--aiui-no-chrome` — force the Chrome DevTools MCP on
|
|
67
87
|
* (even under CI) / leave it off. Passing both is an error.
|
|
88
|
+
* - `--aiui-browser` / `--aiui-no-browser` — force opening the page in the
|
|
89
|
+
* session browser (even in CI/headless environments) / never open one.
|
|
90
|
+
* Passing both is an error.
|
|
68
91
|
* - `--aiui-chrome-profile <name>` — launch Chrome with the named profile
|
|
69
92
|
* (created on first use under `.aiui-cache/chrome/<name>`).
|
|
70
93
|
* - `--aiui-chrome-data-dir <path>` — launch Chrome with an explicit user data
|
|
71
94
|
* dir instead of a named profile. Mutually exclusive with the above.
|
|
72
95
|
* - `--aiui-browser-url <url>` — attach the Chrome DevTools MCP to this
|
|
73
96
|
* endpoint (e.g. a tunneled remote browser) instead of managing one.
|
|
97
|
+
* - `--aiui-bind <loopback|host>` — where the channel's web backend binds for
|
|
98
|
+
* this launch (see `channel.bind` in the config guide). Any other value is
|
|
99
|
+
* an error.
|
|
74
100
|
*
|
|
75
101
|
* Any other `--aiui-*` flag throws, so a typo surfaces loudly instead of being
|
|
76
102
|
* silently dropped or leaking into the child command.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a channel process gets configured, in one place.
|
|
3
|
+
*
|
|
4
|
+
* There are two ways the channel server comes up, and they must agree:
|
|
5
|
+
*
|
|
6
|
+
* - **`aiui claude`** builds an argv for the channel's `mcp` subcommand and
|
|
7
|
+
* hands it to Claude Code inside `--mcp-config`, which spawns it.
|
|
8
|
+
* - **`aiui mcp serve` / `aiui mcp mcp`** run the very same channel CLI
|
|
9
|
+
* directly, with no Claude Code anywhere in the loop.
|
|
10
|
+
*
|
|
11
|
+
* The one durable setting they must both derive from config is where the web
|
|
12
|
+
* backend binds (`channel.bind`) — the trusted-LAN posture. The second path
|
|
13
|
+
* used to forward its arguments verbatim and so never computed it; this module
|
|
14
|
+
* is what makes both paths resolve the same {@link ChannelLaunch} and emit the
|
|
15
|
+
* same `--bind` flag, so a standalone channel binds exactly like a session's.
|
|
16
|
+
*
|
|
17
|
+
* Which sidecars a channel hosts is NOT decided here anymore. The channel
|
|
18
|
+
* imports and mounts its own standard set (intent, bar, pencil — see the
|
|
19
|
+
* channel's standard-sidecars.ts), so there is nothing to pass: every channel
|
|
20
|
+
* hosts all four, and `channel.bind` alone decides whether a remote device can
|
|
21
|
+
* reach them.
|
|
22
|
+
*
|
|
23
|
+
* What else deliberately does NOT live here: `--launch-info` (a summary of *how
|
|
24
|
+
* a Claude Code session was assembled* — browser wiring, key preflight — which
|
|
25
|
+
* a direct launch has nothing to say about, and which `serve` does not even
|
|
26
|
+
* accept), and `--tag` / `--name` / `--port`, which are per-invocation identity
|
|
27
|
+
* rather than durable configuration.
|
|
28
|
+
*/
|
|
29
|
+
import type { AiuiConfig, ChannelBind } from "./config";
|
|
30
|
+
/** Everything a launcher knows that can influence the channel's configuration. */
|
|
31
|
+
export interface ChannelLaunchInput {
|
|
32
|
+
/** Merged user + project config (`util/config`). */
|
|
33
|
+
config: AiuiConfig;
|
|
34
|
+
/** An explicit per-launch bind (`--aiui-bind`, or `serve`'s own `--bind`). */
|
|
35
|
+
bind?: ChannelBind;
|
|
36
|
+
}
|
|
37
|
+
/** The resolved settings, ready to be rendered as channel CLI flags. */
|
|
38
|
+
export interface ChannelLaunch {
|
|
39
|
+
bind: ChannelBind;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the channel's bind for a launch. Follows the three-tier precedence
|
|
43
|
+
* the docs promise: per-launch flag, then durable config, then the built-in
|
|
44
|
+
* default (`loopback`).
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveChannelLaunch(input: ChannelLaunchInput): ChannelLaunch;
|
|
47
|
+
/**
|
|
48
|
+
* Render a {@link ChannelLaunch} as flags for the channel CLI. Both the `mcp`
|
|
49
|
+
* and `serve` subcommands accept `--bind` (see the channel's program.ts).
|
|
50
|
+
*/
|
|
51
|
+
export declare function channelLaunchFlags(launch: ChannelLaunch): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Whether `aiui mcp <args...>` will start a channel process, and so wants the
|
|
54
|
+
* config-derived settings. Callers use this to leave the subcommands that
|
|
55
|
+
* merely talk to a channel someone else is running (`quick`, `config`)
|
|
56
|
+
* untouched.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isChannelLaunch(args: string[]): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Apply the config-derived channel settings to a raw `aiui mcp <args...>`
|
|
61
|
+
* invocation.
|
|
62
|
+
*
|
|
63
|
+
* Returns `args` untouched unless its subcommand actually launches a channel.
|
|
64
|
+
* A setting the caller named explicitly is never overridden — `aiui mcp serve
|
|
65
|
+
* --bind host` means `host` no matter what config says.
|
|
66
|
+
*/
|
|
67
|
+
export declare function applyChannelLaunchArgs(args: string[], launch: ChannelLaunch): string[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* channel-target.ts — resolve which running channel a channel-CONNECTING
|
|
3
|
+
* command should talk to.
|
|
4
|
+
*
|
|
5
|
+
* Used by the commands that genuinely reach into a channel: `aiui debug` (the
|
|
6
|
+
* trace viewer, bound to one channel's port) and, in spirit, the intent
|
|
7
|
+
* client's standalone `pnpm dev` launcher. Deliberately NOT used by `aiui
|
|
8
|
+
* vite` any more: an app it serves reaches the channel through the intent
|
|
9
|
+
* client at `/intent/`, not a build-time port, so there is nothing to resolve.
|
|
10
|
+
* (This helper lived in `commands/vite.ts` while vite was that connection;
|
|
11
|
+
* it moved here when vite stopped connecting — owner, 2026-07-17.)
|
|
12
|
+
*/
|
|
13
|
+
import type { RunningServer } from "@habemus-papadum/aiui-claude-channel";
|
|
14
|
+
/** The channel a command should point at, or why it couldn't be resolved. */
|
|
15
|
+
export interface ChannelTarget {
|
|
16
|
+
/** The server resolved without prompting (by tag). */
|
|
17
|
+
server?: RunningServer;
|
|
18
|
+
/** Servers to offer in the interactive selector (no tag given, ≥1 running). */
|
|
19
|
+
select?: RunningServer[];
|
|
20
|
+
/** A human-readable reason a requested server couldn't be resolved. */
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Decide which running channel server a command should connect to.
|
|
25
|
+
*
|
|
26
|
+
* Pure so it can be unit-tested without spawning anything:
|
|
27
|
+
* - With a `targetTag`, return the server whose `tag` matches exactly; if none
|
|
28
|
+
* matches, return an `error` naming the tag and the tags that *are* running.
|
|
29
|
+
* - Without a `targetTag`, don't guess: return `{}` when nothing is running, or
|
|
30
|
+
* `{ select }` so the caller runs the same selector as `quick` (which
|
|
31
|
+
* auto-picks a lone server and prompts when there are several).
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveChannelTarget(servers: RunningServer[], targetTag: string | undefined): ChannelTarget;
|