typebulb 0.56.1 → 0.57.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
@@ -67,7 +67,6 @@ typebulb stop --global Stop every running bulb and mirror, all projects
67
67
  typebulb trust [file] Remember a bulb as trusted (no arg: list trusted bulbs)
68
68
  typebulb untrust <file> Forget a bulb's trust (back to Restricted)
69
69
  typebulb --no-watch <file> Disable hot reload
70
- typebulb --port 3333 <file> Bind this exact port (fails if taken)
71
70
  typebulb --no-open <file> Open nothing at launch, not even inside VS Code
72
71
  typebulb --mode <name> <file> Also load .env.<name> on top of .env / .env.local
73
72
  typebulb --batch <name> <file> Scope tb.dir + relative tb.fs paths to <bulb-folder>/batches/<name> (batch runs)
@@ -252,7 +251,7 @@ The agent mirror turns that block into a live, sandboxed app, with a *breakout
252
251
 
253
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.
254
253
 
255
- **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. For inline bulbs, the same subscription is `typebulb wait agent` on the mirror — see [Emitting an inline bulb](#emitting-an-inline-bulb).
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 page that closes mid-run logs `[page] disconnected` a few seconds later, so a wait on a page-driven run's completion tag pairs with a second `wait <file> --match "[page] disconnected"`: a run whose page is gone never completes. For inline bulbs, the same subscription is `typebulb wait agent` on the mirror — see [Emitting an inline bulb](#emitting-an-inline-bulb).
256
255
 
257
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.
258
257
 
@@ -265,7 +264,8 @@ The agent mirror turns that block into a live, sandboxed app, with a *breakout
265
264
 
266
265
  That one launch *is* the loop: the server watches the file, so every save recompiles and reloads the page (`server.ts` included) — editing the file is the iteration.
267
266
 
268
- - **Don't relaunch, and don't wrap it in the `timeout` command.** A relaunch replaces the running server (one per bulb file), reclaiming the same port so the open tab reloads itself — it costs the page's in-memory state and nothing else. The `timeout` command kills the server outright, and the racing relaunch is what spawns extra windows; your harness's own tool timeout does not kill it.
267
+ - **Don't relaunch, and don't wrap it in the `timeout` command.** A relaunch replaces the running server (one per bulb file), reclaiming the same port so the open tab reloads itself — it costs the page's in-memory state and nothing else. The `timeout` command kills the server outright, and the racing relaunch is what spawns extra windows; your harness's own tool timeout does not kill it. When you must relaunch, relaunch plainly: a relaunch replaces on its own, so `stop` first is a wasted step (harmless — it closes the page, and the relaunch opens a fresh one).
268
+ - **When something has left two pages open, `stop` then relaunch is the reset.** `stop` closes the bulb's pages before its server goes, so it is the one gesture that ends every copy; the relaunch gives you exactly one page again. Nothing else you can type reaches a browser tab.
269
269
  - **What needs a restart:** a `.env` change (read once at boot) and in-memory `server.ts` state (reset on each reload).
270
270
  - **Each reload re-runs the bulb.** A save re-executes `code.tsx` from scratch, so work you start on mount repeats every edit — re-spending GPU/network, re-firing side effects, flooding the log. Put expensive or side-effecting work behind a trigger: `tb.onMessage(() => start())`, then `typebulb send <file>` when ready (also a general terminal→page channel — pass params, drive a loop).
271
271
  - **Reading the log:** it appends across every reload, so `typebulb logs --run latest <file>` shows just the current run (no need to clear).
@@ -279,7 +279,7 @@ That one launch *is* the loop: the server watches the file, so every save recomp
279
279
  - **Slow work settles in the handler** — a handler may be async, and the reply waits for its promise: keep a done-promise, `await` it, and return the finished state, with `--wait=<ms>` sized to the work (instead of polling flags in a sleep loop, or snapshot-polling from outside). The sharp edge: settle the promise on *every* exit of the run — success, failure, supersession, in a `finally` — and start idle with an already-resolved one, or the handler hangs and reads as a broken bulb. One case stays two-step: a `tb:*` gesture that kicks off slow work replies with the immediate frame (runtime-answered), so follow it with this settle probe.
280
280
  - **Rendered truth** — `typebulb send <file> tb:snapshot` prints the page's accessibility outline (roles, names, visible text) without disturbing its state. Use it when logs say ok but the screen might not, and as the first probe on a live page in a state you can't reproduce — a save would hot-reload and destroy it. (`tb:` messages are answered by the runtime, never your handlers, and imply `--wait`.) Its first line is the page's geometry — viewport, content size, and a fits-or-overflows verdict — so an unwanted scrollbar shows up in the first read.
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
- - **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, 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>`.
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
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 --wait` or any `tb:` verb that finds none, so a tab the user closed reopens and the message delivers. Run the bulb, edit, `send --wait`; never relaunch for a page. 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.
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.56.1
4
+ version: 0.57.0
5
5
  ---
6
6
 
7
- > Generated from typebulb v0.56.1. `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.57.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
 
@@ -75,7 +75,6 @@ typebulb stop --global Stop every running bulb and mirror, all projects
75
75
  typebulb trust [file] Remember a bulb as trusted (no arg: list trusted bulbs)
76
76
  typebulb untrust <file> Forget a bulb's trust (back to Restricted)
77
77
  typebulb --no-watch <file> Disable hot reload
78
- typebulb --port 3333 <file> Bind this exact port (fails if taken)
79
78
  typebulb --no-open <file> Open nothing at launch, not even inside VS Code
80
79
  typebulb --mode <name> <file> Also load .env.<name> on top of .env / .env.local
81
80
  typebulb --batch <name> <file> Scope tb.dir + relative tb.fs paths to <bulb-folder>/batches/<name> (batch runs)
@@ -260,7 +259,7 @@ The agent mirror turns that block into a live, sandboxed app, with a *breakout
260
259
 
261
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.
262
261
 
263
- **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. For inline bulbs, the same subscription is `typebulb wait agent` on the mirror — see [Emitting an inline bulb](#emitting-an-inline-bulb).
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 page that closes mid-run logs `[page] disconnected` a few seconds later, so a wait on a page-driven run's completion tag pairs with a second `wait <file> --match "[page] disconnected"`: a run whose page is gone never completes. For inline bulbs, the same subscription is `typebulb wait agent` on the mirror — see [Emitting an inline bulb](#emitting-an-inline-bulb).
264
263
 
265
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.
266
265
 
@@ -273,7 +272,8 @@ The agent mirror turns that block into a live, sandboxed app, with a *breakout
273
272
 
274
273
  That one launch *is* the loop: the server watches the file, so every save recompiles and reloads the page (`server.ts` included) — editing the file is the iteration.
275
274
 
276
- - **Don't relaunch, and don't wrap it in the `timeout` command.** A relaunch replaces the running server (one per bulb file), reclaiming the same port so the open tab reloads itself — it costs the page's in-memory state and nothing else. The `timeout` command kills the server outright, and the racing relaunch is what spawns extra windows; your harness's own tool timeout does not kill it.
275
+ - **Don't relaunch, and don't wrap it in the `timeout` command.** A relaunch replaces the running server (one per bulb file), reclaiming the same port so the open tab reloads itself — it costs the page's in-memory state and nothing else. The `timeout` command kills the server outright, and the racing relaunch is what spawns extra windows; your harness's own tool timeout does not kill it. When you must relaunch, relaunch plainly: a relaunch replaces on its own, so `stop` first is a wasted step (harmless — it closes the page, and the relaunch opens a fresh one).
276
+ - **When something has left two pages open, `stop` then relaunch is the reset.** `stop` closes the bulb's pages before its server goes, so it is the one gesture that ends every copy; the relaunch gives you exactly one page again. Nothing else you can type reaches a browser tab.
277
277
  - **What needs a restart:** a `.env` change (read once at boot) and in-memory `server.ts` state (reset on each reload).
278
278
  - **Each reload re-runs the bulb.** A save re-executes `code.tsx` from scratch, so work you start on mount repeats every edit — re-spending GPU/network, re-firing side effects, flooding the log. Put expensive or side-effecting work behind a trigger: `tb.onMessage(() => start())`, then `typebulb send <file>` when ready (also a general terminal→page channel — pass params, drive a loop).
279
279
  - **Reading the log:** it appends across every reload, so `typebulb logs --run latest <file>` shows just the current run (no need to clear).
@@ -287,7 +287,7 @@ That one launch *is* the loop: the server watches the file, so every save recomp
287
287
  - **Slow work settles in the handler** — a handler may be async, and the reply waits for its promise: keep a done-promise, `await` it, and return the finished state, with `--wait=<ms>` sized to the work (instead of polling flags in a sleep loop, or snapshot-polling from outside). The sharp edge: settle the promise on *every* exit of the run — success, failure, supersession, in a `finally` — and start idle with an already-resolved one, or the handler hangs and reads as a broken bulb. One case stays two-step: a `tb:*` gesture that kicks off slow work replies with the immediate frame (runtime-answered), so follow it with this settle probe.
288
288
  - **Rendered truth** — `typebulb send <file> tb:snapshot` prints the page's accessibility outline (roles, names, visible text) without disturbing its state. Use it when logs say ok but the screen might not, and as the first probe on a live page in a state you can't reproduce — a save would hot-reload and destroy it. (`tb:` messages are answered by the runtime, never your handlers, and imply `--wait`.) Its first line is the page's geometry — viewport, content size, and a fits-or-overflows verdict — so an unwanted scrollbar shows up in the first read.
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
- - **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, 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>`.
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
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 --wait` or any `tb:` verb that finds none, so a tab the user closed reopens and the message delivers. Run the bulb, edit, `send --wait`; never relaunch for a page. 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.