@habemus-papadum/aiui 0.3.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +6 -1
  2. package/dist/cli.js +1121 -1149
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/browser.d.ts +6 -4
  5. package/dist/commands/clean.d.ts +5 -4
  6. package/dist/commands/debug.d.ts +1 -9
  7. package/dist/commands/env.d.ts +36 -0
  8. package/dist/commands/extension.d.ts +46 -0
  9. package/dist/commands/mcp.d.ts +10 -0
  10. package/dist/commands/native-host.d.ts +16 -0
  11. package/dist/commands/pencil-url.d.ts +18 -0
  12. package/dist/commands/vite.d.ts +17 -29
  13. package/dist/config-xLcoLiLT.js +315 -0
  14. package/dist/config-xLcoLiLT.js.map +1 -0
  15. package/dist/index.d.ts +4 -6
  16. package/dist/index.js +4 -4
  17. package/dist/index.js.map +1 -1
  18. package/dist/util/aiui-args.d.ts +0 -14
  19. package/dist/util/channel-launch.d.ts +67 -0
  20. package/dist/util/channel-target.d.ts +33 -0
  21. package/dist/util/chrome.d.ts +70 -27
  22. package/dist/util/config-schema.d.ts +23 -1
  23. package/dist/util/config.d.ts +21 -11
  24. package/dist/util/gemini-preflight.d.ts +56 -0
  25. package/dist/util/managed-browser.d.ts +109 -0
  26. package/dist/util/openai-preflight.d.ts +1 -1
  27. package/package.json +9 -9
  28. package/dist/commands/demo.d.ts +0 -28
  29. package/dist/commands/paint.d.ts +0 -3
  30. package/dist/sidecars-BKlhUA0V.js +0 -48
  31. package/dist/sidecars-BKlhUA0V.js.map +0 -1
  32. package/dist/util/cft.d.ts +0 -79
  33. package/dist/util/sidecars.d.ts +0 -62
  34. package/templates/demo/CLAUDE.md +0 -16
  35. package/templates/demo/README.md +0 -36
  36. package/templates/demo/gitignore +0 -4
  37. package/templates/demo/index.html +0 -11
  38. package/templates/demo/package.json +0 -23
  39. package/templates/demo/src/main.ts +0 -51
  40. package/templates/demo/tsconfig.json +0 -11
  41. package/templates/demo/vite.config.ts +0 -17
