typebulb 0.10.4 → 0.10.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 +1 -1
- package/bulbs/claude.bulb.md +41 -4
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -226,7 +226,7 @@ A local `.bulb.md` can be re-imported into typebulb.com. If it has a `**server.t
|
|
|
226
226
|
|
|
227
227
|
The agent viewer currently supports Claude Code only. `npx typebulb agent:claude` gives the user a great scratchpad experience:
|
|
228
228
|
|
|
229
|
-
* a view over the Claude Code session, where assistant messages containing bulbs render as sandboxed embedded bulbs inline in the conversation, alongside KaTeX math
|
|
229
|
+
* a view over the Claude Code session, where assistant messages containing bulbs render as sandboxed embedded bulbs inline in the conversation, alongside KaTeX math, mermaid diagrams and svg.
|
|
230
230
|
* run and stop any bulb in their project.
|
|
231
231
|
* promote any embedded bulb to a `.bulb.md` file in the `typebulbs/` folder.
|
|
232
232
|
|
package/bulbs/claude.bulb.md
CHANGED
|
@@ -820,6 +820,16 @@ function decodeHtmlEntities(s: string): string {
|
|
|
820
820
|
// ```mermaid``` fences render to inline SVG. A parse error falls through to the
|
|
821
821
|
// default code-block rendering, so an unsupported diagram degrades to readable
|
|
822
822
|
// source rather than a broken message.
|
|
823
|
+
// A hover-revealed copy pill for a rendered fence. Every fenced block — code, svg, mermaid, an
|
|
824
|
+
// unrecognised language — flows through the fence rule below, and the thing worth copying is always
|
|
825
|
+
// its raw body, so one primitive covers them all. The source rides the button's own data-src
|
|
826
|
+
// (escaped going in; getAttribute auto-unescapes coming out, so multi-line bodies round-trip), which
|
|
827
|
+
// decouples the delegated handler from each block's container structure. `.copyable` is the shared
|
|
828
|
+
// marker the rule stamps on every block so one CSS rule can reveal the pill on hover.
|
|
829
|
+
function copyButton(src: string): string {
|
|
830
|
+
return `<button class="overlay-pill copy-src" type="button" title="Copy source" data-src="${md.utils.escapeHtml(src)}">copy</button>`
|
|
831
|
+
}
|
|
832
|
+
|
|
823
833
|
const defaultFence: MdRenderRule = md.renderer.rules.fence
|
|
824
834
|
?? ((tokens, idx, opts, _env, self) => self.renderToken(tokens, idx, opts))
|
|
825
835
|
md.renderer.rules.fence = (tokens: MdToken[], idx: number, opts: unknown, env: unknown, self: MdRenderer) => {
|
|
@@ -840,7 +850,8 @@ md.renderer.rules.fence = (tokens: MdToken[], idx: number, opts: unknown, env: u
|
|
|
840
850
|
// surface is stripped. Lets the agent draw anything (smiley, plot from an
|
|
841
851
|
// equation) without an iframe, since it's static markup, not executed code.
|
|
842
852
|
if (lang === 'svg' && live) {
|
|
843
|
-
|
|
853
|
+
const safe = DOMPurify.sanitize(t.content, { USE_PROFILES: { svg: true, svgFilters: true } })
|
|
854
|
+
return `<div class="embed svg-embed copyable">${safe}${copyButton(t.content)}</div>`
|
|
844
855
|
}
|
|
845
856
|
if (lang === 'mermaid' && live) {
|
|
846
857
|
try {
|
|
@@ -861,10 +872,13 @@ md.renderer.rules.fence = (tokens: MdToken[], idx: number, opts: unknown, env: u
|
|
|
861
872
|
font: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
862
873
|
transparent: true,
|
|
863
874
|
})
|
|
864
|
-
return `<div class="mermaid">${svg}</div>`
|
|
875
|
+
return `<div class="mermaid copyable">${svg}${copyButton(t.content)}</div>`
|
|
865
876
|
} catch { /* fall through to a plain code block */ }
|
|
866
877
|
}
|
|
867
|
-
|
|
878
|
+
// Every other fence (code, an unrecognised language, or a non-live svg/mermaid in a user turn) is a
|
|
879
|
+
// copyable source block: the default <pre><code> wrapped so it gets the same hover copy pill. The
|
|
880
|
+
// wrapper — not the <pre> — is the `.md` flow child now; see `.code-block` CSS for the rhythm.
|
|
881
|
+
return `<div class="code-block copyable">${defaultFence(tokens, idx, opts, env, self)}${copyButton(t.content)}</div>`
|
|
868
882
|
}
|
|
869
883
|
|
|
870
884
|
// Author classDef/style colors render as literal fill/stroke/color that override
|
|
@@ -926,6 +940,16 @@ function fitSvgEmbeds(root: Element) {
|
|
|
926
940
|
// served from the project root, so the browser would GET the path and 404. Links
|
|
927
941
|
// with a scheme (http, mailto, …) fall through to the default new-tab behavior.
|
|
928
942
|
function onMarkdownClick(e: Event) {
|
|
943
|
+
// Per-fence copy: the source is on the button's own data-src (see copyButton), so no container walk.
|
|
944
|
+
const copyBtn = (e.target as Element | null)?.closest<HTMLButtonElement>('.copy-src')
|
|
945
|
+
if (copyBtn) {
|
|
946
|
+
e.preventDefault()
|
|
947
|
+
navigator.clipboard?.writeText(copyBtn.dataset.src ?? '')
|
|
948
|
+
copyBtn.classList.add('done')
|
|
949
|
+
copyBtn.textContent = 'copied'
|
|
950
|
+
setTimeout(() => { copyBtn.classList.remove('done'); copyBtn.textContent = 'copy' }, 1200)
|
|
951
|
+
return
|
|
952
|
+
}
|
|
929
953
|
const anchor = (e.target as Element | null)?.closest('a')
|
|
930
954
|
if (!anchor) return
|
|
931
955
|
const href = anchor.getAttribute('href') ?? ''
|
|
@@ -3050,6 +3074,19 @@ a.server-port:hover { color: var(--accent); text-decoration: underline; }
|
|
|
3050
3074
|
}
|
|
3051
3075
|
.overlay-pill:hover { color: var(--accent); border-color: var(--accent); }
|
|
3052
3076
|
|
|
3077
|
+
/* Per-fence copy pill (code / svg / mermaid) — one reveal rule for every copyable block, since the
|
|
3078
|
+
fence rule stamps `.copyable` on each. The svg/mermaid containers are already position:relative for
|
|
3079
|
+
the chop/breakout; the code-block wrapper needs it for the absolute pill. The pill is positioned
|
|
3080
|
+
(absolute), so it paints over the static fence content regardless of DOM order. */
|
|
3081
|
+
.md .copyable { position: relative; }
|
|
3082
|
+
.md .copyable:hover .copy-src,
|
|
3083
|
+
.md .copy-src:focus-visible { opacity: 1; }
|
|
3084
|
+
.md .copy-src.done { color: var(--accent); border-color: var(--accent); }
|
|
3085
|
+
/* The wrapper carries the code block's vertical rhythm (it, not the <pre>, is the `.md` flow child
|
|
3086
|
+
now, so the first/last-child margin resets land on it); the <pre> sits flush inside. */
|
|
3087
|
+
.md .code-block { margin: .6rem 0; }
|
|
3088
|
+
.md .code-block pre { margin: 0; }
|
|
3089
|
+
|
|
3053
3090
|
/* Controls (code ⇄ run, then copy, then breakout) — a centered overlay straddling the
|
|
3054
3091
|
bulb's top edge: clear of the running bulb below, free to overlap the prose above.
|
|
3055
3092
|
Absolute, so toggling run↔code (which resizes the bulb below) never moves it, and so
|
|
@@ -3302,7 +3339,7 @@ a.server-port:hover { color: var(--accent); text-decoration: underline; }
|
|
|
3302
3339
|
"beautiful-mermaid": "^1.1.3",
|
|
3303
3340
|
"dompurify": "^3.2.6",
|
|
3304
3341
|
"highlight.js": "^11.10.0",
|
|
3305
|
-
"typebulb": "^0.10.
|
|
3342
|
+
"typebulb": "^0.10.5"
|
|
3306
3343
|
}
|
|
3307
3344
|
}
|
|
3308
3345
|
```
|
package/dist/index.js
CHANGED
|
@@ -668,6 +668,6 @@ ${r.trim()}
|
|
|
668
668
|
`)}catch(f){console.error("Compile error:",f)}}),F=vt({bulbPath:r,emitter:u}),n){let{name:f,serveDir:b}=n;L=Fn({dir:b,onChange:()=>{console.log(`Local package '${f}' changed. Browser reloading...
|
|
669
669
|
`),a.emit("reload")}})}}e.open&&await it(g);let _=async()=>{console.log(`
|
|
670
670
|
Shutting down...`),h.close(),F?.(),L?.(),i(),await qt(process.pid);let u=ve.join(ve.dirname(r),".typebulb","server.mjs");await Bn.rm(u,{force:!0}).catch(()=>{}),process.exit(0)};process.on("SIGINT",_),process.on("SIGTERM",_)}import*as Un from"path";import{EventEmitter as Mo}from"events";async function Wn(r,e,t,n,s){let o=ge(t),i=!1,a=async()=>{let{bulb:c,config:l}=await W(r);i||(mt(o,r,c.server),i=!0),await Nt(c.server,s,n,l.dependencies)};if(console.log(`Running ${Un.basename(r)}...`),await a(),e){console.log(`Watching for changes...
|
|
671
|
-
`);let c=new Mo;c.on("reload",async()=>{try{console.log("Re-running..."),await a()}catch(l){console.error("Error:",l)}}),vt({bulbPath:r,emitter:c})}}var Jn="0.10.
|
|
671
|
+
`);let c=new Mo;c.on("reload",async()=>{try{console.log("Re-running..."),await a()}catch(l){console.error("Error:",l)}}),vt({bulbPath:r,emitter:c})}}var Jn="0.10.5";async function jo(){let r=ur(process.argv.slice(2));if(r.version&&(console.log(`typebulb ${Jn}`),process.exit(0)),r.help&&(pr(),process.exit(0)),r.subcommand==="logs"){await _n(r.file||void 0,{follow:r.follow,lines:r.lines});return}if(r.subcommand==="stop"){await Cn(r.file||void 0);return}if(r.subcommand==="skill"){await Pn(Jn);return}if(r.subcommand==="models"){await wn(r.mode);return}if(r.subcommand==="agent"){if(!r.agentTarget){await gn();return}me(r.agentTarget)||(console.error(`Unknown agent '${r.agentTarget}'. Known: ${Wt().join(", ")}.`),process.exit(1));let l=await zt(process.cwd(),r.agentTarget);if(l){console.log(`Viewer '${r.agentTarget}' is already running for this project:
|
|
672
672
|
${l.url}`),r.open&&await it(l.url);return}r.file=r.agentTarget,r.subcommand="run"}if(r.subcommand==="trust"||r.subcommand==="untrust"){await on(r.file||void 0,r.subcommand==="trust");return}let e,t=!1;if(!r.file||r.file==="."){let l=await Hr(process.cwd());l||(console.error("No .bulb.md file found in current directory"),process.exit(1)),e=l}else e=q.resolve(r.file);if(!await Vn.access(e).then(()=>!0,()=>!1)){let l=r.agentTarget?me(r.file):void 0;l?(e=l,t=!0):Wt().includes(r.file)?(console.error(`To open the ${r.file} agent viewer, 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:q.relative(process.cwd(),e)||q.basename(e),o=`npx typebulb --trust ${s.includes(" ")?`"${s}"`:s}`;if(r.subcommand==="predict"){await sn(e,o);return}if(t)r.trust=!r.noTrust,r.trust&&console.log("trust: granted (built-in bulb)");else{let l=!r.noTrust&&ot(e);l&&!r.trust&&console.log("trust: granted from memory (run `typebulb untrust` to revoke)"),r.trust=r.noTrust?!1:r.trust||l}let i;try{i=await W(e)}catch{}let a;if(r.local){i&&!(r.local.name in(i.config.dependencies??{}))&&(console.error(`--replace: '${r.local.name}' is not a dependency in this bulb's config.json; nothing to replace.`),process.exit(1)),i&&(!i.bulb.code||r.server)&&console.warn("warning: --replace has no effect in server mode (the override is client-only).");try{a=await lr(r.local)}catch(l){console.error(l instanceof Error?l.message:String(l)),process.exit(1)}console.log(`replace: ${a.name} \u2192 ${q.relative(process.cwd(),a.dir)||"."}`)}if(r.subcommand==="check"){await nn(e,a);return}let c=t?process.cwd():q.dirname(e);if(i&&i.bulb.server&&(!i.bulb.code||r.server)){r.trust||(console.error(`This bulb runs server-side Node code (server.ts), which --trust must authorize:
|
|
673
673
|
${o}`),process.exit(1)),await Wn(e,r.watch,r.mode,a,c);return}await Ln(e,r,o,a,c)}jo().catch(r=>{console.error("Error:",r.message),process.exit(1)});
|