typebulb 0.57.0 → 0.57.2
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 +7 -5
- package/SKILL.md +9 -7
- package/dist/agents/claude/client.js +19 -2
- package/dist/agents/claude/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/agents/claude/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/agents/claude/index.html +47 -2
- package/dist/agents/claude/katex.min.css +1 -0
- package/dist/agents/claude/styles.css +24 -0
- package/dist/agents/codex/client.js +19 -2
- package/dist/agents/codex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/agents/codex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/agents/codex/index.html +47 -2
- package/dist/agents/codex/katex.min.css +1 -0
- package/dist/agents/codex/styles.css +24 -0
- package/dist/agents/pi/client.js +19 -2
- package/dist/agents/pi/fonts/KaTeX_AMS-Regular.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Main-Bold.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Main-Italic.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Main-Regular.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Math-Italic.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Script-Regular.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Size1-Regular.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Size2-Regular.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Size3-Regular.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Size4-Regular.woff2 +0 -0
- package/dist/agents/pi/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/dist/agents/pi/index.html +47 -2
- package/dist/agents/pi/katex.min.css +1 -0
- package/dist/agents/pi/styles.css +24 -0
- package/dist/index.js +289 -270
- package/dist/render.js +18 -1
- package/dist/servers.js +103 -86
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ This document is dedicated to the typebulb CLI. At its core, it compiles and ser
|
|
|
21
21
|
- **Env files** — `.env` / `.env.local` load from cwd, `.env.local` overriding `.env` (an exported shell var wins over both). `--mode <name>` adds `.env.<name>` to switch environments (local/staging/prod); a startup line reports which keys loaded from where.
|
|
22
22
|
- **Server mode** — `--server` runs only the `**server.ts**` section in Node, skipping the web server. Bulbs with only `**server.ts**` (no `**code.tsx**`) use this mode automatically.
|
|
23
23
|
- **Type-check without running** — `typebulb check <file>` runs `tsc --noEmit` against the bulb: non-zero exit with diagnostics on errors, a one-line all-clear on stderr on success.
|
|
24
|
-
- **Filesystem access** — `tb.fs.read()` (UTF-8 text), `tb.fs.readBytes()` (raw `Uint8Array`), and `tb.fs.write()` (text or bytes); relative paths land in the bulb's own folder (`--batch <name>` scopes a run to `batches/<name>` inside it). Requires `--trust`.
|
|
24
|
+
- **Filesystem access** — `tb.fs.read()` (UTF-8 text), `tb.fs.readBytes()` (raw `Uint8Array`), and `tb.fs.write()` (text or bytes); relative paths land in the bulb's own folder (`--batch <name>` scopes a run to `batches/<name>` inside it), and `../` reaches a sibling bulb's; anything outside the project throws. Requires `--trust`.
|
|
25
25
|
- **Hot reload** — Recompiles on save and refreshes the browser (on by default; disable with `--no-watch`)
|
|
26
26
|
- **Package resolution** — Client dependencies are automatically resolved by generating import maps (same resolver as typebulb.com). Server dependencies are automatically installed via npm.
|
|
27
27
|
- **Replace dependency** — `--replace <name>=<path>` replaces a declared dependency with a local *built* package folder (browser-ready ESM, no external bare imports) instead of a CDN, for testing an unpublished build. Supplies both runtime bytes and types; applies to `run` and `check`. Under `--watch` the folder is watched and the browser reloads on rebuild (`--no-watch` freezes it). Dev-only; nothing is written to the bulb.
|
|
@@ -53,7 +53,7 @@ typebulb get <file> <kind> Print one block's content (data, insight, code,
|
|
|
53
53
|
typebulb put <file> <k>=<src> Write a file's (or stdin's) content into a block, surgically
|
|
54
54
|
typebulb pull <url|file> Fetch a bulb from typebulb.com into typebulbs/u/<user>/<slug>.bulb.md
|
|
55
55
|
typebulb push <file> Upload a local bulb to typebulb.com as you (needs TYPEBULB_TOKEN in .env)
|
|
56
|
-
typebulb check [file.bulb.md] Type-check a bulb without running it
|
|
56
|
+
typebulb check [file.bulb.md] Type-check a bulb without running it (exit 2 = no TypeScript installed)
|
|
57
57
|
typebulb predict [file] Report the capability a bulb probably needs, without running it
|
|
58
58
|
typebulb models List AI models for tb.ai, filtered by your .env API keys
|
|
59
59
|
typebulb slug <name> Print the slug a title derives to — the filename to save the bulb as
|
|
@@ -96,7 +96,7 @@ A bulb is a single **markdown** file — the minimum viable structure for a smal
|
|
|
96
96
|
### Frontmatter and config
|
|
97
97
|
|
|
98
98
|
- **`name:`** (frontmatter) is the bulb's title — a few words, not a sentence — and the file takes its slug: `<slug>.bulb.md`, in the project's **`typebulbs/`** folder. The slug is derived, not chosen: `Counter` → `counter.bulb.md` is obvious, `Rock & Roll` → `rock-and-roll` is not, so run `npx typebulb slug "<name>"` rather than guess. Once pushed, that name is the bulb's URL.
|
|
99
|
-
- **`description`** (in `config.json`) is the bulb's search-result blurb
|
|
99
|
+
- **`description`** (in `config.json`) is the bulb's search-result blurb: what makes someone open it, read cold by someone who has never seen the bulb, so every reference must resolve inside the sentence. Keep it short; it truncates past ~160 chars.
|
|
100
100
|
|
|
101
101
|
### Example
|
|
102
102
|
|
|
@@ -251,7 +251,7 @@ The agent mirror turns that block into a live, sandboxed app, with a *breakout
|
|
|
251
251
|
|
|
252
252
|
`typebulb wait` turns a background task into a subscription. It blocks until the target server logs a new line (`--match <substr>` filters), prints it, and exits — and since an agent harness re-invokes the agent when a background task finishes, the exit *is* the wake-up (Claude Code and pi; Codex has no background wake — its recipe is the bounded foreground wait above, and this loop doesn't reach it). It resumes where your last `wait` or `call` on that target left off, so an event that lands while you're acting — or before the wait attaches — still fires it immediately; arm order doesn't matter. It parks until the event. Exit `2` means it gave up before any event arrived (re-arm if you still care, or move on); exit `3` means the server died.
|
|
253
253
|
|
|
254
|
-
**The turn-based loop** (a game, an approval flow): a bulb whose `server.ts` does `console.log` on each user action is the event channel. Per turn — act via `typebulb call`, arm `wait <file> --match <tag>` in the background, end your turn; on wake, read state with `typebulb call <file> <getState>` (never parse it from the log line) and repeat. **`call` always boots a fresh `server.ts` instance** — it never attaches to the running bulb's server — so any state shared between the page and your calls must live on disk (load/save it in each export), not in `server.ts` module memory. A bulb's uncaught browser errors land in the same log as `[runtime error] …`, so the wake channel also catches your bulb breaking. A
|
|
254
|
+
**The turn-based loop** (a game, an approval flow): a bulb whose `server.ts` does `console.log` on each user action is the event channel. Per turn — act via `typebulb call`, arm `wait <file> --match <tag>` in the background, end your turn; on wake, read state with `typebulb call <file> <getState>` (never parse it from the log line) and repeat. **`call` always boots a fresh `server.ts` instance** — it never attaches to the running bulb's server — so any state shared between the page and your calls must live on disk (load/save it in each export), not in `server.ts` module memory. A bulb's uncaught browser errors land in the same log as `[runtime error] …`, so the wake channel also catches your bulb breaking. A run whose page closes never completes, so the wait ends itself when that happens (exit 3, the code a stopped server gives) rather than parking on a tag nothing can log. For inline bulbs, the same subscription is `typebulb wait agent` on the mirror — see [Emitting an inline bulb](#emitting-an-inline-bulb).
|
|
255
255
|
|
|
256
256
|
**Keep every loop command argument-stable.** A harness that permission-matches exact command strings prompts the user on *every* event if varying data (a move, a payload) rides the command line. Keep it off: write the args to a fixed file and pipe them — `cat <bulb-folder>/args.json | typebulb call <file> <fn> --args -` — so each of the loop's commands is one constant string, approved once. `send` takes its message the same way (`typebulb send <file> -`), which is also how a large or quote-heavy payload avoids the shell. `wait` and a `getState` call are constant already.
|
|
257
257
|
|
|
@@ -281,7 +281,7 @@ That one launch *is* the loop: the server watches the file, so every save recomp
|
|
|
281
281
|
- **Measuring layout** — `typebulb send <file> 'tb:rect button "Pass"'` prints that control's viewport-relative rect as JSON (`{x, y, width, height, viewport}`, integers): how big something ended up, whether two things align, whether one is offscreen — arithmetic on rects, no probe handler. Only what the outline names is measurable; give a structural container a `role` **and** an `aria-label` (`<div role="group" aria-label="board">`) to measure it — a label alone leaves it invisible.
|
|
282
282
|
- **Acting on the page** — `typebulb send <file> 'tb:click button "Pass"'` clicks the one control matching that role and name (exact, else a unique case-insensitive substring) and replies with a fresh snapshot; `tb:set combobox "strength" = hard` is the same for form controls (checkboxes and radios take `tb:click`). A disabled, readonly, or covered target is an error naming it — that silence is the bug class these verbs catch. The click is synthetic, so a handler needing user activation (a clipboard write, fullscreen) does nothing and reports nothing. Needs exactly one page open (if two are, `typebulb stop <file>` then relaunch is the reset), and the reply is the immediate frame (slow work: follow up with `tb:snapshot`). Only what the outline names is targetable: real `<button>`s and labeled controls, not an `onClick` `<div>`.
|
|
283
283
|
- **Poking state** — for state beyond what a form control expresses (`tb:set` covers those), author a set-handler up front: a `tb.onMessage` branch that takes a data payload (JSON arrives parsed), applies it to your state — committing the change if your framework needs an explicit step — and returns the new state: `typebulb send <file> '{"set":"speed","value":2}' --wait` prints it. In React, register it in an effect so it closes over the setters (the returned unsubscribe is the cleanup).
|
|
284
|
-
- **A page must be open** — the CLI runs no browser of its own, so every client-side check waits on a real window. The CLI opens one where the agent mirror is open (in VS Code beside it, or in the user's browser): at launch, and again from `send
|
|
284
|
+
- **A page must be open** — the CLI runs no browser of its own, so every client-side check waits on a real window. The CLI opens one where the agent mirror is open (in VS Code beside it, or in the user's browser): at launch, and again from any `send` that finds none, which then waits for it to arrive before delivering. So a tab the user closed reopens and the message still lands. Run the bulb, edit, `send`; never relaunch for a page. **A `send` that reached no page exits 1** — believe it, and don't chain a `wait` behind one. With no mirror page open anywhere, nothing can open one: end your turn with the link, arming `typebulb wait <file> --match "[page] connected"` in the background first; the user opening it is your wake-up. Never open a window at the user yourself.
|
|
285
285
|
- **Reading a canvas** — `typebulb send <file> tb:png` writes the page's canvas to a PNG (a stable per-bulb path under `~/.typebulb/`, overwritten each read) and prints the path — rendered truth for a bulb whose output is drawn, with no probe handler and nothing disturbed. One canvas needs no name; several take `tb:png "<name>"` — an `aria-label` on the canvas, or `role="img" aria-label="…"` on the container when a chart library owns the canvas. A WebGL/WebGPU canvas without `preserveDrawingBuffer` reads back blank outside its own frame — capture during the draw instead.
|
|
286
286
|
|
|
287
287
|
### Emitting a server-only bulb
|
|
@@ -387,6 +387,8 @@ Which must be declared in the dependencies section:
|
|
|
387
387
|
|
|
388
388
|
Typebulb has a package resolver that will load and cache these packages from `esm.sh` when the bulb runs.
|
|
389
389
|
|
|
390
|
+
Deep paths into a package are bare specifiers too, but the linter rejects a `.js` extension on one: import `three/examples/jsm/controls/OrbitControls`, not `three/addons/controls/OrbitControls.js`.
|
|
391
|
+
|
|
390
392
|
## AI Models
|
|
391
393
|
|
|
392
394
|
Three ways to use models from different providers in typebulb:
|
package/SKILL.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: typebulb
|
|
3
3
|
description: "Author and run Typebulb bulbs — single-file markdown apps (TypeScript/TSX) that run locally via `npx typebulb` (full power: filesystem, database, `server.ts`, `tb.ai`) or render live inline in your coding agent's session through Typebulb's agent mirror (sandboxed, client-only). A bulb can be a visual widget (chart, simulation, diagram, calculator, UI), a full-stack tool with a Node backend, or an AI app that calls models at runtime. Covers the bulb format, the `tb.*` API, trust, and the local run/inline workflow. Use when the user wants a bulb, a quick local tool (visual, backend-backed, or AI-powered), or something visual rendered inline in the conversation."
|
|
4
|
-
version: 0.57.
|
|
4
|
+
version: 0.57.2
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
> Generated from typebulb v0.57.
|
|
7
|
+
> Generated from typebulb v0.57.2. `npx typebulb agent` prints the running version alongside the path to its packaged SKILL.md: if that version is newer than this one, replace this file with that one.
|
|
8
8
|
|
|
9
9
|
# typebulb
|
|
10
10
|
|
|
@@ -29,7 +29,7 @@ This document is dedicated to the typebulb CLI. At its core, it compiles and ser
|
|
|
29
29
|
- **Env files** — `.env` / `.env.local` load from cwd, `.env.local` overriding `.env` (an exported shell var wins over both). `--mode <name>` adds `.env.<name>` to switch environments (local/staging/prod); a startup line reports which keys loaded from where.
|
|
30
30
|
- **Server mode** — `--server` runs only the `**server.ts**` section in Node, skipping the web server. Bulbs with only `**server.ts**` (no `**code.tsx**`) use this mode automatically.
|
|
31
31
|
- **Type-check without running** — `typebulb check <file>` runs `tsc --noEmit` against the bulb: non-zero exit with diagnostics on errors, a one-line all-clear on stderr on success.
|
|
32
|
-
- **Filesystem access** — `tb.fs.read()` (UTF-8 text), `tb.fs.readBytes()` (raw `Uint8Array`), and `tb.fs.write()` (text or bytes); relative paths land in the bulb's own folder (`--batch <name>` scopes a run to `batches/<name>` inside it). Requires `--trust`.
|
|
32
|
+
- **Filesystem access** — `tb.fs.read()` (UTF-8 text), `tb.fs.readBytes()` (raw `Uint8Array`), and `tb.fs.write()` (text or bytes); relative paths land in the bulb's own folder (`--batch <name>` scopes a run to `batches/<name>` inside it), and `../` reaches a sibling bulb's; anything outside the project throws. Requires `--trust`.
|
|
33
33
|
- **Hot reload** — Recompiles on save and refreshes the browser (on by default; disable with `--no-watch`)
|
|
34
34
|
- **Package resolution** — Client dependencies are automatically resolved by generating import maps (same resolver as typebulb.com). Server dependencies are automatically installed via npm.
|
|
35
35
|
- **Replace dependency** — `--replace <name>=<path>` replaces a declared dependency with a local *built* package folder (browser-ready ESM, no external bare imports) instead of a CDN, for testing an unpublished build. Supplies both runtime bytes and types; applies to `run` and `check`. Under `--watch` the folder is watched and the browser reloads on rebuild (`--no-watch` freezes it). Dev-only; nothing is written to the bulb.
|
|
@@ -61,7 +61,7 @@ typebulb get <file> <kind> Print one block's content (data, insight, code,
|
|
|
61
61
|
typebulb put <file> <k>=<src> Write a file's (or stdin's) content into a block, surgically
|
|
62
62
|
typebulb pull <url|file> Fetch a bulb from typebulb.com into typebulbs/u/<user>/<slug>.bulb.md
|
|
63
63
|
typebulb push <file> Upload a local bulb to typebulb.com as you (needs TYPEBULB_TOKEN in .env)
|
|
64
|
-
typebulb check [file.bulb.md] Type-check a bulb without running it
|
|
64
|
+
typebulb check [file.bulb.md] Type-check a bulb without running it (exit 2 = no TypeScript installed)
|
|
65
65
|
typebulb predict [file] Report the capability a bulb probably needs, without running it
|
|
66
66
|
typebulb models List AI models for tb.ai, filtered by your .env API keys
|
|
67
67
|
typebulb slug <name> Print the slug a title derives to — the filename to save the bulb as
|
|
@@ -104,7 +104,7 @@ A bulb is a single **markdown** file — the minimum viable structure for a smal
|
|
|
104
104
|
### Frontmatter and config
|
|
105
105
|
|
|
106
106
|
- **`name:`** (frontmatter) is the bulb's title — a few words, not a sentence — and the file takes its slug: `<slug>.bulb.md`, in the project's **`typebulbs/`** folder. The slug is derived, not chosen: `Counter` → `counter.bulb.md` is obvious, `Rock & Roll` → `rock-and-roll` is not, so run `npx typebulb slug "<name>"` rather than guess. Once pushed, that name is the bulb's URL.
|
|
107
|
-
- **`description`** (in `config.json`) is the bulb's search-result blurb
|
|
107
|
+
- **`description`** (in `config.json`) is the bulb's search-result blurb: what makes someone open it, read cold by someone who has never seen the bulb, so every reference must resolve inside the sentence. Keep it short; it truncates past ~160 chars.
|
|
108
108
|
|
|
109
109
|
### Example
|
|
110
110
|
|
|
@@ -259,7 +259,7 @@ The agent mirror turns that block into a live, sandboxed app, with a *breakout
|
|
|
259
259
|
|
|
260
260
|
`typebulb wait` turns a background task into a subscription. It blocks until the target server logs a new line (`--match <substr>` filters), prints it, and exits — and since an agent harness re-invokes the agent when a background task finishes, the exit *is* the wake-up (Claude Code and pi; Codex has no background wake — its recipe is the bounded foreground wait above, and this loop doesn't reach it). It resumes where your last `wait` or `call` on that target left off, so an event that lands while you're acting — or before the wait attaches — still fires it immediately; arm order doesn't matter. It parks until the event. Exit `2` means it gave up before any event arrived (re-arm if you still care, or move on); exit `3` means the server died.
|
|
261
261
|
|
|
262
|
-
**The turn-based loop** (a game, an approval flow): a bulb whose `server.ts` does `console.log` on each user action is the event channel. Per turn — act via `typebulb call`, arm `wait <file> --match <tag>` in the background, end your turn; on wake, read state with `typebulb call <file> <getState>` (never parse it from the log line) and repeat. **`call` always boots a fresh `server.ts` instance** — it never attaches to the running bulb's server — so any state shared between the page and your calls must live on disk (load/save it in each export), not in `server.ts` module memory. A bulb's uncaught browser errors land in the same log as `[runtime error] …`, so the wake channel also catches your bulb breaking. A
|
|
262
|
+
**The turn-based loop** (a game, an approval flow): a bulb whose `server.ts` does `console.log` on each user action is the event channel. Per turn — act via `typebulb call`, arm `wait <file> --match <tag>` in the background, end your turn; on wake, read state with `typebulb call <file> <getState>` (never parse it from the log line) and repeat. **`call` always boots a fresh `server.ts` instance** — it never attaches to the running bulb's server — so any state shared between the page and your calls must live on disk (load/save it in each export), not in `server.ts` module memory. A bulb's uncaught browser errors land in the same log as `[runtime error] …`, so the wake channel also catches your bulb breaking. A run whose page closes never completes, so the wait ends itself when that happens (exit 3, the code a stopped server gives) rather than parking on a tag nothing can log. For inline bulbs, the same subscription is `typebulb wait agent` on the mirror — see [Emitting an inline bulb](#emitting-an-inline-bulb).
|
|
263
263
|
|
|
264
264
|
**Keep every loop command argument-stable.** A harness that permission-matches exact command strings prompts the user on *every* event if varying data (a move, a payload) rides the command line. Keep it off: write the args to a fixed file and pipe them — `cat <bulb-folder>/args.json | typebulb call <file> <fn> --args -` — so each of the loop's commands is one constant string, approved once. `send` takes its message the same way (`typebulb send <file> -`), which is also how a large or quote-heavy payload avoids the shell. `wait` and a `getState` call are constant already.
|
|
265
265
|
|
|
@@ -289,7 +289,7 @@ That one launch *is* the loop: the server watches the file, so every save recomp
|
|
|
289
289
|
- **Measuring layout** — `typebulb send <file> 'tb:rect button "Pass"'` prints that control's viewport-relative rect as JSON (`{x, y, width, height, viewport}`, integers): how big something ended up, whether two things align, whether one is offscreen — arithmetic on rects, no probe handler. Only what the outline names is measurable; give a structural container a `role` **and** an `aria-label` (`<div role="group" aria-label="board">`) to measure it — a label alone leaves it invisible.
|
|
290
290
|
- **Acting on the page** — `typebulb send <file> 'tb:click button "Pass"'` clicks the one control matching that role and name (exact, else a unique case-insensitive substring) and replies with a fresh snapshot; `tb:set combobox "strength" = hard` is the same for form controls (checkboxes and radios take `tb:click`). A disabled, readonly, or covered target is an error naming it — that silence is the bug class these verbs catch. The click is synthetic, so a handler needing user activation (a clipboard write, fullscreen) does nothing and reports nothing. Needs exactly one page open (if two are, `typebulb stop <file>` then relaunch is the reset), and the reply is the immediate frame (slow work: follow up with `tb:snapshot`). Only what the outline names is targetable: real `<button>`s and labeled controls, not an `onClick` `<div>`.
|
|
291
291
|
- **Poking state** — for state beyond what a form control expresses (`tb:set` covers those), author a set-handler up front: a `tb.onMessage` branch that takes a data payload (JSON arrives parsed), applies it to your state — committing the change if your framework needs an explicit step — and returns the new state: `typebulb send <file> '{"set":"speed","value":2}' --wait` prints it. In React, register it in an effect so it closes over the setters (the returned unsubscribe is the cleanup).
|
|
292
|
-
- **A page must be open** — the CLI runs no browser of its own, so every client-side check waits on a real window. The CLI opens one where the agent mirror is open (in VS Code beside it, or in the user's browser): at launch, and again from `send
|
|
292
|
+
- **A page must be open** — the CLI runs no browser of its own, so every client-side check waits on a real window. The CLI opens one where the agent mirror is open (in VS Code beside it, or in the user's browser): at launch, and again from any `send` that finds none, which then waits for it to arrive before delivering. So a tab the user closed reopens and the message still lands. Run the bulb, edit, `send`; never relaunch for a page. **A `send` that reached no page exits 1** — believe it, and don't chain a `wait` behind one. With no mirror page open anywhere, nothing can open one: end your turn with the link, arming `typebulb wait <file> --match "[page] connected"` in the background first; the user opening it is your wake-up. Never open a window at the user yourself.
|
|
293
293
|
- **Reading a canvas** — `typebulb send <file> tb:png` writes the page's canvas to a PNG (a stable per-bulb path under `~/.typebulb/`, overwritten each read) and prints the path — rendered truth for a bulb whose output is drawn, with no probe handler and nothing disturbed. One canvas needs no name; several take `tb:png "<name>"` — an `aria-label` on the canvas, or `role="img" aria-label="…"` on the container when a chart library owns the canvas. A WebGL/WebGPU canvas without `preserveDrawingBuffer` reads back blank outside its own frame — capture during the draw instead.
|
|
294
294
|
|
|
295
295
|
### Emitting a server-only bulb
|
|
@@ -395,6 +395,8 @@ Which must be declared in the dependencies section:
|
|
|
395
395
|
|
|
396
396
|
Typebulb has a package resolver that will load and cache these packages from `esm.sh` when the bulb runs.
|
|
397
397
|
|
|
398
|
+
Deep paths into a package are bare specifiers too, but the linter rejects a `.js` extension on one: import `three/examples/jsm/controls/OrbitControls`, not `three/addons/controls/OrbitControls.js`.
|
|
399
|
+
|
|
398
400
|
## AI Models
|
|
399
401
|
|
|
400
402
|
Three ways to use models from different providers in typebulb:
|
|
@@ -1854,7 +1854,20 @@ ${LCt("tbStreamUrl")}
|
|
|
1854
1854
|
} catch (err) { location.replace('about:blank'); }
|
|
1855
1855
|
}, 250);
|
|
1856
1856
|
});
|
|
1857
|
-
|
|
1857
|
+
// The stream attaches during this script's own eval, in <head> \u2014 before the bulb's module (and
|
|
1858
|
+
// its import graph) have run, so before a single \`tb.onMessage\` handler exists. The server now
|
|
1859
|
+
// delivers the instant the stream attaches (it holds a send for the page it opened), so a
|
|
1860
|
+
// message can beat the page's own code by the width of a CDN fetch. A page that is attached but
|
|
1861
|
+
// has not yet run is not a page with no handlers, so hold the message until the deferred module
|
|
1862
|
+
// scripts have executed (DOMContentLoaded waits for them). Page-side and bounded by one load:
|
|
1863
|
+
// the server still never buffers, and a page that is awake handles inline as before.
|
|
1864
|
+
let pageAwake = document.readyState !== 'loading';
|
|
1865
|
+
const beforeAwake = [];
|
|
1866
|
+
if (!pageAwake) document.addEventListener('DOMContentLoaded', () => {
|
|
1867
|
+
pageAwake = true;
|
|
1868
|
+
for (const run of beforeAwake.splice(0)) run();
|
|
1869
|
+
});
|
|
1870
|
+
const onMessageEvent = async (e) => {
|
|
1858
1871
|
// Wire envelope { id?, payload } (JSON \u2014 SSE-line-safe). tb:* payloads are the shim's reserved
|
|
1859
1872
|
// namespace (TB-Interrogation.md): answered by a built-in handler, never delivered to
|
|
1860
1873
|
// tb.onMessage. Each settled non-undefined return is a reply candidate \u2014 the CLI enforces the
|
|
@@ -1896,6 +1909,10 @@ ${LCt("tbStreamUrl")}
|
|
|
1896
1909
|
if (env.id !== undefined) {
|
|
1897
1910
|
try { fetch('/__send-reply', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: env.id, results, errors }) }); } catch {}
|
|
1898
1911
|
}
|
|
1912
|
+
};
|
|
1913
|
+
tbOn('message', (e) => {
|
|
1914
|
+
if (pageAwake) { void onMessageEvent(e); return; }
|
|
1915
|
+
beforeAwake.push(() => { void onMessageEvent(e); });
|
|
1899
1916
|
});
|
|
1900
1917
|
}
|
|
1901
1918
|
})();
|
|
@@ -2067,7 +2084,7 @@ ${a||this.currentRow()?.pending?"New conversation \u2014 not saved yet":`Session
|
|
|
2067
2084
|
`),user:""}}stickToBottomNextRender(){this.#r=!0}#a(s,a){s.segments=[...s.segments??[s.text],a],s.text=s.segments.join(`
|
|
2068
2085
|
|
|
2069
2086
|
`)}applyUser(s){let a=this.messages[this.messages.length-1];if(a&&a.role==="user"){this.#a(a,s.text),a.copy?a.copy.setText(a.text):a.text&&(a.copy=this.#u(a.text));return}this.#l({id:++this.#e,role:"user",text:s.text,thinking:"",tools:[]})}applyAssistant(s){let a=this.messages[this.messages.length-1]?.role!=="assistant",d=this.#l({id:++this.#e,role:"assistant",text:s.text,thinking:s.thinking,tools:s.tools.map(v=>({...v,isError:!1}))}),p=this.messages[this.messages.length-2],b=this.#h();if(d.turnCopy=p?.role==="assistant"&&p.turnCopy||this.#u(""),d.turnCopy.setText(b.prose),d.turnView=p?.role==="assistant"&&p.turnView||this.#f(),a&&d.turnView.adoptLiveOverride(this.liveTurnView.takeOverride()),d.turnView.setText(b.prose,b.user),this.#d(d,s.text),this.#p(),s.live)for(let v of s.tools)h5n.has(v.name)&&this.openTools.add(v.id)}#d(s,a){let d=nEe(a);d.some(p=>p.kind==="bulb")&&(s.body=d.map(p=>{if(p.kind==="md")return p.text;let b=new nie(p.source,`inline-${this.#n++}`);return this.inlineBulbs.push(b),b}))}#p(){let s=this.inlineBulbs.map(p=>p.name),a=zSt(s),d=KSt(s);this.#t=new Set(this.inlineBulbs.filter((p,b)=>a[b])),this.inlineBulbs.forEach((p,b)=>{p.setChainPosition(d[b]),p.setMounted(!a[b]||this.expandedInlineBulbs.has(p.key))})}#m(s){oEe(this.expandedInlineBulbs,s),this.#p(),this.update()}applyToolResult(s){let a=this.#w(s.id);a&&(a.result=s.content,a.isError=s.isError,a.digest=s.digest)}applyFork(s){let a=this.#b(s.events);this.#l({id:++this.#e,role:"fork",text:"",thinking:"",tools:[],fork:{count:s.count,sub:a}})}#b(s){let a=[];for(let d of s)if(d.type==="user"){let p=a[a.length-1];p&&p.role==="user"?this.#a(p,d.text):a.push({id:++this.#e,role:"user",text:d.text,thinking:"",tools:[]})}else if(d.type==="assistant")a.push({id:++this.#e,role:"assistant",text:d.text,thinking:d.thinking,tools:d.tools.map(p=>({...p,isError:!1}))});else if(d.type==="tool_result"){let p=a.flatMap(b=>b.tools).find(b=>b.id===d.id);p&&(p.result=d.content,p.isError=d.isError,p.digest=d.digest)}return a}#w(s){return this.messages.flatMap(a=>a.tools).find(a=>a.id===s)}scrollSoon(){requestAnimationFrame(()=>{this.scrollEl&&this.#r&&(this.scrollEl.scrollTop=this.scrollEl.scrollHeight)})}onScroll(){let s=this.scrollEl;s&&(this.#r=IF(s,50))}view(){return xt({class:"messages",onScroll:()=>this.onScroll(),onMounted:s=>{this.scrollEl=s}},this.parent.ready?this.renderMessages():xt({class:"note"},"Connecting\u2026"))}renderMessages(){let s=[],a=-1;for(let D of this.messages)D.role==="user"||s.length===0?(D.role==="user"&&a++,s.push({idx:Math.max(0,a),msgs:[D]})):s[s.length-1].msgs.push(D);let d=this.parent.echo,p=this.parent.draft,b=this.parent.working&&!d,v=s.flatMap((D,P)=>this.renderTurn(D.msgs,D.idx,b&&P===s.length-1));v.unshift(this.#v());let w=d?a+1:Math.max(0,a),I=b?s[s.length-1]?.msgs??[]:[],T=[...I].reverse().find(D=>D.role==="assistant")?.turnView,B=!!T&&!!this.#x(I,T,!0);return d&&v.push(this.#C(d,w)),p?v.push(this.#y(p,w,T,!B)):(this.#i.clear(),this.#o=void 0,this.parent.working?v.push(this.#y({text:"",thinking:"",tool:this.#A()},w,T,!B)):this.liveTurnView.reset()),v}#A(){for(let s=this.messages.length-1;s>=0;s--){let a=this.messages[s];if(a.role==="assistant")return a.tools.find(d=>d.result===void 0)?.name}}#v(){return xt({class:"masthead",key:"masthead"},Kee({class:"masthead-logo light",src:c5n,alt:"typebulb"}),Kee({class:"masthead-logo dark",src:u5n,alt:"typebulb"}),xt({class:"masthead-tagline"},"run and publish bulbs \u{1F4A1} your agent\u2019s session live here, bulbs rendered inline"))}#C(s,a){return xt({class:["bubble","user",bC(a)],key:`echo-${s.length}`},this.#g(`md-echo-${s.length}`,tEe({id:-1,role:"user",text:s,thinking:"",tools:[]})))}#y(s,a,d,p=!0){let b=`draft-${s.text.length}-${s.thinking.length}`;s.text.length<this.#s&&this.#i.clear(),this.#s=s.text.length;let v=d??this.liveTurnView,w=v.raw(!0),I=w&&!!s.thinking,T=w?s.tool:void 0,B=!s.text.trim()&&!T,D=w&&(T?`${rie(T)}\u2026`:s.text.trim()||I?null:"working\u2026");return xt({class:["bubble","assistant","draft",bC(a)],key:b},p?v.view(!0):null,I?Jye({class:"thinking",open:this.#o??!0,onToggle:P=>{this.#o=P.currentTarget.open}},Uye(B?ar({class:"shimmer-text shimmer-slow"},"thinking\u2026"):"thinking"),this.#S(s.thinking)):null,s.text.trim()?this.#I(b,s.text):null,D?xt({class:"draft-wait shimmer-text shimmer-slow"},D):null)}#I(s,a){let d=nEe(a);return d.some(p=>p.kind==="bulb")?d.map((p,b)=>p.kind==="md"?p.text.trim()?this.#g(`${s}-md-${b}`,tD(p.text)):null:this.#T(p.source,b)):this.#g(s,tD(a))}#k(s,a){return{onScroll:d=>{let p=d.currentTarget;this.#i.set(s,{top:p.scrollTop,stuck:IF(p,50)})},onMounted:d=>{a?.(d);let p=this.#i.get(s);d.scrollTop=p&&!p.stuck?p.top:d.scrollHeight}}}#S(s){return i6({key:`think-${s.length}`,...this.#k(-1)},s)}#T(s,a){let d=EH(s),p=s.split(`
|
|
2070
|
-
`).length;return xt({class:"md",key:`draft-bulb-${a}`},xt({class:"embed bulb-streaming"},xt({class:"bulb-streaming-head"},ar("\u{1F4A1} "),ar({class:"shimmer-text shimmer-slow"},`${d?`${d} \u2014 `:""}writing\u2026`),ar({class:"bulb-streaming-count"},`${p} lines`)),xt({class:["bulb-code","md"],key:`bulb-code-${s.length}`,...this.#k(a,b=>{b.innerHTML=eD.render(CH(s))})})))}renderTurn(s,a,d){let p=s.filter(B=>B.role==="user"||B.text||B.body),b=p.find(B=>B.role==="assistant"&&(!!B.text||!!B.body)),v=[...p].reverse().find(B=>B.role==="assistant"&&(!!B.text||!!B.body)),w=[...s].reverse().find(B=>B.role==="assistant"),I=(v??w)?.turnView;if(!I)return this.#_(s,a);let T=this.#x(s,I,d);return I.raw(d)?this.#M(s,a,T,I,d):I.summary?this.#B(a,p,b,I,d):this.#D(s,a,p,T,v,I,d)}#x(s,a,d){return a.raw(d)?s.find(p=>p.role!=="user"):s.find(p=>p.role==="assistant"&&(!!p.text||!!p.body))}#_(s,a){return s.map(d=>d.role==="fork"?this.forkStub(d,a):this.bubble(d,a))}#M(s,a,d,p,b){return s.map(v=>v.role==="fork"?this.forkStub(v,a,v===d?p:void 0,b):this.bubble(v,a,void 0,!0,v===d?p:void 0,!0,b))}#D(s,a,d,p,b,v,w){let I=d.map(T=>{let B=T.role==="user"?T.copy:T===b?T.turnCopy:void 0;return this.bubble(T,a,B??null,!0,T===p?v:void 0,!1,w)});return p||w||I.splice(d[0]?.role==="user"?1:0,0,this.#L(s,a,v)),I}#B(s,a,d,p,b){return a.flatMap(v=>v.role==="assistant"?v===d?[this.#R(v,s,p.summary,p)]:[]:[this.bubble(v,s,v.copy??null,!0,void 0,!1,b)])}#L(s,a,d){return xt({class:["bubble","assistant",bC(a)],key:`no-prose-${a}`},d.view(!1),xt({class:"no-prose"},b5n(s)))}#N(s,a,d,p,b,v,w){let I=d.has(p);return xt({class:["bubble","assistant",bC(a)],key:s},v??null,xt({class:"turn-summary",onClick:()=>{oEe(d,p),this.update()}},sEe(I),ar({class:"turn-summary-text"},b)),w??null)}forkStub(s,a,d,p=!1){let b=s.fork,v=this.expandedForks.has(s.id)?xt({class:"fork-body"},b.sub.map(I=>this.bubble(I,a,void 0,!1))):void 0,w=[f5n(),` ${b.count} message${b.count===1?"":"s"} on an abandoned branch`];return this.#N(`fork-${s.id}`,a,this.expandedForks,s.id,w,d?.view(p),v)}bubble(s,a,d=s.copy,p=!0,b,v=!0,w=!1){let I=s.role==="assistant"&&!s.text&&!s.thinking&&s.tools.length>0;return xt({class:["bubble",s.role,I?"tools-only":"",p?bC(a):""],key:s.id},b?b.view(w):null,v&&s.thinking?Jye({class:"thinking"},Uye("thinking"),i6(s.thinking)):null,this.#j(s),s.role==="user"?this.#O(s):null,v?s.tools.map(T=>this.tool(T)):null,this.#E(d))}#E(s){return s?xt({class:"bubble-controls"},s.view()):null}#R(s,a,d,p){return xt({class:["bubble","assistant",bC(a)],key:`summary-${s.id}`},p.view(!1),this.#g(`md-summary-${s.id}-${d.length}`,tD(d)),this.#E(p.copy))}#O(s){let d=[...s.text.matchAll(o5n)].map(p=>p[1]).map(p=>this.#P(p)).filter(p=>!!p);return d.length?xt({class:"paste-thumbs"},d.map(p=>Kee({class:"paste-thumb",src:p,alt:"pasted image"}))):null}#P(s){let a=this.#c.get(s);if(!(a==="pending"||a==="failed")){if(a)return a;this.#c.set(s,"pending"),(async()=>{try{let d=await tb.server.composerPasteRead(s);this.#c.set(s,d?.ok?`data:${d.mime};base64,${d.base64}`:"failed"),d?.ok&&(this.update(),this.scrollSoon())}catch{this.#c.set(s,"failed")}})()}}#j(s){return s.body?s.body.flatMap((a,d)=>typeof a=="string"?a.trim()?[this.#g(`md-${s.id}-${d}`,tD(a))]:[]:this.#F(a)):s.text?this.#g(`md-${s.id}`,s.role==="user"?tEe(s):tD(s.text)):null}#F(s){if(!this.#t.has(s))return[s.view()];let a=this.expandedInlineBulbs.has(s.key),d=xt({class:"md",key:`fold-${s.key}`},xt({class:"bulb-fold",onClick:()=>this.#m(s.key)},sEe(a),ar({class:"bulb-fold-text"},`${s.name??"bulb"} \u2014 Version ${s.chainPosition}`),ar({class:"bulb-fold-app"},"\u{1F4A1}")));return a?[d,s.view()]:[d]}#g(s,a){return xt({class:"md",key:s,onMounted:a})}tool(s){let a=this.openTools.has(s.id),d=d5n(s.input),p=ZSt(s),b=d?T6t(d,this.parent.cwd):l5n(a5n(s.input));return xt({class:["tool",s.isError?"err":""]},xt({class:"tool-head"},xt({class:"tool-label"},ar({class:"tool-name",...s.name.startsWith("mcp__")?{title:s.name}:{}},rie(s.name)),b?d?gC({class:"tool-sum link",title:d,onClick:v=>{v.preventDefault(),tb.server.openFile(d)}},b):ar({class:"tool-sum"},b):null),s.result===void 0?ar({class:"tool-run"},"\u2026"):null),s.result!==void 0?xt({class:["tool-digest",a?"open":""],onClick:()=>{oEe(this.openTools,s.id),this.update()}},sEe(a),(p?g5n(p):s.digest)||"(no output)"):null,a?xt({class:"tool-card"},this.toolBody(s),s.result!==void 0&&(s.isError||!(p||VSt(s).length&&s.result.trim()===s.digest))?i6({class:"tool-out"},s.result.slice(0,4e3)):null):null)}toolBody(s){let a=ZSt(s);if(a)return xt({class:"todo-list"},a.map(b=>xt({class:["todo",b.status??""]},ar({class:"todo-icon"},b.status==="completed"?"\u2714":b.status==="in_progress"?"\u25FC":"\u25FB"),ar({class:"todo-text"},b.content??""))));let d=VSt(s);if(d.length)return xt({class:"tool-in"},d.map(XSt));let p=p5n(s);if(!p){let b=Object.entries(s.input??{});return b.length?xt({class:"tool-in"},b.map(([v,w])=>xt({class:"tool-field"},ar({class:"tool-key"},`${v}:`),typeof w=="string"&&eTt(w)?XSt(w):i6({class:"tool-val"},typeof w=="string"?w:JSON.stringify(w,null,2))))):null}return xt({class:"diff"},...p.flatMap((b,v)=>[p.length>1?xt({class:"diff-step"},`edit ${v+1}/${p.length}`):null,b.old?i6({class:"diff-old"},String(b.old)):null,b.new?i6({class:"diff-new"},String(b.new)):null]))}};var sie=class extends Ia{ready=!1;cwd="";sessionId=null;sessionPicker=new Xre;tokenPill=new Vre;bulbsPill=new Zre;diffPill=new tie;messageList=new iie;tokens={in:0,out:0,cached:0,cacheCreate:0};cost=0;stats=null;working=!1;latestModel=null;driverModel=null;busy=[];ownPid=0;pills;composer;draft=null;echo=null;#e=0;#n=!1;#t=!1;#r;#i=null;#s;#o;constructor(s){super(),this.pills=s.pills,this.composer=s.composer,this.#r=s.title,this.#s=s.overlays??[],this.#o=s.onPollTick}onAttached(){this.#t||(this.#t=!0,S6t(),this.init())}closePopups(s){this.sessionPicker!==s&&this.sessionPicker.close(),this.bulbsPill!==s&&this.bulbsPill.close(),this.diffPill!==s&&this.diffPill.close();for(let a of this.pills)a!==s&&a.close?.()}async init(){let s=await tb.server.info();this.cwd=s.cwd,this.ownPid=s.pid??0,this.#i=s.elsewhere??null,this.composer&&(this.composer.enabled=!!s.composer),this.ready=!0,this.updateTitle(),this.update(),this.pump()}updateTitle(){let s=Yye(this.cwd),a=this.sessionPicker.currentPreview(),d=tte(a,40);document.title=d&&s?`${d} \u2014 ${s}`:d||(s?`${this.#r} \u2014 ${s}`:this.#r)}refreshSessions(){this.sessionPicker.loadSessions().then(()=>this.update()).catch(()=>{})}pump(){if(this.#n)return;this.#n=!0;let s=async()=>{try{let{events:a,cursor:d,working:p,latestModel:b,composer:v,busy:w}=await tb.server.poll(this.#e);this.#e=d;for(let F of a)this.apply(F);let I=p!==this.working;this.working=p;let T=Array.isArray(w)?w:[],B=T.join(`
|
|
2087
|
+
`).length;return xt({class:"md",key:`draft-bulb-${a}`},xt({class:"embed bulb-streaming"},xt({class:"bulb-streaming-head"},ar("\u{1F4A1} "),ar({class:"shimmer-text shimmer-slow"},`${d?`${d} \u2014 `:""}writing\u2026`),ar({class:"bulb-streaming-count"},`${p} lines`)),xt({class:["bulb-code","md"],key:`bulb-code-${s.length}`,...this.#k(a,b=>{b.innerHTML=eD.render(CH(s))})})))}renderTurn(s,a,d){let p=s.filter(B=>B.role==="user"||B.text||B.body),b=p.find(B=>B.role==="assistant"&&(!!B.text||!!B.body)),v=[...p].reverse().find(B=>B.role==="assistant"&&(!!B.text||!!B.body)),w=[...s].reverse().find(B=>B.role==="assistant"),I=(v??w)?.turnView;if(!I)return this.#_(s,a);let T=this.#x(s,I,d);return I.raw(d)?this.#M(s,a,T,I,d):I.summary?this.#B(a,p,b,I,d):this.#D(s,a,p,T,v,I,d)}#x(s,a,d){return a.raw(d)?s.find(p=>p.role!=="user"):s.find(p=>p.role==="assistant"&&(!!p.text||!!p.body))}#_(s,a){return s.map(d=>d.role==="fork"?this.forkStub(d,a):this.bubble(d,a))}#M(s,a,d,p,b){return s.map(v=>v.role==="fork"?this.forkStub(v,a,v===d?p:void 0,b):this.bubble(v,a,void 0,!0,v===d?p:void 0,!0,b))}#D(s,a,d,p,b,v,w){let I=d.map(T=>{let B=T.role==="user"?T.copy:T===b?T.turnCopy:void 0;return this.bubble(T,a,B??null,!0,T===p?v:void 0,!1,w)});return p||w||I.splice(d[0]?.role==="user"?1:0,0,this.#L(s,a,v)),I}#B(s,a,d,p,b){return a.flatMap(v=>v.role==="assistant"?v===d?[this.#R(v,s,p.summary,p)]:[]:[this.bubble(v,s,v.copy??null,!0,void 0,!1,b)])}#L(s,a,d){return xt({class:["bubble","assistant",bC(a)],key:`no-prose-${a}`},d.view(!1),xt({class:"no-prose"},b5n(s)))}#N(s,a,d,p,b,v,w){let I=d.has(p);return xt({class:["bubble","assistant",bC(a)],key:s},v??null,xt({class:"turn-summary",onClick:()=>{oEe(d,p),this.update()}},sEe(I),ar({class:"turn-summary-text"},b)),w??null)}forkStub(s,a,d,p=!1){let b=s.fork,v=this.expandedForks.has(s.id)?xt({class:"fork-body"},b.sub.map(I=>this.bubble(I,a,void 0,!1))):void 0,w=[f5n(),` ${b.count} message${b.count===1?"":"s"} on an abandoned branch`];return this.#N(`fork-${s.id}`,a,this.expandedForks,s.id,w,d?.view(p),v)}bubble(s,a,d=s.copy,p=!0,b,v=!0,w=!1){let I=s.role==="assistant"&&!s.text&&!s.thinking&&s.tools.length>0;return xt({class:["bubble",s.role,I?"tools-only":"",p?bC(a):""],key:s.id},b?b.view(w):null,v&&s.thinking?Jye({class:"thinking"},Uye("thinking"),i6(s.thinking)):null,this.#j(s),s.role==="user"?this.#O(s):null,v?s.tools.map(T=>this.tool(T)):null,this.#E(d))}#E(s){return s?xt({class:"bubble-controls"},s.view()):null}#R(s,a,d,p){return xt({class:["bubble","assistant",bC(a)],key:`summary-${s.id}`},p.view(!1),this.#g(`md-summary-${s.id}-${d.length}`,tD(d)),this.#E(p.copy))}#O(s){let d=[...s.text.matchAll(o5n)].map(p=>p[1]).map(p=>this.#P(p)).filter(p=>!!p);return d.length?xt({class:"paste-thumbs"},d.map(p=>Kee({class:"paste-thumb",src:p,alt:"pasted image"}))):null}#P(s){let a=this.#c.get(s);if(!(a==="pending"||a==="failed")){if(a)return a;this.#c.set(s,"pending"),(async()=>{try{let d=await tb.server.composerPasteRead(s);this.#c.set(s,d?.ok?`data:${d.mime};base64,${d.base64}`:"failed"),d?.ok&&(this.update(),this.scrollSoon())}catch{this.#c.set(s,"failed")}})()}}#j(s){return s.body?s.body.flatMap((a,d)=>typeof a=="string"?a.trim()?[this.#g(`md-${s.id}-${d}`,tD(a))]:[]:this.#F(a)):s.text?this.#g(`md-${s.id}`,s.role==="user"?tEe(s):tD(s.text)):null}#F(s){if(!this.#t.has(s))return[s.view()];let a=this.expandedInlineBulbs.has(s.key),d=xt({class:"md",key:`fold-${s.key}`},xt({class:"bulb-fold",onClick:()=>this.#m(s.key)},sEe(a),ar({class:"bulb-fold-text"},`${s.name??"bulb"} \u2014 Version ${s.chainPosition}`),ar({class:"bulb-fold-app"},"\u{1F4A1}")));return a?[d,s.view()]:[d]}#g(s,a){return xt({class:"md",key:s,onMounted:a})}tool(s){let a=this.openTools.has(s.id),d=d5n(s.input),p=ZSt(s),b=d?T6t(d,this.parent.cwd):l5n(a5n(s.input));return xt({class:["tool",s.isError?"err":""]},xt({class:"tool-head"},xt({class:"tool-label"},ar({class:"tool-name",...s.name.startsWith("mcp__")?{title:s.name}:{}},rie(s.name)),b?d?gC({class:"tool-sum link",title:d,onClick:v=>{v.preventDefault(),tb.server.openFile(d)}},b):ar({class:"tool-sum"},b):null),s.result===void 0?ar({class:"tool-run"},"\u2026"):null),s.result!==void 0?xt({class:["tool-digest",a?"open":""],onClick:()=>{oEe(this.openTools,s.id),this.update()}},sEe(a),(p?g5n(p):s.digest)||"(no output)"):null,a?xt({class:"tool-card"},this.toolBody(s),s.result!==void 0&&(s.isError||!(p||VSt(s).length&&s.result.trim()===s.digest))?i6({class:"tool-out"},s.result.slice(0,4e3)):null):null)}toolBody(s){let a=ZSt(s);if(a)return xt({class:"todo-list"},a.map(b=>xt({class:["todo",b.status??""]},ar({class:"todo-icon"},b.status==="completed"?"\u2714":b.status==="in_progress"?"\u25FC":"\u25FB"),ar({class:"todo-text"},b.content??""))));let d=VSt(s);if(d.length)return xt({class:"tool-in"},d.map(XSt));let p=p5n(s);if(!p){let b=Object.entries(s.input??{});return b.length?xt({class:"tool-in"},b.map(([v,w])=>xt({class:"tool-field"},ar({class:"tool-key"},`${v}:`),typeof w=="string"&&eTt(w)?XSt(w):i6({class:"tool-val"},typeof w=="string"?w:JSON.stringify(w,null,2))))):null}return xt({class:"diff"},...p.flatMap((b,v)=>[p.length>1?xt({class:"diff-step"},`edit ${v+1}/${p.length}`):null,b.old?i6({class:"diff-old"},String(b.old)):null,b.new?i6({class:"diff-new"},String(b.new)):null]))}};var sie=class extends Ia{ready=!1;cwd="";sessionId=null;sessionPicker=new Xre;tokenPill=new Vre;bulbsPill=new Zre;diffPill=new tie;messageList=new iie;tokens={in:0,out:0,cached:0,cacheCreate:0};cost=0;stats=null;working=!1;latestModel=null;driverModel=null;busy=[];ownPid=0;pills;composer;draft=null;echo=null;#e=0;#n=!1;#t=!1;#r;#i=null;#s;#o;constructor(s){super(),this.pills=s.pills,this.composer=s.composer,this.#r=s.title,this.#s=s.overlays??[],this.#o=s.onPollTick}onAttached(){this.#t||(this.#t=!0,S6t(),this.init())}closePopups(s){this.sessionPicker!==s&&this.sessionPicker.close(),this.bulbsPill!==s&&this.bulbsPill.close(),this.diffPill!==s&&this.diffPill.close();for(let a of this.pills)a!==s&&a.close?.()}async init(){let s=await tb.server.info();this.cwd=s.cwd,this.ownPid=s.pid??0,this.#i=s.elsewhere??null,this.composer&&(this.composer.enabled=!!s.composer),this.ready=!0,document.getElementById("boot")?.remove(),this.updateTitle(),this.update(),this.pump()}updateTitle(){let s=Yye(this.cwd),a=this.sessionPicker.currentPreview(),d=tte(a,40);document.title=d&&s?`${d} \u2014 ${s}`:d||(s?`${this.#r} \u2014 ${s}`:this.#r)}refreshSessions(){this.sessionPicker.loadSessions().then(()=>this.update()).catch(()=>{})}pump(){if(this.#n)return;this.#n=!0;let s=async()=>{try{let{events:a,cursor:d,working:p,latestModel:b,composer:v,busy:w}=await tb.server.poll(this.#e);this.#e=d;for(let F of a)this.apply(F);let I=p!==this.working;this.working=p;let T=Array.isArray(w)?w:[],B=T.join(`
|
|
2071
2088
|
`)!==this.busy.join(`
|
|
2072
2089
|
`);this.busy=T;let D=b!==this.latestModel;this.latestModel=b??null;let P=this.composer&&v?this.composer.syncFromPoll(v):!1;(a.length||I||D||P||B)&&this.update(),(a.length||P)&&this.messageList.scrollSoon(),this.#o?.()}catch(a){console.error("[mirror] poll failed",a)}setTimeout(s,600)};s()}apply(s){switch(s.type){case"cleared":this.messageList.clear(),this.tokens={in:0,out:0,cached:0,cacheCreate:0},this.cost=0;break;case"session":this.sessionId=s.sessionId,s.sessionId&&!this.sessionPicker.currentRow()?this.refreshSessions():this.updateTitle();break;case"user":this.messageList.applyUser(s);break;case"assistant":this.messageList.applyAssistant(s);break;case"tool_result":this.messageList.applyToolResult(s);break;case"fork":this.messageList.applyFork(s);break;case"usage":this.tokens={in:s.in,out:s.out,cached:s.cached,cacheCreate:s.cacheCreate},this.cost+=s.cost??0;break}}view(){return xt({class:"app"},xt({class:"chat"},this.diffPill.viewing?this.diffPill.docView():this.messageList.view(),this.#c(),...this.#s.map(s=>s()),this.statusbar()),this.composer?.enabled?this.composer.view():null)}#c(){let s=this.#i;return!this.ready||this.sessionId!==null||!s?null:xt({class:"cwd-hint"},xt({class:"cwd-hint-head"},`No sessions for ${this.cwd}`),xt(`${s.count} session${s.count===1?"":"s"} exist for `,ar({class:"cwd-hint-dir"},s.dir)," \u2014 this mirror was likely launched from a subfolder. Relaunch it from the project root."))}statusbar(){return xt({class:"statusbar"},xt({class:["statusbar-actions",this.diffPill.viewing?"doc-open":""]},this.diffPill.view(),...this.pills.map(s=>s.view()),this.tokenPill.view(),this.sessionPicker.view(),this.bulbsPill.view()))}};var m5n=[[0,"Low"],[1,"Med"],[2,"High"]],w5n=1,oie=o=>o?.message??String(o),A5n=()=>E6t({viewBox:"0 0 24 24",width:"15",height:"15",class:"anthropic-glyph",fill:"currentColor"},x6t({d:"M13.827 3.52h3.603L24 20h-3.603l-6.57-16.48zm-7.258 0h3.767L16.906 20h-3.674l-1.343-3.461H5.017l-1.344 3.46H0L6.57 3.522zm4.132 9.959L8.453 7.687 6.205 13.48H10.7z"})),tTt=o=>o===!0?"ok":o===!1?"bad":"warn",cie=class extends y4{models=[];hasKey=!0;loaded=!1;current={active:!1,managed:!1,model:null,port:null,provider:null,routedModel:null,caching:"unknown",reasoning:null,externalBaseUrl:null};busy=!1;err=null;enteringKey=!1;keyDraft="";probes={};probing=null;openDiag=null;infoOpen=!1;ticking=!1;keepOpenSelector=".model-wrap";filterId="model-filter";listSelector="#model-list";filterNoun="model";search(){return Promise.resolve([])}modeToggle(){return null}onActivate(s){let a=this.rows()[s];a&&(a.kind==="default"?this.clear():a.kind==="unlock"?this.beginKeyEntry():this.set(a.id))}onAttached(){this.refresh()}async refresh(){try{let[s,a]=await Promise.all([tb.server.listSwitchModels(),tb.server.switchState()]);this.models=s.models,this.hasKey=s.hasKey,this.current=a,this.err=s.error??null}catch(s){this.err=oie(s),console.error("[mirror] switcher refresh failed",s)}finally{this.loaded=!0,this.update()}}async tickState(){if(!(this.ticking||this.enteringKey)){this.ticking=!0;try{let s=await tb.server.switchState(),a=s.active!==this.current.active||s.model!==this.current.model||s.caching!==this.current.caching||s.routedModel!==this.current.routedModel||s.externalBaseUrl!==this.current.externalBaseUrl||s.provider!==this.current.provider||s.reasoning!==this.current.reasoning;this.current=s,a&&this.update()}catch{}finally{this.ticking=!1}}}rows(){let s=[];if(!this.hasKey)s.push({kind:"unlock"});else{let a=this.filter.trim().toLowerCase(),d=null;for(let p of this.models){if(a&&!p.label.toLowerCase().includes(a)&&!p.id.toLowerCase().includes(a))continue;let b={kind:"model",id:p.id,label:p.label,active:this.current.active&&this.current.model===p.id};b.active?d=b:s.push(b)}d&&s.push(d)}return s.push({kind:"default",active:!this.current.active&&!this.current.externalBaseUrl}),s}activeIndex(){let s=this.rows(),a=s.findIndex(d=>d.active);return a<0?Math.max(0,s.length-1):a}async show(){this.beginOpen(),this.refreshList(this.activeIndex()),this.focusFilter(),await this.refresh(),this.refreshList(this.activeIndex()),this.armClose()}onClosed(){super.onClosed(),this.enteringKey=!1,this.keyDraft="",this.openDiag=null,this.infoOpen=!1,this.err=null}async probe(s){if(!this.probing){this.probing=s,this.update();try{this.probes[s]=await tb.server.probeModel({model:s})}catch(a){this.probes[s]={ok:!1,error:oie(a)}}this.probing=null,this.update()}}showDiag(s){this.openDiag=s,this.update()}closeDiag(){this.openDiag=null,this.update(),this.focusFilter()}label(s){return s?this.models.find(a=>a.id===s)?.label||s.split("/").pop()||s:"Anthropic"}async runBusy(s,a){this.busy=!0,this.err=null,this.update();try{let d=await s();if(d?.ok===!1)return this.err=d.error||a,this.busy=!1,this.update(),!1}catch(d){return this.err=oie(d),this.busy=!1,this.update(),!1}return this.busy=!1,!0}async set(s){await this.runBusy(()=>tb.server.setSwitchModel({model:s}),"failed to set model")&&(await this.refresh(),this.close())}async clear(){await this.runBusy(()=>tb.server.clearSwitchModel(),"failed to revert")&&(await this.refresh(),this.close())}get reasoningSel(){return this.current.reasoning??w5n}set reasoningSel(s){this.setReasoning(s)}async setReasoning(s){if(this.current.reasoning!==s){this.current={...this.current,reasoning:s},this.update();try{let a=await tb.server.setReasoning({level:s});a?.ok===!1&&(this.err=a.error||"failed to set reasoning",this.update())}catch(a){this.err=oie(a),this.update()}}}beginKeyEntry(){this.enteringKey=!0,this.keyDraft="",this.update(),setTimeout(()=>document.getElementById("or-key-input")?.focus())}cancelKeyEntry(){this.enteringKey=!1,this.keyDraft="",this.update(),this.focusFilter()}async saveKey(){let s=this.keyDraft.trim();s&&await this.runBusy(()=>tb.server.saveOpenRouterKey({key:s}),"failed to save key")&&(this.enteringKey=!1,this.keyDraft="",await this.refresh(),this.refreshList(this.activeIndex()))}get overridden(){return this.current.active}get uncached(){return this.current.active&&this.current.caching==="uncached"}watchdogView(){return this.uncached?xt({class:["model-banner","warn"]},ar({class:"model-banner-text"},`${this.label(this.current.model)} was served uncached via OpenRouter \u2014 the full context is re-billed each turn. Probe the model to pin a provider that caches, or pick a different one.`)):null}head(){return this.current.active?xt({class:"model-head"},Po({class:"model-info-toggle",onClick:s=>{s.stopPropagation(),this.infoOpen=!0,this.update()}},"Routing info"),xt({class:"reasoning-ctl",title:"Reasoning sets the route\u2019s base thinking effort."},ar({class:"reasoning-label"},"reasoning"),this.reasoningSelect())):this.current.externalBaseUrl?xt({class:"model-head"},ar({class:["model-route","warn"]},"Custom base URL set elsewhere \u2014 \u201CAnthropic (native)\u201D clears it")):null}reasoningSelect(){return k6t({target:this,prop:()=>this.reasoningSel,attrs:{class:"reasoning-select",onClick:s=>s.stopPropagation()},options:m5n.map(([s,a])=>({value:s,label:a}))})}infoView(){let s=this.label(this.current.model),a=()=>{this.infoOpen=!1,this.update()};return xt({class:"picker model-picker diag-mode",tabIndex:0,onMounted:d=>d.focus(),onKeyDown:d=>{d.key==="Escape"&&(d.stopPropagation(),a())}},xt({class:"diag-head"},ar({class:"diag-name"},"Routing"),xt({class:"diag-head-acts"},Po({class:"diag-back",title:"Back to the model list",onClick:d=>{d.stopPropagation(),a()}},"\u2190 models"))),xt({class:"diag-body"},xt({class:"diag-line"},`You are currently routing to ${s} via OpenRouter, not your Anthropic subscription.`),xt({class:"diag-line"},"The route is shared by every Claude Code conversation scoped to this project."),xt({class:"diag-line"},"Switching to or from Anthropic toggles the proxy, so for existing conversations you\u2019ll need to:"),xt({class:["diag-line","diag-bullet"]},"\u2022 Relaunch the Claude CLI"),xt({class:["diag-line","diag-bullet"]},"\u2022 or, in the VS Code extension, run \u201CDeveloper: Restart Extension Host\u201D (Cmd/Ctrl+Shift+P).")))}view(){let s=this.current.active,a=this.uncached,d=!s&&!!this.current.externalBaseUrl,p=this.label(this.current.model),b=a?`${p} via OpenRouter, uncached \u2014 full context re-billed each turn. Click to switch or revert.`:s?`claude \u2192 ${this.current.model}: its Anthropic API is proxied to OpenRouter. Click to switch or revert.`:d?`Custom ANTHROPIC_BASE_URL set outside this tool (${this.current.externalBaseUrl}). Click to manage.`:"Anthropic (native) \u2014 click to proxy claude\u2019s Anthropic API to an OpenRouter model.",v=s?p:d?"custom route":A5n();return xt({class:"model-wrap pop-center"},Po({class:["pill",s||d?"on":"glyph",a?"warn":""],"data-tip":b,ariaLabel:s||d?void 0:"Anthropic (native)",onClick:w=>{w.stopPropagation(),this.open?this.close():this.show()}},v),this.open?this.popup():null)}popup(){if(this.enteringKey)return this.keyView();if(this.infoOpen)return this.infoView();if(this.openDiag)return this.diagView(this.openDiag);let s=this.rows(),a=this.loaded&&this.hasKey&&!this.models.length;return xt({class:"picker model-picker"},this.head(),xt({id:"model-list",class:"picker-list",onScroll:()=>this.onListScroll()},s.map((d,p)=>this.row(d,p)),a?xt({class:"picker-empty"},this.err?`Couldn't load models: ${this.err}`:"No OpenRouter models in the catalog right now."):null),this.err&&!a?xt({class:"model-err"},`\u2717 ${this.err}`):null,this.hasKey&&this.models.length?this.filterBox(this.models.length,"model"):null)}row(s,a){let d=()=>{this.highlighted!==a&&(this.highlighted=a,this.update())},p=["picker-row",a===this.highlighted?"active":""];if(s.kind==="unlock")return xt({class:p,onMouseEnter:d,onClick:()=>this.onActivate(a)},xt({class:"picker-row-main"},ar({class:"picker-dot"}),ar({class:"picker-preview"},"+ Add OpenRouter key to unlock models\u2026")));if(s.kind==="default")return xt({class:[...p,s.active?"current":"","model-row","model-default-row"],title:"Your normal claude (subscription / API) \u2014 clears the override",onMouseEnter:d,onClick:()=>this.onActivate(a)},xt({class:"picker-row-main"},ar({class:"picker-dot"}),ar({class:"picker-preview"},"Anthropic (native)"),xt({class:"model-trailing"},s.active?this.activeBadge():null)));let b=s.id;return xt({class:[...p,s.active?"current":"","model-row"],title:b,onMouseEnter:d,onClick:()=>this.onActivate(a)},xt({class:"picker-row-main"},ar({class:"picker-dot"}),ar({class:"picker-preview"},s.label),xt({class:"model-trailing"},s.active?this.activeBadge():null,this.statusButton(b))))}activeBadge(){return ar({class:["model-btn","model-active"],title:"Active \u2014 bare claude routes here now"},"active")}statusButton(s){if(this.probing===s)return ar({class:["model-btn","model-status","probing"]},"probing\u2026");let a=this.probes[s];if(!a)return Po({class:["model-btn","model-status","probe"],title:"Check if it\u2019s callable and which provider caches \u2014 pins the caching one.",onClick:p=>{p.stopPropagation(),this.probe(s)}},"probe");let d=this.badge(a);return Po({class:["model-btn","model-status",d.cls],title:`${d.title} \xB7 click for details`,onClick:p=>{p.stopPropagation(),this.showDiag(s)}},d.text)}badge(s){if(!s.ok||s.callable===!1)return{text:"error",cls:"bad",title:s.error||"unavailable"};let a=s.provider?` on ${s.provider}`:"",d=s.cache?.works,p=tTt(d);return d===!0?{text:"cached",cls:p,title:`caching confirmed${a} \u2014 ${s.cache?.note||""}`}:d===!1?{text:"uncached",cls:p,title:`no caching${a} \u2014 ${s.cache?.note||""}`}:{text:"cache?",cls:p,title:`${s.cache?.note||"caching unconfirmed"}${a}`}}diagView(s){return xt({class:"picker model-picker diag-mode",tabIndex:0,onMounted:a=>a.focus(),onKeyDown:a=>{a.key==="Escape"&&(a.stopPropagation(),this.closeDiag())}},xt({class:"diag-head"},ar({class:"diag-name"},this.label(s)),xt({class:"diag-head-acts"},Po({class:"diag-back",title:"Re-run the probe",disabled:this.probing===s,onClick:a=>{a.stopPropagation(),this.probe(s)}},this.probing===s?"probing\u2026":"probe again"),Po({class:"diag-back",title:"Back to the model list",onClick:a=>{a.stopPropagation(),this.closeDiag()}},"\u2190 models"))),xt({class:"diag-body"},...this.diagLines(s)))}diagLines(s){let a=this.probes[s];if(!a)return[xt({class:["diag-line","muted"]},"Not probed yet \u2014 close this and hit probe.")];let d=[];if(!a.ok||a.callable===!1)d.push(xt({class:["diag-line","bad"]},`\u2717 Not callable${a.status?` (HTTP ${a.status})`:""}: ${a.error||"unknown error"}`)),a.restriction&&d.push(xt({class:["diag-line","warn"]},`\u26A0 ${a.restriction}`));else{d.push(xt({class:["diag-line","ok"]},`\u2713 Callable${a.ms?` \xB7 ${a.ms}ms`:""}${a.sample?` \xB7 replied \u201C${a.sample}\u201D`:""}`)),a.provider&&d.push(xt({class:["diag-line","muted"]},`Served by ${a.provider} this probe \u2014 the proxy will pin CC's turns to it when caching is confirmed.`));let p=a.cache;if(p){let b=tTt(p.works),v=p.works===!0?"\u2713 Caching works":p.works===!1?"\u2717 No caching":"~ Caching unconfirmed",w=a.provider?` on ${a.provider}`:"";d.push(xt({class:["diag-line",b]},`${v}${w} \u2014 ${p.note}`)),p.works!==!0&&d.push(xt({class:["diag-line","muted"]},"Without prompt caching every turn re-bills the whole context \u2014 costly over a long session."))}}return d.push(xt({class:["diag-line","muted","small"]},"Caching on OpenRouter is per-provider; the proxy pins the provider a probe confirms so CC keeps caching. A probe makes two small test calls.")),d}keyView(){return xt({class:"picker model-picker key-entry",tabIndex:0,onKeyDown:s=>{s.key==="Escape"&&(s.stopPropagation(),this.cancelKeyEntry())}},xt({class:"key-entry-head"},"Paste your OpenRouter API key"),Yee({target:this,prop:()=>this.keyDraft,id:"or-key-input",attrs:{class:"bulb-filter",placeholder:"sk-or-\u2026",ariaLabel:"OpenRouter API key",onKeyDown:s=>{s.key==="Enter"?(s.preventDefault(),this.saveKey()):s.key==="Escape"&&(s.stopPropagation(),this.cancelKeyEntry())}}}),this.err?xt({class:"model-err"},`\u2717 ${this.err}`):null,xt({class:"key-entry-acts"},Po({class:"pill",onClick:s=>{s.stopPropagation(),this.cancelKeyEntry()}},"Cancel"),Po({class:["pill","on"],onClick:s=>{s.stopPropagation(),this.saveKey()}},this.busy?"Saving\u2026":"Save")),xt({class:"key-entry-note"},"Written to .env in this project (gitignored). Used only via OpenRouter."))}};document.title="Claude Mirror";var cEe=new cie;new w6t({root:new sie({title:"Claude Mirror",pills:[cEe],overlays:[()=>cEe.watchdogView()],onPollTick:()=>{cEe.tickState()}}),id:"app"});
|
|
2073
2090
|
/*! Bundled license information:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,2 +1,47 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
1
|
+
<div id="app"></div>
|
|
2
|
+
|
|
3
|
+
<!-- The boot overlay: what the window shows between this HTML landing and the client being up. It
|
|
4
|
+
ships in the served markup, so it paints before any script runs — and its script lives HERE with
|
|
5
|
+
it rather than in page.ts's chrome, because getHtml re-reads this file per request while page.ts
|
|
6
|
+
is bundled into the running process: together, the two halves are always the same version as
|
|
7
|
+
each other and hot-reload as one. The client retires the overlay by removing #boot
|
|
8
|
+
(client/root.ts), which needs nothing from the script below. -->
|
|
9
|
+
<div id="boot" class="boot">
|
|
10
|
+
<div class="boot-title"><span class="shimmer-text shimmer-slow">Starting the agent mirror</span> 💡</div>
|
|
11
|
+
<div class="boot-why"></div>
|
|
12
|
+
</div>
|
|
13
|
+
<script>
|
|
14
|
+
(() => {
|
|
15
|
+
const el = document.getElementById('boot')
|
|
16
|
+
const why = el && el.querySelector('.boot-why')
|
|
17
|
+
// Markup and script ship in this one file, so a mismatch means a truncated or hand-altered stub.
|
|
18
|
+
// Bail rather than wedge on it; removal is the client's job and needs nothing from here.
|
|
19
|
+
if (!why) return
|
|
20
|
+
// The overlay says NOTHING while it is merely loading. The shimmering title already says "working",
|
|
21
|
+
// and narrating which step is in flight teaches the user nothing they can act on — a wait we cannot
|
|
22
|
+
// explain is better left unexplained. It speaks only when something has actually FAILED, which is
|
|
23
|
+
// the one thing they cannot see for themselves.
|
|
24
|
+
const say = text => { if (el.isConnected) why.textContent = text }
|
|
25
|
+
let retried = false
|
|
26
|
+
// A module script's FETCH failure fires on the element and never bubbles to window, hence capture.
|
|
27
|
+
// Untreated it is invisible: the page shimmers on over a bundle that is never coming.
|
|
28
|
+
addEventListener('error', e => {
|
|
29
|
+
const script = e.target
|
|
30
|
+
if (script instanceof HTMLScriptElement) {
|
|
31
|
+
// One silent re-fetch first — silent because a retry that works is not news. The common cause
|
|
32
|
+
// is transient (a mirror serving assets that were rewritten under it mid-load), and
|
|
33
|
+
// re-appending the script rather than reloading the page cannot loop and cannot cost the page.
|
|
34
|
+
if (retried) { say("The agent mirror didn't finish loading. Reload to retry."); return }
|
|
35
|
+
retried = true
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
const again = document.createElement('script')
|
|
38
|
+
again.type = 'module'
|
|
39
|
+
again.src = script.src.split('?')[0] + '?retry=' + Date.now() // past a cached bad response
|
|
40
|
+
document.body.appendChild(again)
|
|
41
|
+
}, 1000)
|
|
42
|
+
} else if (e.message) {
|
|
43
|
+
say('The agent mirror stopped: ' + e.message)
|
|
44
|
+
}
|
|
45
|
+
}, true)
|
|
46
|
+
})()
|
|
47
|
+
</script>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@font-face{font-display:block;font-family:KaTeX_AMS;font-style:normal;font-weight:400;src:url(/agents/claude/fonts/KaTeX_AMS-Regular.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_AMS-Regular.woff) format("woff"),url(/agents/claude/fonts/KaTeX_AMS-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700;src:url(/agents/claude/fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Caligraphic-Bold.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Caligraphic-Bold.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400;src:url(/agents/claude/fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Caligraphic-Regular.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Caligraphic-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Fraktur;font-style:normal;font-weight:700;src:url(/agents/claude/fonts/KaTeX_Fraktur-Bold.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Fraktur-Bold.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Fraktur-Bold.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Fraktur;font-style:normal;font-weight:400;src:url(/agents/claude/fonts/KaTeX_Fraktur-Regular.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Fraktur-Regular.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Fraktur-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:normal;font-weight:700;src:url(/agents/claude/fonts/KaTeX_Main-Bold.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Main-Bold.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Main-Bold.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:italic;font-weight:700;src:url(/agents/claude/fonts/KaTeX_Main-BoldItalic.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Main-BoldItalic.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Main-BoldItalic.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:italic;font-weight:400;src:url(/agents/claude/fonts/KaTeX_Main-Italic.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Main-Italic.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Main-Italic.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:normal;font-weight:400;src:url(/agents/claude/fonts/KaTeX_Main-Regular.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Main-Regular.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Main-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Math;font-style:italic;font-weight:700;src:url(/agents/claude/fonts/KaTeX_Math-BoldItalic.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Math-BoldItalic.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Math-BoldItalic.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Math;font-style:italic;font-weight:400;src:url(/agents/claude/fonts/KaTeX_Math-Italic.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Math-Italic.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Math-Italic.ttf) format("truetype")}@font-face{font-display:block;font-family:"KaTeX_SansSerif";font-style:normal;font-weight:700;src:url(/agents/claude/fonts/KaTeX_SansSerif-Bold.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_SansSerif-Bold.woff) format("woff"),url(/agents/claude/fonts/KaTeX_SansSerif-Bold.ttf) format("truetype")}@font-face{font-display:block;font-family:"KaTeX_SansSerif";font-style:italic;font-weight:400;src:url(/agents/claude/fonts/KaTeX_SansSerif-Italic.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_SansSerif-Italic.woff) format("woff"),url(/agents/claude/fonts/KaTeX_SansSerif-Italic.ttf) format("truetype")}@font-face{font-display:block;font-family:"KaTeX_SansSerif";font-style:normal;font-weight:400;src:url(/agents/claude/fonts/KaTeX_SansSerif-Regular.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_SansSerif-Regular.woff) format("woff"),url(/agents/claude/fonts/KaTeX_SansSerif-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Script;font-style:normal;font-weight:400;src:url(/agents/claude/fonts/KaTeX_Script-Regular.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Script-Regular.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Script-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Size1;font-style:normal;font-weight:400;src:url(/agents/claude/fonts/KaTeX_Size1-Regular.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Size1-Regular.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Size1-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Size2;font-style:normal;font-weight:400;src:url(/agents/claude/fonts/KaTeX_Size2-Regular.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Size2-Regular.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Size2-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Size3;font-style:normal;font-weight:400;src:url(/agents/claude/fonts/KaTeX_Size3-Regular.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Size3-Regular.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Size3-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Size4;font-style:normal;font-weight:400;src:url(/agents/claude/fonts/KaTeX_Size4-Regular.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Size4-Regular.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Size4-Regular.ttf) format("truetype")}@font-face{font-display:block;font-family:KaTeX_Typewriter;font-style:normal;font-weight:400;src:url(/agents/claude/fonts/KaTeX_Typewriter-Regular.woff2) format("woff2"),url(/agents/claude/fonts/KaTeX_Typewriter-Regular.woff) format("woff"),url(/agents/claude/fonts/KaTeX_Typewriter-Regular.ttf) format("truetype")}.katex{font:normal 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;position:relative;text-indent:0;text-rendering:auto}.katex *{-ms-high-contrast-adjust:none!important;border-color:currentColor}.katex .katex-version:after{content:"0.16.47"}.katex .katex-mathml{border:0;-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.katex .katex-html>.newline{display:block}.katex .base{position:relative;white-space:nowrap;width:-webkit-min-content;width:-moz-min-content;width:min-content}.katex .base,.katex .strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathnormal{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-style:italic;font-weight:700}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathboldfrak,.katex .textboldfrak{font-family:KaTeX_Fraktur;font-weight:700}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .mathsfit,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{border-collapse:collapse;display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;position:relative;vertical-align:bottom}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;font-size:1px;min-width:2px;vertical-align:bottom;width:2px}.katex .vbox{align-items:baseline;display:inline-flex;flex-direction:column}.katex .hbox{width:100%}.katex .hbox,.katex .thinbox{display:inline-flex;flex-direction:row}.katex .thinbox{max-width:0;width:0}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline,.katex .hline,.katex .mfrac .frac-line,.katex .overline .overline-line,.katex .rule,.katex .underline .underline-line{min-height:1px}.katex .mspace{display:inline-block}.katex .smash{display:inline;line-height:0}.katex .clap,.katex .llap,.katex .rlap{position:relative;width:0}.katex .clap>.inner,.katex .llap>.inner,.katex .rlap>.inner{position:absolute}.katex .clap>.fix,.katex .llap>.fix,.katex .rlap>.fix{display:inline-block}.katex .llap>.inner{right:0}.katex .clap>.inner,.katex .rlap>.inner{left:0}.katex .clap>.inner>span{margin-left:-50%;margin-right:50%}.katex .rule{border:0 solid;display:inline-block;position:relative}.katex .hline,.katex .overline .overline-line,.katex .underline .underline-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .hdashline{border-bottom-style:dashed;display:inline-block;width:100%}.katex .sqrt>.root{margin-left:.2777777778em;margin-right:-.5555555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .sizing.reset-size2.size1{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .sizing.reset-size2.size3{font-size:1.1666666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .sizing.reset-size2.size4{font-size:1.3333333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .sizing.reset-size2.size6{font-size:1.6666666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .sizing.reset-size2.size10{font-size:3.4566666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .sizing.reset-size2.size11{font-size:4.1466666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .sizing.reset-size3.size1{font-size:.7142857143em}.katex .fontsize-ensurer.reset-size3.size2,.katex .sizing.reset-size3.size2{font-size:.8571428571em}.katex .fontsize-ensurer.reset-size3.size3,.katex .sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .sizing.reset-size3.size4{font-size:1.1428571429em}.katex .fontsize-ensurer.reset-size3.size5,.katex .sizing.reset-size3.size5{font-size:1.2857142857em}.katex .fontsize-ensurer.reset-size3.size6,.katex .sizing.reset-size3.size6{font-size:1.4285714286em}.katex .fontsize-ensurer.reset-size3.size7,.katex .sizing.reset-size3.size7{font-size:1.7142857143em}.katex .fontsize-ensurer.reset-size3.size8,.katex .sizing.reset-size3.size8{font-size:2.0571428571em}.katex .fontsize-ensurer.reset-size3.size9,.katex .sizing.reset-size3.size9{font-size:2.4685714286em}.katex .fontsize-ensurer.reset-size3.size10,.katex .sizing.reset-size3.size10{font-size:2.9628571429em}.katex .fontsize-ensurer.reset-size3.size11,.katex .sizing.reset-size3.size11{font-size:3.5542857143em}.katex .fontsize-ensurer.reset-size4.size1,.katex .sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .sizing.reset-size5.size1{font-size:.5555555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .sizing.reset-size5.size2{font-size:.6666666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .sizing.reset-size5.size3{font-size:.7777777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .sizing.reset-size5.size4{font-size:.8888888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .sizing.reset-size5.size6{font-size:1.1111111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .sizing.reset-size5.size7{font-size:1.3333333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .sizing.reset-size5.size10{font-size:2.3044444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .sizing.reset-size5.size11{font-size:2.7644444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .sizing.reset-size7.size1{font-size:.4166666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .sizing.reset-size7.size3{font-size:.5833333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .sizing.reset-size7.size4{font-size:.6666666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .sizing.reset-size7.size6{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .sizing.reset-size7.size10{font-size:1.7283333333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .sizing.reset-size7.size11{font-size:2.0733333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .sizing.reset-size8.size1{font-size:.3472222222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .sizing.reset-size8.size2{font-size:.4166666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .sizing.reset-size8.size3{font-size:.4861111111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .sizing.reset-size8.size4{font-size:.5555555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .sizing.reset-size8.size6{font-size:.6944444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .sizing.reset-size8.size7{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .sizing.reset-size8.size10{font-size:1.4402777778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .sizing.reset-size8.size11{font-size:1.7277777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .sizing.reset-size9.size1{font-size:.2893518519em}.katex .fontsize-ensurer.reset-size9.size2,.katex .sizing.reset-size9.size2{font-size:.3472222222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .sizing.reset-size9.size3{font-size:.4050925926em}.katex .fontsize-ensurer.reset-size9.size4,.katex .sizing.reset-size9.size4{font-size:.462962963em}.katex .fontsize-ensurer.reset-size9.size5,.katex .sizing.reset-size9.size5{font-size:.5208333333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .sizing.reset-size9.size6{font-size:.5787037037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .sizing.reset-size9.size7{font-size:.6944444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .sizing.reset-size9.size8{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .sizing.reset-size9.size10{font-size:1.2002314815em}.katex .fontsize-ensurer.reset-size9.size11,.katex .sizing.reset-size9.size11{font-size:1.4398148148em}.katex .fontsize-ensurer.reset-size10.size1,.katex .sizing.reset-size10.size1{font-size:.2410800386em}.katex .fontsize-ensurer.reset-size10.size2,.katex .sizing.reset-size10.size2{font-size:.2892960463em}.katex .fontsize-ensurer.reset-size10.size3,.katex .sizing.reset-size10.size3{font-size:.337512054em}.katex .fontsize-ensurer.reset-size10.size4,.katex .sizing.reset-size10.size4{font-size:.3857280617em}.katex .fontsize-ensurer.reset-size10.size5,.katex .sizing.reset-size10.size5{font-size:.4339440694em}.katex .fontsize-ensurer.reset-size10.size6,.katex .sizing.reset-size10.size6{font-size:.4821600771em}.katex .fontsize-ensurer.reset-size10.size7,.katex .sizing.reset-size10.size7{font-size:.5785920926em}.katex .fontsize-ensurer.reset-size10.size8,.katex .sizing.reset-size10.size8{font-size:.6943105111em}.katex .fontsize-ensurer.reset-size10.size9,.katex .sizing.reset-size10.size9{font-size:.8331726133em}.katex .fontsize-ensurer.reset-size10.size10,.katex .sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .sizing.reset-size10.size11{font-size:1.1996142719em}.katex .fontsize-ensurer.reset-size11.size1,.katex .sizing.reset-size11.size1{font-size:.2009646302em}.katex .fontsize-ensurer.reset-size11.size2,.katex .sizing.reset-size11.size2{font-size:.2411575563em}.katex .fontsize-ensurer.reset-size11.size3,.katex .sizing.reset-size11.size3{font-size:.2813504823em}.katex .fontsize-ensurer.reset-size11.size4,.katex .sizing.reset-size11.size4{font-size:.3215434084em}.katex .fontsize-ensurer.reset-size11.size5,.katex .sizing.reset-size11.size5{font-size:.3617363344em}.katex .fontsize-ensurer.reset-size11.size6,.katex .sizing.reset-size11.size6{font-size:.4019292605em}.katex .fontsize-ensurer.reset-size11.size7,.katex .sizing.reset-size11.size7{font-size:.4823151125em}.katex .fontsize-ensurer.reset-size11.size8,.katex .sizing.reset-size11.size8{font-size:.578778135em}.katex .fontsize-ensurer.reset-size11.size9,.katex .sizing.reset-size11.size9{font-size:.6945337621em}.katex .fontsize-ensurer.reset-size11.size10,.katex .sizing.reset-size11.size10{font-size:.8336012862em}.katex .fontsize-ensurer.reset-size11.size11,.katex .sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .accent>.vlist-t,.katex .op-limits>.vlist-t{text-align:center}.katex .accent .accent-body{position:relative}.katex .accent .accent-body:not(.accent-full){width:0}.katex .overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{fill:currentColor;stroke:currentColor;display:block;height:inherit;position:absolute;width:100%}.katex svg path{stroke:none}.katex svg{fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1}.katex img{border-style:none;max-height:none;max-width:none;min-height:0;min-width:0}.katex .stretchy{display:block;overflow:hidden;position:relative;width:100%}.katex .stretchy:after,.katex .stretchy:before{content:""}.katex .hide-tail{overflow:hidden;position:relative;width:100%}.katex .halfarrow-left{left:0;overflow:hidden;position:absolute;width:50.2%}.katex .halfarrow-right{overflow:hidden;position:absolute;right:0;width:50.2%}.katex .brace-left{left:0;overflow:hidden;position:absolute;width:25.1%}.katex .brace-center{left:25%;overflow:hidden;position:absolute;width:50%}.katex .brace-right{overflow:hidden;position:absolute;right:0;width:25.1%}.katex .x-arrow-pad{padding:0 .5em}.katex .cd-arrow-pad{padding:0 .55556em 0 .27778em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{border:.04em solid;box-sizing:border-box}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .sout{border-bottom-style:solid;border-bottom-width:.08em}.katex .angl{border-right:.049em solid;border-top:.049em solid;box-sizing:border-box;margin-right:.03889em}.katex .anglpad{padding:0 .03889em}.katex .eqn-num:before{content:"(" counter(katexEqnNo) ")";counter-increment:katexEqnNo}.katex .mml-eqn-num:before{content:"(" counter(mmlEqnNo) ")";counter-increment:mmlEqnNo}.katex .mtr-glue{width:50%}.katex .cd-vert-arrow{display:inline-block;position:relative}.katex .cd-label-left{display:inline-block;position:absolute;right:calc(50% + .3em);text-align:left}.katex .cd-label-right{display:inline-block;left:calc(50% + .3em);position:absolute;text-align:right}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.tag{left:0;right:auto}.katex-display.fleqn>.katex{padding-left:2em;text-align:left}body{counter-reset:katexEqnNo mmlEqnNo}
|
|
@@ -337,6 +337,30 @@ html:not([data-theme="dark"]) .turn-view > .shimmer-text {
|
|
|
337
337
|
rides a whole buffering window). */
|
|
338
338
|
.pill-busy { border-color: var(--accent); --shimmer-base: var(--panel); --shimmer-mix: 25%; }
|
|
339
339
|
|
|
340
|
+
/* ---- Boot overlay (index.html, markup and script both) — what the window shows between the HTML
|
|
341
|
+
landing and the client having a transcript. Opaque and over #app, so the empty
|
|
342
|
+
chrome never flashes through first. The 1.2s animation delay is the whole don't-flash-on-a-fast-load
|
|
343
|
+
mechanism: a normal boot is retired before this fades in, so the wait is only ever announced when
|
|
344
|
+
there is one. Measured, not guessed — a warm local boot lands at 590-930ms (dev, where the client
|
|
345
|
+
bundle is unminified and slowest), so the delay has to clear that, not merely beat a blank screen.
|
|
346
|
+
Geometry follows .cwd-hint, the mirror's other centred whole-window message. */
|
|
347
|
+
.boot {
|
|
348
|
+
position: fixed;
|
|
349
|
+
inset: 0;
|
|
350
|
+
background: var(--bg);
|
|
351
|
+
display: grid;
|
|
352
|
+
place-content: center;
|
|
353
|
+
justify-items: center;
|
|
354
|
+
gap: .5rem;
|
|
355
|
+
padding: 2rem;
|
|
356
|
+
text-align: center;
|
|
357
|
+
animation: boot-in .35s ease 1.2s both;
|
|
358
|
+
}
|
|
359
|
+
.boot-title { font-size: 1.15rem; }
|
|
360
|
+
/* min-height reserves the line the seconds count lands on, so naming the wait doesn't shift the title. */
|
|
361
|
+
.boot-why { color: var(--muted); font-size: .95rem; max-width: 32rem; min-height: 1.4em; }
|
|
362
|
+
@keyframes boot-in { from { opacity: 0; } to { opacity: 1; } }
|
|
363
|
+
|
|
340
364
|
/* ── Tooltip — the styled stand-in for native `title`, and only on FIXED EXPLANATORY copy: text
|
|
341
365
|
that teaches what a control does. Dynamic identifiers (a path, a model id, an mcp tool's full
|
|
342
366
|
name) keep the native attribute, which is better at them: they run long, they want to wrap, and
|
|
@@ -1854,7 +1854,20 @@ ${xCt("tbStreamUrl")}
|
|
|
1854
1854
|
} catch (err) { location.replace('about:blank'); }
|
|
1855
1855
|
}, 250);
|
|
1856
1856
|
});
|
|
1857
|
-
|
|
1857
|
+
// The stream attaches during this script's own eval, in <head> \u2014 before the bulb's module (and
|
|
1858
|
+
// its import graph) have run, so before a single \`tb.onMessage\` handler exists. The server now
|
|
1859
|
+
// delivers the instant the stream attaches (it holds a send for the page it opened), so a
|
|
1860
|
+
// message can beat the page's own code by the width of a CDN fetch. A page that is attached but
|
|
1861
|
+
// has not yet run is not a page with no handlers, so hold the message until the deferred module
|
|
1862
|
+
// scripts have executed (DOMContentLoaded waits for them). Page-side and bounded by one load:
|
|
1863
|
+
// the server still never buffers, and a page that is awake handles inline as before.
|
|
1864
|
+
let pageAwake = document.readyState !== 'loading';
|
|
1865
|
+
const beforeAwake = [];
|
|
1866
|
+
if (!pageAwake) document.addEventListener('DOMContentLoaded', () => {
|
|
1867
|
+
pageAwake = true;
|
|
1868
|
+
for (const run of beforeAwake.splice(0)) run();
|
|
1869
|
+
});
|
|
1870
|
+
const onMessageEvent = async (e) => {
|
|
1858
1871
|
// Wire envelope { id?, payload } (JSON \u2014 SSE-line-safe). tb:* payloads are the shim's reserved
|
|
1859
1872
|
// namespace (TB-Interrogation.md): answered by a built-in handler, never delivered to
|
|
1860
1873
|
// tb.onMessage. Each settled non-undefined return is a reply candidate \u2014 the CLI enforces the
|
|
@@ -1896,6 +1909,10 @@ ${xCt("tbStreamUrl")}
|
|
|
1896
1909
|
if (env.id !== undefined) {
|
|
1897
1910
|
try { fetch('/__send-reply', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: env.id, results, errors }) }); } catch {}
|
|
1898
1911
|
}
|
|
1912
|
+
};
|
|
1913
|
+
tbOn('message', (e) => {
|
|
1914
|
+
if (pageAwake) { void onMessageEvent(e); return; }
|
|
1915
|
+
beforeAwake.push(() => { void onMessageEvent(e); });
|
|
1899
1916
|
});
|
|
1900
1917
|
}
|
|
1901
1918
|
})();
|
|
@@ -2067,7 +2084,7 @@ ${a||this.currentRow()?.pending?"New conversation \u2014 not saved yet":`Session
|
|
|
2067
2084
|
`),user:""}}stickToBottomNextRender(){this.#r=!0}#a(s,a){s.segments=[...s.segments??[s.text],a],s.text=s.segments.join(`
|
|
2068
2085
|
|
|
2069
2086
|
`)}applyUser(s){let a=this.messages[this.messages.length-1];if(a&&a.role==="user"){this.#a(a,s.text),a.copy?a.copy.setText(a.text):a.text&&(a.copy=this.#u(a.text));return}this.#l({id:++this.#e,role:"user",text:s.text,thinking:"",tools:[]})}applyAssistant(s){let a=this.messages[this.messages.length-1]?.role!=="assistant",d=this.#l({id:++this.#e,role:"assistant",text:s.text,thinking:s.thinking,tools:s.tools.map(v=>({...v,isError:!1}))}),p=this.messages[this.messages.length-2],b=this.#h();if(d.turnCopy=p?.role==="assistant"&&p.turnCopy||this.#u(""),d.turnCopy.setText(b.prose),d.turnView=p?.role==="assistant"&&p.turnView||this.#f(),a&&d.turnView.adoptLiveOverride(this.liveTurnView.takeOverride()),d.turnView.setText(b.prose,b.user),this.#d(d,s.text),this.#p(),s.live)for(let v of s.tools)Zvn.has(v.name)&&this.openTools.add(v.id)}#d(s,a){let d=Xxe(a);d.some(p=>p.kind==="bulb")&&(s.body=d.map(p=>{if(p.kind==="md")return p.text;let b=new eie(p.source,`inline-${this.#n++}`);return this.inlineBulbs.push(b),b}))}#p(){let s=this.inlineBulbs.map(p=>p.name),a=PSt(s),d=jSt(s);this.#t=new Set(this.inlineBulbs.filter((p,b)=>a[b])),this.inlineBulbs.forEach((p,b)=>{p.setChainPosition(d[b]),p.setMounted(!a[b]||this.expandedInlineBulbs.has(p.key))})}#m(s){tEe(this.expandedInlineBulbs,s),this.#p(),this.update()}applyToolResult(s){let a=this.#w(s.id);a&&(a.result=s.content,a.isError=s.isError,a.digest=s.digest)}applyFork(s){let a=this.#b(s.events);this.#l({id:++this.#e,role:"fork",text:"",thinking:"",tools:[],fork:{count:s.count,sub:a}})}#b(s){let a=[];for(let d of s)if(d.type==="user"){let p=a[a.length-1];p&&p.role==="user"?this.#a(p,d.text):a.push({id:++this.#e,role:"user",text:d.text,thinking:"",tools:[]})}else if(d.type==="assistant")a.push({id:++this.#e,role:"assistant",text:d.text,thinking:d.thinking,tools:d.tools.map(p=>({...p,isError:!1}))});else if(d.type==="tool_result"){let p=a.flatMap(b=>b.tools).find(b=>b.id===d.id);p&&(p.result=d.content,p.isError=d.isError,p.digest=d.digest)}return a}#w(s){return this.messages.flatMap(a=>a.tools).find(a=>a.id===s)}scrollSoon(){requestAnimationFrame(()=>{this.scrollEl&&this.#r&&(this.scrollEl.scrollTop=this.scrollEl.scrollHeight)})}onScroll(){let s=this.scrollEl;s&&(this.#r=IF(s,50))}view(){return rn({class:"messages",onScroll:()=>this.onScroll(),onMounted:s=>{this.scrollEl=s}},this.parent.ready?this.renderMessages():rn({class:"note"},"Connecting\u2026"))}renderMessages(){let s=[],a=-1;for(let B of this.messages)B.role==="user"||s.length===0?(B.role==="user"&&a++,s.push({idx:Math.max(0,a),msgs:[B]})):s[s.length-1].msgs.push(B);let d=this.parent.echo,p=this.parent.draft,b=this.parent.working&&!d,v=s.flatMap((B,P)=>this.renderTurn(B.msgs,B.idx,b&&P===s.length-1));v.unshift(this.#v());let w=d?a+1:Math.max(0,a),I=b?s[s.length-1]?.msgs??[]:[],T=[...I].reverse().find(B=>B.role==="assistant")?.turnView,D=!!T&&!!this.#x(I,T,!0);return d&&v.push(this.#C(d,w)),p?v.push(this.#y(p,w,T,!D)):(this.#i.clear(),this.#o=void 0,this.parent.working?v.push(this.#y({text:"",thinking:"",tool:this.#A()},w,T,!D)):this.liveTurnView.reset()),v}#A(){for(let s=this.messages.length-1;s>=0;s--){let a=this.messages[s];if(a.role==="assistant")return a.tools.find(d=>d.result===void 0)?.name}}#v(){return rn({class:"masthead",key:"masthead"},Kee({class:"masthead-logo light",src:Kvn,alt:"typebulb"}),Kee({class:"masthead-logo dark",src:Wvn,alt:"typebulb"}),rn({class:"masthead-tagline"},"run and publish bulbs \u{1F4A1} your agent\u2019s session live here, bulbs rendered inline"))}#C(s,a){return rn({class:["bubble","user",bC(a)],key:`echo-${s.length}`},this.#g(`md-echo-${s.length}`,Yxe({id:-1,role:"user",text:s,thinking:"",tools:[]})))}#y(s,a,d,p=!0){let b=`draft-${s.text.length}-${s.thinking.length}`;s.text.length<this.#s&&this.#i.clear(),this.#s=s.text.length;let v=d??this.liveTurnView,w=v.raw(!0),I=w&&!!s.thinking,T=w?s.tool:void 0,D=!s.text.trim()&&!T,B=w&&(T?`${tie(T)}\u2026`:s.text.trim()||I?null:"working\u2026");return rn({class:["bubble","assistant","draft",bC(a)],key:b},p?v.view(!0):null,I?$ye({class:"thinking",open:this.#o??!0,onToggle:P=>{this.#o=P.currentTarget.open}},Hye(D?Ir({class:"shimmer-text shimmer-slow"},"thinking\u2026"):"thinking"),this.#S(s.thinking)):null,s.text.trim()?this.#I(b,s.text):null,B?rn({class:"draft-wait shimmer-text shimmer-slow"},B):null)}#I(s,a){let d=Xxe(a);return d.some(p=>p.kind==="bulb")?d.map((p,b)=>p.kind==="md"?p.text.trim()?this.#g(`${s}-md-${b}`,tD(p.text)):null:this.#T(p.source,b)):this.#g(s,tD(a))}#k(s,a){return{onScroll:d=>{let p=d.currentTarget;this.#i.set(s,{top:p.scrollTop,stuck:IF(p,50)})},onMounted:d=>{a?.(d);let p=this.#i.get(s);d.scrollTop=p&&!p.stuck?p.top:d.scrollHeight}}}#S(s){return r6({key:`think-${s.length}`,...this.#k(-1)},s)}#T(s,a){let d=EH(s),p=s.split(`
|
|
2070
|
-
`).length;return rn({class:"md",key:`draft-bulb-${a}`},rn({class:"embed bulb-streaming"},rn({class:"bulb-streaming-head"},Ir("\u{1F4A1} "),Ir({class:"shimmer-text shimmer-slow"},`${d?`${d} \u2014 `:""}writing\u2026`),Ir({class:"bulb-streaming-count"},`${p} lines`)),rn({class:["bulb-code","md"],key:`bulb-code-${s.length}`,...this.#k(a,b=>{b.innerHTML=eD.render(CH(s))})})))}renderTurn(s,a,d){let p=s.filter(D=>D.role==="user"||D.text||D.body),b=p.find(D=>D.role==="assistant"&&(!!D.text||!!D.body)),v=[...p].reverse().find(D=>D.role==="assistant"&&(!!D.text||!!D.body)),w=[...s].reverse().find(D=>D.role==="assistant"),I=(v??w)?.turnView;if(!I)return this.#_(s,a);let T=this.#x(s,I,d);return I.raw(d)?this.#M(s,a,T,I,d):I.summary?this.#B(a,p,b,I,d):this.#D(s,a,p,T,v,I,d)}#x(s,a,d){return a.raw(d)?s.find(p=>p.role!=="user"):s.find(p=>p.role==="assistant"&&(!!p.text||!!p.body))}#_(s,a){return s.map(d=>d.role==="fork"?this.forkStub(d,a):this.bubble(d,a))}#M(s,a,d,p,b){return s.map(v=>v.role==="fork"?this.forkStub(v,a,v===d?p:void 0,b):this.bubble(v,a,void 0,!0,v===d?p:void 0,!0,b))}#D(s,a,d,p,b,v,w){let I=d.map(T=>{let D=T.role==="user"?T.copy:T===b?T.turnCopy:void 0;return this.bubble(T,a,D??null,!0,T===p?v:void 0,!1,w)});return p||w||I.splice(d[0]?.role==="user"?1:0,0,this.#L(s,a,v)),I}#B(s,a,d,p,b){return a.flatMap(v=>v.role==="assistant"?v===d?[this.#R(v,s,p.summary,p)]:[]:[this.bubble(v,s,v.copy??null,!0,void 0,!1,b)])}#L(s,a,d){return rn({class:["bubble","assistant",bC(a)],key:`no-prose-${a}`},d.view(!1),rn({class:"no-prose"},r5n(s)))}#N(s,a,d,p,b,v,w){let I=d.has(p);return rn({class:["bubble","assistant",bC(a)],key:s},v??null,rn({class:"turn-summary",onClick:()=>{tEe(d,p),this.update()}},eEe(I),Ir({class:"turn-summary-text"},b)),w??null)}forkStub(s,a,d,p=!1){let b=s.fork,v=this.expandedForks.has(s.id)?rn({class:"fork-body"},b.sub.map(I=>this.bubble(I,a,void 0,!1))):void 0,w=[Vvn(),` ${b.count} message${b.count===1?"":"s"} on an abandoned branch`];return this.#N(`fork-${s.id}`,a,this.expandedForks,s.id,w,d?.view(p),v)}bubble(s,a,d=s.copy,p=!0,b,v=!0,w=!1){let I=s.role==="assistant"&&!s.text&&!s.thinking&&s.tools.length>0;return rn({class:["bubble",s.role,I?"tools-only":"",p?bC(a):""],key:s.id},b?b.view(w):null,v&&s.thinking?$ye({class:"thinking"},Hye("thinking"),r6(s.thinking)):null,this.#j(s),s.role==="user"?this.#O(s):null,v?s.tools.map(T=>this.tool(T)):null,this.#E(d))}#E(s){return s?rn({class:"bubble-controls"},s.view()):null}#R(s,a,d,p){return rn({class:["bubble","assistant",bC(a)],key:`summary-${s.id}`},p.view(!1),this.#g(`md-summary-${s.id}-${d.length}`,tD(d)),this.#E(p.copy))}#O(s){let d=[...s.text.matchAll(zvn)].map(p=>p[1]).map(p=>this.#P(p)).filter(p=>!!p);return d.length?rn({class:"paste-thumbs"},d.map(p=>Kee({class:"paste-thumb",src:p,alt:"pasted image"}))):null}#P(s){let a=this.#c.get(s);if(!(a==="pending"||a==="failed")){if(a)return a;this.#c.set(s,"pending"),(async()=>{try{let d=await tb.server.composerPasteRead(s);this.#c.set(s,d?.ok?`data:${d.mime};base64,${d.base64}`:"failed"),d?.ok&&(this.update(),this.scrollSoon())}catch{this.#c.set(s,"failed")}})()}}#j(s){return s.body?s.body.flatMap((a,d)=>typeof a=="string"?a.trim()?[this.#g(`md-${s.id}-${d}`,tD(a))]:[]:this.#F(a)):s.text?this.#g(`md-${s.id}`,s.role==="user"?Yxe(s):tD(s.text)):null}#F(s){if(!this.#t.has(s))return[s.view()];let a=this.expandedInlineBulbs.has(s.key),d=rn({class:"md",key:`fold-${s.key}`},rn({class:"bulb-fold",onClick:()=>this.#m(s.key)},eEe(a),Ir({class:"bulb-fold-text"},`${s.name??"bulb"} \u2014 Version ${s.chainPosition}`),Ir({class:"bulb-fold-app"},"\u{1F4A1}")));return a?[d,s.view()]:[d]}#g(s,a){return rn({class:"md",key:s,onMounted:a})}tool(s){let a=this.openTools.has(s.id),d=e5n(s.input),p=GSt(s),b=d?w6t(d,this.parent.cwd):Xvn(Yvn(s.input));return rn({class:["tool",s.isError?"err":""]},rn({class:"tool-head"},rn({class:"tool-label"},Ir({class:"tool-name",...s.name.startsWith("mcp__")?{title:s.name}:{}},tie(s.name)),b?d?gC({class:"tool-sum link",title:d,onClick:v=>{v.preventDefault(),tb.server.openFile(d)}},b):Ir({class:"tool-sum"},b):null),s.result===void 0?Ir({class:"tool-run"},"\u2026"):null),s.result!==void 0?rn({class:["tool-digest",a?"open":""],onClick:()=>{tEe(this.openTools,s.id),this.update()}},eEe(a),(p?n5n(p):s.digest)||"(no output)"):null,a?rn({class:"tool-card"},this.toolBody(s),s.result!==void 0&&(s.isError||!(p||qSt(s).length&&s.result.trim()===s.digest))?r6({class:"tool-out"},s.result.slice(0,4e3)):null):null)}toolBody(s){let a=GSt(s);if(a)return rn({class:"todo-list"},a.map(b=>rn({class:["todo",b.status??""]},Ir({class:"todo-icon"},b.status==="completed"?"\u2714":b.status==="in_progress"?"\u25FC":"\u25FB"),Ir({class:"todo-text"},b.content??""))));let d=qSt(s);if(d.length)return rn({class:"tool-in"},d.map(HSt));let p=t5n(s);if(!p){let b=Object.entries(s.input??{});return b.length?rn({class:"tool-in"},b.map(([v,w])=>rn({class:"tool-field"},Ir({class:"tool-key"},`${v}:`),typeof w=="string"&&QSt(w)?HSt(w):r6({class:"tool-val"},typeof w=="string"?w:JSON.stringify(w,null,2))))):null}return rn({class:"diff"},...p.flatMap((b,v)=>[p.length>1?rn({class:"diff-step"},`edit ${v+1}/${p.length}`):null,b.old?r6({class:"diff-old"},String(b.old)):null,b.new?r6({class:"diff-new"},String(b.new)):null]))}};var rie=class extends Ku{ready=!1;cwd="";sessionId=null;sessionPicker=new Wre;tokenPill=new Yre;bulbsPill=new Xre;diffPill=new Zre;messageList=new nie;tokens={in:0,out:0,cached:0,cacheCreate:0};cost=0;stats=null;working=!1;latestModel=null;driverModel=null;busy=[];ownPid=0;pills;composer;draft=null;echo=null;#e=0;#n=!1;#t=!1;#r;#i=null;#s;#o;constructor(s){super(),this.pills=s.pills,this.composer=s.composer,this.#r=s.title,this.#s=s.overlays??[],this.#o=s.onPollTick}onAttached(){this.#t||(this.#t=!0,m6t(),this.init())}closePopups(s){this.sessionPicker!==s&&this.sessionPicker.close(),this.bulbsPill!==s&&this.bulbsPill.close(),this.diffPill!==s&&this.diffPill.close();for(let a of this.pills)a!==s&&a.close?.()}async init(){let s=await tb.server.info();this.cwd=s.cwd,this.ownPid=s.pid??0,this.#i=s.elsewhere??null,this.composer&&(this.composer.enabled=!!s.composer),this.ready=!0,this.updateTitle(),this.update(),this.pump()}updateTitle(){let s=Jye(this.cwd),a=this.sessionPicker.currentPreview(),d=Zee(a,40);document.title=d&&s?`${d} \u2014 ${s}`:d||(s?`${this.#r} \u2014 ${s}`:this.#r)}refreshSessions(){this.sessionPicker.loadSessions().then(()=>this.update()).catch(()=>{})}pump(){if(this.#n)return;this.#n=!0;let s=async()=>{try{let{events:a,cursor:d,working:p,latestModel:b,composer:v,busy:w}=await tb.server.poll(this.#e);this.#e=d;for(let F of a)this.apply(F);let I=p!==this.working;this.working=p;let T=Array.isArray(w)?w:[],D=T.join(`
|
|
2087
|
+
`).length;return rn({class:"md",key:`draft-bulb-${a}`},rn({class:"embed bulb-streaming"},rn({class:"bulb-streaming-head"},Ir("\u{1F4A1} "),Ir({class:"shimmer-text shimmer-slow"},`${d?`${d} \u2014 `:""}writing\u2026`),Ir({class:"bulb-streaming-count"},`${p} lines`)),rn({class:["bulb-code","md"],key:`bulb-code-${s.length}`,...this.#k(a,b=>{b.innerHTML=eD.render(CH(s))})})))}renderTurn(s,a,d){let p=s.filter(D=>D.role==="user"||D.text||D.body),b=p.find(D=>D.role==="assistant"&&(!!D.text||!!D.body)),v=[...p].reverse().find(D=>D.role==="assistant"&&(!!D.text||!!D.body)),w=[...s].reverse().find(D=>D.role==="assistant"),I=(v??w)?.turnView;if(!I)return this.#_(s,a);let T=this.#x(s,I,d);return I.raw(d)?this.#M(s,a,T,I,d):I.summary?this.#B(a,p,b,I,d):this.#D(s,a,p,T,v,I,d)}#x(s,a,d){return a.raw(d)?s.find(p=>p.role!=="user"):s.find(p=>p.role==="assistant"&&(!!p.text||!!p.body))}#_(s,a){return s.map(d=>d.role==="fork"?this.forkStub(d,a):this.bubble(d,a))}#M(s,a,d,p,b){return s.map(v=>v.role==="fork"?this.forkStub(v,a,v===d?p:void 0,b):this.bubble(v,a,void 0,!0,v===d?p:void 0,!0,b))}#D(s,a,d,p,b,v,w){let I=d.map(T=>{let D=T.role==="user"?T.copy:T===b?T.turnCopy:void 0;return this.bubble(T,a,D??null,!0,T===p?v:void 0,!1,w)});return p||w||I.splice(d[0]?.role==="user"?1:0,0,this.#L(s,a,v)),I}#B(s,a,d,p,b){return a.flatMap(v=>v.role==="assistant"?v===d?[this.#R(v,s,p.summary,p)]:[]:[this.bubble(v,s,v.copy??null,!0,void 0,!1,b)])}#L(s,a,d){return rn({class:["bubble","assistant",bC(a)],key:`no-prose-${a}`},d.view(!1),rn({class:"no-prose"},r5n(s)))}#N(s,a,d,p,b,v,w){let I=d.has(p);return rn({class:["bubble","assistant",bC(a)],key:s},v??null,rn({class:"turn-summary",onClick:()=>{tEe(d,p),this.update()}},eEe(I),Ir({class:"turn-summary-text"},b)),w??null)}forkStub(s,a,d,p=!1){let b=s.fork,v=this.expandedForks.has(s.id)?rn({class:"fork-body"},b.sub.map(I=>this.bubble(I,a,void 0,!1))):void 0,w=[Vvn(),` ${b.count} message${b.count===1?"":"s"} on an abandoned branch`];return this.#N(`fork-${s.id}`,a,this.expandedForks,s.id,w,d?.view(p),v)}bubble(s,a,d=s.copy,p=!0,b,v=!0,w=!1){let I=s.role==="assistant"&&!s.text&&!s.thinking&&s.tools.length>0;return rn({class:["bubble",s.role,I?"tools-only":"",p?bC(a):""],key:s.id},b?b.view(w):null,v&&s.thinking?$ye({class:"thinking"},Hye("thinking"),r6(s.thinking)):null,this.#j(s),s.role==="user"?this.#O(s):null,v?s.tools.map(T=>this.tool(T)):null,this.#E(d))}#E(s){return s?rn({class:"bubble-controls"},s.view()):null}#R(s,a,d,p){return rn({class:["bubble","assistant",bC(a)],key:`summary-${s.id}`},p.view(!1),this.#g(`md-summary-${s.id}-${d.length}`,tD(d)),this.#E(p.copy))}#O(s){let d=[...s.text.matchAll(zvn)].map(p=>p[1]).map(p=>this.#P(p)).filter(p=>!!p);return d.length?rn({class:"paste-thumbs"},d.map(p=>Kee({class:"paste-thumb",src:p,alt:"pasted image"}))):null}#P(s){let a=this.#c.get(s);if(!(a==="pending"||a==="failed")){if(a)return a;this.#c.set(s,"pending"),(async()=>{try{let d=await tb.server.composerPasteRead(s);this.#c.set(s,d?.ok?`data:${d.mime};base64,${d.base64}`:"failed"),d?.ok&&(this.update(),this.scrollSoon())}catch{this.#c.set(s,"failed")}})()}}#j(s){return s.body?s.body.flatMap((a,d)=>typeof a=="string"?a.trim()?[this.#g(`md-${s.id}-${d}`,tD(a))]:[]:this.#F(a)):s.text?this.#g(`md-${s.id}`,s.role==="user"?Yxe(s):tD(s.text)):null}#F(s){if(!this.#t.has(s))return[s.view()];let a=this.expandedInlineBulbs.has(s.key),d=rn({class:"md",key:`fold-${s.key}`},rn({class:"bulb-fold",onClick:()=>this.#m(s.key)},eEe(a),Ir({class:"bulb-fold-text"},`${s.name??"bulb"} \u2014 Version ${s.chainPosition}`),Ir({class:"bulb-fold-app"},"\u{1F4A1}")));return a?[d,s.view()]:[d]}#g(s,a){return rn({class:"md",key:s,onMounted:a})}tool(s){let a=this.openTools.has(s.id),d=e5n(s.input),p=GSt(s),b=d?w6t(d,this.parent.cwd):Xvn(Yvn(s.input));return rn({class:["tool",s.isError?"err":""]},rn({class:"tool-head"},rn({class:"tool-label"},Ir({class:"tool-name",...s.name.startsWith("mcp__")?{title:s.name}:{}},tie(s.name)),b?d?gC({class:"tool-sum link",title:d,onClick:v=>{v.preventDefault(),tb.server.openFile(d)}},b):Ir({class:"tool-sum"},b):null),s.result===void 0?Ir({class:"tool-run"},"\u2026"):null),s.result!==void 0?rn({class:["tool-digest",a?"open":""],onClick:()=>{tEe(this.openTools,s.id),this.update()}},eEe(a),(p?n5n(p):s.digest)||"(no output)"):null,a?rn({class:"tool-card"},this.toolBody(s),s.result!==void 0&&(s.isError||!(p||qSt(s).length&&s.result.trim()===s.digest))?r6({class:"tool-out"},s.result.slice(0,4e3)):null):null)}toolBody(s){let a=GSt(s);if(a)return rn({class:"todo-list"},a.map(b=>rn({class:["todo",b.status??""]},Ir({class:"todo-icon"},b.status==="completed"?"\u2714":b.status==="in_progress"?"\u25FC":"\u25FB"),Ir({class:"todo-text"},b.content??""))));let d=qSt(s);if(d.length)return rn({class:"tool-in"},d.map(HSt));let p=t5n(s);if(!p){let b=Object.entries(s.input??{});return b.length?rn({class:"tool-in"},b.map(([v,w])=>rn({class:"tool-field"},Ir({class:"tool-key"},`${v}:`),typeof w=="string"&&QSt(w)?HSt(w):r6({class:"tool-val"},typeof w=="string"?w:JSON.stringify(w,null,2))))):null}return rn({class:"diff"},...p.flatMap((b,v)=>[p.length>1?rn({class:"diff-step"},`edit ${v+1}/${p.length}`):null,b.old?r6({class:"diff-old"},String(b.old)):null,b.new?r6({class:"diff-new"},String(b.new)):null]))}};var rie=class extends Ku{ready=!1;cwd="";sessionId=null;sessionPicker=new Wre;tokenPill=new Yre;bulbsPill=new Xre;diffPill=new Zre;messageList=new nie;tokens={in:0,out:0,cached:0,cacheCreate:0};cost=0;stats=null;working=!1;latestModel=null;driverModel=null;busy=[];ownPid=0;pills;composer;draft=null;echo=null;#e=0;#n=!1;#t=!1;#r;#i=null;#s;#o;constructor(s){super(),this.pills=s.pills,this.composer=s.composer,this.#r=s.title,this.#s=s.overlays??[],this.#o=s.onPollTick}onAttached(){this.#t||(this.#t=!0,m6t(),this.init())}closePopups(s){this.sessionPicker!==s&&this.sessionPicker.close(),this.bulbsPill!==s&&this.bulbsPill.close(),this.diffPill!==s&&this.diffPill.close();for(let a of this.pills)a!==s&&a.close?.()}async init(){let s=await tb.server.info();this.cwd=s.cwd,this.ownPid=s.pid??0,this.#i=s.elsewhere??null,this.composer&&(this.composer.enabled=!!s.composer),this.ready=!0,document.getElementById("boot")?.remove(),this.updateTitle(),this.update(),this.pump()}updateTitle(){let s=Jye(this.cwd),a=this.sessionPicker.currentPreview(),d=Zee(a,40);document.title=d&&s?`${d} \u2014 ${s}`:d||(s?`${this.#r} \u2014 ${s}`:this.#r)}refreshSessions(){this.sessionPicker.loadSessions().then(()=>this.update()).catch(()=>{})}pump(){if(this.#n)return;this.#n=!0;let s=async()=>{try{let{events:a,cursor:d,working:p,latestModel:b,composer:v,busy:w}=await tb.server.poll(this.#e);this.#e=d;for(let F of a)this.apply(F);let I=p!==this.working;this.working=p;let T=Array.isArray(w)?w:[],D=T.join(`
|
|
2071
2088
|
`)!==this.busy.join(`
|
|
2072
2089
|
`);this.busy=T;let B=b!==this.latestModel;this.latestModel=b??null;let P=this.composer&&v?this.composer.syncFromPoll(v):!1;(a.length||I||B||P||D)&&this.update(),(a.length||P)&&this.messageList.scrollSoon(),this.#o?.()}catch(a){console.error("[mirror] poll failed",a)}setTimeout(s,600)};s()}apply(s){switch(s.type){case"cleared":this.messageList.clear(),this.tokens={in:0,out:0,cached:0,cacheCreate:0},this.cost=0;break;case"session":this.sessionId=s.sessionId,s.sessionId&&!this.sessionPicker.currentRow()?this.refreshSessions():this.updateTitle();break;case"user":this.messageList.applyUser(s);break;case"assistant":this.messageList.applyAssistant(s);break;case"tool_result":this.messageList.applyToolResult(s);break;case"fork":this.messageList.applyFork(s);break;case"usage":this.tokens={in:s.in,out:s.out,cached:s.cached,cacheCreate:s.cacheCreate},this.cost+=s.cost??0;break}}view(){return rn({class:"app"},rn({class:"chat"},this.diffPill.viewing?this.diffPill.docView():this.messageList.view(),this.#c(),...this.#s.map(s=>s()),this.statusbar()),this.composer?.enabled?this.composer.view():null)}#c(){let s=this.#i;return!this.ready||this.sessionId!==null||!s?null:rn({class:"cwd-hint"},rn({class:"cwd-hint-head"},`No sessions for ${this.cwd}`),rn(`${s.count} session${s.count===1?"":"s"} exist for `,Ir({class:"cwd-hint-dir"},s.dir)," \u2014 this mirror was likely launched from a subfolder. Relaunch it from the project root."))}statusbar(){return rn({class:"statusbar"},rn({class:["statusbar-actions",this.diffPill.viewing?"doc-open":""]},this.diffPill.view(),...this.pills.map(s=>s.view()),this.tokenPill.view(),this.sessionPicker.view(),this.bulbsPill.view()))}};var iie=class extends Ku{get parent(){return this.ctx.parent}view(){let s=this.parent.latestModel;return s?Ir({class:"model-wrap"},Ir({class:"token","data-tip":`Model: ${s}`},s)):Ir({class:"model-wrap"})}};document.title="Codex Mirror";new l6t({root:new rie({title:"Codex Mirror",pills:[new iie]}),id:"app"});
|
|
2073
2090
|
/*! Bundled license information:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|