bostrat-node 1.13.2 → 1.13.3

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/bin/bostrat-ship CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- var i=process.env.BOSTRAT_AGENT_URL||`http://127.0.0.1:${process.env.BOSTRAT_AGENT_PORT||3362}`,n="[bostrat-ship]";function a(e){let t=/fetch failed|ECONNREFUSED/.test(String(e))?`the bostrat agent isn't reachable at ${i} \u2014 run this from inside a Track box`:e;console.error(`${n} ${t}`),process.exit(1)}var c=process.argv.slice(2),h=c.shift();h!=="offer"&&a("usage: bostrat-ship offer [plan.json | -] (see the header for the plan shape)");var r=c.find(e=>!e.startsWith("--")),p=async()=>{let e=[];for await(let t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf8")},s;try{let e=!r||r==="-"?await p():await(await import("node:fs/promises")).readFile(r,"utf8");s=e.trim()?JSON.parse(e):{}}catch{a("plan must be JSON (a file path, or piped on stdin) \u2014 see the `bostrat-ship` header for the shape")}s.ship||(s={label:s.label,why:s.why,ship:{actions:s.actions,toPr:s.toPr,rebase:s.rebase,base:s.base,repos:s.repos}});s.label=s.label||"Ship";s.trigger="now";Array.isArray(s.ship.actions)||(s.ship.actions=["commit","push","pr"]);try{let e=await fetch(`${i}/api/followups/offer`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({cwd:process.cwd(),plan:s})}),t=await e.json().catch(()=>({}));e.ok||a(t.error||`HTTP ${e.status}`);let o=t.result||{};o.skipped?console.log(`${n} ${o.reason} \u2014 no card rendered.`):(console.log(`${n} ship offer posted: ${o.offered.join(" + ")} on ${o.branch}${o.prState==="open"?" (PR already open)":""} \u2014 one-tap controls are in the Track chat.`),o.replaced&&console.log(" This REPLACED the ship offer you already posted this turn (its card is withdrawn). Offer once, as the last thing you do."),console.log(" HOLD HERE: end your turn now. The go-ahead comes back as a chat message naming the steps to run."))}catch(e){a(e.message)}
2
+ var d=process.env.BOSTRAT_AGENT_URL||`http://127.0.0.1:${process.env.BOSTRAT_AGENT_PORT||3362}`,i="[bostrat-ship]";function o(e){let a=/fetch failed|ECONNREFUSED/.test(String(e))?`the bostrat agent isn't reachable at ${d} \u2014 run this from inside a Track box`:e;console.error(`${i} ${a}`),process.exit(1)}var n=process.argv.slice(2),h=n.shift();if(h==="updated"){let e=t=>{let r=n.indexOf(t);return r>=0?n[r+1]:void 0},a=e("--pr");a!=null&&!/^\d+$/.test(String(a).replace(/^#/,""))&&o("--pr takes a PR number");try{let t=await fetch(`${d}/api/ship/updated`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({cwd:process.cwd(),number:a!=null?Number(String(a).replace(/^#/,"")):null,rebased:n.includes("--rebased"),base:e("--base")??null})}),r=await t.json().catch(()=>({}));t.ok||o(r.error||`HTTP ${t.status}`);let c=r.result||{};c.skipped?console.log(`${i} ${c.reason} \u2014 no new card.`):console.log(`${i} update to #${c.number} reported (${String(c.sha).slice(0,7)}) \u2014 the "Updated PR" card with merge + archive posts below your closing message when this turn ends.`)}catch(t){o(t.message)}process.exit(0)}h!=="offer"&&o("usage: bostrat-ship offer [plan.json | -] | bostrat-ship updated [--pr N] [--rebased] [--base main]");var p=n.find(e=>!e.startsWith("--")),l=async()=>{let e=[];for await(let a of process.stdin)e.push(a);return Buffer.concat(e).toString("utf8")},s;try{let e=!p||p==="-"?await l():await(await import("node:fs/promises")).readFile(p,"utf8");s=e.trim()?JSON.parse(e):{}}catch{o("plan must be JSON (a file path, or piped on stdin) \u2014 see the `bostrat-ship` header for the shape")}s.ship||(s={label:s.label,why:s.why,ship:{actions:s.actions,toPr:s.toPr,rebase:s.rebase,base:s.base,repos:s.repos}});s.label=s.label||"Ship";s.trigger="now";Array.isArray(s.ship.actions)||(s.ship.actions=["commit","push","pr"]);try{let e=await fetch(`${d}/api/followups/offer`,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({cwd:process.cwd(),plan:s})}),a=await e.json().catch(()=>({}));e.ok||o(a.error||`HTTP ${e.status}`);let t=a.result||{};t.skipped?console.log(`${i} ${t.reason} \u2014 no card rendered.`):(console.log(`${i} ship offer posted: ${t.offered.join(" + ")} on ${t.branch}${t.prState==="open"?" (PR already open)":""} \u2014 one-tap controls are in the Track chat.`),t.replaced&&console.log(" This REPLACED the ship offer you already posted this turn (its card is withdrawn). Offer once, as the last thing you do."),console.log(" HOLD HERE: end your turn now. The go-ahead comes back as a chat message naming the steps to run."))}catch(e){o(e.message)}
package/dist/cli.js CHANGED
@@ -112,7 +112,7 @@ Run it? (Y/n)`,"Y")).toLowerCase().startsWith("n"))return n.write(`aborted \u201
112
112
  redeploy: fly deploy ${E} -c ${Se.join(E,"fly.toml")} -a ${f} --ha=false
113
113
  Vendor logins (Claude/Codex) are NOT set up here \u2014 you type those later, inside
114
114
  boxes, and they land on this machine's volume, never on bostrat (box-auth-policy).
115
- `),h(0)}var Tr,vs,tn,wt,kr=k(()=>{Tr="wss://plane.bostrat.ai/v1/node",vs=_s(import.meta.url)("../package.json").version,tn=`ghcr.io/globocodes/bostrat-node:${vs}`;wt=(e,{stdin:t,exec:n}={})=>new Promise((o,i)=>{if(n)return n(e,{stdin:t}).then(o,i);let c=Ts("fly",e,{timeout:6e5,maxBuffer:16*1024*1024},(d,m,h)=>d?i(Object.assign(d,{stdout:m,stderr:h})):o({stdout:m,stderr:h}));t!=null&&c.stdin?.end(t)})});function $r(e,t,n){if(e==null)return t==="from"?-1/0:1/0;if(!$s.test(String(e)))throw new Error(`pricing.js: ${n} window ${t} must be YYYY-MM-DD, got ${JSON.stringify(e)}`);let o=Date.parse(`${e}T${t==="from"?"00:00:00.000":"23:59:59.999"}Z`);if(!Number.isFinite(o))throw new Error(`pricing.js: ${n} window ${t} is not a real date: ${e}`);return o}function xs(e){let t={};for(let[n,o]of Object.entries(e)){if(!Array.isArray(o)||o.length===0)throw new Error(`pricing.js: ${n} must be a non-empty array of rate windows`);t[n]=o.map(i=>({...i,fromMs:$r(i.from,"from",n),toMs:$r(i.to,"to",n)}))}return t}var ks,$s,_l,Sl,xr=k(()=>{ks={"claude-opus-5":[{from:null,to:null,input:5,output:25,cacheWrite:10,cacheRead:.5,fast:{input:10,output:50,cacheWrite:20,cacheRead:1},verifiedAt:"2026-08-20",note:"GA rates; verified 2026-08-06 (claude-api skill), re-verified 2026-08-17 (live pricing page); fast-mode variant added 2026-08-20."}],"claude-opus-4-8":[{from:null,to:null,input:5,output:25,cacheWrite:10,cacheRead:.5,fast:{input:10,output:50,cacheWrite:20,cacheRead:1},verifiedAt:"2026-08-20",note:"verified 2026-08-06 (claude-api skill), re-verified 2026-08-17 (live pricing page); fast-mode variant added 2026-08-20."}],"claude-sonnet-5":[{from:null,to:null,input:2,output:10,cacheWrite:4,cacheRead:.2,verifiedAt:"2026-08-17",note:"$2/$10 is the standard price (intro pricing made permanent; the 2026-09-01 $3/$15 increase was cancelled) \u2014 verified 2026-08-17 against platform.claude.com/docs/en/about-claude/pricing."}],"claude-sonnet-4-6":[{from:null,to:null,input:3,output:15,cacheWrite:6,cacheRead:.3,verifiedAt:"2026-08-17",note:"verified 2026-08-06 (claude-api skill), re-verified 2026-08-17 (live pricing page)."}],"claude-haiku-4-5":[{from:null,to:null,input:1,output:5,cacheWrite:2,cacheRead:.1,verifiedAt:"2026-08-17",note:"verified 2026-08-06 (claude-api skill), re-verified 2026-08-17 (live pricing page)."}],"claude-fable-5-1":[{from:null,to:null,input:10,output:50,cacheWrite:20,cacheRead:.25,cacheReadX:.025,verifiedAt:"2026-09-07",note:"verified 2026-09-07 against platform.claude.com/docs/en/about-claude/pricing: $10/$50, 1h cache write $20 (2\xD7), cache hits $0.25 (0.025\xD7 \u2014 the 5.1-only multiplier; footnote 1 on the page)."}],"claude-fable-5":[{from:null,to:null,input:10,output:50,cacheWrite:20,cacheRead:1,verifiedAt:"2026-08-17",note:"verified 2026-08-06 (claude-api skill), re-verified 2026-08-17 (live pricing page); 83% of fleet spend."}],"gpt-5.5":[{from:null,to:null,input:5,output:30,cacheWrite:5,cacheRead:.5,verifiedAt:"2026-08-17",note:"verified 2026-07-03, re-verified 2026-08-17 (developers.openai.com pricing); no cache-write premium on 5.5-and-earlier."}],"gpt-5.6-sol":[{from:null,to:"2026-08-23",input:5,output:30,cacheWrite:6.25,cacheRead:.5,verifiedAt:"2026-08-17",note:"verified 2026-07-18, re-verified 2026-08-17 (developers.openai.com pricing); cacheWrite = 1.25\xD7 input (30-min TTL). Superseded 2026-08-24 by the vendor's promotional cut (window below)."},{from:"2026-08-24",to:null,input:4,output:20,cacheWrite:5,cacheRead:.4,verifiedAt:"2026-08-24",note:"promotional pricing verified 2026-08-24 (developers.openai.com pricing), 'at least through November 21, 2026'; cacheWrite = 1.25\xD7 input (30-min TTL)."}],"gpt-5.6-terra":[{from:null,to:"2026-08-16",input:2.5,output:15,cacheWrite:3.125,cacheRead:.25,verifiedAt:"2026-07-18",note:"as verified 2026-07-18; superseded by the 2026-08-17 sweep (0 corpus rows in this window)."},{from:"2026-08-17",to:null,input:2,output:12,cacheWrite:2.5,cacheRead:.2,verifiedAt:"2026-08-17",note:"verified 2026-08-17 (developers.openai.com pricing); cacheWrite = 1.25\xD7 input (30-min TTL)."}],"gpt-5.6-luna":[{from:null,to:"2026-08-16",input:1,output:6,cacheWrite:1.25,cacheRead:.1,verifiedAt:"2026-07-18",note:"as verified 2026-07-18; superseded by the 2026-08-17 sweep (0 corpus rows in this window)."},{from:"2026-08-17",to:null,input:.2,output:1.2,cacheWrite:.25,cacheRead:.02,verifiedAt:"2026-08-17",note:"verified 2026-08-17 (developers.openai.com pricing); cacheWrite = 1.25\xD7 input (30-min TTL)."}],"gpt-6-astra":[{from:null,to:null,input:10,output:50,cacheWrite:12.5,cacheRead:1,fast:{input:20,output:100,cacheWrite:25,cacheRead:2},verifiedAt:"2026-09-07",note:"verified 2026-09-07 (developers.openai.com pricing); cacheWrite = 1.25\xD7 input; fast tier $20/$100 recorded as the fast variant (codex reports no speed)."}]},$s=/^\d{4}-\d{2}-\d{2}$/;_l=xs(ks),Sl=Object.freeze({staleDays:90,leadDays:14,confirmDays:30})});var Er=k(()=>{xr()});import Or from"node:fs/promises";import Ir from"node:path";import Es from"node:os";function Mr(e){let t=new Date(e);return t.setHours(0,0,0,0),t.setDate(t.getDate()-(t.getDay()+6)%7),t.getTime()}function Dr(e){let t=new Date(e);return t.setHours(0,0,0,0),t.setDate(1),t.getTime()}function Cr(e){let t=new Date(Mr(e));return t.setDate(t.getDate()+7),t.getTime()}function Lr(e){let t=new Date(Dr(e));return t.setMonth(t.getMonth()+1),t.getTime()}function Ps(e=Date.now()){for(let[t,n]of Object.entries(Ns)){let o=n.startOf(e);if(o!==yt[t]){yt[t]=o;for(let i of nn.values())i[t]=0;for(let i of Rr)i.includes(`:${t}:`)&&!i.endsWith(`:${o}`)&&Rr.delete(i)}}}function Ms(e){let t={};for(let[n,o]of Object.entries(e||{})){if(typeof n!="string"||!/^[a-z][a-z0-9-]{0,31}$/.test(n))throw new Error(`bad provider key "${n}"`);let i={};for(let c of["weeklyUsd","monthlyUsd"]){let d=o?.[c];if(d==null||d==="")continue;let m=Number(d);if(!Number.isFinite(m)||m<=0)throw new Error(`${n}.${c} must be a positive number or null`);i[c]=m}Object.keys(i).length&&(t[n]=i)}return t}async function Br(e,{source:t="local",updatedAt:n=Date.now()}={}){Ee={caps:Ms(e),updatedAt:n,source:t};try{await Or.mkdir(Pr,{recursive:!0}),await Or.writeFile(Os(),JSON.stringify(Ee,null,2))}catch{}return Ds()}function Ds(e=Date.now()){Ps(e);let t=new Set([...nn.keys(),...Object.keys(Ee?.caps||{})]),n={};for(let o of t){let i=nn.get(o);n[o]={weeklyUsd:Nr(i?.weekly||0),monthlyUsd:Nr(i?.monthly||0)}}return{caps:Ee?.caps||{},configured:!!Ee,seeded:Is,updatedAt:Ee?.updatedAt??null,spent:n,windows:{weekly:{start:yt.weekly,resetsAt:Cr(e)},monthly:{start:yt.monthly,resetsAt:Lr(e)}},warnPct:Rs}}var Pr,Os,Rs,Ns,Ee,Is,yt,nn,Rr,Nr,jr=k(()=>{pt();Er();Pr=process.env.BOSTRAT_STATE_DIR||Ir.join(process.env.HOME||Es.tmpdir(),".bostrat"),Os=()=>Ir.join(Pr,"usage-caps.json"),Rs=.8;Ns={weekly:{startOf:Mr,nextStart:Cr,capKey:"weeklyUsd"},monthly:{startOf:Dr,nextStart:Lr,capKey:"monthlyUsd"}},Ee=null,Is=!1,yt={weekly:0,monthly:0},nn=new Map,Rr=new Set;Nr=e=>Math.round(e*1e4)/1e4});var Yr={};Ft(Yr,{REMOVED_MARKER:()=>ln,detectInstallKind:()=>Ne,detectProviderAuth:()=>Gr,maskToken:()=>cn,readRemovedMarker:()=>zr,startPlaneLink:()=>Zs,updateCommandFor:()=>qe,versionLt:()=>Re});import Oe from"node:fs";import Ye from"node:fs/promises";import Cs from"node:http";import U from"node:path";import gt from"node:os";import{createRequire as Ls}from"node:module";import rn from"ws";function cn(e){return typeof e!="string"||e.length<8?"\u2022\u2022\u2022\u2022":`${e.slice(0,6)}\u2026${e.slice(-4)}`}function zr(e=ln){try{let t=JSON.parse(Oe.readFileSync(e,"utf8"));return t&&typeof t=="object"?t:null}catch{return null}}function Ne({env:e=process.env,pkgDir:t=U.resolve(U.dirname(new URL(import.meta.url).pathname),".."),exists:n=Oe.existsSync}={}){let o=String(e.BOSTRAT_INSTALL_KIND||"").trim();return["container","npm-global","git-checkout","dev"].includes(o)?o:/\/lib\/node_modules\/bostrat-node(?:\/|$)/.test(t.replace(/\\/g,"/"))?"npm-global":n(U.join(t,"..",".git"))?"git-checkout":"dev"}function Re(e,t){let n=String(e).split(".").map(Number),o=String(t).split(".").map(Number);if(n.some(Number.isNaN)||o.some(Number.isNaN))return!1;for(let i=0;i<Math.max(n.length,o.length);i++){let c=(n[i]||0)-(o[i]||0);if(c)return c<0}return!1}function qe(e){switch(e){case"npm-global":return"npm install -g bostrat-node && restart the bostrat-node service";case"git-checkout":return"scripts/deploy-pi.sh (or The Bakery \u2192 Deploy to Pi \u2192 bostrat)";case"container":return"Links \u2192 Fly \u2192 Update (or the plane's auto-update while the node sleeps)";default:return"update this node's bostrat-node checkout/install"}}function Gr({home:e=process.env.HOME||gt.homedir(),env:t=process.env}={}){let n=o=>{try{return Oe.statSync(o),!0}catch{return!1}};return{claude:n(U.join(e,".claude",".credentials.json")),codex:n(U.join(t.CODEX_HOME||U.join(e,".codex"),"auth.json")),cursor:n(U.join(t.XDG_CONFIG_HOME||U.join(e,".config"),"cursor","auth.json"))}}function Zs({url:e,token:t,nodeId:n=gt.hostname(),name:o=gt.hostname(),listTracks:i,detectAgents:c=null,onTracksTransferred:d=null,onTracksMoves:m=null,sampleMetrics:h=null,deliverySample:x=null,onHashKey:f=null,onViewBase:T=null,metricsMs:$=Js,onUpdateAvailable:S=null,onUpdatePolicy:v=null,onHelloRefused:P=null,updatePolicy:O=null,installKind:E=Ne(),selfUrl:b=`http://127.0.0.1:${process.env.PORT||3362}`,apiToken:_=null,removedMarkerFile:A=ln,onRemoved:M=null,onDegraded:R=null,onRecovered:K=null,onRefused:kt=null,onConnected:co=null,ingestMode:$t=["ws","http","auto"].includes(process.env.BOSTRAT_PLANE_INGEST)?process.env.BOSTRAT_PLANE_INGEST:"auto",deliveryTimeoutMs:_n=zs,unackedRetryMs:Je=Ws,pingMs:Sn=Hs,deadLinkMs:xt=Math.round(Sn*Ks),maxInflightEvents:lo=qs,httpTimeoutMs:Ze=Xs,stallBackoffMinMs:vn=Ys,fetchImpl:bn=globalThis.fetch,log:w=console}){if(!e||!t)throw new Error("plane-link: url and token are required");if(typeof i!="function")throw new Error("plane-link: listTracks is required");e=Ue(e);let uo=b.replace(/^http/,"ws"),An=new Set,kn=s=>{let l=typeof s.latestNodeVersion=="string"?s.latestNodeVersion:null;if(!(!(l&&Z&&Re(Z,l))||An.has(l))){An.add(l),w.log(`[plane-link] bostrat-node ${l} is available (this node runs ${Z}) \u2014 ${qe(E)}`);try{S?.({current:Z,latest:l,min:s.minNodeVersion??null,installKind:E,required:!!(s.minNodeVersion&&Re(Z,s.minNodeVersion))})}catch(a){w.error(`[plane-link] update notice failed: ${a?.message||a}`)}}},fe=new Map,pe=new Map,Et=U.join(an,"plane-link","cursor.json"),Ot=U.join(an,"plane-link","dead-letter.ndjson"),$n=e.replace(/^ws(s?):\/\//,"http$1://").replace(/\/v1\/node\/?$/,""),ne={},ee={},N=new Map,Qe=null,ie=null,re=0,Me=0,xn=0,et={},En=new Set,On=Promise.resolve(),C="ws",be=0,J=!1,he=0,Rt=!1,V=le,Ae=new Set,ae=[],z=null,Y=null,q=!1,Nt=!1,De=Fr,Ce=null,ke=null,$e=null,oe=null,me=0,Rn=0,se=null,It=!1,tt=0,Le=!1,ce=zr(A);if(ce)if(ce.tokenPreview===cn(t))Le=!0,Yt({reason:ce.reason??"removed",at:ce.at??null,nodeId:ce.nodeId??n}),w.error(`[plane-link] this node was REMOVED from the plane (${ce.reason??"removed"}${ce.at?` at ${new Date(ce.at).toISOString()}`:""}) \u2014 not dialing. Re-provision it with a new node token, or delete ${A} to force a dial.`);else try{Oe.unlinkSync(A),w.log("[plane-link] stale removed-marker discarded \u2014 the node token changed")}catch{}function Pt({reason:s="removed",at:l=Date.now(),via:a="ctl"}={}){if(!Le){Le=!0,Yt({reason:s,at:l,nodeId:n,via:a});try{Oe.mkdirSync(U.dirname(A),{recursive:!0}),Oe.writeFileSync(A,JSON.stringify({at:l,reason:s,via:a,nodeId:n,tokenPreview:cn(t)},null,2))}catch(r){w.error(`[plane-link] could not write ${A}: ${r?.message||r}`)}w.error(`[plane-link] the plane REMOVED this node (${s}, via ${a}) \u2014 going dormant: no more dials, ingest or broker calls. Re-provision with a new node token to reconnect; the box and its data are untouched.`);try{M?.({reason:s,at:l,via:a})}catch{}Un({bye:!1})}}async function fo(){try{ne=JSON.parse(await Ye.readFile(Et,"utf8"))}catch{ne={}}}function Nn(){It=!0,!$e&&($e=setTimeout(async()=>{if($e=null,!!It){It=!1;try{await Ye.mkdir(U.dirname(Et),{recursive:!0}),await Ye.writeFile(Et,JSON.stringify(ne))}catch(s){w.error("[plane-link] cursor flush failed:",s?.message||s)}}},Us),$e.unref?.())}function L(s){return!Y||Y.readyState!==rn.OPEN?!1:(Y.send(JSON.stringify(s)),!0)}function Be(s){let l=Date.now();for(let{trackId:r,event:u}of s){let p=N.get(u.id);N.set(u.id,{trackId:r,seq:u.seq,event:u,sentAt:l,firstSentAt:p?.firstSentAt??l,retryAt:l+Je,tries:(p?.tries??0)+1,nacks:p?.nacks??0})}let a=s.map(({trackId:r,event:u})=>({trackId:r,event:Vs(u)}));return C==="http"?(go(a),!0):L({v:B,ch:"events",type:"append",events:a})}function po(s){let l=null;for(let a of N.values())a.trackId===s&&(l==null||a.seq<l)&&(l=a.seq);return l}function Mt(s){let l=ee[s];if(l==null)return;let a=po(s),r=a==null?l:Math.min(l,a-1);r>(ne[s]??0)&&(ne[s]=r,Nn())}function je(){if(Qe=Date.now(),ie!=null){let s=Date.now()-ie;w.log(`[plane-link] spine sync recovered after ${Math.round(s/1e3)}s degraded (${re} stall redial(s))`);let l=ie;ie=null,re=0,Me=0,J=!1,he=0;try{K?.({since:l,downMs:s})}catch(a){w.error(`[plane-link] onRecovered failed: ${a?.message||a}`)}}}function Dt(s){if(!Array.isArray(s)||!s.length)return;let l=new Set;for(let a of s){let r=N.get(a);r&&(N.delete(a),(ee[r.trackId]??0)<r.seq&&(ee[r.trackId]=r.seq),l.add(r.trackId))}for(let a of l)Mt(a);je()}function In(s,l){xn++;let a={at:Date.now(),trackId:s.trackId,seq:s.seq,id:s.event?.id??null,type:s.event?.type??null,reason:l,event:s.event??null};w.error(`[plane-link] DEAD-LETTER ${s.trackId} seq ${s.seq} (${a.type??"?"}): plane rejected it permanently (${l}) \u2014 kept in ${Ot}`),On=On.then(async()=>{await Ye.mkdir(U.dirname(Ot),{recursive:!0}),await Ye.appendFile(Ot,JSON.stringify(a)+`
115
+ `),h(0)}var Tr,vs,tn,wt,kr=k(()=>{Tr="wss://plane.bostrat.ai/v1/node",vs=_s(import.meta.url)("../package.json").version,tn=`ghcr.io/globocodes/bostrat-node:${vs}`;wt=(e,{stdin:t,exec:n}={})=>new Promise((o,i)=>{if(n)return n(e,{stdin:t}).then(o,i);let c=Ts("fly",e,{timeout:6e5,maxBuffer:16*1024*1024},(d,m,h)=>d?i(Object.assign(d,{stdout:m,stderr:h})):o({stdout:m,stderr:h}));t!=null&&c.stdin?.end(t)})});function $r(e,t,n){if(e==null)return t==="from"?-1/0:1/0;if(!$s.test(String(e)))throw new Error(`pricing.js: ${n} window ${t} must be YYYY-MM-DD, got ${JSON.stringify(e)}`);let o=Date.parse(`${e}T${t==="from"?"00:00:00.000":"23:59:59.999"}Z`);if(!Number.isFinite(o))throw new Error(`pricing.js: ${n} window ${t} is not a real date: ${e}`);return o}function xs(e){let t={};for(let[n,o]of Object.entries(e)){if(!Array.isArray(o)||o.length===0)throw new Error(`pricing.js: ${n} must be a non-empty array of rate windows`);t[n]=o.map(i=>({...i,fromMs:$r(i.from,"from",n),toMs:$r(i.to,"to",n)}))}return t}var ks,$s,_l,Sl,xr=k(()=>{ks={"claude-opus-5-5":[{from:null,to:null,input:4,output:20,cacheWrite:8,cacheRead:.2,cacheReadX:.05,fast:{input:8,output:40,cacheWrite:16,cacheRead:.4},verifiedAt:"2026-09-28",note:"verified 2026-09-28 against platform.claude.com/docs/en/about-claude/pricing: $4/$20, 1h cache write $8 (2\xD7), cache hits $0.20 (0.05\xD7 \u2014 footnote 2), fast $8/$40; added before any fleet turn was recorded."}],"claude-opus-5":[{from:null,to:null,input:5,output:25,cacheWrite:10,cacheRead:.5,fast:{input:10,output:50,cacheWrite:20,cacheRead:1},verifiedAt:"2026-08-20",note:"GA rates; verified 2026-08-06 (claude-api skill), re-verified 2026-08-17 (live pricing page); fast-mode variant added 2026-08-20."}],"claude-opus-4-8":[{from:null,to:null,input:5,output:25,cacheWrite:10,cacheRead:.5,fast:{input:10,output:50,cacheWrite:20,cacheRead:1},verifiedAt:"2026-08-20",note:"verified 2026-08-06 (claude-api skill), re-verified 2026-08-17 (live pricing page); fast-mode variant added 2026-08-20."}],"claude-sonnet-5":[{from:null,to:null,input:2,output:10,cacheWrite:4,cacheRead:.2,verifiedAt:"2026-08-17",note:"$2/$10 is the standard price (intro pricing made permanent; the 2026-09-01 $3/$15 increase was cancelled) \u2014 verified 2026-08-17 against platform.claude.com/docs/en/about-claude/pricing."}],"claude-sonnet-4-6":[{from:null,to:null,input:3,output:15,cacheWrite:6,cacheRead:.3,verifiedAt:"2026-08-17",note:"verified 2026-08-06 (claude-api skill), re-verified 2026-08-17 (live pricing page)."}],"claude-haiku-4-5":[{from:null,to:null,input:1,output:5,cacheWrite:2,cacheRead:.1,verifiedAt:"2026-08-17",note:"verified 2026-08-06 (claude-api skill), re-verified 2026-08-17 (live pricing page)."}],"claude-fable-5-1":[{from:null,to:null,input:10,output:50,cacheWrite:20,cacheRead:.25,cacheReadX:.025,verifiedAt:"2026-09-07",note:"verified 2026-09-07 against platform.claude.com/docs/en/about-claude/pricing: $10/$50, 1h cache write $20 (2\xD7), cache hits $0.25 (0.025\xD7 \u2014 the 5.1-only multiplier; footnote 1 on the page)."}],"claude-fable-5":[{from:null,to:null,input:10,output:50,cacheWrite:20,cacheRead:1,verifiedAt:"2026-08-17",note:"verified 2026-08-06 (claude-api skill), re-verified 2026-08-17 (live pricing page); 83% of fleet spend."}],"gpt-5.5":[{from:null,to:null,input:5,output:30,cacheWrite:5,cacheRead:.5,verifiedAt:"2026-08-17",note:"verified 2026-07-03, re-verified 2026-08-17 (developers.openai.com pricing); no cache-write premium on 5.5-and-earlier."}],"gpt-5.6-sol":[{from:null,to:"2026-08-23",input:5,output:30,cacheWrite:6.25,cacheRead:.5,verifiedAt:"2026-08-17",note:"verified 2026-07-18, re-verified 2026-08-17 (developers.openai.com pricing); cacheWrite = 1.25\xD7 input (30-min TTL). Superseded 2026-08-24 by the vendor's promotional cut (window below)."},{from:"2026-08-24",to:null,input:4,output:20,cacheWrite:5,cacheRead:.4,verifiedAt:"2026-08-24",note:"promotional pricing verified 2026-08-24 (developers.openai.com pricing), 'at least through November 21, 2026'; cacheWrite = 1.25\xD7 input (30-min TTL)."}],"gpt-5.6-terra":[{from:null,to:"2026-08-16",input:2.5,output:15,cacheWrite:3.125,cacheRead:.25,verifiedAt:"2026-07-18",note:"as verified 2026-07-18; superseded by the 2026-08-17 sweep (0 corpus rows in this window)."},{from:"2026-08-17",to:null,input:2,output:12,cacheWrite:2.5,cacheRead:.2,verifiedAt:"2026-08-17",note:"verified 2026-08-17 (developers.openai.com pricing); cacheWrite = 1.25\xD7 input (30-min TTL)."}],"gpt-5.6-luna":[{from:null,to:"2026-08-16",input:1,output:6,cacheWrite:1.25,cacheRead:.1,verifiedAt:"2026-07-18",note:"as verified 2026-07-18; superseded by the 2026-08-17 sweep (0 corpus rows in this window)."},{from:"2026-08-17",to:null,input:.2,output:1.2,cacheWrite:.25,cacheRead:.02,verifiedAt:"2026-08-17",note:"verified 2026-08-17 (developers.openai.com pricing); cacheWrite = 1.25\xD7 input (30-min TTL)."}],"gpt-6-astra":[{from:null,to:null,input:10,output:50,cacheWrite:12.5,cacheRead:1,fast:{input:20,output:100,cacheWrite:25,cacheRead:2},verifiedAt:"2026-09-07",note:"verified 2026-09-07 (developers.openai.com pricing); cacheWrite = 1.25\xD7 input; fast tier $20/$100 recorded as the fast variant (codex reports no speed)."}]},$s=/^\d{4}-\d{2}-\d{2}$/;_l=xs(ks),Sl=Object.freeze({staleDays:90,leadDays:14,confirmDays:30})});var Er=k(()=>{xr()});import Or from"node:fs/promises";import Ir from"node:path";import Es from"node:os";function Mr(e){let t=new Date(e);return t.setHours(0,0,0,0),t.setDate(t.getDate()-(t.getDay()+6)%7),t.getTime()}function Dr(e){let t=new Date(e);return t.setHours(0,0,0,0),t.setDate(1),t.getTime()}function Cr(e){let t=new Date(Mr(e));return t.setDate(t.getDate()+7),t.getTime()}function Lr(e){let t=new Date(Dr(e));return t.setMonth(t.getMonth()+1),t.getTime()}function Ps(e=Date.now()){for(let[t,n]of Object.entries(Ns)){let o=n.startOf(e);if(o!==yt[t]){yt[t]=o;for(let i of nn.values())i[t]=0;for(let i of Rr)i.includes(`:${t}:`)&&!i.endsWith(`:${o}`)&&Rr.delete(i)}}}function Ms(e){let t={};for(let[n,o]of Object.entries(e||{})){if(typeof n!="string"||!/^[a-z][a-z0-9-]{0,31}$/.test(n))throw new Error(`bad provider key "${n}"`);let i={};for(let c of["weeklyUsd","monthlyUsd"]){let d=o?.[c];if(d==null||d==="")continue;let m=Number(d);if(!Number.isFinite(m)||m<=0)throw new Error(`${n}.${c} must be a positive number or null`);i[c]=m}Object.keys(i).length&&(t[n]=i)}return t}async function Br(e,{source:t="local",updatedAt:n=Date.now()}={}){Ee={caps:Ms(e),updatedAt:n,source:t};try{await Or.mkdir(Pr,{recursive:!0}),await Or.writeFile(Os(),JSON.stringify(Ee,null,2))}catch{}return Ds()}function Ds(e=Date.now()){Ps(e);let t=new Set([...nn.keys(),...Object.keys(Ee?.caps||{})]),n={};for(let o of t){let i=nn.get(o);n[o]={weeklyUsd:Nr(i?.weekly||0),monthlyUsd:Nr(i?.monthly||0)}}return{caps:Ee?.caps||{},configured:!!Ee,seeded:Is,updatedAt:Ee?.updatedAt??null,spent:n,windows:{weekly:{start:yt.weekly,resetsAt:Cr(e)},monthly:{start:yt.monthly,resetsAt:Lr(e)}},warnPct:Rs}}var Pr,Os,Rs,Ns,Ee,Is,yt,nn,Rr,Nr,jr=k(()=>{pt();Er();Pr=process.env.BOSTRAT_STATE_DIR||Ir.join(process.env.HOME||Es.tmpdir(),".bostrat"),Os=()=>Ir.join(Pr,"usage-caps.json"),Rs=.8;Ns={weekly:{startOf:Mr,nextStart:Cr,capKey:"weeklyUsd"},monthly:{startOf:Dr,nextStart:Lr,capKey:"monthlyUsd"}},Ee=null,Is=!1,yt={weekly:0,monthly:0},nn=new Map,Rr=new Set;Nr=e=>Math.round(e*1e4)/1e4});var Yr={};Ft(Yr,{REMOVED_MARKER:()=>ln,detectInstallKind:()=>Ne,detectProviderAuth:()=>Gr,maskToken:()=>cn,readRemovedMarker:()=>zr,startPlaneLink:()=>Zs,updateCommandFor:()=>qe,versionLt:()=>Re});import Oe from"node:fs";import Ye from"node:fs/promises";import Cs from"node:http";import U from"node:path";import gt from"node:os";import{createRequire as Ls}from"node:module";import rn from"ws";function cn(e){return typeof e!="string"||e.length<8?"\u2022\u2022\u2022\u2022":`${e.slice(0,6)}\u2026${e.slice(-4)}`}function zr(e=ln){try{let t=JSON.parse(Oe.readFileSync(e,"utf8"));return t&&typeof t=="object"?t:null}catch{return null}}function Ne({env:e=process.env,pkgDir:t=U.resolve(U.dirname(new URL(import.meta.url).pathname),".."),exists:n=Oe.existsSync}={}){let o=String(e.BOSTRAT_INSTALL_KIND||"").trim();return["container","npm-global","git-checkout","dev"].includes(o)?o:/\/lib\/node_modules\/bostrat-node(?:\/|$)/.test(t.replace(/\\/g,"/"))?"npm-global":n(U.join(t,"..",".git"))?"git-checkout":"dev"}function Re(e,t){let n=String(e).split(".").map(Number),o=String(t).split(".").map(Number);if(n.some(Number.isNaN)||o.some(Number.isNaN))return!1;for(let i=0;i<Math.max(n.length,o.length);i++){let c=(n[i]||0)-(o[i]||0);if(c)return c<0}return!1}function qe(e){switch(e){case"npm-global":return"npm install -g bostrat-node && restart the bostrat-node service";case"git-checkout":return"scripts/deploy-pi.sh (or The Bakery \u2192 Deploy to Pi \u2192 bostrat)";case"container":return"Links \u2192 Fly \u2192 Update (or the plane's auto-update while the node sleeps)";default:return"update this node's bostrat-node checkout/install"}}function Gr({home:e=process.env.HOME||gt.homedir(),env:t=process.env}={}){let n=o=>{try{return Oe.statSync(o),!0}catch{return!1}};return{claude:n(U.join(e,".claude",".credentials.json")),codex:n(U.join(t.CODEX_HOME||U.join(e,".codex"),"auth.json")),cursor:n(U.join(t.XDG_CONFIG_HOME||U.join(e,".config"),"cursor","auth.json"))}}function Zs({url:e,token:t,nodeId:n=gt.hostname(),name:o=gt.hostname(),listTracks:i,detectAgents:c=null,onTracksTransferred:d=null,onTracksMoves:m=null,sampleMetrics:h=null,deliverySample:x=null,onHashKey:f=null,onViewBase:T=null,metricsMs:$=Js,onUpdateAvailable:S=null,onUpdatePolicy:v=null,onHelloRefused:P=null,updatePolicy:O=null,installKind:E=Ne(),selfUrl:b=`http://127.0.0.1:${process.env.PORT||3362}`,apiToken:_=null,removedMarkerFile:A=ln,onRemoved:M=null,onDegraded:R=null,onRecovered:K=null,onRefused:kt=null,onConnected:co=null,ingestMode:$t=["ws","http","auto"].includes(process.env.BOSTRAT_PLANE_INGEST)?process.env.BOSTRAT_PLANE_INGEST:"auto",deliveryTimeoutMs:_n=zs,unackedRetryMs:Je=Ws,pingMs:Sn=Hs,deadLinkMs:xt=Math.round(Sn*Ks),maxInflightEvents:lo=qs,httpTimeoutMs:Ze=Xs,stallBackoffMinMs:vn=Ys,fetchImpl:bn=globalThis.fetch,log:w=console}){if(!e||!t)throw new Error("plane-link: url and token are required");if(typeof i!="function")throw new Error("plane-link: listTracks is required");e=Ue(e);let uo=b.replace(/^http/,"ws"),An=new Set,kn=s=>{let l=typeof s.latestNodeVersion=="string"?s.latestNodeVersion:null;if(!(!(l&&Z&&Re(Z,l))||An.has(l))){An.add(l),w.log(`[plane-link] bostrat-node ${l} is available (this node runs ${Z}) \u2014 ${qe(E)}`);try{S?.({current:Z,latest:l,min:s.minNodeVersion??null,installKind:E,required:!!(s.minNodeVersion&&Re(Z,s.minNodeVersion))})}catch(a){w.error(`[plane-link] update notice failed: ${a?.message||a}`)}}},fe=new Map,pe=new Map,Et=U.join(an,"plane-link","cursor.json"),Ot=U.join(an,"plane-link","dead-letter.ndjson"),$n=e.replace(/^ws(s?):\/\//,"http$1://").replace(/\/v1\/node\/?$/,""),ne={},ee={},N=new Map,Qe=null,ie=null,re=0,Me=0,xn=0,et={},En=new Set,On=Promise.resolve(),C="ws",be=0,J=!1,he=0,Rt=!1,V=le,Ae=new Set,ae=[],z=null,Y=null,q=!1,Nt=!1,De=Fr,Ce=null,ke=null,$e=null,oe=null,me=0,Rn=0,se=null,It=!1,tt=0,Le=!1,ce=zr(A);if(ce)if(ce.tokenPreview===cn(t))Le=!0,Yt({reason:ce.reason??"removed",at:ce.at??null,nodeId:ce.nodeId??n}),w.error(`[plane-link] this node was REMOVED from the plane (${ce.reason??"removed"}${ce.at?` at ${new Date(ce.at).toISOString()}`:""}) \u2014 not dialing. Re-provision it with a new node token, or delete ${A} to force a dial.`);else try{Oe.unlinkSync(A),w.log("[plane-link] stale removed-marker discarded \u2014 the node token changed")}catch{}function Pt({reason:s="removed",at:l=Date.now(),via:a="ctl"}={}){if(!Le){Le=!0,Yt({reason:s,at:l,nodeId:n,via:a});try{Oe.mkdirSync(U.dirname(A),{recursive:!0}),Oe.writeFileSync(A,JSON.stringify({at:l,reason:s,via:a,nodeId:n,tokenPreview:cn(t)},null,2))}catch(r){w.error(`[plane-link] could not write ${A}: ${r?.message||r}`)}w.error(`[plane-link] the plane REMOVED this node (${s}, via ${a}) \u2014 going dormant: no more dials, ingest or broker calls. Re-provision with a new node token to reconnect; the box and its data are untouched.`);try{M?.({reason:s,at:l,via:a})}catch{}Un({bye:!1})}}async function fo(){try{ne=JSON.parse(await Ye.readFile(Et,"utf8"))}catch{ne={}}}function Nn(){It=!0,!$e&&($e=setTimeout(async()=>{if($e=null,!!It){It=!1;try{await Ye.mkdir(U.dirname(Et),{recursive:!0}),await Ye.writeFile(Et,JSON.stringify(ne))}catch(s){w.error("[plane-link] cursor flush failed:",s?.message||s)}}},Us),$e.unref?.())}function L(s){return!Y||Y.readyState!==rn.OPEN?!1:(Y.send(JSON.stringify(s)),!0)}function Be(s){let l=Date.now();for(let{trackId:r,event:u}of s){let p=N.get(u.id);N.set(u.id,{trackId:r,seq:u.seq,event:u,sentAt:l,firstSentAt:p?.firstSentAt??l,retryAt:l+Je,tries:(p?.tries??0)+1,nacks:p?.nacks??0})}let a=s.map(({trackId:r,event:u})=>({trackId:r,event:Vs(u)}));return C==="http"?(go(a),!0):L({v:B,ch:"events",type:"append",events:a})}function po(s){let l=null;for(let a of N.values())a.trackId===s&&(l==null||a.seq<l)&&(l=a.seq);return l}function Mt(s){let l=ee[s];if(l==null)return;let a=po(s),r=a==null?l:Math.min(l,a-1);r>(ne[s]??0)&&(ne[s]=r,Nn())}function je(){if(Qe=Date.now(),ie!=null){let s=Date.now()-ie;w.log(`[plane-link] spine sync recovered after ${Math.round(s/1e3)}s degraded (${re} stall redial(s))`);let l=ie;ie=null,re=0,Me=0,J=!1,he=0;try{K?.({since:l,downMs:s})}catch(a){w.error(`[plane-link] onRecovered failed: ${a?.message||a}`)}}}function Dt(s){if(!Array.isArray(s)||!s.length)return;let l=new Set;for(let a of s){let r=N.get(a);r&&(N.delete(a),(ee[r.trackId]??0)<r.seq&&(ee[r.trackId]=r.seq),l.add(r.trackId))}for(let a of l)Mt(a);je()}function In(s,l){xn++;let a={at:Date.now(),trackId:s.trackId,seq:s.seq,id:s.event?.id??null,type:s.event?.type??null,reason:l,event:s.event??null};w.error(`[plane-link] DEAD-LETTER ${s.trackId} seq ${s.seq} (${a.type??"?"}): plane rejected it permanently (${l}) \u2014 kept in ${Ot}`),On=On.then(async()=>{await Ye.mkdir(U.dirname(Ot),{recursive:!0}),await Ye.appendFile(Ot,JSON.stringify(a)+`
116
116
  `)}).catch(r=>w.error(`[plane-link] dead-letter write failed: ${r?.message||r}`))}function ho(s,l){if(et[s]=(et[s]??0)+1,En.has(s))return;En.add(s);let a=Gs[s]??"see the plane's tenancy alarm ring (GET /plane/tenancy/alarms)";w.error(`[plane-link] REFUSED by the plane (${s}) on track ${l}: events are being dropped, not stored \u2014 ${a}`);try{kt?.({reason:s,trackId:l,count:et[s],hint:a})}catch(r){w.error(`[plane-link] onRefused failed: ${r?.message||r}`)}}function Ct(s,l,a,r=null){if(!Array.isArray(s)||!s.length)return;let u=new Set,p=Date.now();for(let g of s){let y=N.get(g);y&&(a?(N.delete(g),In(y,l),String(l).startsWith(on)&&ho(String(l).slice(on.length),y.trackId),(ee[y.trackId]??0)<y.seq&&(ee[y.trackId]=y.seq),u.add(y.trackId)):(y.nacks++,y.retryAt=p+Math.max(1e3,Number(r)||Je)))}for(let g of u)Mt(g);je()}function Pn(s=Date.now()){let l=1/0;for(let a of N.values())a.firstSentAt<l&&(l=a.firstSentAt);return l===1/0?0:s-l}function mo(s){if(!xt||!s||s!==Y||!me)return!1;let l=Date.now()-me;if(l<=xt)return!1;Rn++,w.error(`[plane-link] no frame from the plane for ${Math.round(l/1e3)}s \u2014 link is half-open; terminating and redialing`);try{s.terminate()}catch{}return!0}function Mn(){if(!q||!N.size)return;let s=Date.now(),l=Pn(s);if(l<_n||Qe!=null&&s-Qe<_n||C==="http"&&J||Me>s)return;re++;let a=Math.min(vn*2**(re-1),Hr);if(ie==null){ie=s-l,w.error(`[plane-link] DEGRADED: no ack or nack for ${Math.round(l/1e3)}s with ${N.size} event(s) in flight (${C} door) \u2014 the plane is not answering appends`);try{R?.({since:ie,unacked:N.size,oldestAgeMs:l,mode:C})}catch(r){w.error(`[plane-link] onDegraded failed: ${r?.message||r}`)}}if(C==="http"){J=!0,he=s+a,w.error(`[plane-link] ingest circuit OPEN (stall #${re}); probing /v1/ingest/health in ${Math.round(a/1e3)}s`);return}Me=s+a,w.error(`[plane-link] closing the link and redialing in ${Math.round(a/1e3)}s (stall redial #${re})`);try{Y?.terminate()}catch{}}function Lt(){if(!q)return;let s=Date.now();if(C==="http"&&J)return;let l=[...N.values()].filter(r=>r.event&&r.retryAt<=s&&!(C==="http"&&Ae.has(r.trackId)));if(!l.length)return;l.sort((r,u)=>r.trackId===u.trackId?r.seq-u.seq:r.trackId<u.trackId?-1:1),w.log(`[plane-link] re-sending ${l.length} unacked event(s) the plane never confirmed`);let a=C==="http"?V:le;if(C==="http"){let r=new Map;for(let u of l)(r.get(u.trackId)??r.set(u.trackId,[]).get(u.trackId)).push(u);for(let[,u]of r){if(be>=sn)return;Be(u.slice(0,a).map(p=>({trackId:p.trackId,event:p.event})))}return}for(let r=0;r<l.length;r+=a){let u=l.slice(r,r+a).map(p=>({trackId:p.trackId,event:p.event}));if(!Be(u))return}}async function wo(s=null){for(;;){if(!q)return!1;let l=N.size<lo,a=C!=="http"||be<sn&&!J&&(!s||!Ae.has(s));if(l&&a)return!0;await new Promise(r=>setTimeout(r,100))}}function Bt(){if(z=null,!q||!ae.length)return;let s=ae;if(ae=[],C!=="http"){for(let a=0;a<s.length;a+=le)Be(s.slice(a,a+le));return}let l=new Map;for(let a of s)(l.get(a.trackId)??l.set(a.trackId,[]).get(a.trackId)).push(a);for(let[a,r]of l){if(Ae.has(a)||be>=sn||J){ae.push(...r);continue}for(let u=0;u<r.length;u+=V)Be(r.slice(u,u+V))}ae.length&&!z&&(z=setTimeout(Bt,Kr),z.unref?.())}let yo=s=>{let l=new Map;for(let{trackId:a,event:r}of s)(l.get(a)??l.set(a,[]).get(a)).push(r);return[...l].map(([a,r])=>({trackId:a,events:r}))},we=(s,l,a,{progress:r})=>{let u=Date.now();for(let{event:p}of s){let g=N.get(p.id);g&&(g.nacks++,g.retryAt=u+Math.max(1e3,a||Je))}r&&je()};async function go(s){let l=new Set(s.map(a=>a.trackId));for(let a of l)Ae.add(a);be++;try{let a=await bn(`${$n}/v1/ingest`,{method:"POST",headers:{authorization:`Bearer ${t}`,"content-type":"application/json"},body:JSON.stringify({node:n,batches:yo(s)}),signal:AbortSignal.timeout(Ze)}),r=(Number(a.headers.get("retry-after"))||0)*1e3;if(a.status===200){let u=await a.json();V=le;for(let y of u.results??[]){Dt(y.acked??[]);let D=(te,W)=>{let nt=new Map;for(let ye of te??[])(nt.get(ye.reason)??nt.set(ye.reason,[]).get(ye.reason)).push(ye.id);for(let[ye,Hn]of nt)Ct(Hn,ye,W,u.retryAfterMs??null)};D(y.rejected,!0),D(y.deferred,!1)}let p=new Set,g=new Map;for(let y of Array.isArray(u.refused)?u.refused:[])!y||typeof y.id!="string"||(p.add(y.id),(g.get(y.reason)??g.set(y.reason,[]).get(y.reason)).push(y.id));for(let[y,D]of g)Ct(D,`${on}${y}`,!0);Dt((u.discarded??[]).filter(y=>!p.has(y))),je();return}if(a.status===429){we(s,"overloaded",r||5e3,{progress:!0});return}if(a.status===413&&s.length>1){V=Math.max(1,Math.floor(s.length/2)),w.error(`[plane-link] ingest request too large (${s.length} events) \u2014 halving batches to ${V}`),we(s,"too_large",1e3,{progress:!0});return}if(a.status===413){let u=N.get(s[0].event.id);u&&(N.delete(u.event.id),In(u,"too_large"),(ee[u.trackId]??0)<u.seq&&(ee[u.trackId]=u.seq),Mt(u.trackId)),je();return}if(a.status===410){Pt({reason:"removed",via:"410"}),we(s,"http_410",6e4,{progress:!1});return}if(a.status===401||a.status===403){let{tripped:u,holdMs:p}=ut();if(u&&w.error(`[plane-link] holding every plane call for ${Math.round(p/6e4)} min \u2014 the ingest door refused this node's token three times in a row`),Date.now()-tt>=Wr){tt=Date.now();let g="";try{g=(await a.json())?.error??""}catch{}w.error(`[plane-link] plane refused ingest (HTTP ${a.status}${g?`: ${g}`:""}) \u2014 events are held locally and retried; check this node's token/binding`)}we(s,`http_${a.status}`,Math.max(6e4,We()),{progress:!1});return}if(a.status===400){w.error(`[plane-link] plane rejected an ingest request as malformed (400) \u2014 holding ${s.length} event(s) for retry; this is a bug, not data`),we(s,"http_400",6e4,{progress:!1});return}we(s,`http_${a.status}`,r||5e3,{progress:!1})}catch(a){let r=a?.name==="TimeoutError"||a?.name==="AbortError";r&&s.length>1&&(V=Math.max(1,Math.floor(s.length/2)),w.error(`[plane-link] ingest request of ${s.length} event(s) outlived its ${Ze} ms deadline \u2014 halving batches to ${V}`)),we(s,r?"timeout":"network",5e3,{progress:!1})}finally{be--;for(let a of l)Ae.delete(a)}}async function To(){if(C!=="http"||!J||!q||Rt)return;let s=Date.now();if(!(he>s||dt(s))){Rt=!0;try{let l=await bn(`${$n}/v1/ingest/health`,{headers:{authorization:`Bearer ${t}`},signal:AbortSignal.timeout(Ze)});if(!l.ok)throw new Error(`HTTP ${l.status}`);w.log("[plane-link] ingest door answering \u2014 closing the circuit and re-sending"),J=!1,he=0,Me=Date.now()+Ze+Je+1e3;for(let a of N.values())a.retryAt=Math.min(a.retryAt,Date.now());Lt()}catch(l){re++;let a=Math.min(vn*2**(re-1),Hr);he=Date.now()+a,w.error(`[plane-link] ingest door still not answering (${l?.message||l}); next probe in ${Math.round(a/1e3)}s`)}finally{Rt=!1}}}function Dn(){let s=Date.now();return{mode:C,unacked:N.size,oldestUnackedAgeMs:Pn(s),lastProgressAt:Qe,degradedSince:ie,stallRedials:re,deadLettered:xn,http:C==="http"?{inFlight:be,circuitOpen:J,nextProbeInMs:J?Math.max(0,he-s):0,batchCap:V}:null}}async function Cn(){if(!h)return;let s;try{s=await h()}catch(a){w.error("[plane-link] metrics sample failed:",a?.message||a);return}let l=null;try{l=x?.()??null}catch{}s&&L({v:B,ch:"ctl",type:"metrics",metrics:{...s,planeLink:Dn(),...l?{delivery:l}:{}}})}function _o(){h&&(se&&clearInterval(se),Cn(),se=setInterval(Cn,$),se.unref?.())}async function So(){let s;try{s=await i()}catch(l){w.error("[plane-link] backfill listTracks failed:",l?.message||l);return}L({v:B,ch:"ctl",type:"tracks",ids:s.filter(l=>!l.transferredTo).map(l=>l.id).filter(l=>typeof l=="string")});for(let l of s)if(!q||!await Ln(l))return}async function Ln(s){let l;try{l=await ft(s)}catch{return!0}let a=ne[s.id]??0,r=l.filter(p=>p.seq>a&&typeof p.id=="string"),u=C==="http"?V:le;for(let p=0;p<r.length;p+=u){if(!await wo(s.id))return!1;let g=r.slice(p,p+Math.min(u,C==="http"?V:le)).map(y=>({trackId:s.id,event:y}));if(!Be(g))return!1}return!0}let jt=new Map;async function Bn(s,l){if(jt.get(s)===l)return!1;jt.set(s,l);let a=null;try{a=(await i()).find(g=>g?.id===s)??null}catch{return!1}if(!a)return!1;let r;try{r=await ft(a)}catch{return!1}let u=[...r].reverse().find(g=>(g?.type==="track.transferred"||g?.type==="track.recovered")&&g.payload?.toNode===n);if(!u||!Number.isFinite(u.seq))return!1;let p=u.seq-1;return(ne[s]??0)>p&&(ne[s]=p,Nn()),(ee[s]??0)>p&&(ee[s]=p),w.log(`[plane-link] re-sending ${s}'s move-commit (seq ${u.seq}, ${u.type}) \u2014 the plane's move record predates this node's adoption`),q?Ln(a):!1}function jn(s){!q||!s?.trackId||typeof s.id!="string"||(ae.push({trackId:s.trackId,event:s}),z||(z=setTimeout(Bt,Kr),z.unref?.()))}let Fe=new Map;async function vo(s){let{reqId:l,method:a,path:r,headers:u={},body:p}=s,g=Object.fromEntries(Object.entries(u).filter(([,D])=>D!=null)),y=new AbortController;Fe.set(l,y);try{if(s.host){await bo(s,g,y);return}_&&(g["x-bostrat-token"]=_);let D=await fetch(b+r,{method:a,headers:g,body:p?Buffer.from(p,"base64"):void 0,signal:y.signal}),te=Object.fromEntries(D.headers);for(let W of["connection","keep-alive","transfer-encoding","upgrade","content-encoding","content-length"])delete te[W];if(te["content-type"]||(te["content-type"]="application/octet-stream"),L({v:B,ch:"control",type:"http_head",reqId:l,status:D.status,headers:te}),D.body){for await(let W of D.body)if(!L({v:B,ch:"control",type:"http_data",reqId:l,data:Buffer.from(W).toString("base64")}))break}L({v:B,ch:"control",type:"http_end",reqId:l,error:null})}finally{Fe.delete(l)}}function bo(s,l,a){let{reqId:r,method:u,path:p,body:g}=s,y=new URL(b);return new Promise(D=>{let te=Cs.request({host:y.hostname,port:y.port,method:u,path:p,headers:{...l,host:s.host},signal:a.signal},W=>{let{connection:nt,"keep-alive":ye,"transfer-encoding":Hn,upgrade:zi,...Ao}=W.headers;L({v:B,ch:"control",type:"http_head",reqId:r,status:W.statusCode||502,headers:Ao}),W.on("data",ko=>{L({v:B,ch:"control",type:"http_data",reqId:r,data:Buffer.from(ko).toString("base64")})||W.destroy()}),W.on("end",()=>{L({v:B,ch:"control",type:"http_end",reqId:r,error:null}),D()}),W.on("error",()=>{L({v:B,ch:"control",type:"http_end",reqId:r,error:"view upstream error"}),D()})});te.on("error",W=>{L({v:B,ch:"control",type:"http_end",reqId:r,error:W?.message||"view request failed"}),D()}),g&&te.write(Buffer.from(g,"base64")),te.end()})}function Fn(){if(Nt||Le)return;Y=new rn(e,{headers:{authorization:`Bearer ${t}`}});let s=Y;me=0,s.on("ping",()=>{me=Date.now()}),s.on("pong",()=>{me=Date.now()}),Y.on("open",async()=>{let a=null;try{a=await c?.()||null}catch{a=null}L({v:B,ch:"ctl",type:"hello",nodeId:n,name:o,platform:process.platform,version:Z,installKind:E,...typeof O=="function"?{updatePolicy:O()}:O?{updatePolicy:O}:{},providers:Gr(),...a?{agents:a}:{}})}),Y.on("message",a=>{me=Date.now();let r;try{r=JSON.parse(a)}catch{return}if(r.ch==="ctl"&&r.type==="hello_ok"){jt.clear(),q=!0,De=Fr,Ce=null,Zn(),w.log(`[plane-link] connected to plane as ${n}`);try{co?.()}catch{}if(typeof r.hashKey=="string"&&r.hashKey)try{f?.(r.hashKey)}catch{}try{T?.(typeof r.viewBase=="string"&&r.viewBase?r.viewBase:null)}catch{}r.minNodeVersion&&Z&&Re(Z,r.minNodeVersion)&&w.error(`[plane-link] this node is bostrat-node ${Z}; the plane asks for \u2265${r.minNodeVersion} \u2014 update with: ${qe(E)}`);try{v?.(typeof r.updatePolicy=="string"?r.updatePolicy:null)}catch{}kn(r);let u=r.ingest==="http";C=$t==="ws"?"ws":$t==="http"||u?"http":"ws",$t==="http"&&!u&&w.error("[plane-link] BOSTRAT_PLANE_INGEST=http but this plane offers no HTTPS ingest door \u2014 events will fail until it does"),C==="http"&&w.log("[plane-link] spine rides the HTTPS ingest door"),J=!1,he=0,V=le,oe&&clearInterval(oe),me=Date.now(),oe=setInterval(()=>{mo(s)||(L({v:B,ch:"ctl",type:"ping"}),Mn(),To().catch(()=>{}),Lt(),ae.length&&!z&&Bt())},Sn),oe.unref?.(),_o(),So().catch(p=>w.error("[plane-link] backfill failed:",p?.message||p));return}if(r.ch==="ctl"&&r.type==="hello_refused"){let u=typeof r.minNodeVersion=="string"?r.minNodeVersion:null,p=typeof r.latestNodeVersion=="string"?r.latestNodeVersion:null;w.error(`[plane-link] plane REFUSED this node: bostrat-node ${Z??"?"} is below its minimum ${u??"?"} \u2014 ${r.fix||qe(E)}`),De=Ur,Ce=0;try{P?.({current:Z,min:u,latest:p,fix:r.fix||qe(E),installKind:E})}catch(g){w.error(`[plane-link] refusal handler failed: ${g?.message||g}`)}return}if(r.ch==="ctl"&&r.type==="removed"){L({v:B,ch:"ctl",type:"removed_ack",nodeId:n}),Pt({reason:typeof r.reason=="string"?r.reason:"removed",at:Number(r.at)||Date.now(),via:"ctl"});return}if(r.ch==="ctl"&&r.type==="update_available"){kn(r);return}if(r.ch==="ctl"&&r.type==="update_policy"){try{v?.(typeof r.mode=="string"?r.mode:null)}catch{}return}if(r.ch==="ctl"&&r.type==="usage_caps"){Br(r.caps??{},{source:"plane",updatedAt:r.updatedAt??Date.now()}).then(u=>w.log(`[plane-link] spend caps ${Object.keys(u.caps).length?`updated (${Object.keys(u.caps).join(", ")})`:"cleared"} from plane`)).catch(u=>w.error("[plane-link] spend-caps push rejected:",u?.message||u));return}if(r.ch==="ctl"&&r.type==="transferred"&&Array.isArray(r.tracks)){Promise.resolve().then(()=>d?.(r.tracks)).then(async u=>{for(let p of Array.isArray(u)?u:[])p?.stale&&p.id&&await Bn(p.id,p.since??null)}).catch(u=>w.error("[plane-link] transfer notice failed:",u?.message||u));return}if(r.ch==="ctl"&&r.type==="moves"&&Array.isArray(r.tracks)){Promise.resolve().then(()=>m?.(r.tracks)).then(async u=>{for(let p of Array.isArray(u)?u:[])p?.stale&&p.id&&await Bn(p.id,p.since??null)}).catch(u=>w.error("[plane-link] move-record reconcile failed:",u?.message||u));return}if(r.ch==="events"&&r.type==="ack"&&Array.isArray(r.ids)){Dt(r.ids);return}if(r.ch==="events"&&r.type==="nack"&&Array.isArray(r.ids)){Ct(r.ids,r.reason,r.permanent===!0,r.retryAfterMs??null);return}if(r.ch==="control"&&r.type==="http_open"){vo(r).catch(u=>L({v:B,ch:"control",type:"http_end",reqId:r.reqId,error:u?.message||"tunnel error"}));return}if(r.ch==="control"&&r.type==="http_cancel"){Fe.get(r.reqId)?.abort();return}if(r.ch==="attach"&&r.type==="open"){let u=new rn(uo+r.path,Array.isArray(r.protocols)?r.protocols:[],r.host?{headers:{host:r.host}}:void 0);fe.set(r.streamId,u),pe.set(r.streamId,[]),u.on("open",()=>{let g=pe.get(r.streamId)||[];pe.delete(r.streamId);for(let{buf:y,bin:D}of g)u.send(y,{binary:D})}),u.on("message",(g,y)=>{let D=y?g:Buffer.from(String(g));L({v:B,ch:"attach",type:"data",streamId:r.streamId,data:D.toString("base64"),...y?{bin:!0}:{}})});let p=()=>{pe.delete(r.streamId),fe.delete(r.streamId)&&L({v:B,ch:"attach",type:"close",streamId:r.streamId})};u.on("close",p),u.on("error",p);return}if(r.ch==="attach"&&r.type==="data"){let u=fe.get(r.streamId);if(!u)return;let p=Buffer.from(r.data,"base64"),g=r.bin===!0,y=pe.get(r.streamId);y?y.push({buf:p,bin:g}):u.send(p,{binary:g});return}if(r.ch==="attach"&&r.type==="close"){let u=fe.get(r.streamId);fe.delete(r.streamId),pe.delete(r.streamId),u?.close();return}});let l=()=>{q&&w.log("[plane-link] disconnected from plane"),q=!1,oe&&(clearInterval(oe),oe=null),se&&(clearInterval(se),se=null),N.clear(),ee={},ae=[],z&&(clearTimeout(z),z=null),Ae.clear();for(let u of fe.values())u.close();fe.clear(),pe.clear();for(let u of Fe.values())u.abort();if(Fe.clear(),Nt||ke)return;Ce==null&&(Ce=Date.now());let a=Date.now()-Ce<Bs,r=Math.max(a?js+Math.random()*Fs:De,Me-Date.now(),We());ke=setTimeout(()=>{ke=null,Fn()},r),ke.unref?.(),a||(De=Math.min(De*2,Ur))};Y.on("close",l),Y.on("error",a=>{let r=/Unexpected server response: (\d+)/.exec(a?.message||"")?.[1];if(r==="410"){Pt({reason:"removed",via:"410"});return}if(r==="401"||r==="403"){let{tripped:u,holdMs:p}=ut();u?w.error(`[plane-link] holding every plane call for ${Math.round(p/6e4)} min \u2014 three credential refusals in a row (HTTP ${r}) mean the token is revoked or unknown, not that the plane is down; mint a new node token and update this node's BOSTRAT_PLANE_TOKEN. Next dial in ${Math.round(p/6e4)} min.`):Date.now()-tt>=Wr&&(tt=Date.now(),w.error(`[plane-link] plane refused this node's credentials (HTTP ${r}) \u2014 the token was revoked or is unknown to the plane; mint a new node token and update this node's BOSTRAT_PLANE_TOKEN (still retrying)`))}l()})}Ge.on("event",jn),fo().then(Fn);function Un({bye:s=!0}={}){Nt=!0,Ge.off("event",jn),ke&&clearTimeout(ke),$e&&clearTimeout($e),oe&&clearInterval(oe),se&&clearInterval(se),z&&clearTimeout(z),s&&L({v:B,ch:"ctl",type:"bye",reason:"shutdown"}),Y?.close()}return{stop:()=>Un(),status:()=>({connected:q,nodeId:n,cursor:{...ne},inflight:N.size,...Dn(),auth:Qn(),dormant:Le,removed:qt(),deadLinkMs:xt,deadLinkRedials:Rn,refused:{...et}}),_tick:()=>{Mn(),Lt()}}}var Z,an,ln,B,Fr,Ur,Bs,js,Fs,le,Us,Hs,Ks,Ws,Vr,zs,on,Gs,Vs,Ys,Hr,qs,Kr,Xs,sn,Js,Wr,un=k(()=>{pt();jr();Ht();Xt();Z=(()=>{try{return Ls(import.meta.url)("../package.json").version||null}catch{return null}})(),an=process.env.BOSTRAT_STATE_DIR||U.join(process.env.HOME||gt.tmpdir(),".bostrat"),ln=U.join(an,"plane-link","removed.json");B=1,Fr=1e3,Ur=3e4,Bs=6e4,js=1500,Fs=500,le=100,Us=2e3,Hs=25e3,Ks=2.5,Ws=6e4,Vr=(e,t)=>{let n=Number(e);return Number.isFinite(n)&&n>0?n:t},zs=Vr(process.env.BOSTRAT_PLANE_DELIVERY_TIMEOUT_MS,18e4),on="refused:",Gs={forgedUserId:"this node's events carry a userId the plane's token does not own \u2014 remove BOSTRAT_OWNER from node.env and the shell environment, then restart the node (current releases never stamp one)",foreignTrack:"the Track belongs to another user's node \u2014 this node must not append to it",squattedTrack:"the Track id is already claimed by another user \u2014 a local Track collided with theirs",transferredAway:"the Track moved to another node \u2014 this copy is stale; it should be demoted, not resumed",slugHijack:"a View slug belongs to another owner \u2014 pick a different slug"},Vs=e=>{if(!e||e.userId===void 0)return e;let{userId:t,...n}=e;return n},Ys=3e4,Hr=10*6e4,qs=400,Kr=250,Xs=Vr(process.env.BOSTRAT_PLANE_INGEST_TIMEOUT_MS,1e4),sn=4,Js=6e4,Wr=6e4});var qr=k(()=>{Gt()});var no={};Ft(no,{ACTIONABLE_KINDS:()=>bt,INSTALL_ATTEMPTS:()=>Tt,INSTALL_RETRY_STEP_S:()=>Xr,REGISTRY_RETRY_ATTEMPTS:()=>pn,REGISTRY_WAIT_POLLS:()=>dn,REGISTRY_WAIT_STEP_S:()=>fn,RESTART_SIGNAL:()=>Zr,SELF_UPDATE_DIR:()=>St,SUPERVISOR_PID_FILE:()=>Jr,createAutoUpdater:()=>pi,inFlight:()=>yn,readStatus:()=>wn,restartCommand:()=>Qr,restartStrategy:()=>mn,selfUpdatePlan:()=>eo,settleSelfUpdate:()=>fi,startSelfUpdate:()=>to,underService:()=>hn,updatePolicy:()=>ii});import Xe from"node:fs";import ue from"node:fs/promises";import Qs from"node:os";import j from"node:path";import{spawn as ei,execFileSync as ti}from"node:child_process";import{createRequire as ni}from"node:module";function ii({kind:e=Ne(),env:t=process.env}={}){let n=String(t.BOSTRAT_UPDATE_POLICY||"").trim();return["auto","prompt","off"].includes(n)?n:bt.has(e)?e==="git-checkout"||hn(t)?"auto":"prompt":"off"}function hn(e=process.env){return!!(e.INVOCATION_ID||e.XPC_SERVICE_NAME&&e.XPC_SERVICE_NAME!=="0")}function mn({env:e=process.env,platform:t=process.platform,uid:n=typeof process.getuid=="function"?process.getuid():0,serviceName:o=t==="darwin"?"com.bostrat.node":"bostrat-node.service",pidFile:i=Jr,readFile:c=m=>Xe.readFileSync(m,"utf8"),alive:d=ai}={}){let m=t==="darwin"?{mode:"launchd",uid:n,serviceName:o}:{mode:"systemd",serviceName:o};if(hn(e))return m;let h=Number(e.BOSTRAT_RUN_SUPERVISOR_PID);if(!(Number.isInteger(h)&&h>0)){try{h=Number(String(c(i)).trim())}catch{h=NaN}Number.isInteger(h)&&h>0&&d(h)||(h=NaN)}return Number.isInteger(h)&&h>0?{mode:"supervisor",pid:h}:m}function ai(e){try{process.kill(e,0)}catch{return!1}try{let t=ti("ps",["-p",String(e),"-o","command="],{encoding:"utf8",stdio:["ignore","pipe","ignore"]});return/bostrat-node|\/cli\.js|node-cli\.js/.test(t)}catch{return!1}}function Qr(e){switch(e?.mode){case"systemd":return`systemctl --user restart ${X(e.serviceName||"bostrat-node.service")}`;case"launchd":return`launchctl kickstart -k gui/${Number(e.uid)||0}/${e.serviceName||"com.bostrat.node"}`;case"supervisor":return`kill -${Zr.replace(/^SIG/,"")} ${Number(e.pid)}`;default:throw new Error(`self-update: unknown restart strategy "${e?.mode}"`)}}function eo({kind:e,to:t,from:n=_t,statusFile:o=vt,logFile:i=j.join(St,"last.log"),port:c=Number(process.env.PORT)||3362,npmPrefix:d=null,serviceName:m=process.platform==="darwin"?"com.bostrat.node":"bostrat-node.service",uid:h=typeof process.getuid=="function"?process.getuid():0,restart:x=mn({env:{},platform:process.platform,uid:h,serviceName:m,readFile:()=>{throw new Error("no pidfile")}}),liveDir:f=process.env.BOSTRAT_LIVE_CHECKOUT||j.resolve(j.dirname(new URL(import.meta.url).pathname),"..",".."),startedAt:T=Date.now(),attempts:$=1}={}){if(!bt.has(e))throw new Error(`self-update: install kind "${e}" is not actionable here`);if(!/^\d+\.\d+\.\d+$/.test(String(t)))throw new Error(`self-update: bad target version "${t}"`);if(n&&!/^\d+\.\d+\.\d+$/.test(String(n)))throw new Error(`self-update: bad current version "${n}"`);let S=(b,_="")=>`write_status ${X(b)} ${_}`,v=Qr(x),P=`
117
117
  health() {
118
118
  for i in $(seq 1 45); do