borgmcp 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,48 +1,48 @@
1
- import{dirname as K,basename as R}from"node:path";import{randomUUID as oe}from"node:crypto";import{roleSlug as ie,matchRoleByName as ae,pickDefaultRole as se}from"./role-resolver.js";import{deriveCubeName as le,parseGitRemote as ce,sanitizeRemoteUrl as ue}from"./cube-name.js";import{validateName as q}from"./name-validator.js";import{renderAssimilationWelcome as me}from"./assimilate-welcome.js";import{shellEscape as de}from"./shell-escape.js";import{withCodexCwdArg as fe}from"./codex-remote.js";import{buildAgentKickoffPrompt as he,buildKickoffWakePathClause as ge,recordCodexWakeTarget as we,socketPathFromRemoteArgs as be}from"./codex-launch.js";import{perWorktreeBranchName as W,adoptWorktree as ke,computeWorktreePath as z,localBranchExists as J,isMerged as ye}from"./worktree-lifecycle.js";import{DroneEvictedError as ve}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as pe}from"./launch-gate.js";import{inboxPathForDrone as $e}from"./cubes.js";import{resolveLaunchEnv as xe,resolveOllamaBaseUrl as Se,parseModel as Ce}from"./model-presets.js";import{unlinkSync as Re}from"node:fs";import{gcOrphanInboxesForCube as Ee,defaultListInboxLogs as _e,defaultInboxLivenessDeps as Ie,isInboxLive as Ae,ORPHAN_INBOX_STALE_MS as Ne}from"./gc-orphan-inboxes.js";import{installBorgPlugin as Pe}from"./opencode-plugin.js";import{ensureOpenCodeServerEnvConfig as Le}from"./config-utils.js";import{computeOpenCodePort as Te}from"./opencode-drone.js";async function lt(r,e){if(r.role!==void 0){const t=q(r.role);if(!t.ok)return e.stderr(t.error+`
2
- `),1}if(r.flags.worktree!==void 0){const t=q(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
3
- `),1}let a=await e.getCachedAuth();if(!a){if(!e.isTTY()&&!r.flags.yes)return e.stderr("borg setup required and stdin is non-interactive. Run `borg setup` first in an interactive terminal, then `borg assimilate`.\n"),1;a=await e.runSetup()}const o=e.findProjectRoot(e.cwd());let i;if(r.flags.cubeName)i=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],o),n=t.status===0?t.stdout:null;if(i=le(o,n),n){const c=ue(n),m=c?ce(c):null;c&&!m&&i&&e.stderr(`couldn't parse git remote '${c}' \u2014 using directory name '${i}' as cube name
4
- `)}}let s=null;if(i&&i.includes("@")&&i.includes(":")){const t=i.lastIndexOf(":");s={ownerEmail:i.substring(0,t),cubeName:i.substring(t+1)},i=s.cubeName}const E=e.cwd();e.stderr(`Checking your cubes\u2026
5
- `);let I;try{I=await e.listCubes(a.apiUrl,a.token)}catch(t){const n=t instanceof Error?t.message:String(t);if(n.includes("Authentication required")||n.includes("Authentication expired"))e.stderr(`Re-authenticating...
6
- `),a=await e.runSetup(),I=await e.listCubes(a.apiUrl,a.token);else throw t}const A=I.find(t=>t.name===i);if(!A&&s)return e.stderr(`No cube named '${s.cubeName}' accessible to you owned by '${s.ownerEmail}'. Did you accept their invite? See borgmcp.ai/dashboard.
7
- `),1;let u,N;if(A)u=await e.getCube(a.apiUrl,a.token,A.id),N=!1;else{let t;if(r.flags.template)t=r.flags.template;else if(r.flags.noTemplate)t=void 0;else if(e.isTTY())if(r.flags.yes)t="starter";else{const n=await e.listTemplates(a.apiUrl,a.token),c=["First drone joining a new cube. Apply a template?"];n.forEach((p,$)=>{const _=$===0?" (default)":"";c.push(` ${$+1}) ${p.name}${_} \u2014 ${p.description}`)}),c.push(` ${n.length+1}) skip \u2014 no template`);const m=(await e.prompt(c.join(`
1
+ import{dirname as z,basename as R}from"node:path";import{randomUUID as ae}from"node:crypto";import{roleSlug as le,matchRoleByName as se,pickDefaultRole as ce}from"./role-resolver.js";import{deriveCubeName as ue,parseGitRemote as me,sanitizeRemoteUrl as de}from"./cube-name.js";import{validateName as J}from"./name-validator.js";import{renderAssimilationWelcome as fe}from"./assimilate-welcome.js";import{shellEscape as he}from"./shell-escape.js";import{withCodexCwdArg as ge}from"./codex-remote.js";import{buildAgentKickoffPrompt as we,buildKickoffWakePathClause as be,recordCodexWakeTarget as ke,socketPathFromRemoteArgs as ye}from"./codex-launch.js";import{perWorktreeBranchName as M,adoptWorktree as pe,computeWorktreePath as V,localBranchExists as X,isMerged as ve}from"./worktree-lifecycle.js";import{DroneEvictedError as $e}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as xe}from"./launch-gate.js";import{inboxPathForDrone as Se}from"./cubes.js";import{resolveLaunchEnv as Ce,resolveOllamaBaseUrl as Re,parseModel as _e}from"./model-presets.js";import{unlinkSync as Ee}from"node:fs";import{gcOrphanInboxesForCube as Ie,defaultListInboxLogs as Ne,defaultInboxLivenessDeps as Ae,isInboxLive as Pe,ORPHAN_INBOX_STALE_MS as Le}from"./gc-orphan-inboxes.js";import{installBorgPlugin as Te}from"./opencode-plugin.js";import{ensureOpenCodeServerEnvConfig as De}from"./config-utils.js";import{computeOpenCodePort as Oe,connectOpenCodeDrone as Ue,injectInitialKickoff as je}from"./opencode-drone.js";async function mt(r,e){if(r.role!==void 0){const t=J(r.role);if(!t.ok)return e.stderr(t.error+`
2
+ `),1}if(r.flags.worktree!==void 0){const t=J(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
3
+ `),1}let a=await e.getCachedAuth();if(!a){if(!e.isTTY()&&!r.flags.yes)return e.stderr("borg setup required and stdin is non-interactive. Run `borg setup` first in an interactive terminal, then `borg assimilate`.\n"),1;a=await e.runSetup()}const o=e.findProjectRoot(e.cwd());let i;if(r.flags.cubeName)i=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],o),n=t.status===0?t.stdout:null;if(i=ue(o,n),n){const c=de(n),f=c?me(c):null;c&&!f&&i&&e.stderr(`couldn't parse git remote '${c}' \u2014 using directory name '${i}' as cube name
4
+ `)}}let l=null;if(i&&i.includes("@")&&i.includes(":")){const t=i.lastIndexOf(":");l={ownerEmail:i.substring(0,t),cubeName:i.substring(t+1)},i=l.cubeName}const _=e.cwd();e.stderr(`Checking your cubes\u2026
5
+ `);let N;try{N=await e.listCubes(a.apiUrl,a.token)}catch(t){const n=t instanceof Error?t.message:String(t);if(n.includes("Authentication required")||n.includes("Authentication expired"))e.stderr(`Re-authenticating...
6
+ `),a=await e.runSetup(),N=await e.listCubes(a.apiUrl,a.token);else throw t}const A=N.find(t=>t.name===i);if(!A&&l)return e.stderr(`No cube named '${l.cubeName}' accessible to you owned by '${l.ownerEmail}'. Did you accept their invite? See borgmcp.ai/dashboard.
7
+ `),1;let u,P;if(A)u=await e.getCube(a.apiUrl,a.token,A.id),P=!1;else{let t;if(r.flags.template)t=r.flags.template;else if(r.flags.noTemplate)t=void 0;else if(e.isTTY())if(r.flags.yes)t="starter";else{const n=await e.listTemplates(a.apiUrl,a.token),c=["First drone joining a new cube. Apply a template?"];n.forEach((v,$)=>{const I=$===0?" (default)":"";c.push(` ${$+1}) ${v.name}${I} \u2014 ${v.description}`)}),c.push(` ${n.length+1}) skip \u2014 no template`);const f=(await e.prompt(c.join(`
8
8
  `)+`
9
- [1]: `)).trim(),y=m===""?1:parseInt(m,10);if(Number.isNaN(y)||y<1||y>n.length+1)return e.stderr(`invalid choice "${m}"
9
+ [1]: `)).trim(),y=f===""?1:parseInt(f,10);if(Number.isNaN(y)||y<1||y>n.length+1)return e.stderr(`invalid choice "${f}"
10
10
  `),1;t=y<=n.length?n[y-1].name:void 0}else{if(!r.flags.yes)return e.stderr(`cube creation needs a template choice but stdin is non-interactive.
11
11
  Pass --template <name>, --no-template, or --yes (defaults to starter).
12
12
  `),1;t="starter"}e.stderr(i?`Creating cube '${i}'\u2026
13
13
  `:`Creating your cube\u2026
14
- `),u=await e.createCube(a.apiUrl,a.token,t?{name:i??void 0,template:t}:{name:i??void 0}),N=!0}let g;if(r.role!==void 0){if(g=ae(u.roles,r.role),!g){const t=u.roles.map(m=>m.name).join(", "),n=je(r.role,u.roles.map(m=>m.name)),c=n?` Did you mean "${n}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${u.name}". Available: ${t}.${c}
14
+ `),u=await e.createCube(a.apiUrl,a.token,t?{name:i??void 0,template:t}:{name:i??void 0}),P=!0}let g;if(r.role!==void 0){if(g=se(u.roles,r.role),!g){const t=u.roles.map(f=>f.name).join(", "),n=He(r.role,u.roles.map(f=>f.name)),c=n?` Did you mean "${n}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${u.name}". Available: ${t}.${c}
15
15
  (Use --template <name> on first-drone setup or run \`borg_create-role\` from inside Claude.)
16
- `),1}}else{const t=new Set((u.drones??[]).map(n=>n.role_id));if(g=se(u.roles,{isFirstDrone:N,occupiedRoleIds:t}),!g)return e.stderr(`cube "${u.name}" has no default or human-seat role; cannot infer a role. Either pass a role argument explicitly (e.g. \`borg assimilate builder\`) or run \`borg_create-role\` from inside Claude to set up roles.
16
+ `),1}}else{const t=new Set((u.drones??[]).map(n=>n.role_id));if(g=ce(u.roles,{isFirstDrone:P,occupiedRoleIds:t}),!g)return e.stderr(`cube "${u.name}" has no default or human-seat role; cannot infer a role. Either pass a role argument explicitly (e.g. \`borg assimilate builder\`) or run \`borg_create-role\` from inside Claude to set up roles.
17
17
  `),1}const x=await e.getActiveCube();let S;if(x&&r.flags.here)if(x.cubeId===u.id)S=x.droneId;else return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
