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 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.hasOwnKeys()` | Whether the user's own AI keys back `tb.ai` — `false` means courtesy model only; always `false` embedded | |
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; `typebulb send <file> png --wait` prints it raw, and decoding the base64 after the `data:image/png;base64,` prefix yields a PNG you can view. That's the visual-verification loop for canvas/WebGPU bulbs.
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.hasOwnKeys()`.** `false` means only the quota-limited courtesy model backs `tb.ai` (or, in the CLI, no keys at all) — fine for a call or two, but a bulb that makes many (an agent loop, a model-vs-model game) should render a "use your own keys" notice instead of the run controls.
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
- ## Custom AI Models
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.45.2
4
+ version: 0.47.0
5
5
  ---
6
6
 
7
- > Generated from typebulb v0.45.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.
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.hasOwnKeys()` | Whether the user's own AI keys back `tb.ai` — `false` means courtesy model only; always `false` embedded | |
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; `typebulb send <file> png --wait` prints it raw, and decoding the base64 after the `data:image/png;base64,` prefix yields a PNG you can view. That's the visual-verification loop for canvas/WebGPU bulbs.
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.hasOwnKeys()`.** `false` means only the quota-limited courtesy model backs `tb.ai` (or, in the CLI, no keys at all) — fine for a call or two, but a bulb that makes many (an agent loop, a model-vs-model game) should render a "use your own keys" notice instead of the run controls.
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
- ## Custom AI Models
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.