@@ -0,0 +1,109 @@
1
+ import { Browser } from "@puppeteer/browsers";
2
+ import { type ManagedFlavor, type ManageMode } from "./config";
3
+ /** Re-resolve the latest build id at most this often. */
4
+ export declare const CHECK_TTL_MS: number;
5
+ /** The per-flavor facts that differ between the two managed builds. */
6
+ export interface ManagedFlavorSpec {
7
+ flavor: ManagedFlavor;
8
+ /** The `@puppeteer/browsers` browser this flavor installs. */
9
+ browser: Browser;
10
+ /**
11
+ * The tag `resolveBuildId` accepts for "newest": Chrome for Testing tracks
12
+ * the "stable" release channel; Chromium only supports "latest" (a snapshot
13
+ * revision).
14
+ */
15
+ latestTag: string;
16
+ /** Human name for prompts and status. */
17
+ displayName: string;
18
+ /** Cache subdirectory under `~/.cache/aiui/` (also the on-disk browser name). */
19
+ cacheSubdir: string;
20
+ /** Rough download size, for the install prompt. */
21
+ approxSizeMb: number;
22
+ }
23
+ export declare const MANAGED_FLAVOR_SPECS: Record<ManagedFlavor, ManagedFlavorSpec>;
24
+ export declare function flavorSpec(flavor: ManagedFlavor): ManagedFlavorSpec;
25
+ /** One managed install. */
26
+ export interface ManagedInstall {
27
+ flavor: ManagedFlavor;
28
+ buildId: string;
29
+ executablePath: string;
30
+ }
31
+ /** Prompt/check bookkeeping, persisted in each flavor's cache dir. */
32
+ export interface ManagedState {
33
+ /** Epoch ms of the last successful latest-build lookup. */
34
+ checkedAt?: number;
35
+ /** The latest build id as of `checkedAt`. */
36
+ latestBuildId?: string;
37
+ /** Update prompt answered "skip" for this build id — don't re-ask for it. */
38
+ skippedBuildId?: string;
39
+ /** Epoch ms when an install offer was declined — snooze re-asking for a day. */
40
+ installDeclinedAt?: number;
41
+ }
42
+ /** Where a flavor's managed builds (and its state file) live. */
43
+ export declare function managedCacheDir(flavor: ManagedFlavor, create?: boolean): string;
44
+ /** Every flavor's cache dir — what `aiui clean` sweeps. */
45
+ export declare function allManagedCacheDirs(create?: boolean): string[];
46
+ export declare function readManagedState(flavor: ManagedFlavor): ManagedState;
47
+ export declare function writeManagedState(flavor: ManagedFlavor, patch: Partial<ManagedState>): void;
48
+ /**
49
+ * Numeric, segment-wise build id comparison. Handles both shapes: Chrome for
50
+ * Testing's dotted "138.0.7204.94" and Chromium's single-integer snapshot
51
+ * revision "1358901" (one segment → plain numeric compare).
52
+ */
53
+ export declare function compareBuildIds(a: string, b: string): number;
54
+ /** The newest managed install for a flavor, if any. Never touches the network. */
55
+ export declare function installedManaged(flavor: ManagedFlavor): Promise<ManagedInstall | undefined>;
56
+ /**
57
+ * The latest build id for a flavor, freshness-limited and offline-tolerant.
58
+ *
59
+ * Consults the network at most once per `maxAgeMs` (persisted in the state
60
+ * file) with a short timeout; on failure it falls back to the last known
61
+ * value, or undefined — callers treat undefined as "can't tell, don't nag".
62
+ */
63
+ export declare function latestManaged(flavor: ManagedFlavor, opts?: {
64
+ maxAgeMs?: number;
65
+ timeoutMs?: number;
66
+ now?: number;
67
+ }): Promise<string | undefined>;
68
+ /**
69
+ * Install the given build into the flavor's managed cache and drop superseded
70
+ * builds (they're ~150-160 MB each; the whole point of the managed dir is that
71
+ * there's exactly one, current, browser in it).
72
+ */
73
+ export declare function installManaged(flavor: ManagedFlavor, buildId: string): Promise<ManagedInstall>;
74
+ /**
75
+ * Bring a flavor's managed install to latest (the `aiui chrome install` /
76
+ * `update` implementation). Unlike the launch path this resolves with no
77
+ * timeout — an explicit command is allowed to wait on the network — and
78
+ * reports what it did.
79
+ */
80
+ export declare function ensureLatestManaged(flavor: ManagedFlavor, report: (line: string) => void): Promise<ManagedInstall & {
81
+ outcome: "current" | "installed" | "updated";
82
+ }>;
83
+ /**
84
+ * The launch-path managed-browser sync: decide which browser this session
85
+ * should use, and (interactively, when allowed) offer to install or update the
86
+ * managed build for `flavor`.
87
+ *
88
+ * Returns the executable path to prefer, or undefined to fall back to the
89
+ * system Chrome. Callers only invoke this when config names no browser
90
+ * explicitly (no `chrome.executablePath` / `chrome.channel`).
91
+ *
92
+ * The mode ladder (`chrome.manage`, default "prompt"):
93
+ * - "off" — never check, never prompt; an already-installed managed build is
94
+ * still used (install one deliberately with `aiui chrome install`).
95
+ * - "auto" — install/update to latest without asking.
96
+ * - "prompt" — offer to install when missing, offer to update when stale;
97
+ * answers can rewrite the mode in the user config.
98
+ *
99
+ * Nothing here ever blocks a non-interactive session: without a TTY (or under
100
+ * CI, or in print mode) this degrades to "use whatever is already installed".
101
+ * Downloads are likewise interactive-only — even "auto" won't pull ~150 MB
102
+ * into a headless one-shot.
103
+ */
104
+ export declare function syncManagedBrowser(opts: {
105
+ flavor: ManagedFlavor;
106
+ mode: ManageMode;
107
+ interactive: boolean;
108
+ now?: number;
109
+ }): Promise<string | undefined>;
@@ -14,7 +14,7 @@
14
14
  * blocks the launch; the modality still mounts, but transcription/correction are
15
15
  * unavailable until the key is set (the widget says so — `mock` is the explicit
16
16
  * offline choice, not a silent fallback). The most valuable case this catches is the one that
17
- * bit the bench twice (see workbench field-notes, "Keys & config"): a **stale
17
+ * bit the bench twice (archive/workbench/field-notes.md, "Keys & config"): a **stale
18
18
  * shell export** shadowing the real key, which surfaces as a confusing 401 deep
19
19
  * in the pipeline rather than a clear message up front.
20
20
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@habemus-papadum/aiui",
3
- "version": "0.3.0",
3
+ "version": "0.6.0",
4
4
  "description": "ai ui frontends",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,8 +25,7 @@
25
25
  "aiui": "./dist/cli.js"
26
26
  },
27
27
  "files": [
28
- "dist",
29
- "templates"
28
+ "dist"
30
29
  ],
