typebulb 0.3.0 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +73 -7
  2. package/dist/index.js +67 -15
  3. package/package.json +3 -1
package/README.md CHANGED
@@ -1,13 +1,36 @@
1
1
  # typebulb
2
2
 
3
- Run single-file TypeScript apps. A `.bulb.md` file bundles code, HTML, CSS, and server-side logic in one markdown file `typebulb` compiles and serves it locally with hot reload.
3
+ Run single-file apps from markdown. A `.bulb.md` file bundles code, styles, data, and config in one file. `typebulb` compiles and serves it locally with hot reload.
4
4
 
5
- Create bulbs on [typebulb.com](https://typebulb.com) and export, or generate `.bulb.md` files with any AI coding tool.
5
+ Create bulbs on [typebulb.com](https://typebulb.com) and export, or generate `.bulb.md` files with any AI coding tool. See the [FAQ](https://typebulb.com/faq) for the bulb format and `tb.*` API reference.
6
6
 
7
7
  ## Quick Start
8
8
 
9
+ A bulb is a markdown file with named code blocks:
10
+
11
+ ````markdown
12
+ ---
13
+ format: typebulb/v1
14
+ name: My App
15
+ ---
16
+
17
+ **code.tsx**
18
+
19
+ ```tsx
20
+ document.getElementById("root")!.textContent = "Hello from a bulb!";
9
21
  ```
10
- npx typebulb my-bulb.bulb.md
22
+
23
+ **index.html**
24
+
25
+ ```html
26
+ <div id="root"></div>
27
+ ```
28
+ ````
29
+
30
+ Run it:
31
+
32
+ ```
33
+ npx typebulb my-app.bulb.md
11
34
  ```
12
35
 
13
36
  Or install globally:
@@ -25,21 +48,64 @@ typebulb --no-watch <file> Disable hot reload
25
48
  typebulb --port 3333 <file> Custom port
26
49
  typebulb --no-open <file> Don't auto-open browser
27
50
  typebulb --server <file> Run server.ts only, no web server
51
+ typebulb --help Show help
52
+ typebulb --version Show version
28
53
  ```
29
54
 
30
55
  ## Features
31
56
 
32
- - **Hot reload** — Recompiles on save and refreshes the browser (on by default; disable with `--no-watch`)
33
- - **Filesystem access** — `tb.fs.read()` and `tb.fs.write()` for local files
34
- - **Env files** — `.env` and `.env.local` auto-loaded from cwd
35
57
  - **Server-side code** — Add a `**server.ts**` section; exported functions become callable from the browser via `tb.server.<name>()` (e.g., `export async function query(...)` → `await tb.server.query(...)`)
36
58
  - **CLI logging** — `tb.server.log(...)` prints to the CLI's stdout
59
+ - **Env files** — `.env` and `.env.local` auto-loaded from cwd
37
60
  - **Server mode** — `--server` runs only the `**server.ts**` section in Node, skipping the web server. Bulbs with only `**server.ts**` (no `**code.tsx**`) use this mode automatically.
61
+ - **Filesystem access** — `tb.fs.read()` and `tb.fs.write()` for local files
62
+ - **Hot reload** — Recompiles on save and refreshes the browser (on by default; disable with `--no-watch`)
38
63
  - **Package resolution** — Client dependencies are automatically resolved by generating import maps (same resolver as typebulb.com). Server dependencies are automatically installed via npm.
64
+ - **AI calls** — `tb.ai()` for general-purpose AI (chatbots, agents, experiments). `tb.models()` lists available models. Set API keys in `.env` (see below).
65
+
66
+ ## AI Setup
67
+
68
+ Bulbs can call AI providers via `tb.ai()`. Add API keys to your `.env` file:
69
+
70
+ | Provider name | API key env var |
71
+ |---------------|-----------------|
72
+ | `anthropic` | `ANTHROPIC_API_KEY` |
73
+ | `openai` | `OPENAI_API_KEY` |
74
+ | `gemini` | `GOOGLE_API_KEY` |
75
+ | `openrouter` | `OPENROUTER_API_KEY` |
76
+
77
+ Set your default provider and model:
78
+
79
+ ```
80
+ TB_AI_PROVIDER=anthropic
81
+ TB_AI_MODEL=claude-haiku-4-5-20251001
82
+ ```
83
+
84
+ Both can be overridden per-call: `tb.ai({ provider: "gemini", model: "gemini-2.5-flash", ... })`.
85
+
86
+ ### Reasoning
87
+
88
+ `tb.ai()` accepts an optional `reasoning` parameter (0–3) that hints at how much extended thinking the model should use:
89
+
90
+ | Level | Label | Effect |
91
+ |-------|-------|--------|
92
+ | 0 | Min | No extended reasoning (default) |
93
+ | 1 | Low | Light reasoning |
94
+ | 2 | Med | Moderate reasoning |
95
+ | 3 | Max | Maximum reasoning |
96
+
97
+ ```typescript
98
+ const { text } = await tb.ai({
99
+ messages: [{ role: "user", content: "Explain quantum tunneling" }],
100
+ reasoning: 2,
101
+ });
102
+ ```
103
+
104
+ Provider support varies — the level is mapped to provider-specific parameters (e.g. Anthropic's adaptive thinking, OpenAI's reasoning effort).
39
105
 
40
106
  ## Limitations
41
107
 
42
- - **Inference** — `tb.infer()` is not yet supported locally. Bulbs that use inference will render but cannot run LLM calls.
108
+ - **Inference** — `tb.infer()` is not supported locally. Bulbs that use inference will render but cannot run inference calls. Use `tb.ai()` for programmatic AI access instead.
43
109
 
44
110
  ## License
45
111
 
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import*as v from"fs/promises";import{readFileSync as Fe,existsSync as ce}from"fs";import*as w from"path";import{pathToFileURL as Ae}from"url";import{execFile as Be}from"child_process";import{promisify as Ve}from"util";import{EventEmitter as J}from"events";var me={code:{path:"code.tsx",language:"typescript"},css:{path:"styles.css",language:"css"},html:{path:"index.html",language:"html"},config:{path:"config.json",language:"json"},notes:{path:"notes.md",language:"markdown"},data:{path:"data.txt",language:"text"},infer:{path:"infer.md",language:"markdown"},insight:{path:"insight.json",language:"json"},server:{path:"server.ts",language:"typescript"}};function k(s){try{let e=s.split(`
3
- `),t=0;if(e[t]?.trim()!=="---")return null;t++;let r=[];for(;t<e.length&&e[t]?.trim()!=="---";)r.push(e[t]),t++;if(e[t]?.trim()!=="---")return null;t++;let n=ge(r);if(!n)return null;let o=new Map;for(;t<e.length;){let l=e[t]?.trim()?.match(/^\*\*(.+)\*\*$/);if(l){let d=l[1].trim();for(t++;t<e.length&&e[t]?.trim()==="";)t++;let u=e[t]?.match(/^(`{3,})(\w*)\s*$/);if(!u){t++;continue}let a=u[1];t++;let p=[];for(;t<e.length&&!e[t]?.match(new RegExp(`^${a}\\s*$`));)p.push(e[t]),t++;t++,o.set(d,p.join(`
4
- `))}else t++}return{frontmatter:n,files:o}}catch{return null}}function ge(s){let e={};for(let t of s){let r=t.indexOf(":");if(r===-1)continue;let n=t.slice(0,r).trim(),o=t.slice(r+1).trim();switch(n){case"format":e.format=o;break;case"name":e.name=we(o);break}}return!e.format?.startsWith("typebulb")||!e.name?null:e}function we(s){return s.startsWith('"')&&s.endsWith('"')?s.slice(1,-1).replace(/\\"/g,'"'):s.startsWith("'")&&s.endsWith("'")?s.slice(1,-1):s}function D(s){let e=t=>s.files.get(me[t].path)||"";return{name:s.frontmatter.name,code:e("code"),css:e("css"),html:e("html"),config:e("config"),notes:e("notes"),data:e("data"),infer:e("infer"),insight:e("insight"),server:e("server")}}function ve(s){try{return JSON.parse(s),!0}catch{}return!!(/^\s*<[\s\S]*>/.test(s)||/^---\s*$/m.test(s)||/^\w[\w\s]*:[ \t]/m.test(s))}function z(s){let e=s.trim();return e?ve(e)?[e]:s.split(/\n\n\n+/).map(t=>t.trim()).filter(Boolean):[]}function A(s){if(!s.trim())return{};try{return JSON.parse(s)}catch{return{}}}import{transform as G}from"sucrase";function be(s,e){try{let{code:t}=G(s,{transforms:["typescript","jsx"],jsxRuntime:"automatic",jsxImportSource:e.jsxImportSource||"react",production:!0});return{code:t}}catch(t){return{code:"",error:String(t)}}}function K(s,e={}){return be(s,e)}function Y(s){try{let{code:e}=G(s,{transforms:["typescript"]});return{code:e}}catch(e){return{code:"",error:String(e)}}}var R="https://esm.sh",I="https://cdn.jsdelivr.net/npm/",B="https://data.jsdelivr.com/v1/package/npm/";function Z(s){let e=(s||"").replace(/^\/+/,"").replace(/\/+$/,"");return e?e.split("/"):[]}var m=class s{constructor(e,t,r){let n=typeof e=="string"?s.parse(e):e;this.name=n.name,this.version=S(t??n.version),this.subpath=S(r??n.subpath)}static parse(e){let t=Z(e||"");if(!t.length)return new s({name:""});if(t[0].startsWith("@")){let n=t[0],[o,i]=X(t[1]??""),l=S(t.slice(2).join("/"));return new s({name:`${n}/${o}`,version:i,subpath:l})}else{let[n,o]=X(t[0]),i=S(t.slice(1).join("/"));return new s({name:n,version:o,subpath:i})}}static fromUrl(e){try{let t=new URL(e),r=new URL(R).host,n=new URL(I).host;if(t.host===r){let o=Z(t.pathname.replace(/^\/v\d+\//,"/"));if(!o.length)return;let i=o[0].startsWith("@")?`${o[0]}/${o[1]??""}`:o[0];return s.parse(i)}if(t.host===n){let o=t.pathname.split("/npm/")[1];if(!o)return;let i=o.split("/")[0]||"";return s.parse(i)}return}catch{return}}static versionFromUrl(e){return s.fromUrl(e)?.version}format(){let e=this.version?`${this.name}@${this.version}`:this.name;return this.subpath?`${e}/${this.subpath}`:e}root(){return this.name}static rootOf(e){return s.parse(e).name}withVersion(e){return new s({name:this.name,version:S(e),subpath:this.subpath})}withPreferredVersion(e,t){let r=e||t;return r?this.withVersion(r):this}static isBare(e){if(!e||e.startsWith(".")||e.startsWith("/"))return!1;let t=e.toLowerCase();return!t.startsWith("http://")&&!t.startsWith("https://")}},S=s=>s&&s.length?s:void 0,X=s=>{let e=s.indexOf("@");return e<0?[s,void 0]:[s.slice(0,e),S(s.slice(e+1))]};async function b(s){try{return await s()}catch{return}}var $=class{constructor(e,t){this.cache=e,this.http=t,this.esmHost=R,this.jsDelivrBase=I,this.jsDelivrMeta=B,this.pinMs=1e4,this.versionsIndexMs=1440*60*1e3,this.metaTtlMs=10080*60*1e3,this.pinCache=new Map}normalizeRelative(e){let t=e||"";return t.startsWith("./")?t.slice(2):t.replace(/^\/+/,"")}ensureLeadingDotSlash(e){return e.startsWith("./")?e:`./${e}`}baseDir(e){let t=typeof e=="string"?new m(e):e;return`${this.jsDelivrBase}${t.name}${t.version?`@${t.version}`:""}/`}file(e,t){return new URL(this.normalizeRelative(t),this.baseDir(e)).toString()}packageJson(e){return this.file(e,"package.json")}buildEsmUrl(e,t={}){let{target:r="es2022",bundle:n=!1,external:o}=t,i=new URLSearchParams({target:r});return n&&i.append("bundle",""),o?.length&&i.append("external",o.join(",")),`${this.esmHost}/${e}?${i.toString()}`}async pinEsmUrl(e,t="es2022"){let r=this.buildEsmUrl(e,{target:t}),n=await b(()=>this.http.head(r));return n?.ok?n.url||r:void 0}async resolveExactVersion(e){let t=Date.now(),r=this.pinCache.get(e);if(r&&t-r.ts<this.pinMs)return r.value;let n=await this.tryResolveFromUrls([this.buildEsmUrl(e),`${this.esmHost}/${e}`]);return this.pinCache.set(e,{value:n,ts:t}),n}async tryResolveFromUrls(e){for(let t of e){let r=await b(()=>this.http.head(t)),n=this.parseVersionFromUrl(r?.url||t);if(n)return n}}async fetchVersionsIndex(e){if(await this.cache.isNegative(e))return;let t=await this.cache.getIndex(e);if(t&&Date.now()-t.updatedAt<this.versionsIndexMs)return{versions:t.versions,distTags:t.distTags};let r=await b(()=>this.http.getJson(`${this.jsDelivrMeta}${encodeURIComponent(e)}`));if(!r?.versions?.length){await this.cache.recordNegative(e);return}await this.cache.clearNegative(e);let n=r.distTags&&Object.keys(r.distTags).length?r.distTags:void 0;return await this.cache.setIndex(e,r.versions,n),r}parseVersionFromUrl(e){let t=m.fromUrl(e)?.version;return t&&/\d+\.\d+\.\d+/.test(t)?t:void 0}async fetchPackageMeta(e,t){let r=await this.cache.getMeta(e,t);if(r&&Date.now()-r.updatedAt<this.metaTtlMs){let{dependencies:a,peerDependencies:p,peerDependenciesMeta:c}=r;return{name:e,version:t,dependencies:a,peerDependencies:p,peerDependenciesMeta:c}}let n=this.packageJson(new m(`${e}@${t}`)),o=await b(()=>this.http.getJson(n));if(!o)return;let i=a=>a&&Object.keys(a).length?a:void 0,l=i(o.dependencies),d=i(o.peerDependencies),u=i(o.peerDependenciesMeta);return await this.cache.setMeta(e,t,l,d,u),{name:e,version:t,dependencies:l,peerDependencies:d,peerDependenciesMeta:u}}};var Q=s=>s.startsWith("@types/"),j=s=>Object.keys(s?.peerDependencies||{}).filter(e=>!Q(e)),V=s=>Object.keys(s?.dependencies||{}).filter(e=>!Q(e)),ye=s=>j(s).filter(e=>!s?.peerDependenciesMeta?.[e]?.optional),C=class{constructor(e){this.cdn=e}async resolve(e,t){let r=await this.fetchMeta(e),{allRoots:n,autoAddedPeers:o}=await this.expandWithPeers(r,t),i=this.computeFlags(n);return{allRoots:n,flags:i,autoAddedPeers:o}}async fetchMeta(e){return Promise.all(e.map(async({name:t,version:r})=>({name:t,version:r,meta:await this.cdn.fetchPackageMeta(t,r)})))}async expandWithPeers(e,t){let r=new Map(e.map(o=>[o.name,o])),n=[];for(let o of e)for(let i of ye(o.meta))!r.has(i)&&!n.some(l=>l.name===i)&&n.push({name:i,requiredBy:o.name});for(let{name:o,requiredBy:i}of n)try{let l=await t(o),d=await this.cdn.fetchPackageMeta(o,l);r.set(o,{name:o,version:l,meta:d})}catch(l){console.warn(`[typebulb] Failed to resolve peer "${o}" for "${i}":`,l)}return{allRoots:[...r.values()],autoAddedPeers:n.filter(o=>r.has(o.name))}}computeFlags(e){let t=new Set(e.flatMap(o=>j(o.meta))),r=new Map;for(let o of e)for(let i of V(o.meta))r.set(i,(r.get(i)||0)+1);let n=new Set([...r.entries()].filter(([,o])=>o>=2).map(([o])=>o));return new Map(e.map(o=>[o.name,{isPeerRoot:t.has(o.name),hasPeers:j(o.meta).length>0,isSharedDep:n.has(o.name)}]))}};var M=class{constructor(e,t,r){this.cache=e,this.cdn=t,this.semver=r}selectVersionFromIndex(e,t,r){return this.semver.selectBestVersion(e,{range:t,distTags:r})}async learnExactVersion(e){let t=await b(()=>this.cdn.fetchVersionsIndex(e));if(t?.versions?.length){let r=this.semver.selectBestVersion(t.versions,{distTags:t.distTags});if(r)return r}return this.cdn.resolveExactVersion(e)}async resolveExactForRoot(e,t){if(!t)return this.learnExactVersion(e);let r=await this.cache.getPinnedExact(e,t);if(r){if(this.semver.isExactVersion(r))return r;console.warn("[versionResolver] Rejecting invalid cached version for",e,":",r)}let n=await b(()=>this.cdn.fetchVersionsIndex(e));if(n?.versions?.length){let i=this.selectVersionFromIndex(n.versions,t,n.distTags);if(i){if(this.semver.isExactVersion(i))return await this.cache.setPinnedExact(e,t,i),i}else{console.warn("[versionResolver] Invalidating stale versions cache for",e,"- range",t,"not satisfied"),await this.cache.invalidateVersionsCache(e);let l=await b(()=>this.cdn.fetchVersionsIndex(e));if(l?.versions?.length){let d=this.selectVersionFromIndex(l.versions,t,l.distTags);if(d&&this.semver.isExactVersion(d))return await this.cache.setPinnedExact(e,t,d),d}}}let o=await this.cdn.resolveExactVersion(`${e}@${t}`);if(o&&this.semver.isExactVersion(o))return await this.cache.setPinnedExact(e,t,o),o}async effectivePackage(e,t){let r=new m(e),n=r.root(),o=t[n],i=o?await b(()=>this.cache.getPinnedExact(n,o))??await b(()=>this.resolveExactForRoot(n,o)):void 0;return{effectivePackage:i?r.withVersion(i).format():e,root:n,range:o,pinned:i}}};import{init as xe,parse as Se}from"es-module-lexer";var E=class s{constructor(e,t,r,n){this.version=e,this.cdn=t,this.peer=r,this.cache=n}extractImportsSync(e){let t=new Set;for(let r of s.importPatterns){r.lastIndex=0;for(let n of e.matchAll(r))m.isBare(n[1])&&t.add(n[1])}return Array.from(t)}async extractImports(e){let t=new Set,r=n=>{m.isBare(n)&&t.add(n)};try{await xe;let[n]=Se(e);n.forEach(o=>r(e.slice(o.s,o.e).trim()))}catch{return this.extractImportsSync(e)}return Array.from(t)}async buildImportMap(e,t){let r=await this.extractImports(e),n=[...new Set(r.map(m.rootOf))],o=await Promise.all(n.map(async a=>({name:a,version:await this.resolveVersion(a,t)}))),{allRoots:i,flags:l,autoAddedPeers:d}=await this.peer.resolve(o,a=>this.resolveVersion(a,t)),u=this.buildEntries([...r,...d.map(a=>a.name)],i,l,t);return{importMap:{imports:Object.fromEntries(u)},prefetchUrls:u.map(([,a])=>a)}}async resolveVersion(e,t){let r=t[e],n=await this.version.resolveExactForRoot(e,r);if(!n){let o=r?`${e}@${r}`:e,i=await b(()=>this.cdn.pinEsmUrl(o));if(!i)throw new Error(`Cannot resolve version for ${e} - network may be unavailable.`);n=m.versionFromUrl(i),n&&r&&await b(()=>this.cache.setPinnedExact(e,r,n))}if(!n)throw new Error(`Cannot resolve version for ${e}`);return n}buildEntries(e,t,r,n){let o=new Map(t.map(c=>[c.name,c])),i=c=>{let f=o.get(m.rootOf(c));return new m(c).withPreferredVersion(f.version,n[f.name]).format()},l=new Set([...r.entries()].filter(([,c])=>c.isPeerRoot||c.isSharedDep).map(([c])=>c)),d=new Set(e.filter(c=>c!==m.rootOf(c)).map(m.rootOf)),u=[],a=new Set,p=new Set(e.filter(c=>c===m.rootOf(c)));for(let c of e){let f=m.rootOf(c),g=o.get(f),{isPeerRoot:h,hasPeers:y,isSharedDep:T}=r.get(f),O=d.has(f),ue=c!==f,fe=!(h||T)&&(O||!y),F=this.singletonDepsOf(g,l),he=ue&&p.has(f)?[...F,f]:F.length?F:void 0;u.push([c,this.cdn.buildEsmUrl(i(c),{bundle:fe,external:he})]),a.add(c)}for(let c of l)o.has(c)&&(a.has(c)||u.push([c,this.cdn.buildEsmUrl(i(c),{})]),u.push([`${c}/`,`${this.cdn.esmHost}/${i(c)}/`]));return u}singletonDepsOf(e,t){return[...new Set([...j(e.meta),...V(e.meta)])].filter(r=>t.has(r))}};E.importPatterns=[/\bimport\s+(?:[^'";]*?from\s*)?['"]([^'"]+)['"]/g,/\bexport\s+[^'";]*?from\s*['"]([^'"]+)['"]/g];import{gt as ee,satisfies as Ee,maxSatisfying as U,major as Pe,prerelease as Re,rsort as Ie,valid as $e}from"semver";var _=class{cmp(e,t){return e===t?0:ee(e,t)?1:ee(t,e)?-1:0}satisfies(e,t){return!e||!e.trim()?!0:!!Ee(t,e,{includePrerelease:!0})}pickMaxSatisfying(e,t){if(!e?.length)return;let r=U(e,t,{includePrerelease:!0});return r===null?void 0:r}pickLatest(e){return e?.length?Ie(e)[0]:void 0}selectBestVersion(e,t){if(!e?.length)return;let r=t?.range?.trim()||"*",n=t?.preferStable??!0,o=t?.distTags?.latest;if(o&&e.includes(o)&&this.satisfies(r,o))return o;if(n){let l=U(e,r,{includePrerelease:!1});if(l)return l}return U(e,r,{includePrerelease:!0})??void 0}majorOf(e){return Pe(e)}isPrerelease(e){return Re(e)!==null}isExactVersion(e){return $e(e)!==null}},W=new _;function te(s,e){let t=new $(s,e),r=new C(t),n=new M(s,t,W);return{packageService:new E(n,t,r,s),versionResolver:n,cdnClient:t,peerResolver:r}}var N=class{pins=new Map;indexes=new Map;negatives=new Set;meta=new Map;async getPinnedExact(e,t){return this.pins.get(`${e}@${t}`)}async setPinnedExact(e,t,r){this.pins.set(`${e}@${t}`,r)}async getIndex(e){return this.indexes.get(e)}async setIndex(e,t,r){this.indexes.set(e,{versions:t,distTags:r,updatedAt:Date.now()})}async invalidateVersionsCache(e){this.indexes.delete(e)}async isNegative(e){return this.negatives.has(e)}async recordNegative(e){this.negatives.add(e)}async clearNegative(e){this.negatives.delete(e)}async getMeta(e,t){return this.meta.get(`${e}@${t}`)}async setMeta(e,t,r,n,o){this.meta.set(`${e}@${t}`,{dependencies:r,peerDependencies:n,peerDependenciesMeta:o,updatedAt:Date.now()})}},je={async getJson(s){try{let e=await fetch(s,{redirect:"follow"});return e.ok?await e.json():void 0}catch{return}},async head(s){try{let e=await fetch(s,{method:"HEAD",redirect:"follow"});return{ok:e.ok,url:e.url}}catch{return}}},Ce=new N,{packageService:re,versionResolver:kt,cdnClient:Dt,peerResolver:_t}=te(Ce,je);var se=`
2
+ import*as v from"fs/promises";import{readFileSync as et,existsSync as Se}from"fs";import*as y from"path";import{pathToFileURL as tt}from"url";import{execFile as rt}from"child_process";import{promisify as st}from"util";import{EventEmitter as se}from"events";var Ae={code:{path:"code.tsx",language:"typescript"},css:{path:"styles.css",language:"css"},html:{path:"index.html",language:"html"},config:{path:"config.json",language:"json"},notes:{path:"notes.md",language:"markdown"},data:{path:"data.txt",language:"text"},infer:{path:"infer.md",language:"markdown"},insight:{path:"insight.json",language:"json"},server:{path:"server.ts",language:"typescript"}};function W(n){try{let e=n.split(`
3
+ `),t=0;if(e[t]?.trim()!=="---")return null;t++;let r=[];for(;t<e.length&&e[t]?.trim()!=="---";)r.push(e[t]),t++;if(e[t]?.trim()!=="---")return null;t++;let s=ke(r);if(!s)return null;let o=new Map;for(;t<e.length;){let l=e[t]?.trim()?.match(/^\*\*(.+)\*\*$/);if(l){let d=l[1].trim();for(t++;t<e.length&&e[t]?.trim()==="";)t++;let u=e[t]?.match(/^(`{3,})(\w*)\s*$/);if(!u){t++;continue}let a=u[1];t++;let p=[];for(;t<e.length&&!e[t]?.match(new RegExp(`^${a}\\s*$`));)p.push(e[t]),t++;t++,o.set(d,p.join(`
4
+ `))}else t++}return{frontmatter:s,files:o}}catch{return null}}function ke(n){let e={};for(let t of n){let r=t.indexOf(":");if(r===-1)continue;let s=t.slice(0,r).trim(),o=t.slice(r+1).trim();switch(s){case"format":e.format=o;break;case"name":e.name=Te(o);break}}return!e.format?.startsWith("typebulb")||!e.name?null:e}function Te(n){return n.startsWith('"')&&n.endsWith('"')?n.slice(1,-1).replace(/\\"/g,'"'):n.startsWith("'")&&n.endsWith("'")?n.slice(1,-1):n}function J(n){let e=t=>n.files.get(Ae[t].path)||"";return{name:n.frontmatter.name,code:e("code"),css:e("css"),html:e("html"),config:e("config"),notes:e("notes"),data:e("data"),infer:e("infer"),insight:e("insight"),server:e("server")}}function Oe(n){try{return JSON.parse(n),!0}catch{}return!!(/^\s*<[\s\S]*>/.test(n)||/^---\s*$/m.test(n)||/^\w[\w\s]*:[ \t]/m.test(n))}function oe(n){let e=n.trim();return e?Oe(e)?[e]:n.split(/\n\n\n+/).map(t=>t.trim()).filter(Boolean):[]}function G(n){if(!n.trim())return{};try{return JSON.parse(n)}catch{return{}}}import{transform as ie}from"sucrase";function Me(n,e){try{let{code:t}=ie(n,{transforms:["typescript","jsx"],jsxRuntime:"automatic",jsxImportSource:e.jsxImportSource||"react",production:!0});return{code:t}}catch(t){return{code:"",error:String(t)}}}function ae(n,e={}){return Me(n,e)}function ce(n){try{let{code:e}=ie(n,{transforms:["typescript"]});return{code:e}}catch(e){return{code:"",error:String(e)}}}var M="https://esm.sh",$="https://cdn.jsdelivr.net/npm/",z="https://data.jsdelivr.com/v1/package/npm/";function le(n){let e=(n||"").replace(/^\/+/,"").replace(/\/+$/,"");return e?e.split("/"):[]}var g=class n{constructor(e,t,r){let s=typeof e=="string"?n.parse(e):e;this.name=s.name,this.version=I(t??s.version),this.subpath=I(r??s.subpath)}static parse(e){let t=le(e||"");if(!t.length)return new n({name:""});if(t[0].startsWith("@")){let s=t[0],[o,i]=pe(t[1]??""),l=I(t.slice(2).join("/"));return new n({name:`${s}/${o}`,version:i,subpath:l})}else{let[s,o]=pe(t[0]),i=I(t.slice(1).join("/"));return new n({name:s,version:o,subpath:i})}}static fromUrl(e){try{let t=new URL(e),r=new URL(M).host,s=new URL($).host;if(t.host===r){let o=le(t.pathname.replace(/^\/v\d+\//,"/"));if(!o.length)return;let i=o[0].startsWith("@")?`${o[0]}/${o[1]??""}`:o[0];return n.parse(i)}if(t.host===s){let o=t.pathname.split("/npm/")[1];if(!o)return;let i=o.split("/")[0]||"";return n.parse(i)}return}catch{return}}static versionFromUrl(e){return n.fromUrl(e)?.version}format(){let e=this.version?`${this.name}@${this.version}`:this.name;return this.subpath?`${e}/${this.subpath}`:e}root(){return this.name}static rootOf(e){return n.parse(e).name}withVersion(e){return new n({name:this.name,version:I(e),subpath:this.subpath})}withPreferredVersion(e,t){let r=e||t;return r?this.withVersion(r):this}static isBare(e){if(!e||e.startsWith(".")||e.startsWith("/"))return!1;let t=e.toLowerCase();return!t.startsWith("http://")&&!t.startsWith("https://")}},I=n=>n&&n.length?n:void 0,pe=n=>{let e=n.indexOf("@");return e<0?[n,void 0]:[n.slice(0,e),I(n.slice(e+1))]};async function b(n){try{return await n()}catch{return}}var C=class{constructor(e,t){this.cache=e,this.http=t,this.esmHost=M,this.jsDelivrBase=$,this.jsDelivrMeta=z,this.pinMs=1e4,this.versionsIndexMs=1440*60*1e3,this.metaTtlMs=10080*60*1e3,this.pinCache=new Map}normalizeRelative(e){let t=e||"";return t.startsWith("./")?t.slice(2):t.replace(/^\/+/,"")}ensureLeadingDotSlash(e){return e.startsWith("./")?e:`./${e}`}baseDir(e){let t=typeof e=="string"?new g(e):e;return`${this.jsDelivrBase}${t.name}${t.version?`@${t.version}`:""}/`}file(e,t){return new URL(this.normalizeRelative(t),this.baseDir(e)).toString()}packageJson(e){return this.file(e,"package.json")}buildEsmUrl(e,t={}){let{target:r="es2022",bundle:s=!1,external:o}=t,i=new URLSearchParams({target:r});return s&&i.append("bundle",""),o?.length&&i.append("external",o.join(",")),`${this.esmHost}/${e}?${i.toString()}`}async pinEsmUrl(e,t="es2022"){let r=this.buildEsmUrl(e,{target:t}),s=await b(()=>this.http.head(r));return s?.ok?s.url||r:void 0}async resolveExactVersion(e){let t=Date.now(),r=this.pinCache.get(e);if(r&&t-r.ts<this.pinMs)return r.value;let s=await this.tryResolveFromUrls([this.buildEsmUrl(e),`${this.esmHost}/${e}`]);return this.pinCache.set(e,{value:s,ts:t}),s}async tryResolveFromUrls(e){for(let t of e){let r=await b(()=>this.http.head(t)),s=this.parseVersionFromUrl(r?.url||t);if(s)return s}}async fetchVersionsIndex(e){if(await this.cache.isNegative(e))return;let t=await this.cache.getIndex(e);if(t&&Date.now()-t.updatedAt<this.versionsIndexMs)return{versions:t.versions,distTags:t.distTags};let r=await b(()=>this.http.getJson(`${this.jsDelivrMeta}${encodeURIComponent(e)}`));if(!r?.versions?.length){await this.cache.recordNegative(e);return}await this.cache.clearNegative(e);let s=r.distTags&&Object.keys(r.distTags).length?r.distTags:void 0;return await this.cache.setIndex(e,r.versions,s),r}parseVersionFromUrl(e){let t=g.fromUrl(e)?.version;return t&&/\d+\.\d+\.\d+/.test(t)?t:void 0}async fetchPackageMeta(e,t){let r=await this.cache.getMeta(e,t);if(r&&Date.now()-r.updatedAt<this.metaTtlMs){let{dependencies:a,peerDependencies:p,peerDependenciesMeta:c}=r;return{name:e,version:t,dependencies:a,peerDependencies:p,peerDependenciesMeta:c}}let s=this.packageJson(new g(`${e}@${t}`)),o=await b(()=>this.http.getJson(s));if(!o)return;let i=a=>a&&Object.keys(a).length?a:void 0,l=i(o.dependencies),d=i(o.peerDependencies),u=i(o.peerDependenciesMeta);return await this.cache.setMeta(e,t,l,d,u),{name:e,version:t,dependencies:l,peerDependencies:d,peerDependenciesMeta:u}}};var de=n=>n.startsWith("@types/"),D=n=>Object.keys(n?.peerDependencies||{}).filter(e=>!de(e)),K=n=>Object.keys(n?.dependencies||{}).filter(e=>!de(e)),$e=n=>D(n).filter(e=>!n?.peerDependenciesMeta?.[e]?.optional),B=class{constructor(e){this.cdn=e}async resolve(e,t){let r=await this.fetchMeta(e),{allRoots:s,autoAddedPeers:o}=await this.expandWithPeers(r,t),i=this.computeFlags(s);return{allRoots:s,flags:i,autoAddedPeers:o}}async fetchMeta(e){return Promise.all(e.map(async({name:t,version:r})=>({name:t,version:r,meta:await this.cdn.fetchPackageMeta(t,r)})))}async expandWithPeers(e,t){let r=new Map(e.map(o=>[o.name,o])),s=[];for(let o of e)for(let i of $e(o.meta))!r.has(i)&&!s.some(l=>l.name===i)&&s.push({name:i,requiredBy:o.name});for(let{name:o,requiredBy:i}of s)try{let l=await t(o),d=await this.cdn.fetchPackageMeta(o,l);r.set(o,{name:o,version:l,meta:d})}catch(l){console.warn(`[typebulb] Failed to resolve peer "${o}" for "${i}":`,l)}return{allRoots:[...r.values()],autoAddedPeers:s.filter(o=>r.has(o.name))}}computeFlags(e){let t=new Set(e.flatMap(o=>D(o.meta))),r=new Map;for(let o of e)for(let i of K(o.meta))r.set(i,(r.get(i)||0)+1);let s=new Set([...r.entries()].filter(([,o])=>o>=2).map(([o])=>o));return new Map(e.map(o=>[o.name,{isPeerRoot:t.has(o.name),hasPeers:D(o.meta).length>0,isSharedDep:s.has(o.name)}]))}};var N=class{constructor(e,t,r){this.cache=e,this.cdn=t,this.semver=r}selectVersionFromIndex(e,t,r){return this.semver.selectBestVersion(e,{range:t,distTags:r})}async learnExactVersion(e){let t=await b(()=>this.cdn.fetchVersionsIndex(e));if(t?.versions?.length){let r=this.semver.selectBestVersion(t.versions,{distTags:t.distTags});if(r)return r}return this.cdn.resolveExactVersion(e)}async resolveExactForRoot(e,t){if(!t)return this.learnExactVersion(e);let r=await this.cache.getPinnedExact(e,t);if(r){if(this.semver.isExactVersion(r))return r;console.warn("[versionResolver] Rejecting invalid cached version for",e,":",r)}let s=await b(()=>this.cdn.fetchVersionsIndex(e));if(s?.versions?.length){let i=this.selectVersionFromIndex(s.versions,t,s.distTags);if(i){if(this.semver.isExactVersion(i))return await this.cache.setPinnedExact(e,t,i),i}else{console.warn("[versionResolver] Invalidating stale versions cache for",e,"- range",t,"not satisfied"),await this.cache.invalidateVersionsCache(e);let l=await b(()=>this.cdn.fetchVersionsIndex(e));if(l?.versions?.length){let d=this.selectVersionFromIndex(l.versions,t,l.distTags);if(d&&this.semver.isExactVersion(d))return await this.cache.setPinnedExact(e,t,d),d}}}let o=await this.cdn.resolveExactVersion(`${e}@${t}`);if(o&&this.semver.isExactVersion(o))return await this.cache.setPinnedExact(e,t,o),o}async effectivePackage(e,t){let r=new g(e),s=r.root(),o=t[s],i=o?await b(()=>this.cache.getPinnedExact(s,o))??await b(()=>this.resolveExactForRoot(s,o)):void 0;return{effectivePackage:i?r.withVersion(i).format():e,root:s,range:o,pinned:i}}};import{init as Ce,parse as De}from"es-module-lexer";var A=class n{constructor(e,t,r,s){this.version=e,this.cdn=t,this.peer=r,this.cache=s}extractImportsSync(e){let t=new Set;for(let r of n.importPatterns){r.lastIndex=0;for(let s of e.matchAll(r))g.isBare(s[1])&&t.add(s[1])}return Array.from(t)}async extractImports(e){let t=new Set,r=s=>{g.isBare(s)&&t.add(s)};try{await Ce;let[s]=De(e);s.forEach(o=>r(e.slice(o.s,o.e).trim()))}catch{return this.extractImportsSync(e)}return Array.from(t)}async buildImportMap(e,t){let r=await this.extractImports(e),s=[...new Set(r.map(g.rootOf))],o=await Promise.all(s.map(async a=>({name:a,version:await this.resolveVersion(a,t)}))),{allRoots:i,flags:l,autoAddedPeers:d}=await this.peer.resolve(o,a=>this.resolveVersion(a,t)),u=this.buildEntries([...r,...d.map(a=>a.name)],i,l,t);return{importMap:{imports:Object.fromEntries(u)},prefetchUrls:u.map(([,a])=>a)}}async resolveVersion(e,t){let r=t[e],s=await this.version.resolveExactForRoot(e,r);if(!s){let o=r?`${e}@${r}`:e,i=await b(()=>this.cdn.pinEsmUrl(o));if(!i)throw new Error(`Cannot resolve version for ${e} - network may be unavailable.`);s=g.versionFromUrl(i),s&&r&&await b(()=>this.cache.setPinnedExact(e,r,s))}if(!s)throw new Error(`Cannot resolve version for ${e}`);return s}buildEntries(e,t,r,s){let o=new Map(t.map(c=>[c.name,c])),i=c=>{let f=o.get(g.rootOf(c));return new g(c).withPreferredVersion(f.version,s[f.name]).format()},l=new Set([...r.entries()].filter(([,c])=>c.isPeerRoot||c.isSharedDep).map(([c])=>c)),d=new Set(e.filter(c=>c!==g.rootOf(c)).map(g.rootOf)),u=[],a=new Set,p=new Set(e.filter(c=>c===g.rootOf(c)));for(let c of e){let f=g.rootOf(c),h=o.get(f),{isPeerRoot:m,hasPeers:w,isSharedDep:R}=r.get(f),P=d.has(f),V=c!==f,H=!(m||R)&&(P||!w),E=this.singletonDepsOf(h,l),Ie=V&&p.has(f)?[...E,f]:E.length?E:void 0;u.push([c,this.cdn.buildEsmUrl(i(c),{bundle:H,external:Ie})]),a.add(c)}for(let c of l)o.has(c)&&(a.has(c)||u.push([c,this.cdn.buildEsmUrl(i(c),{})]),u.push([`${c}/`,`${this.cdn.esmHost}/${i(c)}/`]));return u}singletonDepsOf(e,t){return[...new Set([...D(e.meta),...K(e.meta)])].filter(r=>t.has(r))}};A.importPatterns=[/\bimport\s+(?:[^'";]*?from\s*)?['"]([^'"]+)['"]/g,/\bexport\s+[^'";]*?from\s*['"]([^'"]+)['"]/g];import{gt as ue,satisfies as Be,maxSatisfying as Y,major as Ne,prerelease as je,rsort as Fe,valid as Ue}from"semver";var q=class{cmp(e,t){return e===t?0:ue(e,t)?1:ue(t,e)?-1:0}satisfies(e,t){return!e||!e.trim()?!0:!!Be(t,e,{includePrerelease:!0})}pickMaxSatisfying(e,t){if(!e?.length)return;let r=Y(e,t,{includePrerelease:!0});return r===null?void 0:r}pickLatest(e){return e?.length?Fe(e)[0]:void 0}selectBestVersion(e,t){if(!e?.length)return;let r=t?.range?.trim()||"*",s=t?.preferStable??!0,o=t?.distTags?.latest;if(o&&e.includes(o)&&this.satisfies(r,o))return o;if(s){let l=Y(e,r,{includePrerelease:!1});if(l)return l}return Y(e,r,{includePrerelease:!0})??void 0}majorOf(e){return Ne(e)}isPrerelease(e){return je(e)!==null}isExactVersion(e){return Ue(e)!==null}},X=new q;function fe(n,e){let t=new C(n,e),r=new B(t),s=new N(n,t,X);return{packageService:new A(s,t,r,n),versionResolver:s,cdnClient:t,peerResolver:r}}var Z=class{pins=new Map;indexes=new Map;negatives=new Set;meta=new Map;async getPinnedExact(e,t){return this.pins.get(`${e}@${t}`)}async setPinnedExact(e,t,r){this.pins.set(`${e}@${t}`,r)}async getIndex(e){return this.indexes.get(e)}async setIndex(e,t,r){this.indexes.set(e,{versions:t,distTags:r,updatedAt:Date.now()})}async invalidateVersionsCache(e){this.indexes.delete(e)}async isNegative(e){return this.negatives.has(e)}async recordNegative(e){this.negatives.add(e)}async clearNegative(e){this.negatives.delete(e)}async getMeta(e,t){return this.meta.get(`${e}@${t}`)}async setMeta(e,t,r,s,o){this.meta.set(`${e}@${t}`,{dependencies:r,peerDependencies:s,peerDependenciesMeta:o,updatedAt:Date.now()})}},Le={async getJson(n){try{let e=await fetch(n,{redirect:"follow"});return e.ok?await e.json():void 0}catch{return}},async head(n){try{let e=await fetch(n,{method:"HEAD",redirect:"follow"});return{ok:e.ok,url:e.url}}catch{return}}},Ve=new Z,{packageService:me,versionResolver:Yt,cdnClient:Xt,peerResolver:Zt}=fe(Ve,Le);var he=`
5
5
  (() => {
6
6
  // JSON parser (handles jsonish - unquoted keys)
7
7
  const parseJson = (str) => {
@@ -70,6 +70,30 @@ import*as v from"fs/promises";import{readFileSync as Fe,existsSync as ce}from"fs
70
70
  setData: () => {},
71
71
  resetInferenceState: () => {},
72
72
 
73
+ // AI - calls local server which proxies to LLM provider
74
+ ai: async ({ messages, system, reasoning, provider, model }) => {
75
+ const resp = await fetch('/__ai', {
76
+ method: 'POST',
77
+ headers: { 'Content-Type': 'application/json' },
78
+ body: JSON.stringify({ messages, system, reasoning, provider, model })
79
+ });
80
+ const data = await resp.json();
81
+ if (!resp.ok) {
82
+ const err = new Error(data.message || 'tb.ai() call failed');
83
+ err.code = data.code || 'unknown';
84
+ err.retryable = !!data.retryable;
85
+ throw err;
86
+ }
87
+ return data;
88
+ },
89
+
90
+ // Model discovery - fetches catalog from typebulb.com, filtered by local API keys
91
+ models: async () => {
92
+ const resp = await fetch('/__models');
93
+ if (!resp.ok) return [];
94
+ return resp.json();
95
+ },
96
+
73
97
  // Dump just logs to console in local mode
74
98
  dump: async (...args) => console.log('[tb.dump]', ...args),
75
99
 
@@ -106,7 +130,10 @@ import*as v from"fs/promises";import{readFileSync as Fe,existsSync as ce}from"fs
106
130
  }),
107
131
 
108
132
  // Filesystem - local CLI extension
109
- fs
133
+ fs,
134
+
135
+ // Environment
136
+ mode: 'local'
110
137
  });
111
138
 
112
139
  // Hot reload listener
@@ -122,12 +149,12 @@ import*as v from"fs/promises";import{readFileSync as Fe,existsSync as ce}from"fs
122
149
  };
123
150
  }
124
151
  })();
125
- `;function ne(s){let{name:e,code:t,css:r,html:n,data:o,insight:i,importMap:l,watch:d}=s,u=n.trim()||'<div id="app"></div>',a=p=>p.replace(/<\/script/gi,"<\\/script");return`<!DOCTYPE html>
152
+ `;function ge(n){let{name:e,code:t,css:r,html:s,data:o,insight:i,importMap:l,watch:d}=n,u=s.trim()||'<div id="app"></div>',a=p=>p.replace(/<\/script/gi,"<\\/script");return`<!DOCTYPE html>
126
153
  <html>
127
154
  <head>
128
155
  <meta charset="utf-8">
129
156
  <meta name="viewport" content="width=device-width, initial-scale=1">
130
- <title>${Me(e)} - typebulb</title>
157
+ <title>${He(e)} - typebulb</title>
131
158
  <script type="importmap">
132
159
  ${JSON.stringify(l,null,2)}
133
160
  </script>
@@ -148,14 +175,23 @@ ${i?`<script>window.__TB_INSIGHT__ = ${a(JSON.stringify(i))};</script>`:""}
148
175
  ${d?"<script>window.__TYPEBULB_WATCH__ = true;</script>":""}
149
176
 
150
177
  <script>
151
- ${se}
178
+ ${he}
152
179
  </script>
153
180
 
154
181
  <script type="module">
155
182
  ${a(t)}
156
183
  </script>
157
184
  </body>
158
- </html>`}function Me(s){return s.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}import{Hono as Te}from"hono";import{serve as Oe}from"@hono/node-server";import{streamSSE as ke}from"hono/streaming";import*as P from"fs/promises";import*as x from"path";async function ie(s){let{getHtml:e,basePath:t,port:r,reloadEmitter:n,getServerExports:o}=s,i=new Te;i.use("*",async(a,p)=>{await p(),a.res.headers.set("Cross-Origin-Opener-Policy","same-origin"),a.res.headers.set("Cross-Origin-Embedder-Policy","credentialless")}),i.get("/",a=>a.html(e())),i.post("/__fs/read",async a=>{try{let{path:p}=await a.req.json(),c=oe(p,t),f=await P.readFile(c,"utf-8");return a.json({content:f})}catch(p){let c=p instanceof Error?p.message:"Unknown error";return a.json({error:c},400)}}),i.post("/__fs/write",async a=>{try{let{path:p,content:c}=await a.req.json(),f=oe(p,t);return await P.mkdir(x.dirname(f),{recursive:!0}),await P.writeFile(f,c,"utf-8"),a.json({success:!0})}catch(p){let c=p instanceof Error?p.message:"Unknown error";return a.json({error:c},400)}});let l={log:console.log};i.post("/__api/:name",async a=>{try{let p=o?.(),c=a.req.param("name"),f=p?.[c]??l[c];if(!f||typeof f!="function")return a.json({error:`API function '${c}' not found`},404);let{args:g}=await a.req.json(),h=await f(...g||[]);return a.json({result:h})}catch(p){let c=p instanceof Error?p.message:"Unknown error";return a.json({error:c},500)}});let d=["esm.sh","unpkg.com","cdn.jsdelivr.net","cdnjs.cloudflare.com"];i.get("/proxy/*",async a=>{let p=a.req.path.slice(7),c=p.lastIndexOf("https://");if(c===-1)return a.text("Invalid proxy URL",400);let f=p.slice(c),g;try{g=new URL(f)}catch{return a.text("Invalid URL",400)}if(g.protocol!=="https:")return a.text("HTTPS only",400);if(!d.includes(g.hostname))return a.text("Host not allowed",403);try{let h=await fetch(f,{headers:{Accept:a.req.header("Accept")||"*/*"},redirect:"follow"});if(!h.ok)return a.text(`Upstream ${h.status}`,h.status);let y=new Headers,T=h.headers.get("Content-Type");T&&y.set("Content-Type",T);let O=h.headers.get("Cache-Control");return O&&y.set("Cache-Control",O),y.set("Access-Control-Allow-Origin","*"),y.set("Cross-Origin-Resource-Policy","cross-origin"),new Response(h.body,{status:h.status,headers:y})}catch(h){return a.text(`Proxy fetch failed: ${h instanceof Error?h.message:h}`,502)}}),n&&i.get("/__reload",a=>ke(a,async p=>{let c=()=>{p.writeSSE({event:"reload",data:""})};for(n.on("reload",c),p.onAbort(()=>{n.removeListener("reload",c)});;)await p.sleep(3e4)}));let u=Oe({fetch:i.fetch,port:r});return{port:r,close:()=>u.close()}}function oe(s,e){let t=x.resolve(e,s),r=x.normalize(e);if(!x.normalize(t).startsWith(r))throw new Error("Path traversal detected - access denied");return t}async function L(s){let e=await import("net");return new Promise(t=>{let r=e.createServer();r.listen(s,()=>{let n=r.address(),o=typeof n=="object"&&n?n.port:s;r.close(()=>t(o))}),r.on("error",()=>{t(L(s+1))})})}import De from"open";async function ae(s){await De(s)}import _e from"chokidar";function H(s){let{bulbPath:e,emitter:t}=s,r=_e.watch(e,{persistent:!0,ignoreInitial:!0,awaitWriteFinish:{stabilityThreshold:100,pollInterval:50}});return r.on("change",()=>{t.emit("reload")}),()=>r.close()}var Ue=Ve(Be),We="0.1.3";function Ne(s){let e={file:"",port:3e3,watch:!0,open:!0,server:!1,help:!1,version:!1};for(let t=0;t<s.length;t++){let r=s[t];if(r==="--help"||r==="-h")e.help=!0;else if(r==="--version"||r==="-V")e.version=!0;else if(r==="--no-watch")e.watch=!1;else if(r==="--no-open")e.open=!1;else if(r==="--server")e.server=!0;else if(r==="--port"||r==="-p"){let n=s[++t],o=parseInt(n,10);isNaN(o)&&(console.error(`Invalid port: ${n}`),process.exit(1)),e.port=o}else r.startsWith("-")||(e.file=r)}return e}function Le(){console.log(`
185
+ </html>`}function He(n){return n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}import{Hono as Je}from"hono";import{serve as qe}from"@hono/node-server";import{streamSSE as Ge}from"hono/streaming";import*as O from"fs/promises";import*as _ from"path";var S=class extends Error{constructor(e,t="unknown",r=!1){super(e),this.code=t,this.retryable=r}},x=class{getPath(e,t){return this.path}parseStreamChunk(e){return this.checkAndThrowError(e),this.parseProviderStreamChunk(e)}isReasoningEnabled(e){return e?.reasoning!==void 0&&e.reasoning>0}extractSystemMessages(e,t=`
186
+
187
+ `){let r=e.filter(s=>s.role==="system").map(s=>s.content);return{system:r.length?r.join(t):void 0,conversationMessages:e.filter(s=>s.role!=="system")}}parseJsonError(e,t,r=!1){if(!e)return{message:`HTTP ${t}`};try{let s=JSON.parse(e);if(s.error&&typeof s.error=="object"){let o={message:s.error.message||`HTTP ${t}`,type:s.error.type};return r&&(o.code=s.error.code),o}return s.message?{message:s.message}:{message:e}}catch{return{message:e}}}checkAndThrowError(e){if("type"in e&&e.type==="error"&&"message"in e){let t=e.message,r=e.code||"unknown",s=!!e.retryable;throw new S(t,r,s)}if("error"in e&&e.error){let t=this.extractErrorMessage(e.error);throw new S(t)}}extractErrorMessage(e){if(typeof e=="string")try{let t=JSON.parse(e);return t.error?.message||t.message||e}catch{return e}return typeof e=="object"&&e!==null?e.message||JSON.stringify(e):`${this.providerName} returned an error`}};var j=class extends x{constructor(){super(...arguments),this.providerName="Anthropic",this.defaultBaseUrl="https://api.anthropic.com",this.path="/v1/messages"}buildHeaders(e){return{"x-api-key":e,"anthropic-version":"2023-06-01","Content-Type":"application/json",Accept:"application/json"}}buildPayload(e,t,r,s){let{system:o,conversationMessages:i}=this.extractSystemMessages(e),l={model:t,max_tokens:this.getMaxTokens(t),messages:i,stream:s};if(r?.webSearch!==!1&&(l.tools=[{type:"web_search_20250305",name:"web_search"}]),o&&(l.system=o),this.isReasoningEnabled(r)){let d=r.reasoning;if(this.isAdaptiveModel(t)){let u={0:"low",1:"low",2:"medium",3:"high"};l.thinking={type:"adaptive"},l.output_config={effort:u[d]}}else{let u={0:0,1:2048,2:4096,3:8192};l.thinking={type:"enabled",budget_tokens:u[d]}}}return l}parseError(e,t){return this.parseJsonError(e,t)}parseNonStreamingResponse(e){if(!this.isAnthropicResponse(e))return{text:""};let t=e.content.filter(s=>s.type==="text").map(s=>s.text).join(""),r=e.content.filter(s=>s.type==="thinking").map(s=>s.thinking).join(`
188
+
189
+ `);return{text:t,reasoning:r||void 0}}parseProviderStreamChunk(e){if(!("type"in e))return null;switch(e.type){case"content_block_delta":return e.delta.type==="text_delta"?{text:e.delta.text||""}:e.delta.type==="thinking_delta"?{reasoning:e.delta.thinking||""}:null;case"message_start":case"content_block_start":case"content_block_stop":case"message_delta":case"message_stop":case"ping":return null;default:return null}}isAnthropicResponse(e){return"content"in e&&Array.isArray(e.content)&&"type"in e&&e.type==="message"}isAdaptiveModel(e){return e.startsWith("claude-opus-4-6")}getMaxTokens(e){return e.startsWith("claude-opus-4-6")?64e3:32e3}};var F=class extends x{constructor(){super(...arguments),this.providerName="OpenAI",this.defaultBaseUrl="https://api.openai.com",this.path="/v1/responses",this.effortMap={0:"minimal",1:"low",2:"medium",3:"high"}}buildHeaders(e){return{Authorization:`Bearer ${e}`,"Content-Type":"application/json",Accept:"application/json"}}buildPayload(e,t,r,s){let o=this.convertMessagesToInput(e),i={model:t,input:o,stream:s};return r?.webSearch!==!1&&(i.tools=[{type:"web_search"}]),this.isReasoningEnabled(r)&&(i.reasoning={effort:this.effortMap[r.reasoning],summary:"auto"}),i}parseError(e,t){return this.parseJsonError(e,t,!0)}parseNonStreamingResponse(e){if(!this.isResponsesApiResponse(e))return{text:""};let t=e.output_text||"",r;if(e.output&&Array.isArray(e.output))for(let s of e.output)s.type==="reasoning"&&s.summary&&(r=s.summary.map(o=>o.text).join(`
190
+ `)),!t&&s.type==="message"&&s.content&&(t=s.content.filter(o=>o.type==="output_text").map(o=>o.text).join(""));return{text:t,reasoning:r}}parseProviderStreamChunk(e){if(!this.isResponsesApiEvent(e))return null;switch(e.type){case"error":return null;case"response.failed":{let s=e.response?.error,o=s?.message||"Response failed",i=s?.code==="insufficient_quota"||s?.code==="rate_limit_exceeded";throw new S(o,i?"rate_limit":"unknown",i)}case"response.output_text.delta":return{text:e.delta};case"response.reasoning_summary_text.delta":return{reasoning:e.delta};case"response.created":case"response.in_progress":case"response.output_item.added":case"response.output_item.done":case"response.content_part.added":case"response.content_part.done":case"response.output_text.done":case"response.output_text.annotation.added":case"response.reasoning_summary_text.done":case"response.completed":case"response.web_search_call.in_progress":case"response.web_search_call.searching":case"response.web_search_call.completed":return null;default:return null}}convertMessagesToInput(e){return e.map(t=>t.role==="system"?`System: ${t.content}`:t.role==="user"?`User: ${t.content}`:t.role==="assistant"?`Assistant: ${t.content}`:t.content).join(`
191
+
192
+ `)}isResponsesApiEvent(e){return"type"in e&&typeof e.type=="string"}isResponsesApiResponse(e){return"object"in e&&e.object==="response"}};var U=class extends x{constructor(){super(...arguments),this.providerName="Gemini",this.defaultBaseUrl="https://generativelanguage.googleapis.com",this.path="/v1beta/models"}getPath(e,t){return`/v1beta/models/${e}:${t?"streamGenerateContent":"generateContent"}${t?"?alt=sse":""}`}buildHeaders(e){return{"Content-Type":"application/json","x-goog-api-key":e}}buildPayload(e,t,r,s){let{system:o,conversationMessages:i}=this.extractSystemMessages(e,`
193
+ `),d={contents:i.map(u=>({role:u.role==="assistant"?"model":"user",parts:[{text:u.content}]}))};return r?.webSearch!==!1&&(d.tools=[{google_search:{}}]),o&&(d.systemInstruction={role:"system",parts:[{text:o}]}),this.isReasoningEnabled(r)&&(d.generationConfig={temperature:.7+r.reasoning*.1}),d}parseError(e,t){if(!e)return{message:`HTTP ${t}`};try{let r=JSON.parse(e),s=Array.isArray(r)?r[0]?.error:r?.error;return s&&typeof s=="object"?{message:(s.message||`HTTP ${t}`).split(`
194
+ `)[0],type:s.status,code:s.code?.toString()}:r.message?{message:r.message}:{message:e}}catch{return{message:e}}}parseNonStreamingResponse(e){if(this.checkGeminiError(e),!this.isGeminiResponse(e))return{text:"",status:"failed",error:"Invalid response format"};let t=this.extractText(e)||"",r=e.candidates?.[0]?.finishReason,s="complete";return r==="MAX_TOKENS"?s="interrupted":(r==="SAFETY"||r==="RECITATION")&&(s="failed"),{text:t,status:s}}parseProviderStreamChunk(e){if(this.checkGeminiError(e),!this.isGeminiResponse(e))return null;let t=this.extractText(e);return t?{text:t}:null}isGeminiResponse(e){return typeof e=="object"&&e!==null&&"candidates"in e&&Array.isArray(e.candidates)}extractText(e){let t=e.candidates?.[0];if(t?.content?.parts)return t.content.parts.map(r=>r.text).filter(Boolean).join("")}checkGeminiError(e){if(typeof e=="object"&&e!==null&&"error"in e){let t=e.error,r;if(typeof t=="string")r=t;else if(typeof t=="object"&&t!==null){let s=t;r=s.message||s.status||"Gemini returned an error"}else r="Gemini returned an error";throw new S(r)}if(this.isGeminiResponse(e)&&e.promptFeedback?.blockReason){let t=e.promptFeedback.blockReason;throw new S(`Prompt blocked: ${t}`)}}};var L=class extends x{constructor(){super(...arguments),this.providerName="OpenRouter",this.defaultBaseUrl="https://openrouter.ai/api",this.path="/api/v1/chat/completions",this.effortMap={0:"low",1:"low",2:"medium",3:"high"}}buildHeaders(e,t){let r={Authorization:`Bearer ${e}`,"x-api-key":e,"Content-Type":"application/json",Accept:"application/json","X-Title":"Typebulb"};return t&&(r["HTTP-Referer"]=t,r.Referer=t,r.Origin=t),r}buildPayload(e,t,r,s){let o={model:t,messages:e,stream:s};return r?.webSearch===!0&&(o.plugins=[{id:"web"}]),this.isReasoningEnabled(r)&&(o.reasoning={effort:this.effortMap[r.reasoning]}),o}parseError(e,t){return this.parseJsonError(e,t,!0)}parseNonStreamingResponse(e){if(!this.hasChoices(e))return{text:""};let t=e.choices[0],r=t?.message?.content??t?.text??"",s=t?.message?.reasoning??e.reasoning;return{text:r,reasoning:s}}parseProviderStreamChunk(e){if(!this.hasChoices(e))return null;let t=e.choices[0];if(!t)return null;let r=t.delta||t.message;if(!r)return null;let s=r.content||void 0,o=r.reasoning||void 0;return!s&&!o?null:{text:s,reasoning:o}}hasChoices(e){return typeof e=="object"&&e!==null&&"choices"in e&&Array.isArray(e.choices)}};var We=new Map([["openai",new F],["openrouter",new L],["anthropic",new j],["gemini",new U]]);function k(n){let e=We.get(n);if(!e)throw new Error(`Unsupported protocol: ${n}`);return e}async function Q(n,e){let t=k(n.protocol),r=t.getPath(e.model,e.stream),s=new URL(r,n.baseUrl).toString(),o=t.buildHeaders(n.apiKey,e.origin),i=t.buildPayload(e.messages,e.model,{reasoning:e.reasoning,webSearch:e.webSearch},e.stream);return e.modifyPayload?.(i),fetch(s,{method:"POST",headers:o,body:JSON.stringify(i),signal:e.signal})}async function ee(n,e){let t=k(e),r=await n.text().catch(()=>""),{message:s}=t.parseError(r,n.status),o=n.status,i="unknown";return o===429?i="rate_limit":o===413&&(i="context_exceeded"),{code:i,message:s,retryable:o===429}}async function we(n){let{getHtml:e,basePath:t,port:r,reloadEmitter:s,getServerExports:o}=n,i=new Je;i.use("*",async(a,p)=>{await p(),a.res.headers.set("Cross-Origin-Opener-Policy","same-origin"),a.res.headers.set("Cross-Origin-Embedder-Policy","credentialless")}),i.get("/",a=>a.html(e())),i.post("/__fs/read",async a=>{try{let{path:p}=await a.req.json(),c=ve(p,t),f=await O.readFile(c,"utf-8");return a.json({content:f})}catch(p){let c=p instanceof Error?p.message:"Unknown error";return a.json({error:c},400)}}),i.post("/__fs/write",async a=>{try{let{path:p,content:c}=await a.req.json(),f=ve(p,t);return await O.mkdir(_.dirname(f),{recursive:!0}),await O.writeFile(f,c,"utf-8"),a.json({success:!0})}catch(p){let c=p instanceof Error?p.message:"Unknown error";return a.json({error:c},400)}});let l={log:console.log};i.post("/__api/:name",async a=>{try{let p=o?.(),c=a.req.param("name"),f=p?.[c]??l[c];if(!f||typeof f!="function")return a.json({error:`API function '${c}' not found`},404);let{args:h}=await a.req.json(),m=await f(...h||[]);return a.json({result:m})}catch(p){let c=p instanceof Error?p.message:"Unknown error";return a.json({error:c},500)}}),i.post("/__ai",async a=>{try{let{messages:p,system:c,reasoning:f,provider:h,model:m}=await a.req.json();if(!p||!Array.isArray(p)||p.length===0)return a.json({message:"messages array is required",code:"unknown",retryable:!1},400);let w=ze(h,m);if(typeof w=="string")return a.json({message:w,code:"unknown",retryable:!1},400);let R=[...c?[{role:"system",content:c}]:[],...p.map(E=>({role:E.role,content:E.content}))],P=await Q(w,{model:w.model,messages:R,stream:!1,reasoning:f??0,webSearch:!1});if(!P.ok){let E=await ee(P,w.protocol);return a.json(E,P.status)}let V=await P.json(),H=k(w.protocol).parseNonStreamingResponse(V);return H.text||console.warn("[tb.ai] Empty response from provider. Raw response:",JSON.stringify(V).slice(0,500)),a.json({text:H.text})}catch(p){let c=p instanceof Error?p.message:"Unknown error";return a.json({message:c,code:"unknown",retryable:!1},500)}}),i.get("/__models",async a=>{try{let p=await Xe();return a.json(p)}catch{return a.json([],200)}});let d=["esm.sh","unpkg.com","cdn.jsdelivr.net","cdnjs.cloudflare.com"];i.get("/proxy/*",async a=>{let p=a.req.path.slice(7),c=p.lastIndexOf("https://");if(c===-1)return a.text("Invalid proxy URL",400);let f=p.slice(c),h;try{h=new URL(f)}catch{return a.text("Invalid URL",400)}if(h.protocol!=="https:")return a.text("HTTPS only",400);if(!d.includes(h.hostname))return a.text("Host not allowed",403);try{let m=await fetch(f,{headers:{Accept:a.req.header("Accept")||"*/*"},redirect:"follow"});if(!m.ok)return a.text(`Upstream ${m.status}`,m.status);let w=new Headers,R=m.headers.get("Content-Type");R&&w.set("Content-Type",R);let P=m.headers.get("Cache-Control");return P&&w.set("Cache-Control",P),w.set("Access-Control-Allow-Origin","*"),w.set("Cross-Origin-Resource-Policy","cross-origin"),new Response(m.body,{status:m.status,headers:w})}catch(m){return a.text(`Proxy fetch failed: ${m instanceof Error?m.message:m}`,502)}}),s&&i.get("/__reload",a=>Ge(a,async p=>{let c=()=>{p.writeSSE({event:"reload",data:""})};for(s.on("reload",c),p.onAbort(()=>{s.removeListener("reload",c)});;)await p.sleep(3e4)}));let u=qe({fetch:i.fetch,port:r});return{port:r,close:()=>u.close()}}var be={anthropic:"ANTHROPIC_API_KEY",openai:"OPENAI_API_KEY",gemini:"GOOGLE_API_KEY",openrouter:"OPENROUTER_API_KEY"};function ze(n,e){let t=n??process.env.TB_AI_PROVIDER,r=e??process.env.TB_AI_MODEL;if(!t)return"No provider specified. Set TB_AI_PROVIDER in your .env file or pass provider in the tb.ai() call.";if(!r)return"No model specified. Set TB_AI_MODEL in your .env file or pass model in the tb.ai() call.";let s;try{s=k(t)}catch{return`Unknown provider '${t}'.`}let o=be[t],i=process.env[o];return i?{apiKey:i,baseUrl:s.defaultBaseUrl,protocol:t,model:r,isFreeModel:!1}:`No API key for '${t}'. Set ${o} in your .env file.`}var Ke="https://api.typebulb.com/api/models",Ye=1440*60*1e3,T=null;async function Xe(){if(!T||Date.now()-T.fetchedAt>Ye){let n=await fetch(Ke);if(!n.ok)return T?ye(T.models):[];T={models:await n.json(),fetchedAt:Date.now()}}return ye(T.models)}function ye(n){let e=new Set(Object.entries(be).filter(([,t])=>!!process.env[t]).map(([t])=>t));return n.filter(t=>e.has(t.provider))}function ve(n,e){let t=_.resolve(e,n),r=_.normalize(e);if(!_.normalize(t).startsWith(r))throw new Error("Path traversal detected - access denied");return t}async function te(n){let e=await import("net");return new Promise(t=>{let r=e.createServer();r.listen(n,()=>{let s=r.address(),o=typeof s=="object"&&s?s.port:n;r.close(()=>t(o))}),r.on("error",()=>{t(te(n+1))})})}import Ze from"open";async function xe(n){await Ze(n)}import Qe from"chokidar";function re(n){let{bulbPath:e,emitter:t}=n,r=Qe.watch(e,{persistent:!0,ignoreInitial:!0,awaitWriteFinish:{stabilityThreshold:100,pollInterval:50}});return r.on("change",()=>{t.emit("reload")}),()=>r.close()}var nt=st(rt),ot="0.1.3";function it(n){let e={file:"",port:3e3,watch:!0,open:!0,server:!1,help:!1,version:!1};for(let t=0;t<n.length;t++){let r=n[t];if(r==="--help"||r==="-h")e.help=!0;else if(r==="--version"||r==="-V")e.version=!0;else if(r==="--no-watch")e.watch=!1;else if(r==="--no-open")e.open=!1;else if(r==="--server")e.server=!0;else if(r==="--port"||r==="-p"){let s=n[++t],o=parseInt(s,10);isNaN(o)&&(console.error(`Invalid port: ${s}`),process.exit(1)),e.port=o}else r.startsWith("-")||(e.file=r)}return e}function at(){console.log(`
159
195
  typebulb - Local bulb runner for Typebulb
160
196
 
161
197
  Usage:
@@ -182,15 +218,31 @@ Server API:
182
218
  // in **code.tsx**: const rows = await tb.server.query(sql)
183
219
  .env and .env.local are auto-loaded from the working directory.
184
220
 
221
+ Built-in server functions (available without a **server.ts** section):
222
+ tb.server.log(...) Print to the CLI's stdout
223
+
224
+ AI API:
225
+ Bulbs can call AI providers via tb.ai(). Set API keys in .env:
226
+ ANTHROPIC_API_KEY=sk-ant-...
227
+ OPENAI_API_KEY=sk-...
228
+ GOOGLE_API_KEY=AIza...
229
+ OPENROUTER_API_KEY=sk-or-...
230
+ Set provider and model (required):
231
+ TB_AI_PROVIDER=anthropic
232
+ TB_AI_MODEL=claude-haiku-4-5-20251001
233
+ Both can be overridden per-call: tb.ai({ provider: "openai", model: "gpt-4o", ... })
234
+ Optional reasoning depth (0=min, 1=low, 2=med, 3=max):
235
+ tb.ai({ ..., reasoning: 2 })
236
+
185
237
  Examples:
186
238
  typebulb my-editor.bulb.md
187
239
  typebulb --no-watch --port 8080 my-editor.bulb.md
188
240
  typebulb .
189
- `)}async function He(s){let t=(await v.readdir(s)).find(r=>r.endsWith(".bulb.md"));return t?w.join(s,t):null}function pe(){q([".env",".env.local"],process.cwd(),!0)}function q(s,e,t=!1){for(let r of s){let n=w.resolve(e,r);try{let o=Fe(n,"utf-8");for(let i of o.split(`
190
- `)){let l=i.trim();if(!l||l.startsWith("#"))continue;let d=l.indexOf("=");if(d===-1)continue;let u=l.slice(0,d).trim(),a=l.slice(d+1).trim();(a.startsWith('"')&&a.endsWith('"')||a.startsWith("'")&&a.endsWith("'"))&&(a=a.slice(1,-1)),process.env[u]??=a}}catch{t||console.warn(` Warning: env file not found: ${r}`)}}}function Je(s){let e=new Set,t=/\bimport\s+(?:[\s\S]*?\s+from\s+)?['"]([^./][^'"]*)['"]/g,r;for(;r=t.exec(s);){let n=r[1];if(n.startsWith("node:"))continue;let o=n.startsWith("@")?n.split("/").slice(0,2).join("/"):n.split("/")[0];e.add(o)}return[...e]}async function qe(s,e){let t=s.filter(n=>!ce(w.join(e,"node_modules",n)));if(t.length===0)return;let r=w.join(e,"package.json");ce(r)||await v.writeFile(r,JSON.stringify({name:"typebulb-server",private:!0})),console.log(` Installing: ${t.join(", ")}`),await Ue("npm",["install","--no-audit","--no-fund",...t],{cwd:e,shell:!0})}async function de(s,e){let t=Y(s);if(t.error)throw new Error(`Server compilation error: ${t.error}`);let r=w.join(e,".typebulb");await v.mkdir(r,{recursive:!0});let n=Je(t.code);n.length>0&&await qe(n,r);let o=w.join(r,"server.mjs");return await v.writeFile(o,t.code,"utf-8"),await import(`${Ae(o).href}?t=${Date.now()}`)}async function le(s,e){let t=await v.readFile(s,"utf-8"),r=k(t);if(!r)throw new Error("Invalid .bulb.md file format");let n=D(r),o=A(n.config),i=z(n.data),l=K(n.code,{jsxImportSource:o.jsxImportSource});l.error&&console.error("Compilation error:",l.error);let{importMap:d}=await re.buildImportMap(l.code,o.dependencies??{}),u=ne({name:n.name,code:l.code,css:n.css,html:n.html,data:i,insight:n.insight,importMap:d,watch:e}),a=w.dirname(s);o.env?.length&&q(o.env,a);let p=null;return n.server&&(p=await de(n.server,a)),{html:u,bulb:n,serverExports:p}}async function ze(s,e){pe();let t=async()=>{let r=await v.readFile(s,"utf-8"),n=k(r);if(!n)throw new Error("Invalid .bulb.md file format");let o=D(n),i=A(o.config),l=w.dirname(s);i.env?.length&&q(i.env,l),await de(o.server,l)};if(console.log(`Running ${w.basename(s)}...`),await t(),e){console.log(`Watching for changes...
191
- `);let r=new J;r.on("reload",async()=>{try{console.log("Re-running..."),await t()}catch(n){console.error("Error:",n)}}),H({bulbPath:s,emitter:r})}}async function Ge(){let s=Ne(process.argv.slice(2));s.version&&(console.log(`typebulb ${We}`),process.exit(0)),s.help&&(Le(),process.exit(0));let e;if(!s.file||s.file==="."){let g=await He(process.cwd());g||(console.error("No .bulb.md file found in current directory"),process.exit(1)),e=g}else e=w.resolve(s.file);try{await v.access(e)}catch{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 t=await v.readFile(e,"utf-8"),r=k(t);if(r){let g=D(r);if(g.server&&(!g.code||s.server)){await ze(e,s.watch);return}}let n=process.cwd(),o=s.watch?new J:void 0;pe(),console.log(`Loading ${w.basename(e)}...`);let{html:i,bulb:l,serverExports:d}=await le(e,s.watch),u=await L(s.port),a=await ie({getHtml:()=>i,basePath:n,port:u,reloadEmitter:o,getServerExports:()=>d}),p=`http://localhost:${u}`;console.log(`
241
+ `)}async function ct(n){let t=(await v.readdir(n)).find(r=>r.endsWith(".bulb.md"));return t?y.join(n,t):null}function Ee(){ne([".env",".env.local"],process.cwd(),!0)}function ne(n,e,t=!1){for(let r of n){let s=y.resolve(e,r);try{let o=et(s,"utf-8");for(let i of o.split(`
242
+ `)){let l=i.trim();if(!l||l.startsWith("#"))continue;let d=l.indexOf("=");if(d===-1)continue;let u=l.slice(0,d).trim(),a=l.slice(d+1).trim();(a.startsWith('"')&&a.endsWith('"')||a.startsWith("'")&&a.endsWith("'"))&&(a=a.slice(1,-1)),process.env[u]??=a}}catch{t||console.warn(` Warning: env file not found: ${r}`)}}}function lt(n){let e=new Set,t=/\bimport\s+(?:[\s\S]*?\s+from\s+)?['"]([^./][^'"]*)['"]/g,r;for(;r=t.exec(n);){let s=r[1];if(s.startsWith("node:"))continue;let o=s.startsWith("@")?s.split("/").slice(0,2).join("/"):s.split("/")[0];e.add(o)}return[...e]}async function pt(n,e){let t=n.filter(s=>!Se(y.join(e,"node_modules",s)));if(t.length===0)return;let r=y.join(e,"package.json");Se(r)||await v.writeFile(r,JSON.stringify({name:"typebulb-server",private:!0})),console.log(` Installing: ${t.join(", ")}`),await nt("npm",["install","--no-audit","--no-fund",...t],{cwd:e,shell:!0})}async function _e(n,e){let t=ce(n);if(t.error)throw new Error(`Server compilation error: ${t.error}`);let r=y.join(e,".typebulb");await v.mkdir(r,{recursive:!0});let s=lt(t.code);s.length>0&&await pt(s,r);let o=y.join(r,"server.mjs");return await v.writeFile(o,t.code,"utf-8"),await import(`${tt(o).href}?t=${Date.now()}`)}async function Pe(n,e){let t=await v.readFile(n,"utf-8"),r=W(t);if(!r)throw new Error("Invalid .bulb.md file format");let s=J(r),o=G(s.config),i=oe(s.data),l=ae(s.code,{jsxImportSource:o.jsxImportSource});l.error&&console.error("Compilation error:",l.error);let{importMap:d}=await me.buildImportMap(l.code,o.dependencies??{}),u=ge({name:s.name,code:l.code,css:s.css,html:s.html,data:i,insight:s.insight,importMap:d,watch:e}),a=y.dirname(n);o.env?.length&&ne(o.env,a);let p=null;return s.server&&(p=await _e(s.server,a)),{html:u,bulb:s,serverExports:p}}async function dt(n,e){Ee();let t=async()=>{let r=await v.readFile(n,"utf-8"),s=W(r);if(!s)throw new Error("Invalid .bulb.md file format");let o=J(s),i=G(o.config),l=y.dirname(n);i.env?.length&&ne(i.env,l),await _e(o.server,l)};if(console.log(`Running ${y.basename(n)}...`),await t(),e){console.log(`Watching for changes...
243
+ `);let r=new se;r.on("reload",async()=>{try{console.log("Re-running..."),await t()}catch(s){console.error("Error:",s)}}),re({bulbPath:n,emitter:r})}}async function ut(){let n=it(process.argv.slice(2));n.version&&(console.log(`typebulb ${ot}`),process.exit(0)),n.help&&(at(),process.exit(0));let e;if(!n.file||n.file==="."){let h=await ct(process.cwd());h||(console.error("No .bulb.md file found in current directory"),process.exit(1)),e=h}else e=y.resolve(n.file);try{await v.access(e)}catch{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 t=await v.readFile(e,"utf-8"),r=W(t);if(r){let h=J(r);if(h.server&&(!h.code||n.server)){await dt(e,n.watch);return}}let s=process.cwd(),o=n.watch?new se:void 0;Ee(),console.log(`Loading ${y.basename(e)}...`);let{html:i,bulb:l,serverExports:d}=await Pe(e,n.watch),u=await te(n.port),a=await we({getHtml:()=>i,basePath:s,port:u,reloadEmitter:o,getServerExports:()=>d}),p=`http://localhost:${u}`;console.log(`
192
244
  ${l.name}`),console.log(` ${p}
193
- `),s.watch&&console.log(` Watching for changes...
194
- `);let c;if(s.watch&&o){let g=new J;g.on("reload",async()=>{try{console.log("Recompiling...");let h=await le(e,!0);i=h.html,d=h.serverExports,o.emit("reload"),console.log(`Done. Browser reloading...
195
- `)}catch(h){console.error("Compile error:",h)}}),c=H({bulbPath:e,emitter:g})}s.open&&await ae(p);let f=async()=>{console.log(`
196
- Shutting down...`),a.close(),c?.();let g=w.join(w.dirname(e),".typebulb","server.mjs");await v.rm(g,{force:!0}).catch(()=>{}),process.exit(0)};process.on("SIGINT",f),process.on("SIGTERM",f)}Ge().catch(s=>{console.error("Error:",s.message),process.exit(1)});
245
+ `),n.watch&&console.log(` Watching for changes...
246
+ `);let c;if(n.watch&&o){let h=new se;h.on("reload",async()=>{try{console.log("Recompiling...");let m=await Pe(e,!0);i=m.html,d=m.serverExports,o.emit("reload"),console.log(`Done. Browser reloading...
247
+ `)}catch(m){console.error("Compile error:",m)}}),c=re({bulbPath:e,emitter:h})}n.open&&await xe(p);let f=async()=>{console.log(`
248
+ Shutting down...`),a.close(),c?.();let h=y.join(y.dirname(e),".typebulb","server.mjs");await v.rm(h,{force:!0}).catch(()=>{}),process.exit(0)};process.on("SIGINT",f),process.on("SIGTERM",f)}ut().catch(n=>{console.error("Error:",n.message),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typebulb",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Local bulb runner CLI for Typebulb",
5
5
  "license": "MIT",
6
6
  "engines": { "node": ">=18" },
@@ -19,6 +19,8 @@
19
19
  "prepublishOnly": "rimraf dist && node esbuild.config.mjs"
20
20
  },
21
21
  "dependencies": {
22
+ "@typebulb/shared-providers": "workspace:*",
23
+ "@typebulb/shared-types": "workspace:*",
22
24
  "@hono/node-server": "^1.14.1",
23
25
  "chokidar": "^4.0.3",
24
26
  "es-module-lexer": "^1.7.0",