typebulb 0.14.9 → 0.15.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
@@ -39,10 +39,11 @@ typebulb agent An agent's first command — brings up the agent
39
39
  prints its URL + the authoring-skill paths; always exits 0
40
40
  typebulb skill Print this README as an Agent Skill on stdout
41
41
  typebulb call <file> <fn> […] Invoke one server.ts export headlessly: prints its return as JSON to stdout, logs/errors to stderr (needs --trust)
42
+ typebulb send <file> [msg] Push a message into a running bulb's page (its tb.onMessage handlers); the client-side twin of call, no --trust
42
43
  typebulb check [file.bulb.md] Type-check a bulb without running it
43
44
  typebulb predict [file] Report the capability a bulb probably needs, without running it
44
45
  typebulb models List AI models for tb.ai, filtered by your .env API keys
45
- typebulb logs [file|pid] Print a running bulb's captured console (no arg: list running servers; -f follow, -n N tail)
46
+ typebulb logs [file|pid] Print a running bulb's captured console (no arg: list running servers; -f follow, -n N tail, --clear truncate for a clean run)
46
47
  typebulb wait [file|agent] Block until the target server logs a new line, print it, exit — an agent's wake-up
47
48
  (--match <substr> filters; --timeout <sec>, default 1800, exit 2)
48
49
  typebulb stop [file|pid] Stop a running bulb (no arg: list this project's running servers)
@@ -175,6 +176,7 @@ npm install -g typebulb
175
176
  | `tb.copy(text)` | Copy text to the clipboard | ✅ | ✅ |
176
177
  | `tb.url()` | Get the bulb URL (the served localhost URL, locally) | ✅ | ✅ |
177
178
  | `tb.models()` | List available AI models (for dynamic model selectors); returns `[]` when embedded (no host AI) | ✅ | ✅ |
179
+ | `tb.onMessage(cb)` | Receive a value pushed in from the terminal by `typebulb send` — inert when embedded (no sender) | ✅ | ✅ |
178
180
  | `tb.fs.read/readBytes/write` | Read and write local files | ✅ `--trust` | ❌ |
179
181
  | `tb.server.<name>(...)` | Call a function exported from the `server.ts` block | ✅ `--trust` | ❌ |
180
182
  | `tb.ai({ messages, … })` | General-purpose AI call (chat, agents) | ✅ `--trust` | ❌ |
@@ -237,12 +239,22 @@ The host owns a bulb's **width**; you own its **height**.
237
239
 
238
240
  **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. `wait` and a `getState` call are constant already.
239
241
 
242
+ ## Iterating on a local bulb
243
+
244
+ Run a bulb **once** and let hot reload drive the loop.
245
+
246
+ - **Launch once.** `npx typebulb foo.bulb.md` opens one server and tab and watches the file — every save recompiles and reloads, `server.ts` included. Editing the file *is* the loop.
247
+ - **Don't relaunch, and don't wrap it in `timeout`.** A relaunch only replaces the running server (one per bulb file); `timeout` kills it, and the racing relaunch is what spawns a second window on a fresh port.
248
+ - **What needs a restart:** a `.env` change (read once at boot) and in-memory `server.ts` state (reset on each reload).
249
+ - **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).
250
+ - **When done:** Ctrl-C, or `typebulb stop <file>` — closing the terminal leaves the server running detached.
251
+
240
252
  ## Tips for Agents
241
253
 