31
30
  "publishConfig": {
32
31
  "access": "public"
@@ -38,12 +37,13 @@
38
37
  "commander": "^15.0.0",
39
38
  "execa": "^9.6.1",
40
39
  "vite": "^6.4.3",
41
- "@habemus-papadum/aiui-claude-plugin": "^0.3.0",
42
- "@habemus-papadum/aiui-devtools-extension": "^0.3.0",
43
- "@habemus-papadum/aiui-dev-overlay": "^0.3.0",
44
- "@habemus-papadum/aiui-paint": "^0.3.0",
45
- "@habemus-papadum/aiui-claude-channel": "^0.3.0",
46
- "@habemus-papadum/aiui-util": "^0.3.0"
40
+ "@habemus-papadum/aiui-claude-plugin": "^0.6.0",
41
+ "@habemus-papadum/aiui-claude-channel": "^0.6.0",
42
+ "@habemus-papadum/aiui-trace-ui": "^0.6.0",
43
+ "@habemus-papadum/aiui-util": "^0.6.0"
44
+ },
45
+ "devDependencies": {
46
+ "@habemus-papadum/aiui-lowering-pipeline": "^0.6.0"
47
47
  },
48
48
  "scripts": {
49
49
  "build": "tsc --emitDeclarationOnly -p tsconfig.json && vite build",
@@ -1,28 +0,0 @@
1
- export interface DemoOptions {
2
- /** Scaffold only — skip `npm install` (used by the packaging test/CI). */
3
- skipInstall?: boolean;
4
- }
5
- /** What `aiui demo` finds at the target path. */
6
- export type DemoTargetState = "new" | "existing-demo" | "occupied";
7
- export declare function runDemo(dir: string | undefined, opts?: DemoOptions): Promise<void>;
8
- /**
9
- * Decide what to do with the target: `new` (missing or empty), an
10
- * `existing-demo` (package.json carries the scaffold marker — continue), or
11
- * `occupied` (anything else — refuse; never clobber unknown content).
12
- */
13
- export declare function classifyDemoTarget(target: string): DemoTargetState;
14
- /**
15
- * The dependency range the scaffold pins aiui packages to: this build's exact
16
- * release line when it has one, `latest` from a dev build (whose `0.0.0+dev`
17
- * doesn't exist on the registry).
18
- */
19
- export declare function demoDependencyRange(version: string): string;
20
- /** Copy the template, restore dot-paths npm strips, and pin dependency ranges. */
21
- export declare function scaffoldDemo(template: string, target: string): void;
22
- /**
23
- * The template directory shipped with this install. Probed upward from this
24
- * module because the relative depth differs between layouts: `dist/cli.js`
25
- * (bundled, installed) sits one level below the package root; the tsx-run
26
- * `src/commands/demo.ts` sits two below.
27
- */
28
- export declare function templateRoot(): string | undefined;
@@ -1,3 +0,0 @@
1
- export declare function runPaintUrl(opts?: {
2
- json?: boolean;
3
- }): Promise<void>;
@@ -1,48 +0,0 @@
1
- import { createRequire as c } from "node:module";
2
- const l = c(import.meta.url), n = [
3
- {
4
- // The iPad paint stream. Always on: it rides the channel's own port (no
5
- // process, no extra listener), so hosting it costs nothing. Whether an
6
- // iPad can actually REACH it is the channel bind's decision (`channel.bind`
7
- // / `--aiui-bind`, asked at first run) — the security posture lives there,
8
- // not here. `--aiui-no-sidecar paint` / `sidecars.paint: false` turn it off.
9
- name: "paint",
10
- autoEnable: () => !0,
11
- descriptor: (a, o) => ({
12
- name: "paint",
13
- module: o("@habemus-papadum/aiui-paint/sidecar"),
14
- export: "paintSidecar",
15
- options: { root: a }
16
- })
17
- }
18
- ];
19
- function u(a, o, i = {}) {
20
- const d = i.resolveModule ?? ((e) => l.resolve(e)), s = i.log ?? ((e) => process.stderr.write(`[aiui] warning: ${e}
21
- `)), t = /* @__PURE__ */ new Set();
22
- for (const e of n)
23
- try {
24
- e.autoEnable(a) && t.add(e.name);
25
- } catch (r) {
26
- s(
27
- `sidecar "${e.name}" auto-detect failed: ${r instanceof Error ? r.message : String(r)}`
28
- );
29
- }
30
- for (const e of o.enable)
31
- n.some((r) => r.name === e) && t.add(e);
32
- for (const e of o.disable)
33
- t.delete(e);
34
- return n.filter((e) => t.has(e.name)).map((e) => {
35
- try {
36
- return e.descriptor(a, d);
37
- } catch (r) {
38
- s(
39
- `sidecar "${e.name}" disabled — its module failed to resolve: ${r instanceof Error ? r.message : String(r)}`
40
- );
41
- return;
42
- }
43
- }).filter((e) => e !== void 0);
44
- }
45
- export {
46
- u as r
47
- };
48
- //# sourceMappingURL=sidecars-BKlhUA0V.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sidecars-BKlhUA0V.js","sources":["../src/util/sidecars.ts"],"sourcesContent":["/**\n * Deciding which **session sidecars** the channel should host for a launch.\n *\n * A sidecar is an extra backend the channel mounts alongside the intent\n * pipeline — the concrete one today is the iPad paint stream. The channel takes\n * no dependency on any sidecar: `aiui claude` hands it a JSON array of\n * {@link SidecarDescriptor}s on `--sidecars`, and the channel dynamic-imports\n * each `module`, calls `mod[export ?? \"default\"](options)`, and mounts the\n * result (a bad descriptor is logged + skipped there).\n *\n * This module is the CLI's half: a small registry of the sidecars the launcher\n * knows how to construct, plus a pure resolver that decides which are on for a\n * given project root and set of `--aiui-sidecar` / `--aiui-no-sidecar` flags.\n */\n\nimport { createRequire } from \"node:module\";\n\nconst nodeRequire = createRequire(import.meta.url);\n\n/**\n * The contract the channel's `--sidecars` argument expects, mirrored here so\n * the CLI can emit it. The channel imports `module`, calls its `export` (or\n * `default`) with `options`, and mounts the returned sidecar.\n */\nexport interface SidecarDescriptor {\n /** Stable identifier, used for `--aiui-sidecar`/`--aiui-no-sidecar` and logs. */\n name: string;\n /** Importable specifier the channel `import()`s (e.g. a package subpath). */\n module: string;\n /** Named export to call as the factory. Defaults to `\"default\"`. */\n export?: string;\n /** Passed opaquely to the factory (e.g. `{ root: \"/proj\" }`). */\n options?: unknown;\n}\n\n/** Resolves a package specifier to an absolute path the channel can `import()`. */\ntype ResolveModule = (specifier: string) => string;\n\n/** Injectable seams, so the resolver is unit-testable without on-disk state. */\nexport interface ResolveSidecarsDeps {\n /**\n * Resolves a sidecar's package specifier to an ABSOLUTE path. Defaults to\n * `createRequire(import.meta.url).resolve`. This matters: the channel\n * dynamic-imports the descriptor's `module`, but it does NOT depend on any\n * sidecar package — so a bare specifier resolves from the channel's own\n * node_modules and fails (pnpm's isolated layout). Resolving here (from the\n * `aiui` CLI, which DOES depend on the sidecar package) to an absolute path\n * makes the import work in both the source-first workspace and an install.\n */\n resolveModule?: ResolveModule;\n /** Warning sink for a sidecar that had to be dropped (defaults to stderr). */\n log?: (message: string) => void;\n}\n\n/** A sidecar the CLI knows how to enable and construct, keyed by name. */\ninterface KnownSidecar {\n name: string;\n /** Whether this sidecar auto-enables for the given project root. */\n autoEnable: (root: string) => boolean;\n /** Build the descriptor the channel will mount for this root. */\n descriptor: (root: string, resolveModule: ResolveModule) => SidecarDescriptor;\n}\n\n/**\n * The registry of sidecars the launcher can enable. Emit order follows this\n * list, so the resolver's output is stable regardless of flag order. The paint\n * stream is always on — see its entry.\n */\nconst KNOWN_SIDECARS: KnownSidecar[] = [\n {\n // The iPad paint stream. Always on: it rides the channel's own port (no\n // process, no extra listener), so hosting it costs nothing. Whether an\n // iPad can actually REACH it is the channel bind's decision (`channel.bind`\n // / `--aiui-bind`, asked at first run) — the security posture lives there,\n // not here. `--aiui-no-sidecar paint` / `sidecars.paint: false` turn it off.\n name: \"paint\",\n autoEnable: () => true,\n descriptor: (root, resolveModule) => ({\n name: \"paint\",\n module: resolveModule(\"@habemus-papadum/aiui-paint/sidecar\"),\n export: \"paintSidecar\",\n options: { root },\n }),\n },\n];\n\n/**\n * Decide which session sidecars to host for `root`.\n *\n * Starts from the auto-detected set (each known sidecar whose `autoEnable`\n * predicate is truthy for this root), then applies the flags: `opts.enable`\n * force-adds a known sidecar by name even when it wouldn't auto-detect, and\n * `opts.disable` removes one. Disable wins over enable. Enable names the CLI\n * doesn't know how to construct are ignored (the launcher can only build the\n * sidecars in {@link KNOWN_SIDECARS}). Descriptors come back in the registry's\n * stable order.\n */\nexport function resolveSidecars(\n root: string,\n opts: { enable: string[]; disable: string[] },\n deps: ResolveSidecarsDeps = {},\n): SidecarDescriptor[] {\n const resolveModule =\n deps.resolveModule ?? ((specifier: string) => nodeRequire.resolve(specifier));\n const log =\n deps.log ?? ((message: string) => process.stderr.write(`[aiui] warning: ${message}\\n`));\n const enabled = new Set<string>();\n\n // Auto-detected sidecars. A throwing detector must not kill the launch —\n // warn and treat as not detected.\n for (const known of KNOWN_SIDECARS) {\n try {\n if (known.autoEnable(root)) {\n enabled.add(known.name);\n }\n } catch (err) {\n log(\n `sidecar \"${known.name}\" auto-detect failed: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n }\n }\n // Force-enable, but only sidecars the CLI knows how to construct.\n for (const name of opts.enable) {\n if (KNOWN_SIDECARS.some((k) => k.name === name)) {\n enabled.add(name);\n }\n }\n // Disable wins over enable.\n for (const name of opts.disable) {\n enabled.delete(name);\n }\n\n return KNOWN_SIDECARS.filter((k) => enabled.has(k.name))\n .map((k) => {\n try {\n return k.descriptor(root, resolveModule);\n } catch (err) {\n // The sidecar's package isn't resolvable (e.g. not installed) — skip it\n // rather than failing the whole launch, but say so: silently dropping it\n // would surface as \"the sidecar is mysteriously absent\".\n log(\n `sidecar \"${k.name}\" disabled — its module failed to resolve: ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n return undefined;\n }\n })\n .filter((d): d is SidecarDescriptor => d !== undefined);\n}\n"],"names":["nodeRequire","createRequire","KNOWN_SIDECARS","root","resolveModule","resolveSidecars","opts","deps","specifier","log","message","enabled","known","err","name","k","d"],"mappings":";AAiBA,MAAMA,IAAcC,EAAc,YAAY,GAAG,GAmD3CC,IAAiC;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAME,MAAM;AAAA,IACN,YAAY,MAAM;AAAA,IAClB,YAAY,CAACC,GAAMC,OAAmB;AAAA,MACpC,MAAM;AAAA,MACN,QAAQA,EAAc,qCAAqC;AAAA,MAC3D,QAAQ;AAAA,MACR,SAAS,EAAE,MAAAD,EAAA;AAAA,IAAK;AAAA,EAClB;AAEJ;AAaO,SAASE,EACdF,GACAG,GACAC,IAA4B,CAAA,GACP;AACrB,QAAMH,IACJG,EAAK,kBAAkB,CAACC,MAAsBR,EAAY,QAAQQ,CAAS,IACvEC,IACJF,EAAK,QAAQ,CAACG,MAAoB,QAAQ,OAAO,MAAM,mBAAmBA,CAAO;AAAA,CAAI,IACjFC,wBAAc,IAAA;AAIpB,aAAWC,KAASV;AAClB,QAAI;AACF,MAAIU,EAAM,WAAWT,CAAI,KACvBQ,EAAQ,IAAIC,EAAM,IAAI;AAAA,IAE1B,SAASC,GAAK;AACZ,MAAAJ;AAAA,QACE,YAAYG,EAAM,IAAI,yBACpBC,aAAe,QAAQA,EAAI,UAAU,OAAOA,CAAG,CACjD;AAAA,MAAA;AAAA,IAEJ;AAGF,aAAWC,KAAQR,EAAK;AACtB,IAAIJ,EAAe,KAAK,CAACa,MAAMA,EAAE,SAASD,CAAI,KAC5CH,EAAQ,IAAIG,CAAI;AAIpB,aAAWA,KAAQR,EAAK;AACtB,IAAAK,EAAQ,OAAOG,CAAI;AAGrB,SAAOZ,EAAe,OAAO,CAACa,MAAMJ,EAAQ,IAAII,EAAE,IAAI,CAAC,EACpD,IAAI,CAACA,MAAM;AACV,QAAI;AACF,aAAOA,EAAE,WAAWZ,GAAMC,CAAa;AAAA,IACzC,SAASS,GAAK;AAIZ,MAAAJ;AAAA,QACE,YAAYM,EAAE,IAAI,8CAChBF,aAAe,QAAQA,EAAI,UAAU,OAAOA,CAAG,CACjD;AAAA,MAAA;AAEF;AAAA,IACF;AAAA,EACF,CAAC,EACA,OAAO,CAACG,MAA8BA,MAAM,MAAS;AAC1D;"}
@@ -1,79 +0,0 @@
1
- import { type ForTestingMode } from "./config";
2
- /** Re-resolve the latest stable build id at most this often. */
3
- export declare const CHECK_TTL_MS: number;
4
- /** One managed CfT install. */
5
- export interface CftInstall {
6
- buildId: string;
7
- executablePath: string;
8
- }
9
- /** Prompt/check bookkeeping, persisted in the CfT cache dir. */
10
- export interface CftState {
11
- /** Epoch ms of the last successful latest-stable lookup. */
12
- checkedAt?: number;
13
- /** The latest stable build id as of `checkedAt`. */
14
- latestBuildId?: string;
15
- /** Update prompt answered "skip" for this build id — don't re-ask for it. */
16
- skippedBuildId?: string;
17
- /** Epoch ms when an install offer was declined — snooze re-asking for a day. */
18
- installDeclinedAt?: number;
19
- }
20
- /** Where managed CfT builds (and the state file) live. */
21
- export declare function cftCacheDir(create?: boolean): string;
22
- export declare function readCftState(): CftState;
23
- export declare function writeCftState(patch: Partial<CftState>): void;
24
- /** Numeric, segment-wise build id comparison ("138.0.7204.94"-style). */
25
- export declare function compareBuildIds(a: string, b: string): number;
26
- /** The newest managed CfT install, if any. Never touches the network. */
27
- export declare function installedCft(): Promise<CftInstall | undefined>;
28
- /**
29
- * The latest stable CfT build id, freshness-limited and offline-tolerant.
30
- *
31
- * Consults the network at most once per `maxAgeMs` (persisted in the state
32
- * file) with a short timeout; on failure it falls back to the last known
33
- * value, or undefined — callers treat undefined as "can't tell, don't nag".
34
- */
35
- export declare function latestStableCft(opts?: {
36
- maxAgeMs?: number;
37
- timeoutMs?: number;
38
- now?: number;
39
- }): Promise<string | undefined>;
40
- /**
41
- * Install the given CfT build into the managed cache and drop superseded
42
- * builds (they're ~160 MB each; the whole point of the managed dir is that
43
- * there's exactly one, current, browser in it).
44
- */
45
- export declare function installCft(buildId: string): Promise<CftInstall>;
46
- /**
47
- * Bring the managed CfT to the latest stable (the `aiui chrome install` /
48
- * `update` implementation). Unlike the launch path this resolves with no
49
- * timeout — an explicit command is allowed to wait on the network — and
50
- * reports what it did.
51
- */
52
- export declare function ensureLatestCft(report: (line: string) => void): Promise<CftInstall & {
53
- outcome: "current" | "installed" | "updated";
54
- }>;
55
- /**
56
- * The launch-path CfT sync: decide which browser this session should use, and
57
- * (interactively, when allowed) offer to install or update the managed CfT.
58
- *
59
- * Returns the CfT executable path to prefer, or undefined to fall back to the
60
- * system Chrome. Callers only invoke this when config names no browser
61
- * explicitly (no `chrome.executablePath` / `chrome.channel`).
62
- *
63
- * The mode ladder (`chrome.forTesting`, default "prompt"):
64
- * - "off" — never check, never prompt; an already-installed managed CfT is
65
- * still used (install one deliberately with `aiui chrome install`).
66
- * - "auto" — install/update to latest stable without asking.
67
- * - "prompt" — offer to install when missing, offer to update when stale;
68
- * answers can rewrite the mode in the user config.
69
- *
70
- * Nothing here ever blocks a non-interactive session: without a TTY (or under
71
- * CI, or in print mode) this degrades to "use whatever is already installed".
72
- * Downloads are likewise interactive-only — even "auto" won't pull ~160 MB
73
- * into a headless one-shot.
74
- */
75
- export declare function syncChromeForTesting(opts: {
76
- mode: ForTestingMode;
77
- interactive: boolean;
78
- now?: number;
79
- }): Promise<string | undefined>;
@@ -1,62 +0,0 @@
1
- /**
2
- * Deciding which **session sidecars** the channel should host for a launch.
3
- *
4
- * A sidecar is an extra backend the channel mounts alongside the intent
5
- * pipeline — the concrete one today is the iPad paint stream. The channel takes
6
- * no dependency on any sidecar: `aiui claude` hands it a JSON array of
7
- * {@link SidecarDescriptor}s on `--sidecars`, and the channel dynamic-imports
8
- * each `module`, calls `mod[export ?? "default"](options)`, and mounts the
9
- * result (a bad descriptor is logged + skipped there).
10
- *
11
- * This module is the CLI's half: a small registry of the sidecars the launcher
12
- * knows how to construct, plus a pure resolver that decides which are on for a
13
- * given project root and set of `--aiui-sidecar` / `--aiui-no-sidecar` flags.
14
- */
15
- /**
16
- * The contract the channel's `--sidecars` argument expects, mirrored here so
17
- * the CLI can emit it. The channel imports `module`, calls its `export` (or
18
- * `default`) with `options`, and mounts the returned sidecar.
19
- */
20
- export interface SidecarDescriptor {
21
- /** Stable identifier, used for `--aiui-sidecar`/`--aiui-no-sidecar` and logs. */
22
- name: string;
23
- /** Importable specifier the channel `import()`s (e.g. a package subpath). */
24
- module: string;
25
- /** Named export to call as the factory. Defaults to `"default"`. */
26
- export?: string;
27
- /** Passed opaquely to the factory (e.g. `{ root: "/proj" }`). */
28
- options?: unknown;
29
- }
30
- /** Resolves a package specifier to an absolute path the channel can `import()`. */
31
- type ResolveModule = (specifier: string) => string;
32
- /** Injectable seams, so the resolver is unit-testable without on-disk state. */
33
- export interface ResolveSidecarsDeps {
34
- /**
35
- * Resolves a sidecar's package specifier to an ABSOLUTE path. Defaults to
36
- * `createRequire(import.meta.url).resolve`. This matters: the channel
37
- * dynamic-imports the descriptor's `module`, but it does NOT depend on any
38
- * sidecar package — so a bare specifier resolves from the channel's own
39
- * node_modules and fails (pnpm's isolated layout). Resolving here (from the
40
- * `aiui` CLI, which DOES depend on the sidecar package) to an absolute path
41
- * makes the import work in both the source-first workspace and an install.
42
- */
43
- resolveModule?: ResolveModule;
44
- /** Warning sink for a sidecar that had to be dropped (defaults to stderr). */
45
- log?: (message: string) => void;
46
- }
47
- /**
48
- * Decide which session sidecars to host for `root`.
49
- *
50
- * Starts from the auto-detected set (each known sidecar whose `autoEnable`
51
- * predicate is truthy for this root), then applies the flags: `opts.enable`
52
- * force-adds a known sidecar by name even when it wouldn't auto-detect, and
53
- * `opts.disable` removes one. Disable wins over enable. Enable names the CLI
54
- * doesn't know how to construct are ignored (the launcher can only build the
55
- * sidecars in {@link KNOWN_SIDECARS}). Descriptors come back in the registry's
56
- * stable order.
57
- */
58
- export declare function resolveSidecars(root: string, opts: {
59
- enable: string[];
60
- disable: string[];
61
- }, deps?: ResolveSidecarsDeps): SidecarDescriptor[];
62
- export {};
@@ -1,16 +0,0 @@
1
- # aiui demo playground
2
-
3
- This directory is a scaffolded, disposable aiui demo — a sandbox the user is exploring the aiui
4
- workflow in. Ground rules for working here:
5
-
6
- - **The app is scenery.** `src/main.ts` renders a fake "spectra" viewer purely so there's
7
- something to point at and modify. Redesign it, extend it, replace it — that's the point of the
8
- demo. Prefer small, visible changes the user can watch land.
9
- - **Don't remove the integration.** The `aiuiDevOverlay()` plugin in `vite.config.ts` is what
10
- mounts the intent tool and connects it to this session's channel. The demo stops working
11
- without it.
12
- - The dev server runs via `npm run dev` (which is `aiui vite dev` — it injects the channel port
13
- as `VITE_AIUI_PORT`). Plain `vite` also serves the app, but the intent tool won't find the
14
- channel.
15
- - This is a standalone git repo scaffolded by `aiui demo`; commit freely — history here belongs
16
- to the user's sandbox and goes nowhere else.
@@ -1,36 +0,0 @@
1
- # aiui demo playground
2
-
3
- A disposable sandbox for trying [aiui](https://habemus-papadum.github.io/pdum_aiui/): a Claude
4
- Code session with a custom channel, a shared agent+human browser, and a web intent tool floating
5
- over a demo app. It's a local git repo of its own — let the agent redesign, break, and rebuild
6
- the app; nothing flows back anywhere.
7
-
8
- > ⚠️ First read *Read before running* in the aiui docs: `aiui claude` can skip permissions and
9
- > gives the agent a browser. This sandbox assumes you've decided to trust it.
10
-
11
- ## Run it
12
-
13
- ```sh
14
- npm run claude # terminal 1 — Claude Code with the aiui channel + session browser
15
- npm run dev # terminal 2 — the demo app (Vite + the intent tool overlay)
16
- ```
17
-
18
- Then open the app **in the session browser** (the window you share with the agent):
19
-
20
- ```sh
21
- npx aiui open http://localhost:5173
22
- ```
23
-
24
- Click the floating **✳ aiui** button, type an intent — *"make the baseline curve red"* — and
25
- watch it land in the Claude session as a prompt. The 🔍 button shows how your input was lowered
26
- into that prompt.
27
-
28
- ## What's what
29
-
30
- - `vite.config.ts` — the **entire** aiui integration: one `aiuiDevOverlay()` plugin. This is the
31
- part you'd copy into your own app.
32
- - `src/main.ts` — throwaway scenery (a fake spectrum viewer). No aiui code in it.
33
- - Re-running the scaffold command in this directory just picks up where you left off — it never
34
- overwrites your (or the agent's) changes.
35
-
36
- Docs: <https://habemus-papadum.github.io/pdum_aiui/guide/getting-started>
@@ -1,4 +0,0 @@
1
- node_modules/
2
- dist/
3
- .aiui-cache/
4
- *.log
@@ -1,11 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <title>spectra — aiui demo playground</title>
7
- </head>
8
- <body>
9
- <script type="module" src="/src/main.ts"></script>
10
- </body>
11
- </html>
@@ -1,23 +0,0 @@
1
- {
2
- "name": "aiui-demo-playground",
3
- "private": true,
4
- "type": "module",
5
- "aiui": {
6
- "demo": true
7
- },
8
- "scripts": {
9
- "claude": "aiui claude",
10
- "dev": "aiui vite dev",
11
- "open": "aiui open"
12
- },
13
- "dependencies": {
14
- "@habemus-papadum/aiui-dev-overlay": "__AIUI_VERSION_RANGE__"
15
- },
16
- "devDependencies": {
17
- "@babel/core": "^7.29.7",
18
- "@habemus-papadum/aiui": "__AIUI_VERSION_RANGE__",
19
- "typescript": "^5.6.0",
20
- "typescript-language-server": "^5.3.0",
21
- "vite": "^6.4.1"
22
- }
23
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * The demo app: a fake "scientific UI" the intent tool mounts over.
3
- *
4
- * The app itself is throwaway scenery (an SVG spectrum plot) — note there is
5
- * **no aiui code in here at all**. The whole integration is the
6
- * `aiuiDevOverlay()` plugin in vite.config.ts, which injects and mounts the
7
- * widget into every served page. Run it the intended way:
8
- *
9
- * terminal 1: npm run claude (Claude Code with the aiui channel + browser)
10
- * terminal 2: npm run dev (this app, via `aiui vite`)
11
- *
12
- * then open the printed URL in the session browser
13
- * (`npx aiui open http://localhost:5173`), click the ✳ aiui button, type
14
- * something — "make the baseline curve red", say — and watch it arrive in the
15
- * session. The 🔍 button opens the lowering-trace debugger.
16
- *
17
- * This playground is yours: let the agent redesign it, break it, rebuild it.
18
- * Nothing here flows back upstream.
19
- */
20
-
21
- document.body.innerHTML = `
22
- <style>
23
- body { margin: 0; background: #0f1117; color: #e8e8ea; font: 14px/1.5 ui-sans-serif, system-ui; }
24
- header { padding: 18px 28px 6px; }
25
- h1 { font-size: 17px; margin: 0; } h1 span { color: #8ab4f8; }
26
- .sub { color: #9aa0aa; font-size: 12px; }
27
- main { padding: 10px 28px; }
28
- .card { background: #171b25; border: 1px solid #262c3a; border-radius: 12px; padding: 16px 18px; max-width: 720px; }
29
- .legend { display: flex; gap: 14px; font-size: 11px; color: #9aa0aa; margin-top: 8px; }
30
- .legend i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 4px; vertical-align: -1px; }
31
- </style>
32
- <header>
33
- <h1><span>spectra</span> · absorption viewer</h1>
34
- <div class="sub">demo app for the aiui web intent tool — the widget in the corner is the tool</div>
35
- </header>
36
- <main>
37
- <div class="card">
38
- <svg viewBox="0 0 640 220" width="100%" role="img" aria-label="demo spectrum plot">
39
- <g stroke="#262c3a"><line x1="40" y1="10" x2="40" y2="190"/><line x1="40" y1="190" x2="620" y2="190"/>
40
- <line x1="40" y1="55" x2="620" y2="55" stroke-dasharray="3 5"/><line x1="40" y1="100" x2="620" y2="100" stroke-dasharray="3 5"/>
41
- <line x1="40" y1="145" x2="620" y2="145" stroke-dasharray="3 5"/></g>
42
- <polyline fill="none" stroke="#8ab4f8" stroke-width="2"
43
- points="40,180 90,176 130,168 165,120 185,60 205,38 225,64 260,150 310,170 355,162 390,132 420,90 445,74 470,96 510,158 560,174 620,178"/>
44
- <polyline fill="none" stroke="#7ee0a3" stroke-width="2" stroke-dasharray="5 4"
45
- points="40,185 100,182 150,176 200,150 250,120 300,104 350,110 400,128 450,148 500,164 560,175 620,180"/>
46
- <g fill="#9aa0aa" font-size="10"><text x="30" y="200" text-anchor="end">400</text><text x="330" y="205" text-anchor="middle">wavelength (nm)</text><text x="620" y="200" text-anchor="end">700</text></g>
47
- </svg>
48
- <div class="legend"><span><i style="background:#8ab4f8"></i>sample A-113</span><span><i style="background:#7ee0a3"></i>baseline</span></div>
49
- </div>
50
- </main>
51
- `;
@@ -1,11 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "ESNext",
5
- "moduleResolution": "bundler",
6
- "strict": true,
7
- "noEmit": true,
8
- "types": ["vite/client"]
9
- },
10
- "include": ["src", "vite.config.ts"]
11
- }
@@ -1,17 +0,0 @@
1
- import aiuiDevOverlay from "@habemus-papadum/aiui-dev-overlay/vite";
2
- import { defineConfig } from "vite";
3
-
4
- // This one plugin is the entire aiui integration — the part to copy into your
5
- // own app. It mounts the intent tool into every served page, declares the
6
- // message format the tool speaks, and bridges the channel port from
7
- // `aiui vite` (VITE_AIUI_PORT in this dev server's env) plus this app's source
8
- // root into the browser.
9
- //
10
- // `locator` turns on compile-time source stamping: every host JSX element gets
11
- // data-source-loc = "src/…:line:col", and `cell()` call sites get their
12
- // `{ name, loc }` identity injected — so anything you build here is legible to
13
- // an agent (the `locate` tool) out of the box. It needs @babel/core (an
14
- // optional peer, provided as a devDependency of this sandbox).
15
- export default defineConfig({
16
- plugins: [aiuiDevOverlay({ format: "text-concat", locator: { cellFactories: ["cell"] } })],
17
- });