typebulb 0.58.0 → 0.58.1
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 +8 -7
- package/SKILL.md +10 -9
- package/dist/agents/claude/client.js +2 -3
- package/dist/agents/codex/client.js +2 -3
- package/dist/agents/pi/client.js +2 -3
- package/dist/dts/tbTypings.d.ts.map +1 -1
- package/dist/dts/tbTypings.js +284 -281
- package/dist/dts/tbTypings.js.map +1 -1
- package/dist/format/runtimeState.d.ts +9 -8
- package/dist/format/runtimeState.d.ts.map +1 -1
- package/dist/format/runtimeState.js +63 -62
- package/dist/format/runtimeState.js.map +1 -1
- package/dist/index.js +18 -16
- package/dist/render.js +2 -3
- package/dist/servers.js +2 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -192,13 +192,13 @@ everywhere.
|
|
|
192
192
|
|-----|--------------|:-----------:|
|
|
193
193
|
| `tb.data(n)` / `tb.json(n)` | Read data chunk `n` from the `data.txt` block — raw string, or parsed JSON | |
|
|
194
194
|
| `tb.insight()` | Read the `insight.json` block as JSON | |
|
|
195
|
-
| `tb.setData(chunks)` / `tb.setInsight(v)` | Replace this run's data / insight
|
|
195
|
+
| `tb.setData(chunks)` / `tb.setInsight(v)` | Replace this run's data / insight; the URL fragment follows, the file is untouched | |
|
|
196
196
|
| `tb.theme` | Get/set the light/dark override; `undefined` follows the OS | |
|
|
197
197
|
| `tb.mode` | Runtime mode — `'local'` (CLI) or `'inline'` (sandboxed iframe); `'ide'`/`'published'` on typebulb.com | |
|
|
198
198
|
| `tb.proxy(url)` | Rewrite a CDN URL to load through the host origin (Web Worker / WASM) | |
|
|
199
199
|
| `tb.dump(...)` | Log values (incl. lazy / device-backed tensors) to the browser console | |
|
|
200
200
|
| `tb.copy(text)` | Copy text to the clipboard | |
|
|
201
|
-
| `tb.url()` | Get the bulb URL (the served localhost URL, locally) | |
|
|
201
|
+
| `tb.url()` | Get the bulb's canonical URL, `#tb=` fragment included (the served localhost URL, locally) | |
|
|
202
202
|
| `tb.models()` | List available AI models (for dynamic model selectors); the `.env` default is flagged (`default: true`); returns `[]` when inline (no host AI) | |
|
|
203
203
|
| `tb.aiAccess()` | What backs `tb.ai` — `'own' \| 'courtesy' \| 'none'` | |
|
|
204
204
|
| `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 | |
|
|
@@ -511,17 +511,18 @@ the whole file; `put` replaces it blind, knowing neither its size, its format, n
|
|
|
511
511
|
## Runtime data (`tb.setData`)
|
|
512
512
|
|
|
513
513
|
A bulb that computes its own results (a scrape, a batch score, a simulation, a tournament) can swap
|
|
514
|
-
them in as the data it's running on, and
|
|
514
|
+
them in as the data it's running on, and the page URL then carries them:
|
|
515
515
|
|
|
516
516
|
```ts
|
|
517
|
-
|
|
518
|
-
|
|
517
|
+
await tb.setData(JSON.stringify(results))
|
|
518
|
+
tb.copy(await tb.url())
|
|
519
519
|
```
|
|
520
520
|
|
|
521
521
|
- `tb.data()` / `tb.json()` return the new chunks for the rest of the page, and the URL's `#tb=` fragment holds them, so reloading or opening that link restores the run. `tb.setInsight(value)` is the same for `tb.insight()`.
|
|
522
|
+
- **The write is the URL update.** Awaiting a setter means the address bar holds the run. Nothing comes back: `tb.url()` is how you read the link. On typebulb.com the fragment lands in fullscreen, exactly as `tb.infer()` does; an inline bulb has no URL of its own, so there the swap happens and nothing else does.
|
|
522
523
|
- **Runtime only, never the file.** A reload without the fragment is back on the bulb's own `data.txt`. Two gestures promote a run to source, and no `tb.*` call does: `typebulb put` from the terminal, or `tb.infer()` to raise the modal and press **Save to bulb**, which files whatever the page holds — an LLM call is not needed and the size ceiling does not apply (needs `--trust`).
|
|
523
|
-
- **The link has a size ceiling; the file doesn't.** Around 60KB encoded, so pass what a share needs rather than everything.
|
|
524
|
-
- **Set only what you changed.** An unset slot stays out of the link and falls through to the bulb's own block, so a data-only run doesn't drag a copy of `insight.json` along. Both in one tick is one encode
|
|
524
|
+
- **The link has a size ceiling; the file doesn't.** Around 60KB encoded, so pass what a share needs rather than everything. Over it the run still swaps, but the fragment clears and there is no link.
|
|
525
|
+
- **Set only what you changed.** An unset slot stays out of the link and falls through to the bulb's own block, so a data-only run doesn't drag a copy of `insight.json` along. Both in one tick is one encode: `tb.setData(d); await tb.setInsight(i)`.
|
|
525
526
|
|
|
526
527
|
## Charts
|
|
527
528
|
|
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.58.
|
|
4
|
+
version: 0.58.1
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
> Generated from typebulb v0.58.
|
|
7
|
+
> Generated from typebulb v0.58.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.
|
|
8
8
|
|
|
9
9
|
# typebulb
|
|
10
10
|
|
|
@@ -200,13 +200,13 @@ everywhere.
|
|
|
200
200
|
|-----|--------------|:-----------:|
|
|
201
201
|
| `tb.data(n)` / `tb.json(n)` | Read data chunk `n` from the `data.txt` block — raw string, or parsed JSON | |
|
|
202
202
|
| `tb.insight()` | Read the `insight.json` block as JSON | |
|
|
203
|
-
| `tb.setData(chunks)` / `tb.setInsight(v)` | Replace this run's data / insight
|
|
203
|
+
| `tb.setData(chunks)` / `tb.setInsight(v)` | Replace this run's data / insight; the URL fragment follows, the file is untouched | |
|
|
204
204
|
| `tb.theme` | Get/set the light/dark override; `undefined` follows the OS | |
|
|
205
205
|
| `tb.mode` | Runtime mode — `'local'` (CLI) or `'inline'` (sandboxed iframe); `'ide'`/`'published'` on typebulb.com | |
|
|
206
206
|
| `tb.proxy(url)` | Rewrite a CDN URL to load through the host origin (Web Worker / WASM) | |
|
|
207
207
|
| `tb.dump(...)` | Log values (incl. lazy / device-backed tensors) to the browser console | |
|
|
208
208
|
| `tb.copy(text)` | Copy text to the clipboard | |
|
|
209
|
-
| `tb.url()` | Get the bulb URL (the served localhost URL, locally) | |
|
|
209
|
+
| `tb.url()` | Get the bulb's canonical URL, `#tb=` fragment included (the served localhost URL, locally) | |
|
|
210
210
|
| `tb.models()` | List available AI models (for dynamic model selectors); the `.env` default is flagged (`default: true`); returns `[]` when inline (no host AI) | |
|
|
211
211
|
| `tb.aiAccess()` | What backs `tb.ai` — `'own' \| 'courtesy' \| 'none'` | |
|
|
212
212
|
| `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 | |
|
|
@@ -519,17 +519,18 @@ the whole file; `put` replaces it blind, knowing neither its size, its format, n
|
|
|
519
519
|
## Runtime data (`tb.setData`)
|
|
520
520
|
|
|
521
521
|
A bulb that computes its own results (a scrape, a batch score, a simulation, a tournament) can swap
|
|
522
|
-
them in as the data it's running on, and
|
|
522
|
+
them in as the data it's running on, and the page URL then carries them:
|
|
523
523
|
|
|
524
524
|
```ts
|
|
525
|
-
|
|
526
|
-
|
|
525
|
+
await tb.setData(JSON.stringify(results))
|
|
526
|
+
tb.copy(await tb.url())
|
|
527
527
|
```
|
|
528
528
|
|
|
529
529
|
- `tb.data()` / `tb.json()` return the new chunks for the rest of the page, and the URL's `#tb=` fragment holds them, so reloading or opening that link restores the run. `tb.setInsight(value)` is the same for `tb.insight()`.
|
|
530
|
+
- **The write is the URL update.** Awaiting a setter means the address bar holds the run. Nothing comes back: `tb.url()` is how you read the link. On typebulb.com the fragment lands in fullscreen, exactly as `tb.infer()` does; an inline bulb has no URL of its own, so there the swap happens and nothing else does.
|
|
530
531
|
- **Runtime only, never the file.** A reload without the fragment is back on the bulb's own `data.txt`. Two gestures promote a run to source, and no `tb.*` call does: `typebulb put` from the terminal, or `tb.infer()` to raise the modal and press **Save to bulb**, which files whatever the page holds — an LLM call is not needed and the size ceiling does not apply (needs `--trust`).
|
|
531
|
-
- **The link has a size ceiling; the file doesn't.** Around 60KB encoded, so pass what a share needs rather than everything.
|
|
532
|
-
- **Set only what you changed.** An unset slot stays out of the link and falls through to the bulb's own block, so a data-only run doesn't drag a copy of `insight.json` along. Both in one tick is one encode
|
|
532
|
+
- **The link has a size ceiling; the file doesn't.** Around 60KB encoded, so pass what a share needs rather than everything. Over it the run still swaps, but the fragment clears and there is no link.
|
|
533
|
+
- **Set only what you changed.** An unset slot stays out of the link and falls through to the bulb's own block, so a data-only run doesn't drag a copy of `insight.json` along. Both in one tick is one encode: `tb.setData(d); await tb.setInsight(i)`.
|
|
533
534
|
|
|
534
535
|
## Charts
|
|
535
536
|
|
|
@@ -1109,8 +1109,8 @@ ${V9t}
|
|
|
1109
1109
|
// tb.setData / tb.setInsight (TB-State.md), on the engine both hosts share. The globals and the
|
|
1110
1110
|
// transport are all this host owns: encoding round-trips to the server because the page carries
|
|
1111
1111
|
// no fflate on purpose, and the fragment write is the address bar the setters exist to fill.
|
|
1112
|
-
// Resolves
|
|
1113
|
-
//
|
|
1112
|
+
// Resolves once that write has landed, and hands back nothing: the link is then tb.url() like
|
|
1113
|
+
// any other. An inline bulb has no address bar, so its swap is page-local and never encodes.
|
|
1114
1114
|
window.__tbState.init({
|
|
1115
1115
|
applyData: (chunks) => { window.__TB_DATA__ = chunks; },
|
|
1116
1116
|
applyInsight: (json) => { window.__TB_INSIGHT__ = json; },
|
|
@@ -1128,7 +1128,6 @@ ${V9t}
|
|
|
1128
1128
|
// earlier fragment, or the address bar keeps addressing a run the page is no longer
|
|
1129
1129
|
// showing \u2014 and a reload would silently revert to it.
|
|
1130
1130
|
history.replaceState(null, '', location.pathname + location.search + hash);
|
|
1131
|
-
return hash ? location.href : undefined;
|
|
1132
1131
|
});
|
|
1133
1132
|
}
|
|
1134
1133
|
});
|
|
@@ -1109,8 +1109,8 @@ ${q9t}
|
|
|
1109
1109
|
// tb.setData / tb.setInsight (TB-State.md), on the engine both hosts share. The globals and the
|
|
1110
1110
|
// transport are all this host owns: encoding round-trips to the server because the page carries
|
|
1111
1111
|
// no fflate on purpose, and the fragment write is the address bar the setters exist to fill.
|
|
1112
|
-
// Resolves
|
|
1113
|
-
//
|
|
1112
|
+
// Resolves once that write has landed, and hands back nothing: the link is then tb.url() like
|
|
1113
|
+
// any other. An inline bulb has no address bar, so its swap is page-local and never encodes.
|
|
1114
1114
|
window.__tbState.init({
|
|
1115
1115
|
applyData: (chunks) => { window.__TB_DATA__ = chunks; },
|
|
1116
1116
|
applyInsight: (json) => { window.__TB_INSIGHT__ = json; },
|
|
@@ -1128,7 +1128,6 @@ ${q9t}
|
|
|
1128
1128
|
// earlier fragment, or the address bar keeps addressing a run the page is no longer
|
|
1129
1129
|
// showing \u2014 and a reload would silently revert to it.
|
|
1130
1130
|
history.replaceState(null, '', location.pathname + location.search + hash);
|
|
1131
|
-
return hash ? location.href : undefined;
|
|
1132
1131
|
});
|
|
1133
1132
|
}
|
|
1134
1133
|
});
|
package/dist/agents/pi/client.js
CHANGED
|
@@ -1109,8 +1109,8 @@ ${W9t}
|
|
|
1109
1109
|
// tb.setData / tb.setInsight (TB-State.md), on the engine both hosts share. The globals and the
|
|
1110
1110
|
// transport are all this host owns: encoding round-trips to the server because the page carries
|
|
1111
1111
|
// no fflate on purpose, and the fragment write is the address bar the setters exist to fill.
|
|
1112
|
-
// Resolves
|
|
1113
|
-
//
|
|
1112
|
+
// Resolves once that write has landed, and hands back nothing: the link is then tb.url() like
|
|
1113
|
+
// any other. An inline bulb has no address bar, so its swap is page-local and never encodes.
|
|
1114
1114
|
window.__tbState.init({
|
|
1115
1115
|
applyData: (chunks) => { window.__TB_DATA__ = chunks; },
|
|
1116
1116
|
applyInsight: (json) => { window.__TB_INSIGHT__ = json; },
|
|
@@ -1128,7 +1128,6 @@ ${W9t}
|
|
|
1128
1128
|
// earlier fragment, or the address bar keeps addressing a run the page is no longer
|
|
1129
1129
|
// showing \u2014 and a reload would silently revert to it.
|
|
1130
1130
|
history.replaceState(null, '', location.pathname + location.search + hash);
|
|
1131
|
-
return hash ? location.href : undefined;
|
|
1132
1131
|
});
|
|
1133
1132
|
}
|
|
1134
1133
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tbTypings.d.ts","sourceRoot":"","sources":["../../dts/src/tbTypings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"tbTypings.d.ts","sourceRoot":"","sources":["../../dts/src/tbTypings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAgTH,kEAAkE;AAClE,eAAO,MAAM,eAAe,QAO3B,CAAA;AASD;;;wDAGwD;AACxD,eAAO,MAAM,eAAe,QAO3B,CAAA"}
|
package/dist/dts/tbTypings.js
CHANGED
|
@@ -9,312 +9,315 @@
|
|
|
9
9
|
* TypeChecker, and the CLI's emitted typecheck dirs.
|
|
10
10
|
*/
|
|
11
11
|
import { modeUnion } from 'typebulb/format';
|
|
12
|
-
const dataAndJson = `
|
|
13
|
-
/**
|
|
14
|
-
* Get raw data chunk from the Data tab.
|
|
15
|
-
* @param index - Chunk index (0-based). Separate chunks with 2 blank lines.
|
|
16
|
-
*/
|
|
17
|
-
data(index: number): string;
|
|
18
|
-
/**
|
|
19
|
-
* Get data chunk parsed as JSON (handles JSON-ish with unquoted keys).
|
|
20
|
-
* @param index - Chunk index (0-based)
|
|
21
|
-
* @throws If chunk is not valid JSON/JSON-ish
|
|
22
|
-
*/
|
|
12
|
+
const dataAndJson = `
|
|
13
|
+
/**
|
|
14
|
+
* Get raw data chunk from the Data tab.
|
|
15
|
+
* @param index - Chunk index (0-based). Separate chunks with 2 blank lines.
|
|
16
|
+
*/
|
|
17
|
+
data(index: number): string;
|
|
18
|
+
/**
|
|
19
|
+
* Get data chunk parsed as JSON (handles JSON-ish with unquoted keys).
|
|
20
|
+
* @param index - Chunk index (0-based)
|
|
21
|
+
* @throws If chunk is not valid JSON/JSON-ish
|
|
22
|
+
*/
|
|
23
23
|
json<T = unknown>(index: number): T;`;
|
|
24
|
-
const insight = `
|
|
25
|
-
/**
|
|
26
|
-
* Get the insight data produced by the inference layer.
|
|
27
|
-
*
|
|
28
|
-
* Returns the parsed JSON from insight.json, populated by the inference LLM.
|
|
29
|
-
* Use a type parameter to get typed access to the insight data.
|
|
30
|
-
*
|
|
31
|
-
* @returns The parsed insight JSON, or undefined if no insight is available
|
|
32
|
-
*/
|
|
24
|
+
const insight = `
|
|
25
|
+
/**
|
|
26
|
+
* Get the insight data produced by the inference layer.
|
|
27
|
+
*
|
|
28
|
+
* Returns the parsed JSON from insight.json, populated by the inference LLM.
|
|
29
|
+
* Use a type parameter to get typed access to the insight data.
|
|
30
|
+
*
|
|
31
|
+
* @returns The parsed insight JSON, or undefined if no insight is available
|
|
32
|
+
*/
|
|
33
33
|
insight<T = unknown>(): T | undefined;`;
|
|
34
34
|
// Runtime-state writers — duals of tb.data()/tb.json() and tb.insight() (TB-State.md).
|
|
35
|
-
const runtimeStateWriters = `
|
|
36
|
-
/**
|
|
37
|
-
* Replace the data this run is working on
|
|
38
|
-
*
|
|
39
|
-
* Updates what \`tb.data()\` / \`tb.json()\` return for the rest of this page, and writes the
|
|
40
|
-
* result into the URL fragment so the address bar addresses this run
|
|
41
|
-
* \`data.txt\` is never touched: a reload without the fragment goes back
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* @
|
|
45
|
-
*
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
|
|
57
|
-
|
|
35
|
+
const runtimeStateWriters = `
|
|
36
|
+
/**
|
|
37
|
+
* Replace the data this run is working on.
|
|
38
|
+
*
|
|
39
|
+
* Updates what \`tb.data()\` / \`tb.json()\` return for the rest of this page, and writes the
|
|
40
|
+
* result into the URL fragment, so the address bar addresses this run and \`tb.url()\` carries
|
|
41
|
+
* it. The bulb's source \`data.txt\` is never touched: a reload without the fragment goes back
|
|
42
|
+
* to the file.
|
|
43
|
+
*
|
|
44
|
+
* @param chunks - The new data chunks (a bare string is treated as one chunk)
|
|
45
|
+
* @returns Resolves once the address bar reflects the write. Data too large to fit a URL
|
|
46
|
+
* (~60KB encoded) still swaps, but carries no fragment: pass what a share needs, not
|
|
47
|
+
* necessarily everything.
|
|
48
|
+
*/
|
|
49
|
+
setData(chunks: string | string[]): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Replace the insight this run is working on.
|
|
52
|
+
*
|
|
53
|
+
* The \`tb.insight()\` counterpart of \`setData\`, with the same fragment behavior and the same
|
|
54
|
+
* rule: runtime only, never the bulb's source \`insight.json\`.
|
|
55
|
+
*
|
|
56
|
+
* @param value - Any JSON-serializable value
|
|
57
|
+
*/
|
|
58
|
+
setInsight(value: unknown): Promise<void>;`;
|
|
58
59
|
// One options shape, reused by tb.ai() and tb.ai.stream().
|
|
59
|
-
const aiOptions = `options: {
|
|
60
|
-
messages: Array<{ role: "user" | "assistant"; content: string }>;
|
|
61
|
-
system?: string;
|
|
62
|
-
/** Reasoning effort: 0=minimal, 1=low, 2=med, 3=high, 4=xhigh. Mapped to each provider's native mechanism (OpenAI/OpenRouter reasoning effort, Gemini thinking level, Anthropic adaptive thinking). 0 reaches the vendor's explicit off switch, and floors to a little thinking only on always-thinking models. 4 reaches the rung above high where the vendor has one (OpenAI/OpenRouter xhigh, Anthropic max) and clamps to high where it doesn't (Gemini) — never an error. Pin 1 (low) for most work; pick 0 deliberately, when you want the raw model or latency is the constraint. Omitting is NOT off — it buys the model's own default (medium on GPT-5.6), which reasons and bills invisibly. Ignored on the courtesy model, which runs at the level Typebulb sets. */
|
|
63
|
-
effort?: 0 | 1 | 2 | 3 | 4;
|
|
64
|
-
provider?: string;
|
|
65
|
-
model?: string;
|
|
66
|
-
/** Give the model a web-search tool, billed to the user's key. Off by default; the free model never searches. */
|
|
67
|
-
webSearch?: boolean;
|
|
68
|
-
/** Abort the request. On abort the promise rejects / the stream ends. */
|
|
69
|
-
signal?: AbortSignal;
|
|
60
|
+
const aiOptions = `options: {
|
|
61
|
+
messages: Array<{ role: "user" | "assistant"; content: string }>;
|
|
62
|
+
system?: string;
|
|
63
|
+
/** Reasoning effort: 0=minimal, 1=low, 2=med, 3=high, 4=xhigh. Mapped to each provider's native mechanism (OpenAI/OpenRouter reasoning effort, Gemini thinking level, Anthropic adaptive thinking). 0 reaches the vendor's explicit off switch, and floors to a little thinking only on always-thinking models. 4 reaches the rung above high where the vendor has one (OpenAI/OpenRouter xhigh, Anthropic max) and clamps to high where it doesn't (Gemini) — never an error. Pin 1 (low) for most work; pick 0 deliberately, when you want the raw model or latency is the constraint. Omitting is NOT off — it buys the model's own default (medium on GPT-5.6), which reasons and bills invisibly. Ignored on the courtesy model, which runs at the level Typebulb sets. */
|
|
64
|
+
effort?: 0 | 1 | 2 | 3 | 4;
|
|
65
|
+
provider?: string;
|
|
66
|
+
model?: string;
|
|
67
|
+
/** Give the model a web-search tool, billed to the user's key. Off by default; the free model never searches. */
|
|
68
|
+
webSearch?: boolean;
|
|
69
|
+
/** Abort the request. On abort the promise rejects / the stream ends. */
|
|
70
|
+
signal?: AbortSignal;
|
|
70
71
|
}`;
|
|
71
72
|
/** A streamed delta from \`tb.ai.stream()\` — a discriminated union, exactly one kind per chunk. */
|
|
72
|
-
const aiChunkType = `
|
|
73
|
-
/** Provider-reported token counts for one call — real usage from the API, never an estimate.
|
|
74
|
-
* \`output\` is total billed output (reasoning included); \`reasoning\` is the itemized subset where
|
|
75
|
-
* the provider splits it (OpenAI, Gemini, OpenRouter — Anthropic doesn't); \`cacheRead\` is the
|
|
76
|
-
* cached subset of \`input\`. */
|
|
77
|
-
type AiUsage = { input: number; output: number; reasoning?: number; cacheRead?: number };
|
|
78
|
-
|
|
79
|
-
/** A single streamed delta from \`tb.ai.stream()\`. Discriminated by \`kind\`. */
|
|
80
|
-
type AiChunk =
|
|
81
|
-
| { kind: "text"; text: string }
|
|
82
|
-
| { kind: "reasoning"; text: string }
|
|
83
|
-
| { kind: "usage"; usage: AiUsage };
|
|
73
|
+
const aiChunkType = `
|
|
74
|
+
/** Provider-reported token counts for one call — real usage from the API, never an estimate.
|
|
75
|
+
* \`output\` is total billed output (reasoning included); \`reasoning\` is the itemized subset where
|
|
76
|
+
* the provider splits it (OpenAI, Gemini, OpenRouter — Anthropic doesn't); \`cacheRead\` is the
|
|
77
|
+
* cached subset of \`input\`. */
|
|
78
|
+
type AiUsage = { input: number; output: number; reasoning?: number; cacheRead?: number };
|
|
79
|
+
|
|
80
|
+
/** A single streamed delta from \`tb.ai.stream()\`. Discriminated by \`kind\`. */
|
|
81
|
+
type AiChunk =
|
|
82
|
+
| { kind: "text"; text: string }
|
|
83
|
+
| { kind: "reasoning"; text: string }
|
|
84
|
+
| { kind: "usage"; usage: AiUsage };
|
|
84
85
|
`;
|
|
85
86
|
/** What \`tb.aiAccess()\` answers. Named so bulbs can hold it in state without respelling the union. */
|
|
86
|
-
const aiAccessType = `
|
|
87
|
-
/** What backs \`tb.ai\`: the user's own keys, the quota-limited courtesy model, or nothing. */
|
|
88
|
-
type AiAccess = "own" | "courtesy" | "none";
|
|
87
|
+
const aiAccessType = `
|
|
88
|
+
/** What backs \`tb.ai\`: the user's own keys, the quota-limited courtesy model, or nothing. */
|
|
89
|
+
type AiAccess = "own" | "courtesy" | "none";
|
|
89
90
|
`;
|
|
90
|
-
const ai = `
|
|
91
|
-
/**
|
|
92
|
-
* General-purpose AI call. \`tb.ai(opts)\` resolves with the full text; \`tb.ai.stream(opts)\`
|
|
93
|
-
* returns an async iterable of {@link AiChunk} deltas you consume with \`for await\`.
|
|
94
|
-
*
|
|
95
|
-
* @returns Promise resolving to { text, usage? } — \`usage\` is the provider-reported token
|
|
96
|
-
* counts ({@link AiUsage}), absent when the provider reported none
|
|
97
|
-
* @throws On rate limit, network error, or provider error
|
|
98
|
-
*/
|
|
99
|
-
ai: {
|
|
100
|
-
(${aiOptions}): Promise<{ text: string; usage?: AiUsage }>;
|
|
101
|
-
/**
|
|
102
|
-
* Streaming counterpart of \`tb.ai()\`. Yields \`{ kind: "text" | "reasoning", text }\` deltas
|
|
103
|
-
* as they arrive, then one final \`{ kind: "usage", usage }\` chunk with the call's token
|
|
104
|
-
* counts (when the provider reported them); break the loop (or abort \`signal\`) to cancel
|
|
105
|
-
* and stop the upstream. Match \`kind\` positively — a bare \`else\` branch that assumes
|
|
106
|
-
* "not reasoning means text" will misread the usage chunk.
|
|
107
|
-
*
|
|
108
|
-
* \`kind: "reasoning"\` deltas only arrive when you pass \`effort: 1-4\` AND use a
|
|
109
|
-
* thinking-capable model; otherwise the stream is \`text\`-only.
|
|
110
|
-
*
|
|
111
|
-
* @example
|
|
112
|
-
* let answer = "";
|
|
113
|
-
* for await (const c of tb.ai.stream({ messages })) {
|
|
114
|
-
* if (c.kind === "text") answer += c.text;
|
|
115
|
-
* }
|
|
116
|
-
*/
|
|
117
|
-
stream(${aiOptions}): AsyncIterable<AiChunk>;
|
|
91
|
+
const ai = `
|
|
92
|
+
/**
|
|
93
|
+
* General-purpose AI call. \`tb.ai(opts)\` resolves with the full text; \`tb.ai.stream(opts)\`
|
|
94
|
+
* returns an async iterable of {@link AiChunk} deltas you consume with \`for await\`.
|
|
95
|
+
*
|
|
96
|
+
* @returns Promise resolving to { text, usage? } — \`usage\` is the provider-reported token
|
|
97
|
+
* counts ({@link AiUsage}), absent when the provider reported none
|
|
98
|
+
* @throws On rate limit, network error, or provider error
|
|
99
|
+
*/
|
|
100
|
+
ai: {
|
|
101
|
+
(${aiOptions}): Promise<{ text: string; usage?: AiUsage }>;
|
|
102
|
+
/**
|
|
103
|
+
* Streaming counterpart of \`tb.ai()\`. Yields \`{ kind: "text" | "reasoning", text }\` deltas
|
|
104
|
+
* as they arrive, then one final \`{ kind: "usage", usage }\` chunk with the call's token
|
|
105
|
+
* counts (when the provider reported them); break the loop (or abort \`signal\`) to cancel
|
|
106
|
+
* and stop the upstream. Match \`kind\` positively — a bare \`else\` branch that assumes
|
|
107
|
+
* "not reasoning means text" will misread the usage chunk.
|
|
108
|
+
*
|
|
109
|
+
* \`kind: "reasoning"\` deltas only arrive when you pass \`effort: 1-4\` AND use a
|
|
110
|
+
* thinking-capable model; otherwise the stream is \`text\`-only.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* let answer = "";
|
|
114
|
+
* for await (const c of tb.ai.stream({ messages })) {
|
|
115
|
+
* if (c.kind === "text") answer += c.text;
|
|
116
|
+
* }
|
|
117
|
+
*/
|
|
118
|
+
stream(${aiOptions}): AsyncIterable<AiChunk>;
|
|
118
119
|
};`;
|
|
119
|
-
const models = `
|
|
120
|
-
/**
|
|
121
|
-
* Returns AI models available to the current user.
|
|
122
|
-
* Models are filtered by the user's configured API keys.
|
|
123
|
-
* If no keys are configured, returns only the courtesy model.
|
|
124
|
-
*/
|
|
125
|
-
models(): Promise<Array<{
|
|
126
|
-
/** Provider protocol: "anthropic", "openai", "gemini", "openrouter" */
|
|
127
|
-
provider: string;
|
|
128
|
-
/** Model identifier, e.g. "claude-sonnet-4-6" */
|
|
129
|
-
name: string;
|
|
130
|
-
/** Human-readable display name, e.g. "Sonnet 4.6" */
|
|
131
|
-
friendlyName: string;
|
|
132
|
-
/** Provider display name, e.g. "Anthropic" */
|
|
133
|
-
providerName: string;
|
|
134
|
-
/** True on the .env-configured default model (TB_AI_PROVIDER + TB_AI_MODEL); absent otherwise */
|
|
135
|
-
default?: boolean;
|
|
136
|
-
}>>;
|
|
137
|
-
/**
|
|
138
|
-
* What backs \`tb.ai\` right now:
|
|
139
|
-
*
|
|
140
|
-
* - \`'own'\` — the user's own API keys (or their own local model server)
|
|
141
|
-
* - \`'courtesy'\` — the quota-limited courtesy model, fine for a call or two
|
|
142
|
-
* - \`'none'\` — no AI at all (the CLI with no keys, an inline bulb)
|
|
143
|
-
*
|
|
144
|
-
* A bulb making many AI calls should show a "use your own keys" notice
|
|
145
|
-
* instead of running unless this is \`'own'\`. Never derive it from
|
|
146
|
-
* \`tb.mode\` or the length of \`tb.models()\` — which hosts offer a courtesy
|
|
147
|
-
* model is the host's business and changes without your bulb changing.
|
|
148
|
-
*/
|
|
120
|
+
const models = `
|
|
121
|
+
/**
|
|
122
|
+
* Returns AI models available to the current user.
|
|
123
|
+
* Models are filtered by the user's configured API keys.
|
|
124
|
+
* If no keys are configured, returns only the courtesy model.
|
|
125
|
+
*/
|
|
126
|
+
models(): Promise<Array<{
|
|
127
|
+
/** Provider protocol: "anthropic", "openai", "gemini", "openrouter" */
|
|
128
|
+
provider: string;
|
|
129
|
+
/** Model identifier, e.g. "claude-sonnet-4-6" */
|
|
130
|
+
name: string;
|
|
131
|
+
/** Human-readable display name, e.g. "Sonnet 4.6" */
|
|
132
|
+
friendlyName: string;
|
|
133
|
+
/** Provider display name, e.g. "Anthropic" */
|
|
134
|
+
providerName: string;
|
|
135
|
+
/** True on the .env-configured default model (TB_AI_PROVIDER + TB_AI_MODEL); absent otherwise */
|
|
136
|
+
default?: boolean;
|
|
137
|
+
}>>;
|
|
138
|
+
/**
|
|
139
|
+
* What backs \`tb.ai\` right now:
|
|
140
|
+
*
|
|
141
|
+
* - \`'own'\` — the user's own API keys (or their own local model server)
|
|
142
|
+
* - \`'courtesy'\` — the quota-limited courtesy model, fine for a call or two
|
|
143
|
+
* - \`'none'\` — no AI at all (the CLI with no keys, an inline bulb)
|
|
144
|
+
*
|
|
145
|
+
* A bulb making many AI calls should show a "use your own keys" notice
|
|
146
|
+
* instead of running unless this is \`'own'\`. Never derive it from
|
|
147
|
+
* \`tb.mode\` or the length of \`tb.models()\` — which hosts offer a courtesy
|
|
148
|
+
* model is the host's business and changes without your bulb changing.
|
|
149
|
+
*/
|
|
149
150
|
aiAccess(): Promise<AiAccess>;`;
|
|
150
|
-
const theme = `
|
|
151
|
-
/**
|
|
152
|
-
* The bulb's theme override (\`<html data-theme>\`).
|
|
153
|
-
*
|
|
154
|
-
* - Get: the current override — \`'dark'\` | \`'light'\`, or \`undefined\` when
|
|
155
|
-
* following the OS preference.
|
|
156
|
-
* - Set \`'dark'\`/\`'light'\` to force and persist it (per-bulb); set
|
|
157
|
-
* \`undefined\` to clear the override and follow the OS again.
|
|
158
|
-
*
|
|
159
|
-
* Drives \`<html data-theme>\`, so render off \`html[data-theme="…"]\` selectors
|
|
160
|
-
* (or observe the attribute) rather than reading \`tb.theme\`.
|
|
161
|
-
*/
|
|
151
|
+
const theme = `
|
|
152
|
+
/**
|
|
153
|
+
* The bulb's theme override (\`<html data-theme>\`).
|
|
154
|
+
*
|
|
155
|
+
* - Get: the current override — \`'dark'\` | \`'light'\`, or \`undefined\` when
|
|
156
|
+
* following the OS preference.
|
|
157
|
+
* - Set \`'dark'\`/\`'light'\` to force and persist it (per-bulb); set
|
|
158
|
+
* \`undefined\` to clear the override and follow the OS again.
|
|
159
|
+
*
|
|
160
|
+
* Drives \`<html data-theme>\`, so render off \`html[data-theme="…"]\` selectors
|
|
161
|
+
* (or observe the attribute) rather than reading \`tb.theme\`.
|
|
162
|
+
*/
|
|
162
163
|
theme: 'light' | 'dark' | undefined;`;
|
|
163
|
-
const mode = `
|
|
164
|
-
/**
|
|
165
|
-
* The mode this bulb is running in.
|
|
166
|
-
*
|
|
167
|
-
* - \`'local'\` — Running via the typebulb CLI
|
|
168
|
-
* - \`'ide'\` — Running in the typebulb.com editor
|
|
169
|
-
* - \`'published'\` — Running as a published/standalone bulb on typebulb.com
|
|
170
|
-
* - \`'inline'\` — Running inline in an agent's transcript, rendered by the mirror
|
|
171
|
-
* (sandboxed, client-only: AI, filesystem, and server RPC are unavailable)
|
|
172
|
-
*/
|
|
164
|
+
const mode = `
|
|
165
|
+
/**
|
|
166
|
+
* The mode this bulb is running in.
|
|
167
|
+
*
|
|
168
|
+
* - \`'local'\` — Running via the typebulb CLI
|
|
169
|
+
* - \`'ide'\` — Running in the typebulb.com editor
|
|
170
|
+
* - \`'published'\` — Running as a published/standalone bulb on typebulb.com
|
|
171
|
+
* - \`'inline'\` — Running inline in an agent's transcript, rendered by the mirror
|
|
172
|
+
* (sandboxed, client-only: AI, filesystem, and server RPC are unavailable)
|
|
173
|
+
*/
|
|
173
174
|
mode: ${modeUnion};`;
|
|
174
|
-
const fs = `
|
|
175
|
-
/**
|
|
176
|
-
* Local filesystem access (CLI only).
|
|
177
|
-
*
|
|
178
|
-
* Relative paths resolve against the bulb's folder (\`tb.dir\` —
|
|
179
|
-
* \`<bulb-dir>/<filename-stem>/\`, created on demand), so
|
|
180
|
-
* \`tb.fs.write('results.json')\` lands beside the bulb. \`../\` reaches sibling
|
|
181
|
-
* bulbs' folders; everything stays confined to the project (the launch cwd).
|
|
182
|
-
* Throws in ide/published mode.
|
|
183
|
-
*/
|
|
184
|
-
fs: {
|
|
185
|
-
/** Read a file as UTF-8 text. Throws if the file is not valid UTF-8 — use readBytes for binary. */
|
|
186
|
-
read(path: string): Promise<string>;
|
|
187
|
-
/** Read a file as raw bytes. */
|
|
188
|
-
readBytes(path: string): Promise<Uint8Array>;
|
|
189
|
-
/** Write text or raw bytes to a file. Creates parent directories if needed. */
|
|
190
|
-
write(path: string, content: string | Uint8Array): Promise<boolean>;
|
|
175
|
+
const fs = `
|
|
176
|
+
/**
|
|
177
|
+
* Local filesystem access (CLI only).
|
|
178
|
+
*
|
|
179
|
+
* Relative paths resolve against the bulb's folder (\`tb.dir\` —
|
|
180
|
+
* \`<bulb-dir>/<filename-stem>/\`, created on demand), so
|
|
181
|
+
* \`tb.fs.write('results.json')\` lands beside the bulb. \`../\` reaches sibling
|
|
182
|
+
* bulbs' folders; everything stays confined to the project (the launch cwd).
|
|
183
|
+
* Throws in ide/published mode.
|
|
184
|
+
*/
|
|
185
|
+
fs: {
|
|
186
|
+
/** Read a file as UTF-8 text. Throws if the file is not valid UTF-8 — use readBytes for binary. */
|
|
187
|
+
read(path: string): Promise<string>;
|
|
188
|
+
/** Read a file as raw bytes. */
|
|
189
|
+
readBytes(path: string): Promise<Uint8Array>;
|
|
190
|
+
/** Write text or raw bytes to a file. Creates parent directories if needed. */
|
|
191
|
+
write(path: string, content: string | Uint8Array): Promise<boolean>;
|
|
191
192
|
};`;
|
|
192
|
-
const dir = `
|
|
193
|
-
/**
|
|
194
|
-
* The bulb's folder — absolute path to \`<bulb-dir>/<filename-stem>/\`
|
|
195
|
-
* (or its \`batches/<name>/\` folder when the run is scoped with \`--batch\`).
|
|
196
|
-
*
|
|
197
|
-
* For interop only (handing a path to \`server.ts\` or a spawned tool):
|
|
198
|
-
* \`tb.fs\` already resolves relative paths against it, so bulb code writing
|
|
199
|
-
* its own files never needs it. CLI only — throws in ide/published/inline mode.
|
|
200
|
-
*/
|
|
193
|
+
const dir = `
|
|
194
|
+
/**
|
|
195
|
+
* The bulb's folder — absolute path to \`<bulb-dir>/<filename-stem>/\`
|
|
196
|
+
* (or its \`batches/<name>/\` folder when the run is scoped with \`--batch\`).
|
|
197
|
+
*
|
|
198
|
+
* For interop only (handing a path to \`server.ts\` or a spawned tool):
|
|
199
|
+
* \`tb.fs\` already resolves relative paths against it, so bulb code writing
|
|
200
|
+
* its own files never needs it. CLI only — throws in ide/published/inline mode.
|
|
201
|
+
*/
|
|
201
202
|
readonly dir: string;`;
|
|
202
|
-
const clientOnlyMembers = `
|
|
203
|
-
/**
|
|
204
|
-
* Async value inspector for tensor-like objects.
|
|
205
|
-
*
|
|
206
|
-
* Materializes lazy values (like GPU tensors) and logs them with metadata.
|
|
207
|
-
* Handles objects with \`.js()\`, \`.data()\`, \`.array()\`, \`.arraySync()\`, etc.
|
|
208
|
-
*
|
|
209
|
-
* @remarks
|
|
210
|
-
* - Always use \`await\` - materialization may be async (GPU→CPU readback)
|
|
211
|
-
* - Large values are truncated (max 1000 elements)
|
|
212
|
-
* - Promises are logged as \`[Promise]\` (not awaited - could hang)
|
|
213
|
-
*/
|
|
214
|
-
dump(...args: any[]): Promise<void>;
|
|
215
|
-
/**
|
|
216
|
-
* Trigger inference to generate new insight data.
|
|
217
|
-
*
|
|
218
|
-
* Opens a confirmation modal showing the data to be analyzed, then streams
|
|
219
|
-
* the inference result. On success, updates the insight so subsequent
|
|
220
|
-
* \`tb.insight()\` calls return the new value.
|
|
221
|
-
*
|
|
222
|
-
* @param opts - Options for inference
|
|
223
|
-
* @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.
|
|
224
|
-
* @returns Promise that resolves with the parsed insight JSON
|
|
225
|
-
* @throws If inference is already in progress, or on network/parse/rate limit errors
|
|
226
|
-
*/
|
|
227
|
-
infer<T = unknown>(opts?: { data?: string | string[] }): Promise<T>;
|
|
228
|
-
/**
|
|
229
|
-
* Proxy a CDN URL through the sandbox origin for Web Worker/WASM same-origin loading.
|
|
230
|
-
*
|
|
231
|
-
* In the sandbox, prepends \`/proxy/\` so the URL is served from the same origin.
|
|
232
|
-
* Outside the sandbox (exported HTML, CLI), returns the URL unchanged.
|
|
233
|
-
*
|
|
234
|
-
* @param url - Full HTTPS URL to an allowlisted CDN (esm.sh, unpkg.com, cdn.jsdelivr.net, cdnjs.cloudflare.com)
|
|
235
|
-
* @returns The proxied URL (sandbox) or the original URL (standalone/CLI)
|
|
236
|
-
*/
|
|
237
|
-
proxy(url: string): string;
|
|
238
|
-
/**
|
|
239
|
-
* Copy text to clipboard.
|
|
240
|
-
* Must be called synchronously within a user gesture (click/keydown).
|
|
241
|
-
* @returns true if successful, false otherwise
|
|
242
|
-
*/
|
|
243
|
-
copy(text: string): Promise<boolean>;
|
|
244
|
-
/**
|
|
245
|
-
* Get the canonical URL of this bulb.
|
|
246
|
-
*
|
|
247
|
-
* Returns the parent typebulb.com URL (including
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
|
|
203
|
+
const clientOnlyMembers = `
|
|
204
|
+
/**
|
|
205
|
+
* Async value inspector for tensor-like objects.
|
|
206
|
+
*
|
|
207
|
+
* Materializes lazy values (like GPU tensors) and logs them with metadata.
|
|
208
|
+
* Handles objects with \`.js()\`, \`.data()\`, \`.array()\`, \`.arraySync()\`, etc.
|
|
209
|
+
*
|
|
210
|
+
* @remarks
|
|
211
|
+
* - Always use \`await\` - materialization may be async (GPU→CPU readback)
|
|
212
|
+
* - Large values are truncated (max 1000 elements)
|
|
213
|
+
* - Promises are logged as \`[Promise]\` (not awaited - could hang)
|
|
214
|
+
*/
|
|
215
|
+
dump(...args: any[]): Promise<void>;
|
|
216
|
+
/**
|
|
217
|
+
* Trigger inference to generate new insight data.
|
|
218
|
+
*
|
|
219
|
+
* Opens a confirmation modal showing the data to be analyzed, then streams
|
|
220
|
+
* the inference result. On success, updates the insight so subsequent
|
|
221
|
+
* \`tb.insight()\` calls return the new value.
|
|
222
|
+
*
|
|
223
|
+
* @param opts - Options for inference
|
|
224
|
+
* @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.
|
|
225
|
+
* @returns Promise that resolves with the parsed insight JSON
|
|
226
|
+
* @throws If inference is already in progress, or on network/parse/rate limit errors
|
|
227
|
+
*/
|
|
228
|
+
infer<T = unknown>(opts?: { data?: string | string[] }): Promise<T>;
|
|
229
|
+
/**
|
|
230
|
+
* Proxy a CDN URL through the sandbox origin for Web Worker/WASM same-origin loading.
|
|
231
|
+
*
|
|
232
|
+
* In the sandbox, prepends \`/proxy/\` so the URL is served from the same origin.
|
|
233
|
+
* Outside the sandbox (exported HTML, CLI), returns the URL unchanged.
|
|
234
|
+
*
|
|
235
|
+
* @param url - Full HTTPS URL to an allowlisted CDN (esm.sh, unpkg.com, cdn.jsdelivr.net, cdnjs.cloudflare.com)
|
|
236
|
+
* @returns The proxied URL (sandbox) or the original URL (standalone/CLI)
|
|
237
|
+
*/
|
|
238
|
+
proxy(url: string): string;
|
|
239
|
+
/**
|
|
240
|
+
* Copy text to clipboard.
|
|
241
|
+
* Must be called synchronously within a user gesture (click/keydown).
|
|
242
|
+
* @returns true if successful, false otherwise
|
|
243
|
+
*/
|
|
244
|
+
copy(text: string): Promise<boolean>;
|
|
245
|
+
/**
|
|
246
|
+
* Get the canonical URL of this bulb.
|
|
247
|
+
*
|
|
248
|
+
* Returns the parent typebulb.com URL (including query and \`#tb=\` fragment), resolving
|
|
249
|
+
* correctly from inside the cross-origin sandbox iframe. That is the published \`/full\` page
|
|
250
|
+
* even while authoring in the IDE, because it is the link someone you send it to can open.
|
|
251
|
+
* Locally it is the served localhost URL. Use this instead of \`location.href\` or
|
|
252
|
+
* \`document.referrer\`.
|
|
253
|
+
*
|
|
254
|
+
* @returns The full canonical URL
|
|
255
|
+
*/
|
|
253
256
|
url(): Promise<string>;`;
|
|
254
|
-
const onMessage = `
|
|
255
|
-
/**
|
|
256
|
-
* Subscribe to a value pushed from the terminal via \`typebulb send <file> [message]\`.
|
|
257
|
-
*
|
|
258
|
-
* The dual of \`tb.log\` (data out): a value sent *in* from the CLI, no \`--trust\` required.
|
|
259
|
-
* Use it to start expensive work on demand instead of on load — e.g. \`tb.onMessage(() => start())\`
|
|
260
|
-
* — so hot reloads don't re-trigger it while you edit, and an agent kicks off one run when ready.
|
|
261
|
-
*
|
|
262
|
-
* The message is the JSON-parsed value of what \`send\` was given (or the raw string if it isn't
|
|
263
|
-
* JSON; \`undefined\` for a bare \`typebulb send <file>\`). A non-\`undefined\` return value (awaited)
|
|
264
|
-
* becomes the reply \`send --wait\` prints on stdout — JSON-serializable, at most one handler
|
|
265
|
-
* replying — the structured read-back for self-tests. Returns an unsubscribe function. Inert in
|
|
266
|
-
* an inline bulb (no sender) — the handler is registered but never fires.
|
|
267
|
-
*
|
|
268
|
-
* @param handler - Called with each pushed message; may return a JSON-serializable reply.
|
|
269
|
-
* @returns An unsubscribe function.
|
|
270
|
-
*/
|
|
257
|
+
const onMessage = `
|
|
258
|
+
/**
|
|
259
|
+
* Subscribe to a value pushed from the terminal via \`typebulb send <file> [message]\`.
|
|
260
|
+
*
|
|
261
|
+
* The dual of \`tb.log\` (data out): a value sent *in* from the CLI, no \`--trust\` required.
|
|
262
|
+
* Use it to start expensive work on demand instead of on load — e.g. \`tb.onMessage(() => start())\`
|
|
263
|
+
* — so hot reloads don't re-trigger it while you edit, and an agent kicks off one run when ready.
|
|
264
|
+
*
|
|
265
|
+
* The message is the JSON-parsed value of what \`send\` was given (or the raw string if it isn't
|
|
266
|
+
* JSON; \`undefined\` for a bare \`typebulb send <file>\`). A non-\`undefined\` return value (awaited)
|
|
267
|
+
* becomes the reply \`send --wait\` prints on stdout — JSON-serializable, at most one handler
|
|
268
|
+
* replying — the structured read-back for self-tests. Returns an unsubscribe function. Inert in
|
|
269
|
+
* an inline bulb (no sender) — the handler is registered but never fires.
|
|
270
|
+
*
|
|
271
|
+
* @param handler - Called with each pushed message; may return a JSON-serializable reply.
|
|
272
|
+
* @returns An unsubscribe function.
|
|
273
|
+
*/
|
|
271
274
|
onMessage(handler: (message: any) => unknown): () => void;`;
|
|
272
|
-
const log = `
|
|
273
|
-
/**
|
|
274
|
-
* Print to the CLI's stdout — the bulb's log channel, read back with \`typebulb logs <file>\`.
|
|
275
|
-
*
|
|
276
|
-
* Ungated: needs no \`server.ts\` block and no \`--trust\`, so a Restricted client-only bulb can
|
|
277
|
-
* instrument itself. Args cross to the CLI as JSON; where no CLI serves the page (web, inline,
|
|
278
|
-
* or a transport failure) it falls back to the browser console. Works in \`server.ts\` too (same
|
|
279
|
-
* as \`console.log\` there).
|
|
280
|
-
*/
|
|
275
|
+
const log = `
|
|
276
|
+
/**
|
|
277
|
+
* Print to the CLI's stdout — the bulb's log channel, read back with \`typebulb logs <file>\`.
|
|
278
|
+
*
|
|
279
|
+
* Ungated: needs no \`server.ts\` block and no \`--trust\`, so a Restricted client-only bulb can
|
|
280
|
+
* instrument itself. Args cross to the CLI as JSON; where no CLI serves the page (web, inline,
|
|
281
|
+
* or a transport failure) it falls back to the browser console. Works in \`server.ts\` too (same
|
|
282
|
+
* as \`console.log\` there).
|
|
283
|
+
*/
|
|
281
284
|
log(...args: any[]): void;`;
|
|
282
|
-
const clientServerProxy = `
|
|
283
|
-
/**
|
|
284
|
-
* Server-side function proxy.
|
|
285
|
-
*
|
|
286
|
-
* In the CLI, calls exported functions from the \`**server.ts**\` section.
|
|
287
|
-
*
|
|
288
|
-
* A normal export is awaited for its result (\`await tb.server.fn()\`). An \`async function*\`
|
|
289
|
-
* export streams: \`for await (const chunk of tb.server.gen())\`. The call object supports both;
|
|
290
|
-
* break the \`for await\` to cancel and tear down the server generator.
|
|
291
|
-
*/
|
|
285
|
+
const clientServerProxy = `
|
|
286
|
+
/**
|
|
287
|
+
* Server-side function proxy.
|
|
288
|
+
*
|
|
289
|
+
* In the CLI, calls exported functions from the \`**server.ts**\` section.
|
|
290
|
+
*
|
|
291
|
+
* A normal export is awaited for its result (\`await tb.server.fn()\`). An \`async function*\`
|
|
292
|
+
* export streams: \`for await (const chunk of tb.server.gen())\`. The call object supports both;
|
|
293
|
+
* break the \`for await\` to cancel and tear down the server generator.
|
|
294
|
+
*/
|
|
292
295
|
server: Record<string, (...args: any[]) => Promise<any> & AsyncIterable<any>>;`;
|
|
293
296
|
/** Typebulb globals available in browser-side code (code.tsx). */
|
|
294
|
-
export const clientTbTypings = `${aiChunkType}${aiAccessType}
|
|
295
|
-
/**
|
|
296
|
-
* Typebulb utilities namespace.
|
|
297
|
-
* Type \`tb.\` to discover available helpers.
|
|
298
|
-
*/
|
|
299
|
-
declare const tb: {${dataAndJson}${clientOnlyMembers}${insight}${runtimeStateWriters}${log}${clientServerProxy}${onMessage}${ai}${fs}${dir}${models}${theme}${mode}
|
|
300
|
-
};
|
|
297
|
+
export const clientTbTypings = `${aiChunkType}${aiAccessType}
|
|
298
|
+
/**
|
|
299
|
+
* Typebulb utilities namespace.
|
|
300
|
+
* Type \`tb.\` to discover available helpers.
|
|
301
|
+
*/
|
|
302
|
+
declare const tb: {${dataAndJson}${clientOnlyMembers}${insight}${runtimeStateWriters}${log}${clientServerProxy}${onMessage}${ai}${fs}${dir}${models}${theme}${mode}
|
|
303
|
+
};
|
|
301
304
|
`;
|
|
302
|
-
const serverLog = `
|
|
303
|
-
/**
|
|
304
|
-
* Print to the CLI's stdout — the same channel as \`console.log\` here (the server's console IS
|
|
305
|
-
* the bulb's log). One log verb across blocks: page-side \`tb.log\` reaches this same stdout.
|
|
306
|
-
*/
|
|
305
|
+
const serverLog = `
|
|
306
|
+
/**
|
|
307
|
+
* Print to the CLI's stdout — the same channel as \`console.log\` here (the server's console IS
|
|
308
|
+
* the bulb's log). One log verb across blocks: page-side \`tb.log\` reaches this same stdout.
|
|
309
|
+
*/
|
|
307
310
|
log(...args: any[]): void;`;
|
|
308
311
|
/** Typebulb globals available in Node-side code (server.ts).
|
|
309
312
|
* Only what carries a bulb-specific rule Node can't know — tb.ai, tb.fs, tb.dir (TB-FS.md);
|
|
310
313
|
* plus the tb.log uniformity exception (serverTb.ts). The browser-only helpers are intentionally
|
|
311
314
|
* absent. Must match serverTb.ts's runtime surface. */
|
|
312
|
-
export const serverTbTypings = `${aiChunkType}${aiAccessType}
|
|
313
|
-
/**
|
|
314
|
-
* Typebulb utilities namespace (server-side).
|
|
315
|
-
* Type \`tb.\` to discover available helpers.
|
|
316
|
-
*/
|
|
317
|
-
declare const tb: {${serverLog}${ai}${fs}${dir}${models}${mode}
|
|
318
|
-
};
|
|
315
|
+
export const serverTbTypings = `${aiChunkType}${aiAccessType}
|
|
316
|
+
/**
|
|
317
|
+
* Typebulb utilities namespace (server-side).
|
|
318
|
+
* Type \`tb.\` to discover available helpers.
|
|
319
|
+
*/
|
|
320
|
+
declare const tb: {${serverLog}${ai}${fs}${dir}${models}${mode}
|
|
321
|
+
};
|
|
319
322
|
`;
|
|
320
323
|
//# sourceMappingURL=tbTypings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tbTypings.js","sourceRoot":"","sources":["../../dts/src/tbTypings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,MAAM,WAAW,GAAG;;;;;;;;;;;uCAWmB,CAAA;AAEvC,MAAM,OAAO,GAAG;;;;;;;;;yCASyB,CAAA;AAEzC,uFAAuF;AACvF,MAAM,mBAAmB,GAAG
|
|
1
|
+
{"version":3,"file":"tbTypings.js","sourceRoot":"","sources":["../../dts/src/tbTypings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,MAAM,WAAW,GAAG;;;;;;;;;;;uCAWmB,CAAA;AAEvC,MAAM,OAAO,GAAG;;;;;;;;;yCASyB,CAAA;AAEzC,uFAAuF;AACvF,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;6CAuBiB,CAAA;AAE7C,2DAA2D;AAC3D,MAAM,SAAS,GAAG;;;;;;;;;;;IAWd,CAAA;AAEJ,oGAAoG;AACpG,MAAM,WAAW,GAAG;;;;;;;;;;;;CAYnB,CAAA;AAED,wGAAwG;AACxG,MAAM,YAAY,GAAG;;;CAGpB,CAAA;AAED,MAAM,EAAE,GAAG;;;;;;;;;;OAUJ,SAAS;;;;;;;;;;;;;;;;;aAiBH,SAAS;KACjB,CAAA;AAEL,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iCA8BkB,CAAA;AAEjC,MAAM,KAAK,GAAG;;;;;;;;;;;;uCAYyB,CAAA;AAEvC,MAAM,IAAI,GAAG;;;;;;;;;;UAUH,SAAS,GAAG,CAAA;AAEtB,MAAM,EAAE,GAAG;;;;;;;;;;;;;;;;;KAiBN,CAAA;AAEL,MAAM,GAAG,GAAG;;;;;;;;;wBASY,CAAA;AAExB,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BAqDA,CAAA;AAE1B,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;6DAiB2C,CAAA;AAE7D,MAAM,GAAG,GAAG;;;;;;;;;6BASiB,CAAA;AAE7B,MAAM,iBAAiB,GAAG;;;;;;;;;;iFAUuD,CAAA;AAEjF,kEAAkE;AAClE,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,WAAW,GAAG,YAAY;;;;;qBAKvC,WAAW,GAAG,iBAAiB,GAAG,OAAO,GAAG,mBAAmB,GAAG,GAAG,GAAG,iBAAiB,GAAG,SAAS,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,MAAM,GAAG,KAAK,GAAG,IAAI;;CAEjK,CAAA;AAED,MAAM,SAAS,GAAG;;;;;6BAKW,CAAA;AAE7B;;;wDAGwD;AACxD,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,WAAW,GAAG,YAAY;;;;;qBAKvC,SAAS,GAAG,EAAE,GAAG,EAAE,GAAG,GAAG,GAAG,MAAM,GAAG,IAAI;;CAE7D,CAAA"}
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
* init({ applyData, applyInsight, send })
|
|
9
9
|
* applyData(chunks) -> put the chunks in this host's data global
|
|
10
10
|
* applyInsight(json) -> put the serialized insight in this host's insight global
|
|
11
|
-
* send(pair) -> hand { data?, insight? } to this host's transport; resolve the
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* send(pair) -> hand { data?, insight? } to this host's transport; resolve once the
|
|
12
|
+
* write has landed. Must never reject: a transport failure costs the
|
|
13
|
+
* fragment, never the swap.
|
|
14
14
|
*
|
|
15
|
-
* setData(chunks) / setInsight(value) -> the two tb.* writers, resolving
|
|
15
|
+
* setData(chunks) / setInsight(value) -> the two tb.* writers, resolving once the address bar
|
|
16
|
+
* reflects the write. Neither hands back a link: the fragment is
|
|
17
|
+
* already in the URL, which a bulb reads with tb.url() like any other.
|
|
16
18
|
* seed(pair) -> apply and record what a decoded `#tb=` fragment carried, without sending it back
|
|
17
19
|
* pair() -> the wire pair, for a host that files it (the CLI modal's Save). Empty when
|
|
18
20
|
* nothing has been set, which is also how a host asks whether anything has.
|
|
@@ -24,10 +26,9 @@
|
|
|
24
26
|
* which stays exact for a null insight and needs no companion flag.
|
|
25
27
|
*
|
|
26
28
|
* Coalescing keys on whether the in-flight write has read the pair yet, not on whether one is
|
|
27
|
-
* outstanding. Writes landing before that ride it
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* and skipping the re-send would leave the address bar doing so (Invariant 6).
|
|
29
|
+
* outstanding. Writes landing before that ride it, one round trip for the pair; a write landing
|
|
30
|
+
* after it queues one re-send, because the write on the wire carries a state the page has moved
|
|
31
|
+
* past, and skipping the re-send would leave the address bar addressing it (Invariant 6).
|
|
31
32
|
*/
|
|
32
33
|
export declare const runtimeStateEngine = "(function () {\n var W = window;\n var rtData, rtInsight, rtHasInsight = false;\n var applyData, applyInsight, send;\n var pending = null, sent = false, queued = null;\n\n var pair = function () {\n var p = {};\n if (rtData !== undefined) p.data = rtData;\n if (rtHasInsight) p.insight = rtInsight;\n return p;\n };\n\n var start = function () {\n sent = false;\n return Promise.resolve()\n .then(function () { sent = true; return send(pair()); })\n .catch(function () { return undefined; })\n .finally(function () { pending = null; });\n };\n var publish = function () {\n if (!pending) { pending = start(); return pending; }\n if (!sent) return pending;\n if (!queued) queued = pending.then(function () { queued = null; return publish(); });\n return queued;\n };\n\n W.__tbState = {\n init: function (o) { applyData = o.applyData; applyInsight = o.applyInsight; send = o.send; },\n pair: pair,\n // Applied but not sent: the fragment already holds this, so sending it back would re-encode\n // the state the page just booted from.\n seed: function (p) {\n if (p.data !== undefined) { rtData = p.data; applyData(p.data); }\n if ('insight' in p) { rtInsight = p.insight; rtHasInsight = true; applyInsight(JSON.stringify(p.insight, null, 2)); }\n },\n // The globals move before the round trip, so a synchronous tb.data() right after the await is\n // never stale.\n setData: function (chunks) {\n var arr = Array.isArray(chunks) ? chunks : [chunks];\n rtData = arr;\n applyData(arr);\n return publish();\n },\n setInsight: function (value) {\n // Serialize before recording: a value JSON.stringify rejects (a cycle, a BigInt) must leave\n // the slot untouched, or every later setter carries a pair that can never be encoded or sent.\n var json = JSON.stringify(value, null, 2);\n rtInsight = value;\n rtHasInsight = true;\n applyInsight(json);\n return publish();\n }\n };\n})();";
|
|
33
34
|
//# sourceMappingURL=runtimeState.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtimeState.d.ts","sourceRoot":"","sources":["../../format/src/runtimeState.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"runtimeState.d.ts","sourceRoot":"","sources":["../../format/src/runtimeState.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,kBAAkB,4/DAsDzB,CAAA"}
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
* init({ applyData, applyInsight, send })
|
|
9
9
|
* applyData(chunks) -> put the chunks in this host's data global
|
|
10
10
|
* applyInsight(json) -> put the serialized insight in this host's insight global
|
|
11
|
-
* send(pair) -> hand { data?, insight? } to this host's transport; resolve the
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* send(pair) -> hand { data?, insight? } to this host's transport; resolve once the
|
|
12
|
+
* write has landed. Must never reject: a transport failure costs the
|
|
13
|
+
* fragment, never the swap.
|
|
14
14
|
*
|
|
15
|
-
* setData(chunks) / setInsight(value) -> the two tb.* writers, resolving
|
|
15
|
+
* setData(chunks) / setInsight(value) -> the two tb.* writers, resolving once the address bar
|
|
16
|
+
* reflects the write. Neither hands back a link: the fragment is
|
|
17
|
+
* already in the URL, which a bulb reads with tb.url() like any other.
|
|
16
18
|
* seed(pair) -> apply and record what a decoded `#tb=` fragment carried, without sending it back
|
|
17
19
|
* pair() -> the wire pair, for a host that files it (the CLI modal's Save). Empty when
|
|
18
20
|
* nothing has been set, which is also how a host asks whether anything has.
|
|
@@ -24,64 +26,63 @@
|
|
|
24
26
|
* which stays exact for a null insight and needs no companion flag.
|
|
25
27
|
*
|
|
26
28
|
* Coalescing keys on whether the in-flight write has read the pair yet, not on whether one is
|
|
27
|
-
* outstanding. Writes landing before that ride it
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* and skipping the re-send would leave the address bar doing so (Invariant 6).
|
|
29
|
+
* outstanding. Writes landing before that ride it, one round trip for the pair; a write landing
|
|
30
|
+
* after it queues one re-send, because the write on the wire carries a state the page has moved
|
|
31
|
+
* past, and skipping the re-send would leave the address bar addressing it (Invariant 6).
|
|
31
32
|
*/
|
|
32
|
-
export const runtimeStateEngine = `(function () {
|
|
33
|
-
var W = window;
|
|
34
|
-
var rtData, rtInsight, rtHasInsight = false;
|
|
35
|
-
var applyData, applyInsight, send;
|
|
36
|
-
var pending = null, sent = false, queued = null;
|
|
37
|
-
|
|
38
|
-
var pair = function () {
|
|
39
|
-
var p = {};
|
|
40
|
-
if (rtData !== undefined) p.data = rtData;
|
|
41
|
-
if (rtHasInsight) p.insight = rtInsight;
|
|
42
|
-
return p;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
var start = function () {
|
|
46
|
-
sent = false;
|
|
47
|
-
return Promise.resolve()
|
|
48
|
-
.then(function () { sent = true; return send(pair()); })
|
|
49
|
-
.catch(function () { return undefined; })
|
|
50
|
-
.finally(function () { pending = null; });
|
|
51
|
-
};
|
|
52
|
-
var publish = function () {
|
|
53
|
-
if (!pending) { pending = start(); return pending; }
|
|
54
|
-
if (!sent) return pending;
|
|
55
|
-
if (!queued) queued = pending.then(function () { queued = null; return publish(); });
|
|
56
|
-
return queued;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
W.__tbState = {
|
|
60
|
-
init: function (o) { applyData = o.applyData; applyInsight = o.applyInsight; send = o.send; },
|
|
61
|
-
pair: pair,
|
|
62
|
-
// Applied but not sent: the fragment already holds this, so sending it back would re-encode
|
|
63
|
-
// the state the page just booted from.
|
|
64
|
-
seed: function (p) {
|
|
65
|
-
if (p.data !== undefined) { rtData = p.data; applyData(p.data); }
|
|
66
|
-
if ('insight' in p) { rtInsight = p.insight; rtHasInsight = true; applyInsight(JSON.stringify(p.insight, null, 2)); }
|
|
67
|
-
},
|
|
68
|
-
// The globals move before the round trip, so a synchronous tb.data() right after the await is
|
|
69
|
-
// never stale.
|
|
70
|
-
setData: function (chunks) {
|
|
71
|
-
var arr = Array.isArray(chunks) ? chunks : [chunks];
|
|
72
|
-
rtData = arr;
|
|
73
|
-
applyData(arr);
|
|
74
|
-
return publish();
|
|
75
|
-
},
|
|
76
|
-
setInsight: function (value) {
|
|
77
|
-
// Serialize before recording: a value JSON.stringify rejects (a cycle, a BigInt) must leave
|
|
78
|
-
// the slot untouched, or every later setter carries a pair that can never be encoded or sent.
|
|
79
|
-
var json = JSON.stringify(value, null, 2);
|
|
80
|
-
rtInsight = value;
|
|
81
|
-
rtHasInsight = true;
|
|
82
|
-
applyInsight(json);
|
|
83
|
-
return publish();
|
|
84
|
-
}
|
|
85
|
-
};
|
|
33
|
+
export const runtimeStateEngine = `(function () {
|
|
34
|
+
var W = window;
|
|
35
|
+
var rtData, rtInsight, rtHasInsight = false;
|
|
36
|
+
var applyData, applyInsight, send;
|
|
37
|
+
var pending = null, sent = false, queued = null;
|
|
38
|
+
|
|
39
|
+
var pair = function () {
|
|
40
|
+
var p = {};
|
|
41
|
+
if (rtData !== undefined) p.data = rtData;
|
|
42
|
+
if (rtHasInsight) p.insight = rtInsight;
|
|
43
|
+
return p;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
var start = function () {
|
|
47
|
+
sent = false;
|
|
48
|
+
return Promise.resolve()
|
|
49
|
+
.then(function () { sent = true; return send(pair()); })
|
|
50
|
+
.catch(function () { return undefined; })
|
|
51
|
+
.finally(function () { pending = null; });
|
|
52
|
+
};
|
|
53
|
+
var publish = function () {
|
|
54
|
+
if (!pending) { pending = start(); return pending; }
|
|
55
|
+
if (!sent) return pending;
|
|
56
|
+
if (!queued) queued = pending.then(function () { queued = null; return publish(); });
|
|
57
|
+
return queued;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
W.__tbState = {
|
|
61
|
+
init: function (o) { applyData = o.applyData; applyInsight = o.applyInsight; send = o.send; },
|
|
62
|
+
pair: pair,
|
|
63
|
+
// Applied but not sent: the fragment already holds this, so sending it back would re-encode
|
|
64
|
+
// the state the page just booted from.
|
|
65
|
+
seed: function (p) {
|
|
66
|
+
if (p.data !== undefined) { rtData = p.data; applyData(p.data); }
|
|
67
|
+
if ('insight' in p) { rtInsight = p.insight; rtHasInsight = true; applyInsight(JSON.stringify(p.insight, null, 2)); }
|
|
68
|
+
},
|
|
69
|
+
// The globals move before the round trip, so a synchronous tb.data() right after the await is
|
|
70
|
+
// never stale.
|
|
71
|
+
setData: function (chunks) {
|
|
72
|
+
var arr = Array.isArray(chunks) ? chunks : [chunks];
|
|
73
|
+
rtData = arr;
|
|
74
|
+
applyData(arr);
|
|
75
|
+
return publish();
|
|
76
|
+
},
|
|
77
|
+
setInsight: function (value) {
|
|
78
|
+
// Serialize before recording: a value JSON.stringify rejects (a cycle, a BigInt) must leave
|
|
79
|
+
// the slot untouched, or every later setter carries a pair that can never be encoded or sent.
|
|
80
|
+
var json = JSON.stringify(value, null, 2);
|
|
81
|
+
rtInsight = value;
|
|
82
|
+
rtHasInsight = true;
|
|
83
|
+
applyInsight(json);
|
|
84
|
+
return publish();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
86
87
|
})();`;
|
|
87
88
|
//# sourceMappingURL=runtimeState.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtimeState.js","sourceRoot":"","sources":["../../format/src/runtimeState.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"runtimeState.js","sourceRoot":"","sources":["../../format/src/runtimeState.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAsD5B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -302,7 +302,7 @@ ${t.comment}`:t.comment}this.doc.range[2]=t.offset;break}default:this.errors.pus
|
|
|
302
302
|
`),l=i.filter(u=>u.type==="tool_use").map(u=>({id:u.id??"",name:u.name??"",input:u.input??{}}));if(o||a||l.length){let u=Date.parse(e.timestamp??""),f=!isNaN(u)&&u>=t;r.push({type:"assistant",text:o,thinking:a,tools:l,live:f})}let c=e.message?.usage??e.usage,d=c&&{in:c.input_tokens??0,out:c.output_tokens??0,cached:c.cache_read_input_tokens??0,cacheCreate:c.cache_creation_input_tokens??0};return{events:r,usage:d,model:e.message?.model}}return{events:r}}chainWorking(e){let t;for(let o of e)(o.type==="user"||o.type==="assistant")&&(t=o);if(!t)return!1;if(t.type==="user"){try{let o=t.message?.content,a=typeof o=="string"?o:Array.isArray(o)?o.filter(c=>c&&c.type==="text").map(c=>c.text??"").join(""):"";if(!(Array.isArray(o)&&o.some(c=>c&&c.type==="tool_result"))&&a.trim()&&Nt(a)==="")return!1}catch{}return!0}let s=(Array.isArray(t.message?.content)?t.message.content:[]).filter(o=>o.type==="tool_use").map(o=>o.id);if(s.length===0)return!1;let i=new Set;for(let o of e){let a=o.message?.content;if(Array.isArray(a))for(let l of a)l.type==="tool_result"&&l.tool_use_id&&i.add(l.tool_use_id)}return s.some(o=>o&&!i.has(o))}sessionAlive(e){return SA(e)}readPreview(e){return vA(e)}searchText(e){if(!e||ps(e)||e.type!=="user"&&e.type!=="assistant")return"";let t=e.message?.content,r="";if(typeof t=="string")r=Nt(t);else if(Array.isArray(t)){let s=t;r=e.type==="user"?s.map(ru).filter(Boolean).join(" "):s.filter(i=>i.type==="text"&&typeof i.text=="string").map(i=>i.text).join(" ")}return r.replace(/\s+/g," ").trim()}}});import{statSync as kA,readdirSync as xA,openSync as EA,readSync as AA,closeSync as TA,existsSync as PA}from"fs";import{basename as OA,join as ou,resolve as _A}from"path";import{homedir as bg}from"os";function RA(n){let e=n.trimStart();return CA.some(t=>e.startsWith(t))}function ms(n,e){return Array.isArray(n)?n.map(t=>typeof t?.text=="string"?e&&RA(t.text)?"":Ae(t.text):t?.type==="input_image"&&typeof t.image_url=="string"&&t.image_url.startsWith("data:")?``:"").filter(Boolean).join(`
|
|
303
303
|
`):""}function IA(n){return typeof n=="string"?Ae(n):ms(n,!1)}function NA(n){let e=n??"";for(let t of[/tools\.shell_command\((\{[\s\S]*?\})\)/,/tools\.shell_command\((\{[\s\S]*\})\)/]){let r=t.exec(e);if(r)try{return JSON.parse(r[1])}catch{}}return{script:e}}function $A(n){if(typeof n=="string"&&n)try{let e=JSON.parse(n);if(e&&typeof e=="object"&&!Array.isArray(e))return e}catch{}return{arguments:n??""}}function vg(n,e){let t;try{t=EA(n,"r")}catch{return}try{let r=Buffer.alloc(e),s=AA(t,r,0,e,0);return{text:r.subarray(0,s).toString("utf8"),bytes:s}}finally{TA(t)}}function LA(n){let t=vg(n,65536);if(t===void 0)return;let r=t.text.indexOf(`
|
|
304
304
|
`);if(r<0)return t.bytes>=65536?"":void 0;try{let s=JSON.parse(t.text.slice(0,r));return s?.type==="session_meta"&&typeof s.payload?.cwd=="string"?s.payload.cwd:""}catch{return""}}var CA,wg,Wn,au=v(()=>{"use strict";fs();mo();CA=["<permissions instructions>","<apps_instructions>","<plugins_instructions>","<skills_instructions>","<recommended_plugins>","<environment_context>","<user_instructions>"];wg=n=>{let e=_A(n);return process.platform==="win32"?e.toLowerCase():e},Wn=class extends It{constructor(t=ou(bg(),".codex","sessions")){super();this.root=t}displayName="Codex Mirror";#e=0;#s;#t=new Map;detectsSelf(){return!!process.env.CODEX_THREAD_ID}detectsInstalled(){return PA(ou(bg(),".codex"))}sessionsDir(t){return this.root}listSessionFiles(t){let r=wg(t),s=[];for(let i of this.#r()){let o;try{let l=kA(i);if(!l.isFile())continue;o=l.mtimeMs}catch{continue}let a=this.#t.get(i);if(a===void 0){if(a=LA(i),a===void 0)continue;this.#t.set(i,a)}wg(a)===r&&s.push({sessionId:OA(i,".jsonl"),file:i,mtime:o})}return s}#r(){let t=[],r=s=>{try{return xA(s).map(i=>ou(s,i))}catch{return[]}};for(let s of r(this.root))for(let i of r(s))for(let o of r(i))for(let a of r(o))a.endsWith(".jsonl")&&t.push(a);return t}parseEntry(t){let r;try{r=JSON.parse(t)}catch{return null}return!r||typeof r.type!="string"?null:r}idOf(t){return t.ord===void 0&&(t.ord=String(this.#e++),t.parent=this.#s,this.#s=t.ord),t.ord}parentOf(t){return t.parent}timestampOf(t){return t.timestamp}isSidechain(t){return!1}isLeafType(t){return!0}isRecoveryNoise(t){return!1}apply(t,r){let s=[],i=t.payload;if(!i)return{events:s};let o=()=>{let a=Date.parse(t.timestamp??"");return!isNaN(a)&&a>=r};if(t.type==="response_item"){if(i.type==="message"){if(i.role==="user"){let a=ms(i.content,!0).trim();a&&s.push({type:"user",text:a})}else if(i.role==="assistant"){let a=ms(i.content,!1);a&&s.push({type:"assistant",text:a,thinking:"",tools:[],live:o()})}return{events:s}}if(i.type==="reasoning"){let a=ms(i.summary,!1);return a&&s.push({type:"assistant",text:"",thinking:a,tools:[],live:o()}),{events:s}}if(i.type==="custom_tool_call"||i.type==="function_call"){let a=i.type==="custom_tool_call"?NA(i.input):$A(i.arguments);return s.push({type:"assistant",text:"",thinking:"",tools:[{id:i.call_id??"",name:i.name??"",input:a}],live:o()}),{events:s}}if(i.type==="custom_tool_call_output"||i.type==="function_call_output"){let a=IA(i.output);return s.push({type:"tool_result",id:i.call_id??"",content:a,isError:!1,digest:qe(a)}),{events:s}}return{events:s}}if(t.type==="event_msg"){if(i.type==="token_count"){let a=i.info?.last_token_usage;if(!a)return{events:s};let l={in:Math.max(0,(a.input_tokens??0)-(a.cached_input_tokens??0)),out:a.output_tokens??0,cached:a.cached_input_tokens??0,cacheCreate:a.cache_write_input_tokens??0};return{events:s,usage:l}}return{events:s}}return t.type==="turn_context"?{events:s,model:i.model}:{events:s}}chainWorking(t){let r=!1;for(let s of t){if(s.type!=="event_msg")continue;let i=s.payload?.type;i==="task_started"?r=!0:(i==="task_complete"||i==="turn_aborted")&&(r=!1)}return r}sessionAlive(){}readPreview(t){for(let r of(vg(t,256*1024)?.text??"").split(`
|
|
305
|
-
`))if(r.includes('"user_message"'))try{let s=JSON.parse(r);if(s?.type==="event_msg"&&s.payload?.type==="user_message"&&typeof s.payload.message=="string"){let i=s.payload.message.replace(/\s+/g," ").trim();if(i)return i.slice(0,200)}}catch{}return""}searchText(t){if(t?.type!=="response_item"||t.payload?.type!=="message")return"";let r=t.payload.role;return r!=="user"&&r!=="assistant"?"":ms(t.payload.content,r==="user").replace(/\s+/g," ").trim()}}});import{realpathSync as MA}from"fs";import{createHash as BA}from"crypto";import{join as bo,resolve as DA}from"path";import{homedir as jA}from"os";function $t(){let n=process.env.TYPEBULB_SERVERS_DIR;return n?bo(n,".."):bo(jA(),".typebulb")}function lt(){Sg||(Sg=!0,console.error(`${$t()} not writable \u2014 continuing stateless: ports won't stick, caches stay cold, and typebulb logs/wait/send/stop may not see this run`))}function ct(){return process.env.TYPEBULB_SERVERS_DIR||bo($t(),"servers")}function kg(n){let e=se(n),t=e.slice(e.lastIndexOf("/")+1).replace(/\.bulb\.md$/,"");return bo($t(),"canvas",`${t}-${gs(n)}.png`)}function gs(n){return BA("sha1").update(se(n)).digest("hex").slice(0,8)}function se(n){let e=DA(n),t=e;try{t=MA.native(e)}catch{}return(process.platform==="win32"?t.toLowerCase():t).replace(/\\/g,"/")}function xg(n,e){let t=se(n),r=se(e);return t.startsWith(r.endsWith("/")?r:r+"/")?!t.includes("/node_modules/"):!1}var Sg,et=v(()=>{"use strict";Sg=!1});import*as Tg from"net";import{mkdir as FA,readFile as UA,writeFile as Pg}from"fs/promises";import{existsSync as qA}from"fs";import{join as Og}from"path";function Rg(){return Og($t(),"blocks")}function lu(n){return Og(Rg(),`${n}.json`)}function vo(n){return new Promise(e=>{let t=Tg.createServer();t.once("error",()=>e(!1)),t.listen(n,"127.0.0.1",()=>t.close(()=>e(!0)))})}async function HA(n){for(let e=n;e<65535;e++)if(await vo(e))return e;throw new Error(`No free port found above ${n}`)}async function cu(n){let{listBulbServers:e}=await Promise.resolve().then(()=>(Re(),Ig)),t=(await e()).find(s=>s.port===n),r=t?` (pid ${t.pid}, ${t.agent?`agent:${t.agent}`:t.file})`:"";console.error(`port ${n} in use${r}`),process.exit(1)}async function So(n){try{let e=JSON.parse(await UA(lu(n),"utf8"));return e&&typeof e.cwd=="string"?{cwd:e.cwd,slots:e.slots??{},mirrors:e.mirrors??{}}:void 0}catch{return}}async function ko(n,e){try{return await Pg(lu(n),JSON.stringify(e,null,2)),!0}catch{return lt(),!1}}async function YA(n){let e=se(n);try{await FA(Rg(),{recursive:!0})}catch{return}let t=[];for(let o=0;o<Cg;o++)t.push(_g+o*sn);let r=await Promise.all(t.map(async o=>({base:o,block:await So(o)})));for(let{base:o,block:a}of r)if(a?.cwd===e)return o;let s={cwd:e,slots:{},mirrors:{}};for(let{base:o,block:a}of r){if(!a)try{return await Pg(lu(o),JSON.stringify(s,null,2),{flag:"wx"}),o}catch(l){if((await So(o))?.cwd===e)return o;l.code!=="EEXIST"&<();continue}if(!qA(a.cwd))return await ko(o,s)?o:void 0}let i=r.filter(o=>!!o.block).sort((o,a)=>Eg(o.block)-Eg(a.block))[0];if(i)return await ko(i.base,s)?i.base:void 0}function Eg(n){return Object.values(n.slots).reduce((e,t)=>Math.max(e,t.usedAt??0),0)}async function VA(n,e){let t=await So(n)??{cwd:"",slots:{},mirrors:{}};if(e.kind==="mirror"){let i=t.mirrors[e.agent];if(i!==void 0)return i;let o=Ag(t),a=o.has(0)?KA:0;for(;a>wo&&o.has(a);)a--;return t.mirrors[e.agent]=a,await ko(n,t),a}let r=se(e.file),s=t.slots[r]?.offset;if(s===void 0){let i=Ag(t);for(s=wo;s<sn&&i.has(s);)s++;if(s>=sn){let o=Object.entries(t.slots).sort((a,l)=>(a[1].usedAt??0)-(l[1].usedAt??0))[0];s=o?o[1].offset:wo,o&&delete t.slots[o[0]]}}return t.slots[r]={offset:s,usedAt:Date.now()},await ko(n,t),s}function Ag(n){return new Set([...Object.values(n.mirrors),...Object.values(n.slots).map(e=>e.offset)])}async function Hn(n,e){let t=await YA(e);return t===void 0?void 0:t+await VA(t,n)}async function on(n){let e=se(n);for(let t=0;t<Cg;t++){let r=await So(_g+t*sn);if(r?.cwd===e)return s=>r.slots[se(s)]?.usedAt??0}return()=>0}async function xo(n){if(n.explicit!==void 0)return await vo(n.explicit)||await cu(n.explicit),n.target.kind==="bulb"&&await Hn(n.target,n.cwd),{port:n.explicit};let e=await Hn(n.target,n.cwd);if(e!==void 0){let r=e-e%sn,s=Date.now()+WA;do{if(await vo(e))return{port:e};await new Promise(i=>setTimeout(i,150))}while(Date.now()<s);for(let i=wo;i<sn;i++){let o=r+i;if(o!==e&&await vo(o))return{port:o,note:`(slot ${e} was busy)`}}}return{port:await HA(JA),note:e===void 0?"(no port block available)":"(this project's block was full)"}}var _g,sn,Cg,JA,wo,KA,WA,an=v(()=>{"use strict";et();_g=20100,sn=100,Cg=19,JA=23e3,wo=1,KA=sn-1,WA=3e3});var Ie,Eo=v(()=>{"use strict";Ie="0.58.
|
|
305
|
+
`))if(r.includes('"user_message"'))try{let s=JSON.parse(r);if(s?.type==="event_msg"&&s.payload?.type==="user_message"&&typeof s.payload.message=="string"){let i=s.payload.message.replace(/\s+/g," ").trim();if(i)return i.slice(0,200)}}catch{}return""}searchText(t){if(t?.type!=="response_item"||t.payload?.type!=="message")return"";let r=t.payload.role;return r!=="user"&&r!=="assistant"?"":ms(t.payload.content,r==="user").replace(/\s+/g," ").trim()}}});import{realpathSync as MA}from"fs";import{createHash as BA}from"crypto";import{join as bo,resolve as DA}from"path";import{homedir as jA}from"os";function $t(){let n=process.env.TYPEBULB_SERVERS_DIR;return n?bo(n,".."):bo(jA(),".typebulb")}function lt(){Sg||(Sg=!0,console.error(`${$t()} not writable \u2014 continuing stateless: ports won't stick, caches stay cold, and typebulb logs/wait/send/stop may not see this run`))}function ct(){return process.env.TYPEBULB_SERVERS_DIR||bo($t(),"servers")}function kg(n){let e=se(n),t=e.slice(e.lastIndexOf("/")+1).replace(/\.bulb\.md$/,"");return bo($t(),"canvas",`${t}-${gs(n)}.png`)}function gs(n){return BA("sha1").update(se(n)).digest("hex").slice(0,8)}function se(n){let e=DA(n),t=e;try{t=MA.native(e)}catch{}return(process.platform==="win32"?t.toLowerCase():t).replace(/\\/g,"/")}function xg(n,e){let t=se(n),r=se(e);return t.startsWith(r.endsWith("/")?r:r+"/")?!t.includes("/node_modules/"):!1}var Sg,et=v(()=>{"use strict";Sg=!1});import*as Tg from"net";import{mkdir as FA,readFile as UA,writeFile as Pg}from"fs/promises";import{existsSync as qA}from"fs";import{join as Og}from"path";function Rg(){return Og($t(),"blocks")}function lu(n){return Og(Rg(),`${n}.json`)}function vo(n){return new Promise(e=>{let t=Tg.createServer();t.once("error",()=>e(!1)),t.listen(n,"127.0.0.1",()=>t.close(()=>e(!0)))})}async function HA(n){for(let e=n;e<65535;e++)if(await vo(e))return e;throw new Error(`No free port found above ${n}`)}async function cu(n){let{listBulbServers:e}=await Promise.resolve().then(()=>(Re(),Ig)),t=(await e()).find(s=>s.port===n),r=t?` (pid ${t.pid}, ${t.agent?`agent:${t.agent}`:t.file})`:"";console.error(`port ${n} in use${r}`),process.exit(1)}async function So(n){try{let e=JSON.parse(await UA(lu(n),"utf8"));return e&&typeof e.cwd=="string"?{cwd:e.cwd,slots:e.slots??{},mirrors:e.mirrors??{}}:void 0}catch{return}}async function ko(n,e){try{return await Pg(lu(n),JSON.stringify(e,null,2)),!0}catch{return lt(),!1}}async function YA(n){let e=se(n);try{await FA(Rg(),{recursive:!0})}catch{return}let t=[];for(let o=0;o<Cg;o++)t.push(_g+o*sn);let r=await Promise.all(t.map(async o=>({base:o,block:await So(o)})));for(let{base:o,block:a}of r)if(a?.cwd===e)return o;let s={cwd:e,slots:{},mirrors:{}};for(let{base:o,block:a}of r){if(!a)try{return await Pg(lu(o),JSON.stringify(s,null,2),{flag:"wx"}),o}catch(l){if((await So(o))?.cwd===e)return o;l.code!=="EEXIST"&<();continue}if(!qA(a.cwd))return await ko(o,s)?o:void 0}let i=r.filter(o=>!!o.block).sort((o,a)=>Eg(o.block)-Eg(a.block))[0];if(i)return await ko(i.base,s)?i.base:void 0}function Eg(n){return Object.values(n.slots).reduce((e,t)=>Math.max(e,t.usedAt??0),0)}async function VA(n,e){let t=await So(n)??{cwd:"",slots:{},mirrors:{}};if(e.kind==="mirror"){let i=t.mirrors[e.agent];if(i!==void 0)return i;let o=Ag(t),a=o.has(0)?KA:0;for(;a>wo&&o.has(a);)a--;return t.mirrors[e.agent]=a,await ko(n,t),a}let r=se(e.file),s=t.slots[r]?.offset;if(s===void 0){let i=Ag(t);for(s=wo;s<sn&&i.has(s);)s++;if(s>=sn){let o=Object.entries(t.slots).sort((a,l)=>(a[1].usedAt??0)-(l[1].usedAt??0))[0];s=o?o[1].offset:wo,o&&delete t.slots[o[0]]}}return t.slots[r]={offset:s,usedAt:Date.now()},await ko(n,t),s}function Ag(n){return new Set([...Object.values(n.mirrors),...Object.values(n.slots).map(e=>e.offset)])}async function Hn(n,e){let t=await YA(e);return t===void 0?void 0:t+await VA(t,n)}async function on(n){let e=se(n);for(let t=0;t<Cg;t++){let r=await So(_g+t*sn);if(r?.cwd===e)return s=>r.slots[se(s)]?.usedAt??0}return()=>0}async function xo(n){if(n.explicit!==void 0)return await vo(n.explicit)||await cu(n.explicit),n.target.kind==="bulb"&&await Hn(n.target,n.cwd),{port:n.explicit};let e=await Hn(n.target,n.cwd);if(e!==void 0){let r=e-e%sn,s=Date.now()+WA;do{if(await vo(e))return{port:e};await new Promise(i=>setTimeout(i,150))}while(Date.now()<s);for(let i=wo;i<sn;i++){let o=r+i;if(o!==e&&await vo(o))return{port:o,note:`(slot ${e} was busy)`}}}return{port:await HA(JA),note:e===void 0?"(no port block available)":"(this project's block was full)"}}var _g,sn,Cg,JA,wo,KA,WA,an=v(()=>{"use strict";et();_g=20100,sn=100,Cg=19,JA=23e3,wo=1,KA=sn-1,WA=3e3});var Ie,Eo=v(()=>{"use strict";Ie="0.58.1"});var Ig={};oi(Ig,{agentViewerCommand:()=>qg,bulbServerCommand:()=>Ug,clearServerLog:()=>hu,findProjectViewer:()=>Po,isAlive:()=>Yn,isMirror:()=>me,isProjectBulb:()=>wu,isProjectMirror:()=>Gn,launchAgentViewer:()=>ku,launchBulbServer:()=>ws,listBulbServers:()=>X,probePageCount:()=>Su,readServerLog:()=>Ke,readWaitCursor:()=>To,recordDenial:()=>bu,registerServer:()=>yu,relayOpen:()=>vu,runMarker:()=>mu,serverLogPath:()=>zn,serversForBulb:()=>cn,sliceRunLog:()=>gu,startServerLog:()=>bs,stopBulbServer:()=>Vn,stopServer:()=>Lt,stopServersForBulb:()=>Oo,typebulbBinPath:()=>Xn,unregisterServer:()=>ln,writeWaitCursor:()=>ys});import{spawn as Ng}from"child_process";import{readdir as zA,readFile as Mg,writeFile as Bg,unlink as uu,mkdir as GA}from"fs/promises";import{mkdirSync as Dg,writeFileSync as Ao,appendFileSync as XA,readFileSync as du}from"fs";import*as Te from"path";import{fileURLToPath as QA}from"url";function fu(n){return Te.join(ct(),`${n}.json`)}function zn(n){return Te.join(ct(),`${n}.log`)}function pu(n){return Te.join(ct(),`${n}.wait.json`)}function jg(n){try{let e=JSON.parse(du(pu(n),"utf8"));return e.cursors&&typeof e.cursors=="object"?e.cursors:{}}catch{return{}}}function To(n,e=""){let t=jg(n)[e];return typeof t=="number"&&Number.isFinite(t)&&t>=0?t:void 0}function ys(n,e,t=""){try{Dg(ct(),{recursive:!0});let r=jg(n);r[t]=e,Ao(pu(n),JSON.stringify({cursors:r}))}catch{}}function bs(n){let e=zn(n);try{Dg(ct(),{recursive:!0}),Ao(e,"")}catch{return()=>{}}let t=process.stdout.write.bind(process.stdout),r=process.stderr.write.bind(process.stderr),s=0,i=!1,o=a=>{if(!i)try{let l=typeof a=="string"?Buffer.from(a,"utf8"):Buffer.from(a);if(XA(e,l),s+=l.length,s>2*$g){let c=du(e),d=c.subarray(Math.max(0,c.length-$g));Ao(e,d),s=d.length}}catch{i=!0}};return process.stdout.write=((a,...l)=>(o(a),t(a,...l))),process.stderr.write=((a,...l)=>(o(a),r(a,...l))),()=>{process.stdout.write=t,process.stderr.write=r}}function hu(n){try{Ao(zn(n),"")}catch{}}function Ke(n,e=0){try{let t=du(zn(n)),r=e>=0&&e<=t.length?e:0;return{text:t.subarray(r).toString("utf8"),offset:t.length}}catch{return{text:"",offset:0}}}function mu(n){return`\u2500\u2500 run ${n} \u2500\u2500 ${new Date().toTimeString().slice(0,8)}`}function eT(n){let e=ZA.exec(n);return e?Number(e[1]):void 0}function gu(n,e){let t=n.split(`
|
|
306
306
|
`),r=[];if(t.forEach((o,a)=>{let l=eT(o);l!==void 0&&r.push({id:l,idx:a})}),!r.length)return e==="latest"?n:"";let s=e==="latest"?r[r.length-1]:r.find(o=>o.id===e);if(!s)return"";let i=r.find(o=>o.idx>s.idx);return t.slice(s.idx,i?i.idx:t.length).join(`
|
|
307
307
|
`)}function Yn(n){try{return process.kill(n,0),!0}catch(e){return e.code==="EPERM"}}async function yu(n){try{await GA(ct(),{recursive:!0}),await Bg(fu(n.pid),JSON.stringify({version:Ie,...n}))}catch{lt()}}async function bu(n,e){let t=fu(n);try{let r=JSON.parse(await Mg(t,"utf8"));if(r.denied===e)return;r.denied=e,await Bg(t,JSON.stringify(r))}catch{}}async function ln(n){await uu(fu(n)).catch(()=>{}),await uu(zn(n)).catch(()=>{}),await uu(pu(n)).catch(()=>{})}function me(n){return n.agent!=null}function Gn(n,e){return me(n)&&n.cwd!=null&&se(n.cwd)===se(e)}function wu(n,e){return!me(n)&&xg(n.file,e)}async function X(n){let e;try{e=await zA(ct())}catch{return[]}let t=[];return await Promise.all(e.map(async s=>{if(!/^\d+\.json$/.test(s))return;let i=parseInt(s,10),o;try{o=JSON.parse(await Mg(Te.join(ct(),s),"utf8"))}catch{await ln(i);return}o&&typeof o.pid=="number"&&Yn(o.pid)?t.push(o):await ln(i)})),(n?t.filter(s=>wu(s,n)):t).sort((s,i)=>s.startedAt-i.startedAt)}async function Po(n,e){return(await X()).find(t=>Gn(t,n)&&(e?t.agent===e:!0))}async function vu(n,e){let t=new URL(n).port,r=(await X()).filter(a=>String(a.port)!==t),s=r.filter(a=>Gn(a,e)),i=r.filter(a=>wu(a,e)),o=0;for(let a of[...s,...i])try{let l=await fetch(`${a.url}/__open`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:n}),signal:AbortSignal.timeout(2e3)});if(!l.ok)continue;let{opened:c,pages:d}=await l.json();if(c)return{where:"editor",via:a.agent?"the agent mirror":`${Te.basename(a.file)}'s page`};me(a)&&(o+=d??0)}catch{}return o>0?{where:"external"}:void 0}async function Vn(n){try{process.kill(n,"SIGTERM")}catch{}await ln(n)}async function Su(n){try{let e=await fetch(`${n.url}/__pages`,{signal:AbortSignal.timeout(tT)});if(!e.ok)return;let{pages:t}=await e.json();return typeof t=="number"?t:void 0}catch{return}}async function Lt(n,e=me(n)?"keep":"close"){try{let t=await fetch(`${n.url}/__stop`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({pages:e}),signal:AbortSignal.timeout(Lg)});if(!t.ok)throw new Error(`/__stop ${t.status}`);let{closed:r=0,stuck:s=0}=await t.json(),i=Date.now()+Lg;for(;Yn(n.pid)&&Date.now()<i;)await Fg(100);return Yn(n.pid)?(await Vn(n.pid),"killed"):(await ln(n.pid),{closed:r,stuck:s})}catch{return await Vn(n.pid),"killed"}}function cn(n,e){let t=se(e);return n.filter(r=>!me(r)&&r.pid!==process.pid&&se(r.file)===t)}async function Oo(n,e){let t=cn(await X(),n);for(let r of t)await Lt(r,e!==void 0&&r.port===e?"keep":"close");return t}function nT(n,e,t){(process.platform==="win32"?Ng("cmd",["/c","start","","/b",n,...e],{cwd:t,stdio:"ignore",windowsHide:!0}):Ng(n,e,{cwd:t,detached:!0,stdio:"ignore"})).unref()}function Xn(){return Te.join(Te.dirname(QA(import.meta.url)),"index.js")}function Ug(n,e={}){return{command:process.execPath,args:[Xn(),...e.trust?["--trust"]:[],...e.batch?["--batch",e.batch]:[],...e.mode?["--mode",e.mode]:[],n,...e.open===!1?["--no-open"]:[]]}}function qg(n){return{command:process.execPath,args:[Xn(),`agent:${n}`,"--no-open"]}}async function Jg(n,e,t,r){let s=await n();if(s)return s;nT(e.command,e.args,t);let i=Date.now()+2e4;for(;Date.now()<i;){await Fg(150);let o=await n();if(o)return o}throw new Error(`Launched ${r} but it did not register within 20s.`)}async function ws(n,e={}){let t=e.cwd??process.cwd(),r=Te.resolve(t,n),s=await Hn({kind:"bulb",file:r},t),[i]=await Oo(r,s),o={...e,batch:e.batch??i?.batch,mode:e.mode??i?.mode};return Jg(async()=>cn(await X(),r)[0],Ug(n,o),t,Te.basename(n))}async function ku(n,e=process.cwd()){return Jg(()=>Po(e,n),qg(n),e,`the ${n} mirror`)}var $g,ZA,Lg,tT,Fg,Re=v(()=>{"use strict";et();an();Eo();$g=1e6;ZA=/^── run (\d+) ── /;Lg=6e3,tT=500;Fg=n=>new Promise(e=>setTimeout(e,n))});function Qn(n){return{...Uf(n),server:n.files.get("server.ts")??""}}function _o(n){return!!n.server&&!n.code}var vs=v(()=>{"use strict";ee();ee()});function Co(n){let e=/^---[^\n]*\n([\s\S]*?)\n---/.exec(n),t=e?/^\s*name:\s*(.+?)\s*$/m.exec(e[1]):null;return t?t[1].replace(/^["']|["']$/g,""):void 0}function xu(n){return qn(Co(n)??"bulb")||"bulb"}var Eu=v(()=>{"use strict";ee()});import{transform as rT}from"sucrase";function Au(n,e={}){let t=e.serverOnly?["typescript"]:["typescript","jsx"];try{let{code:r}=rT(n,{transforms:t,jsxRuntime:"automatic",jsxImportSource:e.jsxImportSource||"react",production:!0});return{code:r}}catch(r){return{code:"",error:String(r)}}}var Kg=v(()=>{});function Tu(n){let e=n.split(""),t=(i,o)=>{for(let a=i;a<o&&a<e.length;a++)e[a]!==`
|
|
308
308
|
`&&(e[a]=" ")},r=0,s="";for(;r<n.length;){let i=n[r],o=n[r+1];if(i==="/"&&o==="/"){let a=r;for(;a<n.length&&n[a]!==`
|
|
@@ -608,8 +608,8 @@ ${cg}
|
|
|
608
608
|
// tb.setData / tb.setInsight (TB-State.md), on the engine both hosts share. The globals and the
|
|
609
609
|
// transport are all this host owns: encoding round-trips to the server because the page carries
|
|
610
610
|
// no fflate on purpose, and the fragment write is the address bar the setters exist to fill.
|
|
611
|
-
// Resolves
|
|
612
|
-
//
|
|
611
|
+
// Resolves once that write has landed, and hands back nothing: the link is then tb.url() like
|
|
612
|
+
// any other. An inline bulb has no address bar, so its swap is page-local and never encodes.
|
|
613
613
|
window.__tbState.init({
|
|
614
614
|
applyData: (chunks) => { window.__TB_DATA__ = chunks; },
|
|
615
615
|
applyInsight: (json) => { window.__TB_INSIGHT__ = json; },
|
|
@@ -627,7 +627,6 @@ ${cg}
|
|
|
627
627
|
// earlier fragment, or the address bar keeps addressing a run the page is no longer
|
|
628
628
|
// showing \u2014 and a reload would silently revert to it.
|
|
629
629
|
history.replaceState(null, '', location.pathname + location.search + hash);
|
|
630
|
-
return hash ? location.href : undefined;
|
|
631
630
|
});
|
|
632
631
|
}
|
|
633
632
|
});
|
|
@@ -2229,27 +2228,28 @@ Examples:
|
|
|
2229
2228
|
*/
|
|
2230
2229
|
insight<T = unknown>(): T | undefined;`,PC=`
|
|
2231
2230
|
/**
|
|
2232
|
-
* Replace the data this run is working on
|
|
2231
|
+
* Replace the data this run is working on.
|
|
2233
2232
|
*
|
|
2234
2233
|
* Updates what \`tb.data()\` / \`tb.json()\` return for the rest of this page, and writes the
|
|
2235
|
-
* result into the URL fragment so the address bar addresses this run
|
|
2236
|
-
* \`data.txt\` is never touched: a reload without the fragment goes back
|
|
2234
|
+
* result into the URL fragment, so the address bar addresses this run and \`tb.url()\` carries
|
|
2235
|
+
* it. The bulb's source \`data.txt\` is never touched: a reload without the fragment goes back
|
|
2236
|
+
* to the file.
|
|
2237
2237
|
*
|
|
2238
2238
|
* @param chunks - The new data chunks (a bare string is treated as one chunk)
|
|
2239
|
-
* @returns
|
|
2240
|
-
*
|
|
2239
|
+
* @returns Resolves once the address bar reflects the write. Data too large to fit a URL
|
|
2240
|
+
* (~60KB encoded) still swaps, but carries no fragment: pass what a share needs, not
|
|
2241
|
+
* necessarily everything.
|
|
2241
2242
|
*/
|
|
2242
|
-
setData(chunks: string | string[]): Promise<
|
|
2243
|
+
setData(chunks: string | string[]): Promise<void>;
|
|
2243
2244
|
/**
|
|
2244
|
-
* Replace the insight this run is working on
|
|
2245
|
+
* Replace the insight this run is working on.
|
|
2245
2246
|
*
|
|
2246
2247
|
* The \`tb.insight()\` counterpart of \`setData\`, with the same fragment behavior and the same
|
|
2247
2248
|
* rule: runtime only, never the bulb's source \`insight.json\`.
|
|
2248
2249
|
*
|
|
2249
2250
|
* @param value - Any JSON-serializable value
|
|
2250
|
-
* @returns The shareable URL, or undefined (see \`setData\`)
|
|
2251
2251
|
*/
|
|
2252
|
-
setInsight(value: unknown): Promise<
|
|
2252
|
+
setInsight(value: unknown): Promise<void>;`,Pw=`options: {
|
|
2253
2253
|
messages: Array<{ role: "user" | "assistant"; content: string }>;
|
|
2254
2254
|
system?: string;
|
|
2255
2255
|
/** Reasoning effort: 0=minimal, 1=low, 2=med, 3=high, 4=xhigh. Mapped to each provider's native mechanism (OpenAI/OpenRouter reasoning effort, Gemini thinking level, Anthropic adaptive thinking). 0 reaches the vendor's explicit off switch, and floors to a little thinking only on always-thinking models. 4 reaches the rung above high where the vendor has one (OpenAI/OpenRouter xhigh, Anthropic max) and clamps to high where it doesn't (Gemini) \u2014 never an error. Pin 1 (low) for most work; pick 0 deliberately, when you want the raw model or latency is the constraint. Omitting is NOT off \u2014 it buys the model's own default (medium on GPT-5.6), which reasons and bills invisibly. Ignored on the courtesy model, which runs at the level Typebulb sets. */
|
|
@@ -2405,9 +2405,11 @@ type AiAccess = "own" | "courtesy" | "none";
|
|
|
2405
2405
|
/**
|
|
2406
2406
|
* Get the canonical URL of this bulb.
|
|
2407
2407
|
*
|
|
2408
|
-
* Returns the parent typebulb.com URL (including
|
|
2409
|
-
*
|
|
2410
|
-
*
|
|
2408
|
+
* Returns the parent typebulb.com URL (including query and \`#tb=\` fragment), resolving
|
|
2409
|
+
* correctly from inside the cross-origin sandbox iframe. That is the published \`/full\` page
|
|
2410
|
+
* even while authoring in the IDE, because it is the link someone you send it to can open.
|
|
2411
|
+
* Locally it is the served localhost URL. Use this instead of \`location.href\` or
|
|
2412
|
+
* \`document.referrer\`.
|
|
2411
2413
|
*
|
|
2412
2414
|
* @returns The full canonical URL
|
|
2413
2415
|
*/
|
package/dist/render.js
CHANGED
|
@@ -701,8 +701,8 @@ ${B1}
|
|
|
701
701
|
// tb.setData / tb.setInsight (TB-State.md), on the engine both hosts share. The globals and the
|
|
702
702
|
// transport are all this host owns: encoding round-trips to the server because the page carries
|
|
703
703
|
// no fflate on purpose, and the fragment write is the address bar the setters exist to fill.
|
|
704
|
-
// Resolves
|
|
705
|
-
//
|
|
704
|
+
// Resolves once that write has landed, and hands back nothing: the link is then tb.url() like
|
|
705
|
+
// any other. An inline bulb has no address bar, so its swap is page-local and never encodes.
|
|
706
706
|
window.__tbState.init({
|
|
707
707
|
applyData: (chunks) => { window.__TB_DATA__ = chunks; },
|
|
708
708
|
applyInsight: (json) => { window.__TB_INSIGHT__ = json; },
|
|
@@ -720,7 +720,6 @@ ${B1}
|
|
|
720
720
|
// earlier fragment, or the address bar keeps addressing a run the page is no longer
|
|
721
721
|
// showing \u2014 and a reload would silently revert to it.
|
|
722
722
|
history.replaceState(null, '', location.pathname + location.search + hash);
|
|
723
|
-
return hash ? location.href : undefined;
|
|
724
723
|
});
|
|
725
724
|
}
|
|
726
725
|
});
|
package/dist/servers.js
CHANGED
|
@@ -355,8 +355,8 @@ ${s}`}function b1(t){let e=dr.filter(n=>t[n]).map(n=>U0(n,t[n]));return["---",`f
|
|
|
355
355
|
// tb.setData / tb.setInsight (TB-State.md), on the engine both hosts share. The globals and the
|
|
356
356
|
// transport are all this host owns: encoding round-trips to the server because the page carries
|
|
357
357
|
// no fflate on purpose, and the fragment write is the address bar the setters exist to fill.
|
|
358
|
-
// Resolves
|
|
359
|
-
//
|
|
358
|
+
// Resolves once that write has landed, and hands back nothing: the link is then tb.url() like
|
|
359
|
+
// any other. An inline bulb has no address bar, so its swap is page-local and never encodes.
|
|
360
360
|
window.__tbState.init({
|
|
361
361
|
applyData: (chunks) => { window.__TB_DATA__ = chunks; },
|
|
362
362
|
applyInsight: (json) => { window.__TB_INSIGHT__ = json; },
|
|
@@ -374,7 +374,6 @@ ${s}`}function b1(t){let e=dr.filter(n=>t[n]).map(n=>U0(n,t[n]));return["---",`f
|
|
|
374
374
|
// earlier fragment, or the address bar keeps addressing a run the page is no longer
|
|
375
375
|
// showing \u2014 and a reload would silently revert to it.
|
|
376
376
|
history.replaceState(null, '', location.pathname + location.search + hash);
|
|
377
|
-
return hash ? location.href : undefined;
|
|
378
377
|
});
|
|
379
378
|
}
|
|
380
379
|
});
|