typebulb 0.10.3 → 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 +107 -33
- 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,11 +820,27 @@ 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) => {
|
|
826
836
|
const t = tokens[idx]
|
|
827
837
|
const lang = (t.info ?? '').trim().toLowerCase()
|
|
838
|
+
// Live embeds (svg, mermaid) are an assistant medium — they render only in assistant turns. In a
|
|
839
|
+
// user turn (env.userMessage, set by userMarkdown) they fall through to a plain source code block:
|
|
840
|
+
// the svg chop and the mermaid lane-breakout both assume the transparent assistant flow and clash
|
|
841
|
+
// with the opaque, padded, rounded user card, and a user's pasted/quoted markup reads better as its
|
|
842
|
+
// literal source anyway. KaTeX/@mentions/links still render in user turns (those aren't gated here).
|
|
843
|
+
const live = !(env as { userMessage?: boolean })?.userMessage
|
|
828
844
|
// Note: no `bulb` case here. Live embeds are split out of the text before markdown runs
|
|
829
845
|
// (splitBulbSegments → BulbEmbed), so a ````bulb```` fence reaching markdown is illustrative
|
|
830
846
|
// source — it falls through to defaultFence like any other unrecognised fence.
|
|
@@ -833,10 +849,11 @@ md.renderer.rules.fence = (tokens: MdToken[], idx: number, opts: unknown, env: u
|
|
|
833
849
|
// it goes through DOMPurify's svg profile first — geometry survives, the script
|
|
834
850
|
// surface is stripped. Lets the agent draw anything (smiley, plot from an
|
|
835
851
|
// equation) without an iframe, since it's static markup, not executed code.
|
|
836
|
-
if (lang === 'svg') {
|
|
837
|
-
|
|
852
|
+
if (lang === 'svg' && live) {
|
|
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>`
|
|
838
855
|
}
|
|
839
|
-
if (lang === 'mermaid') {
|
|
856
|
+
if (lang === 'mermaid' && live) {
|
|
840
857
|
try {
|
|
841
858
|
// The library's internal theme vars share names with the bulb's (--fg/--bg/
|
|
842
859
|
// --muted/--border/--accent), so passing var(--fg) here would emit a
|
|
@@ -855,10 +872,13 @@ md.renderer.rules.fence = (tokens: MdToken[], idx: number, opts: unknown, env: u
|
|
|
855
872
|
font: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
856
873
|
transparent: true,
|
|
857
874
|
})
|
|
858
|
-
return `<div class="mermaid">${svg}</div>`
|
|
875
|
+
return `<div class="mermaid copyable">${svg}${copyButton(t.content)}</div>`
|
|
859
876
|
} catch { /* fall through to a plain code block */ }
|
|
860
877
|
}
|
|
861
|
-
|
|
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>`
|
|
862
882
|
}
|
|
863
883
|
|
|
864
884
|
// Author classDef/style colors render as literal fill/stroke/color that override
|
|
@@ -885,11 +905,51 @@ function themeMermaidNodes(root: Element) {
|
|
|
885
905
|
}
|
|
886
906
|
}
|
|
887
907
|
|
|
908
|
+
// Fit each raw ```svg``` embed to its content. A raw <svg> with only a viewBox has NO intrinsic size,
|
|
909
|
+
// so it defaults to width:100% and stretches to fill the column — which both makes the container's
|
|
910
|
+
// justify-content:center moot (where the art sits *inside* the viewBox is all that positions it, so a
|
|
911
|
+
// drawing parked off-centre in an oversized box reads as left/right-aligned) and blows a small drawing
|
|
912
|
+
// up to a column-wide wall. Two coupled steps fix both: (1) tighten the viewBox to the content's bbox,
|
|
913
|
+
// then (2) set the display width to that bbox's user-unit extent as px (1 unit ≈ 1px), capped by the
|
|
914
|
+
// CSS max-width:100%. Now it renders small when small (and the now-narrower svg actually centres), and
|
|
915
|
+
// a genuinely large drawing still caps at the column. getBBox is geometry-only — it ignores stroke,
|
|
916
|
+
// which paints up to half its width *outside* the geometry — so the viewBox is padded by the largest
|
|
917
|
+
// painted half-stroke (clipping depends on stroke width, not drawing size; a fixed % loses to a thick
|
|
918
|
+
// stroke on a small drawing). The +1 is a hairline guard for anti-aliasing / curve overshoot; pad is 0
|
|
919
|
+
// when nothing strokes (fills never overflow). Skips a node that isn't measurable (zero-area, or laid
|
|
920
|
+
// out inside a collapsed turn → getBBox throws / returns 0), leaving it as authored.
|
|
921
|
+
function fitSvgEmbeds(root: Element) {
|
|
922
|
+
for (const svg of root.querySelectorAll<SVGSVGElement>('.svg-embed svg')) {
|
|
923
|
+
try {
|
|
924
|
+
const b = svg.getBBox()
|
|
925
|
+
if (!b.width || !b.height) continue
|
|
926
|
+
let half = 0
|
|
927
|
+
for (const el of svg.querySelectorAll<SVGElement>('*')) {
|
|
928
|
+
const cs = getComputedStyle(el) // computed, so it catches attribute, CSS, and inherited widths
|
|
929
|
+
if (cs.stroke && cs.stroke !== 'none') half = Math.max(half, parseFloat(cs.strokeWidth) / 2)
|
|
930
|
+
}
|
|
931
|
+
const pad = half ? half + 1 : 0
|
|
932
|
+
svg.setAttribute('viewBox', `${b.x - pad} ${b.y - pad} ${b.width + pad * 2} ${b.height + pad * 2}`)
|
|
933
|
+
svg.style.width = `${b.width + pad * 2}px` // natural size (units≈px); max-width:100% caps it, height:auto keeps ratio
|
|
934
|
+
} catch { /* not laid out / unmeasurable — leave the authored viewBox */ }
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
888
938
|
// A relative-path link in assistant markdown is a local file citation (optionally
|
|
889
939
|
// with a #Lnnn line anchor): open it in the editor, not the browser — the bulb is
|
|
890
940
|
// served from the project root, so the browser would GET the path and 404. Links
|
|
891
941
|
// with a scheme (http, mailto, …) fall through to the default new-tab behavior.
|
|
892
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
|
+
}
|
|
893
953
|
const anchor = (e.target as Element | null)?.closest('a')
|
|
894
954
|
if (!anchor) return
|
|
895
955
|
const href = anchor.getAttribute('href') ?? ''
|
|
@@ -904,10 +964,11 @@ function onMarkdownClick(e: Event) {
|
|
|
904
964
|
// Render markdown into the element via innerHTML. The click handler is bound natively here, not via
|
|
905
965
|
// domeleon's `onClick`: the anchors are raw innerHTML the vdom never sees, so a delegated handler
|
|
906
966
|
// wouldn't fire. onMarkdownClick is a stable ref, so re-mounts don't stack duplicate listeners.
|
|
907
|
-
const renderMarkdown = (text: string) => (el: Element) => {
|
|
967
|
+
const renderMarkdown = (text: string, env?: { userMessage?: boolean }) => (el: Element) => {
|
|
908
968
|
try {
|
|
909
|
-
el.innerHTML = md.render(text)
|
|
969
|
+
el.innerHTML = md.render(text, env)
|
|
910
970
|
themeMermaidNodes(el)
|
|
971
|
+
fitSvgEmbeds(el)
|
|
911
972
|
} catch {
|
|
912
973
|
;(el as HTMLElement).textContent = text
|
|
913
974
|
}
|
|
@@ -936,7 +997,7 @@ const atMentionLink = (m: string): string => {
|
|
|
936
997
|
const userMarkdown = (msg: Msg) => {
|
|
937
998
|
const segs = msg.segments ?? [msg.text]
|
|
938
999
|
const src = segs.join('\n\n---\n\n').replace(AT_MENTION, atMentionLink)
|
|
939
|
-
return renderMarkdown(src)
|
|
1000
|
+
return renderMarkdown(src, { userMessage: true })
|
|
940
1001
|
}
|
|
941
1002
|
|
|
942
1003
|
// Split an assistant message into ordered markdown chunks and live ````bulb```` sources, using
|
|
@@ -1821,7 +1882,7 @@ class BulbEmbed extends Component {
|
|
|
1821
1882
|
}
|
|
1822
1883
|
|
|
1823
1884
|
view() {
|
|
1824
|
-
const cls = ['bulb-embed', this.spread ? 'spread' : 'inline', this.showingCode ? 'code-open' : '', this.#compileError ? 'err' : '']
|
|
1885
|
+
const cls = ['embed', 'bulb-embed', this.spread ? 'spread' : 'inline', this.showingCode ? 'code-open' : '', this.#compileError ? 'err' : '']
|
|
1825
1886
|
const inner =
|
|
1826
1887
|
this.#compileError ? [`bulb: ${this.#compileError}`]
|
|
1827
1888
|
: !this.#frame ? ['compiling bulb…']
|
|
@@ -2922,8 +2983,9 @@ a.server-port:hover { color: var(--accent); text-decoration: underline; }
|
|
|
2922
2983
|
.md .mermaid svg { max-width: none; height: auto; flex: 0 0 auto; }
|
|
2923
2984
|
|
|
2924
2985
|
/* Raw ```svg``` embeds: no breakout — these are drawings (smiley, plot), not wide
|
|
2925
|
-
diagrams, so keep them in the prose column, centred and capped at its width.
|
|
2926
|
-
|
|
2986
|
+
diagrams, so keep them in the prose column, centred and capped at its width. They
|
|
2987
|
+
share the `.embed` stripe-chop below (a drawing earns its own row like a bulb). */
|
|
2988
|
+
.md .svg-embed { justify-content: center; }
|
|
2927
2989
|
.md .svg-embed svg { max-width: 100%; height: auto; }
|
|
2928
2990
|
|
|
2929
2991
|
/* ````bulb```` embeds: a sandboxed nested app. createBulbFrame owns the iframe (auto-height,
|
|
@@ -2934,28 +2996,27 @@ a.server-port:hover { color: var(--accent); text-decoration: underline; }
|
|
|
2934
2996
|
position:relative anchors the controls overlay in BOTH modes (spread re-declares it for the
|
|
2935
2997
|
z-index lift). .err is the compile-failure fallback; .bulb-err-strip is a runtime-error strip
|
|
2936
2998
|
under a live embed (both monospace, muted red). */
|
|
2937
|
-
|
|
2999
|
+
/* The stripe-chop, shared by every opaque in-column visual artifact (the inline bulb and a raw
|
|
3000
|
+
`svg` — each opts in by carrying `.embed`). An opaque bg over position:relative + z-index:0
|
|
3001
|
+
paints above .bubble::before; a -1rem left extension reaches the stripe in the gutter without a
|
|
3002
|
+
full breakout (padding-left:1rem keeps the content in the prose column, and the box's right edge
|
|
3003
|
+
stays at prose-right, so a bulb's controls anchored there don't move). The vertical gap is opaque
|
|
3004
|
+
padding, not margin: it occludes the stripe top and bottom so the cut is flush with the prose (no
|
|
3005
|
+
stub in a transparent gap), symmetric above/below to separate the artifact from surrounding text.
|
|
3006
|
+
margin top/bottom are zeroed — and the abutting prose margins too (below) — so the gap butts the
|
|
3007
|
+
text. Mermaid is NOT on this path: it's transparent and breaks out, occluding via box-shadow.
|
|
3008
|
+
Per-artifact layout (flex direction, centring) lives on .bulb-embed / .svg-embed. */
|
|
3009
|
+
.md .embed {
|
|
2938
3010
|
display: flex;
|
|
2939
|
-
flex-direction: column;
|
|
2940
3011
|
position: relative;
|
|
2941
3012
|
z-index: 0;
|
|
2942
|
-
/* Always cut the turn stripe, both modes: an opaque bg over position:relative + z-index:0
|
|
2943
|
-
paints above .bubble::before, and a -1rem left extension reaches the stripe in the gutter
|
|
2944
|
-
without a full breakout (padding-left:1rem keeps the content in the prose column; the box's
|
|
2945
|
-
right edge stays at prose-right, so the controls anchored there don't move). In spread mode the
|
|
2946
|
-
iframe's own breakout covers the stripe too. */
|
|
2947
3013
|
background: var(--bg);
|
|
2948
|
-
margin
|
|
2949
|
-
padding
|
|
2950
|
-
/* Symmetric vertical gap as opaque padding, not margin: it occludes the stripe top and bottom
|
|
2951
|
-
so it's cut flush with the prose (no stub in a transparent gap), and is the same amount above
|
|
2952
|
-
and below to separate the bulb from surrounding text. margin top/bottom are zeroed — and the
|
|
2953
|
-
abutting prose margins too (below) — so the gap butts against the text. */
|
|
2954
|
-
margin-top: 0;
|
|
2955
|
-
margin-bottom: 0;
|
|
2956
|
-
padding-top: 1.1rem;
|
|
2957
|
-
padding-bottom: 1.1rem;
|
|
3014
|
+
margin: 0 0 0 -1rem;
|
|
3015
|
+
padding: 1.1rem 0 1.1rem 1rem;
|
|
2958
3016
|
}
|
|
3017
|
+
/* A bulb stacks its controls/frame/code vertically; in spread mode the inner iframe's own
|
|
3018
|
+
breakout covers the stripe too (the wrapper still chops in column). */
|
|
3019
|
+
.md .bulb-embed { flex-direction: column; }
|
|
2959
3020
|
/* Inline (default): cap the embed's height so a tall bulb doesn't run away down the transcript.
|
|
2960
3021
|
Past the cap the embed scrolls internally (its own overflow, set by the host↔embed protocol —
|
|
2961
3022
|
the iframe element can't scroll its srcdoc from out here). spread removes the cap. */
|
|
@@ -2980,11 +3041,11 @@ a.server-port:hover { color: var(--accent); text-decoration: underline; }
|
|
|
2980
3041
|
position: relative;
|
|
2981
3042
|
z-index: 0;
|
|
2982
3043
|
}
|
|
2983
|
-
/* Kill the transparent margin between
|
|
2984
|
-
the
|
|
3044
|
+
/* Kill the transparent margin between an embed and the prose it abuts (top and bottom):
|
|
3045
|
+
the embed's own opaque padding is the gap, and a leftover prose margin would let the
|
|
2985
3046
|
turn stripe show through it as a stub past the last/next line of text. */
|
|
2986
|
-
.md :has(+ .
|
|
2987
|
-
.md .
|
|
3047
|
+
.md :has(+ .embed) { margin-bottom: 0; }
|
|
3048
|
+
.md .embed + * { margin-top: 0; }
|
|
2988
3049
|
/* The rounded clip lives on the inner surfaces, not the embed (whose padding is now the
|
|
2989
3050
|
gap), so the bulb's own card — the iframe, or the code view behind the toggle — is
|
|
2990
3051
|
what carries the corners. */
|
|
@@ -3013,6 +3074,19 @@ a.server-port:hover { color: var(--accent); text-decoration: underline; }
|
|
|
3013
3074
|
}
|
|
3014
3075
|
.overlay-pill:hover { color: var(--accent); border-color: var(--accent); }
|
|
3015
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
|
+
|
|
3016
3090
|
/* Controls (code ⇄ run, then copy, then breakout) — a centered overlay straddling the
|
|
3017
3091
|
bulb's top edge: clear of the running bulb below, free to overlap the prose above.
|
|
3018
3092
|
Absolute, so toggling run↔code (which resizes the bulb below) never moves it, and so
|
|
@@ -3265,7 +3339,7 @@ a.server-port:hover { color: var(--accent); text-decoration: underline; }
|
|
|
3265
3339
|
"beautiful-mermaid": "^1.1.3",
|
|
3266
3340
|
"dompurify": "^3.2.6",
|
|
3267
3341
|
"highlight.js": "^11.10.0",
|
|
3268
|
-
"typebulb": "^0.10.
|
|
3342
|
+
"typebulb": "^0.10.5"
|
|
3269
3343
|
}
|
|
3270
3344
|
}
|
|
3271
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)});
|