typebulb 0.45.2 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -4
- package/SKILL.md +23 -6
- package/dist/agents/claude/client.js +138 -70
- package/dist/agents/pi/client.js +138 -70
- package/dist/agents/pi/matchu-patchu.ts +1 -1
- package/dist/ai/protocol.d.ts +4 -0
- package/dist/ai/protocol.d.ts.map +1 -1
- package/dist/dts/tbTypings.d.ts +2 -2
- package/dist/dts/tbTypings.d.ts.map +1 -1
- package/dist/dts/tbTypings.js +18 -7
- package/dist/dts/tbTypings.js.map +1 -1
- package/dist/index.js +314 -237
- package/dist/render.js +125 -57
- package/dist/servers.js +137 -70
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ typebulb send <file> tb:snapshot Print the live page's rendered outline (roles,
|
|
|
46
46
|
typebulb send <file> tb:rect … Print a named control's rect ('tb:rect button "Pass"' → {x,y,width,height} + viewport)
|
|
47
47
|
typebulb send <file> tb:click … Click a control by role+name ('tb:click button "Pass"'); the reply is a fresh snapshot
|
|
48
48
|
typebulb send <file> tb:set … Set a form control ('tb:set combobox "level" = hard'), firing input+change
|
|
49
|
+
typebulb send <file> tb:png … Save the live canvas as PNG, print its path (sole canvas needs no name; 'tb:png "<name>"' among several)
|
|
49
50
|
typebulb get <file> <kind> Print one block's content (data, insight, code, …) to stdout
|
|
50
51
|
typebulb put <file> <k>=<src> Write a file's (or stdin's) content into a block, surgically
|
|
51
52
|
typebulb pull <url|file> Fetch a bulb from typebulb.com into typebulbs/u/<user>/<slug>.bulb.md
|
|
@@ -197,7 +198,7 @@ everywhere.
|
|
|
197
198
|
| `tb.copy(text)` | Copy text to the clipboard | |
|
|
198
199
|
| `tb.url()` | Get the bulb URL (the served localhost URL, locally) | |
|
|
199
200
|
| `tb.models()` | List available AI models (for dynamic model selectors); the `.env` default is flagged (`default: true`); returns `[]` when embedded (no host AI) | |
|
|
200
|
-
| `tb.
|
|
201
|
+
| `tb.aiAccess()` | What backs `tb.ai` — `'own' \| 'courtesy' \| 'none'` | |
|
|
201
202
|
| `tb.log(...)` | Print to the CLI's stdout (read back with `typebulb logs`); falls back to the browser console when no CLI serves the page | |
|
|
202
203
|
| `tb.onMessage(cb)` | Receive a value pushed in from the terminal by `typebulb send`; a non-`undefined` return becomes the reply `send --wait` prints — inert when embedded (no sender) | |
|
|
203
204
|
| `tb.fs.read/readBytes/write` | Read and write local files | yes |
|
|
@@ -272,6 +273,7 @@ That one launch *is* the loop: the server watches the file, so every save recomp
|
|
|
272
273
|
- **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. Needs exactly one page open, 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>`.
|
|
273
274
|
- **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).
|
|
274
275
|
- **A page must be open** — the CLI runs no browser of its own, so every client-side check waits on a real window (and `--no-open` means there isn't one). `send` says which case it is: nobody has ever connected (share the link), or a page dropped and hasn't returned (it's stale — reload it). Never open a window at the user: the server logs `[page] connected` when a page attaches, so end your turn with the link, arming `typebulb wait <file> --match "[page] connected"` in the background first — the user opening the page is your wake-up.
|
|
276
|
+
- **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 names it). A WebGL/WebGPU canvas without `preserveDrawingBuffer` reads back blank outside its own frame — capture during the draw instead.
|
|
275
277
|
|
|
276
278
|
### Emitting a server-only bulb
|
|
277
279
|
|
|
@@ -302,14 +304,14 @@ The host owns a bulb's **width**; you own its **height**.
|
|
|
302
304
|
- **Self-testing a local bulb** — To confirm a bulb works, run it, instrument with `tb.log(...)`, and read it back with `typebulb logs`. That's the loop to verify behaviour without asking the user to copy-paste console output. `tb.fs.write(...)` is handy for dumping large outputs.
|
|
303
305
|
- **Self-testing client code** — gate checks behind `tb.onMessage(m => { if (m === 'selftest') return run() })`, trigger with `typebulb send <file> selftest --wait`, and assert on the JSON reply — see [Interrogating the live page](#interrogating-the-live-page).
|
|
304
306
|
- **Probe handlers go in the first draft** — the selftest, readback, and set handlers you'll want later: adding one is an edit, and that hot reload destroys the very state you meant to inspect.
|
|
305
|
-
- **Read a canvas back as an image** — return a bare `canvas.toDataURL()` string from a handler
|
|
307
|
+
- **Read a canvas back as an image** — `typebulb send <file> tb:png`: the visual-verification loop for canvas/WebGPU bulbs (see [Interrogating the live page](#interrogating-the-live-page)). The authored form — return a bare `canvas.toDataURL()` string from a handler — remains for what the verb can't reach, like capturing a WebGL frame during its own draw.
|
|
306
308
|
- **Testing a `server.ts` export directly** — `typebulb call <file> <fn> [arg…]` boots `server.ts`, invokes one export, and prints its return as JSON to stdout (logs/errors to stderr, so `… | jq` works). Args after `<fn>` are JSON-or-string; `--args '<json-array>'` (or `--args -` for stdin) escapes tricky quoting. Needs `--trust`.
|
|
307
309
|
- **Mount to the container your `index.html` declares.** The corpus convention is `<div id="root"></div>` with `createRoot(document.getElementById("root")!)`.
|
|
308
310
|
- **All imports at the top of `code.tsx`, and every bare import declared in `config.json` `dependencies`.** Bare imports (`react`, `d3`, `three`, …) resolve from a CDN — no install step — but declaring them is **required, not optional**: an import missing from `dependencies` is a lint error that fails `npx typebulb check` *and* refuses to run. Declaring is also what pins versions and lets `check` fetch type defs (without it you get errors like `TS2875: react/jsx-runtime`). So a bulb with imports must carry a `config.json` with a matching `dependencies` entry for each.
|
|
309
311
|
- **Theme-aware styling.** Style off CSS variables / `currentColor` so the bulb reads correctly in both light and dark; the host sets the theme.
|
|
310
312
|
- **Native dropdowns.** Style `select, option { background: Canvas; color: CanvasText }` (system colors track the host's `color-scheme`) — a `transparent` `<select>` otherwise opens an unthemed popup, white-on-white in dark mode.
|
|
311
313
|
- **`tb.ai()` takes more than the basics** — the full shape is `tb.ai({ messages, system?, effort?, provider?, model?, webSearch? })` → `Promise<{ text }>`. `webSearch` defaults **on** in the CLI (you supply your own key); pass `webSearch: false` to turn it off. For token-by-token output use `tb.ai.stream(...)` (see [`tb.ai()` § Streaming](#streaming)).
|
|
312
|
-
- **Gate AI-heavy bulbs on `tb.
|
|
314
|
+
- **Gate AI-heavy bulbs on `tb.aiAccess()`** — `'own'` / `'courtesy'` / `'none'`, never re-derived from `tb.mode` or the model list (see [AI access](#ai-access)).
|
|
313
315
|
- **`tb.theme` drives the `html[data-theme]` attribute** — style off that selector (`html[data-theme="dark"] { … }`); don't read `tb.theme` to branch your rendering.
|
|
314
316
|
- **`color-scheme` is set for you** — the host always applies `html[data-theme="dark"] { color-scheme: dark }` / `html[data-theme="light"] { color-scheme: light }` on top of your `styles.css`.
|
|
315
317
|
- **Math (KaTeX) renders in your replies** — write inline `$…$` / display `$$…$$` (prefer `$y = x^2$` over inline-code or a Unicode `y = x²`). The mirror's KaTeX renders only in prose and doesn't reach inside a fenced block (bulb, mermaid, svg, code).
|
|
@@ -371,7 +373,7 @@ Which must be declared in the dependencies section:
|
|
|
371
373
|
|
|
372
374
|
Typebulb has a package resolver that will load and cache these packages from `esm.sh` when the bulb runs.
|
|
373
375
|
|
|
374
|
-
##
|
|
376
|
+
## AI Models
|
|
375
377
|
|
|
376
378
|
Three ways to use models from different providers in typebulb:
|
|
377
379
|
|
|
@@ -440,6 +442,21 @@ for await (const c of tb.ai.stream({ messages })) {
|
|
|
440
442
|
|
|
441
443
|
Breaking the loop stops the stream; same options as `tb.ai()`. **`kind: "reasoning"` chunks require `effort: 1-3` and a thinking-capable model**.
|
|
442
444
|
|
|
445
|
+
### AI access
|
|
446
|
+
|
|
447
|
+
`tb.aiAccess()` reports what backs `tb.ai`, typed as the ambient `AiAccess`.
|
|
448
|
+
|
|
449
|
+
| Value | What it means | What a bulb does |
|
|
450
|
+
|-------|---------------|------------------|
|
|
451
|
+
| `'own'` | The user's own keys, or their own local model server | Run everything |
|
|
452
|
+
| `'courtesy'` | typebulb.com's quota-limited courtesy model | Fine for a call or two; a bulb that makes many (an agent loop, a model-vs-model game) shows a "use your own keys" notice instead of the run controls |
|
|
453
|
+
| `'none'` | No AI at all — the CLI with no keys, or an embedded bulb | Say so; don't leave dead controls on screen |
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
const [access, setAccess] = useState<AiAccess>("own");
|
|
457
|
+
useEffect(() => { tb.aiAccess().then(setAccess); }, []);
|
|
458
|
+
```
|
|
459
|
+
|
|
443
460
|
### Ollama & OpenAI-compatible endpoints
|
|
444
461
|
|
|
445
462
|
`provider: "ollama"` is the zero-config local preset: it talks to a local [Ollama](https://ollama.com) server over its OpenAI-compatible endpoint — no API key, defaults to `http://localhost:11434` (override with `OLLAMA_HOST`). `typebulb models` lists your installed Ollama models alongside cloud ones.
|
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 (embedded, 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/embed 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.
|
|
4
|
+
version: 0.47.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
> Generated from typebulb v0.
|
|
7
|
+
> Generated from typebulb v0.47.0. `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
|
|
|
@@ -54,6 +54,7 @@ typebulb send <file> tb:snapshot Print the live page's rendered outline (roles,
|
|
|
54
54
|
typebulb send <file> tb:rect … Print a named control's rect ('tb:rect button "Pass"' → {x,y,width,height} + viewport)
|
|
55
55
|
typebulb send <file> tb:click … Click a control by role+name ('tb:click button "Pass"'); the reply is a fresh snapshot
|
|
56
56
|
typebulb send <file> tb:set … Set a form control ('tb:set combobox "level" = hard'), firing input+change
|
|
57
|
+
typebulb send <file> tb:png … Save the live canvas as PNG, print its path (sole canvas needs no name; 'tb:png "<name>"' among several)
|
|
57
58
|
typebulb get <file> <kind> Print one block's content (data, insight, code, …) to stdout
|
|
58
59
|
typebulb put <file> <k>=<src> Write a file's (or stdin's) content into a block, surgically
|
|
59
60
|
typebulb pull <url|file> Fetch a bulb from typebulb.com into typebulbs/u/<user>/<slug>.bulb.md
|
|
@@ -205,7 +206,7 @@ everywhere.
|
|
|
205
206
|
| `tb.copy(text)` | Copy text to the clipboard | |
|
|
206
207
|
| `tb.url()` | Get the bulb URL (the served localhost URL, locally) | |
|
|
207
208
|
| `tb.models()` | List available AI models (for dynamic model selectors); the `.env` default is flagged (`default: true`); returns `[]` when embedded (no host AI) | |
|
|
208
|
-
| `tb.
|
|
209
|
+
| `tb.aiAccess()` | What backs `tb.ai` — `'own' \| 'courtesy' \| 'none'` | |
|
|
209
210
|
| `tb.log(...)` | Print to the CLI's stdout (read back with `typebulb logs`); falls back to the browser console when no CLI serves the page | |
|
|
210
211
|
| `tb.onMessage(cb)` | Receive a value pushed in from the terminal by `typebulb send`; a non-`undefined` return becomes the reply `send --wait` prints — inert when embedded (no sender) | |
|
|
211
212
|
| `tb.fs.read/readBytes/write` | Read and write local files | yes |
|
|
@@ -280,6 +281,7 @@ That one launch *is* the loop: the server watches the file, so every save recomp
|
|
|
280
281
|
- **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. Needs exactly one page open, 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>`.
|
|
281
282
|
- **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).
|
|
282
283
|
- **A page must be open** — the CLI runs no browser of its own, so every client-side check waits on a real window (and `--no-open` means there isn't one). `send` says which case it is: nobody has ever connected (share the link), or a page dropped and hasn't returned (it's stale — reload it). Never open a window at the user: the server logs `[page] connected` when a page attaches, so end your turn with the link, arming `typebulb wait <file> --match "[page] connected"` in the background first — the user opening the page is your wake-up.
|
|
284
|
+
- **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 names it). A WebGL/WebGPU canvas without `preserveDrawingBuffer` reads back blank outside its own frame — capture during the draw instead.
|
|
283
285
|
|
|
284
286
|
### Emitting a server-only bulb
|
|
285
287
|
|
|
@@ -310,14 +312,14 @@ The host owns a bulb's **width**; you own its **height**.
|
|
|
310
312
|
- **Self-testing a local bulb** — To confirm a bulb works, run it, instrument with `tb.log(...)`, and read it back with `typebulb logs`. That's the loop to verify behaviour without asking the user to copy-paste console output. `tb.fs.write(...)` is handy for dumping large outputs.
|
|
311
313
|
- **Self-testing client code** — gate checks behind `tb.onMessage(m => { if (m === 'selftest') return run() })`, trigger with `typebulb send <file> selftest --wait`, and assert on the JSON reply — see [Interrogating the live page](#interrogating-the-live-page).
|
|
312
314
|
- **Probe handlers go in the first draft** — the selftest, readback, and set handlers you'll want later: adding one is an edit, and that hot reload destroys the very state you meant to inspect.
|
|
313
|
-
- **Read a canvas back as an image** — return a bare `canvas.toDataURL()` string from a handler
|
|
315
|
+
- **Read a canvas back as an image** — `typebulb send <file> tb:png`: the visual-verification loop for canvas/WebGPU bulbs (see [Interrogating the live page](#interrogating-the-live-page)). The authored form — return a bare `canvas.toDataURL()` string from a handler — remains for what the verb can't reach, like capturing a WebGL frame during its own draw.
|
|
314
316
|
- **Testing a `server.ts` export directly** — `typebulb call <file> <fn> [arg…]` boots `server.ts`, invokes one export, and prints its return as JSON to stdout (logs/errors to stderr, so `… | jq` works). Args after `<fn>` are JSON-or-string; `--args '<json-array>'` (or `--args -` for stdin) escapes tricky quoting. Needs `--trust`.
|
|
315
317
|
- **Mount to the container your `index.html` declares.** The corpus convention is `<div id="root"></div>` with `createRoot(document.getElementById("root")!)`.
|
|
316
318
|
- **All imports at the top of `code.tsx`, and every bare import declared in `config.json` `dependencies`.** Bare imports (`react`, `d3`, `three`, …) resolve from a CDN — no install step — but declaring them is **required, not optional**: an import missing from `dependencies` is a lint error that fails `npx typebulb check` *and* refuses to run. Declaring is also what pins versions and lets `check` fetch type defs (without it you get errors like `TS2875: react/jsx-runtime`). So a bulb with imports must carry a `config.json` with a matching `dependencies` entry for each.
|
|
317
319
|
- **Theme-aware styling.** Style off CSS variables / `currentColor` so the bulb reads correctly in both light and dark; the host sets the theme.
|
|
318
320
|
- **Native dropdowns.** Style `select, option { background: Canvas; color: CanvasText }` (system colors track the host's `color-scheme`) — a `transparent` `<select>` otherwise opens an unthemed popup, white-on-white in dark mode.
|
|
319
321
|
- **`tb.ai()` takes more than the basics** — the full shape is `tb.ai({ messages, system?, effort?, provider?, model?, webSearch? })` → `Promise<{ text }>`. `webSearch` defaults **on** in the CLI (you supply your own key); pass `webSearch: false` to turn it off. For token-by-token output use `tb.ai.stream(...)` (see [`tb.ai()` § Streaming](#streaming)).
|
|
320
|
-
- **Gate AI-heavy bulbs on `tb.
|
|
322
|
+
- **Gate AI-heavy bulbs on `tb.aiAccess()`** — `'own'` / `'courtesy'` / `'none'`, never re-derived from `tb.mode` or the model list (see [AI access](#ai-access)).
|
|
321
323
|
- **`tb.theme` drives the `html[data-theme]` attribute** — style off that selector (`html[data-theme="dark"] { … }`); don't read `tb.theme` to branch your rendering.
|
|
322
324
|
- **`color-scheme` is set for you** — the host always applies `html[data-theme="dark"] { color-scheme: dark }` / `html[data-theme="light"] { color-scheme: light }` on top of your `styles.css`.
|
|
323
325
|
- **Math (KaTeX) renders in your replies** — write inline `$…$` / display `$$…$$` (prefer `$y = x^2$` over inline-code or a Unicode `y = x²`). The mirror's KaTeX renders only in prose and doesn't reach inside a fenced block (bulb, mermaid, svg, code).
|
|
@@ -379,7 +381,7 @@ Which must be declared in the dependencies section:
|
|
|
379
381
|
|
|
380
382
|
Typebulb has a package resolver that will load and cache these packages from `esm.sh` when the bulb runs.
|
|
381
383
|
|
|
382
|
-
##
|
|
384
|
+
## AI Models
|
|
383
385
|
|
|
384
386
|
Three ways to use models from different providers in typebulb:
|
|
385
387
|
|
|
@@ -448,6 +450,21 @@ for await (const c of tb.ai.stream({ messages })) {
|
|
|
448
450
|
|
|
449
451
|
Breaking the loop stops the stream; same options as `tb.ai()`. **`kind: "reasoning"` chunks require `effort: 1-3` and a thinking-capable model**.
|
|
450
452
|
|
|
453
|
+
### AI access
|
|
454
|
+
|
|
455
|
+
`tb.aiAccess()` reports what backs `tb.ai`, typed as the ambient `AiAccess`.
|
|
456
|
+
|
|
457
|
+
| Value | What it means | What a bulb does |
|
|
458
|
+
|-------|---------------|------------------|
|
|
459
|
+
| `'own'` | The user's own keys, or their own local model server | Run everything |
|
|
460
|
+
| `'courtesy'` | typebulb.com's quota-limited courtesy model | Fine for a call or two; a bulb that makes many (an agent loop, a model-vs-model game) shows a "use your own keys" notice instead of the run controls |
|
|
461
|
+
| `'none'` | No AI at all — the CLI with no keys, or an embedded bulb | Say so; don't leave dead controls on screen |
|
|
462
|
+
|
|
463
|
+
```ts
|
|
464
|
+
const [access, setAccess] = useState<AiAccess>("own");
|
|
465
|
+
useEffect(() => { tb.aiAccess().then(setAccess); }, []);
|
|
466
|
+
```
|
|
467
|
+
|
|
451
468
|
### Ollama & OpenAI-compatible endpoints
|
|
452
469
|
|
|
453
470
|
`provider: "ollama"` is the zero-config local preset: it talks to a local [Ollama](https://ollama.com) server over its OpenAI-compatible endpoint — no API key, defaults to `http://localhost:11434` (override with `OLLAMA_HOST`). `typebulb models` lists your installed Ollama models alongside cloud ones.
|