bostrat-node 1.16.7 → 1.16.8
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-audit +6 -6
- package/bin/bostrat-blueprint +1 -1
- package/bin/bostrat-mcp +12 -0
- package/dist/cli.js +75 -75
- package/dist/index.js +332 -317
- package/package.json +1 -1
package/bin/bostrat-audit
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import ee from"node:fs/promises";import No from"node:os";import F from"node:path";var ht=(e,o)=>{if(e==null||e==="")return o;let t=Number(e);return Number.isFinite(t)&&t>=0?t:o},jo=ht(process.env.BOSTRAT_HOOK_FOLD_MS,250);var mt=1e3;function U(e){let o=Number(e?.count);return Number.isInteger(o)&&o>1&&e?.tools&&typeof e.tools=="object"?Math.min(o,mt):1}function v(e){let o=e||{},t=U(o);if(t===1)return[o];let n=(Array.isArray(o.calls)?o.calls:[]).filter(r=>r&&typeof r=="object").slice(0,t),s={};for(let r of n)r.tool&&(s[r.tool]=(s[r.tool]||0)+1);for(let[r,c]of Object.entries(o.tools))for(let u=s[r]||0;u<Number(c)&&n.length<t;u++)n.push({tool:r});for(;n.length<t;)n.push({tool:null});return n}import{createHash as gt}from"node:crypto";var bt=new Set(["userId"]);function se(e){return e===null||typeof e!="object"?e===void 0||typeof e=="number"&&!Number.isFinite(e)?"null":JSON.stringify(e):Array.isArray(e)?`[${e.map(t=>se(t===void 0?null:t)).join(",")}]`:`{${Object.keys(e).filter(t=>e[t]!==void 0).sort().map(t=>`${JSON.stringify(t)}:${se(e[t])}`).join(",")}}`}function _e(e){let o={};for(let t of Object.keys(e))bt.has(t)||(o[t]=e[t]);return gt("sha256").update(se(o)).digest("hex")}var Te=e=>Number.isFinite(e?.seq);function Ie(e){let o={members:0,chained:0,preChain:0,intactFrom:null,intactTo:null,genesis:!1,breaks:[]},t=null,n=!1,s=(r,c)=>{t={seq:r.seq,hash:c},o.intactFrom=r.seq,o.genesis=r.seq===1&&r.prev===null};for(let r of e||[]){if(!Te(r))continue;if(o.members++,r.prev===void 0){t&&!n?(o.breaks.push({kind:"unchained",seq:r.seq,eventId:r.id??null}),n=!0):t||o.preChain++;continue}o.chained++;let c=_e(r);if(!t||n){n=!1,s(r,c);continue}if(r.seq===t.seq+1){if(r.prev===t.hash){t={seq:r.seq,hash:c};continue}o.breaks.push({kind:"prev-mismatch",seq:r.seq,eventId:r.id??null,expected:t.hash,got:r.prev})}else r.seq>t.seq+1?o.breaks.push({kind:"seq-gap",seq:r.seq,eventId:r.id??null,from:t.seq+1,to:r.seq-1}):o.breaks.push({kind:"seq-repeat",seq:r.seq,eventId:r.id??null,headSeq:t.seq});s(r,c)}return o.intactTo=t?t.seq:null,t||(o.intactFrom=null),o}var D=6e4,yt=120*D,Oe=2e3,Ae=5e3,wt=2*D,kt=5e3,St=2*D,G=5e3,$t={Workflow:360*D,Agent:60*D},xt=5*D,Et=10*D,ie=80,j=3,_t=["Checkpoint: write a work-in-progress handoff","Resume from .bostrat/handoff/latest.md","Beat: write a","You are a SUBTASK agent working in an isolated","A subtask box you delegated has finished"],ce={operator:"strong",orchestrator:"strong","card-answer":"strong",loop:"strong","background-job":"strong","task-continuation":"strong","operator-terminal":"weak","orchestrator-unlogged":"weak","background-launch":"weak","task-continuation-unlogged":"weak",peer:"flagged","nested-session":"flagged","unknown-prompt":"flagged",unrooted:"flagged"},N=e=>String(e??"").replace(/\s+/g," ").trim(),h=e=>e?.ts??0,le=(e,o)=>!!e&&!!o&&(e.startsWith(o.slice(0,ie))||o.startsWith(e.slice(0,ie))),Tt=6,It=(e,o)=>le(e,o)||e.length>=Tt&&o.includes(e.slice(0,ie)),Ot=/^<pasted_content\b[^>]*>\s*/i,ae=e=>N(e.payload?.promptHead??e.payload?.text).replace(Ot,""),At=/^<cross-session-message\b/i,vt=/^<task-notification>/i,Nt=/<tool-use-id>\s*([^<\s]+)\s*<\/tool-use-id>/i;function ve(e){let o=e.payload||{},t=U(o),n=v(o)[0],s=n.head||n.path||(o.command?N(o.command):""),r=e.type==="tool.failed"?" [failed]":e.type==="tool.started"?" [never completed]":"";return`${n.tool||"tool"}${s?`: ${s.slice(0,100)}`:""}${r}${t>1?` (+${t-1} more)`:""}`}var Rt=/\.answered$/,jt=new Set(["loop.created","followup.created"]),Dt=16,je=new Set(["tool.started","tool.called","tool.failed"]);function Mt(){return{byId:new Map,byDelivery:new Map,byPrompt:new Map,byToolUse:new Map,jobs:new Map}}function Pt(e,o){if(!o||typeof o.type!="string")return;o.id&&e.byId.set(o.id,o);let t=o.payload||{};if(o.type==="turn.user")o.source==="orchestrator"&&t.deliveryId&&e.byDelivery.set(t.deliveryId,o),o.source==="hook"&&t.promptId&&!e.byPrompt.has(`${o.boxId}\0${t.promptId}`)&&e.byPrompt.set(`${o.boxId}\0${t.promptId}`,o);else if(o.type==="job.started"&&t.id)e.jobs.set(t.id,o);else if(je.has(o.type)&&o.source==="hook")for(let n of v(t))n.toolUseId&&!e.byToolUse.has(n.toolUseId)&&e.byToolUse.set(n.toolUseId,o)}function Ut(e){let o=Mt();for(let t of e)Pt(o,t);return o}function Ne(e,o){let t=e;for(let n=0;t&&n<Dt;n++){let s=t.payload||{},r=t.type;if(t.causationId&&o.byId.has(t.causationId)&&t.causationId!==t.id){t=o.byId.get(t.causationId);continue}if(je.has(r)){let c=s.promptId?o.byPrompt.get(`${t.boxId}\0${s.promptId}`):null;if(!c)return null;t=c;continue}if(r==="turn.user"){if(t.source==="hook"){if(s.kind==="task-notification"&&s.jobId){let u=o.jobs.get(s.jobId);if(!u)return null;t=u;continue}let c=s.deliveryId?o.byDelivery.get(s.deliveryId):null;if(!c)return null;t=c;continue}return t.source==="orchestrator"?s.loopId||s.producer==="loop"?{origin:"loop",root:t}:{origin:t.actor==="orchestrator"?"orchestrator":"operator",root:t}:t.source==="transcript"?{origin:"operator-terminal",root:t}:null}if(r==="job.started"){let c=s.id?o.byToolUse.get(s.id):null;if(!c)return null;t=c;continue}return Rt.test(r)||jt.has(r)?{origin:"card-answer",root:t}:r==="peer.message"?{origin:"peer",root:t}:null}return null}function Ct(e){let o={events:e.length,firstSeq:null,lastSeq:null,headMissing:!1,gaps:0,repeats:0},t=null;for(let s of e)Number.isFinite(s?.seq)&&(t===null?o.firstSeq=s.seq:s.seq>t+1?o.gaps++:s.seq<=t&&o.repeats++,t=s.seq);o.lastSeq=t,o.headMissing=o.firstSeq!==null&&o.firstSeq>1;let n=Ie(e);return o.chain={state:n.chained===0?"pre-chain":n.breaks.length?"broken":n.genesis?"intact":"intact-from-mid-log",chained:n.chained,preChain:n.preChain,intactFrom:n.intactFrom,intactTo:n.intactTo,breaks:n.breaks},o}var Ft=e=>{switch(e.kind){case"prev-mismatch":return`seq ${e.seq} states a predecessor that is not the line before it \u2014 the line before it changed, or was replaced`;case"seq-gap":return`seq ${e.from}${e.to!==e.from?`\u2013${e.to}`:""} missing between chained lines`;case"seq-repeat":return`seq ${e.seq} appears again after the chain passed it (a line was inserted, or the log was rewound)`;case"unchained":return`seq ${e.seq} carries no chain link after chained lines (an older node resumed this log)`;default:return`${e.kind} at seq ${e.seq}`}};function Bt(e){let o=new Map,t=new Map,n=new Map,s=new Map,r=(i,f,m)=>{i.has(f)||i.set(f,[]),i.get(f).push(m)};for(let i of e)if((i.type==="box.state.changed"||i.type.startsWith("handoff."))&&r(s,i.payload?.box||i.boxId||null,h(i)),i.type==="session.ready"&&([...s.get(i.boxId)||[],...s.get(null)||[]].some(m=>m<=h(i)&&h(i)-m<=St)||r(n,i.boxId,h(i))),i.type==="turn.user")if(i.source==="orchestrator"){let f=(i.payload?.attachments||[]).map(m=>m?.id).filter(Boolean);r(o,i.boxId,{ts:h(i),head:N(i.payload?.text),files:f,actor:i.actor,usedAt:null})}else i.source==="transcript"&&r(t,i.boxId,{ts:h(i),head:N(i.payload?.text),note:i.payload?.note?.kind||null});let c=new Map,u=new Map,l=new Map;for(let i of e){if(i.type==="turn.user"&&i.source==="orchestrator"&&u.set(i.boxId,!0),i.source!=="hook")continue;if(i.type==="turn.agent"){let k=l.get(i.boxId)||0;k>0?l.set(i.boxId,k-1):u.set(i.boxId,!1);continue}if(i.type!=="turn.user")continue;let f=ae(i),m=(()=>{if(i.actor==="peer"||At.test(f))return{origin:"peer"};if(vt.test(f))return{origin:"task-continuation",jobId:f.match(Nt)?.[1]||null};let k=(o.get(i.boxId)||[]).filter(b=>(b.usedAt===null||h(i)-b.usedAt<=Ae)&&b.ts>=h(i)-yt&&b.ts<=h(i)+Oe&&(It(b.head,f)||!b.head&&b.files.length>0&&(b.files.some(A=>f.includes(`.bostrat/uploads/${A}`))||h(i)-b.ts<=Ae))),y=k[k.length-1];if(y)return y.usedAt=h(i),{origin:y.actor==="orchestrator"?"orchestrator":"operator"};if(_t.some(b=>f.startsWith(b)))return{origin:"orchestrator-unlogged"};if((t.get(i.boxId)||[]).find(b=>!b.note&&b.ts>=h(i)-Oe&&b.ts<=h(i)+wt&&le(b.head,f)))return{origin:"operator-terminal"};let M=(n.get(i.boxId)||[]).some(b=>b<=h(i)&&h(i)-b<=kt);return{origin:u.get(i.boxId)||M?"nested-session":"unknown-prompt"}})();c.set(i,m),m.origin==="nested-session"?l.set(i.boxId,(l.get(i.boxId)||0)+1):u.set(i.boxId,!0)}return c}function Lt(e){let o=new Map,t=new Map;for(let n of e){let s=n.payload?.id;if(s)if(n.type==="job.started"){let r={from:(n.payload?.ts??h(n))-G,to:1/0,loggedAt:h(n)};t.set(s,r),o.has(n.boxId)||o.set(n.boxId,[]),o.get(n.boxId).push(r)}else n.type==="job.stopped"&&t.has(s)&&(t.get(s).to=h(n)+G)}return{byBox:o,byId:t}}function Ht(e,{trackId:o=null,name:t=null,since:n=0}={}){let s=Bt(e),r=Lt(e),c=new Set;for(let a of e)a.type==="tool.called"&&a.source==="hook"&&c.add(a.boxId);let u=a=>a.type==="tool.called"&&(a.source==="hook"||!(a.provider==="claude"&&c.has(a.boxId)))||a.type==="tool.failed"&&a.source==="hook",l=new Set,i=new Map;for(let a of e)if(a.source==="hook")if(a.type==="tool.called"||a.type==="tool.failed")for(let d of v(a.payload))d.toolUseId&&l.add(d.toolUseId);else a.type==="audit.flagged"&&a.payload?.denied===!0&&a.payload.toolUseId?l.add(a.payload.toolUseId):(a.type==="turn.agent"||a.type==="session.ready"||a.type==="turn.aborted"||a.type==="turn.interrupted")&&(i.has(a.boxId)||i.set(a.boxId,[]),i.get(a.boxId).push(h(a)));let f=e.length?h(e[e.length-1]):0,m=a=>{if(a.type!=="tool.started"||a.source!=="hook")return null;let d=v(a.payload).filter(S=>S.toolUseId&&!l.has(S.toolUseId));return d.length&&((i.get(a.boxId)||[]).some(S=>S>h(a))||f-h(a)>Et)?d:null},k={},y={},$={keyed:0,timing:0},M=Ut(e),b=new Map,A=(a,d,p,S,x,g=1)=>{let B=`${a}\0${p.boxId}\0${S}`,_=b.get(B);return(!_||h(p)-_.to>xt)&&(_={rule:a,severity:d,trackId:o,track:t,boxId:p.boxId,from:h(p),to:h(p),count:0,note:x,samples:[]},b.set(B,_),W.push(_)),_.to=h(p),_.count+=g,_},W=[],ne=new Map,ft=a=>(ne.has(a)||ne.set(a,{open:!1,origin:null,nested:0,soft:null,lastSendHead:null,launch:null}),ne.get(a));for(let a of e){let d=ft(a.boxId),p=h(a)>=n;if(a.type==="session.foreign"){if(a.payload?.phase==="prompted"&&p){let w=A("nested-session","medium",a,"","a session that is not the box's own was prompted through this box \u2014 typically an agent-launched `claude -p`");w.samples.length<j&&w.samples.push(N(a.payload?.promptHead).slice(0,100))}continue}if(a.type==="turn.user"&&a.source==="hook"){let w=s.get(a);if(w.origin==="nested-session"){if(d.nested++,p){let P=A("nested-session","medium",a,"","a prompt no recorded send accounts for arrived mid-turn or right after a SessionStart \u2014 typically an agent-launched `claude -p`");P.samples.length<j&&P.samples.push(ae(a).slice(0,100))}continue}if(d.open=!0,d.origin=w.origin,w.origin==="task-continuation"&&!r.byId.has(w.jobId)&&(d.origin="task-continuation-unlogged"),d.origin==="unknown-prompt"&&p){let P=A("unknown-prompt","medium",a,"","a turn opened by a prompt with no operator, machinery or job record behind it");P.samples.length<j&&P.samples.push(ae(a).slice(0,100))}}else if(a.type==="turn.agent"&&a.source==="hook")d.nested>0?d.nested--:d.open=!1;else if(a.type==="turn.aborted"||a.type==="turn.interrupted")d.open=!1;else if(a.type==="turn.user"&&a.source==="orchestrator")d.lastSendHead=N(a.payload?.text),d.soft=a.actor==="orchestrator"?"orchestrator":"operator",d.open=!0,d.origin=d.soft;else if(a.type==="turn.user"&&a.source==="transcript"){let w=N(a.payload?.text);!a.payload?.note&&!le(w,d.lastSendHead)&&(d.soft="operator-terminal")}else a.type==="peer.message"&&(d.soft="peer");let S=m(a);if(!S&&!u(a))continue;let x=S?S.length:a.source==="hook"?U(a.payload):1,g;if(a.payload?.foreignSession)g="nested-session";else if(a.source==="hook"){let w=(r.byBox.get(a.boxId)||[]).some(R=>h(a)>=R.from&&h(a)<=R.to);d.open?g=d.origin:w?g="background-job":d.nested>0?g="nested-session":d.launch&&h(a)<=d.launch?g="background-launch":g="unrooted";let P=(r.byBox.get(a.boxId)||[]).some(R=>R.loggedAt<=h(a)+G);for(let R of new Set(v(a.payload).map(Ee=>Ee.tool)))(R==="Workflow"||R==="Agent"&&!P)&&d.open&&ce[d.origin]!=="flagged"&&(d.launch=Math.max(d.launch||0,h(a)+$t[R]))}else g=d.soft||"unrooted";let B=a.source==="hook"&&!a.payload?.foreignSession?Ne(a,M):null;if(B&&(g=B.origin),!p)continue;$[B?"keyed":"timing"]+=x,k[g]=(k[g]||0)+x;let _=a.provider||"unknown";if(y[_]||={actions:0,fidelity:a.source==="hook"?"hook":"transcript",identified:!1},y[_].actions+=x,a.source==="hook"&&(y[_].fidelity="hook"),a.source==="hook"&&v(a.payload).some(w=>w.toolUseId)&&(y[_].identified=!0),S){let w=A("unfinished-calls","low",a,"","calls the CLI began (PreToolUse) and never reported complete or failed \u2014 killed, crashed or restarted mid-call; the tool may have run",x);w.samples.length<j&&w.samples.push(ve(a))}if(g==="unrooted"){let w=A("unrooted-actions","high",a,"","tool calls with no open turn, no recorded job and no launch that accounts for them",x);w.samples.length<j&&w.samples.push(ve(a))}else if(g==="peer")A("peer-driven-actions","low",a,"","tool calls in a turn another session's message opened",x);else if(g==="nested-session"||g==="unknown-prompt"){let w=b.get(`${g}\0${a.boxId}\0`);w&&(w.actions=(w.actions||0)+x)}}let K={total:0,keyed:0,timing:0,unrooted:0};{let a=new Map,d=new Map;for(let p of e)p.source==="hook"&&(p.type==="turn.user"?d.set(p.boxId,h(p)):(p.type==="turn.agent"||p.type==="turn.aborted"||p.type==="turn.interrupted")&&d.has(p.boxId)&&((a.get(p.boxId)||a.set(p.boxId,[]).get(p.boxId)).push([d.get(p.boxId),h(p)]),d.delete(p.boxId)));for(let[p,S]of d)(a.get(p)||a.set(p,[]).get(p)).push([S,1/0]);for(let p of e){if(p.type!=="code.committed"||p.source!=="hook"||h(p)<n)continue;K.total++;let S=Number(p.payload?.ts??p.ts)||h(p);if(Ne(p,M))K.keyed++;else if((a.get(p.boxId)||[]).some(([x,g])=>S>=x-G&&S<=g+G))K.timing++;else{K.unrooted++;let x=A("unrooted-commit","medium",p,"","a commit landed in the box's workspace with no turn open and no recorded cause");x.samples.length<j&&x.samples.push(`${String(p.payload?.sha||"").slice(0,7)} ${N(p.payload?.subject).slice(0,80)}`)}}}{let a=e.filter(d=>d.type==="audit.flagged"&&d.source!=="plane"&&d.payload?.denied===!0&&h(d)>=n);if(a.length){let d=[...new Set(a.map(p=>p.payload?.rule).filter(Boolean))];W.push({rule:"guard-denied",severity:"high",trackId:o,track:t,boxId:a[0].boxId??null,from:h(a[0]),to:h(a.at(-1)),count:a.length,samples:a.slice(0,j).map(p=>`${p.payload?.rule||"?"}: ${N(p.payload?.samples?.[0]||p.payload?.tool||"")}`.slice(0,160)),note:`${a.length} call(s) denied before running by the node's PreToolUse guard (${d.join(", ")}) \u2014 BOSTRAT_AUDIT_GUARD is on for this node`})}}let O=Ct(e),xe=Object.values(k).reduce((a,d)=>a+d,0);if(O.headMissing&&xe&&W.push({rule:"log-head-missing",severity:"medium",trackId:o,track:t,boxId:null,from:h(e[0]),to:h(e[0]),count:1,samples:[],note:`the log starts at seq ${O.firstSeq} \u2014 its earlier history is not on this node`}),O.chain.breaks.length){let a=new Map;for(let g of e)Number.isFinite(g?.seq)&&!a.has(g.seq)&&a.set(g.seq,g);let d=O.chain.breaks.filter(g=>g.kind==="prev-mismatch"||g.kind==="seq-repeat"),p=g=>h(a.get(g.seq))||h(e.at(-1)),S=O.chain.breaks[0],x=O.chain.breaks.at(-1);W.push({rule:"chain-broken",severity:d.length?"high":"medium",trackId:o,track:t,boxId:null,from:p(S),to:p(x),count:O.chain.breaks.length,samples:O.chain.breaks.slice(0,j).map(Ft),note:`the log's hash chain breaks ${O.chain.breaks.length} time(s) on this node \u2014 advisory: the plane's verified head is the anchor (GET /plane/admin/chain/${o??"<track>"})`})}return{trackId:o,track:t,actions:xe,byOrigin:k,byEvidence:$,byProvider:y,commits:K,findings:W,integrity:O}}var Re={high:0,medium:1,low:2};function De(e,{days:o=14,now:t=Date.now()}={}){let n=o?t-o*864e5:0,s={days:o||null,tracks:0,actions:0,byOrigin:{},byStrength:{strong:0,weak:0,flagged:0},byEvidence:{keyed:0,timing:0},commits:{total:0,keyed:0,timing:0,unrooted:0},byProvider:{},findings:[],integrity:{logs:0,headMissing:0,withGaps:0,withRepeats:0,chain:{intact:0,intactFromMidLog:0,broken:0,preChain:0}}};for(let r of e||[]){let c=Ht(r.events||[],{trackId:r.trackId,name:r.name,since:n});s.integrity.logs++,c.integrity.headMissing&&s.integrity.headMissing++,c.integrity.gaps&&s.integrity.withGaps++,c.integrity.repeats&&s.integrity.withRepeats++;let u=c.integrity.chain?.state;if(u==="intact"?s.integrity.chain.intact++:u==="intact-from-mid-log"?s.integrity.chain.intactFromMidLog++:u==="broken"?s.integrity.chain.broken++:s.integrity.chain.preChain++,!(!c.actions&&!c.findings.length)){s.tracks++,s.actions+=c.actions;for(let l of Object.keys(s.byEvidence))s.byEvidence[l]+=c.byEvidence?.[l]||0;for(let l of Object.keys(s.commits))s.commits[l]+=c.commits?.[l]||0;for(let[l,i]of Object.entries(c.byOrigin))s.byOrigin[l]=(s.byOrigin[l]||0)+i,s.byStrength[ce[l]||"flagged"]+=i;for(let[l,i]of Object.entries(c.byProvider))s.byProvider[l]||={actions:0,fidelity:i.fidelity,identified:!1},s.byProvider[l].actions+=i.actions,i.fidelity==="hook"&&(s.byProvider[l].fidelity="hook"),i.identified&&(s.byProvider[l].identified=!0);s.findings.push(...c.findings)}}return s.findings.sort((r,c)=>Re[r.severity]-Re[c.severity]||c.to-r.to),s.ok=!s.findings.some(r=>r.severity==="high"),s}var z=(e,o)=>o?`${(e/o*100).toFixed(e&&e/o<.001?2:1)}%`:"0%",re=e=>new Date(e).toISOString().slice(0,16).replace("T"," ");function Me(e,{limit:o=20,containment:t=null}={}){let n=[];n.push(`AGENT ACTION AUDIT \xB7 ${e.days?`last ${e.days}d`:"all history"} \xB7 ${e.actions} action(s) across ${e.tracks} Track(s)`),n.push(""),n.push("Who asked for it");for(let l of["strong","weak","flagged"]){let i=Object.entries(e.byOrigin).filter(([f])=>(ce[f]||"flagged")===l).sort((f,m)=>m[1]-f[1]);if(i.length){n.push(` ${l.toUpperCase()} \xB7 ${e.byStrength[l]} (${z(e.byStrength[l],e.actions)})`);for(let[f,m]of i)n.push(` ${f.padEnd(27)} ${String(m).padStart(7)} ${z(m,e.actions)}`)}}n.push("");let s=e.byEvidence||{keyed:0,timing:0};n.push(`Rooted by recorded key \xB7 ${s.keyed} (${z(s.keyed,e.actions)}) \xB7 by timing \xB7 ${s.timing} (${z(s.timing,e.actions)})`);let r=e.commits||{total:0,keyed:0,timing:0,unrooted:0};r.total&&n.push(`Commits \xB7 ${r.total}: ${r.keyed} cite their turn, ${r.timing} inside an open turn, ${r.unrooted} unrooted`),n.push(""),n.push("Evidence by provider");for(let[l,i]of Object.entries(e.byProvider).sort((f,m)=>m[1].actions-f[1].actions))n.push(` ${l.padEnd(10)} ${String(i.actions).padStart(7)} `+(i.fidelity==="hook"?`hook-recorded (turn boundaries known${i.identified?"; calls identified, intent and outcome paired":""})`:"transcript only (agent-written; no turn boundaries)"));n.push("");let c=e.integrity,u=c.chain||{intact:0,intactFromMidLog:0,broken:0,preChain:0};if(n.push(`Log integrity \xB7 ${c.logs} log(s): hash chain intact on ${u.intact} from seq 1 and ${u.intactFromMidLog} from mid-log, broken on ${u.broken}, ${u.preChain} pre-chain (written before the chain); ${c.headMissing} missing their head, ${c.withGaps} with seq gaps, ${c.withRepeats} with repeated seq`),n.push(" (the chain is walked over this node's copy \u2014 advisory; the plane's verified head is the anchor)"),n.push(""),!e.findings.length)n.push("Findings: none.");else{n.push(`Findings \xB7 ${e.findings.length}`);for(let l of e.findings.slice(0,o)){let i=l.to-l.from>D?`${re(l.from)} \u2192 ${re(l.to).slice(11)}`:re(l.from),f=l.actions?`${l.count} prompt(s), ${l.actions} action(s)`:`${l.count}`;n.push(` [${l.severity}] ${l.rule} \xB7 ${l.track||l.trackId}${l.boxId?` \xB7 ${l.boxId}`:""} \xB7 ${i} \xB7 ${f}`),n.push(` ${l.note}`);for(let m of l.samples)n.push(` - ${m}`)}e.findings.length>o&&n.push(` \u2026 ${e.findings.length-o} more (--json for all)`)}return n.push(""),n.push(e.ok?`No action without an origin. ${t||"The log is agent-writable, so this is evidence, not proof."}`:`Actions with NO origin were found \u2014 read the [high] findings above.${t?` ${t}`:""}`),n.join(`
|
|
3
|
-
`)}import
|
|
4
|
-
`).filter(o=>o&&!o.startsWith("#")).map(o=>o.split(":"))}catch{return[]}}function
|
|
5
|
-
`)[0].slice(0,160);async function Zt(){try{return await fe("sudo",["-n","true"],{timeout:3e3}),!0}catch{return!1}}async function Qt(e=process.env,{ask:o=!0}={}){let t=e.BOSTRAT_AUDIT_LOG||"/var/log/audit/audit.log",n=!1;try{await Ce.access(t,L.constants.R_OK),n=!0}catch{}let s=e.BOSTRAT_AUDIT_RULES_FILE||"/etc/audit/rules.d/bostrat-box.rules",r=L.existsSync(s),c=null;if(o&&r){let u=Y()===0?["auditctl",["-l"]]:["sudo",["-n","auditctl","-l"]];try{let{stdout:l}=await fe(u[0],u[1],{timeout:5e3});c=l.includes(`key=${Wt}`)}catch{c=null}}else r||(c=!1);return{log:t,readable:n,configured:r,rule:c,rulesFile:s}}function Ge(e){return e.level==="uid"&&!e.problems.length?`Boxes on this node run as ${e.user}, which holds no route to root and cannot write the event log or its hook settings${e.auditd?.rule===!0&&e.auditd?.readable?"; its every exec is logged by auditd and shipped to the plane":""}.`:e.level==="uid"?`Boxes on this node run as ${e.user}, but containment has gaps (${e.problems.join("; ")}) \u2014 treat this record as evidence, not proof.`:e.level==="root-capable"?`Boxes on this node are root-capable (${e.user}: ${e.routes.join(", ")}), so a root-capable agent can defeat node-side evidence \u2014 this is evidence, not proof.`:e.level==="misconfigured"?`Box containment is configured but unusable (${e.error}); no box can start until it is fixed.`:`Boxes on this node share the node's uid (${e.user}), so an agent can rewrite its own log \u2014 this is evidence, not proof.`}var eo="BP-AU-R4",ot=160,Ve={high:0,medium:1,low:2},Xe=200,ze=5,Ye=8,Je=5,Ze=10*6e4,to=Object.freeze(["localhost","127.0.0.1","0.0.0.0","github.com","api.github.com","raw.githubusercontent.com","objects.githubusercontent.com","registry.npmjs.org","pypi.org","files.pythonhosted.org"]),oo=5e3,ge=new Set(["tool.called","tool.failed"]),he=new Set(["Write","Edit","NotebookEdit"]),no=e=>String(e??"").replace(/\s+/g," ").trim(),C=(e,o=120)=>no(e).slice(0,o);function so(e,o=ot){let t=String(e??""),n=t.split(/\s*(?:;|&&|\|\||\||\n)\s*/).map(s=>s.trim()).filter(Boolean);return Number.isFinite(o)&&t.length>=o&&n.length&&n.pop(),n}function Q(e){return String(e??"").replace(/\d?>>?\s*\S+|<\s*\S+|2>&1/g," ").trim().split(/\s+/).filter(Boolean)}function nt(e){let o=0;for(;o<e.length&&/^[A-Za-z_][A-Za-z0-9_]*=/.test(e[o]);)o++;return{cmd:e[o]??null,rest:e.slice(o+1),at:o}}function Qe(e){let o=Q(e),t=o.findIndex(i=>i==="git"||i.endsWith("/git"));if(t<0)return null;for(t++;t<o.length&&o[t].startsWith("-");)(o[t]==="-C"||o[t]==="-c")&&t++,t++;if(o[t]!=="push")return null;let n=[],s=[];for(let i=t+1;i<o.length;i++){let f=o[i];f.startsWith("-")?(n.push(f),(f==="-o"||f==="--push-option"||f==="--repo"||f==="--receive-pack"||f==="--exec")&&i++):s.push(f)}let r=n.some(i=>i==="--force"||/^-[a-zA-Z]+$/.test(i)&&i.includes("f"))||s.some(i=>i.startsWith("+")),c=n.some(i=>i.startsWith("--force-with-lease")||i.startsWith("--force-if-includes")),u=s[0]??null,l=s.slice(1).map(i=>i.replace(/^["']|["']$/g,"").replace(/^\+/,"")).map(i=>i.includes(":")?i.slice(i.indexOf(":")+1):i).filter(i=>i&&!i.startsWith("$"));return{force:r,lease:c,remote:u,dsts:l}}var be=String.raw`(?:~|\$HOME|\$\{HOME\}|/home/[^\s/"']+|/root|/Users/[^\s/"']+)`,ro=new RegExp(`${be}/\\.ssh(?:/|\\b)`),io=new RegExp(`${be}/(?:\\.claude\\.json\\b|\\.claude/\\.credentials\\.json|\\.codex/auth\\.json|\\.cursor(?:/|\\b))`),ao=/(?:^|[\s/"'=(])(\.env(?:\.[A-Za-z0-9_.-]+)?)(?=$|[\s"'|;&)])/g,co=/^\.env\.(?:example|sample|template|dist)$/,st=/(\.claude\/settings(?:\.local)?\.json|\.claude\/hooks\/[^\s"']*|\.bostrat\/events(?:\/[^\s"']*)?)/,rt=new RegExp(`((?:/etc|/usr/lib|/lib|/run)/systemd/[^\\s"']*|${be}/\\.config/systemd/[^\\s"']*|/etc/cron(?:tab|\\.d|\\.hourly|\\.daily|\\.weekly|\\.monthly)(?:/[^\\s"']*)?|/var/spool/cron(?:/[^\\s"']*)?)`),lo=/^(?:enable|disable|start|stop|restart|try-restart|reload|reload-or-restart|daemon-reload|daemon-reexec|link|mask|unmask|edit|preset|preset-all|set-property|revert|isolate|kill|reset-failed|import-environment|set-environment|add-wants|add-requires)$/,uo=/^\/tmp\/|\/\.bostrat\/tmp\//,po=/^(?:refs\/heads\/)?(?:main|master|develop|development|trunk|production|prod|staging|release(?:\/|$)|v\d)/,fo=/(?:^|\/)track\/|^HEAD$|^refs\/bostrat\//,ho=/https?:\/\/([a-z0-9.-]+\.[a-z]{2,}|localhost|\d+\.\d+\.\d+\.\d+)(?::\d+)?/gi,et=e=>ro.test(e)||io.test(e),tt=e=>[...String(e??"").matchAll(ao)].some(o=>!co.test(o[1])),mo=e=>st.test(e),go=e=>rt.test(e),bo=e=>(e.startsWith("/")||e.startsWith("~"))&&!uo.test(e);function it(e){let{cmd:o,at:t}=nt(e);if(o==="sudo"){for(t++;t<e.length&&e[t].startsWith("-");)t++;o=e[t]??null}return{cmd:o,at:t}}function at(e,o){let t=String(e??"").match(o);if(!t)return null;let n=t[1],s=n.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&");if(new RegExp(`>{1,2}\\s*["']?[^\\s"']*${s}`).test(e))return"redirect";let r=Q(e),{cmd:c}=it(r);return!c||!r.some(u=>u.includes(n))?null:/^(?:rm|mv|truncate|chmod|chown|tee|install|shred|unlink|ln|mkdir|touch)$/.test(c)?c:c==="sed"&&r.some(u=>/^-[a-zA-Z]*i/.test(u))?"sed -i":c==="cp"&&r[r.length-1].includes(n)?"cp":/^(?:python3?|perl|node|ruby)$/.test(c)&&/open\([^)]*['"][wa]|writeFile|write_text|appendFile|fs\.write/.test(e)?`${c} write`:null}var yo=e=>at(e,st);function wo(e){let o=Q(e),{cmd:t,at:n}=it(o);if(!t)return null;let s=o.slice(n+1);if(t==="crontab"||t.endsWith("/crontab")){let c=!1,u=!1;for(let l=0;l<s.length;l++){if(s[l]==="-u"){l++;continue}s[l]==="-l"||/^-(?:h|V|T|-help|-version)$/.test(s[l])?c=!0:u=!0}return u||!c?"crontab":null}if(t==="systemctl"||t.endsWith("/systemctl")){let c=s.find(u=>!u.startsWith("-"));return c&&lo.test(c)?`systemctl ${c}`:null}if((t==="loginctl"||t.endsWith("/loginctl"))&&s.includes("enable-linger"))return"loginctl enable-linger";if(t==="at"&&s.length&&/^(?:now|midnight|noon|teatime|today|tomorrow|next|\d|-[fqmvt])/.test(s[0]))return"at";let r=at(e,rt);return r?`${r} unit/cron path`:null}function ko(e){let o=new Set;for(let t of String(e??"").matchAll(ho))o.add(t[1].toLowerCase());return[...o]}function So({knownHosts:e=[]}={}){return{known:new Set([...to,...[...e].map(o=>String(o).toLowerCase()).filter(Boolean)]),hosts:new Set,tracks:new Map}}function $o(e,o){let t=e.tracks.get(o??"");return t||e.tracks.set(o??"",t={costs:[],peer:new Map}),t}var T=(e,o,t,n)=>Object.freeze({severity:e,requirement:eo,note:o,measured:t,detect:n}),V=(e,o)=>{let t=0,n=null;for(let s of e.calls)if(s.head)for(let r of so(s.head,e.headCap)){let c=o(r,s);c&&(t++,n??=C(c))}return t?{count:t,sample:n}:null},me=(e,o)=>{let t=0,n=null;for(let s of e.calls){let r=o(s);r&&(t++,n??=C(r))}return t?{count:t,sample:n}:null},H=Object.freeze({"force-push":T("high","git push --force / -f / +refspec in a command head (--force-with-lease, the Track's own rebase flow, is not one)","2026-10-21 fleet replay \xB7 0 hits / 98,690 hook calls \xB7 1,080 logs 2026-08\u219210 \xB7 58 --force-with-lease pushes excluded by design \u2192 high",(e,o)=>V(o,t=>{let n=Qe(t);return n&&n.force&&!n.lease?t:null})),"protected-config-write":T("high","a write to the box's own harness: .claude/settings*.json, a .claude/hooks/ script or the Track's .bostrat/events/ log \u2014 by a file tool, a redirect, rm/mv/tee/sed -i, or a scripted open-for-write (reads are not hits)","2026-10-21 fleet replay \xB7 0 hits / 98,690 hook calls \xB7 165 reads of those paths (cat, grep, python json.loads) excluded by design \u2192 high",(e,o)=>{let t=0,n=null;for(let r of o.calls)he.has(r.tool)&&r.path&&mo(r.path)&&(t++,n??=C(`${r.tool} ${r.path}`));let s=V(o,r=>{let c=yo(r);return c?`${c}: ${r}`:null});return s&&(t+=s.count,n??=s.sample),t?{count:t,sample:n}:null}),"persistence-install":T("medium","a shell segment that installs something the OS runs on its own \u2014 crontab (install / edit / remove; -l is a read), a mutating systemctl verb (enable / start / restart / daemon-reload \u2026; status / show / cat / list / is-* are reads), loginctl enable-linger, at / batch \u2014 or a write (file tool, redirect, cp / mv / tee / ln / sed -i) into a systemd unit or cron directory: how a process outlives its turn by the OS's hand (BP-AU-D6's other half)","2026-10-21 fleet replay \xB7 11 hits / 99,122 hook calls \xB7 10 Tracks \xB7 0 in the last 14 d \xB7 every hit operator-asked service work on the operator's own Pi (systemctl --user restart/stop/start of bostrat-node, tmp-sweep, a samba disable, one `crontab -` install for a backfill) \xB7 the bare `at` word a quoted `|` regex leaves in a segment (4 hits) excluded by design \u2192 medium (rail), the guard's deny when opted in",(e,o)=>{let t=0,n=null;for(let r of o.calls)he.has(r.tool)&&r.path&&go(r.path)&&(t++,n??=C(`${r.tool} ${r.path}`));let s=V(o,r=>{let c=wo(r);return c?`${c}: ${r}`:null});return s&&(t+=s.count,n??=s.sample),t?{count:t,sample:n}:null}),"push-outside-track":T("medium","git push to a protected branch (main/master/develop/release\u2026), to a branch that is not a Track branch, or to a remote other than origin/upstream \u2014 a Track's own code leaves on its track/ branch or the WIP shadow ref","2026-10-21 fleet replay \xB7 7 hits / 98,690 hook calls \xB7 4 Tracks \xB7 0 in the last 14 d \xB7 every hit operator-instructed work on another repo (two dep/ branches, a fork's multiplayer branch, one HEAD:main) \u2192 medium, not a page",(e,o)=>V(o,t=>{let n=Qe(t);return n&&(n.remote&&!/^(?:origin|upstream)$/.test(n.remote)&&!n.remote.startsWith("$")&&!n.remote.startsWith("-")||n.dsts.some(s=>po.test(s)||!fo.test(s)))?t:null})),"credential-read":T("medium","a command head or file path naming SSH material (~/.ssh) or a vendor auth file (~/.claude.json, ~/.claude/.credentials.json, ~/.codex/auth.json, ~/.cursor) \u2014 the box-auth policy's denylist shapes; ~/.claude/projects (transcripts) is not one","2026-10-21 fleet replay \xB7 12 hits / 98,690 hook calls \xB7 8 Tracks \xB7 3 in the last 14 d \xB7 every hit an operator-asked inspection on the operator's own node (ssh config for a host alias, stat/keys of ~/.claude.json) \u2192 medium until a 14-day window reads 0",(e,o)=>me(o,t=>t.head&&et(t.head)?t.head:t.path&&et(t.path)?`${t.tool} ${t.path}`:null)),"dotenv-read":T("low","a command head or file path naming a .env file (.env, .env.local, .env.production \u2026; the committed .env.example / .sample / .template are not ones)","2026-10-21 fleet replay \xB7 309 hits / 98,690 hook calls \xB7 44 Tracks \xB7 0 in the last 14 d \xB7 all of them an instructed `source \u2026/almanac/.env` on the operator's other repos \u2192 low (report only)",(e,o)=>me(o,t=>t.head&&tt(t.head)?t.head:t.path&&tt(t.path)?`${t.tool} ${t.path}`:null)),"privileged-command":T("medium","a shell segment whose command is sudo or docker (after any VAR=value prefix) \u2014 host-level reach from inside a box","2026-10-21 fleet replay \xB7 220 hits (183 events) / 98,690 hook calls \xB7 57 Tracks \xB7 3 in the last 14 d \xB7 sudo = the operator's own journalctl/systemctl asks on the Pi, docker = instructed compose/psql work on the operator's other services \u2192 medium (a hosted node's first hit is the number to watch)",(e,o)=>V(o,t=>{let{cmd:n}=nt(Q(t));return n==="sudo"||n==="docker"?t:null})),"write-outside-workspace":T("low","a file tool (Write / Edit / NotebookEdit) on an absolute path \u2014 the hook strips the box's workspace prefix, so an absolute path is outside it \u2014 that is not the box's scratch dir (/tmp/\u2026, \u2026/.bostrat/tmp/\u2026)","2026-10-21 fleet replay \xB7 106 hits / 98,690 hook calls \xB7 10 Tracks \xB7 44 in the last 14 d \xB7 the operator's other repos and Agent-tool worktrees under .bostrat/repos on the operator's own node \u2192 low (report only)",(e,o)=>me(o,t=>he.has(t.tool)&&t.path&&bo(t.path)?`${t.tool} ${t.path}`:null)),"egress-new-host":T("low","a command head reaching, by URL, a host no box in the fleet has reached before (this projection's lifetime, seeded from the warm tails; KNOWN_HOSTS + PLANE_AUDIT_KNOWN_HOSTS never count) \u2014 a baseline-growth signal, so each host is reported once","2026-10-21 fleet replay \xB7 140 first sightings / 98,690 hook calls \xB7 69 Tracks \xB7 8 in the last 14 d \xB7 198 if the baseline were per Track \xB7 every host a vendor API, a docs site or the operator's own services \u2192 low (report only)",(e,o)=>{let t=0,n=null;for(let s of o.calls)if(s.head)for(let r of ko(s.head))o.state.known.has(r)||o.state.hosts.has(r)||(o.state.hosts.add(r),o.state.hosts.size>oo&&o.state.hosts.delete(o.state.hosts.values().next().value),t++,n??=C(`${r} \xB7 ${s.head}`));return t?{count:t,sample:n}:null}),"spend-spike":T("low",`a cost.recorded turn over ${Ye}\xD7 the Track's running median (its last ${Xe} cost events, at least ${ze} of them) and over $${Je} \u2014 a turn that cost far more than this Track's turns do`,"2026-10-21 fleet replay \xB7 114 hits / 8,421 cost events \xB7 64 Tracks \xB7 2 in the last 14 d \xB7 every hit a long build turn after short prompts (a $25\u201378 turn on a $0.37 median) \u2014 real spend, no one's attack \u2192 low (report only)",(e,o)=>{if(e.type!=="cost.recorded")return null;let t=o.track.costs,n=Number(e.payload?.usd);if(!Number.isFinite(n)||n<0)return null;let s=null;if(t.length>=ze&&n>=Je){let r=[...t].sort((u,l)=>u-l),c=r[Math.floor(r.length/2)];n>Ye*c&&(s={count:1,sample:`$${n.toFixed(2)} vs median $${c.toFixed(2)} over ${t.length} turns${e.payload?.model?` \xB7 ${e.payload.model}`:""}`})}return t.push(n),t.length>Xe&&t.shift(),s}),"peer-injected-call":T("medium",`a tool call within ${Ze/6e4} min of a peer.message on the same box with no operator or node-typed turn in between \u2014 the cross-session injection vector (peer-message.js); a peer is never a root (BP-AU-D1)`,"2026-10-21 fleet replay \xB7 0 peer.message events in 288,395 \xB7 the replay cannot bound this rule's rate, so it stays medium (rail) until a window with peer traffic has been measured; the shape itself is BP-AU-D1's stated R4 case",(e,o)=>{if(!o.calls.length)return null;let t=o.track.peer.get(o.boxKey);if(t==null||o.ts-t>Ze||o.ts<t)return null;let n=o.calls[0];return{count:o.count,sample:C(`${Math.round((o.ts-t)/1e3)}s after a peer message \xB7 ${n.tool}${n.head?`: ${n.head}`:n.path?` ${n.path}`:""}`)}}),"foreign-session-call":T("medium","a hook-recorded tool call from a claude session that is not the box's own (payload.foreignSession, BP-AU-I12): a nested or stray CLI driving the box's hooks \u2014 R3 never roots it, this names it","2026-10-21 fleet replay \xB7 14 hits / 98,744 hook calls \xB7 3 Tracks \xB7 4 in the last 14 d \xB7 all the xscout loop's nested claude prompts, I12's own acceptance case \u2192 medium (rail), not a page",(e,o)=>{if(!o.calls.length||!e.payload?.foreignSession)return null;let t=o.calls[0];return{count:o.count,sample:C(`session ${String(e.payload.sessionId||"?").slice(0,8)} \xB7 ${t.tool}${t.head?`: ${t.head}`:t.path?` ${t.path}`:""}`)}})}),q=Object.freeze(Object.keys(H)),xo=new Set([...ge,"cost.recorded","peer.message","turn.user"]);function Eo(e,o,{rules:t=null,headCap:n=ot}={}){if(!o||!xo.has(o.type)||o.source==="plane")return[];let s=$o(e,o.trackId),r=o.payload||{},c=o.boxId??"",u=Number(o.ts)||0;if(o.type==="turn.user")return(o.actor==="user"||o.actor==="orchestrator")&&s.peer.delete(c),[];if(o.type==="peer.message")return s.peer.set(c,u),[];let l=ge.has(o.type)&&o.source==="hook",f={calls:l?v(r):[],count:l?U(r):0,track:s,state:e,ts:u,boxKey:c,headCap:n},m=[];for(let k of t||q){let y=H[k];if(!y)continue;let $=null;try{$=y.detect(o,f)}catch{$=null}$&&$.count>0&&m.push({rule:k,count:$.count,sample:$.sample??null})}return m}function ct(e,{days:o=14,now:t=Date.now(),rules:n=null}={}){let s=o?t-o*864e5:0,r=So(),c=n||q,u={days:o||null,logs:0,events:0,calls:0,callsInWindow:0,rules:Object.fromEntries(c.map(i=>[i,{severity:H[i].severity,note:H[i].note,measured:H[i].measured,hits:0,hitsInWindow:0,events:0,tracks:0,tracksInWindow:0,perTenK:0,samples:[]}]))},l=Object.fromEntries(c.map(i=>[i,{all:new Set,win:new Set}]));for(let i of e||[]){let f=Array.isArray(i?.events)?[...i.events].sort((m,k)=>(Number(m.ts)||0)-(Number(k.ts)||0)):[];if(f.length){u.logs++;for(let m of f){u.events++;let k=(Number(m.ts)||0)>=s;if(ge.has(m.type)&&m.source==="hook"){let y=U(m.payload||{});u.calls+=y,k&&(u.callsInWindow+=y)}for(let y of Eo(r,m,{rules:c})){let $=u.rules[y.rule];if($.hits+=y.count,$.events++,l[y.rule].all.add(m.trackId??i.trackId),k&&($.hitsInWindow+=y.count,l[y.rule].win.add(m.trackId??i.trackId)),y.sample){let M=$.samples.find(b=>b.text===y.sample);M?M.n++:$.samples.length<200&&$.samples.push({text:y.sample,n:1,ts:Number(m.ts)||0,trackId:m.trackId??i.trackId,name:i.name??null})}}}}}for(let i of c){let f=u.rules[i];f.tracks=l[i].all.size,f.tracksInWindow=l[i].win.size,f.perTenK=u.calls?Number((f.hits/u.calls*1e4).toFixed(2)):0,f.samples.sort((m,k)=>k.n-m.n||k.ts-m.ts),f.samples=f.samples.slice(0,50)}return u.ok=!c.some(i=>H[i].severity==="high"&&u.rules[i].hitsInWindow>0),u}var _o=e=>new Date(e).toISOString().slice(0,10);function lt(e,{samples:o=5}={}){let t=[];t.push(`R4 DETECTOR REPLAY \xB7 ${e.days?`window last ${e.days}d`:"all history"} \xB7 ${e.logs} logs \xB7 ${e.events.toLocaleString()} events \xB7 ${e.calls.toLocaleString()} hook calls (${e.callsInWindow.toLocaleString()} in window)`),t.push("A rule's severity is what its measured rate supports; `high` pages (D7). Hits are what the plane fold would have flagged.");let n=Object.keys(e.rules).sort((s,r)=>Ve[e.rules[s].severity]-Ve[e.rules[r].severity]||s.localeCompare(r));for(let s of n){let r=e.rules[s];t.push(""),t.push(`${s} [${r.severity}] \xB7 ${r.hits} hit(s) all-time (${r.events} events, ${r.tracks} Tracks, ${r.perTenK}/10k calls) \xB7 ${r.hitsInWindow} in window (${r.tracksInWindow} Tracks)`),t.push(` ${r.note}`),r.measured&&t.push(` measured: ${r.measured}`);for(let c of r.samples.slice(0,o))t.push(` ${c.n}\xD7 ${_o(c.ts)} ${c.name?`${c.name} `:""}(${c.trackId}) ${c.text}`)}return t.push(""),t.push(e.ok?"ok: no high-severity detector hit in the window":"ATTENTION: a high-severity detector hit inside the window (exit 2)"),t.join(`
|
|
6
|
-
`)}import
|
|
7
|
-
`))if(c.trim())try{
|
|
2
|
+
import te from"node:fs/promises";import jo from"node:os";import L from"node:path";var gt=(e,o)=>{if(e==null||e==="")return o;let t=Number(e);return Number.isFinite(t)&&t>=0?t:o},Mo=gt(process.env.BOSTRAT_HOOK_FOLD_MS,250);var yt=1e3;function C(e){let o=Number(e?.count);return Number.isInteger(o)&&o>1&&e?.tools&&typeof e.tools=="object"?Math.min(o,yt):1}function v(e){let o=e||{},t=C(o);if(t===1)return[o];let n=(Array.isArray(o.calls)?o.calls:[]).filter(s=>s&&typeof s=="object").slice(0,t),r={};for(let s of n)s.tool&&(r[s.tool]=(r[s.tool]||0)+1);for(let[s,c]of Object.entries(o.tools))for(let u=r[s]||0;u<Number(c)&&n.length<t;u++)n.push({tool:s});for(;n.length<t;)n.push({tool:null});return n}import{createHash as bt}from"node:crypto";var wt=new Set(["userId"]);function se(e){return e===null||typeof e!="object"?e===void 0||typeof e=="number"&&!Number.isFinite(e)?"null":JSON.stringify(e):Array.isArray(e)?`[${e.map(t=>se(t===void 0?null:t)).join(",")}]`:`{${Object.keys(e).filter(t=>e[t]!==void 0).sort().map(t=>`${JSON.stringify(t)}:${se(e[t])}`).join(",")}}`}function Te(e){let o={};for(let t of Object.keys(e))wt.has(t)||(o[t]=e[t]);return bt("sha256").update(se(o)).digest("hex")}var Oe=e=>Number.isFinite(e?.seq);function Ae(e){let o={members:0,chained:0,preChain:0,intactFrom:null,intactTo:null,genesis:!1,breaks:[]},t=null,n=!1,r=(s,c)=>{t={seq:s.seq,hash:c},o.intactFrom=s.seq,o.genesis=s.seq===1&&s.prev===null};for(let s of e||[]){if(!Oe(s))continue;if(o.members++,s.prev===void 0){t&&!n?(o.breaks.push({kind:"unchained",seq:s.seq,eventId:s.id??null}),n=!0):t||o.preChain++;continue}o.chained++;let c=Te(s);if(!t||n){n=!1,r(s,c);continue}if(s.seq===t.seq+1){if(s.prev===t.hash){t={seq:s.seq,hash:c};continue}o.breaks.push({kind:"prev-mismatch",seq:s.seq,eventId:s.id??null,expected:t.hash,got:s.prev})}else s.seq>t.seq+1?o.breaks.push({kind:"seq-gap",seq:s.seq,eventId:s.id??null,from:t.seq+1,to:s.seq-1}):o.breaks.push({kind:"seq-repeat",seq:s.seq,eventId:s.id??null,headSeq:t.seq});r(s,c)}return o.intactTo=t?t.seq:null,t||(o.intactFrom=null),o}var M=6e4,kt=120*M,ve=2e3,Ne=5e3,$t=2*M,St=5e3,xt=2*M,X=5e3,Et={Workflow:360*M,Agent:60*M},_t=5*M,It=10*M,ce=80,N=3,Tt=["Checkpoint: write a work-in-progress handoff","Resume from .bostrat/handoff/latest.md","Beat: write a","You are a SUBTASK agent working in an isolated","A subtask box you delegated has finished"],ue={operator:"strong",orchestrator:"strong","card-answer":"strong",loop:"strong","background-job":"strong","task-continuation":"strong","operator-terminal":"weak","orchestrator-unlogged":"weak","background-launch":"weak","task-continuation-unlogged":"weak",peer:"flagged","nested-session":"flagged","unknown-prompt":"flagged",unrooted:"flagged"},De={claude:{source:"hook",note:"hook-sourced: the CLI's hooks record the turn start (prompt_id), each call's intent and outcome (tool_use_id), failed calls and the Stop; the transcript is the duplicate"},codex:{source:"transcript",note:"transcript-sourced (a file the agent writes): the rollout names each turn (turn_id, turn_trigger, parent_turn_id) and each call (call_id), so a call cites the send that opened its turn; no intent before a call, no failed-call record, no turn end on the log, and a prompt typed at the pane is not on the log"},cursor:{source:"transcript",note:"transcript-sourced (a store the agent writes): message order is the turn structure, so a call cites the user message that opened its turn (its content hash); no turn ids, no per-message time (ts is synthesized), no intent, no failed-call record, no turn end, and a prompt typed at the pane is not on the log"}},R=e=>String(e??"").replace(/\s+/g," ").trim(),h=e=>e?.ts??0,de=(e,o)=>!!e&&!!o&&(e.startsWith(o.slice(0,ce))||o.startsWith(e.slice(0,ce))),Ot=6,At=(e,o)=>de(e,o)||e.length>=Ot&&o.includes(e.slice(0,ce)),vt=/^<pasted_content\b[^>]*>\s*/i,le=e=>R(e.payload?.promptHead??e.payload?.text).replace(vt,""),Nt=/^<cross-session-message\b/i,Rt=/^<task-notification>/i,jt=/<tool-use-id>\s*([^<\s]+)\s*<\/tool-use-id>/i;function ie(e){let o=e.payload||{},t=C(o),n=v(o)[0],r=n.head||n.path||(o.command?R(o.command):""),s=e.type==="tool.failed"?" [failed]":e.type==="tool.started"?" [never completed]":"";return`${n.tool||"tool"}${r?`: ${r.slice(0,100)}`:""}${s}${t>1?` (+${t-1} more)`:""}`}var Dt=/\.answered$/,Mt=new Set(["loop.created","followup.created"]),Pt=16,Me=new Set(["tool.started","tool.called","tool.failed"]);function Ut(){return{byId:new Map,byDelivery:new Map,byPrompt:new Map,byToolUse:new Map,jobs:new Map}}function Ct(e,o){if(!o||typeof o.type!="string")return;o.id&&e.byId.set(o.id,o);let t=o.payload||{};if(o.type==="turn.user")o.source==="orchestrator"&&t.deliveryId&&e.byDelivery.set(t.deliveryId,o),o.source==="hook"&&t.promptId&&!e.byPrompt.has(`${o.boxId}\0${t.promptId}`)&&e.byPrompt.set(`${o.boxId}\0${t.promptId}`,o);else if(o.type==="job.started"&&t.id)e.jobs.set(t.id,o);else if(Me.has(o.type)&&o.source==="hook")for(let n of v(t))n.toolUseId&&!e.byToolUse.has(n.toolUseId)&&e.byToolUse.set(n.toolUseId,o)}function Ft(e){let o=Ut();for(let t of e)Ct(o,t);return o}function Re(e,o){let t=e;for(let n=0;t&&n<Pt;n++){let r=t.payload||{},s=t.type;if(t.causationId&&o.byId.has(t.causationId)&&t.causationId!==t.id){t=o.byId.get(t.causationId);continue}if(Me.has(s)){let c=r.promptId?o.byPrompt.get(`${t.boxId}\0${r.promptId}`):null;if(!c)return null;t=c;continue}if(s==="turn.user"){if(t.source==="hook"){if(r.kind==="task-notification"&&r.jobId){let u=o.jobs.get(r.jobId);if(!u)return null;t=u;continue}let c=r.deliveryId?o.byDelivery.get(r.deliveryId):null;if(!c)return null;t=c;continue}return t.source==="orchestrator"?r.loopId||r.producer==="loop"?{origin:"loop",root:t}:{origin:t.actor==="orchestrator"?"orchestrator":"operator",root:t}:t.source==="transcript"?{origin:"operator-terminal",root:t}:null}if(s==="job.started"){let c=r.id?o.byToolUse.get(r.id):null;if(!c)return null;t=c;continue}return Dt.test(s)||Mt.has(s)?{origin:"card-answer",root:t}:s==="peer.message"?{origin:"peer",root:t}:null}return null}function Lt(e){let o={events:e.length,firstSeq:null,lastSeq:null,headMissing:!1,gaps:0,repeats:0},t=null;for(let r of e)Number.isFinite(r?.seq)&&(t===null?o.firstSeq=r.seq:r.seq>t+1?o.gaps++:r.seq<=t&&o.repeats++,t=r.seq);o.lastSeq=t,o.headMissing=o.firstSeq!==null&&o.firstSeq>1;let n=Ae(e);return o.chain={state:n.chained===0?"pre-chain":n.breaks.length?"broken":n.genesis?"intact":"intact-from-mid-log",chained:n.chained,preChain:n.preChain,intactFrom:n.intactFrom,intactTo:n.intactTo,breaks:n.breaks},o}var Bt=e=>{switch(e.kind){case"prev-mismatch":return`seq ${e.seq} states a predecessor that is not the line before it \u2014 the line before it changed, or was replaced`;case"seq-gap":return`seq ${e.from}${e.to!==e.from?`\u2013${e.to}`:""} missing between chained lines`;case"seq-repeat":return`seq ${e.seq} appears again after the chain passed it (a line was inserted, or the log was rewound)`;case"unchained":return`seq ${e.seq} carries no chain link after chained lines (an older node resumed this log)`;default:return`${e.kind} at seq ${e.seq}`}};function Ht(e){let o=new Map,t=new Map,n=new Map,r=new Map,s=(i,d,m)=>{i.has(d)||i.set(d,[]),i.get(d).push(m)};for(let i of e)if((i.type==="box.state.changed"||i.type.startsWith("handoff."))&&s(r,i.payload?.box||i.boxId||null,h(i)),i.type==="session.ready"&&([...r.get(i.boxId)||[],...r.get(null)||[]].some(m=>m<=h(i)&&h(i)-m<=xt)||s(n,i.boxId,h(i))),i.type==="turn.user")if(i.source==="orchestrator"){let d=(i.payload?.attachments||[]).map(m=>m?.id).filter(Boolean);s(o,i.boxId,{ts:h(i),head:R(i.payload?.text),files:d,actor:i.actor,usedAt:null})}else i.source==="transcript"&&s(t,i.boxId,{ts:h(i),head:R(i.payload?.text),note:i.payload?.note?.kind||null});let c=new Map,u=new Map,l=new Map;for(let i of e){if(i.type==="turn.user"&&i.source==="orchestrator"&&u.set(i.boxId,!0),i.source!=="hook")continue;if(i.type==="turn.agent"){let k=l.get(i.boxId)||0;k>0?l.set(i.boxId,k-1):u.set(i.boxId,!1);continue}if(i.type!=="turn.user")continue;let d=le(i),m=(()=>{if(i.actor==="peer"||Nt.test(d))return{origin:"peer"};if(Rt.test(d))return{origin:"task-continuation",jobId:d.match(jt)?.[1]||null};let k=(o.get(i.boxId)||[]).filter(w=>(w.usedAt===null||h(i)-w.usedAt<=Ne)&&w.ts>=h(i)-kt&&w.ts<=h(i)+ve&&(At(w.head,d)||!w.head&&w.files.length>0&&(w.files.some(O=>d.includes(`.bostrat/uploads/${O}`))||h(i)-w.ts<=Ne))),b=k[k.length-1];if(b)return b.usedAt=h(i),{origin:b.actor==="orchestrator"?"orchestrator":"operator"};if(Tt.some(w=>d.startsWith(w)))return{origin:"orchestrator-unlogged"};if((t.get(i.boxId)||[]).find(w=>!w.note&&w.ts>=h(i)-ve&&w.ts<=h(i)+$t&&de(w.head,d)))return{origin:"operator-terminal"};let P=(n.get(i.boxId)||[]).some(w=>w<=h(i)&&h(i)-w<=St);return{origin:u.get(i.boxId)||P?"nested-session":"unknown-prompt"}})();c.set(i,m),m.origin==="nested-session"?l.set(i.boxId,(l.get(i.boxId)||0)+1):u.set(i.boxId,!0)}return c}function qt(e){let o=new Map,t=new Map;for(let n of e){let r=n.payload?.id;if(r)if(n.type==="job.started"){let s={from:(n.payload?.ts??h(n))-X,to:1/0,loggedAt:h(n)};t.set(r,s),o.has(n.boxId)||o.set(n.boxId,[]),o.get(n.boxId).push(s)}else n.type==="job.stopped"&&t.has(r)&&(t.get(r).to=h(n)+X)}return{byBox:o,byId:t}}function Wt(e,{trackId:o=null,name:t=null,since:n=0}={}){let r=Ht(e),s=qt(e),c=new Set;for(let a of e)a.type==="tool.called"&&a.source==="hook"&&c.add(a.boxId);let u=a=>a.type==="tool.called"&&(a.source==="hook"||!(a.provider==="claude"&&c.has(a.boxId)))||a.type==="tool.failed"&&a.source==="hook",l=new Set,i=new Map;for(let a of e)if(a.source==="hook")if(a.type==="tool.called"||a.type==="tool.failed")for(let p of v(a.payload))p.toolUseId&&l.add(p.toolUseId);else a.type==="audit.flagged"&&a.payload?.denied===!0&&a.payload.toolUseId?l.add(a.payload.toolUseId):(a.type==="turn.agent"||a.type==="session.ready"||a.type==="turn.aborted"||a.type==="turn.interrupted")&&(i.has(a.boxId)||i.set(a.boxId,[]),i.get(a.boxId).push(h(a)));let d=e.length?h(e[e.length-1]):0,m=a=>{if(a.type!=="tool.started"||a.source!=="hook")return null;let p=v(a.payload).filter($=>$.toolUseId&&!l.has($.toolUseId));return p.length&&((i.get(a.boxId)||[]).some($=>$>h(a))||d-h(a)>It)?p:null},k={},b={},x={keyed:0,turn:0,timing:0},P=Ft(e),w=new Map,O=(a,p,f,$,S,g=1)=>{let j=`${a}\0${f.boxId}\0${$}`,I=w.get(j);return(!I||h(f)-I.to>_t)&&(I={rule:a,severity:p,trackId:o,track:t,boxId:f.boxId,from:h(f),to:h(f),count:0,note:S,samples:[]},w.set(j,I),K.push(I)),I.to=h(f),I.count+=g,I},K=[],re=new Map,mt=a=>(re.has(a)||re.set(a,{open:!1,origin:null,nested:0,soft:null,lastSendHead:null,launch:null}),re.get(a));for(let a of e){let p=mt(a.boxId),f=h(a)>=n;if(a.type==="session.foreign"){if(a.payload?.phase==="prompted"&&f){let y=O("nested-session","medium",a,"","a session that is not the box's own was prompted through this box \u2014 typically an agent-launched `claude -p`");y.samples.length<N&&y.samples.push(R(a.payload?.promptHead).slice(0,100))}continue}if(a.type==="turn.user"&&a.source==="hook"){let y=r.get(a);if(y.origin==="nested-session"){if(p.nested++,f){let U=O("nested-session","medium",a,"","a prompt no recorded send accounts for arrived mid-turn or right after a SessionStart \u2014 typically an agent-launched `claude -p`");U.samples.length<N&&U.samples.push(le(a).slice(0,100))}continue}if(p.open=!0,p.origin=y.origin,y.origin==="task-continuation"&&!s.byId.has(y.jobId)&&(p.origin="task-continuation-unlogged"),p.origin==="unknown-prompt"&&f){let U=O("unknown-prompt","medium",a,"","a turn opened by a prompt with no operator, machinery or job record behind it");U.samples.length<N&&U.samples.push(le(a).slice(0,100))}}else if(a.type==="turn.agent"&&a.source==="hook")p.nested>0?p.nested--:p.open=!1;else if(a.type==="turn.aborted"||a.type==="turn.interrupted")p.open=!1;else if(a.type==="turn.user"&&a.source==="orchestrator")p.lastSendHead=R(a.payload?.text),p.soft=a.actor==="orchestrator"?"orchestrator":"operator",p.open=!0,p.origin=p.soft;else if(a.type==="turn.user"&&a.source==="transcript"){let y=R(a.payload?.text);!a.payload?.note&&!de(y,p.lastSendHead)&&(p.soft="operator-terminal")}else a.type==="peer.message"&&(p.soft="peer");let $=m(a);if(!$&&!u(a))continue;let S=$?$.length:a.source==="hook"?C(a.payload):1,g;if(a.payload?.foreignSession)g="nested-session";else if(a.source==="hook"){let y=(s.byBox.get(a.boxId)||[]).some(D=>h(a)>=D.from&&h(a)<=D.to);p.open?g=p.origin:y?g="background-job":p.nested>0?g="nested-session":p.launch&&h(a)<=p.launch?g="background-launch":g="unrooted";let U=(s.byBox.get(a.boxId)||[]).some(D=>D.loggedAt<=h(a)+X);for(let D of new Set(v(a.payload).map(Ie=>Ie.tool)))(D==="Workflow"||D==="Agent"&&!U)&&p.open&&ue[p.origin]!=="flagged"&&(p.launch=Math.max(p.launch||0,h(a)+Et[D]))}else a.payload?.turnId?g=String(a.payload.turnTrigger||"user")==="user"?"operator-terminal":"unknown-prompt":g=p.soft||"unrooted";let j=a.payload?.foreignSession?null:Re(a,P);if(j&&(g=j.origin),!f)continue;let I=!j&&a.source!=="hook"&&!!a.payload?.turnId;x[j?"keyed":I?"turn":"timing"]+=S,k[g]=(k[g]||0)+S;let B=a.provider||"unknown";if(b[B]||={actions:0,fidelity:a.source==="hook"?"hook":"transcript",identified:!1,turnKeyed:!1,rooted:{key:0,turn:0,timing:0}},b[B].actions+=S,b[B].rooted[j?"key":I?"turn":"timing"]+=S,a.source==="hook"&&(b[B].fidelity="hook"),a.source==="hook"&&v(a.payload).some(y=>y.toolUseId)&&(b[B].identified=!0),a.source!=="hook"&&a.payload?.turnId&&(b[B].turnKeyed=!0),g==="unknown-prompt"&&a.source!=="hook"){let y=O("unexplained-turn","medium",a,String(a.payload.turnId),`calls in a transcript turn the CLI says no prompt opened (turn_trigger: ${String(a.payload.turnTrigger)}) and that cites no recorded cause`,S);y.samples.length<N&&y.samples.push(ie(a));continue}if($){let y=O("unfinished-calls","low",a,"","calls the CLI began (PreToolUse) and never reported complete or failed \u2014 killed, crashed or restarted mid-call; the tool may have run",S);y.samples.length<N&&y.samples.push(ie(a))}if(g==="unrooted"){let y=O("unrooted-actions","high",a,"","tool calls with no open turn, no recorded job and no launch that accounts for them",S);y.samples.length<N&&y.samples.push(ie(a))}else if(g==="peer")O("peer-driven-actions","low",a,"","tool calls in a turn another session's message opened",S);else if(g==="nested-session"||g==="unknown-prompt"){let y=w.get(`${g}\0${a.boxId}\0`);y&&(y.actions=(y.actions||0)+S)}}let G={total:0,keyed:0,timing:0,unrooted:0};{let a=new Map,p=new Map;for(let f of e)f.source==="hook"&&(f.type==="turn.user"?p.set(f.boxId,h(f)):(f.type==="turn.agent"||f.type==="turn.aborted"||f.type==="turn.interrupted")&&p.has(f.boxId)&&((a.get(f.boxId)||a.set(f.boxId,[]).get(f.boxId)).push([p.get(f.boxId),h(f)]),p.delete(f.boxId)));for(let[f,$]of p)(a.get(f)||a.set(f,[]).get(f)).push([$,1/0]);for(let f of e){if(f.type!=="code.committed"||f.source!=="hook"||h(f)<n)continue;G.total++;let $=Number(f.payload?.ts??f.ts)||h(f);if(Re(f,P))G.keyed++;else if((a.get(f.boxId)||[]).some(([S,g])=>$>=S-X&&$<=g+X))G.timing++;else{G.unrooted++;let S=O("unrooted-commit","medium",f,"","a commit landed in the box's workspace with no turn open and no recorded cause");S.samples.length<N&&S.samples.push(`${String(f.payload?.sha||"").slice(0,7)} ${R(f.payload?.subject).slice(0,80)}`)}}}{let a=e.filter(p=>p.type==="audit.flagged"&&p.source!=="plane"&&p.payload?.denied===!0&&h(p)>=n);if(a.length){let p=[...new Set(a.map(f=>f.payload?.rule).filter(Boolean))];K.push({rule:"guard-denied",severity:"high",trackId:o,track:t,boxId:a[0].boxId??null,from:h(a[0]),to:h(a.at(-1)),count:a.length,samples:a.slice(0,N).map(f=>`${f.payload?.rule||"?"}: ${R(f.payload?.samples?.[0]||f.payload?.tool||"")}`.slice(0,160)),note:`${a.length} call(s) denied before running by the node's PreToolUse guard (${p.join(", ")}) \u2014 BOSTRAT_AUDIT_GUARD is on for this node`})}}let A=Lt(e),_e=Object.values(k).reduce((a,p)=>a+p,0);if(A.headMissing&&_e&&K.push({rule:"log-head-missing",severity:"medium",trackId:o,track:t,boxId:null,from:h(e[0]),to:h(e[0]),count:1,samples:[],note:`the log starts at seq ${A.firstSeq} \u2014 its earlier history is not on this node`}),A.chain.breaks.length){let a=new Map;for(let g of e)Number.isFinite(g?.seq)&&!a.has(g.seq)&&a.set(g.seq,g);let p=A.chain.breaks.filter(g=>g.kind==="prev-mismatch"||g.kind==="seq-repeat"),f=g=>h(a.get(g.seq))||h(e.at(-1)),$=A.chain.breaks[0],S=A.chain.breaks.at(-1);K.push({rule:"chain-broken",severity:p.length?"high":"medium",trackId:o,track:t,boxId:null,from:f($),to:f(S),count:A.chain.breaks.length,samples:A.chain.breaks.slice(0,N).map(Bt),note:`the log's hash chain breaks ${A.chain.breaks.length} time(s) on this node \u2014 advisory: the plane's verified head is the anchor (GET /plane/admin/chain/${o??"<track>"})`})}return{trackId:o,track:t,actions:_e,byOrigin:k,byEvidence:x,byProvider:b,commits:G,findings:K,integrity:A}}var je={high:0,medium:1,low:2};function Pe(e,{days:o=14,now:t=Date.now()}={}){let n=o?t-o*864e5:0,r={days:o||null,tracks:0,actions:0,byOrigin:{},byStrength:{strong:0,weak:0,flagged:0},byEvidence:{keyed:0,turn:0,timing:0},commits:{total:0,keyed:0,timing:0,unrooted:0},byProvider:{},findings:[],integrity:{logs:0,headMissing:0,withGaps:0,withRepeats:0,chain:{intact:0,intactFromMidLog:0,broken:0,preChain:0}}};for(let s of e||[]){let c=Wt(s.events||[],{trackId:s.trackId,name:s.name,since:n});r.integrity.logs++,c.integrity.headMissing&&r.integrity.headMissing++,c.integrity.gaps&&r.integrity.withGaps++,c.integrity.repeats&&r.integrity.withRepeats++;let u=c.integrity.chain?.state;if(u==="intact"?r.integrity.chain.intact++:u==="intact-from-mid-log"?r.integrity.chain.intactFromMidLog++:u==="broken"?r.integrity.chain.broken++:r.integrity.chain.preChain++,!(!c.actions&&!c.findings.length)){r.tracks++,r.actions+=c.actions;for(let l of Object.keys(r.byEvidence))r.byEvidence[l]+=c.byEvidence?.[l]||0;for(let l of Object.keys(r.commits))r.commits[l]+=c.commits?.[l]||0;for(let[l,i]of Object.entries(c.byOrigin))r.byOrigin[l]=(r.byOrigin[l]||0)+i,r.byStrength[ue[l]||"flagged"]+=i;for(let[l,i]of Object.entries(c.byProvider)){r.byProvider[l]||={actions:0,fidelity:i.fidelity,identified:!1,turnKeyed:!1,rooted:{key:0,turn:0,timing:0}},r.byProvider[l].actions+=i.actions,i.fidelity==="hook"&&(r.byProvider[l].fidelity="hook"),i.identified&&(r.byProvider[l].identified=!0),i.turnKeyed&&(r.byProvider[l].turnKeyed=!0);for(let d of["key","turn","timing"])r.byProvider[l].rooted[d]+=i.rooted?.[d]||0}r.findings.push(...c.findings)}}for(let[s,c]of Object.entries(r.byProvider))c.note=De[s]?.note??(c.fidelity==="hook"?"hook-sourced":"transcript-sourced (a record the agent writes); no turn structure read by its adapter \u2014 calls attributed to the latest prompt");return r.findings.sort((s,c)=>je[s.severity]-je[c.severity]||c.to-s.to),r.ok=!r.findings.some(s=>s.severity==="high"),r}var V=(e,o)=>o?`${(e/o*100).toFixed(e&&e/o<.001?2:1)}%`:"0%",ae=e=>new Date(e).toISOString().slice(0,16).replace("T"," ");function Ue(e,{limit:o=20,containment:t=null}={}){let n=[];n.push(`AGENT ACTION AUDIT \xB7 ${e.days?`last ${e.days}d`:"all history"} \xB7 ${e.actions} action(s) across ${e.tracks} Track(s)`),n.push(""),n.push("Who asked for it");for(let l of["strong","weak","flagged"]){let i=Object.entries(e.byOrigin).filter(([d])=>(ue[d]||"flagged")===l).sort((d,m)=>m[1]-d[1]);if(i.length){n.push(` ${l.toUpperCase()} \xB7 ${e.byStrength[l]} (${V(e.byStrength[l],e.actions)})`);for(let[d,m]of i)n.push(` ${d.padEnd(27)} ${String(m).padStart(7)} ${V(m,e.actions)}`)}}n.push("");let r=e.byEvidence||{keyed:0,turn:0,timing:0};n.push(`Rooted by recorded key \xB7 ${r.keyed} (${V(r.keyed,e.actions)})`+(r.turn?` \xB7 by its transcript turn only \xB7 ${r.turn} (${V(r.turn,e.actions)})`:"")+` \xB7 by timing \xB7 ${r.timing} (${V(r.timing,e.actions)})`);let s=e.commits||{total:0,keyed:0,timing:0,unrooted:0};s.total&&n.push(`Commits \xB7 ${s.total}: ${s.keyed} cite their turn, ${s.timing} inside an open turn, ${s.unrooted} unrooted`),n.push(""),n.push("Evidence by provider");for(let[l,i]of Object.entries(e.byProvider).sort((d,m)=>m[1].actions-d[1].actions)){let d=i.rooted||{key:0,turn:0,timing:0},m=i.fidelity==="hook"?`${d.key} by recorded key \xB7 ${d.timing} by timing`:i.turnKeyed?`${d.key} cite the send that opened their turn \xB7 ${d.turn} cite only their transcript turn \xB7 ${d.timing} by timing (no turn key)`:"all by timing (attributed to the latest prompt)";n.push(` ${l.padEnd(10)} ${String(i.actions).padStart(7)} `+(i.fidelity==="hook"?`hook-recorded (turn boundaries known${i.identified?"; calls identified, intent and outcome paired":""})`:i.turnKeyed?"transcript-recorded (agent-written; turns keyed by the CLI's own record)":"transcript only (agent-written; no turn boundaries)")+` \xB7 ${m}`);let k=i.note??De[l]?.note;k&&n.push(` ${k}`)}n.push("");let c=e.integrity,u=c.chain||{intact:0,intactFromMidLog:0,broken:0,preChain:0};if(n.push(`Log integrity \xB7 ${c.logs} log(s): hash chain intact on ${u.intact} from seq 1 and ${u.intactFromMidLog} from mid-log, broken on ${u.broken}, ${u.preChain} pre-chain (written before the chain); ${c.headMissing} missing their head, ${c.withGaps} with seq gaps, ${c.withRepeats} with repeated seq`),n.push(" (the chain is walked over this node's copy \u2014 advisory; the plane's verified head is the anchor)"),n.push(""),!e.findings.length)n.push("Findings: none.");else{n.push(`Findings \xB7 ${e.findings.length}`);for(let l of e.findings.slice(0,o)){let i=l.to-l.from>M?`${ae(l.from)} \u2192 ${ae(l.to).slice(11)}`:ae(l.from),d=l.actions?`${l.count} prompt(s), ${l.actions} action(s)`:`${l.count}`;n.push(` [${l.severity}] ${l.rule} \xB7 ${l.track||l.trackId}${l.boxId?` \xB7 ${l.boxId}`:""} \xB7 ${i} \xB7 ${d}`),n.push(` ${l.note}`);for(let m of l.samples)n.push(` - ${m}`)}e.findings.length>o&&n.push(` \u2026 ${e.findings.length-o} more (--json for all)`)}return n.push(""),n.push(e.ok?`No action without an origin. ${t||"The log is agent-writable, so this is evidence, not proof."}`:`Actions with NO origin were found \u2014 read the [high] findings above.${t?` ${t}`:""}`),n.join(`
|
|
3
|
+
`)}import H from"node:fs";import Le from"node:fs/promises";import Z from"node:os";import E from"node:path";import{execFile as Be}from"node:child_process";import{promisify as Kt}from"node:util";var me=Kt(Be),Ce="BOSTRAT_BOX_USER",Gt="bostrat-box",He=Object.freeze(["root","sudo","wheel","admin","docker","lxd","libvirt","disk"]);var Vt=/^[a-z_][a-z0-9_-]{0,31}$/,Xt=new Set(["","off","0","false","no","none"]),qe=e=>e.BOSTRAT_STATE_DIR||E.join(e.HOME||Z.homedir(),".bostrat");function We(e){try{return H.readFileSync(e,"utf8").split(`
|
|
4
|
+
`).filter(o=>o&&!o.startsWith("#")).map(o=>o.split(":"))}catch{return[]}}function Ke(e,o=process.env){let n=We(o.BOSTRAT_PASSWD_FILE||"/etc/passwd").find(r=>r[0]===String(e)||r[2]===String(e));return n?{name:n[0],uid:Number(n[2]),gid:Number(n[3]),home:n[5]||null,shell:n[6]||null}:null}function Ge(e,o=process.env){let t=We(o.BOSTRAT_GROUP_FILE||"/etc/group"),n=new Set;for(let r of t)Number(r[2])===e.gid&&n.add(r[0]),(r[3]||"").split(",").includes(e.name)&&n.add(r[0]);return[...n].sort()}var J=()=>typeof process.getuid=="function"?process.getuid():0,pe=null;function Q(e=process.env){let o=String(e[Ce]||"").trim();if(Xt.has(o.toLowerCase()))return null;let t=`${o}|${e.BOSTRAT_PASSWD_FILE||""}|${e.BOSTRAT_GROUP_FILE||""}|${e.BOSTRAT_BOX_HOME_VIEW||""}`;if(pe?.key===t)return pe.value;let n;if(!Vt.test(o))n={name:o,error:`${Ce}="${o}" is not a valid user name`};else{let r=Ke(o,e),s=J();if(!r)n={name:o,error:`user "${o}" does not exist \u2014 run scripts/pi/box-user-setup.sh (or the image's useradd)`};else if(r.uid===0)n={name:o,error:`user "${o}" is root`};else if(r.uid===s)n={name:o,error:`user "${o}" is the node's own uid`};else if(!r.home||r.home==="/"||r.home==="/nonexistent")n={name:o,error:`user "${o}" has no home directory`};else{let c=Ge(r,e),u=c.filter(l=>He.includes(l));n=u.length?{name:o,error:`user "${o}" is in ${u.join(", ")} \u2014 a box user must hold no route to root`}:{...r,groups:c,nodeUid:s,rootNode:s===0}}}return pe={key:t,value:n},n}function zt(e=process.env){let o=Q(e);return o&&!o.error?o.home:Z.homedir()}function Fe(e=process.env){let o=Q(e);return!o||o.error||o.rootNode?zt(e):e.BOSTRAT_BOX_HOME_VIEW||E.join(qe(e),"box-home")}function Yt(e,o=process.env){let t=Q(o);return!t||t.error?e:t.rootNode?["setpriv",`--reuid=${t.uid}`,`--regid=${t.gid}`,"--init-groups","--","env",`HOME=${t.home}`,`USER=${t.name}`,`LOGNAME=${t.name}`,...e]:["sudo","-n","-H","-u",t.name,"--",...e]}async function fe(e,{env:o=process.env,input:t=null,timeout:n=15e3}={}){let[r,...s]=Yt(e,o);return t==null?me(r,s,{timeout:n}):new Promise((c,u)=>{Be(r,s,{timeout:n},(i,d,m)=>i?u(i):c({stdout:d,stderr:m})).stdin.end(t)})}var he;function Jt(e){return e.BOSTRAT_SETFACL_BIN?e.BOSTRAT_SETFACL_BIN:(he===void 0&&(he=["/usr/bin/setfacl","/bin/setfacl"].find(o=>H.existsSync(o))||null),he)}function Zt(e=process.env){let o=Z.homedir(),t=qe(e);return[E.join(o,".ssh"),E.join(o,".claude.json"),E.join(o,".claude",".credentials.json"),E.join(o,".codex","auth.json"),E.join(o,".config","gh","hosts.yml"),E.join(o,".cursor","cli-config.json"),E.join(t,"node.env"),E.join(t,"api-token"),E.join(t,"plane-link"),E.join(t,"secure-inputs")]}async function Ve({env:e=process.env,probe:o=!0}={}){let t=Q(e),n=await to(e);if(!t){let s=Ke(J(),e)||{name:Z.userInfo().username,uid:J(),gid:-1},c=s.uid===0?["root"]:Ge(s,e).filter(i=>He.includes(i)),u=s.uid!==0&&o?await eo():!1,l=[...c,...u&&!c.includes("sudo")?["sudo (sudoers)"]:[]];return{level:l.length?"root-capable":"node-uid",user:s.name,routes:l,problems:[],auditd:n}}if(t.error)return{level:"misconfigured",user:t.name,error:t.error,problems:[t.error],auditd:n};let r=[];if(o){try{let{stdout:l}=await fe(["id","-u"],{env:e,timeout:5e3});Number(l.trim())!==t.uid&&r.push(`switching to ${t.name} landed as uid ${l.trim()}`)}catch(l){r.push(`the node cannot switch to ${t.name} (${t.rootNode?"setpriv":"sudo -n -u"} failed: ${Qt(l)})`)}if(!r.length){await fe(["sudo","-n","true"],{env:e,timeout:5e3}).then(()=>!0,()=>!1)&&r.push(`${t.name} can sudo`);let l=[];for(let i of Zt(e))H.existsSync(i)&&await fe(["test","-r",i],{env:e,timeout:5e3}).then(()=>!0,()=>!1)&&l.push(i);l.length&&r.push(`${t.name} can read ${l.join(", ")}`)}Jt(e)||r.push("setfacl is missing (apt install acl)");let s=Fe(e);s!==t.home&&!H.existsSync(E.join(s,".claude"))&&r.push(`the read-only mirror of ${t.home} is not mounted at ${s} \u2014 the node cannot read box transcripts`);let c=E.join(t.home,".claude","settings.json"),u=await Le.stat(c).catch(()=>null);u&&u.uid===t.uid&&r.push(`${c} is owned by ${t.name} \u2014 the box can rewrite its own hooks`)}return{level:"uid",user:t.name,uid:t.uid,home:t.home,view:Fe(e),problems:r,auditd:n}}var Qt=e=>String(e?.stderr||e?.message||e).trim().split(`
|
|
5
|
+
`)[0].slice(0,160);async function eo(){try{return await me("sudo",["-n","true"],{timeout:3e3}),!0}catch{return!1}}async function to(e=process.env,{ask:o=!0}={}){let t=e.BOSTRAT_AUDIT_LOG||"/var/log/audit/audit.log",n=!1;try{await Le.access(t,H.constants.R_OK),n=!0}catch{}let r=e.BOSTRAT_AUDIT_RULES_FILE||"/etc/audit/rules.d/bostrat-box.rules",s=H.existsSync(r),c=null;if(o&&s){let u=J()===0?["auditctl",["-l"]]:["sudo",["-n","auditctl","-l"]];try{let{stdout:l}=await me(u[0],u[1],{timeout:5e3});c=l.includes(`key=${Gt}`)}catch{c=null}}else s||(c=!1);return{log:t,readable:n,configured:s,rule:c,rulesFile:r}}function Xe(e){return e.level==="uid"&&!e.problems.length?`Boxes on this node run as ${e.user}, which holds no route to root and cannot write the event log or its hook settings${e.auditd?.rule===!0&&e.auditd?.readable?"; its every exec is logged by auditd and shipped to the plane":""}.`:e.level==="uid"?`Boxes on this node run as ${e.user}, but containment has gaps (${e.problems.join("; ")}) \u2014 treat this record as evidence, not proof.`:e.level==="root-capable"?`Boxes on this node are root-capable (${e.user}: ${e.routes.join(", ")}), so a root-capable agent can defeat node-side evidence \u2014 this is evidence, not proof.`:e.level==="misconfigured"?`Box containment is configured but unusable (${e.error}); no box can start until it is fixed.`:`Boxes on this node share the node's uid (${e.user}), so an agent can rewrite its own log \u2014 this is evidence, not proof.`}var oo="BP-AU-R4",rt=160,ze={high:0,medium:1,low:2},Ye=200,Je=5,Ze=8,Qe=5,et=10*6e4,no=Object.freeze(["localhost","127.0.0.1","0.0.0.0","github.com","api.github.com","raw.githubusercontent.com","objects.githubusercontent.com","registry.npmjs.org","pypi.org","files.pythonhosted.org"]),ro=5e3,be=new Set(["tool.called","tool.failed"]),ge=new Set(["Write","Edit","NotebookEdit"]),so=e=>String(e??"").replace(/\s+/g," ").trim(),F=(e,o=120)=>so(e).slice(0,o);function io(e,o=rt){let t=String(e??""),n=t.split(/\s*(?:;|&&|\|\||\||\n)\s*/).map(r=>r.trim()).filter(Boolean);return Number.isFinite(o)&&t.length>=o&&n.length&&n.pop(),n}function ee(e){return String(e??"").replace(/\d?>>?\s*\S+|<\s*\S+|2>&1/g," ").trim().split(/\s+/).filter(Boolean)}function st(e){let o=0;for(;o<e.length&&/^[A-Za-z_][A-Za-z0-9_]*=/.test(e[o]);)o++;return{cmd:e[o]??null,rest:e.slice(o+1),at:o}}function tt(e){let o=ee(e),t=o.findIndex(i=>i==="git"||i.endsWith("/git"));if(t<0)return null;for(t++;t<o.length&&o[t].startsWith("-");)(o[t]==="-C"||o[t]==="-c")&&t++,t++;if(o[t]!=="push")return null;let n=[],r=[];for(let i=t+1;i<o.length;i++){let d=o[i];d.startsWith("-")?(n.push(d),(d==="-o"||d==="--push-option"||d==="--repo"||d==="--receive-pack"||d==="--exec")&&i++):r.push(d)}let s=n.some(i=>i==="--force"||/^-[a-zA-Z]+$/.test(i)&&i.includes("f"))||r.some(i=>i.startsWith("+")),c=n.some(i=>i.startsWith("--force-with-lease")||i.startsWith("--force-if-includes")),u=r[0]??null,l=r.slice(1).map(i=>i.replace(/^["']|["']$/g,"").replace(/^\+/,"")).map(i=>i.includes(":")?i.slice(i.indexOf(":")+1):i).filter(i=>i&&!i.startsWith("$"));return{force:s,lease:c,remote:u,dsts:l}}var we=String.raw`(?:~|\$HOME|\$\{HOME\}|/home/[^\s/"']+|/root|/Users/[^\s/"']+)`,ao=new RegExp(`${we}/\\.ssh(?:/|\\b)`),co=new RegExp(`${we}/(?:\\.claude\\.json\\b|\\.claude/\\.credentials\\.json|\\.codex/auth\\.json|\\.cursor(?:/|\\b))`),lo=/(?:^|[\s/"'=(])(\.env(?:\.[A-Za-z0-9_.-]+)?)(?=$|[\s"'|;&)])/g,uo=/^\.env\.(?:example|sample|template|dist)$/,it=/(\.claude\/settings(?:\.local)?\.json|\.claude\/hooks\/[^\s"']*|\.bostrat\/events(?:\/[^\s"']*)?)/,at=new RegExp(`((?:/etc|/usr/lib|/lib|/run)/systemd/[^\\s"']*|${we}/\\.config/systemd/[^\\s"']*|/etc/cron(?:tab|\\.d|\\.hourly|\\.daily|\\.weekly|\\.monthly)(?:/[^\\s"']*)?|/var/spool/cron(?:/[^\\s"']*)?)`),po=/^(?:enable|disable|start|stop|restart|try-restart|reload|reload-or-restart|daemon-reload|daemon-reexec|link|mask|unmask|edit|preset|preset-all|set-property|revert|isolate|kill|reset-failed|import-environment|set-environment|add-wants|add-requires)$/,fo=/^\/tmp\/|\/\.bostrat\/tmp\//,ho=/^(?:refs\/heads\/)?(?:main|master|develop|development|trunk|production|prod|staging|release(?:\/|$)|v\d)/,mo=/(?:^|\/)track\/|^HEAD$|^refs\/bostrat\//,go=/https?:\/\/([a-z0-9.-]+\.[a-z]{2,}|localhost|\d+\.\d+\.\d+\.\d+)(?::\d+)?/gi,ot=e=>ao.test(e)||co.test(e),nt=e=>[...String(e??"").matchAll(lo)].some(o=>!uo.test(o[1])),yo=e=>it.test(e),bo=e=>at.test(e),wo=e=>(e.startsWith("/")||e.startsWith("~"))&&!fo.test(e);function ct(e){let{cmd:o,at:t}=st(e);if(o==="sudo"){for(t++;t<e.length&&e[t].startsWith("-");)t++;o=e[t]??null}return{cmd:o,at:t}}function lt(e,o){let t=String(e??"").match(o);if(!t)return null;let n=t[1],r=n.replace(/[.*+?^${}()|[\]\\/]/g,"\\$&");if(new RegExp(`>{1,2}\\s*["']?[^\\s"']*${r}`).test(e))return"redirect";let s=ee(e),{cmd:c}=ct(s);return!c||!s.some(u=>u.includes(n))?null:/^(?:rm|mv|truncate|chmod|chown|tee|install|shred|unlink|ln|mkdir|touch)$/.test(c)?c:c==="sed"&&s.some(u=>/^-[a-zA-Z]*i/.test(u))?"sed -i":c==="cp"&&s[s.length-1].includes(n)?"cp":/^(?:python3?|perl|node|ruby)$/.test(c)&&/open\([^)]*['"][wa]|writeFile|write_text|appendFile|fs\.write/.test(e)?`${c} write`:null}var ko=e=>lt(e,it);function $o(e){let o=ee(e),{cmd:t,at:n}=ct(o);if(!t)return null;let r=o.slice(n+1);if(t==="crontab"||t.endsWith("/crontab")){let c=!1,u=!1;for(let l=0;l<r.length;l++){if(r[l]==="-u"){l++;continue}r[l]==="-l"||/^-(?:h|V|T|-help|-version)$/.test(r[l])?c=!0:u=!0}return u||!c?"crontab":null}if(t==="systemctl"||t.endsWith("/systemctl")){let c=r.find(u=>!u.startsWith("-"));return c&&po.test(c)?`systemctl ${c}`:null}if((t==="loginctl"||t.endsWith("/loginctl"))&&r.includes("enable-linger"))return"loginctl enable-linger";if(t==="at"&&r.length&&/^(?:now|midnight|noon|teatime|today|tomorrow|next|\d|-[fqmvt])/.test(r[0]))return"at";let s=lt(e,at);return s?`${s} unit/cron path`:null}function So(e){let o=new Set;for(let t of String(e??"").matchAll(go))o.add(t[1].toLowerCase());return[...o]}function xo({knownHosts:e=[]}={}){return{known:new Set([...no,...[...e].map(o=>String(o).toLowerCase()).filter(Boolean)]),hosts:new Set,tracks:new Map}}function Eo(e,o){let t=e.tracks.get(o??"");return t||e.tracks.set(o??"",t={costs:[],peer:new Map}),t}var _=(e,o,t,n)=>Object.freeze({severity:e,requirement:oo,note:o,measured:t,detect:n}),z=(e,o)=>{let t=0,n=null;for(let r of e.calls)if(r.head)for(let s of io(r.head,e.headCap)){let c=o(s,r);c&&(t++,n??=F(c))}return t?{count:t,sample:n}:null},ye=(e,o)=>{let t=0,n=null;for(let r of e.calls){let s=o(r);s&&(t++,n??=F(s))}return t?{count:t,sample:n}:null},q=Object.freeze({"force-push":_("high","git push --force / -f / +refspec in a command head (--force-with-lease, the Track's own rebase flow, is not one)","2026-10-21 fleet replay \xB7 0 hits / 98,690 hook calls \xB7 1,080 logs 2026-08\u219210 \xB7 58 --force-with-lease pushes excluded by design \u2192 high",(e,o)=>z(o,t=>{let n=tt(t);return n&&n.force&&!n.lease?t:null})),"protected-config-write":_("high","a write to the box's own harness: .claude/settings*.json, a .claude/hooks/ script or the Track's .bostrat/events/ log \u2014 by a file tool, a redirect, rm/mv/tee/sed -i, or a scripted open-for-write (reads are not hits)","2026-10-21 fleet replay \xB7 0 hits / 98,690 hook calls \xB7 165 reads of those paths (cat, grep, python json.loads) excluded by design \u2192 high",(e,o)=>{let t=0,n=null;for(let s of o.calls)ge.has(s.tool)&&s.path&&yo(s.path)&&(t++,n??=F(`${s.tool} ${s.path}`));let r=z(o,s=>{let c=ko(s);return c?`${c}: ${s}`:null});return r&&(t+=r.count,n??=r.sample),t?{count:t,sample:n}:null}),"persistence-install":_("medium","a shell segment that installs something the OS runs on its own \u2014 crontab (install / edit / remove; -l is a read), a mutating systemctl verb (enable / start / restart / daemon-reload \u2026; status / show / cat / list / is-* are reads), loginctl enable-linger, at / batch \u2014 or a write (file tool, redirect, cp / mv / tee / ln / sed -i) into a systemd unit or cron directory: how a process outlives its turn by the OS's hand (BP-AU-D6's other half)","2026-10-21 fleet replay \xB7 11 hits / 99,122 hook calls \xB7 10 Tracks \xB7 0 in the last 14 d \xB7 every hit operator-asked service work on the operator's own Pi (systemctl --user restart/stop/start of bostrat-node, tmp-sweep, a samba disable, one `crontab -` install for a backfill) \xB7 the bare `at` word a quoted `|` regex leaves in a segment (4 hits) excluded by design \u2192 medium (rail), the guard's deny when opted in",(e,o)=>{let t=0,n=null;for(let s of o.calls)ge.has(s.tool)&&s.path&&bo(s.path)&&(t++,n??=F(`${s.tool} ${s.path}`));let r=z(o,s=>{let c=$o(s);return c?`${c}: ${s}`:null});return r&&(t+=r.count,n??=r.sample),t?{count:t,sample:n}:null}),"push-outside-track":_("medium","git push to a protected branch (main/master/develop/release\u2026), to a branch that is not a Track branch, or to a remote other than origin/upstream \u2014 a Track's own code leaves on its track/ branch or the WIP shadow ref","2026-10-21 fleet replay \xB7 7 hits / 98,690 hook calls \xB7 4 Tracks \xB7 0 in the last 14 d \xB7 every hit operator-instructed work on another repo (two dep/ branches, a fork's multiplayer branch, one HEAD:main) \u2192 medium, not a page",(e,o)=>z(o,t=>{let n=tt(t);return n&&(n.remote&&!/^(?:origin|upstream)$/.test(n.remote)&&!n.remote.startsWith("$")&&!n.remote.startsWith("-")||n.dsts.some(r=>ho.test(r)||!mo.test(r)))?t:null})),"credential-read":_("medium","a command head or file path naming SSH material (~/.ssh) or a vendor auth file (~/.claude.json, ~/.claude/.credentials.json, ~/.codex/auth.json, ~/.cursor) \u2014 the box-auth policy's denylist shapes; ~/.claude/projects (transcripts) is not one","2026-10-21 fleet replay \xB7 12 hits / 98,690 hook calls \xB7 8 Tracks \xB7 3 in the last 14 d \xB7 every hit an operator-asked inspection on the operator's own node (ssh config for a host alias, stat/keys of ~/.claude.json) \u2192 medium until a 14-day window reads 0",(e,o)=>ye(o,t=>t.head&&ot(t.head)?t.head:t.path&&ot(t.path)?`${t.tool} ${t.path}`:null)),"dotenv-read":_("low","a command head or file path naming a .env file (.env, .env.local, .env.production \u2026; the committed .env.example / .sample / .template are not ones)","2026-10-21 fleet replay \xB7 309 hits / 98,690 hook calls \xB7 44 Tracks \xB7 0 in the last 14 d \xB7 all of them an instructed `source \u2026/almanac/.env` on the operator's other repos \u2192 low (report only)",(e,o)=>ye(o,t=>t.head&&nt(t.head)?t.head:t.path&&nt(t.path)?`${t.tool} ${t.path}`:null)),"privileged-command":_("medium","a shell segment whose command is sudo or docker (after any VAR=value prefix) \u2014 host-level reach from inside a box","2026-10-21 fleet replay \xB7 220 hits (183 events) / 98,690 hook calls \xB7 57 Tracks \xB7 3 in the last 14 d \xB7 sudo = the operator's own journalctl/systemctl asks on the Pi, docker = instructed compose/psql work on the operator's other services \u2192 medium (a hosted node's first hit is the number to watch)",(e,o)=>z(o,t=>{let{cmd:n}=st(ee(t));return n==="sudo"||n==="docker"?t:null})),"write-outside-workspace":_("low","a file tool (Write / Edit / NotebookEdit) on an absolute path \u2014 the hook strips the box's workspace prefix, so an absolute path is outside it \u2014 that is not the box's scratch dir (/tmp/\u2026, \u2026/.bostrat/tmp/\u2026)","2026-10-21 fleet replay \xB7 106 hits / 98,690 hook calls \xB7 10 Tracks \xB7 44 in the last 14 d \xB7 the operator's other repos and Agent-tool worktrees under .bostrat/repos on the operator's own node \u2192 low (report only)",(e,o)=>ye(o,t=>ge.has(t.tool)&&t.path&&wo(t.path)?`${t.tool} ${t.path}`:null)),"egress-new-host":_("low","a command head reaching, by URL, a host no box in the fleet has reached before (this projection's lifetime, seeded from the warm tails; KNOWN_HOSTS + PLANE_AUDIT_KNOWN_HOSTS never count) \u2014 a baseline-growth signal, so each host is reported once","2026-10-21 fleet replay \xB7 140 first sightings / 98,690 hook calls \xB7 69 Tracks \xB7 8 in the last 14 d \xB7 198 if the baseline were per Track \xB7 every host a vendor API, a docs site or the operator's own services \u2192 low (report only)",(e,o)=>{let t=0,n=null;for(let r of o.calls)if(r.head)for(let s of So(r.head))o.state.known.has(s)||o.state.hosts.has(s)||(o.state.hosts.add(s),o.state.hosts.size>ro&&o.state.hosts.delete(o.state.hosts.values().next().value),t++,n??=F(`${s} \xB7 ${r.head}`));return t?{count:t,sample:n}:null}),"spend-spike":_("low",`a cost.recorded turn over ${Ze}\xD7 the Track's running median (its last ${Ye} cost events, at least ${Je} of them) and over $${Qe} \u2014 a turn that cost far more than this Track's turns do`,"2026-10-21 fleet replay \xB7 114 hits / 8,421 cost events \xB7 64 Tracks \xB7 2 in the last 14 d \xB7 every hit a long build turn after short prompts (a $25\u201378 turn on a $0.37 median) \u2014 real spend, no one's attack \u2192 low (report only)",(e,o)=>{if(e.type!=="cost.recorded")return null;let t=o.track.costs,n=Number(e.payload?.usd);if(!Number.isFinite(n)||n<0)return null;let r=null;if(t.length>=Je&&n>=Qe){let s=[...t].sort((u,l)=>u-l),c=s[Math.floor(s.length/2)];n>Ze*c&&(r={count:1,sample:`$${n.toFixed(2)} vs median $${c.toFixed(2)} over ${t.length} turns${e.payload?.model?` \xB7 ${e.payload.model}`:""}`})}return t.push(n),t.length>Ye&&t.shift(),r}),"peer-injected-call":_("medium",`a tool call within ${et/6e4} min of a peer.message on the same box with no operator or node-typed turn in between \u2014 the cross-session injection vector (peer-message.js); a peer is never a root (BP-AU-D1)`,"2026-10-21 fleet replay \xB7 0 peer.message events in 288,395 \xB7 the replay cannot bound this rule's rate, so it stays medium (rail) until a window with peer traffic has been measured; the shape itself is BP-AU-D1's stated R4 case",(e,o)=>{if(!o.calls.length)return null;let t=o.track.peer.get(o.boxKey);if(t==null||o.ts-t>et||o.ts<t)return null;let n=o.calls[0];return{count:o.count,sample:F(`${Math.round((o.ts-t)/1e3)}s after a peer message \xB7 ${n.tool}${n.head?`: ${n.head}`:n.path?` ${n.path}`:""}`)}}),"foreign-session-call":_("medium","a hook-recorded tool call from a claude session that is not the box's own (payload.foreignSession, BP-AU-I12): a nested or stray CLI driving the box's hooks \u2014 R3 never roots it, this names it","2026-10-21 fleet replay \xB7 14 hits / 98,744 hook calls \xB7 3 Tracks \xB7 4 in the last 14 d \xB7 all the xscout loop's nested claude prompts, I12's own acceptance case \u2192 medium (rail), not a page",(e,o)=>{if(!o.calls.length||!e.payload?.foreignSession)return null;let t=o.calls[0];return{count:o.count,sample:F(`session ${String(e.payload.sessionId||"?").slice(0,8)} \xB7 ${t.tool}${t.head?`: ${t.head}`:t.path?` ${t.path}`:""}`)}})}),W=Object.freeze(Object.keys(q)),_o=new Set([...be,"cost.recorded","peer.message","turn.user"]);function Io(e,o,{rules:t=null,headCap:n=rt}={}){if(!o||!_o.has(o.type)||o.source==="plane")return[];let r=Eo(e,o.trackId),s=o.payload||{},c=o.boxId??"",u=Number(o.ts)||0;if(o.type==="turn.user")return(o.actor==="user"||o.actor==="orchestrator")&&r.peer.delete(c),[];if(o.type==="peer.message")return r.peer.set(c,u),[];let l=be.has(o.type)&&o.source==="hook",d={calls:l?v(s):[],count:l?C(s):0,track:r,state:e,ts:u,boxKey:c,headCap:n},m=[];for(let k of t||W){let b=q[k];if(!b)continue;let x=null;try{x=b.detect(o,d)}catch{x=null}x&&x.count>0&&m.push({rule:k,count:x.count,sample:x.sample??null})}return m}function ut(e,{days:o=14,now:t=Date.now(),rules:n=null}={}){let r=o?t-o*864e5:0,s=xo(),c=n||W,u={days:o||null,logs:0,events:0,calls:0,callsInWindow:0,rules:Object.fromEntries(c.map(i=>[i,{severity:q[i].severity,note:q[i].note,measured:q[i].measured,hits:0,hitsInWindow:0,events:0,tracks:0,tracksInWindow:0,perTenK:0,samples:[]}]))},l=Object.fromEntries(c.map(i=>[i,{all:new Set,win:new Set}]));for(let i of e||[]){let d=Array.isArray(i?.events)?[...i.events].sort((m,k)=>(Number(m.ts)||0)-(Number(k.ts)||0)):[];if(d.length){u.logs++;for(let m of d){u.events++;let k=(Number(m.ts)||0)>=r;if(be.has(m.type)&&m.source==="hook"){let b=C(m.payload||{});u.calls+=b,k&&(u.callsInWindow+=b)}for(let b of Io(s,m,{rules:c})){let x=u.rules[b.rule];if(x.hits+=b.count,x.events++,l[b.rule].all.add(m.trackId??i.trackId),k&&(x.hitsInWindow+=b.count,l[b.rule].win.add(m.trackId??i.trackId)),b.sample){let P=x.samples.find(w=>w.text===b.sample);P?P.n++:x.samples.length<200&&x.samples.push({text:b.sample,n:1,ts:Number(m.ts)||0,trackId:m.trackId??i.trackId,name:i.name??null})}}}}}for(let i of c){let d=u.rules[i];d.tracks=l[i].all.size,d.tracksInWindow=l[i].win.size,d.perTenK=u.calls?Number((d.hits/u.calls*1e4).toFixed(2)):0,d.samples.sort((m,k)=>k.n-m.n||k.ts-m.ts),d.samples=d.samples.slice(0,50)}return u.ok=!c.some(i=>q[i].severity==="high"&&u.rules[i].hitsInWindow>0),u}var To=e=>new Date(e).toISOString().slice(0,10);function dt(e,{samples:o=5}={}){let t=[];t.push(`R4 DETECTOR REPLAY \xB7 ${e.days?`window last ${e.days}d`:"all history"} \xB7 ${e.logs} logs \xB7 ${e.events.toLocaleString()} events \xB7 ${e.calls.toLocaleString()} hook calls (${e.callsInWindow.toLocaleString()} in window)`),t.push("A rule's severity is what its measured rate supports; `high` pages (D7). Hits are what the plane fold would have flagged.");let n=Object.keys(e.rules).sort((r,s)=>ze[e.rules[r].severity]-ze[e.rules[s].severity]||r.localeCompare(s));for(let r of n){let s=e.rules[r];t.push(""),t.push(`${r} [${s.severity}] \xB7 ${s.hits} hit(s) all-time (${s.events} events, ${s.tracks} Tracks, ${s.perTenK}/10k calls) \xB7 ${s.hitsInWindow} in window (${s.tracksInWindow} Tracks)`),t.push(` ${s.note}`),s.measured&&t.push(` measured: ${s.measured}`);for(let c of s.samples.slice(0,o))t.push(` ${c.n}\xD7 ${To(c.ts)} ${c.name?`${c.name} `:""}(${c.trackId}) ${c.text}`)}return t.push(""),t.push(e.ok?"ok: no high-severity detector hit in the window":"ATTENTION: a high-severity detector hit inside the window (exit 2)"),t.join(`
|
|
6
|
+
`)}import Oo from"node:path";import Ao from"node:os";import{EventEmitter as vo}from"node:events";var Jo=16*1024;var No=new vo;No.setMaxListeners(0);var an=process.env.BOSTRAT_STATE_DIR||Oo.join(process.env.HOME||Ao.tmpdir(),".bostrat");var Ro=/(?<!\\)((?:\\\\)*)\\u0000/g,pt=e=>e.includes("\\u0000"),ft=e=>e.replace(Ro,"$1\\ufffd");var oe=process.env.BOSTRAT_STATE_DIR||L.join(process.env.HOME||jo.tmpdir(),".bostrat"),T=process.argv.slice(2),ne=e=>{let o=T.indexOf(e);return o>=0?T[o+1]:null};(T.includes("--help")||T.includes("-h"))&&(console.log("usage: bostrat-audit [--days N | --all] [--track <id-or-name>] [--limit N] [--json]"),console.log(" bostrat-audit --detectors [--days N | --all] [--track <id-or-name>] [--rule <name>]... [--limit N] [--json]"),console.log(` rules: ${W.join(", ")}`),process.exit(0));var Se=[];try{Se=JSON.parse(await te.readFile(L.join(oe,"tracks.json"),"utf8")).tracks||[]}catch{}var Do=new Map(Se.map(e=>[e.id,e.name])),xe=new Set([L.join(oe,"events")]);for(let e of Se)e.cwd&&xe.add(L.join(e.cwd,".bostrat","events"));try{for(let e of await te.readdir(L.join(oe,"workspaces")))xe.add(L.join(oe,"workspaces",e,".bostrat","events"))}catch{}var ke=ne("--track"),Ee=[];for(let e of xe){let o=[];try{o=(await te.readdir(e)).filter(t=>t.endsWith(".ndjson"))}catch{continue}for(let t of o){let n=t.slice(0,-7),r=Do.get(n)||null;if(ke&&n!==ke&&!(r||"").toLowerCase().includes(ke.toLowerCase()))continue;let s=[];for(let c of(await te.readFile(L.join(e,t),"utf8")).split(`
|
|
7
|
+
`))if(c.trim())try{s.push(JSON.parse(pt(c)?ft(c):c))}catch{}s.length&&Ee.push({trackId:n,name:r,events:s})}}var ht=T.includes("--all")?0:Number(ne("--days"))||14;if(T.includes("--detectors")){let e=T.flatMap((n,r)=>n==="--rule"&&T[r+1]?[T[r+1]]:[]),o=e.filter(n=>!W.includes(n));o.length&&(console.error(`unknown rule(s): ${o.join(", ")} \u2014 known: ${W.join(", ")}`),process.exit(1));let t=ut(Ee,{days:ht,rules:e.length?e:null});console.log(T.includes("--json")?JSON.stringify(t,null,2):dt(t,{samples:Number(ne("--limit"))||5})),process.exit(t.ok?0:2)}var Y=Pe(Ee,{days:ht}),$e=await Ve().catch(()=>null);$e&&(Y.containment={...$e,sentence:Xe($e)});console.log(T.includes("--json")?JSON.stringify(Y,null,2):Ue(Y,{limit:Number(ne("--limit"))||20,containment:Y.containment?.sentence}));process.exit(Y.ok?0:2);
|
package/bin/bostrat-blueprint
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var v=process.env.BOSTRAT_AGENT_URL||`http://127.0.0.1:${process.env.BOSTRAT_AGENT_PORT||3362}`;async function b(e,i,
|
|
2
|
+
var v=process.env.BOSTRAT_AGENT_URL||`http://127.0.0.1:${process.env.BOSTRAT_AGENT_PORT||3362}`;async function b(e,i,c){let n=await fetch(`${v}${i}`,{method:e,headers:c?{"content-type":"application/json"}:void 0,body:c?JSON.stringify(c):void 0}),r=await n.json().catch(()=>({}));if(!n.ok)throw Object.assign(new Error(r.error||`HTTP ${n.status}`),{status:n.status,data:r});return r}function l(e){console.error(`[bostrat-blueprint] ${e}`),process.exit(1)}function y(e,i){if(console.error(`[bostrat-blueprint] refused: ${e.error}`),e.code==="stale-base"){if(console.error(` plane holds v${e.current}; this copy was built on v${e.yourBase}.`),e.drift?.length){console.error(" item-level difference:");for(let c of e.drift)console.error(` ${String(c.itemId).padEnd(12)} plane: ${String(c.plane).padEnd(7)} file: ${c.file}`)}else console.error(" no item differs \u2014 the newer version changed prose only.");console.error(" Bring this copy up to date, then republish:"),console.error(" git fetch origin && git merge origin/main # siblings' ticks land on main with their PRs"),console.error(" (copy any ticked item you still lack VERBATIM \u2014 never invent a sibling's note)"),console.error(` bostrat-blueprint show && bostrat-blueprint update ${i}`),console.error(` Or, once the file carries everything above: bostrat-blueprint update --base ${e.current} ${i}`)}else e.code==="untick"&&(console.error(` would un-tick: ${e.untick.join(", ")} (plane v${e.current} has them [x]).`),console.error(" Copy their ticks + status notes into the file (merge origin/main first), then republish."));console.error(" --force publishes the file exactly as it is (un-ticking the items above). Only when that is the intent."),process.exit(1)}function w(e){if(e){console.log(` parsed: ${e.sections.recognized} recognized section(s), ${e.items.total} item(s) (${e.items.bindable} bindable)`);for(let i of e.issues??[])console.log(` \u26A0 ${i.message}`)}}var s=process.argv.slice(2),d=s.shift();if(d==="add"||d==="update"){let e=null,i=null,c=null,n=null,r=null,u=!1;for(let o=0;o<s.length;o++)s[o]==="--circuit"?e=s[++o]:s[o]==="--new-circuit"?i=s[++o]:s[o]==="--name"?c=s[++o]:s[o]==="--base"?r=s[++o]:s[o]==="--force"?u=!0:n=s[o];n||l(`usage: bostrat-blueprint ${d} [--circuit <name-or-id>] [--new-circuit "<name>"] [--name "<title>"] [--base <v>] [--force] <file.md>`),r!=null&&!/^[1-9]\d*$/.test(String(r))&&l(`--base takes a version number (what \`show\` prints), got "${r}"`);try{let o=await(await import("node:fs/promises")).readFile(n,"utf8"),t=null,p=null;try{let{execFileSync:f}=await import("node:child_process"),h=await import("node:path"),m=h.resolve(n),$=f("git",["rev-parse","--show-toplevel"],{cwd:h.dirname(m),encoding:"utf8",stdio:["ignore","pipe","ignore"]}).trim(),g=h.relative($,m);g&&!g.startsWith("..")&&(t=g.split(h.sep).join("/"),p=$)}catch{}let a=await b("POST","/api/blueprints/publish",{cwd:process.cwd(),circuit:e,newCircuit:i,name:c,markdown:o,base:r==null?null:Number(r),force:u,path:t,root:p});console.log(`[bostrat-blueprint] published \u201C${a.blueprint.name}\u201D v${a.blueprint.version} on circuit \u201C${a.circuit.name}\u201D${a.base?` (on v${a.base})`:""}${u?" (forced)":""}`),w(a.report),a.linked&&console.log(` This Track is now in circuit \u201C${a.circuit.name}\u201D (it was in none) \u2014 the blueprint's item ids in a closing message are launch controls from here.`),a.blueprint.version===1&&console.log(" The circuit page now carries this blueprint; PRs/commits mentioning BP ids show as evidence \u2014 tick [x] + a *(done \u2026)* note in the same PR as the item's work.")}catch(o){o.status===409&&o.data?.code&&y(o.data,n),l(o.message)}}else if(d==="show"||d==="report")try{let e=await b("GET",`/api/blueprints/show?cwd=${encodeURIComponent(process.cwd())}${s[0]?`&circuit=${encodeURIComponent(s[0])}`:""}`);if(console.log(`[bostrat-blueprint] \u201C${e.blueprint.name}\u201D v${e.blueprint.version} (circuit ${e.blueprint.circuitId})`),w(e.report),d==="show"){let i=!!e.graph?.edges,c=t=>t.bindable?t.done?"done":i?t.ready?`ready${t.seam?" \xB7 seam":""}`:`waits on ${(t.waitsOn??[]).join(", ")}`:"":"",n=e.items.some(t=>t.bindable&&t.stageLine),r=e.items.map(t=>({i:t,id:t.bindable?t.itemId:"(no id)",pose:c(t)})),u=Math.max(8,...r.map(t=>t.id.length)),o=n?Math.max(0,...r.map(t=>t.pose?t.pose.length+2:0)):0;for(let{i:t,id:p,pose:a}of r){if(!n){console.log(` ${p.padEnd(8)} ${t.sectionKind.padEnd(14)} ${a?`[${a}] `:""}${t.text}`);continue}let f=t.bindable&&t.stageLine?t.stageLine:"";console.log(` ${p.padEnd(u)} ${t.sectionKind.padEnd(14)} ${(a?`[${a}]`:"").padEnd(o)}${o?" ":""}${f}`.trimEnd()),t.text&&console.log(` ${"".padEnd(u)} ${t.text}`)}n&&console.log(" (stage = what the plane observed, with its evidence. merged or higher is a fact \u2014 do not re-verify it; copy it as printed)"),i&&console.log(e.readyAt?` (readiness = every \`after\` item is at ${e.readyAt} or higher \u2014 this blueprint's ready-at; a What-remains list bolds only ready items)`:" (readiness = every `after` item presents as done; a What-remains list bolds only ready items)")}}catch(e){l(e.message)}else if(d==="tag"){s.length||l("usage: bostrat-blueprint tag <BP-id> [<BP-id>\u2026]");try{let e=await b("POST","/api/blueprints/tag",{cwd:process.cwd(),itemIds:s});console.log(`[bostrat-blueprint] tagged this Track onto ${e.itemIds.join(", ")}`),console.log(" (association + cost attribution \u2014 completion still comes from merged PRs or a manual tick)")}catch(e){l(e.message)}}else if(d==="hubs")try{let e=await b("GET",`/api/blueprints/hubs?cwd=${encodeURIComponent(process.cwd())}${s[0]?`&repo=${encodeURIComponent(s[0])}`:""}`);if(!e.hubs?.length)console.log(`[bostrat-blueprint] ${e.repo}: no hub files yet (${e.tracks??0} Track(s) with observed edits)`);else{console.log(`[bostrat-blueprint] ${e.repo}: hub files \u2014 share of ${e.tracks} Track(s) with observed edits that wrote each`);for(let i of e.hubs)console.log(` ${String(Math.round(i.share*100)).padStart(3)}% ${i.path}`);console.log(" (route edits to these through the seam item, or order the items that touch them with `after`)")}}catch(e){l(e.message)}else l("usage: bostrat-blueprint <add|update|show|report|tag|hubs> \u2026 (add [--circuit <ref>] <file.md>)");
|
package/bin/bostrat-mcp
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var $=process.getBuiltinModule("node:fs"),f=(process.env.BOSTRAT_AGENT_URL||`http://127.0.0.1:${process.env.BOSTRAT_AGENT_PORT||3362}`).replace(/\/+$/,""),u=e=>process.stderr.write(`bostrat-mcp: ${e}
|
|
3
|
+
`);function a(e=2){process.stderr.write(["usage:"," bostrat-mcp status"," bostrat-mcp login <entry> [--scope <scopes>] [--client-id <id>]"," bostrat-mcp finish <entry> <redirect-url-or-code>"," bostrat-mcp logout <entry>",""].join(`
|
|
4
|
+
`)),process.exit(e)}function y(){let e={"content-type":"application/json"},o=process.env.BOSTRAT_HOOK_TOKEN||"";o&&(e["x-bostrat-hook"]=o);let t=process.env.BOSTRAT_NODE_API_TOKEN||"";if(!t)try{t=$.readFileSync(`${process.env.BOSTRAT_STATE_DIR||`${process.env.HOME}/.bostrat`}/api-token`,"utf8").trim()}catch{}return t&&(e["x-bostrat-token"]=t),e}async function p(e,o={}){let t;try{t=await fetch(`${f}${e}`,{...o,headers:y()})}catch(s){u(`the Bostrat agent is unreachable at ${f} (${s?.message||s})`),process.exit(3)}let n=await t.json().catch(()=>({}));return t.ok||(u(n?.error||`the agent answered ${t.status}`),process.exit(3)),n}var g=e=>e?new Date(e).toISOString().replace(/\.\d{3}Z$/,"Z"):"";(async()=>{let e=process.argv.slice(2),o=e.shift();if(o==="status"){let{entries:t={},broker:n={}}=await p("/api/box/mcp/status"),s=Object.keys(t).sort();s.length||process.stdout.write(`no managed MCP entries on this node \u2014 add one on the Nodes tab
|
|
5
|
+
`);for(let c of s){let l=t[c],b=Object.entries(l.clis||{}).map(([h,r])=>`${h}=${r}`).join(" ");process.stdout.write(`${c} ${b}${l.auth==="oauth"?" (oauth)":""}
|
|
6
|
+
`);for(let[h,r]of Object.entries(l.env||{})){let d=[r.source,r.expiresAt?`expires ${g(r.expiresAt)}`:"",r.refreshable?"refreshable":"",r.error||""].filter(Boolean).join(" \xB7 ");process.stdout.write(` ${h} ${r.state}${d?` ${d}`:""}
|
|
7
|
+
`)}}let i=Object.entries(n).filter(([,c])=>c.state==="login-pending").map(([c])=>c);i.length&&process.stdout.write(`login in progress for: ${i.join(", ")} \u2014 finish it with \`bostrat-mcp finish <entry> "<url>"\`
|
|
8
|
+
`),process.exit(0)}else if(o==="login"){let t=e.shift();t||a();let n={};for(;e.length;){let i=e.shift();i==="--scope"?n.scope=e.shift():i==="--client-id"?n.clientId=e.shift():i==="--client-secret"?n.clientSecret=e.shift():a()}let s=await p("/api/box/mcp/login",{method:"POST",body:JSON.stringify({name:t,...n})});process.stdout.write([`Open this link to sign in to ${s.entry} (${s.issuer}):`,"",` ${s.authorizeUrl}`,"",`If the browser is on this machine the login completes by itself (callback ${s.redirectUri}).`,"From a phone the last page will not load \u2014 copy its address and run:",` bostrat-mcp finish ${s.entry} "<that url>"`,`The token stays on this node (${s.env} in the broker) and serves claude, codex and cursor here.`,""].join(`
|
|
9
|
+
`)),process.exit(0)}else if(o==="finish"){let[t,n]=e;(!t||!n)&&a();let s=await p("/api/box/mcp/finish",{method:"POST",body:JSON.stringify({name:t,redirect:n})});process.stdout.write(`${s.entry}: signed in on this node${s.expiresAt?` (token valid until ${g(s.expiresAt)}${s.refreshable?", refreshed in the background":" \u2014 no refresh token: log in again when it expires"})`:""}. New CLI sessions here load it; a running one picks it up at its next start.
|
|
10
|
+
`),process.exit(0)}else if(o==="logout"){let[t]=e;t||a();let n=await p("/api/box/mcp/logout",{method:"POST",body:JSON.stringify({name:t})});process.stdout.write(n.removed?`${n.entry}: token dropped \u2014 the entry reads needs-login until the next login.
|
|
11
|
+
`:`${n.entry}: no token was held.
|
|
12
|
+
`),process.exit(0)}else o==="-h"||o==="--help"||o==="help"?a(0):a()})();
|