242
- - **`config.json` `description`** is the bulb's SEO meta description keep it to one or two plain sentences (~150–160 chars), or it gets truncated.
243
- - **The frontmatter `name:` is the bulb's title** — a few words, not a sentence — and the filename should be its slug (`name: Counter` → `counter.bulb.md`).
254
+ - **`config.json` `description`** is the bulb's search-result blurbwhat makes someone open it, kept short (it truncates past ~160 chars).
255
+ - **The frontmatter `name:` is the bulb's title** — a few words, not a sentence — and the filename should be its slug (`name: Counter` → `counter.bulb.md`), saved in the project's **`typebulbs/`** folder.
256
+ - **A bulb's working files belong beside it**, in a folder named for its slug. `tb.fs`/`server.ts` paths are relative to the launch dir (cwd), not the bulb's folder — so run from the project root and prefix the bulb's path: `typebulbs/counter/…`, not a bare `counter/…`.
244
257
  - **Self-testing a local bulb** — To confirm a bulb works, run it, instrument with `tb.server.log(...)` (prints to the server's stdout, captured in the log — and works **even on a Restricted bulb**), 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.
245
- - **A bulb's working files live in a folder named after the bulb.** Whether written by `server.ts` or `tb.fs.write`, favor a sibling folder matching the bulb's slug.
246
258
  - **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`.
247
259
  - **Mount to the container your `index.html` declares.** The corpus convention is `<div id="root"></div>` with `createRoot(document.getElementById("root")!)`.
248
260
  - **All imports at the top of `code.tsx`.** Bare imports (`react`, `d3`, `three`, …) auto-resolve from a CDN — no install step. Declare them in `config.json` `dependencies` anyway: that's what lets `npx typebulb check` fetch type defs (without it you get errors like `TS2875: react/jsx-runtime`) and pins versions.
@@ -871,6 +871,11 @@ const something = require('module-name') // NOT SUPPORTED!
871
871
  // Read from window each time so updates are visible
872
872
  const getData = () => window.__TB_DATA__ || [];
873
873
 
874
+ // tb.onMessage subscribers, fed by the events-SSE 'message' channel (typebulb send). A message is
875
+ // JSON-or-string: parse as JSON, else hand back the raw string; '' (a bare \`send\`) \u21D2 undefined.
876
+ const messageHandlers = new Set();
877
+ const parseMsg = (s) => { if (s === '' || s == null) return undefined; try { return JSON.parse(s); } catch { return s; } };
878
+
874
879
  // Filesystem API - calls back to the local server.
875
880
  // The server returns raw bytes (no JSON envelope); read() decodes as UTF-8.
876
881
  const failIfNotOk = async (resp, action, path) => {
@@ -1028,6 +1033,14 @@ const something = require('module-name') // NOT SUPPORTED!
1028
1033
  // Filesystem - local CLI extension
1029
1034
  fs,
1030
1035
 
1036
+ // Receive a value pushed from the terminal via \`typebulb send\` (data-in, the dual of the
1037
+ // ungated tb.server.log). Returns an unsubscribe fn. Inert when embedded \u2014 no server, so no
1038
+ // sender; the handler is registered but never fires (cf. tb.models returning []).
1039
+ onMessage: (handler) => {
1040
+ if (typeof handler === 'function') messageHandlers.add(handler);
1041
+ return () => messageHandlers.delete(handler);
1042
+ },
1043
+
1031
1044
  // Environment ('embedded' when running as a bulb-in-a-bulb)
1032
1045
  mode: isEmbedded ? 'embedded' : 'local',
1033
1046
 
@@ -1038,13 +1051,22 @@ const something = require('module-name') // NOT SUPPORTED!
1038
1051
  set theme(v) { if (window.__tbTheme) window.__tbTheme.set(v); }
1039
1052
  });
1040
1053
 
1041
- // Hot reload listener
1042
- if (window.__TYPEBULB_WATCH__) {
1054
+ // Events channel (dev server only): 'reload' drives hot reload (only emitted when watching),
1055
+ // 'message' delivers \`typebulb send\` pushes to tb.onMessage. Connect for a CLI-served page (http
1056
+ // origin, not an embed); a srcdoc embed or a file:// static export has no server, so onMessage just
1057
+ // stays inert there. Opening it independent of watch is what lets send reach a --no-watch page.
1058
+ if (!isEmbedded && location.protocol.indexOf('http') === 0) {
1043
1059
  const es = new EventSource('/__reload');
1044
1060
  es.addEventListener('reload', () => {
1045
1061
  console.log('[typebulb] Reloading...');
1046
1062
  window.location.reload();
1047
1063
  });
1064
+ es.addEventListener('message', (e) => {
1065
+ // Wire payload is JSON-encoded (SSE-line-safe); decode it, then interpret JSON-or-string.
1066
+ let value;
1067
+ try { value = parseMsg(JSON.parse(e.data)); } catch { value = undefined; }
1068
+ messageHandlers.forEach((h) => { try { h(value); } catch (err) { console.error(err); } });
1069
+ });
1048
1070
  es.onerror = () => {
1049
1071
  // Server closed, stop trying
1050
1072
  es.close();
@@ -1090,30 +1112,29 @@ const something = require('module-name') // NOT SUPPORTED!
1090
1112
  });
1091
1113
  } catch (e) {}
1092
1114
  })();
1093
- <\/script>`}function UEt(s){let{name:o,code:a,css:d,html:g,data:m,insight:v,importMap:A,watch:I,theme:T,embedded:L}=s,B=g.trim()||'<div id="app"></div>',j=L?"":`
1094
- html, body { height: 100%; }`,Q=W=>W.replace(/<\/script/gi,"<\\/script"),U={imports:pwn(A.imports)};return`<!DOCTYPE html>
1115
+ <\/script>`}function UEt(s){let{name:o,code:a,css:d,html:g,data:m,insight:v,importMap:A,theme:I,embedded:T}=s,L=g.trim()||'<div id="app"></div>',B=T?"":`
1116
+ html, body { height: 100%; }`,j=U=>U.replace(/<\/script/gi,"<\\/script"),Q={imports:pwn(A.imports)};return`<!DOCTYPE html>
1095
1117
  <html>
1096
1118
  <head>
1097
1119
  <meta charset="utf-8">
1098
1120
  <meta name="viewport" content="width=device-width, initial-scale=1">
1099
1121
  <title>${GEt(o)} - typebulb</title>
1100
- ${JEt(o,T)}
1122
+ ${JEt(o,I)}
1101
1123
  <script type="importmap">
1102
- ${JSON.stringify(U,null,2)}
1124
+ ${JSON.stringify(Q,null,2)}
1103
1125
  <\/script>
1104
1126
  <style>
1105
- ${qEt}${j}
1127
+ ${qEt}${B}
1106
1128
  </style>
1107
1129
  <style>
1108
1130
  ${d}
1109
1131
  </style>
1110
1132
  </head>
1111
1133
  <body>
1112
- ${B}
1134
+ ${L}
1113
1135
 
1114
- ${m.length>0?`<script>window.__TB_DATA__ = ${Q(JSON.stringify(m))};<\/script>`:""}
1115
- ${v?`<script>window.__TB_INSIGHT__ = ${Q(JSON.stringify(v))};<\/script>`:""}
1116
- ${I?"<script>window.__TYPEBULB_WATCH__ = true;<\/script>":""}
1136
+ ${m.length>0?`<script>window.__TB_DATA__ = ${j(JSON.stringify(m))};<\/script>`:""}
1137
+ ${v?`<script>window.__TB_INSIGHT__ = ${j(JSON.stringify(v))};<\/script>`:""}
1117
1138
 
1118
1139
  <script>
1119
1140
  ${QEt}
@@ -1122,7 +1143,7 @@ ${QEt}
1122
1143
  ${dwn}
1123
1144
 
1124
1145
  <script type="module">
1125
- ${Q(a)}
1146
+ ${j(a)}
1126
1147
  globalThis.__tbEntryRan = true; /* load-failure backstop \u2014 see embedProtocol */
1127
1148
  <\/script>
1128
1149
  </body>
@@ -9,7 +9,7 @@
9
9
  * TypeChecker, and the CLI's emitted typecheck dirs.
10
10
  */
11
11
  /** Typebulb globals available in browser-side code (code.tsx). */
12
- export declare const clientTbTypings = "\n/**\n * Typebulb utilities namespace.\n * Type `tb.` to discover available helpers.\n */\ndeclare const tb: {\n /**\n * Get raw data chunk from the Data tab.\n * @param index - Chunk index (0-based). Separate chunks with 2 blank lines.\n */\n data(index: number): string;\n /**\n * Get data chunk parsed as JSON (handles JSON-ish with unquoted keys).\n * @param index - Chunk index (0-based)\n * @throws If chunk is not valid JSON/JSON-ish\n */\n json<T = unknown>(index: number): T;\n /**\n * Async value inspector for tensor-like objects.\n *\n * Materializes lazy values (like GPU tensors) and logs them with metadata.\n * Handles objects with `.js()`, `.data()`, `.array()`, `.arraySync()`, etc.\n *\n * @remarks\n * - Always use `await` - materialization may be async (GPU\u2192CPU readback)\n * - Large values are truncated (max 1000 elements)\n * - Promises are logged as `[Promise]` (not awaited - could hang)\n */\n dump(...args: any[]): Promise<void>;\n /**\n * Trigger inference to generate new insight data.\n *\n * Opens a confirmation modal showing the data to be analyzed, then streams\n * the inference result. On success, updates the insight so subsequent\n * `tb.insight()` calls return the new value.\n *\n * @param opts - Options for inference\n * @param opts.data - Data to pre-populate in the modal (string or array of strings). If omitted, modal opens with empty textarea for user to paste.\n * @returns Promise that resolves with the parsed insight JSON\n * @throws If inference is already in progress, or on network/parse/rate limit errors\n */\n infer<T = unknown>(opts?: { data?: string | string[] }): Promise<T>;\n /**\n * Get the current inference state.\n *\n * @returns 'idle' | 'running' | 'complete' | 'error'\n */\n inferenceState(): 'idle' | 'running' | 'complete' | 'error';\n /**\n * Set a data chunk for the next inference call.\n *\n * Use this to programmatically set data that will be sent when `tb.infer()` is called\n * without the `data` option.\n *\n * @param index - The chunk index (0-based)\n * @param content - The content for this chunk\n */\n setData(index: number, content: string): void;\n /**\n * Proxy a CDN URL through the sandbox origin for Web Worker/WASM same-origin loading.\n *\n * In the sandbox, prepends `/proxy/` so the URL is served from the same origin.\n * Outside the sandbox (exported HTML, CLI), returns the URL unchanged.\n *\n * @param url - Full HTTPS URL to an allowlisted CDN (esm.sh, unpkg.com, cdn.jsdelivr.net, cdnjs.cloudflare.com)\n * @returns The proxied URL (sandbox) or the original URL (standalone/CLI)\n */\n proxy(url: string): string;\n /**\n * Copy text to clipboard.\n * Must be called synchronously within a user gesture (click/keydown).\n * @returns true if successful, false otherwise\n */\n copy(text: string): Promise<boolean>;\n /**\n * Get the canonical URL of this bulb.\n *\n * Returns the parent typebulb.com URL (including path, query, and `#tb=` fragment),\n * resolving correctly from inside the cross-origin sandbox iframe.\n * Use this instead of `location.href` or `document.referrer`.\n *\n * @returns The full canonical URL\n */\n url(): Promise<string>;\n /**\n * Get the insight data produced by the inference layer.\n *\n * Returns the parsed JSON from insight.json, populated by the inference LLM.\n * Use a type parameter to get typed access to the insight data.\n *\n * @returns The parsed insight JSON, or undefined if no insight is available\n */\n insight<T = unknown>(): T | undefined;\n /**\n * Server-side function proxy.\n *\n * In the CLI, calls exported functions from the `**server.ts**` section.\n * `tb.server.log(...)` is a built-in that prints to CLI stdout (falls back to console.log on web).\n * User exports override built-ins of the same name.\n */\n server: Record<string, (...args: any[]) => Promise<any>>;\n /**\n * General-purpose AI call.\n *\n * @param options - Messages, system prompt, optional provider/model override\n * @returns Promise resolving to { text: string }\n * @throws On rate limit, network error, or provider error\n */\n ai(options: {\n messages: Array<{ role: \"user\" | \"assistant\"; content: string }>;\n system?: string;\n /** Reasoning depth hint (0=min, 1=low, 2=med, 3=max). Mapped to provider-specific parameters (e.g. Anthropic adaptive thinking, OpenAI reasoning effort). Default: 0. */\n reasoning?: 0 | 1 | 2 | 3;\n provider?: string;\n model?: string;\n /** Enable/disable web search. Default: on for BYOK, always off for free model. */\n webSearch?: boolean;\n }): Promise<{ text: string }>;\n /**\n * Local filesystem access (CLI only).\n *\n * Paths are resolved relative to the directory containing the bulb file.\n * Throws in editor/published mode.\n */\n fs: {\n /** Read a file as UTF-8 text. Throws if the file is not valid UTF-8 \u2014 use readBytes for binary. */\n read(path: string): Promise<string>;\n /** Read a file as raw bytes. */\n readBytes(path: string): Promise<Uint8Array>;\n /** Write text or raw bytes to a file. Creates parent directories if needed. */\n write(path: string, content: string | Uint8Array): Promise<boolean>;\n };\n /**\n * Returns AI models available to the current user.\n * Models are filtered by the user's configured API keys.\n * If no keys are configured, returns only the courtesy model.\n */\n models(): Promise<Array<{\n /** Provider protocol: \"anthropic\", \"openai\", \"gemini\", \"openrouter\" */\n provider: string;\n /** Model identifier, e.g. \"claude-sonnet-4-6\" */\n name: string;\n /** Human-readable display name, e.g. \"Sonnet 4.6\" */\n friendlyName: string;\n /** Provider display name, e.g. \"Anthropic\" */\n providerName: string;\n }>>;\n /**\n * The bulb's theme override (`<html data-theme>`).\n *\n * - Get: the current override \u2014 `'dark'` | `'light'`, or `undefined` when\n * following the OS preference.\n * - Set `'dark'`/`'light'` to force and persist it (per-bulb); set\n * `undefined` to clear the override and follow the OS again.\n *\n * Drives `<html data-theme>`, so render off `html[data-theme=\"\u2026\"]` selectors\n * (or observe the attribute) rather than reading `tb.theme`.\n */\n theme: 'light' | 'dark' | undefined;\n /**\n * The mode this bulb is running in.\n *\n * - `'local'` \u2014 Running via the typebulb CLI\n * - `'editor'` \u2014 Running in the typebulb.com editor\n * - `'published'` \u2014 Running as a published/standalone bulb on typebulb.com\n * - `'embedded'` \u2014 Running as a bulb embedded inside another bulb (sandboxed,\n * client-only: AI, filesystem, and server RPC are unavailable)\n */\n mode: 'local' | 'editor' | 'published' | 'embedded';\n};\n";
12
+ export declare const clientTbTypings = "\n/**\n * Typebulb utilities namespace.\n * Type `tb.` to discover available helpers.\n */\ndeclare const tb: {\n /**\n * Get raw data chunk from the Data tab.\n * @param index - Chunk index (0-based). Separate chunks with 2 blank lines.\n */\n data(index: number): string;\n /**\n * Get data chunk parsed as JSON (handles JSON-ish with unquoted keys).\n * @param index - Chunk index (0-based)\n * @throws If chunk is not valid JSON/JSON-ish\n */\n json<T = unknown>(index: number): T;\n /**\n * Async value inspector for tensor-like objects.\n *\n * Materializes lazy values (like GPU tensors) and logs them with metadata.\n * Handles objects with `.js()`, `.data()`, `.array()`, `.arraySync()`, etc.\n *\n * @remarks\n * - Always use `await` - materialization may be async (GPU\u2192CPU readback)\n * - Large values are truncated (max 1000 elements)\n * - Promises are logged as `[Promise]` (not awaited - could hang)\n */\n dump(...args: any[]): Promise<void>;\n /**\n * Trigger inference to generate new insight data.\n *\n * Opens a confirmation modal showing the data to be analyzed, then streams\n * the inference result. On success, updates the insight so subsequent\n * `tb.insight()` calls return the new value.\n *\n * @param opts - Options for inference\n * @param opts.data - Data to pre-populate in the modal (string or array of strings). If omitted, modal opens with empty textarea for user to paste.\n * @returns Promise that resolves with the parsed insight JSON\n * @throws If inference is already in progress, or on network/parse/rate limit errors\n */\n infer<T = unknown>(opts?: { data?: string | string[] }): Promise<T>;\n /**\n * Get the current inference state.\n *\n * @returns 'idle' | 'running' | 'complete' | 'error'\n */\n inferenceState(): 'idle' | 'running' | 'complete' | 'error';\n /**\n * Set a data chunk for the next inference call.\n *\n * Use this to programmatically set data that will be sent when `tb.infer()` is called\n * without the `data` option.\n *\n * @param index - The chunk index (0-based)\n * @param content - The content for this chunk\n */\n setData(index: number, content: string): void;\n /**\n * Proxy a CDN URL through the sandbox origin for Web Worker/WASM same-origin loading.\n *\n * In the sandbox, prepends `/proxy/` so the URL is served from the same origin.\n * Outside the sandbox (exported HTML, CLI), returns the URL unchanged.\n *\n * @param url - Full HTTPS URL to an allowlisted CDN (esm.sh, unpkg.com, cdn.jsdelivr.net, cdnjs.cloudflare.com)\n * @returns The proxied URL (sandbox) or the original URL (standalone/CLI)\n */\n proxy(url: string): string;\n /**\n * Copy text to clipboard.\n * Must be called synchronously within a user gesture (click/keydown).\n * @returns true if successful, false otherwise\n */\n copy(text: string): Promise<boolean>;\n /**\n * Get the canonical URL of this bulb.\n *\n * Returns the parent typebulb.com URL (including path, query, and `#tb=` fragment),\n * resolving correctly from inside the cross-origin sandbox iframe.\n * Use this instead of `location.href` or `document.referrer`.\n *\n * @returns The full canonical URL\n */\n url(): Promise<string>;\n /**\n * Get the insight data produced by the inference layer.\n *\n * Returns the parsed JSON from insight.json, populated by the inference LLM.\n * Use a type parameter to get typed access to the insight data.\n *\n * @returns The parsed insight JSON, or undefined if no insight is available\n */\n insight<T = unknown>(): T | undefined;\n /**\n * Server-side function proxy.\n *\n * In the CLI, calls exported functions from the `**server.ts**` section.\n * `tb.server.log(...)` is a built-in that prints to CLI stdout (falls back to console.log on web).\n * User exports override built-ins of the same name.\n */\n server: Record<string, (...args: any[]) => Promise<any>>;\n /**\n * Subscribe to a value pushed from the terminal via `typebulb send <file> [message]`.\n *\n * The dual of `tb.server.log` (data out): a value sent *in* from the CLI, no `--trust` required.\n * Use it to start expensive work on demand instead of on load \u2014 e.g. `tb.onMessage(() => start())`\n * \u2014 so hot reloads don't re-trigger it while you edit, and an agent kicks off one run when ready.\n *\n * The message is the JSON-parsed value of what `send` was given (or the raw string if it isn't\n * JSON; `undefined` for a bare `typebulb send <file>`). Returns an unsubscribe function. Inert in\n * an embedded bulb (no sender) \u2014 the handler is registered but never fires.\n *\n * @param handler - Called with each pushed message.\n * @returns An unsubscribe function.\n */\n onMessage(handler: (message: any) => void): () => void;\n /**\n * General-purpose AI call.\n *\n * @param options - Messages, system prompt, optional provider/model override\n * @returns Promise resolving to { text: string }\n * @throws On rate limit, network error, or provider error\n */\n ai(options: {\n messages: Array<{ role: \"user\" | \"assistant\"; content: string }>;\n system?: string;\n /** Reasoning depth hint (0=min, 1=low, 2=med, 3=high). Mapped to provider-specific parameters (e.g. Anthropic adaptive thinking, OpenAI reasoning effort). Default: 0. */\n reasoning?: 0 | 1 | 2 | 3;\n provider?: string;\n model?: string;\n /** Enable/disable web search. Default: on for BYOK, always off for free model. */\n webSearch?: boolean;\n }): Promise<{ text: string }>;\n /**\n * Local filesystem access (CLI only).\n *\n * Paths are resolved relative to the current working directory (where the CLI\n * was launched), consistent with the .env cascade \u2014 not the bulb file's folder.\n * Throws in editor/published mode.\n */\n fs: {\n /** Read a file as UTF-8 text. Throws if the file is not valid UTF-8 \u2014 use readBytes for binary. */\n read(path: string): Promise<string>;\n /** Read a file as raw bytes. */\n readBytes(path: string): Promise<Uint8Array>;\n /** Write text or raw bytes to a file. Creates parent directories if needed. */\n write(path: string, content: string | Uint8Array): Promise<boolean>;\n };\n /**\n * Returns AI models available to the current user.\n * Models are filtered by the user's configured API keys.\n * If no keys are configured, returns only the courtesy model.\n */\n models(): Promise<Array<{\n /** Provider protocol: \"anthropic\", \"openai\", \"gemini\", \"openrouter\" */\n provider: string;\n /** Model identifier, e.g. \"claude-sonnet-4-6\" */\n name: string;\n /** Human-readable display name, e.g. \"Sonnet 4.6\" */\n friendlyName: string;\n /** Provider display name, e.g. \"Anthropic\" */\n providerName: string;\n }>>;\n /**\n * The bulb's theme override (`<html data-theme>`).\n *\n * - Get: the current override \u2014 `'dark'` | `'light'`, or `undefined` when\n * following the OS preference.\n * - Set `'dark'`/`'light'` to force and persist it (per-bulb); set\n * `undefined` to clear the override and follow the OS again.\n *\n * Drives `<html data-theme>`, so render off `html[data-theme=\"\u2026\"]` selectors\n * (or observe the attribute) rather than reading `tb.theme`.\n */\n theme: 'light' | 'dark' | undefined;\n /**\n * The mode this bulb is running in.\n *\n * - `'local'` \u2014 Running via the typebulb CLI\n * - `'editor'` \u2014 Running in the typebulb.com editor\n * - `'published'` \u2014 Running as a published/standalone bulb on typebulb.com\n * - `'embedded'` \u2014 Running as a bulb embedded inside another bulb (sandboxed,\n * client-only: AI, filesystem, and server RPC are unavailable)\n */\n mode: 'local' | 'editor' | 'published' | 'embedded';\n};\n";
13
13
  /** Typebulb globals available in Node-side code (server.ts). */
14
- export declare const serverTbTypings = "\n/**\n * Typebulb utilities namespace (server-side).\n * Type `tb.` to discover available helpers.\n */\ndeclare const tb: {\n /**\n * Get raw data chunk from the Data tab.\n * @param index - Chunk index (0-based). Separate chunks with 2 blank lines.\n */\n data(index: number): string;\n /**\n * Get data chunk parsed as JSON (handles JSON-ish with unquoted keys).\n * @param index - Chunk index (0-based)\n * @throws If chunk is not valid JSON/JSON-ish\n */\n json<T = unknown>(index: number): T;\n /**\n * Get the insight data produced by the inference layer.\n *\n * Returns the parsed JSON from insight.json, populated by the inference LLM.\n * Use a type parameter to get typed access to the insight data.\n *\n * @returns The parsed insight JSON, or undefined if no insight is available\n */\n insight<T = unknown>(): T | undefined;\n /**\n * General-purpose AI call.\n *\n * @param options - Messages, system prompt, optional provider/model override\n * @returns Promise resolving to { text: string }\n * @throws On rate limit, network error, or provider error\n */\n ai(options: {\n messages: Array<{ role: \"user\" | \"assistant\"; content: string }>;\n system?: string;\n /** Reasoning depth hint (0=min, 1=low, 2=med, 3=max). Mapped to provider-specific parameters (e.g. Anthropic adaptive thinking, OpenAI reasoning effort). Default: 0. */\n reasoning?: 0 | 1 | 2 | 3;\n provider?: string;\n model?: string;\n /** Enable/disable web search. Default: on for BYOK, always off for free model. */\n webSearch?: boolean;\n }): Promise<{ text: string }>;\n /**\n * Local filesystem access (CLI only).\n *\n * Paths are resolved relative to the directory containing the bulb file.\n * Throws in editor/published mode.\n */\n fs: {\n /** Read a file as UTF-8 text. Throws if the file is not valid UTF-8 \u2014 use readBytes for binary. */\n read(path: string): Promise<string>;\n /** Read a file as raw bytes. */\n readBytes(path: string): Promise<Uint8Array>;\n /** Write text or raw bytes to a file. Creates parent directories if needed. */\n write(path: string, content: string | Uint8Array): Promise<boolean>;\n };\n /**\n * Server-side function proxy. The built-in `log` prints to CLI stdout\n * (falls back to console.log on web). On the server side, this object only\n * exposes `log` \u2014 server-to-server calls are not supported.\n */\n server: {\n log(...args: any[]): Promise<void>;\n };\n /**\n * Returns AI models available to the current user.\n * Models are filtered by the user's configured API keys.\n * If no keys are configured, returns only the courtesy model.\n */\n models(): Promise<Array<{\n /** Provider protocol: \"anthropic\", \"openai\", \"gemini\", \"openrouter\" */\n provider: string;\n /** Model identifier, e.g. \"claude-sonnet-4-6\" */\n name: string;\n /** Human-readable display name, e.g. \"Sonnet 4.6\" */\n friendlyName: string;\n /** Provider display name, e.g. \"Anthropic\" */\n providerName: string;\n }>>;\n /**\n * The mode this bulb is running in.\n *\n * - `'local'` \u2014 Running via the typebulb CLI\n * - `'editor'` \u2014 Running in the typebulb.com editor\n * - `'published'` \u2014 Running as a published/standalone bulb on typebulb.com\n * - `'embedded'` \u2014 Running as a bulb embedded inside another bulb (sandboxed,\n * client-only: AI, filesystem, and server RPC are unavailable)\n */\n mode: 'local' | 'editor' | 'published' | 'embedded';\n};\n";
14
+ export declare const serverTbTypings = "\n/**\n * Typebulb utilities namespace (server-side).\n * Type `tb.` to discover available helpers.\n */\ndeclare const tb: {\n /**\n * Get raw data chunk from the Data tab.\n * @param index - Chunk index (0-based). Separate chunks with 2 blank lines.\n */\n data(index: number): string;\n /**\n * Get data chunk parsed as JSON (handles JSON-ish with unquoted keys).\n * @param index - Chunk index (0-based)\n * @throws If chunk is not valid JSON/JSON-ish\n */\n json<T = unknown>(index: number): T;\n /**\n * Get the insight data produced by the inference layer.\n *\n * Returns the parsed JSON from insight.json, populated by the inference LLM.\n * Use a type parameter to get typed access to the insight data.\n *\n * @returns The parsed insight JSON, or undefined if no insight is available\n */\n insight<T = unknown>(): T | undefined;\n /**\n * General-purpose AI call.\n *\n * @param options - Messages, system prompt, optional provider/model override\n * @returns Promise resolving to { text: string }\n * @throws On rate limit, network error, or provider error\n */\n ai(options: {\n messages: Array<{ role: \"user\" | \"assistant\"; content: string }>;\n system?: string;\n /** Reasoning depth hint (0=min, 1=low, 2=med, 3=high). Mapped to provider-specific parameters (e.g. Anthropic adaptive thinking, OpenAI reasoning effort). Default: 0. */\n reasoning?: 0 | 1 | 2 | 3;\n provider?: string;\n model?: string;\n /** Enable/disable web search. Default: on for BYOK, always off for free model. */\n webSearch?: boolean;\n }): Promise<{ text: string }>;\n /**\n * Local filesystem access (CLI only).\n *\n * Paths are resolved relative to the current working directory (where the CLI\n * was launched), consistent with the .env cascade \u2014 not the bulb file's folder.\n * Throws in editor/published mode.\n */\n fs: {\n /** Read a file as UTF-8 text. Throws if the file is not valid UTF-8 \u2014 use readBytes for binary. */\n read(path: string): Promise<string>;\n /** Read a file as raw bytes. */\n readBytes(path: string): Promise<Uint8Array>;\n /** Write text or raw bytes to a file. Creates parent directories if needed. */\n write(path: string, content: string | Uint8Array): Promise<boolean>;\n };\n /**\n * Server-side function proxy. The built-in `log` prints to CLI stdout\n * (falls back to console.log on web). On the server side, this object only\n * exposes `log` \u2014 server-to-server calls are not supported.\n */\n server: {\n log(...args: any[]): Promise<void>;\n };\n /**\n * Returns AI models available to the current user.\n * Models are filtered by the user's configured API keys.\n * If no keys are configured, returns only the courtesy model.\n */\n models(): Promise<Array<{\n /** Provider protocol: \"anthropic\", \"openai\", \"gemini\", \"openrouter\" */\n provider: string;\n /** Model identifier, e.g. \"claude-sonnet-4-6\" */\n name: string;\n /** Human-readable display name, e.g. \"Sonnet 4.6\" */\n friendlyName: string;\n /** Provider display name, e.g. \"Anthropic\" */\n providerName: string;\n }>>;\n /**\n * The mode this bulb is running in.\n *\n * - `'local'` \u2014 Running via the typebulb CLI\n * - `'editor'` \u2014 Running in the typebulb.com editor\n * - `'published'` \u2014 Running as a published/standalone bulb on typebulb.com\n * - `'embedded'` \u2014 Running as a bulb embedded inside another bulb (sandboxed,\n * client-only: AI, filesystem, and server RPC are unavailable)\n */\n mode: 'local' | 'editor' | 'published' | 'embedded';\n};\n";
15
15
  //# sourceMappingURL=tbTypings.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tbTypings.d.ts","sourceRoot":"","sources":["../../dts/src/tbTypings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiMH,kEAAkE;AAClE,eAAO,MAAM,eAAe,ozNAO3B,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,eAAe,wgHAO3B,CAAA"}
1
+ {"version":3,"file":"tbTypings.d.ts","sourceRoot":"","sources":["../../dts/src/tbTypings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAmNH,kEAAkE;AAClE,eAAO,MAAM,eAAe,kwPAO3B,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,eAAe,umHAO3B,CAAA"}
@@ -41,7 +41,7 @@ const ai = `
41
41
  ai(options: {
42
42
  messages: Array<{ role: "user" | "assistant"; content: string }>;
43
43
  system?: string;
44
- /** Reasoning depth hint (0=min, 1=low, 2=med, 3=max). Mapped to provider-specific parameters (e.g. Anthropic adaptive thinking, OpenAI reasoning effort). Default: 0. */
44
+ /** Reasoning depth hint (0=min, 1=low, 2=med, 3=high). Mapped to provider-specific parameters (e.g. Anthropic adaptive thinking, OpenAI reasoning effort). Default: 0. */
45
45
  reasoning?: 0 | 1 | 2 | 3;
46
46
  provider?: string;
47
47
  model?: string;
@@ -92,7 +92,8 @@ const fs = `
92
92
  /**
93
93
  * Local filesystem access (CLI only).
94
94
  *
95
- * Paths are resolved relative to the directory containing the bulb file.
95
+ * Paths are resolved relative to the current working directory (where the CLI
96
+ * was launched), consistent with the .env cascade — not the bulb file's folder.
96
97
  * Throws in editor/published mode.
97
98
  */
98
99
  fs: {
@@ -180,6 +181,22 @@ const clientOnlyMembers = `
180
181
  * @returns The full canonical URL
181
182
  */
182
183
  url(): Promise<string>;`;
184
+ const onMessage = `
185
+ /**
186
+ * Subscribe to a value pushed from the terminal via \`typebulb send <file> [message]\`.
187
+ *
188
+ * The dual of \`tb.server.log\` (data out): a value sent *in* from the CLI, no \`--trust\` required.
189
+ * Use it to start expensive work on demand instead of on load — e.g. \`tb.onMessage(() => start())\`
190
+ * — so hot reloads don't re-trigger it while you edit, and an agent kicks off one run when ready.
191
+ *
192
+ * The message is the JSON-parsed value of what \`send\` was given (or the raw string if it isn't
193
+ * JSON; \`undefined\` for a bare \`typebulb send <file>\`). Returns an unsubscribe function. Inert in
194
+ * an embedded bulb (no sender) — the handler is registered but never fires.
195
+ *
196
+ * @param handler - Called with each pushed message.
197
+ * @returns An unsubscribe function.
198
+ */
199
+ onMessage(handler: (message: any) => void): () => void;`;
183
200
  const clientServerProxy = `
184
201
  /**
185
202
  * Server-side function proxy.
@@ -195,7 +212,7 @@ export const clientTbTypings = `
195
212
  * Typebulb utilities namespace.
196
213
  * Type \`tb.\` to discover available helpers.
197
214
  */
198
- declare const tb: {${dataAndJson}${clientOnlyMembers}${insight}${clientServerProxy}${ai}${fs}${models}${theme}${mode}
215
+ declare const tb: {${dataAndJson}${clientOnlyMembers}${insight}${clientServerProxy}${onMessage}${ai}${fs}${models}${theme}${mode}
199
216
  };
200
217
  `;
201
218
  /** Typebulb globals available in Node-side code (server.ts). */
@@ -1 +1 @@
1
- {"version":3,"file":"tbTypings.js","sourceRoot":"","sources":["../../dts/src/tbTypings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,GAAG;;;;;;;;;;;uCAWmB,CAAA;AAEvC,MAAM,OAAO,GAAG;;;;;;;;;yCASyB,CAAA;AAEzC,MAAM,EAAE,GAAG;;;;;;;;;;;;;;;;;iCAiBsB,CAAA;AAEjC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;OAeR,CAAA;AAEP,MAAM,KAAK,GAAG;;;;;;;;;;;;uCAYyB,CAAA;AAEvC,MAAM,IAAI,GAAG;;;;;;;;;;uDAU0C,CAAA;AAEvD,MAAM,EAAE,GAAG;;;;;;;;;;;;;;KAcN,CAAA;AAEL,MAAM,aAAa,GAAG;;;;;;;;KAQjB,CAAA;AAEL,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAmEA,CAAA;AAE1B,MAAM,iBAAiB,GAAG;;;;;;;;4DAQkC,CAAA;AAE5D,kEAAkE;AAClE,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;qBAKV,WAAW,GAAG,iBAAiB,GAAG,OAAO,GAAG,iBAAiB,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,GAAG,KAAK,GAAG,IAAI;;CAEnH,CAAA;AAED,gEAAgE;AAChE,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;qBAKV,WAAW,GAAG,OAAO,GAAG,EAAE,GAAG,EAAE,GAAG,aAAa,GAAG,MAAM,GAAG,IAAI;;CAEnF,CAAA"}
1
+ {"version":3,"file":"tbTypings.js","sourceRoot":"","sources":["../../dts/src/tbTypings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,WAAW,GAAG;;;;;;;;;;;uCAWmB,CAAA;AAEvC,MAAM,OAAO,GAAG;;;;;;;;;yCASyB,CAAA;AAEzC,MAAM,EAAE,GAAG;;;;;;;;;;;;;;;;;iCAiBsB,CAAA;AAEjC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;OAeR,CAAA;AAEP,MAAM,KAAK,GAAG;;;;;;;;;;;;uCAYyB,CAAA;AAEvC,MAAM,IAAI,GAAG;;;;;;;;;;uDAU0C,CAAA;AAEvD,MAAM,EAAE,GAAG;;;;;;;;;;;;;;;KAeN,CAAA;AAEL,MAAM,aAAa,GAAG;;;;;;;;KAQjB,CAAA;AAEL,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAmEA,CAAA;AAE1B,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;0DAewC,CAAA;AAE1D,MAAM,iBAAiB,GAAG;;;;;;;;4DAQkC,CAAA;AAE5D,kEAAkE;AAClE,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;qBAKV,WAAW,GAAG,iBAAiB,GAAG,OAAO,GAAG,iBAAiB,GAAG,SAAS,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,GAAG,KAAK,GAAG,IAAI;;CAE/H,CAAA;AAED,gEAAgE;AAChE,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;qBAKV,WAAW,GAAG,OAAO,GAAG,EAAE,GAAG,EAAE,GAAG,aAAa,GAAG,MAAM,GAAG,IAAI;;CAEnF,CAAA"}