bostrat-node 0.2.23 → 1.6.0
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-loop +5 -0
- package/bin/bostrat-runbook +2 -1
- package/dist/cli.js +7 -3
- package/dist/index.js +165 -151
- package/package.json +1 -1
package/bin/bostrat-loop
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var d=process.env.BOSTRAT_AGENT_URL||`http://127.0.0.1:${process.env.BOSTRAT_AGENT_PORT||3362}`;function t(e){console.error(`[bostrat-loop] ${e}`),process.exit(1)}async function c(){let e=await fetch(`${d}/api/loops`),o=await e.json().catch(()=>({}));if(!e.ok)throw new Error(o.error||`HTTP ${e.status}`);return o.source==="unconfigured"&&t("this node isn't linked to a plane \u2014 loops live in the plane"),o.source==="unreachable"&&t(`plane unreachable (${o.error||"no detail"}) \u2014 try again shortly`),o.loops||[]}var r=e=>e?new Date(e).toISOString().slice(0,16).replace("T"," ")+"Z":"\u2014",g=e=>{let o=(e.runs||[])[e.runs.length-1];return o?`last ${o.error?`FAILED (${o.error})`:"ran"} ${r(o.firedAt)}`:"never ran"},p=process.argv.slice(2),i=p.shift();if(i==="ls")try{let e=await c();e.length||console.log("no loops defined");for(let o of e){let n=o.enabled?"\u25CF":"\u25CB",a=o.enabled?`next ${r(o.nextFireAt)}`:"disabled";console.log(`${n} ${o.name} [${o.schedule} UTC] ${a} ${g(o)} (loop:${o.id})`)}}catch(e){t(e.message)}else if(i==="show"){let e=p.find(o=>!o.startsWith("--"));e||t("usage: bostrat-loop show <name|id>");try{let n=(await c()).find(s=>s.id===e||s.name===e);n||t(`no loop matching "${e}" \u2014 use \`bostrat-loop ls\``);let a=typeof n.assignment=="object"&&n.assignment?n.assignment.provider||JSON.stringify(n.assignment):n.assignment||"\u2014";console.log(`${n.name} (loop:${n.id})`),console.log(` schedule: ${n.schedule} UTC ${n.enabled?`(next ${r(n.nextFireAt)})`:"(disabled)"}`),console.log(` provider: ${a}${n.repo?` repo: ${n.repo}`:""}`),n.trackId&&console.log(` track: ${n.trackId} (the loop's persistent "\u21BB" Track)`),console.log(` prompt:
|
|
3
|
+
${String(n.prompt||"").split(`
|
|
4
|
+
`).map(s=>` ${s}`).join(`
|
|
5
|
+
`)}`);let l=(n.runs||[]).slice(-5);console.log(` recent runs:${l.length?"":" none"}`);for(let s of l)console.log(` ${r(s.firedAt)} ${s.error?`FAILED \u2014 ${s.error}`:"ok"}${s.trackId?` (track ${s.trackId})`:""}`);console.log("Loops are operator-managed (Loops tab) \u2014 to change one, describe the change in chat.")}catch(o){t(o.message)}}else t("usage: bostrat-loop <ls|show> \u2026 (read-only; loops are operator-managed from the Loops tab)");
|
package/bin/bostrat-runbook
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import h from"node:fs/promises";var m=process.env.BOSTRAT_AGENT_URL||`http://127.0.0.1:${process.env.BOSTRAT_AGENT_PORT||3362}`;async function c(o,t,a){let e=await fetch(`${m}${t}`,{method:o,headers:a?{"content-type":"application/json"}:void 0,body:a?JSON.stringify(a):void 0}),n=await e.json().catch(()=>({}));if(!e.ok)throw new Error(n.error||`HTTP ${e.status}`);return n}function r(o){console.error(`[bostrat-runbook] ${o}`),process.exit(1)}async function u(o,{forWrite:t=null}={}){let{runbooks:a}=await c("GET","/api/runbooks"),e=a.find(n=>n.id===o||n.name===o);return e||r(`no runbook matching "${o}" \u2014 use \`bostrat-runbook ls\`, or \`add\` for a new one`),t&&e.remote&&r(`"${e.name}" was authored on node "${e.originNode||"unknown"}" \u2014 it is read-only here. Cannot ${t} it from this node; revise it from a Track on that node (say so in chat), never add a near-duplicate.`),e}function f(o,t){let a=o.version>1?` v${o.version}`:"";console.log(`[bostrat-runbook] ${t}: "${o.name}"${a} [${o.status}] (runbook:${o.id})`),console.log("The operator sees it as a card in the Track chat and in the Runbooks tab \u2014 no need to paste the steps into chat.")}var s=process.argv.slice(2),i=s.shift();if(i==="add"){let o=null,t=null;for(let a=0;a<s.length;a++)s[a]==="--name"?o=s[++a]:s[a].startsWith("--")||(t=s[a]);(!o||!t)&&r('usage: bostrat-runbook add --name "<name>" <file.md>');try{let a=await h.readFile(t,"utf8").catch(()=>r(`cannot read ${t}`)),{runbook:e}=await c("POST","/api/runbooks",{name:o,md:a,cwd:process.cwd()});f(e,"published")}catch(a){r(a.message)}}else if(i==="update"){let o=null,t=[];for(let n=0;n<s.length;n++)s[n]==="--note"?o=s[++n]:s[n].startsWith("--")||t.push(s[n]);let[a,e]=t;(!a||!e)&&r('usage: bostrat-runbook update <name|id> [--note "<what changed>"] <file.md>');try{let n=await u(a,{forWrite:"update"}),d=await h.readFile(e,"utf8").catch(()=>r(`cannot read ${e}`)),{runbook:l}=await c("POST",`/api/runbooks/${n.id}/update`,{md:d,...o?{note:o}:{}});f(l,"updated")}catch(n){r(n.message)}}else if(i==="done"||i==="reopen"){let o=s.find(t=>!t.startsWith("--"));o||r(`usage: bostrat-runbook ${i} <name|id>`);try{let t=await u(o,{forWrite:i}),{runbook:a}=await c("POST",`/api/runbooks/${t.id}/status`,{status:i==="done"?"done":"open"});f(a,i==="done"?"marked done":"reopened")}catch(t){r(t.message)}}else if(i==="ls"){let o=s.includes("--all");try{let t=o?"":`?cwd=${encodeURIComponent(process.cwd())}`,{runbooks:a,fleetDegraded:e}=await c("GET",`/api/runbooks${t}`);a.length||console.log(o?"no runbooks":"no runbooks on this Track (try --all)");for(let n of a){let d=n.version>1?` v${n.version}`:"",l=n.trackDeletedAt?" [orphaned]":"",b=n.remote?` [node:${n.originNode||"?"}]`:"";console.log(`${n.status.padEnd(5)} ${n.name}${d}${l}${b} (runbook:${n.id})`)}e&&console.log("(plane unreachable \u2014 showing this node only; other nodes' runbooks may exist)")}catch(t){r(t.message)}}else if(i==="show"){let o=null,t=[];for(let e=0;e<s.length;e++)s[e]==="--v"?o=s[++e]:s[e].startsWith("--")||t.push(s[e]);let a=t[0];a||r("usage: bostrat-runbook show <name|id> [--v N]");try{let e=await u(a),n=await fetch(`${m}/api/runbooks/${e.id}/md${o?`?v=${encodeURIComponent(o)}`:""}`);if(!n.ok){let l=await n.json().catch(()=>({}));throw new Error(l.error||`HTTP ${n.status}`)}let d=e.remote?` \u2014 authored on node "${e.originNode||"unknown"}", read-only here`:"";console.log(`[bostrat-runbook] "${e.name}" v${o||e.version} [${e.status}]${d} (runbook:${e.id})
|
|
3
|
+
`),console.log(await n.text())}catch(e){r(e.message)}}else if(i==="rm"){let o=s[0];o||r("usage: bostrat-runbook rm <name|id>");try{let t=await u(o,{forWrite:"delete"});await c("DELETE",`/api/runbooks/${t.id}`),console.log(`[bostrat-runbook] deleted: ${t.name}`)}catch(t){r(t.message)}}else r('usage: bostrat-runbook <add|update|done|reopen|ls|show|rm> \u2026 (add --name "<name>" <file.md>; update <name|id> <file.md>; show <name|id>)');
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,10 @@ var he=Object.defineProperty;var ye=(e,t)=>()=>(e&&(t=e(e=0)),t);var we=(e,t)=>{
|
|
|
4
4
|
# nothing inbound, ever. The node token lives in \`fly secrets\`, not here.
|
|
5
5
|
app = "${e}"
|
|
6
6
|
primary_region = "${t}"
|
|
7
|
+
# Swap absorbs agent-CLI memory spikes that would otherwise hang the whole
|
|
8
|
+
# guest (the 2026-08-13 hang class); much slower than RAM \u2014 scale memory for
|
|
9
|
+
# sustained multi-session use.
|
|
10
|
+
swap_size_mb = 1024
|
|
7
11
|
|
|
8
12
|
[build]
|
|
9
13
|
# The published node image (claude + cursor-agent + codex baked in, logged
|
|
@@ -67,11 +71,11 @@ Run it? (Y/n)`,"Y")).toLowerCase().startsWith("n"))return n.write(`aborted \u201
|
|
|
67
71
|
redeploy: fly deploy ${_} -c ${b.join(_,"fly.toml")} -a ${u} --ha=false
|
|
68
72
|
Vendor logins (Claude/Codex) are NOT set up here \u2014 you type those later, inside
|
|
69
73
|
boxes, and they land on this machine's volume, never on bostrat (box-auth-policy).
|
|
70
|
-
`),d(0)}var oe,We,z,j,ue=ye(()=>{oe="wss://plane.bostrat.ai/v1/node",We=ze(import.meta.url)("../package.json").version,z=`ghcr.io/globocodes/bostrat-node:${We}`;j=(e,{stdin:t,exec:n}={})=>new Promise((r,s)=>{if(n)return n(e,{stdin:t}).then(r,s);let o=Ke("fly",e,{timeout:6e5,maxBuffer:16*1024*1024},(i,a,d)=>i?s(Object.assign(i,{stdout:a,stderr:d})):r({stdout:a,stderr:d}));t!=null&&o.stdin?.end(t)})});import m from"node:fs";import l from"node:path";import O from"node:os";import{createRequire as Y}from"node:module";import{execFile as Ve,spawn as Je}from"node:child_process";import{promisify as Ze}from"node:util";import qe from"node:readline/promises";import{randomUUID as Qe}from"node:crypto";import{fileURLToPath as et}from"node:url";import N from"node:fs";import
|
|
74
|
+
`),d(0)}var oe,We,z,j,ue=ye(()=>{oe="wss://plane.bostrat.ai/v1/node",We=ze(import.meta.url)("../package.json").version,z=`ghcr.io/globocodes/bostrat-node:${We}`;j=(e,{stdin:t,exec:n}={})=>new Promise((r,s)=>{if(n)return n(e,{stdin:t}).then(r,s);let o=Ke("fly",e,{timeout:6e5,maxBuffer:16*1024*1024},(i,a,d)=>i?s(Object.assign(i,{stdout:a,stderr:d})):r({stdout:a,stderr:d}));t!=null&&o.stdin?.end(t)})});import m from"node:fs";import l from"node:path";import O from"node:os";import{createRequire as Y}from"node:module";import{execFile as Ve,spawn as Je}from"node:child_process";import{promisify as Ze}from"node:util";import qe from"node:readline/promises";import{randomUUID as Qe}from"node:crypto";import{fileURLToPath as et}from"node:url";import N from"node:fs";import L from"node:path";import ge from"node:os";var D=process.env.BOSTRAT_STATE_DIR||L.join(process.env.HOME||ge.tmpdir(),".bostrat"),A=()=>L.join(D,"node.env");function F(e){let t=String(e||"").trim();if(!t)return"";let n=/^[a-z][a-z0-9+.-]*:\/\//i.test(t)?t:`wss://${t}`,r;try{r=new URL(n)}catch{return t}let s={"https:":"wss:","http:":"ws:","wss:":"wss:","ws:":"ws:"}[r.protocol];return s?(r.protocol=s,(r.pathname===""||r.pathname==="/")&&(r.pathname="/v1/node"),r.toString()):t}function P(e){let t;try{t=N.readFileSync(e,"utf8")}catch{return{}}let n={};for(let r of t.split(`
|
|
71
75
|
`)){let s=r.trim();if(!s||s.startsWith("#"))continue;let o=/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(s);if(!o)continue;let i=o[2].trim();(i.startsWith('"')&&i.endsWith('"')||i.startsWith("'")&&i.endsWith("'"))&&(i=i.slice(1,-1)),n[o[1]]=i}return n}function X(e,t=process.env){let n=P(e);for(let[r,s]of Object.entries(n))r in t||(t[r]=s);return n}function V(e,t,{header:n=""}={}){let r=[];n&&r.push(...n.split(`
|
|
72
|
-
`).map(s=>s?`# ${s}`:"#"));for(let[s,o]of Object.entries(t))o==null||o===""||r.push(`${s}=${o}`);N.mkdirSync(
|
|
76
|
+
`).map(s=>s?`# ${s}`:"#"));for(let[s,o]of Object.entries(t))o==null||o===""||r.push(`${s}=${o}`);N.mkdirSync(L.dirname(e),{recursive:!0}),N.writeFileSync(e,r.join(`
|
|
73
77
|
`)+`
|
|
74
|
-
`,{mode:384}),N.chmodSync(e,384)}function J(e=process.platform){return e==="darwin"?{BOSTRAT_TMUX_BIN:"/opt/homebrew/bin/tmux",BOSTRAT_TMUX_SOCKET:`/tmp/tmux-${process.getuid?.()??501}/bostrat`,BOSTRAT_CHROMIUM_BIN:"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"}:{}}import{execFile as je}from"node:child_process";import{promisify as Ce}from"node:util";import Be from"node:fs";import ke from"node:os";import ve from"node:path";import{execFile as $e}from"node:child_process";import{promisify as Ne}from"node:util";import q from"node:path";import xe from"node:os";import{execFile as Ae}from"node:child_process";import{promisify as Oe}from"node:util";import M from"node:path";import{execFile as _e}from"node:child_process";import{promisify as Se}from"node:util";var Bt=Se(_e),Z=process.env.HOME||"/home/globo",be=M.join(process.env.BOSTRAT_STATE_DIR||M.join(Z,".bostrat"),"repos"),
|
|
78
|
+
`,{mode:384}),N.chmodSync(e,384)}function J(e=process.platform){return e==="darwin"?{BOSTRAT_TMUX_BIN:"/opt/homebrew/bin/tmux",BOSTRAT_TMUX_SOCKET:`/tmp/tmux-${process.getuid?.()??501}/bostrat`,BOSTRAT_CHROMIUM_BIN:"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"}:{}}import{execFile as je}from"node:child_process";import{promisify as Ce}from"node:util";import Be from"node:fs";import ke from"node:os";import ve from"node:path";import{execFile as $e}from"node:child_process";import{promisify as Ne}from"node:util";import q from"node:path";import xe from"node:os";import{execFile as Ae}from"node:child_process";import{promisify as Oe}from"node:util";import M from"node:path";import{execFile as _e}from"node:child_process";import{promisify as Se}from"node:util";var Bt=Se(_e),Z=process.env.HOME||"/home/globo",be=M.join(process.env.BOSTRAT_STATE_DIR||M.join(Z,".bostrat"),"repos"),Lt=[...(process.env.BOSTRAT_REPOS_ROOT||M.join(Z,"Documents")).split(","),be].map(e=>e.trim()).filter(Boolean),Dt=Number(process.env.BOSTRAT_REPOS_DEPTH)||3;var Wt=Oe(Ae),Ee=process.env.BOSTRAT_STATE_DIR||q.join(process.env.HOME||xe.tmpdir(),".bostrat"),Gt=q.join(Ee,"repos");var Xt=5*6e4;var Vt=10*6e4;var an=Ne($e),cn=process.env.BOSTRAT_STATE_DIR||ve.join(process.env.HOME||ke.tmpdir(),".bostrat");var ln=25*1024;var hn=60*6e4;import Pe from"node:os";import Q from"node:path";var Ie=process.env.BOSTRAT_DEBUG_DIR||Q.join(Pe.homedir(),".bostrat","debug"),Rn=Q.join(Ie,"handoff.jsonl"),kn=Number(process.env.BOSTRAT_HANDOFF_DEBUG_MAX)||500;var Le=Ce(je);var Vn=process.env.BOSTRAT_TMUX_BIN||"/usr/bin/tmux";function ne(e){return`/tmp/tmux-${e}/default`}function De({explicit:e,legacyExists:t,runtimeDir:n,uid:r}){if(e)return e;let s=ne(r);return t?s:n?`${n}/bostrat/tmux-default`:s}var ee=typeof process.getuid=="function"?process.getuid():0,Jn=De({explicit:process.env.BOSTRAT_TMUX_SOCKET,legacyExists:(()=>{try{return Be.existsSync(ne(ee))}catch{return!1}})(),runtimeDir:process.env.XDG_RUNTIME_DIR,uid:ee}),Zn=process.env.BOSTRAT_DEFAULT_CWD||process.env.HOME||"/",qn=process.env.BOSTRAT_AGENT_CMD||"claude --dangerously-skip-permissions";async function H(e,{loginShell:t=!1,failOpen:n=!1}={}){let r=await Me(e,{loginShell:t});return r===re?!0:r===U?!1:n}var re="present",U="absent",te="unknown",Fe=5e3;async function Me(e,{loginShell:t=!1,timeoutMs:n=Fe}={}){if(!e)return U;let r=t?process.env.SHELL||"/bin/sh":"sh",s=t?"-lic":"-c";try{return await Le(r,[s,'command -v "$1" >/dev/null 2>&1',"_",e],{timeout:n}),re}catch(o){return o?.killed||o?.signal?te:o?.code===1||o?.code===127?U:te}}var I={claude:"npm install -g @anthropic-ai/claude-code",codex:"npm install -g @openai/codex","cursor-agent":"curl https://cursor.com/install -fsS | bash",gh:"apt install gh (Debian: enable cli.github.com/packages) / brew install gh \u2014 see https://github.com/cli/cli#installation"};var Qn=Number(process.env.BOSTRAT_PROMPT_CAPTURE_COLS)||100,er=Number(process.env.BOSTRAT_PROMPT_CAPTURE_ROWS)||40,tr=Number(process.env.BOSTRAT_PROMPT_CAPTURE_LINES)||80,nr=Number(process.env.BOSTRAT_BOX_COLS)||120,rr=Number(process.env.BOSTRAT_BOX_ROWS)||40;var g=Ze(Ve),W=l.dirname(et(import.meta.url)),x=l.resolve(W,".."),tt=l.join(W,"index.js"),nt="wss://plane.bostrat.ai/v1/node",rt=Y(import.meta.url)("../package.json").version,p=(e,t)=>({name:e,level:"pass",detail:t}),w=(e,t,n)=>({name:e,level:"warn",detail:t,fix:n}),h=(e,t,n)=>({name:e,level:"fail",detail:t,fix:n});async function ot(){let[e,t]=process.versions.node.split(".").map(Number);return e>22||e===22&&t>=5?p("node runtime",`${process.version} at ${process.execPath}`):h("node runtime",`${process.version} < 22.5`,"install Node \u226522.5 via nvm and re-run under it")}function st(e){let t;try{t=m.statSync(e)}catch{return w("node.env",`${e} missing`,"run `bostrat-node init`")}let n=t.mode&511;return n&63?w("node.env",`${e} mode ${n.toString(8)} \u2014 group/world readable and it can hold a key`,`chmod 600 ${e}`):p("node.env",`${e} (0600)`)}async function it(e){let t=e.BOSTRAT_TMUX_BIN||"/usr/bin/tmux";try{let{stdout:n}=await g(t,["-V"],{timeout:5e3});return p("tmux",`${t} (${n.trim()})`)}catch{return h("tmux",`${t} not runnable`,process.platform==="darwin"?"brew install tmux (then BOSTRAT_TMUX_BIN=/opt/homebrew/bin/tmux)":"install tmux")}}async function de(e,{required:t=!1}={}){if(await H(e,{loginShell:!0,failOpen:!1}))return p(e,"resolves on the login-shell PATH");let r="not on the login-shell PATH \u2014 boxes for this agent (and handoffs to it) will be refused",s=I[e]?`${I[e]}${e==="cursor-agent"?" # installs to ~/.local/bin \u2014 ensure your login shell has it on PATH":""}`:`install ${e} so \`$SHELL -lic 'command -v ${e}'\` finds it`;return t?h(e,r,s):w(e,r,s)}async function at(){let e=process.env.SHELL||"/bin/sh";try{let{stdout:t}=await g(e,["-lic","gh --version"],{timeout:1e4}),n=String(t).match(/gh version [^\s]+/)?.[0]||"runs on the login-shell PATH";return p("gh (GitHub CLI)",n)}catch{return w("gh (GitHub CLI)","no real gh on the login-shell PATH (the bostrat gh shim alone doesn't count) \u2014 PR tab, ship offers, and Fix-CI are unavailable on this node",I.gh)}}async function ct(){let e=await Promise.all(["claude","codex","cursor-agent"].map(t=>de(t)));return e.some(t=>t.level==="pass")||e.push(h("agent CLIs","no agent CLI found at all \u2014 a node with nothing to drive","install at least one of claude / codex / cursor-agent")),e}async function lt(){let e=[...process.platform==="linux"?[["fuser","stopped views can't free their ports (they leak, and the port can't be reused)","apt install psmisc"]]:[],["pgrep","agent-swap can't detect an idle pane shell \u2014 swaps may misfire","apt install procps"],["npm","codex auto-update and `npm run` views are unavailable","install Node.js with npm on the service PATH"]];return Promise.all(e.map(async([t,n,r])=>await H(t,{failOpen:!1})?p(t,"resolves on the service PATH"):w(t,`not on the service PATH \u2014 ${n}`,r)))}async function ut(){try{let t=Y(l.join(x,"src","node-cli.js"))("node-pty");return await new Promise((n,r)=>{let s=t.spawn("/bin/echo",["ok"],{cols:20,rows:5}),o=setTimeout(()=>r(new Error("spawn timeout")),5e3);s.onExit(()=>{clearTimeout(o),n()})}),p("node-pty","loads and spawns under this runtime")}catch(e){let t=`cd ${x} && npm rebuild node-pty # with THIS node first on PATH \u2014 never the system npm`;try{let n=Y(l.join(x,"package.json")),r=l.join(l.dirname(n.resolve("node-pty/package.json")),"prebuilds",`${process.platform}-${process.arch}`,"spawn-helper");m.existsSync(r)&&!(m.statSync(r).mode&73)&&(t=`chmod +x ${r} # node-pty ships it non-executable`)}catch{}return h("node-pty",`load/spawn failed: ${e?.message||e}`,t)}}function dt(e){let t=e.BOSTRAT_CHROMIUM_BIN||"/usr/bin/chromium";return m.existsSync(t)?p("chromium (mocks)",t):w("chromium (mocks)",`${t} missing \u2014 mock rendering unavailable`,"install Chromium/Chrome and set BOSTRAT_CHROMIUM_BIN")}function pt(e){let t=e.BOSTRAT_CLAUDE_USE_API_KEY==="1";return t&&!e.ANTHROPIC_API_KEY?h("claude lane","BOSTRAT_CLAUDE_USE_API_KEY=1 but ANTHROPIC_API_KEY is empty","add the key to node.env or set the flag to 0"):p("claude lane",t?"BYO API key (injected per new box)":"box-local login (subscription \u2014 you type `claude login` inside the box)")}async function ft(e,{WebSocketImpl:t}={}){if(!e.BOSTRAT_PLANE_URL||!e.BOSTRAT_PLANE_TOKEN)return w("plane link","BOSTRAT_PLANE_URL/TOKEN unset \u2014 node runs pure M1 (local only)","run `bostrat-node init`");let n=t||(await import("ws")).default,r=Date.now(),s=F(e.BOSTRAT_PLANE_URL);return await new Promise(o=>{let i=new n(s,{headers:{authorization:`Bearer ${e.BOSTRAT_PLANE_TOKEN}`}}),a=f=>{clearTimeout(d);try{i.close()}catch{}o(f)},d=setTimeout(()=>a(h("plane link",`no hello_ok within 5s from ${s}`,"check the URL, the token, and the plane's IP gate")),5e3);i.on("open",()=>i.send(JSON.stringify({v:1,ch:"ctl",type:"hello",nodeId:`doctor-${Qe().slice(0,8)}`}))),i.on("message",f=>{try{let u=JSON.parse(f);u.type==="hello_ok"?a(p("plane link",`hello_ok in ${Date.now()-r}ms (${s})`)):u.type==="error"&&a(h("plane link",`plane said: ${u.code} ${u.message||""}`,"check the token"))}catch{}}),i.on("error",f=>a(h("plane link",`${f?.message||f}`,"check the URL, the token, and the plane's IP gate")))})}function mt(){let e;try{e=JSON.parse(m.readFileSync(l.join(D,"content-outbox.json"),"utf8"))}catch{return p("content outbox","empty \u2014 every deliverable has reached the plane")}let t=e.ops?.length||0,n=e.parked?.length||0;if(n){let r=e.parked[0];return h("content outbox",`${n} op(s) PARKED \u2014 the plane refuses them (e.g. ${r?.op} ${r?.family}/${r?.id}: ${r?.error})`,"fix the plane-side cause; parked ops re-drive on their own once it accepts them")}return t>20?w("content outbox",`${t} ops queued \u2014 the plane is unreachable, slow, or refusing the op at the head`,"check `plane link` above and the agent log for a [content-plane] ALARM; the queue drains on its own once the plane accepts again"):p("content outbox",t?`${t} op(s) in flight`:"drained")}function ht(e){return e.BOSTRAT_STATE_REMOTE?p("state remote",`${e.BOSTRAT_STATE_REMOTE} (this node's own \u2014 never another node's repo)`):w("state remote","unset \u2014 state syncs to a local repo only, no off-box durability","create a per-node private repo and set BOSTRAT_STATE_REMOTE")}async function pe(e,t={}){let n=[];return n.push(await ot()),n.push(st(t.envFile||A())),n.push(await it(e)),n.push(...await ct()),n.push(await de("git",{required:!0})),n.push(await at()),n.push(...await lt()),n.push(await ut()),n.push(dt(e)),n.push(pt(e)),n.push(await ft(e,t)),n.push(mt()),n.push(ht(e)),n}function fe(e,t=console.log){let n={pass:"\u2713",warn:"!",fail:"\u2717"};for(let o of e)t(`${n[o.level]} ${o.name}: ${o.detail}`),o.fix&&o.level!=="pass"&&t(` fix: ${o.fix}`);let r=e.filter(o=>o.level==="fail").length,s=e.filter(o=>o.level==="warn").length;return t(r?`
|
|
75
79
|
FAIL \u2014 ${r} blocking, ${s} warnings`:`
|
|
76
80
|
OK \u2014 0 blocking, ${s} warnings`),r===0}async function yt({envFile:e=A(),input:t=process.stdin,output:n=process.stdout}={}){let r=P(e),s=qe.createInterface({input:t,output:n}),o=async(S,c)=>(await s.question(c?`${S} [${c}]: `:`${S}: `)).trim()||c||"";n.write(`bostrat-node init \u2014 plane link + machine config. No vendor logins here:
|
|
77
81
|
you type those inside each box, later (box-auth-policy).
|