18
- `),1;const M=r.flags.worktree!==void 0||x!==null&&!r.flags.here,X=S??x?.droneId??null,P=M?null:await e.getLaunchModel(u.id,o,X),b=r.flags.model??P?.model??g.default_model??null,L=Se(process.env,b!=null&&b===P?.model?P?.ollamaBaseUrl:void 0);if(b){const t=await e.checkModelReachable(b,e.fetch,L);if(!t.ok)return e.stderr(`${t.message}
19
- `),1}const d=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${u.name}' as ${g.name}\u2026
20
- `);let l;try{l=await e.assimilate(a.apiUrl,a.token,{cube_id:u.id,role_id:g.id,hostname:e.getHostname(),agent_kind:d,model:b,...S?{prior_drone_id:S}:{}})}catch(t){if(t instanceof ve&&S!=null)return e.stderr(`seat evicted \u2014 this worktree's saved seat was evicted from the cube. Re-assimilate fresh from a terminal, or remove this worktree.
18
+ `),1;const B=r.flags.worktree!==void 0||x!==null&&!r.flags.here,Z=S??x?.droneId??null,L=B?null:await e.getLaunchModel(u.id,o,Z),b=r.flags.model??L?.model??g.default_model??null,T=Re(process.env,b!=null&&b===L?.model?L?.ollamaBaseUrl:void 0);if(b){const t=await e.checkModelReachable(b,e.fetch,T);if(!t.ok)return e.stderr(`${t.message}
19
+ `),1}const m=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${u.name}' as ${g.name}\u2026
20
+ `);let s;try{s=await e.assimilate(a.apiUrl,a.token,{cube_id:u.id,role_id:g.id,hostname:e.getHostname(),agent_kind:m,model:b,...S?{prior_drone_id:S}:{}})}catch(t){if(t instanceof $e&&S!=null)return e.stderr(`seat evicted \u2014 this worktree's saved seat was evicted from the cube. Re-assimilate fresh from a terminal, or remove this worktree.
21
21
  `),1;const n=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${n}
22
- `),1}const k=u.roles.find(t=>t.id===l.role_id)??g;l.reattached?e.stderr(`re-attached to existing seat ${l.drone_label} (session token rotated, no new drone minted)
22
+ `),1}const k=u.roles.find(t=>t.id===s.role_id)??g;s.reattached?e.stderr(`re-attached to existing seat ${s.drone_label} (session token rotated, no new drone minted)
23
23
  `):k.id!==g.id&&e.stderr(`Note: your invite didn't grant the "${g.name}" role \u2014 assimilated as "${k.name}" instead.
24
- `);const Q=M;let w=null;if(Q){const t=e.runSync("git",["rev-parse","--verify","HEAD"],o);if(t.status!==0)return e.stderr(`sibling worktree spawn requires HEAD pointing at a commit.
24
+ `);const ee=B;let w=null;if(ee){const t=e.runSync("git",["rev-parse","--verify","HEAD"],o);if(t.status!==0)return e.stderr(`sibling worktree spawn requires HEAD pointing at a commit.
25
25
  Fix: create at least one commit (\`git commit --allow-empty -m "initial"\`)
26
26
  OR: pass --here to skip the sibling spawn and use the current directory
27
- `),1;e.runSync("git",["fetch","origin"],o);let n="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],o).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],o).status===0&&(n="origin/master");const m=t.stdout.trim(),y=e.runSync("git",["rev-parse",n],o).stdout.trim();m!==y&&e.stderr(`note: local HEAD (${m.slice(0,7)}) differs from ${n} (${y.slice(0,7)}); new worktree will start on ${n}
28
- `);const p=R(o),$=r.flags.worktree??ie(k.name);if($.length===0)return e.stderr(`cannot derive a worktree name from role "${k.name}"; pass an explicit --worktree <name>
29
- `),1;const _=e.homedir();let h=z(_,p,$),v=W(R(h),p),Y=2;for(;e.pathExists(h)||Ue(e,o,h)||J(e.runSync,o,v)&&!ye(e.runSync,o,v,n);)h=z(_,p,$,Y),v=W(R(h),p),Y++;e.mkdirp(K(h));const G=J(e.runSync,o,v)?e.runSync("git",["worktree","add",h,v],o):e.runSync("git",["worktree","add","-b",v,h,n],o);if(G.status!==0)return e.stderr(`git worktree add failed: ${V(G.stderr)}
30
- `),1;e.stderr(`spawned sibling worktree at ${h} on branch ${v} (${n}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
31
- `),e.chdir(h),e.stderr(De(h,v,o)),w=e.cwd()}try{await e.setActiveCube({cubeId:l.cube_id,droneId:l.drone_id,name:u.name,sessionToken:l.session_token,droneLabel:l.drone_label,apiUrl:a.apiUrl,roleName:k.name,isHumanSeat:k.is_human_seat,...k.role_class?{roleClass:k.role_class}:{}})}catch(t){const n=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${n}
27
+ `),1;e.runSync("git",["fetch","origin"],o);let n="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],o).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],o).status===0&&(n="origin/master");const f=t.stdout.trim(),y=e.runSync("git",["rev-parse",n],o).stdout.trim();f!==y&&e.stderr(`note: local HEAD (${f.slice(0,7)}) differs from ${n} (${y.slice(0,7)}); new worktree will start on ${n}
28
+ `);const v=R(o),$=r.flags.worktree??le(k.name);if($.length===0)return e.stderr(`cannot derive a worktree name from role "${k.name}"; pass an explicit --worktree <name>
29
+ `),1;const I=e.homedir();let h=V(I,v,$),p=M(R(h),v),G=2;for(;e.pathExists(h)||Be(e,o,h)||X(e.runSync,o,p)&&!ve(e.runSync,o,p,n);)h=V(I,v,$,G),p=M(R(h),v),G++;e.mkdirp(z(h));const q=X(e.runSync,o,p)?e.runSync("git",["worktree","add",h,p],o):e.runSync("git",["worktree","add","-b",p,h,n],o);if(q.status!==0)return e.stderr(`git worktree add failed: ${Q(q.stderr)}
30
+ `),1;e.stderr(`spawned sibling worktree at ${h} on branch ${p} (${n}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
31
+ `),e.chdir(h),e.stderr(We(h,p,o)),w=e.cwd()}try{await e.setActiveCube({cubeId:s.cube_id,droneId:s.drone_id,name:u.name,sessionToken:s.session_token,droneLabel:s.drone_label,apiUrl:a.apiUrl,roleName:k.name,isHumanSeat:k.is_human_seat,...k.role_class?{roleClass:k.role_class}:{}})}catch(t){const n=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${n}
32
32
  `),w){const c=e.runSync("git",["worktree","remove","--force",w],o);c.status===0?e.stderr(`rolled back spawned worktree at ${w}
33
- `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${w}\` (rollback attempt failed: ${V(c.stderr).trim()||"unknown"})
34
- `)}return 1}try{const t=Ie(),n=K($e(l.cube_id,l.drone_id));Ee({cubeInboxDir:n,selfDroneId:l.drone_id,deps:{listInboxLogs:_e,isLive:c=>Ae(c,t),droneState:()=>"absent",unlink:c=>Re(c),now:t.now,staleMs:Ne}})}catch{}e.setTerminalTitle(l.drone_label,u.name);const Z=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(me(k.name,u.name,Z));const f=e.cwd();try{e.installProjectSessionHook(f)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${f}; it will be re-attempted on the next borg launch
35
- `)}if(!w){e.runSync("git",["fetch","origin","--prune"],f);const t=W(R(f),R(o)),n=ke(e.runSync,f,t,"origin/main");n.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
36
- `),e.stderr(Oe(f,t))):n.message&&e.stderr(`worktree sync: ${n.message}
37
- `)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${d} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
38
- `);const ee=e.getInboxPath(l.cube_id,l.drone_id),T=d==="codex"?`borg-wake-${oe()}`:null,te=ge(d,d==="claude"?ee:null);let D,B=[],C,O=null,U=null;const H=e.findProjectRoot(f);b?await e.setLaunchModel(l.cube_id,H,{model:b,ollamaBaseUrl:Ce(b).kind==="ollama"?L:null}):await e.clearLaunchModel(l.cube_id,H);const F=xe(b,L),j={...process.env,...F.set,BORG_SESSION:"1"};for(const t of F.unset)delete j[t];if(d==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
39
- `),D="\u26A0 Codex wake-path capability check failed: remote-control is unavailable for this session. Run borg_regen manually whenever you return, and expect only fallback wakeups until relaunch."):D="Codex wake-path capability check passed: remote-control socket established for this session.",B=t.args,Object.keys(t.env).length>0&&Object.assign(j,t.env),O=be(t.args),U=t.server?.cleanup??null}if(C=[he({cli:d,codexWakeNonce:T,monitorClause:te,codexWakePathClause:D})],d==="codex")C=[...pe(),...B,...fe(C,f)];else if(d==="opencode"){const t=Te(l.drone_id);Pe(),Le(t),C=[f,"--port",String(t),"--auto"]}const re=e.exec(d,C,f,j);d==="codex"&&O&&T&&we({deps:e,cubeId:l.cube_id,droneId:l.drone_id,socketPath:O,cwd:f,previewNeedle:T,launchedAtSeconds:Math.floor(Date.now()/1e3)});const ne=await re;if(U)try{U()}catch{}return w&&E!==w&&e.stderr(`
40
- Agent exited. You were working in ${w}; your shell is back in ${E}.
33
+ `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${w}\` (rollback attempt failed: ${Q(c.stderr).trim()||"unknown"})
34
+ `)}return 1}try{const t=Ae(),n=z(Se(s.cube_id,s.drone_id));Ie({cubeInboxDir:n,selfDroneId:s.drone_id,deps:{listInboxLogs:Ne,isLive:c=>Pe(c,t),droneState:()=>"absent",unlink:c=>Ee(c),now:t.now,staleMs:Le}})}catch{}e.setTerminalTitle(s.drone_label,u.name);const te=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(fe(k.name,u.name,te));const d=e.cwd();try{e.installProjectSessionHook(d)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${d}; it will be re-attempted on the next borg launch
35
+ `)}if(!w){e.runSync("git",["fetch","origin","--prune"],d);const t=M(R(d),R(o)),n=pe(e.runSync,d,t,"origin/main");n.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
36
+ `),e.stderr(Me(d,t))):n.message&&e.stderr(`worktree sync: ${n.message}
37
+ `)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${m} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
38
+ `);const re=e.getInboxPath(s.cube_id,s.drone_id),D=m==="codex"?`borg-wake-${ae()}`:null,ne=be(m,m==="claude"?re:null);let O,H=[],C,U=null,j=null;const F=e.findProjectRoot(d);b?await e.setLaunchModel(s.cube_id,F,{model:b,ollamaBaseUrl:_e(b).kind==="ollama"?T:null}):await e.clearLaunchModel(s.cube_id,F);const K=Ce(b,T),W={...process.env,...K.set,BORG_SESSION:"1"};for(const t of K.unset)delete W[t];if(m==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
39
+ `),O="\u26A0 Codex wake-path capability check failed: remote-control is unavailable for this session. Run borg_regen manually whenever you return, and expect only fallback wakeups until relaunch."):O="Codex wake-path capability check passed: remote-control socket established for this session.",H=t.args,Object.keys(t.env).length>0&&Object.assign(W,t.env),U=ye(t.args),j=t.server?.cleanup??null}const Y=we({cli:m,codexWakeNonce:D,monitorClause:ne,codexWakePathClause:O});let E;C=[Y],m==="codex"?C=[...xe(),...H,...ge(C,d)]:m==="opencode"&&(E=Oe(s.drone_id),Te(),De(E),C=[d,"--port",String(E),"--auto"]);const oe=e.exec(m,C,d,W);if(m==="codex"&&U&&D&&ke({deps:e,cubeId:s.cube_id,droneId:s.drone_id,socketPath:U,cwd:d,previewNeedle:D,launchedAtSeconds:Math.floor(Date.now()/1e3)}),m==="opencode"){const t=`http://127.0.0.1:${E}`;Ue({serverUrl:t,directory:d,droneLabel:s.drone_label,cubeName:i??"borg"}).then(()=>je(Y)).catch(()=>{})}const ie=await oe;if(j)try{j()}catch{}return w&&_!==w&&e.stderr(`
40
+ Agent exited. You were working in ${w}; your shell is back in ${_}.
41
41
  To return:
42
- cd ${de(w)}
43
- `),ne}function De(r,e,a){return`
42
+ cd ${he(w)}
43
+ `),ie}function We(r,e,a){return`
44
44
  WORKTREE STEERING: You are in worktree ${r} on branch ${e}. Do ALL work HERE \u2014 cut your feature branch (fix/.../feat/...) off ${e} in THIS worktree, use relative paths / your cwd. NEVER \`git -C ${a}\` or operate on the primary checkout ${a}: the same branch can't be checked out in two worktrees, so work created in the primary won't reach your wt-branch without manual surgery (cherry-pick/merge).
45
- `}function Oe(r,e){return`
45
+ `}function Me(r,e){return`
46
46
  WORKTREE STEERING: This checkout is now on branch ${e}. Do ALL work HERE in ${r} \u2014 cut your feature branch (fix/.../feat/...) off ${e}, use relative paths / your cwd.
47
- `}function V(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function Ue(r,e,a){const o=r.runSync("git",["worktree","list","--porcelain"],e);return o.status!==0?!1:o.stdout.split(`
48
- `).some(i=>i===`worktree ${a}`)}function je(r,e){if(e.length===0)return null;const a=r.toLowerCase();let o=null;for(const i of e){const s=We(a,i.toLowerCase());s<=2&&(o===null||s<o.distance)&&(o={name:i,distance:s})}return o?o.name:null}function We(r,e){if(r===e)return 0;if(r.length===0)return e.length;if(e.length===0)return r.length;const a=new Array(e.length+1),o=new Array(e.length+1);for(let i=0;i<=e.length;i++)a[i]=i;for(let i=1;i<=r.length;i++){o[0]=i;for(let s=1;s<=e.length;s++){const E=r[i-1]===e[s-1]?0:1;o[s]=Math.min(o[s-1]+1,a[s]+1,a[s-1]+E)}for(let s=0;s<=e.length;s++)a[s]=o[s]}return a[e.length]}export{lt as runAssimilate,V as safeStderr,je as suggestRoleName};
47
+ `}function Q(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function Be(r,e,a){const o=r.runSync("git",["worktree","list","--porcelain"],e);return o.status!==0?!1:o.stdout.split(`
48
+ `).some(i=>i===`worktree ${a}`)}function He(r,e){if(e.length===0)return null;const a=r.toLowerCase();let o=null;for(const i of e){const l=Fe(a,i.toLowerCase());l<=2&&(o===null||l<o.distance)&&(o={name:i,distance:l})}return o?o.name:null}function Fe(r,e){if(r===e)return 0;if(r.length===0)return e.length;if(e.length===0)return r.length;const a=new Array(e.length+1),o=new Array(e.length+1);for(let i=0;i<=e.length;i++)a[i]=i;for(let i=1;i<=r.length;i++){o[0]=i;for(let l=1;l<=e.length;l++){const _=r[i-1]===e[l-1]?0:1;o[l]=Math.min(o[l-1]+1,a[l]+1,a[l-1]+_)}for(let l=0;l<=e.length;l++)a[l]=o[l]}return a[e.length]}export{mt as runAssimilate,Q as safeStderr,He as suggestRoleName};
package/dist/claude.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as B}from"child_process";import{randomUUID as F}from"node:crypto";import{basename as _}from"node:path";import{createInterface as W}from"node:readline/promises";import i from"chalk";import{findProjectRoot as I,getActiveCube as G,getLaunchModel as Y,inboxPathForDrone as U,setCodexWakeTarget as V,pruneDeadCodexWakeTargets as K}from"./cubes.js";import{applyOllamaLaunchEnv as j,checkModelReachable as q}from"./model-presets.js";import{handleVersionFlag as X,getPackageVersion as h}from"./version.js";import{isHelpFlag as L,setupHelpText as z,topLevelHelpText as J,assimilateHelpText as Q}from"./cli-help.js";import{runSpawn as Z}from"./spawn.js";import{buildClaudeLaunchArgs as ee}from"./claude-launch-args.js";import{parseSyncArgs as re,runSync as oe}from"./sync.js";import{parseCleanupArgs as se,runCleanup as te}from"./cleanup-cmd.js";import{parseAssimilateArgs as ie}from"./parse-assimilate-args.js";import{runAssimilate as ae}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as ne}from"./assimilate-deps.js";import{parseLaunchAllArgs as R}from"./parse-launch-all-args.js";import{unknownSubcommand as ce}from"./unknown-subcommand.js";import{runLaunchAll as O}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as k}from"./launch-all-deps.js";import{discoverDroneCandidates as le}from"./launch-all-discovery.js";import{explicitCliLaunchHint as de,runBareLaunchMenu as pe,shouldResolveExplicitCliLaunchHintTargets as ue,shouldShowLaunchMenu as me}from"./bare-launch-menu.js";import{setTerminalTitle as fe}from"./terminal-title.js";import{initConsolePrefix as ge,consolePrefix as t}from"./console-prefix.js";import{initDebugFromArgv as he}from"./debug.js";import{fetchLatestBorgmcpVersion as we,compareVersionsForStaleness as Ce}from"./stale-version-check.js";import{defaultCliChoiceDeps as xe,detectCliAvailability as b,installedCliNames as D,parseCliFlag as ve,resolveCliChoice as ke}from"./cli-platform.js";import{getRefreshToken as be,getIdToken as $e}from"./config.js";import{composeGetStarted as Se,shouldShowGetStarted as ye}from"./get-started.js";import{prepareCodexRemoteLaunch as Ae,withCodexCwdArg as Te,defaultCodexRemoteDeps as Pe,checkCodexBridgeHealthy as Ie}from"./codex-remote.js";import{findLoadedCodexThread as Le}from"./codex-app-server.js";import{buildAgentKickoffPrompt as Re,buildKickoffWakePathClause as Oe,recordCodexWakeTarget as De,socketPathFromRemoteArgs as E}from"./codex-launch.js";import{codexBorgSessionConfigArgs as Ee}from"./launch-gate.js";import{addCodexMcpServer as Me,addCodexSessionStartHook as He,addCodexUserPromptSubmitHook as Ne,addMcpServer as Be,addOpenCodeMcpServer as Fe,addProjectSessionStartHook as _e,addUserPromptSubmitHook as We,ensureOpenCodeServerEnvConfig as Ge,isCodexMcpServerConfigured as Ye,isMcpServerConfigured as Ue,isOpenCodeMcpServerConfigured as Ve,removeSessionStartHook as Ke}from"./config-utils.js";import{installBorgPlugin as je}from"./opencode-plugin.js";import{computeOpenCodePort as qe}from"./opencode-drone.js";async function Xe(){he(process.argv),X(),await ge();const c=(async()=>{if(!process.stderr.isTTY)return;const e=h(),r=await we();if(!r)return;const a=Ce(e,r);a.stale&&a.message&&process.stderr.write(`${t()}${a.message}
3
- `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(J(h())),process.exit(0)),process.argv[2]==="setup"){L(process.argv[3])&&(process.stdout.write(z(h())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(L)&&(process.stdout.write(Q(h())),process.exit(0));const e=ie(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg assimilate: ${e.error}
4
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=ne(),a=await ae({role:e.role,flags:e.flags},r);process.exit(a)}if(process.argv[2]==="spawn"){const e=await Z();process.exit(e)}if(process.argv[2]==="sync"){const e=re(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg sync: ${e.error}
5
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await oe({},e.options);process.exit(r)}if(process.argv[2]==="cleanup"){const e=se(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg cleanup: ${e.error}
6
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await te({},e.options);process.exit(r)}if(process.argv[2]==="launch-all"){const e=R(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg launch-all: ${e.error}
7
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=k(),a=await O(e.args,r);process.exit(a)}const n=ce(process.argv[2]);if(n!==null&&(process.stderr.write(i.red(`${t()}\u25FC unknown command: ${n}
8
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1)),ye(await be()!==null,await $e()!==null)){const e=D(b()).length>0;process.stdout.write(Se(e)),process.exit(0)}const d=ve(process.argv.slice(2));d.error&&(process.stderr.write(i.red(`${t()}\u25FC ${d.error}
9
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const $=async e=>{const r=W({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let o=await ke(d.cli,xe($,()=>process.stdin.isTTY===!0));ze();const s=await G();let w;const S=async()=>s?(w===void 0&&(w=(await le({targetCubeId:s.cubeId},k())).length>0),w):!1,C=process.stdin.isTTY===!0,x=process.stdout.isTTY===!0,y=de({explicitCli:d.cli,stdinIsTTY:C,stdoutIsTTY:x,hasActiveCube:s!==null,hasLaunchAllTargets:ue({explicitCli:d.cli,stdinIsTTY:C,stdoutIsTTY:x,hasActiveCube:s!==null})?await S():!1});if(y&&process.stderr.write(y),me({extraArgs:process.argv.slice(2),stdinIsTTY:C,stdoutIsTTY:x})){const e=D(b()).filter(a=>a!==o),r=await pe({defaultCli:o,otherInstalledClis:e,hasLaunchAllTargets:await S()},$);if(r.kind==="launch-all"){const a=R([]),N=a.ok?await O(a.args,k()):1;process.exit(N)}o=r.cli}const l=d.rest;fe(s?{label:s.droneLabel,cubeName:s.name}:null,_(process.cwd()));const M=Oe(o,s&&o==="claude"?U(s.cubeId,s.droneId):null);await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const A=o==="codex"?`borg-wake-${F()}`:null;let f,T=[],p={...process.env,BORG_SESSION:"1"},u=null,m=null;if(o==="codex"&&!l.includes("--remote")){console.error(`${t()}${i.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await Ae(Pe());e.warning?(console.error(`${t()}${i.yellow(`warning: ${e.warning}`)}`),f="\u26A0 Codex wake-path capability check failed: remote-control is unavailable for this session. Run borg_regen manually whenever you return, and expect only fallback wakeups until relaunch."):f="Codex wake-path capability check passed: remote-control socket established for this session.",T=e.args,p={...process.env,...e.env,BORG_SESSION:"1"},u=E(e.args),m=e.server?.cleanup??null}else o==="codex"&&l.includes("--remote")&&(f="Codex wake-path capability check: using caller-provided --remote socket; if no wake arrives, run borg_regen manually when returning to the session.",u=E(l),u&&(p={...process.env,BORG_CODEX_REMOTE_WAKE:"1",BORG_SESSION:"1"}));if(s){const e=await Y(s.cubeId,I(),s.droneId),r=j(p,e,process.env);if(p=r.env,r.probe){const a=await q(r.probe.descriptor,fetch,r.probe.baseUrl);a.ok||console.error(`${t()}${i.yellow(`warning: ${a.message}`)}`)}}const v=Re({cli:o,codexWakeNonce:A,monitorClause:M,codexWakePathClause:f});let g;if(o==="codex")g=[...Ee(),...T,...Te([...l,v],process.cwd())];else if(o==="opencode"){const e=s?qe(s.droneId):14096;je(),Ge(e),g=[process.cwd(),"--port",String(e),"--auto",...l]}else g=ee(l,v);const H=o==="claude"?"Claude Code":o==="codex"?"Codex":"OpenCode";console.error(`${t()}${i.blue(`\u25FC Launching ${H}\u2026`)}`);const P=B(o,g,{stdio:"inherit",shell:!1,env:p});o==="codex"&&s&&u&&(De({deps:{setCodexWakeTarget:V,findLoadedCodexThread:Le},cubeId:s.cubeId,droneId:s.droneId,socketPath:u,passthroughArgs:l,previewNeedle:A??v.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),K(e=>Ie(e))),P.on("error",e=>{if(m)try{m()}catch{}if(e.code==="ENOENT"){console.error(`${t()}${i.red(`
10
- \u25FC Failed to launch ${o}`)}`);const r=o==="opencode"?"opencode":o;console.error(`${t()}${i.gray(`Make sure ${r} is installed.
2
+ import{spawn as F}from"child_process";import{randomUUID as _}from"node:crypto";import{basename as W}from"node:path";import{createInterface as G}from"node:readline/promises";import i from"chalk";import{findProjectRoot as I,getActiveCube as U,getLaunchModel as Y,inboxPathForDrone as K,setCodexWakeTarget as V,pruneDeadCodexWakeTargets as j}from"./cubes.js";import{applyOllamaLaunchEnv as q,checkModelReachable as X}from"./model-presets.js";import{handleVersionFlag as z,getPackageVersion as w}from"./version.js";import{isHelpFlag as L,setupHelpText as J,topLevelHelpText as Q,assimilateHelpText as Z}from"./cli-help.js";import{runSpawn as ee}from"./spawn.js";import{buildClaudeLaunchArgs as re}from"./claude-launch-args.js";import{parseSyncArgs as oe,runSync as se}from"./sync.js";import{parseCleanupArgs as te,runCleanup as ie}from"./cleanup-cmd.js";import{parseAssimilateArgs as ae}from"./parse-assimilate-args.js";import{runAssimilate as ne}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as ce}from"./assimilate-deps.js";import{parseLaunchAllArgs as R}from"./parse-launch-all-args.js";import{unknownSubcommand as le}from"./unknown-subcommand.js";import{runLaunchAll as O}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as b}from"./launch-all-deps.js";import{discoverDroneCandidates as de}from"./launch-all-discovery.js";import{explicitCliLaunchHint as pe,runBareLaunchMenu as ue,shouldResolveExplicitCliLaunchHintTargets as me,shouldShowLaunchMenu as fe}from"./bare-launch-menu.js";import{setTerminalTitle as ge}from"./terminal-title.js";import{initConsolePrefix as he,consolePrefix as t}from"./console-prefix.js";import{initDebugFromArgv as we}from"./debug.js";import{fetchLatestBorgmcpVersion as Ce,compareVersionsForStaleness as xe}from"./stale-version-check.js";import{defaultCliChoiceDeps as ve,detectCliAvailability as k,installedCliNames as D,parseCliFlag as be,resolveCliChoice as ke}from"./cli-platform.js";import{getRefreshToken as $e,getIdToken as Se}from"./config.js";import{composeGetStarted as ye,shouldShowGetStarted as Ae}from"./get-started.js";import{prepareCodexRemoteLaunch as Te,withCodexCwdArg as Pe,defaultCodexRemoteDeps as Ie,checkCodexBridgeHealthy as Le}from"./codex-remote.js";import{findLoadedCodexThread as Re}from"./codex-app-server.js";import{buildAgentKickoffPrompt as Oe,buildKickoffWakePathClause as De,recordCodexWakeTarget as Ee,socketPathFromRemoteArgs as E}from"./codex-launch.js";import{codexBorgSessionConfigArgs as Me}from"./launch-gate.js";import{addCodexMcpServer as He,addCodexSessionStartHook as Ne,addCodexUserPromptSubmitHook as Be,addMcpServer as Fe,addOpenCodeMcpServer as _e,addProjectSessionStartHook as We,addUserPromptSubmitHook as Ge,ensureOpenCodeServerEnvConfig as Ue,isCodexMcpServerConfigured as Ye,isMcpServerConfigured as Ke,isOpenCodeMcpServerConfigured as Ve,removeSessionStartHook as je}from"./config-utils.js";import{installBorgPlugin as qe}from"./opencode-plugin.js";import{connectOpenCodeDrone as Xe,computeOpenCodePort as M,injectInitialKickoff as ze}from"./opencode-drone.js";async function Je(){we(process.argv),z(),await he();const c=(async()=>{if(!process.stderr.isTTY)return;const e=w(),r=await Ce();if(!r)return;const a=xe(e,r);a.stale&&a.message&&process.stderr.write(`${t()}${a.message}
3
+ `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(Q(w())),process.exit(0)),process.argv[2]==="setup"){L(process.argv[3])&&(process.stdout.write(J(w())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(L)&&(process.stdout.write(Z(w())),process.exit(0));const e=ae(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg assimilate: ${e.error}
4
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=ce(),a=await ne({role:e.role,flags:e.flags},r);process.exit(a)}if(process.argv[2]==="spawn"){const e=await ee();process.exit(e)}if(process.argv[2]==="sync"){const e=oe(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg sync: ${e.error}
5
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await se({},e.options);process.exit(r)}if(process.argv[2]==="cleanup"){const e=te(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg cleanup: ${e.error}
6
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await ie({},e.options);process.exit(r)}if(process.argv[2]==="launch-all"){const e=R(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg launch-all: ${e.error}
7
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=b(),a=await O(e.args,r);process.exit(a)}const n=le(process.argv[2]);if(n!==null&&(process.stderr.write(i.red(`${t()}\u25FC unknown command: ${n}
8
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1)),Ae(await $e()!==null,await Se()!==null)){const e=D(k()).length>0;process.stdout.write(ye(e)),process.exit(0)}const d=be(process.argv.slice(2));d.error&&(process.stderr.write(i.red(`${t()}\u25FC ${d.error}
9
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const $=async e=>{const r=G({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let s=await ke(d.cli,ve($,()=>process.stdin.isTTY===!0));Qe();const o=await U();let C;const S=async()=>o?(C===void 0&&(C=(await de({targetCubeId:o.cubeId},b())).length>0),C):!1,x=process.stdin.isTTY===!0,v=process.stdout.isTTY===!0,y=pe({explicitCli:d.cli,stdinIsTTY:x,stdoutIsTTY:v,hasActiveCube:o!==null,hasLaunchAllTargets:me({explicitCli:d.cli,stdinIsTTY:x,stdoutIsTTY:v,hasActiveCube:o!==null})?await S():!1});if(y&&process.stderr.write(y),fe({extraArgs:process.argv.slice(2),stdinIsTTY:x,stdoutIsTTY:v})){const e=D(k()).filter(a=>a!==s),r=await ue({defaultCli:s,otherInstalledClis:e,hasLaunchAllTargets:await S()},$);if(r.kind==="launch-all"){const a=R([]),B=a.ok?await O(a.args,b()):1;process.exit(B)}s=r.cli}const l=d.rest;ge(o?{label:o.droneLabel,cubeName:o.name}:null,W(process.cwd()));const H=De(s,o&&s==="claude"?K(o.cubeId,o.droneId):null);await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const A=s==="codex"?`borg-wake-${_()}`:null;let f,T=[],p={...process.env,BORG_SESSION:"1"},u=null,m=null;if(s==="codex"&&!l.includes("--remote")){console.error(`${t()}${i.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await Te(Ie());e.warning?(console.error(`${t()}${i.yellow(`warning: ${e.warning}`)}`),f="\u26A0 Codex wake-path capability check failed: remote-control is unavailable for this session. Run borg_regen manually whenever you return, and expect only fallback wakeups until relaunch."):f="Codex wake-path capability check passed: remote-control socket established for this session.",T=e.args,p={...process.env,...e.env,BORG_SESSION:"1"},u=E(e.args),m=e.server?.cleanup??null}else s==="codex"&&l.includes("--remote")&&(f="Codex wake-path capability check: using caller-provided --remote socket; if no wake arrives, run borg_regen manually when returning to the session.",u=E(l),u&&(p={...process.env,BORG_CODEX_REMOTE_WAKE:"1",BORG_SESSION:"1"}));if(o){const e=await Y(o.cubeId,I(),o.droneId),r=q(p,e,process.env);if(p=r.env,r.probe){const a=await X(r.probe.descriptor,fetch,r.probe.baseUrl);a.ok||console.error(`${t()}${i.yellow(`warning: ${a.message}`)}`)}}const g=Oe({cli:s,codexWakeNonce:A,monitorClause:H,codexWakePathClause:f});let h;if(s==="codex")h=[...Me(),...T,...Pe([...l,g],process.cwd())];else if(s==="opencode"){const e=o?M(o.droneId):14096;qe(),Ue(e),h=[process.cwd(),"--port",String(e),"--auto",...l]}else h=re(l,g);const N=s==="claude"?"Claude Code":s==="codex"?"Codex":"OpenCode";console.error(`${t()}${i.blue(`\u25FC Launching ${N}\u2026`)}`);const P=F(s,h,{stdio:"inherit",shell:!1,env:p});if(s==="opencode"){const r=`http://127.0.0.1:${o?M(o.droneId):14096}`;Xe({serverUrl:r,directory:process.cwd(),droneLabel:o?.droneLabel??"opencode",cubeName:o?.name??"borg"}).then(()=>ze(g)).catch(()=>{})}s==="codex"&&o&&u&&(Ee({deps:{setCodexWakeTarget:V,findLoadedCodexThread:Re},cubeId:o.cubeId,droneId:o.droneId,socketPath:u,passthroughArgs:l,previewNeedle:A??g.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),j(e=>Le(e))),P.on("error",e=>{if(m)try{m()}catch{}if(e.code==="ENOENT"){console.error(`${t()}${i.red(`
10
+ \u25FC Failed to launch ${s}`)}`);const r=s==="opencode"?"opencode":s;console.error(`${t()}${i.gray(`Make sure ${r} is installed.
11
11
  `)}`)}else console.error(`${t()}${i.red(`
12
- \u25FC Failed to launch ${o}: ${e.message}
13
- `)}`);process.exit(1)}),P.on("exit",e=>{if(m)try{m()}catch{}process.exit(e??0)})}function ze(){const c=b();if(c.claude)try{Ue()||Be(),_e(I(process.cwd())),Ke(),We()}catch(n){console.error(`${t()}${i.yellow(`warning: Claude Code integration check failed: ${n?.message??n}`)}`)}if(c.codex)try{Ye()||Me(),He(),Ne()}catch(n){console.error(`${t()}${i.yellow(`warning: Codex integration check failed: ${n?.message??n}`)}`)}if(c.opencode)try{Ve()||Fe()}catch(n){console.error(`${t()}${i.yellow(`warning: OpenCode integration check failed: ${n?.message??n}`)}`)}}Xe().catch(c=>{console.error(`${t()}${i.red(`
12
+ \u25FC Failed to launch ${s}: ${e.message}
13
+ `)}`);process.exit(1)}),P.on("exit",e=>{if(m)try{m()}catch{}process.exit(e??0)})}function Qe(){const c=k();if(c.claude)try{Ke()||Fe(),We(I(process.cwd())),je(),Ge()}catch(n){console.error(`${t()}${i.yellow(`warning: Claude Code integration check failed: ${n?.message??n}`)}`)}if(c.codex)try{Ye()||He(),Ne(),Be()}catch(n){console.error(`${t()}${i.yellow(`warning: Codex integration check failed: ${n?.message??n}`)}`)}if(c.opencode)try{Ve()||_e()}catch(n){console.error(`${t()}${i.yellow(`warning: OpenCode integration check failed: ${n?.message??n}`)}`)}}Je().catch(c=>{console.error(`${t()}${i.red(`
14
14
  \u25FC Error: ${c.message}
15
15
  `)}`),process.exit(1)});
package/dist/cli-help.js CHANGED
@@ -1,9 +1,9 @@
1
1
  function n(e){return e==="--help"||e==="-h"}function o(e){return`borgmcp ${e} \u2014 run several AI coding agents on one project, together.
2
- They coordinate through a shared log (a "cube"). For Claude Code & Codex.
2
+ They coordinate through a shared log (a "cube"). For Claude Code, Codex & OpenCode.
3
3
 
4
4
  Docs & quickstart: https://borgmcp.ai/get-started
5
5
 
6
- Install Claude Code or Codex first. Type \`borg ...\` in your terminal;
6
+ Install Claude Code, Codex, or OpenCode first. Type \`borg ...\` in your terminal;
7
7
  type \`borg_...\` inside your agent session once you've joined a cube ("assimilate").
8
8
 
9
9
  Usage:
@@ -16,9 +16,9 @@ Usage:
16
16
  borg sync [--prune] Sync this worktree's branch to origin/main
17
17
  borg cleanup [--prune] Report (or --prune) worktrees orphaned by evicted drones
18
18
  borg launch-all [cube] Launch all drone worktrees of a cube (default: active cube)
19
- borg launch-all [cube] --cli claude|codex
19
+ borg launch-all [cube] --cli claude|codex|opencode
20
20
  Launch all drone worktrees with that agent CLI
21
- borg --cli claude|codex Launch that agent CLI directly
21
+ borg --cli claude|codex|opencode Launch that agent CLI directly
22
22
  borg --version Show installed version
23
23
 
24
24
  All other arguments are passed through to the selected agent CLI.
@@ -37,7 +37,7 @@ Flags:
37
37
  --cube-name <name> Cube to join/create (default: repo basename)
38
38
  --template <name> Bootstrap a new cube from a bundled role template
39
39
  --no-template Create the cube with no template roles
40
- --cli claude|codex Agent CLI to launch (default: claude)
40
+ --cli claude|codex|opencode Agent CLI to launch (default: claude)
41
41
  --model <kind>:<model> Model for this drone (default: your Claude config)
42
42
  --yes, -y Skip confirmation prompts
43
43
 
@@ -52,7 +52,7 @@ Models (--model <kind>:<model>):
52
52
  borg assimilate build --model ollama:qwen3-coder-next
53
53
  `}function r(e){return`borg setup (borgmcp ${e}) \u2014 set up OAuth + register the borg MCP server
54
54
 
55
- Borg MCP needs Claude Code or Codex installed first.
55
+ Borg MCP needs Claude Code, Codex, or OpenCode installed first.
56
56
 
57
57
  Usage:
58
58
  borg setup Run the interactive setup wizard (OAuth sign-in +
package/dist/cubes.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import{existsSync as k}from"node:fs";import{mkdir as A,readFile as f,writeFile as _,unlink as w,rename as U}from"node:fs/promises";import{homedir as W}from"node:os";import{dirname as F,join as c,resolve as N}from"node:path";import{pruneDeadWakeTargets as v}from"./codex-wake-resolve.js";import{MODEL_DESCRIPTOR_REGEX as R}from"./model-presets.js";const l=c(W(),".config","borgmcp"),g=c(l,"cubes.json"),O=c(l,"launch.json"),L=c(l,"codex-wake-targets.json"),m=c(l,"launch-models.json"),D=c(l,"inboxes");function s(t=process.cwd()){let e=N(t);for(;;){if(k(c(e,".git")))return e;const r=F(e);if(r===e)return N(t);e=r}}const a=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function Z(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!a.test(e))throw new Error(`Invalid droneId: ${e}`);return c(D,t,`${e}.log`)}function B(t){return t!==null&&typeof t=="object"&&typeof t.projects=="object"&&t.projects!==null&&!Array.isArray(t.projects)}async function p(){let t;try{t=await f(g,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}let e;try{e=JSON.parse(t)}catch{return null}return B(e)?e:null}async function I(t){await d(g,JSON.stringify(t,null,2)+`
2
2
  `)}let J=0;async function d(t,e,r={}){const n=r.io??{writeFile:_,rename:U,unlink:w},o=r.mode??384;await A(F(t),{recursive:!0});const i=`${t}.${process.pid}.${J++}.tmp`;try{await n.writeFile(i,e,{mode:o}),await n.rename(i,t)}catch(u){try{await n.unlink(i)}catch{}throw u}}function M(t){return t!==null&&typeof t=="object"&&typeof t.projects=="object"&&t.projects!==null&&!Array.isArray(t.projects)}async function h(){let t;try{t=await f(O,"utf8")}catch(e){if(e?.code==="ENOENT")return null;throw e}try{const e=JSON.parse(t);return M(e)?e:null}catch{return null}}async function P(t){await d(O,JSON.stringify(t,null,2)+`
3
3
  `)}function $(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!a.test(e))throw new Error(`Invalid droneId: ${e}`);return`${t}:${e}`}function K(t){return t!==null&&typeof t=="object"&&typeof t.targets=="object"&&t.targets!==null&&!Array.isArray(t.targets)}async function j(){let t;try{t=await f(L,"utf8")}catch(e){if(e?.code==="ENOENT")return null;throw e}try{const e=JSON.parse(t);return K(e)?e:null}catch{return null}}async function S(t){await d(L,JSON.stringify(t,null,2)+`
4
- `)}async function tt(){const t=await p();if(!t)return null;const e=s(),r=t.projects[e];return!r||typeof r.cubeId!="string"||!r.cubeId||typeof r.droneId!="string"||!r.droneId?null:r}async function et(t){const e=await p()??{projects:{}};e.projects[s()]=t,await I(e)}function rt(t,e){const r=e.cube?.name??t.name,n=e.drone?.label??t.droneLabel;return r===t.name&&n===t.droneLabel?t:{...t,name:r,droneLabel:n}}async function nt(){const t=await p();if(!t)return;const e=s();if(e in t.projects){if(delete t.projects[e],Object.keys(t.projects).length===0){try{await w(g)}catch(r){if(r?.code!=="ENOENT")throw r}return}await I(t)}}async function ot(){const t=await h();if(!t)return null;const e=t.projects[s()];return e?.cli==="claude"||e?.cli==="codex"?e.cli:null}async function it(t){const e=await h();if(!e)return null;const r=e.projects[s(t)];return r?.cli==="claude"||r?.cli==="codex"?r.cli:null}async function ct(){const t=await p();return t?Object.entries(t.projects).filter(([,e])=>e!==null&&typeof e=="object"&&typeof e.cubeId=="string"&&e.cubeId.length>0&&typeof e.droneId=="string"&&e.droneId.length>0).map(([e,r])=>({projectPath:e,cube:r})):[]}async function at(t){const e=await h()??{projects:{}};e.projects[s()]={cli:t},await P(e)}async function st(t,e,r){const n=await j()??{targets:{}};n.targets[$(t,e)]={...r,updatedAt:new Date().toISOString()},await S(n)}async function lt(t,e){const r=await j();if(!r)return null;const n=r.targets[$(t,e)];return!n||typeof n.threadId!="string"||typeof n.socketPath!="string"?null:n}async function ut(t){const e=await j();if(!e)return;const{targets:r,changed:n}=v(e.targets,t);n&&await S({...e,targets:r})}function x(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(typeof e!="string"||e.length===0)throw new Error(`Invalid worktree path: ${e}`);return`${t}:${e}`}function X(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!a.test(e))throw new Error(`Invalid droneId: ${e}`);return`${t}:${e}`}function T(t){return!t||typeof t.model!="string"||!R.test(t.model)?null:{model:t.model,ollamaBaseUrl:typeof t.ollamaBaseUrl=="string"?t.ollamaBaseUrl:null}}function G(t){return t!==null&&typeof t=="object"&&typeof t.models=="object"&&t.models!==null&&!Array.isArray(t.models)}async function E(t){let e;try{e=await f(t,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}try{const r=JSON.parse(e);return G(r)?r:null}catch{return null}}async function b(t,e){await d(t,JSON.stringify(e,null,2)+`
4
+ `)}async function tt(){const t=await p();if(!t)return null;const e=s(),r=t.projects[e];return!r||typeof r.cubeId!="string"||!r.cubeId||typeof r.droneId!="string"||!r.droneId?null:r}async function et(t){const e=await p()??{projects:{}};e.projects[s()]=t,await I(e)}function rt(t,e){const r=e.cube?.name??t.name,n=e.drone?.label??t.droneLabel;return r===t.name&&n===t.droneLabel?t:{...t,name:r,droneLabel:n}}async function nt(){const t=await p();if(!t)return;const e=s();if(e in t.projects){if(delete t.projects[e],Object.keys(t.projects).length===0){try{await w(g)}catch(r){if(r?.code!=="ENOENT")throw r}return}await I(t)}}async function ot(){const t=await h();if(!t)return null;const e=t.projects[s()];return e?.cli==="claude"||e?.cli==="codex"||e?.cli==="opencode"?e.cli:null}async function it(t){const e=await h();if(!e)return null;const r=e.projects[s(t)];return r?.cli==="claude"||r?.cli==="codex"||r?.cli==="opencode"?r.cli:null}async function ct(){const t=await p();return t?Object.entries(t.projects).filter(([,e])=>e!==null&&typeof e=="object"&&typeof e.cubeId=="string"&&e.cubeId.length>0&&typeof e.droneId=="string"&&e.droneId.length>0).map(([e,r])=>({projectPath:e,cube:r})):[]}async function at(t){const e=await h()??{projects:{}};e.projects[s()]={cli:t},await P(e)}async function st(t,e,r){const n=await j()??{targets:{}};n.targets[$(t,e)]={...r,updatedAt:new Date().toISOString()},await S(n)}async function lt(t,e){const r=await j();if(!r)return null;const n=r.targets[$(t,e)];return!n||typeof n.threadId!="string"||typeof n.socketPath!="string"?null:n}async function ut(t){const e=await j();if(!e)return;const{targets:r,changed:n}=v(e.targets,t);n&&await S({...e,targets:r})}function x(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(typeof e!="string"||e.length===0)throw new Error(`Invalid worktree path: ${e}`);return`${t}:${e}`}function X(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!a.test(e))throw new Error(`Invalid droneId: ${e}`);return`${t}:${e}`}function T(t){return!t||typeof t.model!="string"||!R.test(t.model)?null:{model:t.model,ollamaBaseUrl:typeof t.ollamaBaseUrl=="string"?t.ollamaBaseUrl:null}}function G(t){return t!==null&&typeof t=="object"&&typeof t.models=="object"&&t.models!==null&&!Array.isArray(t.models)}async function E(t){let e;try{e=await f(t,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}try{const r=JSON.parse(e);return G(r)?r:null}catch{return null}}async function b(t,e){await d(t,JSON.stringify(e,null,2)+`
5
5
  `)}async function ft(t,e,r,n=m){const o=await E(n)??{models:{}};o.models[x(t,e)]={model:r.model,ollamaBaseUrl:r.ollamaBaseUrl},await b(n,o)}async function pt(t,e,r=null,n=m){const o=await E(n);if(!o)return null;const i=x(t,e),u=T(o.models[i]);if(u)return u;if(!r||!a.test(r))return null;const C=X(t,r),y=T(o.models[C]);return y?(o.models[i]=y,delete o.models[C],await b(n,o),y):null}async function dt(t,e,r=m){const n=await E(r);if(!n)return;const o=x(t,e);if(o in n.models){if(delete n.models[o],Object.keys(n.models).length===0){try{await w(r)}catch(i){if(i?.code!=="ENOENT")throw i}return}await b(r,n)}}export{rt as activeCubeWithFreshRegenIdentity,d as atomicWriteFile,nt as clearActiveCube,dt as clearLaunchModel,s as findProjectRoot,tt as getActiveCube,lt as getCodexWakeTarget,pt as getLaunchModel,ot as getProjectCliPreference,it as getProjectCliPreferenceForPath,Z as inboxPathForDrone,ut as pruneDeadCodexWakeTargets,ct as readAllProjectIdentities,et as setActiveCube,st as setCodexWakeTarget,ft as setLaunchModel,at as setProjectCliPreference};
@@ -4,8 +4,25 @@ interface ConnectDeps {
4
4
  droneLabel: string;
5
5
  cubeName: string;
6
6
  }
7
+ /**
8
+ * Connect to the opencode TUI HTTP server. Does NOT create a new session —
9
+ * instead it finds the TUI's auto-created session on first inject.
10
+ */
7
11
  export declare function connectOpenCodeDrone(deps: ConnectDeps): Promise<void>;
12
+ /**
13
+ * Inject the initial kickoff prompt into the TUI's session. This is the
14
+ * equivalent of passing the kickoff as a CLI arg (which opencode doesn't
15
+ * support). Polls until the TUI server and a session are available, then
16
+ * sends the prompt WITHOUT noReply so the agent processes it as its first
17
+ * task. Best-effort — never throws.
18
+ */
19
+ export declare function injectInitialKickoff(text: string): Promise<boolean>;
20
+ /**
21
+ * Inject an SSE entry into the TUI's session as context (noReply).
22
+ * Falls through silently on failure — the caller falls back to inbox write.
23
+ */
8
24
  export declare function injectOpenCodeEntry(text: string): Promise<boolean>;
25
+ /** Check if the TUI server + session are healthy. */
9
26
  export declare function probeOpenCodeDroneArmed(): Promise<boolean | null>;
10
27
  export declare function disconnectOpenCodeDrone(): void;
11
28
  export declare function getOpenCodeConnectionState(): {
@@ -1 +1 @@
1
- import{createOpencodeClient as s}from"@opencode-ai/sdk";let e=null;async function i(t){try{const r=s({baseUrl:t.serverUrl}),n=await r.session.create({body:{title:`borg-drone: ${t.droneLabel} on ${t.cubeName}`}});if(!n.data){e=null;return}e={client:r,sessionId:n.data.id,directory:t.directory,connected:!0,totalEntriesInjected:0}}catch{e=null}}async function l(t){if(!e?.connected)return!1;try{return await e.client.session.prompt({path:{id:e.sessionId},body:{parts:[{type:"text",text:t}]}}),e.totalEntriesInjected++,!0}catch{return e.connected=!1,!1}}async function d(){if(!e?.connected)return null;try{return(await e.client.path.get()).response.status===200}catch{return!1}}function u(){e=null}function p(){return{connected:e?.connected??!1,sessionId:e?.sessionId??null,totalEntriesInjected:e?.totalEntriesInjected??0}}function f(t,r=14096){let n=0;for(let o=0;o<t.length;o++){const c=t.charCodeAt(o);n=(n<<5)-n+c,n|=0}return r+Math.abs(n)%1024}export{f as computeOpenCodePort,i as connectOpenCodeDrone,u as disconnectOpenCodeDrone,p as getOpenCodeConnectionState,l as injectOpenCodeEntry,d as probeOpenCodeDroneArmed};
1
+ import{createOpencodeClient as f}from"@opencode-ai/sdk";let e=null;async function y(t){try{e={client:f({baseUrl:t.serverUrl}),serverUrl:t.serverUrl,sessionId:null,directory:t.directory,connected:!0,totalEntriesInjected:0}}catch{e=null}}async function c(t=15,n=1e3){if(!e?.connected)return null;for(let r=0;r<t;r++){try{const o=(await e.client.session.list()).data??[];if(o.length===0)continue;const a=e.directory,l=a?o.filter(i=>i.directory===a):o;if(l.length===0)continue;const u=l.reduce((i,d)=>i.time.created>d.time.created?i:d);return e.sessionId=u.id,u.id}catch{}await new Promise(s=>setTimeout(s,n))}return null}async function h(t){if(!e?.connected)return!1;try{const n=await c();return n?(await e.client.session.prompt({path:{id:n},body:{parts:[{type:"text",text:t}]}}),e.totalEntriesInjected++,!0):!1}catch{return!1}}async function I(t){if(!e?.connected)return!1;try{const n=e.sessionId??await c(5,500);return n?(await e.client.session.prompt({path:{id:n},body:{noReply:!0,parts:[{type:"text",text:t}]}}),e.totalEntriesInjected++,!0):!1}catch{return e.sessionId=null,!1}}async function m(){if(!e?.connected)return null;try{const t=e.sessionId??await c(3,500);return t?(await e.client.session.get({path:{id:t}})).response.status===200:!1}catch{return!1}}function w(){e=null}function x(){return{connected:e?.connected??!1,sessionId:e?.sessionId??null,totalEntriesInjected:e?.totalEntriesInjected??0}}function C(t,n=14096){let r=0;for(let s=0;s<t.length;s++){const o=t.charCodeAt(s);r=(r<<5)-r+o,r|=0}return n+Math.abs(r)%1024}export{C as computeOpenCodePort,y as connectOpenCodeDrone,w as disconnectOpenCodeDrone,x as getOpenCodeConnectionState,h as injectInitialKickoff,I as injectOpenCodeEntry,m as probeOpenCodeDroneArmed};
@@ -1,3 +1,8 @@
1
- export declare const BORG_PLUGIN_SOURCE = "\n// Borg MCP orientation plugin - installed by borg assimilate\nexport default async function (ctx) {\n const serverUrl = ctx.serverUrl?.toString() ?? '';\n\n if (serverUrl) {\n try {\n const homeDir = process.env.HOME || process.env.USERPROFILE || '';\n const markerDir = homeDir + '/.config/borgmcp';\n const marker = markerDir + '/opencode-server';\n const encoder = new TextEncoder();\n ctx.$.write(marker, encoder.encode(serverUrl));\n } catch {}\n }\n\n return {\n event: async ({ event }) => {\n if (event.type === 'session.created') {\n const sessionId = event.properties.info?.id;\n if (!sessionId) return;\n try {\n await ctx.client.session.prompt({\n path: { id: sessionId },\n body: {\n noReply: true,\n parts: [{ type: 'text', text: '## Borg Cube Context\\nYou are operating in a borgmcp.ai multi-agent cube. Use borg_regen to get full context and recent activity.' }]\n }\n });\n } catch {}\n }\n },\n 'experimental.session.compacting': async (_input, output) => {\n output.context.push('## Borg Cube\\nYou are in a borgmcp.ai multi-agent cube. Use borg_regen to refresh context and see recent activity.');\n },\n };\n}\n";
1
+ /**
2
+ * Plugin source that preserves borg cube context across session compaction.
3
+ * The initial kickoff and ongoing entry injection are handled by the launcher
4
+ * and MCP client via the SDK, not by the plugin.
5
+ */
6
+ export declare const BORG_PLUGIN_SOURCE = "\n// Borg MCP context-preservation plugin \u2014 installed by borg assimilate\nexport default function () {\n return {\n 'experimental.session.compacting': async (_input, output) => {\n output.context.push(\n '## Borg Cube\\nYou are in a borgmcp.ai multi-agent coordination cube. ' +\n 'Use MCP tool borg_regen to get full context and recent activity.'\n );\n },\n };\n}\n";
2
7
  export declare function installBorgPlugin(): void;
3
8
  //# sourceMappingURL=opencode-plugin.d.ts.map
@@ -1,37 +1,13 @@
1
- import e from"node:fs";import i from"node:os";import n from"node:path";const o=`
2
- // Borg MCP orientation plugin - installed by borg assimilate
3
- export default async function (ctx) {
4
- const serverUrl = ctx.serverUrl?.toString() ?? '';
5
-
6
- if (serverUrl) {
7
- try {
8
- const homeDir = process.env.HOME || process.env.USERPROFILE || '';
9
- const markerDir = homeDir + '/.config/borgmcp';
10
- const marker = markerDir + '/opencode-server';
11
- const encoder = new TextEncoder();
12
- ctx.$.write(marker, encoder.encode(serverUrl));
13
- } catch {}
14
- }
15
-
1
+ import n from"node:fs";import e from"node:os";import o from"node:path";const i=`
2
+ // Borg MCP context-preservation plugin \u2014 installed by borg assimilate
3
+ export default function () {
16
4
  return {
17
- event: async ({ event }) => {
18
- if (event.type === 'session.created') {
19
- const sessionId = event.properties.info?.id;
20
- if (!sessionId) return;
21
- try {
22
- await ctx.client.session.prompt({
23
- path: { id: sessionId },
24
- body: {
25
- noReply: true,
26
- parts: [{ type: 'text', text: '## Borg Cube Context\\nYou are operating in a borgmcp.ai multi-agent cube. Use borg_regen to get full context and recent activity.' }]
27
- }
28
- });
29
- } catch {}
30
- }
31
- },
32
5
  'experimental.session.compacting': async (_input, output) => {
33
- output.context.push('## Borg Cube\\nYou are in a borgmcp.ai multi-agent cube. Use borg_regen to refresh context and see recent activity.');
6
+ output.context.push(
7
+ '## Borg Cube\\nYou are in a borgmcp.ai multi-agent coordination cube. ' +
8
+ 'Use MCP tool borg_regen to get full context and recent activity.'
9
+ );
34
10
  },
35
11
  };
36
12
  }
37
- `;function u(){const r=n.join(i.homedir(),".config","opencode","plugins"),t=n.join(r,"borg-orient.js");try{if(e.existsSync(t)&&e.readFileSync(t,"utf-8")===o)return;e.mkdirSync(r,{recursive:!0}),e.writeFileSync(t,o,"utf-8")}catch{}}export{o as BORG_PLUGIN_SOURCE,u as installBorgPlugin};
13
+ `,r=o.join(".config","opencode","plugins","borg-orient.js");function p(){const t=o.join(e.homedir(),r);try{if(n.existsSync(t)&&n.readFileSync(t,"utf-8")===i)return;n.mkdirSync(o.dirname(t),{recursive:!0}),n.writeFileSync(t,i,"utf-8")}catch{}}export{i as BORG_PLUGIN_SOURCE,p as installBorgPlugin};
@@ -1 +1 @@
1
- function i(o){let a;const t={};for(let l=0;l<o.length;l+=1){const r=o[l];if(r==="--worktree"){const e=o[l+1];if(typeof e!="string"||e.length===0)return{ok:!1,error:"--worktree requires a name argument (e.g. `--worktree drone-2`)"};t.worktree=e,l+=1}else if(r==="--template"){const e=o[l+1];if(typeof e!="string"||e.length===0)return{ok:!1,error:"--template requires a name argument (e.g. `--template software-dev`)"};t.template=e,l+=1}else if(r==="--no-template")t.noTemplate=!0;else if(r==="--cube-name"){const e=o[l+1];if(typeof e!="string"||e.length===0)return{ok:!1,error:"--cube-name requires a name argument (e.g. `--cube-name my-cube`)"};t.cubeName=e,l+=1}else if(r==="--here")t.here=!0;else if(r==="--yes"||r==="-y")t.yes=!0;else if(r==="--cli"){const e=o[l+1];if(e!=="claude"&&e!=="codex")return{ok:!1,error:"--cli requires claude or codex"};t.cli=e,l+=1}else if(r.startsWith("--cli=")){const e=r.slice(6);if(e!=="claude"&&e!=="codex")return{ok:!1,error:"--cli requires claude or codex"};t.cli=e}else if(r==="--model"||r==="--backend"){const e=o[l+1];if(typeof e!="string"||e.length===0)return{ok:!1,error:"--model requires a descriptor (e.g. `--model claude:claude-opus-4-8` or `--model ollama:qwen3-coder-next:q4_K_M`)"};if(!e.match(/^(claude|ollama):[A-Za-z0-9._:\/-]+$/))return{ok:!1,error:`invalid model descriptor '${e}' \u2014 expected <kind>:<model> where kind is claude or ollama`};t.model=e,l+=1}else if(r.startsWith("--model=")||r.startsWith("--backend=")){const e=r.slice(r.indexOf("=")+1);if(!e.match(/^(claude|ollama):[A-Za-z0-9._:\/-]+$/))return{ok:!1,error:`invalid model descriptor '${e}' \u2014 expected <kind>:<model> where kind is claude or ollama`};t.model=e}else{if(r.startsWith("--"))return{ok:!1,error:`unknown flag: ${r}. Supported: --worktree, --template, --no-template, --cube-name, --here, --yes, --cli, --model`};if(a!==void 0)return{ok:!1,error:`unexpected extra argument: ${r} (already have role "${a}")`};a=r}}return t.template!==void 0&&t.noTemplate?{ok:!1,error:"--template and --no-template are mutually exclusive"}:{ok:!0,role:a,flags:t}}export{i as parseAssimilateArgs};
1
+ function i(o){let a;const t={};for(let l=0;l<o.length;l+=1){const r=o[l];if(r==="--worktree"){const e=o[l+1];if(typeof e!="string"||e.length===0)return{ok:!1,error:"--worktree requires a name argument (e.g. `--worktree drone-2`)"};t.worktree=e,l+=1}else if(r==="--template"){const e=o[l+1];if(typeof e!="string"||e.length===0)return{ok:!1,error:"--template requires a name argument (e.g. `--template software-dev`)"};t.template=e,l+=1}else if(r==="--no-template")t.noTemplate=!0;else if(r==="--cube-name"){const e=o[l+1];if(typeof e!="string"||e.length===0)return{ok:!1,error:"--cube-name requires a name argument (e.g. `--cube-name my-cube`)"};t.cubeName=e,l+=1}else if(r==="--here")t.here=!0;else if(r==="--yes"||r==="-y")t.yes=!0;else if(r==="--cli"){const e=o[l+1];if(e!=="claude"&&e!=="codex"&&e!=="opencode")return{ok:!1,error:"--cli requires claude, codex, or opencode"};t.cli=e,l+=1}else if(r.startsWith("--cli=")){const e=r.slice(6);if(e!=="claude"&&e!=="codex"&&e!=="opencode")return{ok:!1,error:"--cli requires claude, codex, or opencode"};t.cli=e}else if(r==="--model"||r==="--backend"){const e=o[l+1];if(typeof e!="string"||e.length===0)return{ok:!1,error:"--model requires a descriptor (e.g. `--model claude:claude-opus-4-8` or `--model ollama:qwen3-coder-next:q4_K_M`)"};if(!e.match(/^(claude|ollama):[A-Za-z0-9._:\/-]+$/))return{ok:!1,error:`invalid model descriptor '${e}' \u2014 expected <kind>:<model> where kind is claude or ollama`};t.model=e,l+=1}else if(r.startsWith("--model=")||r.startsWith("--backend=")){const e=r.slice(r.indexOf("=")+1);if(!e.match(/^(claude|ollama):[A-Za-z0-9._:\/-]+$/))return{ok:!1,error:`invalid model descriptor '${e}' \u2014 expected <kind>:<model> where kind is claude or ollama`};t.model=e}else{if(r.startsWith("--"))return{ok:!1,error:`unknown flag: ${r}. Supported: --worktree, --template, --no-template, --cube-name, --here, --yes, --cli, --model`};if(a!==void 0)return{ok:!1,error:`unexpected extra argument: ${r} (already have role "${a}")`};a=r}}return t.template!==void 0&&t.noTemplate?{ok:!1,error:"--template and --no-template are mutually exclusive"}:{ok:!0,role:a,flags:t}}export{i as parseAssimilateArgs};
@@ -1 +1 @@
1
- const c="--mode <tmux|windows|pastelist>, --only <name>, --dry-run, --cli <claude|codex>, --no-attach, --yes/-y, --force, --launch-delay <ms>";function u(t){const r={};let o;for(let n=0;n<t.length;n++){const a=t[n];switch(a){case"--mode":{const e=t[++n];if(e!=="tmux"&&e!=="windows"&&e!=="pastelist")return{ok:!1,error:`--mode must be one of tmux|windows|pastelist (got: ${e??"<missing>"})`};r.mode=e;break}case"--only":{const e=t[++n];if(e===void 0||e.startsWith("--"))return{ok:!1,error:"--only requires a value (role name or drone label)"};r.only=e;break}case"--cli":{const e=t[++n];if(e!=="claude"&&e!=="codex")return{ok:!1,error:`--cli must be one of claude|codex (got: ${e??"<missing>"})`};r.cli=e;break}case"--launch-delay":{const e=t[++n],s=e===void 0?NaN:Number(e);if(!Number.isInteger(s)||s<0)return{ok:!1,error:`--launch-delay requires a non-negative integer (milliseconds); got: ${e??"<missing>"}`};r.launchDelayMs=s;break}case"--dry-run":r.dryRun=!0;break;case"--no-attach":r.noAttach=!0;break;case"--yes":case"-y":r.yes=!0;break;case"--force":r.force=!0;break;default:if(a.startsWith("-"))return{ok:!1,error:`unknown flag: ${a}. Supported: ${c}`};if(o!==void 0)return{ok:!1,error:`unexpected extra argument: ${a}`};o=a;break}}return{ok:!0,args:{cubeName:o,flags:r}}}export{u as parseLaunchAllArgs};
1
+ const c="--mode <tmux|windows|pastelist>, --only <name>, --dry-run, --cli <claude|codex|opencode>, --no-attach, --yes/-y, --force, --launch-delay <ms>";function u(o){const r={};let a;for(let n=0;n<o.length;n++){const t=o[n];switch(t){case"--mode":{const e=o[++n];if(e!=="tmux"&&e!=="windows"&&e!=="pastelist")return{ok:!1,error:`--mode must be one of tmux|windows|pastelist (got: ${e??"<missing>"})`};r.mode=e;break}case"--only":{const e=o[++n];if(e===void 0||e.startsWith("--"))return{ok:!1,error:"--only requires a value (role name or drone label)"};r.only=e;break}case"--cli":{const e=o[++n];if(e!=="claude"&&e!=="codex"&&e!=="opencode")return{ok:!1,error:`--cli must be one of claude|codex|opencode (got: ${e??"<missing>"})`};r.cli=e;break}case"--launch-delay":{const e=o[++n],s=e===void 0?NaN:Number(e);if(!Number.isInteger(s)||s<0)return{ok:!1,error:`--launch-delay requires a non-negative integer (milliseconds); got: ${e??"<missing>"}`};r.launchDelayMs=s;break}case"--dry-run":r.dryRun=!0;break;case"--no-attach":r.noAttach=!0;break;case"--yes":case"-y":r.yes=!0;break;case"--force":r.force=!0;break;default:if(t.startsWith("-"))return{ok:!1,error:`unknown flag: ${t}. Supported: ${c}`};if(a!==void 0)return{ok:!1,error:`unexpected extra argument: ${t}`};a=t;break}}return{ok:!0,args:{cubeName:a,flags:r}}}export{u as parseLaunchAllArgs};
@@ -1,12 +1,12 @@
1
- import{ROLE_SCOPED_SAFETY_DISCIPLINES as k,UNIVERSAL_SAFETY_DISCIPLINES as _}from"./templates.js";import{parseRoleSections as A}from"./role-section.js";import{formatRoleAgentLabel as C}from"./roster-render.js";import{formatDroneAddressToken as N}from"./drone-address.js";function M(e){if(!e||!e.trim())return null;try{const r=JSON.parse(e)?.source;return typeof r=="string"?r:null}catch{return null}}function T(e,o){return e==="codex"?"Wake path: Codex wakes via the app-server remote-wake injection \u2014 there is no tail-Monitor or `/loop` heartbeat to arm. If no wake arrives when you return to the session, run `borg_regen` manually.":["Arm your wake path before working:",`1. **Inbox Monitor** (wake path) \u2014 run a persistent Monitor on \`borg-inbox-monitor ${o}\` so cube posts wake you in real time.`,"2. **Engage `/loop`** (self-paced) so you keep waking to triage the cube.","3. **Fallback heartbeat** \u2014 set a ~3600s (60-min) `ScheduleWakeup` so you never go fully silent."].join(`
2
- `)}function j(e,o){return{cubeName:o?.cube?.name??e.name,droneLabel:o?.drone?.label??e.droneLabel,roleName:o?.role?.name??e.roleName??null}}function q(e){const{cubeName:o,droneLabel:r,roleName:i,inboxPath:t,agentKind:n,source:s}=e,c=s==="clear"?n==="codex"?"\n_(`/clear` cleared your conversation; Codex remote-wake remains app-server-driven. If no wake arrives, run `borg_regen` manually when returning.)_\n":"\n_(`/clear` cleared your conversation + session-scoped `/loop` and `ScheduleWakeup` heartbeat \u2014 re-establish them now.)_\n":"";return[`# Cube: ${o} \u2014 ${r}`,"",`**Your role:** ${i||"_(call `borg_regen` to load)_"}`,c,"You are a Borg drone \u2014 coordinate through the cube log, and never pause for the user. Blocked \u2192 escalate to your cube's coordinating role.","",T(n,t),"","This orientation is intentionally lean. Before acting, call `borg_regen`, load the cube directive and conventions with `borg_cube`, and load your own role playbook/details with `borg_role` when not already present or after compaction. Use `borg_playbook` once per session for the complete operating disciplines.",""].join(`
1
+ import{ROLE_SCOPED_SAFETY_DISCIPLINES as k,UNIVERSAL_SAFETY_DISCIPLINES as _}from"./templates.js";import{parseRoleSections as A}from"./role-section.js";import{formatRoleAgentLabel as C}from"./roster-render.js";import{formatDroneAddressToken as T}from"./drone-address.js";function M(e){if(!e||!e.trim())return null;try{const r=JSON.parse(e)?.source;return typeof r=="string"?r:null}catch{return null}}function N(e,o){return e==="codex"?"Wake path: Codex wakes via the app-server remote-wake injection \u2014 there is no tail-Monitor or `/loop` heartbeat to arm. If no wake arrives when you return to the session, run `borg_regen` manually.":e==="opencode"?"Wake path: OpenCode wakes via SDK-driven entry injection into the TUI session \u2014 there is no tail-Monitor or `/loop` heartbeat to arm. Cube posts are delivered directly into your active session. Use `borg_regen` at any time for fresh context.":["Arm your wake path before working:",`1. **Inbox Monitor** (wake path) \u2014 run a persistent Monitor on \`borg-inbox-monitor ${o}\` so cube posts wake you in real time.`,"2. **Engage `/loop`** (self-paced) so you keep waking to triage the cube.","3. **Fallback heartbeat** \u2014 set a ~3600s (60-min) `ScheduleWakeup` so you never go fully silent."].join(`
2
+ `)}function j(e,o){return{cubeName:o?.cube?.name??e.name,droneLabel:o?.drone?.label??e.droneLabel,roleName:o?.role?.name??e.roleName??null}}function q(e){const{cubeName:o,droneLabel:r,roleName:i,inboxPath:t,agentKind:n,source:s}=e,c=s==="clear"?n==="codex"?"\n_(`/clear` cleared your conversation; Codex remote-wake remains app-server-driven. If no wake arrives, run `borg_regen` manually when returning.)_\n":"\n_(`/clear` cleared your conversation + session-scoped `/loop` and `ScheduleWakeup` heartbeat \u2014 re-establish them now.)_\n":"";return[`# Cube: ${o} \u2014 ${r}`,"",`**Your role:** ${i||"_(call `borg_regen` to load)_"}`,c,"You are a Borg drone \u2014 coordinate through the cube log, and never pause for the user. Blocked \u2192 escalate to your cube's coordinating role.","",N(n,t),"","This orientation is intentionally lean. Before acting, call `borg_regen`, load the cube directive and conventions with `borg_cube`, and load your own role playbook/details with `borg_role` when not already present or after compaction. Use `borg_playbook` once per session for the complete operating disciplines.",""].join(`
3
3
  `)}function x(){return"## How to operate as a Drone\n\nYou're a Drone in a Cube. Coordinate with other drones through the activity log.\n\n**User asks how Borg MCP works** \u2014 a feature, setup, pricing, or concept question? Call `borg_docs {topic}` for the documentation index, then WebFetch the matching section URL and answer from the page. Don't guess borgmcp's own behavior from memory.\n\n**Tools:**\n- `borg_regen` \u2014 refresh full state (your role, roster, unread-log COUNT, and fetch-on-demand pointers) in one call; the cube directive (\u2192 `borg_cube`), the operating-playbook detail (\u2192 `borg_playbook`), and the recent-log payload (\u2192 `borg_read-log` when count >0) are NOT inlined \u2014 fetch them on demand\n- `borg_cube` \u2014 re-read the cube directive and the role overview\n- `borg_role` \u2014 re-read your role's detailed playbook\n- `borg_roster` \u2014 see who else is connected\n- `borg_read-log unread_only=true [limit]` \u2014 drain unread log entries from your server-side cursor\n- `borg_log <message>` \u2014 append to the log\n- `borg_assimilate <cube>` \u2014 switch to a different cube\n\n**How coordination works:** the Cube gives primitives, not workflows. Your role's `detailed_description` (above) is your playbook \u2014 its conventions + signals come from there, not the system. The log is the coordination channel. Different cubes, different conventions.\n\n**Default: act autonomously, coordinate through the log.** Don't wait for user input. Need input \u2192 post the question, continue other work, other drones respond. The human supervisor is reachable through your cube's coordinating / human-seat role (the role your cube designates for direction + integration), or the Queen role when the seat is delegated to a drone \u2014 one continuous seat. Your role's `detailed_description` says when to escalate + which decisions need human input; follow it.\n\n**Operating loop \u2014 each wake, in order:**\n1. Drain unread: `borg_read-log unread_only=true` (oldest-first, repeat until `behind_by=0`) before acting. The \"Cube log\" section gives your UNREAD COUNT.\n2. Apply your role's conventions to each entry. Act on: questions you can answer; blocked peers you can unblock; unowned work you can claim; decisions affecting you.\n3. Actionable signal \u2192 act + post the convention. Don't wait to be asked.\n4. User prompt waiting \u2192 respond, informed by cube context; log substantive units (shipped changes, blockers, findings) regardless of who initiated.\n5. Nothing actionable + no prompt \u2192 done; wait for next wake.\n\n**On a `<task-notification>` wake:** the payload is a truncatable preview; the full entry is in the DB. Drain: `borg_read-log unread_only=true limit=20`, repeat until `behind_by=0`. Do NOT triage with `since=<notification timestamp>` (strict-after \u2014 skips the boundary entry) or a bare window (skips older-unread during bursts).\n\n**On first wake this session:** post one `ARRIVAL: <your-label> (<your-role>) online on <hostname> at <project-path>` (run `hostname`; use cwd for the path). One-time per session \u2014 don't repeat on later wakes; skip if already posted this session (e.g. after a `/mcp` reconnect).\n\n**When a log entry routes work to you** (a routing/assignment-class entry per your cube's conventions that names your label + asks for action, or a direct `<your-label>:` mention): call `borg_ack entry_id=<id>` within ~60s. Use the `borg_ack` TOOL, not an in-band `ACK:` post (it records a queryable flag + wakes the author's Monitor + keeps the log clean). Ack = receipt, not completion (`STARTING` / `DONE` still apply). Ack only routing-class signals \u2014 not every mention.\n\n**Claim a work item before you start it (`borg_ack ... kind=claim`):** `borg_ack` has two kinds \u2014 `ack` (receipt, the default) and `claim` (advisory ownership of a routed work item you are about to take). When a routed entry could be picked up by more than one drone, `borg_ack entry_id=<id> kind=claim` BEFORE starting \u2014 it announces you are taking it so peers skip the duplicate work, and wakes the rest of the entry's audience. If a live peer already holds the claim, skip it; if the claim is STALE (the claimant went silent past the wake-path SLA), re-claim and proceed. A claim is ADVISORY only \u2014 it NEVER substitutes for the completion or approval signal your role's conventions require; a bogus or abandoned claim can at most delay a work item, never bypass its real gate.\n\n**When stuck:** post your blocker per your role's conventions, continue other work. Escalation is per your role detail, not by stalling.\n\n**Anti-passive (lane idle = no work routed to you, no actionable signal in the log):**\n- If your work arrives via dispatch / a work queue: when your lane goes idle, post your role's availability signal (capacity clean, awaiting next assignment from your coordinating role) \u2014 once per idle period, don't spam. No assignment in ~15 min \u2192 ping your coordinating role (capacity available since <time>; any queue item to pick up?).\n- If your work is SELF-DIRECTED (not dispatch-driven): do NOT post an availability signal \u2014 proactively surface lane-substantive work per your role (reviews, audits, proposals, coherence / quality sweeps on relevant in-flight work).\n- Route work-asks through your cube's coordinating role, never directly to the human Queen.\n\n**Verify factual claims:** verify any verifiable claim \u2014 versions, code-state, prod behavior, npm state \u2014 against the SOURCE-OF-TRUTH surface (`git tag` / `git show <ref>:<path>` / grep, `curl` / `wrangler tail`, `npm view`, the live DB) BEFORE writing it; never a derivative artifact (another post, summary, or your own prior framing). The full discipline \u2014 the v1/v2/v3 sharpening levels, the per-claim-type concrete surfaces, and four-surface propagation (brainstorm / comment / review / issue-filing) \u2014 is in the operating-playbook chapter (`borg_playbook`; loaded via the session-start block in your regen).\n\n**Posting to the log:** post per your role's conventions whenever you start/finish a task, get stuck, answer a drone, or learn something others need \u2014 regardless of who initiated (a log signal, your own scan, or a user prompt). Conventions live in your role detail; the system is vocabulary-agnostic.\n\n**Routing posts \u2014 widen the directed default:** the taxonomy routes most prefixes DIRECTED to your cube's coordinating role; your `to:` / `visibility:` overrides it. Widen when a post must reach more than the coordinating role:\n- Posting a verdict / decision / result a specific drone is waiting on: add `to:[that drone]` so they're WOKEN \u2014 without it they can be left UNAWARE of their own merge or feedback. Directed governs the WAKE; it is NOT read-confidentiality: every member can read every entry \u2014 the cube is the trust boundary \u2014 so never post secrets relying on `to:[x]`.\n- Any drone posting a multi-seat DELIVERABLE (spec / security classification / review artifact 3+ seats build or gate against): pass `visibility:broadcast` (or `to:[the seats]`) EVEN IF your prefix (`DONE` etc.) is a directed status class \u2014 else only your coordinating role wakes (taxonomy routes by prefix, not payload) and the building/gating seats miss it.\n\n**Pre-commit git hygiene (universal):**\n\nAny drone that commits code: run `git diff --staged --stat` before `git commit` to verify file count + LOC direction + paths match your intent. Catches deleted files / anomalous -LOC / wrong paths pre-push. Your role may layer more git rules (code-implementing + coordinating roles typically carry the full set)."}const H=x();function V(){return'## Operating playbook \u2014 full disciplines (borg_playbook chapter)\n\nThis is the on-demand detail behind the rule-spine in your regen. Load it ONCE per session; it is static \u2014 do not re-fetch on every wake.\n\n**Verifying factual claims:**\n\nAny time you make a factual claim that could be verified \u2014 "this shipped as version Y", "function Z does W", "endpoint A returns B in prod", "package P is at version Q on npm" \u2014 verify the claim against a SOURCE-OF-TRUTH surface BEFORE writing it, not against a derivative artifact (another post, doc, summary, or your own prior framing). Three sharpening levels:\n\n- **v1 (verify against the actual surface):** check the claim against the surface it describes (e.g. a code-state claim \u2192 grep the file). Apply when the claim is about code-state.\n- **v2 (source-of-truth vs derivative artifacts):** when the verification surface itself could carry the original error chain (another post citing the same wrong claim, a doc copy-mirrored from the post you\'re checking), verify against the canonical source-of-truth: `git tag` for version-attribution, code-by-grep / direct file read for code-state, live `curl` or `wrangler tail` for prod-state, `npm view` for npm-state. Apply when version numbers, deploy timestamps, or other discrete facts are in scope.\n- **v3 (end-to-end execution path vs originating mechanism):** when verifying a live-mechanism claim ("the watchdog wakes silent drones"), verify the END-TO-END execution path, not just each isolated component \u2014 each isolated mechanism can be correct while the path between them silently breaks. Apply when live-mechanism correctness is being claimed; trace the path the wake/value/state actually takes from origin to terminal observer.\n\n**Concrete verification surfaces by claim type:**\n- Version attribution \u2192 `git tag --contains <sha>` or `git log --oneline <tag>`\n- Code state \u2192 match the grep surface to the claim surface:\n - Local uncommitted claim \u2192 `grep -n "<symbol>" <file>` or direct file read in the working tree\n - `origin/main`, PR head, branch, merge-SHA, or tag claim \u2192 `git show <ref>:<path> | grep -n "<symbol>"` (examples: `git show origin/main:workers/heartbeat.ts | grep -n "last_log_post"`; `git show origin/feat/foo:client/src/log-stream.ts | grep -n "ownDrone"`; `git show abc1234:workers/cubes.ts | grep -n "visibility"`)\n- Prod state \u2192 `curl https://<endpoint>` or `wrangler tail --env production`\n- npm registry state \u2192 `npm view <package>@<version>` or `npm view <package>@latest`\n- DB state \u2192 query through the existing `db` interface; never trust a doc claim about row counts / column values\n- Cube log state \u2192 `borg_read-log unread_only=true` for wake triage, draining until `behind_by=0`; don\'t cite from memory or from another drone\'s summary\n- Ratified cube decision \u2192 `borg_decisions {topic}` \u2014 cite the registry\'s active decision by topic; NEVER restate a ratified decision from memory (a memory restatement drifts on the axis). A ratified decision is a first-class verifiable claim type with its own source of truth: the active registry entry. Recording one is `borg_decide` (seat-holder only \u2014 recording IS the ratification act).\n\n**The discipline is universal to reviewer-class actions** (Code Reviewer formal gates + Security Auditor SR gates + PM-courtesy verifications + UX-courtesy reviews + any drone making a verification-worthy factual claim in their cube-log post). It lives in this universal playbook rather than any one role\'s text because it applies to ALL reviewers.\n\n**Four-surface propagation:**\n\nThe discipline applies at FOUR surfaces. Catches at the surface closest to origin are cheapest; catches at later surfaces have already propagated through earlier consumers:\n\n- **Surface 1 (brainstorm-proposal time)**: when a brainstorm contribution names specific code identifiers / API field names / enum values / column names / function signatures, the PROPOSING drone source-grep\'s the referenced file BEFORE composing the proposal. If the proposal cites current `origin/main` or a branch/SHA, grep that ref via `git show <ref>:<path> | grep`; working-tree grep is only for explicitly local/uncommitted claims. Cheapest catch surface; one drone catches one error.\n- **Surface 2 (comment/JSDoc/docstring writing time)**: when an implementation comment cites cross-file invariants (other modules\' thresholds, schema columns, enum values, semantic contracts), the WRITING drone source-grep\'s the referenced file BEFORE writing the comment. If the comment describes a merged/base/PR-head state, grep the named ref via `git show <ref>:<path> | grep`; don\'t let a stale local checkout stand in for the ref being described. Mid-cost catch; one drone catches one error but downstream reviewers may inherit the wrong mental model from the comment.\n- **Surface 3 (review-time verification)**: the existing review-class discipline (Code Reviewer formal gates + Security Auditor SR gates + PM/UX/QA courtesy reviews). Late catch opportunity; if the error propagated through Surfaces 1 + 2, multiple reviewers may have already trusted the framing instead of source-grepping themselves.\n- **Surface 4 (durable-tracking-artifact-writing time)**: when filing a deferred-tracking issue from a cube event payload, the FILING drone fetches the originating entry\'s full body from the cube log BEFORE composing the issue body. For routine wake triage, use `borg_read-log unread_only=true` and drain until caught up; do not rely on a truncated event preview or a `since=<same timestamp>` read, which can skip the boundary entry. Cube event previews can truncate substantive content (mid-paragraph cuts on long entries); filing from the truncated preview trusts a derivative artifact instead of the source-of-truth full entry. Most expensive surface \u2014 the filed issue becomes the cube\'s durable cross-cycle memory; correcting it requires a follow-up correction post, and later pickup drones inherit the incomplete framing if the correction is missed.\n\n**Ratified-decision drift is a four-surface drift-class.** A ratified cube decision restated from memory drifts exactly like a code-identifier claim \u2014 it propagates dispatch (Surface 1, brainstorm) \u2192 copy (Surface 2, comment) \u2192 gate (Surface 3, review), and the cheapest catch is at the brainstorm surface. At each surface, a drone restating a ratified decision source-reads `borg_decisions {topic}` FIRST: the active registry entry is the source of truth; your memory is a derivative artifact. Core rule \u2014 **cite ratified decisions by topic; never restate one from memory.**'}function D(e){const o=typeof e=="string"?new Date(e):e,r=Date.now()-o.getTime();if(!Number.isFinite(r)||r<0)return"just now";const i=Math.floor(r/1e3);if(i<60)return`${i}s ago`;const t=Math.floor(i/60);if(t<60)return`${t}m ago`;const n=Math.floor(t/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}function I(e){return e==null||Array.isArray(e)&&e.length===0?"Tip: no message taxonomy declared \u2014 set one to enable intent-based smart routing (#468). Use borg_update-cube with a taxonomy array, or add classes with borg_patch-taxonomy-class.":""}function J(e,o){return e.drone?.label??o??null}let f=!1,y=null;function G(){f=!1,y=null}function L(e){const o=e??"",r=k.filter(i=>o.includes(i));return[..._,...r]}function $(e,o){return`rationale \u2192 borg_role-rationale ${JSON.stringify(e)} ${JSON.stringify(o)}`}function K(e){const o=e.match(/borg_role-rationale\s+("(?:(?:\\.)|[^"\\])*")\s+("(?:(?:\\.)|[^"\\])*")/);if(!o)return null;try{return{role:JSON.parse(o[1]),section:JSON.parse(o[2])}}catch{return null}}const P=[..._,...k];function F(e,o){return A(o??"").map(t=>{if(t.kind!=="label"||t.heading==null||!t.heading.trim().toLowerCase().endsWith("rationale")||P.some(p=>t.body.includes(p)))return t.body;const s=t.body.indexOf(`
4
4
  `);return(s===-1?t.body+`
5
5
  `:t.body.slice(0,s+1))+$(e,t.heading)+`
6
6
  `}).join("")}function Q(e,o={}){const r=o.mode??"full",i=e.roles.map(a=>`- **${a.name}**${a.is_default?" _(default)_":""} \u2014 ${a.short_description||"_(no short description)_"}`).join(`
7
7
  `),t=e.drones.map(a=>{const u=e.roles.find(g=>g.id===a.role_id),h=C(u?.name??"?",a.agent_kind);return`- **${a.label}** (${h}) \u2014 last seen ${D(new Date(a.last_seen))}`}).join(`
8
8
  `)||"_(no drones connected)_",n=typeof e.behind_by=="number"?e.behind_by:null,s=n===null?"Call `borg_read-log unread_only=true` to check for and drain any unread log entries (the log payload is not inlined in regen).":n>0?`You have **${n}** unread log ${n===1?"entry":"entries"}. Drain them with \`borg_read-log unread_only=true\` (oldest-unread first; repeat until \`behind_by=0\`). The log payload is not inlined here \u2014 fetch on demand.`:"You're caught up \u2014 **0** unread log entries. No need to read the log right now.",p=(n??0)===0&&e.drones.length<=1?["## Getting started","","Welcome to your first cube. Here's how to get going:","",'1. Post your first activity: `borg_log message="Starting work on <your task>"`',"2. Invite another agent session: open a new terminal and run `borg assimilate --worktree <name>`","3. Check who's here: `borg_roster`","","---",""].join(`
9
- `):"",b=I(e.cube.message_taxonomy),S=12,m=Array.isArray(e.decisions)?e.decisions:[],w=(()=>{if(m.length===0)return"";const a=m.slice(0,S),u=a.map(g=>`- **${g.topic}:** ${g.decision}`),h=m.length-a.length;return h>0&&u.push(`- _+${h} more \u2014 \`borg_decisions\`_`),["## Ratified decisions","Cite these by topic \u2014 do NOT restate a ratified decision from memory.",...u].join(`
10
- `)})(),l=e.role.detailed_description_hash??null,E=e.role.detailed_description?F(e.role.name,e.role.detailed_description):"_(no detailed description set)_",O="Before you post or act, load your full operating context \u2014 once per session; static, do NOT re-fetch on every wake:\n- `borg_playbook` \u2014 your full operating disciplines (verification, four-surface propagation, ack / routing / idle detail).\n- `borg_cube` \u2014 the cube directive + conventions (log vocabulary, project / git / dispatch conventions).",v=r==="full"||l==null||l!==y,R=r==="full"||!f,d=[p+`# Cube: ${e.cube.name} \u2014 ${e.drone.label}`,"",`**Your role:** ${e.role.name}`,""];return r==="lite"&&d.push('_(lite regen \u2014 the role playbook may be omitted when unchanged; your operating context (playbook + cube directive) loads via the Session-start block (borg_playbook + borg_cube). If the playbook is NOT in your current context (e.g. after a context-compaction), call `borg_regen mode="full"` to re-orient.)_',""),d.push(r==="full"?"## Session start \u2014 required before acting":"## Session start",r==="full"?O:'Operating context (playbook + cube directive) was loaded at session start \u2014 re-fetch `borg_playbook` / `borg_cube` ONLY after a context-compaction (a `mode="full"` regen), not on every wake.',"",...b?[b,""]:[],`## Your role: ${e.role.name}`,v?E:["_(role playbook unchanged since your last full/lite regen; omitted in lite mode)_","",...L(e.role.detailed_description)].join(`
11
- `),"","## Roles in this cube",i,"","## Connected drones",t,"","## Cube log",s,...w?["",w]:[]),R&&(d.push("",x()),f=!0),v&&l!=null&&(y=l),d.join(`
12
- `)}function X(e,o,r){const i=o.get(e.drone_id),t=i?r.get(i.role_id):null,n=new Date(e.created_at).toISOString(),s=typeof e.id=="string"&&e.id.length>0?` [entry_id: ${e.id}]`:"",c=typeof e.drone_id=="string"&&e.drone_id.length>0?` ${N(e.drone_id)}`:"";return`**[${n}]**${s}${c} ${i?.label??"?"} (${t?.name??"?"}): ${e.message}`}export{H as DRONE_PLAYBOOK,G as __resetRegenSessionState,F as compressRoleText,q as formatLeanOrientation,X as formatLogEntryMarkdown,$ as formatRationalePointer,Q as formatRegenMarkdown,x as getDronePlaybook,V as getDronePlaybookChapter,D as humanAgo,I as nullTaxonomyTip,M as parseHookSource,K as parseRationalePointer,J as regenWakePathDroneLabel,j as resolveLeanIdentity,T as wakePathArming};
9
+ `):"",b=I(e.cube.message_taxonomy),S=12,m=Array.isArray(e.decisions)?e.decisions:[],v=(()=>{if(m.length===0)return"";const a=m.slice(0,S),u=a.map(g=>`- **${g.topic}:** ${g.decision}`),h=m.length-a.length;return h>0&&u.push(`- _+${h} more \u2014 \`borg_decisions\`_`),["## Ratified decisions","Cite these by topic \u2014 do NOT restate a ratified decision from memory.",...u].join(`
10
+ `)})(),l=e.role.detailed_description_hash??null,E=e.role.detailed_description?F(e.role.name,e.role.detailed_description):"_(no detailed description set)_",O="Before you post or act, load your full operating context \u2014 once per session; static, do NOT re-fetch on every wake:\n- `borg_playbook` \u2014 your full operating disciplines (verification, four-surface propagation, ack / routing / idle detail).\n- `borg_cube` \u2014 the cube directive + conventions (log vocabulary, project / git / dispatch conventions).",w=r==="full"||l==null||l!==y,R=r==="full"||!f,d=[p+`# Cube: ${e.cube.name} \u2014 ${e.drone.label}`,"",`**Your role:** ${e.role.name}`,""];return r==="lite"&&d.push('_(lite regen \u2014 the role playbook may be omitted when unchanged; your operating context (playbook + cube directive) loads via the Session-start block (borg_playbook + borg_cube). If the playbook is NOT in your current context (e.g. after a context-compaction), call `borg_regen mode="full"` to re-orient.)_',""),d.push(r==="full"?"## Session start \u2014 required before acting":"## Session start",r==="full"?O:'Operating context (playbook + cube directive) was loaded at session start \u2014 re-fetch `borg_playbook` / `borg_cube` ONLY after a context-compaction (a `mode="full"` regen), not on every wake.',"",...b?[b,""]:[],`## Your role: ${e.role.name}`,w?E:["_(role playbook unchanged since your last full/lite regen; omitted in lite mode)_","",...L(e.role.detailed_description)].join(`
11
+ `),"","## Roles in this cube",i,"","## Connected drones",t,"","## Cube log",s,...v?["",v]:[]),R&&(d.push("",x()),f=!0),w&&l!=null&&(y=l),d.join(`
12
+ `)}function X(e,o,r){const i=o.get(e.drone_id),t=i?r.get(i.role_id):null,n=new Date(e.created_at).toISOString(),s=typeof e.id=="string"&&e.id.length>0?` [entry_id: ${e.id}]`:"",c=typeof e.drone_id=="string"&&e.drone_id.length>0?` ${T(e.drone_id)}`:"";return`**[${n}]**${s}${c} ${i?.label??"?"} (${t?.name??"?"}): ${e.message}`}export{H as DRONE_PLAYBOOK,G as __resetRegenSessionState,F as compressRoleText,q as formatLeanOrientation,X as formatLogEntryMarkdown,$ as formatRationalePointer,Q as formatRegenMarkdown,x as getDronePlaybook,V as getDronePlaybookChapter,D as humanAgo,I as nullTaxonomyTip,M as parseHookSource,K as parseRationalePointer,J as regenWakePathDroneLabel,j as resolveLeanIdentity,N as wakePathArming};
@@ -1 +1 @@
1
- import{getIdToken as f,getRefreshToken as h,clearTokens as m}from"./config.js";import{refreshIdToken as j,RefreshTokenInvalidError as T,RefreshTransientError as g}from"./auth.js";import{consolePrefix as R}from"./console-prefix.js";import{debugLog as b}from"./debug.js";import{assertUuidShape as $}from"./evict-drone.js";import{DroneEvictedError as C,DroneFrozenError as k,DRONE_EVICTED_CODE as P,DRONE_FROZEN_CODE as O,errorCodeFromBody as I}from"./drone-lifecycle.js";import{MODEL_DESCRIPTOR_REGEX as D}from"./model-presets.js";const L=process.env.BORG_API_URL||"https://api.borgmcp.ai",A=3,G=6e4;let l=null;function x(e){return l||(l=j(e).finally(()=>{l=null}),l)}function J(e){if(e==null)return null;const n=e.trim();return/^\d+$/.test(n)?parseInt(n,10)*1e3:null}function v(e,n,t=G,o=()=>Math.random()*500){const s=e??1e3*(n+1);return Math.min(s,t)+o()}function M(e){const n=(t,o)=>`${t}${/[.:!?]$/.test(t)?"":":"} ${o}`;try{const t=JSON.parse(e);if(typeof t?.error=="string")return typeof t.details=="string"?n(t.error,t.details):t.error;if(t?.error&&typeof t.error=="object"){const o=t.error.message,s=t.error.details??t.details;if(typeof o=="string"&&typeof s=="string")return n(o,s);if(typeof o=="string")return o}if(typeof t?.message=="string"&&typeof t?.details=="string")return n(t.message,t.details);if(typeof t?.message=="string")return t.message}catch{}return e}async function U(e,n,t){const o=t.maxRetries??A;let s=e,a=0;for(;s.status===429&&a<o;){const d=v(J(s.headers.get("Retry-After")),a,t.capMs,t.jitter);t.log?.(`rate limited (429); retrying in ${Math.round(d)}ms (attempt ${a+1}/${o})`),await t.sleep(d),a++,s=await n()}return s}function q(e){return new Promise(n=>setTimeout(n,e))}async function B(){let e=await f();if(!e){const n=await h(),t=n!=null;if(n)try{await x(n),e=await f()}catch(o){if(o instanceof T)await m();else throw o instanceof g?o:new g(`Token refresh failed unexpectedly (your saved login was NOT cleared \u2014 retry; if it persists, restart the borg session): ${o?.message??"unknown"}`)}if(!e)throw new Error(t?"Authentication expired \u2014 your saved login has expired. Run: borg setup":"Authentication required \u2014 you are not signed in. Run: borg setup")}return e}async function F(){const e=await h();if(!e)return null;try{return await x(e),await f()}catch(n){if(n instanceof T&&await m(),n instanceof g)throw n;return null}}async function Q(){if(await f())return"valid";const n=await h();if(!n)return"dead";try{return await x(n),await f()?"valid":"transient"}catch(t){return t instanceof T?(await m(),"dead"):"transient"}}async function r(e,n={}){let t=await B();const{droneSession:o,apiUrl:s,headers:a,...d}=n,_=s??L,y=(d.method??"GET").toUpperCase(),w=async c=>{const p={Authorization:`Bearer ${c}`,...a};o&&(p["X-Drone-Session"]=o),b(`\u2192 ${y} ${e}`);const u=await fetch(`${_}${e}`,{...d,headers:p});return b(`\u2190 ${u.status} ${y} ${e}`),u};let i=await w(t);if(i.status===401){const c=await F();c&&(t=c,i=await w(t))}if(i.status===401)throw new Error("Authentication required. Run: borg setup");if(i.status===429&&(i=await U(i,()=>w(t),{sleep:q,log:c=>console.error(`${R()}${c}`)})),!i.ok){const c=await i.text();b(`\u2717 ${i.status} ${y} ${e}: ${c}`);const p=M(c),u=I(c);if(i.status===410&&u===P)throw new C(p);if(i.status===423&&u===O)throw new k(p);if(i.status===429){const E=i.headers.get("Retry-After"),S=E?` (retry after ${E}s)`:"";throw new Error(`HTTP 429: rate limited${S}: ${p}`)}throw new Error(`HTTP ${i.status}: ${p}`)}return i}async function Y(e,n,t,o){const s={hostname:t??null};if((o==="claude"||o==="codex")&&(s.agent_kind=o),typeof e=="string")s.cube_name=e;else if(e.cube_id&&(s.cube_id=e.cube_id),e.cube_name&&(s.cube_name=e.cube_name),e.role_id&&(s.role_id=e.role_id),e.role_name&&(s.role_name=e.role_name),e.prior_drone_id&&(s.prior_drone_id=e.prior_drone_id),e.model!=null){if(!D.test(e.model))throw new Error(`Invalid model descriptor: "${e.model}" (expected "<claude|ollama>:<model>").`);s.model=e.model}return await(await r("/api/assimilate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s),apiUrl:n})).json()}async function K(e,n){return await(await r("/api/drone/cube",{method:"GET",droneSession:e,apiUrl:n})).json()}async function ee(e,n){return await(await r("/api/drone/role",{method:"GET",droneSession:e,apiUrl:n})).json()}async function te(e,n,t){const o=new URLSearchParams({role:t});return await(await r(`/api/drone/role?${o.toString()}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function ne(e,n){return await(await r("/api/drone/whoami",{method:"GET",droneSession:e,apiUrl:n})).json()}async function oe(e,n,t){const o=t?`?since=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/roster${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function se(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since),t.limit!==void 0&&o.set("limit",String(t.limit)),t.unreadOnly&&o.set("unread_only","true");const s=o.toString();return await(await r(`/api/drone/log${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function re(e,n,t,o="ack"){await r(`/api/drone/log/${t}/ack`,{method:"POST",body:JSON.stringify({kind:o}),droneSession:e,apiUrl:n})}async function ae(e,n,t){return await(await r("/api/drone/decide",{method:"POST",body:JSON.stringify(t),droneSession:e,apiUrl:n})).json()}async function ie(e,n,t){const o=t?`?topic=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/decisions${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function ce(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since);const s=o.toString();return await(await r(`/api/drone/regen${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function pe(e,n,t,o){const s=new URLSearchParams({role:t,section:o});return await(await r(`/api/drone/role-rationale?${s.toString()}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function de(e,n,t,o={}){const s={message:t,...o.visibility?{visibility:o.visibility}:{},...o.recipientDroneIds?{recipientDroneIds:o.recipientDroneIds}:{},...o.class?{class:o.class}:{},...o.to?{to:o.to}:{}};return await(await r("/api/drone/log",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(s)})).json()}async function ue(e,n,t){const o={kind:t.kind??"friction",message:t.message,...t.metadata?{metadata:t.metadata}:{}};return await(await r("/api/drone/report",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(o)})).json()}async function fe(){const e=await r("/api/reports",{method:"GET"});if(e.status===403)return{forbidden:!0};if(!e.ok)throw new Error(`Failed to fetch reports: ${e.status}`);return{forbidden:!1,reports:(await e.json()).reports}}async function le(){return await(await r("/api/cubes",{method:"GET"})).json()}async function ye(){return await(await r("/api/templates",{method:"GET"})).json()}async function we(e,n,t){const o={cube_directive:n};e&&(o.name=e),t?.template&&(o.template=t.template),t&&Object.prototype.hasOwnProperty.call(t,"message_taxonomy")&&(o.message_taxonomy=t.message_taxonomy??null);const a=await(await r("/api/cubes",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json();return a.cube?{...a.cube,roles:a.roles??[],drones:a.drones??[]}:a}async function he(e,n){return await(await r(`/api/cubes/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function me(e,n){return await(await r(`/api/cubes/${e}/taxonomy-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function Te(e){await r(`/api/cubes/${e}`,{method:"DELETE"})}async function ge(e,n){return await(await r(`/api/cubes/${e}/roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function be(e,n){return await(await r(`/api/roles/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function xe(e,n){return await(await r(`/api/roles/${e}/section-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function Ee(e){await r(`/api/roles/${e}`,{method:"DELETE"})}async function $e(e,n){return $(e,"drone_id"),await(await r(`/api/drones/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({role_id:n})})).json()}async function _e(e){$(e,"drone_id"),await r(`/api/drones/${e}`,{method:"DELETE"})}async function Se(e){const t=await(await r(`/api/cubes/${e}`,{method:"GET"})).json();return t.cube?{...t.cube,roles:t.roles??[],drones:t.drones??[]}:t}async function je(e,n){return await(await r(`/api/cubes/${e}/apply-template`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n})})).json()}async function Re(){return await(await r("/api/subscription/status",{method:"GET"})).json()}async function Ce(e,n="software-dev",t=!1,o){return await(await r(`/api/cubes/${e}/sync-roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n,apply:t,...o?{decisions:o}:{}})})).json()}async function ke(){const n=await(await r("/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.checkout_url)throw new Error("No checkout URL in response");return n.checkout_url}async function Pe(){const n=await(await r("/api/subscription/portal",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.portal_url)throw new Error(n.message||"No portal URL in response");return n.portal_url}export{L as API_URL,re as ackLogEntry,de as appendLog,je as applyTemplate,Y as assimilate,Re as checkSubscriptionStatus,Pe as createBillingPortalSession,we as createCube,ge as createRole,ke as createSubscription,Te as deleteCube,Ee as deleteRole,_e as evictDrone,M as extractHttpErrorMessage,fe as fetchReports,Se as getCube,K as getCubeInfo,ee as getRoleInfo,te as getRoleInfoByName,oe as getRoster,B as getValidToken,le as listCubes,ie as listDecisions,ye as listTemplates,J as parseRetryAfterMs,xe as patchRoleSection,me as patchTaxonomyClass,Q as probeSession,v as rateLimitWaitMs,se as readLog,$e as reassignDrone,ae as recordDecision,ce as regen,U as retryOn429,pe as roleRationale,ue as submitReport,Ce as syncRoles,he as updateCube,be as updateRole,ne as whoami};
1
+ import{getIdToken as f,getRefreshToken as h,clearTokens as m}from"./config.js";import{refreshIdToken as j,RefreshTokenInvalidError as T,RefreshTransientError as g}from"./auth.js";import{consolePrefix as R}from"./console-prefix.js";import{debugLog as b}from"./debug.js";import{assertUuidShape as $}from"./evict-drone.js";import{DroneEvictedError as C,DroneFrozenError as k,DRONE_EVICTED_CODE as P,DRONE_FROZEN_CODE as O,errorCodeFromBody as I}from"./drone-lifecycle.js";import{MODEL_DESCRIPTOR_REGEX as D}from"./model-presets.js";const L=process.env.BORG_API_URL||"https://api.borgmcp.ai",A=3,G=6e4;let l=null;function x(e){return l||(l=j(e).finally(()=>{l=null}),l)}function J(e){if(e==null)return null;const n=e.trim();return/^\d+$/.test(n)?parseInt(n,10)*1e3:null}function v(e,n,t=G,o=()=>Math.random()*500){const s=e??1e3*(n+1);return Math.min(s,t)+o()}function M(e){const n=(t,o)=>`${t}${/[.:!?]$/.test(t)?"":":"} ${o}`;try{const t=JSON.parse(e);if(typeof t?.error=="string")return typeof t.details=="string"?n(t.error,t.details):t.error;if(t?.error&&typeof t.error=="object"){const o=t.error.message,s=t.error.details??t.details;if(typeof o=="string"&&typeof s=="string")return n(o,s);if(typeof o=="string")return o}if(typeof t?.message=="string"&&typeof t?.details=="string")return n(t.message,t.details);if(typeof t?.message=="string")return t.message}catch{}return e}async function U(e,n,t){const o=t.maxRetries??A;let s=e,a=0;for(;s.status===429&&a<o;){const d=v(J(s.headers.get("Retry-After")),a,t.capMs,t.jitter);t.log?.(`rate limited (429); retrying in ${Math.round(d)}ms (attempt ${a+1}/${o})`),await t.sleep(d),a++,s=await n()}return s}function q(e){return new Promise(n=>setTimeout(n,e))}async function B(){let e=await f();if(!e){const n=await h(),t=n!=null;if(n)try{await x(n),e=await f()}catch(o){if(o instanceof T)await m();else throw o instanceof g?o:new g(`Token refresh failed unexpectedly (your saved login was NOT cleared \u2014 retry; if it persists, restart the borg session): ${o?.message??"unknown"}`)}if(!e)throw new Error(t?"Authentication expired \u2014 your saved login has expired. Run: borg setup":"Authentication required \u2014 you are not signed in. Run: borg setup")}return e}async function F(){const e=await h();if(!e)return null;try{return await x(e),await f()}catch(n){if(n instanceof T&&await m(),n instanceof g)throw n;return null}}async function Q(){if(await f())return"valid";const n=await h();if(!n)return"dead";try{return await x(n),await f()?"valid":"transient"}catch(t){return t instanceof T?(await m(),"dead"):"transient"}}async function r(e,n={}){let t=await B();const{droneSession:o,apiUrl:s,headers:a,...d}=n,_=s??L,y=(d.method??"GET").toUpperCase(),w=async c=>{const p={Authorization:`Bearer ${c}`,...a};o&&(p["X-Drone-Session"]=o),b(`\u2192 ${y} ${e}`);const u=await fetch(`${_}${e}`,{...d,headers:p});return b(`\u2190 ${u.status} ${y} ${e}`),u};let i=await w(t);if(i.status===401){const c=await F();c&&(t=c,i=await w(t))}if(i.status===401)throw new Error("Authentication required. Run: borg setup");if(i.status===429&&(i=await U(i,()=>w(t),{sleep:q,log:c=>console.error(`${R()}${c}`)})),!i.ok){const c=await i.text();b(`\u2717 ${i.status} ${y} ${e}: ${c}`);const p=M(c),u=I(c);if(i.status===410&&u===P)throw new C(p);if(i.status===423&&u===O)throw new k(p);if(i.status===429){const E=i.headers.get("Retry-After"),S=E?` (retry after ${E}s)`:"";throw new Error(`HTTP 429: rate limited${S}: ${p}`)}throw new Error(`HTTP ${i.status}: ${p}`)}return i}async function Y(e,n,t,o){const s={hostname:t??null};if((o==="claude"||o==="codex"||o==="opencode")&&(s.agent_kind=o),typeof e=="string")s.cube_name=e;else if(e.cube_id&&(s.cube_id=e.cube_id),e.cube_name&&(s.cube_name=e.cube_name),e.role_id&&(s.role_id=e.role_id),e.role_name&&(s.role_name=e.role_name),e.prior_drone_id&&(s.prior_drone_id=e.prior_drone_id),e.model!=null){if(!D.test(e.model))throw new Error(`Invalid model descriptor: "${e.model}" (expected "<claude|ollama>:<model>").`);s.model=e.model}return await(await r("/api/assimilate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s),apiUrl:n})).json()}async function K(e,n){return await(await r("/api/drone/cube",{method:"GET",droneSession:e,apiUrl:n})).json()}async function ee(e,n){return await(await r("/api/drone/role",{method:"GET",droneSession:e,apiUrl:n})).json()}async function te(e,n,t){const o=new URLSearchParams({role:t});return await(await r(`/api/drone/role?${o.toString()}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function ne(e,n){return await(await r("/api/drone/whoami",{method:"GET",droneSession:e,apiUrl:n})).json()}async function oe(e,n,t){const o=t?`?since=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/roster${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function se(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since),t.limit!==void 0&&o.set("limit",String(t.limit)),t.unreadOnly&&o.set("unread_only","true");const s=o.toString();return await(await r(`/api/drone/log${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function re(e,n,t,o="ack"){await r(`/api/drone/log/${t}/ack`,{method:"POST",body:JSON.stringify({kind:o}),droneSession:e,apiUrl:n})}async function ae(e,n,t){return await(await r("/api/drone/decide",{method:"POST",body:JSON.stringify(t),droneSession:e,apiUrl:n})).json()}async function ie(e,n,t){const o=t?`?topic=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/decisions${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function ce(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since);const s=o.toString();return await(await r(`/api/drone/regen${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function pe(e,n,t,o){const s=new URLSearchParams({role:t,section:o});return await(await r(`/api/drone/role-rationale?${s.toString()}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function de(e,n,t,o={}){const s={message:t,...o.visibility?{visibility:o.visibility}:{},...o.recipientDroneIds?{recipientDroneIds:o.recipientDroneIds}:{},...o.class?{class:o.class}:{},...o.to?{to:o.to}:{}};return await(await r("/api/drone/log",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(s)})).json()}async function ue(e,n,t){const o={kind:t.kind??"friction",message:t.message,...t.metadata?{metadata:t.metadata}:{}};return await(await r("/api/drone/report",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(o)})).json()}async function fe(){const e=await r("/api/reports",{method:"GET"});if(e.status===403)return{forbidden:!0};if(!e.ok)throw new Error(`Failed to fetch reports: ${e.status}`);return{forbidden:!1,reports:(await e.json()).reports}}async function le(){return await(await r("/api/cubes",{method:"GET"})).json()}async function ye(){return await(await r("/api/templates",{method:"GET"})).json()}async function we(e,n,t){const o={cube_directive:n};e&&(o.name=e),t?.template&&(o.template=t.template),t&&Object.prototype.hasOwnProperty.call(t,"message_taxonomy")&&(o.message_taxonomy=t.message_taxonomy??null);const a=await(await r("/api/cubes",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json();return a.cube?{...a.cube,roles:a.roles??[],drones:a.drones??[]}:a}async function he(e,n){return await(await r(`/api/cubes/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function me(e,n){return await(await r(`/api/cubes/${e}/taxonomy-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function Te(e){await r(`/api/cubes/${e}`,{method:"DELETE"})}async function ge(e,n){return await(await r(`/api/cubes/${e}/roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function be(e,n){return await(await r(`/api/roles/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function xe(e,n){return await(await r(`/api/roles/${e}/section-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function Ee(e){await r(`/api/roles/${e}`,{method:"DELETE"})}async function $e(e,n){return $(e,"drone_id"),await(await r(`/api/drones/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({role_id:n})})).json()}async function _e(e){$(e,"drone_id"),await r(`/api/drones/${e}`,{method:"DELETE"})}async function Se(e){const t=await(await r(`/api/cubes/${e}`,{method:"GET"})).json();return t.cube?{...t.cube,roles:t.roles??[],drones:t.drones??[]}:t}async function je(e,n){return await(await r(`/api/cubes/${e}/apply-template`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n})})).json()}async function Re(){return await(await r("/api/subscription/status",{method:"GET"})).json()}async function Ce(e,n="software-dev",t=!1,o){return await(await r(`/api/cubes/${e}/sync-roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n,apply:t,...o?{decisions:o}:{}})})).json()}async function ke(){const n=await(await r("/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.checkout_url)throw new Error("No checkout URL in response");return n.checkout_url}async function Pe(){const n=await(await r("/api/subscription/portal",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.portal_url)throw new Error(n.message||"No portal URL in response");return n.portal_url}export{L as API_URL,re as ackLogEntry,de as appendLog,je as applyTemplate,Y as assimilate,Re as checkSubscriptionStatus,Pe as createBillingPortalSession,we as createCube,ge as createRole,ke as createSubscription,Te as deleteCube,Ee as deleteRole,_e as evictDrone,M as extractHttpErrorMessage,fe as fetchReports,Se as getCube,K as getCubeInfo,ee as getRoleInfo,te as getRoleInfoByName,oe as getRoster,B as getValidToken,le as listCubes,ie as listDecisions,ye as listTemplates,J as parseRetryAfterMs,xe as patchRoleSection,me as patchTaxonomyClass,Q as probeSession,v as rateLimitWaitMs,se as readLog,$e as reassignDrone,ae as recordDecision,ce as regen,U as retryOn429,pe as roleRationale,ue as submitReport,Ce as syncRoles,he as updateCube,be as updateRole,ne as whoami};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "borgmcp",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Coordinate AI coding agents in shared cubes. Works with Claude Code and Codex. Create projects, assign roles, and share a live activity log.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",