typebulb 0.14.4 → 0.14.5
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 +93 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -219,7 +219,7 @@ The agent mirror turns that block into a live, sandboxed app, with a *breakout
|
|
|
219
219
|
|
|
220
220
|
The host owns a bulb's **width**; you own its **height**.
|
|
221
221
|
|
|
222
|
-
**Width is the host's.** Standalone, a bulb fills its browser window; in the agent mirror, an embed fits the conversation column by default, with a per-embed *spread* toggle to the full transcript width — and a cap so a tall embed doesn't run away down the transcript. Don't set a width or guess how much room you'll get.
|
|
222
|
+
**Width is the host's.** Standalone, a bulb fills its browser window; in the agent mirror, an embed fits the conversation column by default, with a per-embed *spread* toggle to the full transcript width — and a cap so a tall embed doesn't run away down the transcript. Don't set a width or guess how much room you'll get. `max-width` is the one width worth setting — a readability cap that only declines excess, so it's safe at any granted width. It's also what *spread* runs into: a dense visualization that earns the full transcript width should omit it.
|
|
223
223
|
|
|
224
224
|
**Height follows your content.** Set a height that adapts — content-driven or viewport-filling — never a fixed pixel value, which neither grows to fill a broken-out window nor shrinks to its content. Prose, a form, a chart flow to their natural height: set none. A full-bleed surface with no natural height of its own gets `height: 100dvh` **and** a pixel floor like `min-height: 420px`. Both are needed — `100dvh` fills its own window if the bulb is broken out, and the floor holds a definite band when embedded. Without the floor a bare `100dvh` collapses to zero embedded, because the mirror sizes an embed to its content height and `100dvh` gives it nothing to measure against.
|
|
225
225
|
|
|
@@ -251,6 +251,7 @@ The host owns a bulb's **width**; you own its **height**.
|
|
|
251
251
|
- **`tb.theme` drives the `html[data-theme]` attribute** — style off that selector (`html[data-theme="dark"] { … }`); don't read `tb.theme` to branch your rendering.
|
|
252
252
|
- **`color-scheme` is set for you** — the host always applies `html[data-theme="dark"] { color-scheme: dark }` / `html[data-theme="light"] { color-scheme: light }` on top of your `styles.css`.
|
|
253
253
|
- **Math (KaTeX) renders in your replies** — write inline `$…$` / display `$$…$$` (prefer `$y = x^2$` over inline-code or a Unicode `y = x²`). The mirror's KaTeX renders only in prose and doesn't reach inside a fenced block (bulb, mermaid, svg, code).
|
|
254
|
+
- **Charts: prefer a bulb over mermaid's `xychart`** unless a static, unlabeled bar or line is enough — start from the [Charts](#charts) skeleton.
|
|
254
255
|
- **`tb.json<T>(n)` is generic** — `tb.json<Album[]>(0)` returns typed parsed JSON; `tb.data(n)` returns the raw string.
|
|
255
256
|
- **`tb.proxy()` is for same-origin Web Worker / WASM loads** — e.g. ffmpeg or tesseract: `tb.proxy("https://unpkg.com/...")` routes the CDN URL through the local server's origin.
|
|
256
257
|
- **Prefer an `index.html` fragment** over a full HTML document — usually just the mount stub (`<div id="root"></div>`).
|
|
@@ -350,6 +351,97 @@ const { text } = await tb.ai({
|
|
|
350
351
|
|
|
351
352
|
Provider support varies — the level is mapped to provider-specific parameters (e.g. Anthropic's adaptive thinking, OpenAI's reasoning effort).
|
|
352
353
|
|
|
354
|
+
## Charts
|
|
355
|
+
|
|
356
|
+
Mermaid's `xychart-beta` is static, unlabeled bars and lines — no tooltips, no legend, no other chart types. Anything more is a bulb. Start from this skeleton:
|
|
357
|
+
|
|
358
|
+
````markdown
|
|
359
|
+
---
|
|
360
|
+
format: typebulb/v1
|
|
361
|
+
name: Revenue vs Cost
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
**code.tsx**
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
import React from "react"
|
|
368
|
+
import { createRoot } from "react-dom/client"
|
|
369
|
+
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend,
|
|
370
|
+
ResponsiveContainer } from "recharts"
|
|
371
|
+
|
|
372
|
+
type Point = { month: string; revenue: number; cost: number }
|
|
373
|
+
const data = tb.json<Point[]>(0)
|
|
374
|
+
|
|
375
|
+
function App() {
|
|
376
|
+
return (
|
|
377
|
+
<div className="wrap">
|
|
378
|
+
<h1>Revenue vs Cost</h1>
|
|
379
|
+
<ResponsiveContainer width="100%" height={320}>
|
|
380
|
+
<LineChart data={data}>
|
|
381
|
+
<CartesianGrid stroke="currentColor" strokeOpacity={0.1} />
|
|
382
|
+
<XAxis dataKey="month" stroke="currentColor" tick={{ fill: "currentColor", fontSize: 12 }} />
|
|
383
|
+
<YAxis stroke="currentColor" tick={{ fill: "currentColor", fontSize: 12 }} />
|
|
384
|
+
<Tooltip contentStyle={{ background: "Canvas", color: "CanvasText",
|
|
385
|
+
border: "1px solid currentColor", borderRadius: 6 }} />
|
|
386
|
+
<Legend wrapperStyle={{ fontSize: 13 }} />
|
|
387
|
+
<Line dataKey="revenue" stroke="#14b8a6" strokeWidth={2} />
|
|
388
|
+
<Line dataKey="cost" stroke="#e11d48" strokeWidth={2} />
|
|
389
|
+
</LineChart>
|
|
390
|
+
</ResponsiveContainer>
|
|
391
|
+
</div>
|
|
392
|
+
)
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
createRoot(document.getElementById("root")!).render(<App />)
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**index.html**
|
|
399
|
+
|
|
400
|
+
```html
|
|
401
|
+
<div id="root"></div>
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**styles.css**
|
|
405
|
+
|
|
406
|
+
```css
|
|
407
|
+
.wrap {
|
|
408
|
+
max-width: 720px; /* readability cap — omit when the chart earns spread width */
|
|
409
|
+
margin: 0 auto; /* horizontal centering only */
|
|
410
|
+
padding: 24px 16px; /* vertical space as padding, never margin (see Sizing) */
|
|
411
|
+
font: 14px system-ui, sans-serif;
|
|
412
|
+
}
|
|
413
|
+
h1 { font-size: 18px; margin: 0 0 12px; }
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**data.txt**
|
|
417
|
+
|
|
418
|
+
```txt
|
|
419
|
+
[
|
|
420
|
+
{ "month": "Jan", "revenue": 12, "cost": 8 },
|
|
421
|
+
{ "month": "Feb", "revenue": 14, "cost": 9 },
|
|
422
|
+
{ "month": "Mar", "revenue": 11, "cost": 10 },
|
|
423
|
+
{ "month": "Apr", "revenue": 17, "cost": 10 },
|
|
424
|
+
{ "month": "May", "revenue": 21, "cost": 12 },
|
|
425
|
+
{ "month": "Jun", "revenue": 24, "cost": 12 }
|
|
426
|
+
]
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**config.json**
|
|
430
|
+
|
|
431
|
+
```json
|
|
432
|
+
{
|
|
433
|
+
"description": "Monthly revenue vs cost as a two-series line chart.",
|
|
434
|
+
"dependencies": {
|
|
435
|
+
"react": "^19.2.7",
|
|
436
|
+
"react-dom": "^19.2.7",
|
|
437
|
+
"recharts": "^3.8.1"
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
````
|
|
442
|
+
|
|
443
|
+
The non-obvious bits: axes and grid off `currentColor` (light/dark with zero theme JS), the tooltip on `Canvas`/`CanvasText` system colors, and an explicit height on `ResponsiveContainer` — a chart has no natural height; the root still sizes to content. For point-dense marks (thousands of scatter dots or bars) or types recharts lacks (heatmap, candlestick, gauge), use `echarts` (canvas) instead.
|
|
444
|
+
|
|
353
445
|
## License
|
|
354
446
|
|
|
355
447
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -1084,6 +1084,6 @@ ${Nw}
|
|
|
1084
1084
|
Shutting down...`),p.close(),y?.(),t(),await Tr(process.pid),process.exit(0)};process.on("SIGINT",x),process.on("SIGTERM",x)}import*as Md from"path";import{EventEmitter as Ov}from"events";async function Dd(r,e,t,s,n){let i=fe(t),o=!1,a=async()=>{let{bulb:c,config:l}=await ne(r);o||(Le(i,r,c.server),o=!0),await vr(c.server,n,s,l.dependencies)};if(console.log(`Running ${Md.basename(r)}...`),await a(),e){console.log(`Watching for changes...
|
|
1085
1085
|
`);let c=new Ov;c.on("reload",async()=>{try{console.log("Re-running..."),await a()}catch(l){console.error("Error:",l)}}),vn({bulbPath:r,emitter:c})}}import{Console as Nv}from"node:console";ye();Sr();async function Bd(r,e,t,s,n){$v();try{let p=L(r),g=(await B()).find(h=>L(h.file)===p);g&&nn(g.pid,ue(g.pid).offset)}catch{}let i=fe(t),{bulb:o,config:a}=await ne(r);o.server||Pn("This bulb has no **server.ts** block; nothing to call."),Le(i,r,o.server);let c;try{c=await vr(o.server,n,s,a.dependencies)}catch(p){Pn(p instanceof Error?p.message:String(p))}let l=yn(c,e.fn);if(!l){let p=[...Object.keys(c).filter(g=>typeof c[g]=="function"),...Object.keys(sa)];Pn(`Function '${e.fn}' not found. Available: ${p.length?p.join(", "):"(none)"}.`)}let f=await Cv(e),u;try{u=await l(...f)}catch(p){Pn(p instanceof Error?p.stack??p.message:String(p))}let d=_v(u);d!==void 0&&await Mv(d+`
|
|
1086
1086
|
`),process.exit(0)}async function Cv(r){if(r.hasArgsFlag){let e=r.argsJson??"";return e==="-"&&(e=await Dv()),Iv(e)}return Rv(r.positional)}function Rv(r){return r.map(e=>{try{return JSON.parse(e)}catch{return e}})}function Iv(r){let e;try{e=JSON.parse(r)}catch(t){throw new Error(`--args must be a JSON array: ${t instanceof Error?t.message:String(t)}`)}if(!Array.isArray(e))throw new Error(`--args must be a JSON array, got ${e===null?"null":typeof e}`);return e}function _v(r){if(r!==void 0)return JSON.stringify(r,Lv,2)}function Lv(r,e){return typeof e=="bigint"?e.toString():e}function Pn(r){process.stderr.write(r+`
|
|
1087
|
-
`),process.exit(1)}function $v(){let r=new Nv(process.stderr,process.stderr);console.log=r.log.bind(r),console.info=r.info.bind(r),console.debug=r.debug.bind(r),console.dir=r.dir.bind(r)}function Mv(r){return new Promise((e,t)=>{process.stdout.write(r,s=>s?t(s):e())})}async function Dv(){let r=[];for await(let e of process.stdin)r.push(e);return Buffer.concat(r).toString("utf-8")}var qd="0.14.
|
|
1087
|
+
`),process.exit(1)}function $v(){let r=new Nv(process.stderr,process.stderr);console.log=r.log.bind(r),console.info=r.info.bind(r),console.debug=r.debug.bind(r),console.dir=r.dir.bind(r)}function Mv(r){return new Promise((e,t)=>{process.stdout.write(r,s=>s?t(s):e())})}async function Dv(){let r=[];for await(let e of process.stdin)r.push(e);return Buffer.concat(r).toString("utf-8")}var qd="0.14.5";function Fd(r,e){r||(console.error(`This bulb runs server-side Node code (server.ts), which --trust must authorize:
|
|
1088
1088
|
${e}`),process.exit(1))}async function Bv(){let r=Aa(process.argv.slice(2));if(r.version&&(console.log(`typebulb ${qd}`),process.exit(0)),r.help&&(Ta(),process.exit(0)),r.subcommand==="logs"){await Jf(r.file||void 0,{follow:r.follow,lines:r.lines});return}if(r.subcommand==="wait"){await Kf(r.file||void 0,{match:r.match,timeoutSec:r.timeoutSec??1800});return}if(r.subcommand==="stop"){r.stopScope?await Vf(r.stopScope):await Wf(r.file||void 0);return}if(r.subcommand==="skill"){await Ff(qd);return}if(r.subcommand==="models"){await Bf(r.mode);return}if(r.subcommand==="agent"){if(!r.agentTarget){await Lf();return}uf(r.agentTarget)||(console.error(`Unknown agent '${r.agentTarget}'. Known: ${xr().join(", ")}.`),process.exit(1));let l=await Yo(process.cwd(),r.agentTarget);if(l){console.log(`Mirror '${r.agentTarget}' is already running for this project:
|
|
1089
1089
|
${l.url}`),r.open&&await vt(l.url);return}await $d(r);return}if(r.subcommand==="trust"||r.subcommand==="untrust"){await vf(r.file||void 0,r.subcommand==="trust");return}let e;if(!r.file||r.file==="."){let l=await of(process.cwd());l||(console.error("No .bulb.md file found in current directory"),process.exit(1)),e=l}else e=we.resolve(r.file);await jd.access(e).then(()=>!0,()=>!1)||(xr().includes(r.file)&&(console.error(`To open the ${r.file} agent mirror, run: npx typebulb agent:${r.file}`),process.exit(1)),console.error(`File not found: ${e}`),process.exit(1)),e.endsWith(".bulb.md")||(console.error("File must have .bulb.md extension"),process.exit(1));let s=r.file&&r.file!=="."?r.file:we.relative(process.cwd(),e)||we.basename(e),n=`npx typebulb --trust ${s.includes(" ")?`"${s}"`:s}`;if(r.subcommand==="predict"){await wf(e,n);return}let i=!r.noTrust&&We(e);i&&!r.trust&&console.log("trust: granted from memory (run `typebulb untrust` to revoke)"),r.trust=r.noTrust?!1:r.trust||i;let o;try{o=await ne(e)}catch{}let a;if(r.local){o&&!(r.local.name in(o.config.dependencies??{}))&&(console.error(`--replace: '${r.local.name}' is not a dependency in this bulb's config.json; nothing to replace.`),process.exit(1)),o&&r.subcommand!=="call"&&(!o.bulb.code||r.server)&&console.warn("warning: --replace has no effect in server mode (the override is client-only).");try{a=await xa(r.local)}catch(l){console.error(l instanceof Error?l.message:String(l)),process.exit(1)}console.log(`replace: ${a.name} \u2192 ${we.relative(process.cwd(),a.dir)||"."}`)}if(r.subcommand==="check"){await bf(e,a);return}let c=we.dirname(e);if(r.subcommand==="call"){Fd(r.trust,n),await Bd(e,{fn:r.fn,positional:r.callArgs,argsJson:r.argsJson,hasArgsFlag:r.hasArgsFlag},r.mode,a,c);return}if(o&&o.bulb.server&&(!o.bulb.code||r.server)){Fd(r.trust,n),await Dd(e,r.watch,r.mode,a,c);return}await rd(e,r,n,a,c)}Bv().catch(r=>{console.error("Error:",r.message),process.exit(1)});
|
package/package.json
CHANGED