borgmcp 1.1.5 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Multi-agent coordination for AI coding agents.
4
4
 
5
- Borg MCP lets Claude Code and Codex sessions join the same project coordination space, take on named roles, and coordinate through one shared activity log. Borg MCP is an MCP server, so install Claude Code or Codex first. That shared space is a **cube**. Each running agent session is a **drone**. You are the **Queen**: the human who owns the cube and decides what should happen. Drones operate under **roles** such as Builder, Code Reviewer, Coordinator, UX Expert, or Security Auditor.
5
+ Borg MCP lets Claude Code, Codex, and OpenCode sessions join the same project coordination space, take on named roles, and coordinate through one shared activity log. Borg MCP is an MCP server, so install Claude Code, Codex, or OpenCode first. That shared space is a **cube**. Each running agent session is a **drone**. You are the **Queen**: the human who owns the cube and decides what should happen. Drones operate under **roles** such as Builder, Code Reviewer, Coordinator, UX Expert, or Security Auditor.
6
6
 
7
7
  ## What you get
8
8
 
@@ -10,7 +10,7 @@ Borg MCP lets Claude Code and Codex sessions join the same project coordination
10
10
  - Role-based coordination: assign drones to roles and keep their instructions in one place.
11
11
  - Live activity log: post status, dispatches, review gates, and findings without copy-paste handoffs.
12
12
  - Wake-path support: inbox monitoring and stream diagnostics help keep agent sessions responsive.
13
- - Claude Code and Codex launcher support: start one or more agent sessions from the same repo.
13
+ - Claude Code, Codex, and OpenCode launcher support: start one or more agent sessions from the same repo.
14
14
  - Dashboard support: manage cubes, roles, drones, and logs at <https://borgmcp.ai/dashboard>.
15
15
 
16
16
  ## Pricing
@@ -46,13 +46,14 @@ borg setup
46
46
  The wizard signs you in with Google, checks your subscription access, and registers Borg MCP with the supported agent CLIs installed on your machine.
47
47
 
48
48
  `borg ...` commands are terminal commands. `borg_...` commands are MCP tools
49
- you ask your agent to run inside Claude Code or Codex.
49
+ you ask your agent to run inside Claude Code, Codex, or OpenCode.
50
50
 
51
- If both Claude Code and Codex are installed, use `--cli` when launching or assimilating if you want to choose explicitly:
51
+ If multiple CLIs are installed, use `--cli` when launching or assimilating if you want to choose explicitly:
52
52
 
53
53
  ```bash
54
54
  borg assimilate --cli claude
55
55
  borg assimilate --cli codex
56
+ borg assimilate --cli opencode
56
57
  ```
57
58
 
58
59
  ## Join a cube
@@ -1,48 +1,48 @@
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+`
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 B,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{computeOpenCodePort as De,connectOpenCodeDrone as Ue,injectInitialKickoff as Oe}from"./opencode-drone.js";async function ct(r,e){if(r.role!==void 0){const t=J(r.role);if(!t.ok)return e.stderr(t.error+`
2
2
  `),1}if(r.flags.worktree!==void 0){const t=J(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
3
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
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(`
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 N=I.find(t=>t.name===i);if(!N&&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,A;if(N)u=await e.getCube(a.apiUrl,a.token,N.id),A=!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 E=$===0?" (default)":"";c.push(` ${$+1}) ${v.name}${E} \u2014 ${v.description}`)}),c.push(` ${n.length+1}) skip \u2014 no template`);const f=(await e.prompt(c.join(`
8
8
  `)+`
9
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}),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}
14
+ `),u=await e.createCube(a.apiUrl,a.token,t?{name:i??void 0,template:t}:{name:i??void 0}),A=!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=Be(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=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.
16
+ `),1}}else{const t=new Set((u.drones??[]).map(n=>n.role_id));if(g=ce(u.roles,{isFirstDrone:A,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 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}
18
+ `),1;const H=r.flags.worktree!==void 0||x!==null&&!r.flags.here,Z=S??x?.droneId??null,P=H?null:await e.getLaunchModel(u.id,o,Z),b=r.flags.model??P?.model??g.default_model??null,L=Re(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
19
  `),1}const m=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${u.name}' as ${g.name}\u2026
20
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
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 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.
24
+ `);const ee=H;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
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
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)}
29
+ `),1;const E=e.homedir();let h=V(E,v,$),p=B(R(h),v),G=2;for(;e.pathExists(h)||Me(e,o,h)||X(e.runSync,o,p)&&!ve(e.runSync,o,p,n);)h=V(E,v,$,G),p=B(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
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}
31
+ `),e.chdir(h),e.stderr(je(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
33
  `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${w}\` (rollback attempt failed: ${Q(c.stderr).trim()||"unknown"})
34
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}
35
+ `)}if(!w){e.runSync("git",["fetch","origin","--prune"],d);const t=B(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(We(d,t))):n.message&&e.stderr(`worktree sync: ${n.message}
37
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(`
38
+ `);const re=e.getInboxPath(s.cube_id,s.drone_id),T=m==="codex"?`borg-wake-${ae()}`:null,ne=be(m,m==="claude"?re:null);let D,F=[],C,U=null,O=null;const K=e.findProjectRoot(d);b?await e.setLaunchModel(s.cube_id,K,{model:b,ollamaBaseUrl:_e(b).kind==="ollama"?L:null}):await e.clearLaunchModel(s.cube_id,K);const Y=Ce(b,L),j={...process.env,...Y.set,BORG_SESSION:"1"};for(const t of Y.unset)delete j[t];if(m==="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.",F=t.args,Object.keys(t.env).length>0&&Object.assign(j,t.env),U=ye(t.args),O=t.server?.cleanup??null}const W=we({cli:m,codexWakeNonce:T,monitorClause:ne,codexWakePathClause:D});let M;C=[W],m==="codex"?C=[...xe(),...F,...ge(C,d)]:m==="opencode"&&(M=De(s.drone_id),Te(),C=[d,"--port",String(M),"--auto","--prompt",W]);const oe=e.exec(m,C,d,j);if(m==="codex"&&U&&T&&ke({deps:e,cubeId:s.cube_id,droneId:s.drone_id,socketPath:U,cwd:d,previewNeedle:T,launchedAtSeconds:Math.floor(Date.now()/1e3)}),m==="opencode"){const t=`http://127.0.0.1:${M}`;Ue({serverUrl:t,directory:d,droneLabel:s.drone_label,cubeName:i??"borg"}).then(()=>Oe(W)).catch(()=>{})}const ie=await oe;if(O)try{O()}catch{}return w&&_!==w&&e.stderr(`
40
40
  Agent exited. You were working in ${w}; your shell is back in ${_}.
41
41
  To return:
42
42
  cd ${he(w)}
43
- `),ie}function We(r,e,a){return`
43
+ `),ie}function je(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 Me(r,e){return`
45
+ `}function We(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 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};
47
+ `}function Q(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function Me(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 Be(r,e){if(e.length===0)return null;const a=r.toLowerCase();let o=null;for(const i of e){const l=He(a,i.toLowerCase());l<=2&&(o===null||l<o.distance)&&(o={name:i,distance:l})}return o?o.name:null}function He(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{ct as runAssimilate,Q as safeStderr,Be as suggestRoleName};
@@ -1,3 +1,3 @@
1
- import{spawnSync as p,spawn as l}from"node:child_process";import{existsSync as h,mkdirSync as f}from"node:fs";import{hostname as b,homedir as _}from"node:os";import{createInterface as d}from"node:readline/promises";import{API_URL as u,getValidToken as m,listCubes as C,getCube as T,createCube as y,assimilate as k,listTemplates as w}from"./remote-client.js";import{findProjectRoot as g,getActiveCube as L,setActiveCube as x,inboxPathForDrone as M,setCodexWakeTarget as S,getLaunchModel as v,setLaunchModel as R,clearLaunchModel as A}from"./cubes.js";import{authenticateWithGoogle as P}from"./auth.js";import{addProjectSessionStartHook as U}from"./config-utils.js";import{setTerminalTitle as j}from"./terminal-title.js";import{defaultCliChoiceDeps as D,resolveCliChoice as G}from"./cli-platform.js";import{prepareCodexRemoteLaunch as H,defaultCodexRemoteDeps as I}from"./codex-remote.js";import{findLoadedCodexThread as q}from"./codex-app-server.js";import{checkModelReachable as V}from"./model-presets.js";function $(){return{runSync:(e,r,t)=>{const o=p(e,r,{cwd:t,encoding:"utf-8"});return{status:o.status,stdout:o.stdout??"",stderr:o.stderr??""}},pathExists:e=>h(e),cwd:()=>process.cwd(),chdir:e=>process.chdir(e),homedir:()=>_(),mkdirp:e=>f(e,{recursive:!0}),exec:(e,r,t,o)=>new Promise((s,i)=>{const a=l(e,r,{cwd:t,stdio:"inherit",shell:!1,env:o??process.env});a.on("error",i),a.on("exit",n=>s(n??0))}),stderr:e=>process.stderr.write(e),stdout:e=>process.stdout.write(e),prompt:async e=>{const r=d({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}},isTTY:()=>process.stdin.isTTY===!0,getHostname:()=>b(),setTerminalTitle:(e,r)=>{j({label:e,cubeName:r},r)},getActiveCube:()=>L(),setActiveCube:e=>x(e),findProjectRoot:e=>g(e),installProjectSessionHook:e=>{U(e)},getCachedAuth:async()=>{try{return{token:await m(),apiUrl:u}}catch{return null}},runSetup:async()=>(await P(),{token:await m(),apiUrl:u}),listCubes:async(e,r)=>{const{cubes:t}=await C();return t.map(o=>({id:o.id,name:o.name}))},getCube:async(e,r,t)=>{const o=await T(t);return{id:o.id,name:o.name,roles:o.roles,drones:(o.drones??[]).map(s=>({role_id:s.role_id}))}},createCube:async(e,r,t)=>{const o=await y(t.name,"",t.template?{template:t.template}:void 0);return{id:o.id,name:o.name,roles:o.roles}},assimilate:async(e,r,t)=>{const o=await k({cube_id:t.cube_id,role_id:t.role_id,...t.prior_drone_id?{prior_drone_id:t.prior_drone_id}:{},...t.model!=null?{model:t.model}:{}},void 0,t.hostname??null,t.agent_kind??null);return{cube_id:o.cube.id,drone_id:o.drone.id,drone_label:o.drone.label,session_token:o.sessionToken,role_id:o.role.id,reattached:o.reattached===!0}},listTemplates:async(e,r)=>{const{templates:t}=await w();return t.map(o=>({name:o.name,description:o.description}))},getInboxPath:(e,r)=>M(e,r),probeMcpReady:()=>new Promise(e=>{const r=l("borg-mcp",[],{stdio:["pipe","pipe","pipe"],shell:!1});let t="",o=!1;const s=n=>{if(!o){o=!0;try{r.kill("SIGTERM")}catch{}e(n)}},i=setTimeout(()=>s(!1),2e3);r.on("error",()=>{clearTimeout(i),s(!1)}),r.on("exit",()=>{clearTimeout(i),s(o)}),r.stdout?.on("data",n=>{t+=n.toString("utf-8");for(const c of t.split(`
1
+ import{spawnSync as p,spawn as l}from"node:child_process";import{existsSync as h,mkdirSync as f}from"node:fs";import{hostname as b,homedir as _}from"node:os";import{createInterface as d}from"node:readline/promises";import{readinessProbeEnv as C}from"./readiness-probe.js";import{API_URL as u,getValidToken as m,listCubes as T,getCube as y,createCube as k,assimilate as w,listTemplates as g}from"./remote-client.js";import{findProjectRoot as L,getActiveCube as v,setActiveCube as x,inboxPathForDrone as M,setCodexWakeTarget as S,getLaunchModel as R,setLaunchModel as P,clearLaunchModel as A}from"./cubes.js";import{authenticateWithGoogle as U}from"./auth.js";import{addProjectSessionStartHook as j}from"./config-utils.js";import{setTerminalTitle as D}from"./terminal-title.js";import{defaultCliChoiceDeps as G,resolveCliChoice as H}from"./cli-platform.js";import{prepareCodexRemoteLaunch as I,defaultCodexRemoteDeps as q}from"./codex-remote.js";import{findLoadedCodexThread as E}from"./codex-app-server.js";import{checkModelReachable as V}from"./model-presets.js";function te(){return{runSync:(e,r,t)=>{const o=p(e,r,{cwd:t,encoding:"utf-8"});return{status:o.status,stdout:o.stdout??"",stderr:o.stderr??""}},pathExists:e=>h(e),cwd:()=>process.cwd(),chdir:e=>process.chdir(e),homedir:()=>_(),mkdirp:e=>f(e,{recursive:!0}),exec:(e,r,t,o)=>new Promise((s,i)=>{const a=l(e,r,{cwd:t,stdio:"inherit",shell:!1,env:o??process.env});a.on("error",i),a.on("exit",n=>s(n??0))}),stderr:e=>process.stderr.write(e),stdout:e=>process.stdout.write(e),prompt:async e=>{const r=d({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}},isTTY:()=>process.stdin.isTTY===!0,getHostname:()=>b(),setTerminalTitle:(e,r)=>{D({label:e,cubeName:r},r)},getActiveCube:()=>v(),setActiveCube:e=>x(e),findProjectRoot:e=>L(e),installProjectSessionHook:e=>{j(e)},getCachedAuth:async()=>{try{return{token:await m(),apiUrl:u}}catch{return null}},runSetup:async()=>(await U(),{token:await m(),apiUrl:u}),listCubes:async(e,r)=>{const{cubes:t}=await T();return t.map(o=>({id:o.id,name:o.name}))},getCube:async(e,r,t)=>{const o=await y(t);return{id:o.id,name:o.name,roles:o.roles,drones:(o.drones??[]).map(s=>({role_id:s.role_id}))}},createCube:async(e,r,t)=>{const o=await k(t.name,"",t.template?{template:t.template}:void 0);return{id:o.id,name:o.name,roles:o.roles}},assimilate:async(e,r,t)=>{const o=await w({cube_id:t.cube_id,role_id:t.role_id,...t.prior_drone_id?{prior_drone_id:t.prior_drone_id}:{},...t.model!=null?{model:t.model}:{}},void 0,t.hostname??null,t.agent_kind??null);return{cube_id:o.cube.id,drone_id:o.drone.id,drone_label:o.drone.label,session_token:o.sessionToken,role_id:o.role.id,reattached:o.reattached===!0}},listTemplates:async(e,r)=>{const{templates:t}=await g();return t.map(o=>({name:o.name,description:o.description}))},getInboxPath:(e,r)=>M(e,r),probeMcpReady:()=>new Promise(e=>{const r=l("borg-mcp",[],{stdio:["pipe","pipe","pipe"],shell:!1,env:C()});let t="",o=!1;const s=n=>{if(!o){o=!0;try{r.kill("SIGTERM")}catch{}e(n)}},i=setTimeout(()=>s(!1),2e3);r.on("error",()=>{clearTimeout(i),s(!1)}),r.on("exit",()=>{clearTimeout(i),s(o)}),r.stdout?.on("data",n=>{t+=n.toString("utf-8");for(const c of t.split(`
2
2
  `))if(c.includes('"protocolVersion"')&&c.includes('"result"')){clearTimeout(i),s(!0);return}});const a=JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"borg-assimilate-probe",version:"0.9.3"}}});try{r.stdin?.write(a+`
3
- `)}catch{clearTimeout(i),s(!1)}}),resolveCli:e=>G(e,D(async r=>{const t=d({input:process.stdin,output:process.stdout});try{return await t.question(r)}finally{t.close()}},()=>process.stdin.isTTY===!0)),prepareCodexRemoteLaunch:()=>H(I()),setCodexWakeTarget:S,findLoadedCodexThread:q,fetch,checkModelReachable:V,getLaunchModel:(e,r,t)=>v(e,r,t),setLaunchModel:(e,r,t)=>R(e,r,t),clearLaunchModel:(e,r)=>A(e,r)}}export{$ as buildDefaultAssimilateDeps};
3
+ `)}catch{clearTimeout(i),s(!1)}}),resolveCli:e=>H(e,G(async r=>{const t=d({input:process.stdin,output:process.stdout});try{return await t.question(r)}finally{t.close()}},()=>process.stdin.isTTY===!0)),prepareCodexRemoteLaunch:()=>I(q()),setCodexWakeTarget:S,findLoadedCodexThread:E,fetch,checkModelReachable:V,getLaunchModel:(e,r,t)=>R(e,r,t),setLaunchModel:(e,r,t)=>P(e,r,t),clearLaunchModel:(e,r)=>A(e,r)}}export{te as buildDefaultAssimilateDeps};
package/dist/claude.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
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}
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 Me,socketPathFromRemoteArgs as M}from"./codex-launch.js";import{codexBorgSessionConfigArgs as Ee}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,isCodexMcpServerConfigured as Ue,isMcpServerConfigured as Ye,isOpenCodeMcpServerConfigured as Ke,removeSessionStartHook as Ve}from"./config-utils.js";import{installBorgPlugin as je}from"./opencode-plugin.js";import{connectOpenCodeDrone as qe,computeOpenCodePort as E,injectInitialKickoff as Xe}from"./opencode-drone.js";async function ze(){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
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
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
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
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
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
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(`
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));Je();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 g,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}`)}`),g="\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."):g="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=M(e.args),m=e.server?.cleanup??null}else s==="codex"&&l.includes("--remote")&&(g="Codex wake-path capability check: using caller-provided --remote socket; if no wake arrives, run borg_regen manually when returning to the session.",u=M(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 f=Oe({cli:s,codexWakeNonce:A,monitorClause:H,codexWakePathClause:g});let h;if(s==="codex")h=[...Ee(),...T,...Pe([...l,f],process.cwd())];else if(s==="opencode"){const e=o?E(o.droneId):14096;je(),h=[process.cwd(),"--port",String(e),"--auto","--prompt",f,...l]}else h=re(l,f);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?E(o.droneId):14096}`;qe({serverUrl:r,directory:process.cwd(),droneLabel:o?.droneLabel??"opencode",cubeName:o?.name??"borg"}).then(()=>Xe(f)).catch(()=>{})}s==="codex"&&o&&u&&(Me({deps:{setCodexWakeTarget:V,findLoadedCodexThread:Re},cubeId:o.cubeId,droneId:o.droneId,socketPath:u,passthroughArgs:l,previewNeedle:A??f.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
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
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(`
13
+ `)}`);process.exit(1)}),P.on("exit",e=>{if(m)try{m()}catch{}process.exit(e??0)})}function Je(){const c=k();if(c.claude)try{Ye()||Fe(),We(I(process.cwd())),Ve(),Ge()}catch(n){console.error(`${t()}${i.yellow(`warning: Claude Code integration check failed: ${n?.message??n}`)}`)}if(c.codex)try{Ue()||He(),Ne(),Be()}catch(n){console.error(`${t()}${i.yellow(`warning: Codex integration check failed: ${n?.message??n}`)}`)}if(c.opencode)try{Ke()||_e()}catch(n){console.error(`${t()}${i.yellow(`warning: OpenCode integration check failed: ${n?.message??n}`)}`)}}ze().catch(c=>{console.error(`${t()}${i.red(`
14
14
  \u25FC Error: ${c.message}
15
15
  `)}`),process.exit(1)});
@@ -125,11 +125,4 @@ export declare function isOpenCodeMcpServerConfigured(configPath?: string): bool
125
125
  * only see pinned env, not parent process env).
126
126
  */
127
127
  export declare function addOpenCodeMcpServer(): void;
128
- /**
129
- * Ensure the opencode MCP server config includes OPENCODE_SERVER_URL env var
130
- * pointing at the local opencode HTTP server. Idempotent: updates in-place.
131
- * Best-effort (failure is non-fatal — the MCP child discovers the server via
132
- * port scan as a fallback).
133
- */
134
- export declare function ensureOpenCodeServerEnvConfig(port: number): void;
135
128
  //# sourceMappingURL=config-utils.d.ts.map
@@ -1,4 +1,3 @@
1
- import{execSync as d}from"child_process";import n from"fs";import f from"os";import i from"path";import{fileURLToPath as v}from"url";import{dirname as R}from"path";const E=v(import.meta.url),U=R(E),a="borg-regen",u="borg-log-audit",j=i.join(f.homedir(),".claude.json"),H=i.join(f.homedir(),".codex","config.toml"),m=i.join(f.homedir(),".codex","hooks.json"),h=i.join(f.homedir(),".config","opencode","opencode.json"),M="borg";function S(){return i.join(f.homedir(),".claude","settings.json")}function p(){const e=S();if(!n.existsSync(e))return{};const o=n.readFileSync(e,"utf-8");return o.trim()?JSON.parse(o):{}}function g(e){const o=S();n.mkdirSync(i.dirname(o),{recursive:!0}),n.writeFileSync(o,JSON.stringify(e,null,2)+`
2
- `,"utf-8")}function k(e){if(!n.existsSync(e))return{};const o=n.readFileSync(e,"utf-8");return o.trim()?JSON.parse(o):{}}function O(e,o){n.mkdirSync(i.dirname(e),{recursive:!0}),n.writeFileSync(e,JSON.stringify(o,null,2)+`
3
- `,"utf-8")}function $(){return P(S())}function D(e){return P(x(e))}function J(e){return w(x(e))}function x(e){return i.join(e,".claude","settings.local.json")}function P(e){let o;try{o=k(e)}catch(t){return console.error(`\u26A0 Could not parse ${e}: ${t.message}. Skipping hook registration; you can add it manually.`),!1}return o.hooks??={},o.hooks.SessionStart??=[],o.hooks.SessionStart.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===a))?!1:(o.hooks.SessionStart.push({matcher:"*",hooks:[{type:"command",command:a}]}),O(e,o),!0)}function w(e){let o;try{o=k(e)}catch{return!1}const r=o?.hooks?.SessionStart;return Array.isArray(r)?r.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===a)):!1}function T(){let e;try{e=p()}catch{return!1}const o=e?.hooks?.SessionStart;return Array.isArray(o)?o.some(r=>Array.isArray(r?.hooks)&&r.hooks.some(t=>t?.type==="command"&&t?.command===a)):!1}function K(){let e;try{e=p()}catch{return!1}const o=e?.hooks?.UserPromptSubmit;return Array.isArray(o)?o.some(r=>Array.isArray(r?.hooks)&&r.hooks.some(t=>t?.type==="command"&&t?.command===u)):!1}function X(){let e;try{e=p()}catch{return!1}if(!e?.hooks?.SessionStart)return!1;let o=!1;return e.hooks.SessionStart=e.hooks.SessionStart.map(r=>{if(!Array.isArray(r?.hooks))return r;const t=r.hooks.filter(s=>!(s?.type==="command"&&s?.command===a));return t.length!==r.hooks.length?(o=!0,{...r,hooks:t}):r}).filter(r=>Array.isArray(r?.hooks)&&r.hooks.length>0),e.hooks.SessionStart.length===0&&delete e.hooks.SessionStart,Object.keys(e.hooks).length===0&&delete e.hooks,o&&g(e),o}function W(){let e;try{e=p()}catch(r){return console.error(`\u26A0 Could not parse ${S()}: ${r.message}. Skipping audit hook registration.`),!1}return e.hooks??={},e.hooks.UserPromptSubmit??=[],e.hooks.UserPromptSubmit.some(r=>Array.isArray(r?.hooks)&&r.hooks.some(t=>t?.type==="command"&&t?.command===u))?!1:(e.hooks.UserPromptSubmit.push({matcher:"*",hooks:[{type:"command",command:u}]}),g(e),!0)}function V(){let e;try{e=p()}catch{return!1}if(!e?.hooks?.UserPromptSubmit)return!1;let o=!1;return e.hooks.UserPromptSubmit=e.hooks.UserPromptSubmit.map(r=>{if(!Array.isArray(r?.hooks))return r;const t=r.hooks.filter(s=>!(s?.type==="command"&&s?.command===u));return t.length!==r.hooks.length?(o=!0,{...r,hooks:t}):r}).filter(r=>Array.isArray(r?.hooks)&&r.hooks.length>0),e.hooks.UserPromptSubmit.length===0&&delete e.hooks.UserPromptSubmit,Object.keys(e.hooks).length===0&&delete e.hooks,o&&g(e),o}function Q(e=j){try{if(!n.existsSync(e))return!1;const o=n.readFileSync(e,"utf-8");if(!o.trim())return!1;const r=JSON.parse(o);if(!r||typeof r!="object")return!1;const t=r.mcpServers;return!t||typeof t!="object"||Array.isArray(t)?!1:M in t}catch{return!1}}function q(e=H){try{if(!n.existsSync(e))return!1;const o=n.readFileSync(e,"utf-8");return/^\s*\[mcp_servers\.borg\]\s*$/m.test(o)&&/^\s*BORG_CODEX_REMOTE_WAKE\s*=\s*"1"\s*$/m.test(o)}catch{return!1}}function z(){return i.join(U,"index.js")}function Y(){try{try{d("claude mcp remove --scope user borg",{stdio:"ignore"})}catch{}d("claude mcp add --scope user borg borg-mcp",{stdio:"inherit",env:{...process.env,BORG_API_URL:process.env.BORG_API_URL||"https://api.borgmcp.ai"}})}catch(e){throw e.message?.includes("command not found")?new Error("Claude CLI not found. Please install Claude Code first."):new Error(`Failed to add MCP server: ${e.message}`)}}function Z(){try{try{d("codex mcp remove borg",{stdio:"ignore"})}catch{}d("codex mcp add borg --env BORG_API_URL="+_(process.env.BORG_API_URL||"https://api.borgmcp.ai")+" --env BORG_CODEX_REMOTE_WAKE=1 -- borg-mcp",{stdio:"inherit",env:{...process.env,BORG_API_URL:process.env.BORG_API_URL||"https://api.borgmcp.ai",BORG_CODEX_REMOTE_WAKE:"1"}})}catch(e){throw e.message?.includes("command not found")?new Error("Codex CLI not found. Please install Codex first."):new Error(`Failed to add MCP server to Codex: ${e.message}`)}}function _(e){return`'${e.replace(/'/g,"'\\''")}'`}function C(e,o,r={}){let t;try{t=k(m)}catch(l){return console.error(`\u26A0 Could not parse ${m}: ${l.message}. Skipping Codex hook registration.`),!1}t.hooks??={},t.hooks[e]??=[];const s=t.hooks[e];if(!Array.isArray(s)||s.some(l=>Array.isArray(l?.hooks)&&l.hooks.some(A=>A?.type==="command"&&A?.command===o)))return!1;const c={hooks:[{type:"command",command:o}]};return r.matcher&&(c.matcher=r.matcher),typeof r.timeout=="number"&&(c.hooks[0].timeout=r.timeout),s.push(c),O(m,t),!0}function ee(){return C("SessionStart",a,{matcher:"startup|resume",timeout:30})}function re(){return C("UserPromptSubmit",u,{timeout:10})}function b(e,o,r=m){try{const s=k(r)?.hooks?.[e];return Array.isArray(s)?s.some(y=>Array.isArray(y?.hooks)&&y.hooks.some(c=>c?.type==="command"&&c?.command===o)):!1}catch{return!1}}function oe(e=m){return b("SessionStart",a,e)}function te(e=m){return b("UserPromptSubmit",u,e)}function se(e=h){try{if(!n.existsSync(e))return!1;const o=n.readFileSync(e,"utf-8");if(!o.trim())return!1;const r=JSON.parse(o);if(!r||typeof r!="object")return!1;const t=r.mcp?.borg;return!t||typeof t!="object"?!1:t.type==="local"}catch{return!1}}function ne(){try{const e=process.env.BORG_API_URL||"https://api.borgmcp.ai";d(`opencode mcp add borg --env BORG_SESSION=1 --env BORG_OPENCODE=1 --env BORG_API_URL=${_(e)} -- borg-mcp`,{stdio:"inherit"})}catch(e){throw e.message?.includes("command not found")?new Error("opencode CLI not found. Please install opencode first."):new Error(`Failed to add MCP server to opencode: ${e.message}`)}}function ie(e){try{if(!n.existsSync(h))return;const o=n.readFileSync(h,"utf-8");if(!o.trim())return;const r=JSON.parse(o);if(!r||typeof r!="object")return;const t=r.mcp?.borg;if(!t||typeof t!="object")return;t.env=t.env||{},t.env.OPENCODE_SERVER_URL=`http://127.0.0.1:${e}`,n.writeFileSync(h,JSON.stringify(r,null,2)+`
4
- `,"utf-8")}catch{}}export{Z as addCodexMcpServer,ee as addCodexSessionStartHook,re as addCodexUserPromptSubmitHook,Y as addMcpServer,ne as addOpenCodeMcpServer,D as addProjectSessionStartHook,$ as addSessionStartHook,W as addUserPromptSubmitHook,ie as ensureOpenCodeServerEnvConfig,z as getBinaryPath,b as isCodexHookRegistered,q as isCodexMcpServerConfigured,oe as isCodexSessionStartHookRegistered,te as isCodexUserPromptSubmitHookRegistered,Q as isMcpServerConfigured,se as isOpenCodeMcpServerConfigured,J as isProjectSessionStartHookRegistered,T as isSessionStartHookRegistered,K as isUserPromptSubmitHookRegistered,X as removeSessionStartHook,V as removeUserPromptSubmitHook};
1
+ import{execSync as d}from"child_process";import n from"fs";import f from"os";import i from"path";import{fileURLToPath as b}from"url";import{dirname as R}from"path";const v=b(import.meta.url),E=R(v),a="borg-regen",m="borg-log-audit",U=i.join(f.homedir(),".claude.json"),H=i.join(f.homedir(),".codex","config.toml"),u=i.join(f.homedir(),".codex","hooks.json"),j=i.join(f.homedir(),".config","opencode","opencode.json"),M="borg";function h(){return i.join(f.homedir(),".claude","settings.json")}function p(){const e=h();if(!n.existsSync(e))return{};const r=n.readFileSync(e,"utf-8");return r.trim()?JSON.parse(r):{}}function y(e){const r=h();n.mkdirSync(i.dirname(r),{recursive:!0}),n.writeFileSync(r,JSON.stringify(e,null,2)+`
2
+ `,"utf-8")}function S(e){if(!n.existsSync(e))return{};const r=n.readFileSync(e,"utf-8");return r.trim()?JSON.parse(r):{}}function A(e,r){n.mkdirSync(i.dirname(e),{recursive:!0}),n.writeFileSync(e,JSON.stringify(r,null,2)+`
3
+ `,"utf-8")}function $(){return O(h())}function D(e){return O(x(e))}function T(e){return w(x(e))}function x(e){return i.join(e,".claude","settings.local.json")}function O(e){let r;try{r=S(e)}catch(t){return console.error(`\u26A0 Could not parse ${e}: ${t.message}. Skipping hook registration; you can add it manually.`),!1}return r.hooks??={},r.hooks.SessionStart??=[],r.hooks.SessionStart.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===a))?!1:(r.hooks.SessionStart.push({matcher:"*",hooks:[{type:"command",command:a}]}),A(e,r),!0)}function w(e){let r;try{r=S(e)}catch{return!1}const o=r?.hooks?.SessionStart;return Array.isArray(o)?o.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===a)):!1}function J(){let e;try{e=p()}catch{return!1}const r=e?.hooks?.SessionStart;return Array.isArray(r)?r.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===a)):!1}function K(){let e;try{e=p()}catch{return!1}const r=e?.hooks?.UserPromptSubmit;return Array.isArray(r)?r.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===m)):!1}function X(){let e;try{e=p()}catch{return!1}if(!e?.hooks?.SessionStart)return!1;let r=!1;return e.hooks.SessionStart=e.hooks.SessionStart.map(o=>{if(!Array.isArray(o?.hooks))return o;const t=o.hooks.filter(s=>!(s?.type==="command"&&s?.command===a));return t.length!==o.hooks.length?(r=!0,{...o,hooks:t}):o}).filter(o=>Array.isArray(o?.hooks)&&o.hooks.length>0),e.hooks.SessionStart.length===0&&delete e.hooks.SessionStart,Object.keys(e.hooks).length===0&&delete e.hooks,r&&y(e),r}function W(){let e;try{e=p()}catch(o){return console.error(`\u26A0 Could not parse ${h()}: ${o.message}. Skipping audit hook registration.`),!1}return e.hooks??={},e.hooks.UserPromptSubmit??=[],e.hooks.UserPromptSubmit.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===m))?!1:(e.hooks.UserPromptSubmit.push({matcher:"*",hooks:[{type:"command",command:m}]}),y(e),!0)}function Q(){let e;try{e=p()}catch{return!1}if(!e?.hooks?.UserPromptSubmit)return!1;let r=!1;return e.hooks.UserPromptSubmit=e.hooks.UserPromptSubmit.map(o=>{if(!Array.isArray(o?.hooks))return o;const t=o.hooks.filter(s=>!(s?.type==="command"&&s?.command===m));return t.length!==o.hooks.length?(r=!0,{...o,hooks:t}):o}).filter(o=>Array.isArray(o?.hooks)&&o.hooks.length>0),e.hooks.UserPromptSubmit.length===0&&delete e.hooks.UserPromptSubmit,Object.keys(e.hooks).length===0&&delete e.hooks,r&&y(e),r}function V(e=U){try{if(!n.existsSync(e))return!1;const r=n.readFileSync(e,"utf-8");if(!r.trim())return!1;const o=JSON.parse(r);if(!o||typeof o!="object")return!1;const t=o.mcpServers;return!t||typeof t!="object"||Array.isArray(t)?!1:M in t}catch{return!1}}function q(e=H){try{if(!n.existsSync(e))return!1;const r=n.readFileSync(e,"utf-8");return/^\s*\[mcp_servers\.borg\]\s*$/m.test(r)&&/^\s*BORG_CODEX_REMOTE_WAKE\s*=\s*"1"\s*$/m.test(r)}catch{return!1}}function z(){return i.join(E,"index.js")}function Y(){try{try{d("claude mcp remove --scope user borg",{stdio:"ignore"})}catch{}d("claude mcp add --scope user borg borg-mcp",{stdio:"inherit",env:{...process.env,BORG_API_URL:process.env.BORG_API_URL||"https://api.borgmcp.ai"}})}catch(e){throw e.message?.includes("command not found")?new Error("Claude CLI not found. Please install Claude Code first."):new Error(`Failed to add MCP server: ${e.message}`)}}function Z(){try{try{d("codex mcp remove borg",{stdio:"ignore"})}catch{}d("codex mcp add borg --env BORG_API_URL="+P(process.env.BORG_API_URL||"https://api.borgmcp.ai")+" --env BORG_CODEX_REMOTE_WAKE=1 -- borg-mcp",{stdio:"inherit",env:{...process.env,BORG_API_URL:process.env.BORG_API_URL||"https://api.borgmcp.ai",BORG_CODEX_REMOTE_WAKE:"1"}})}catch(e){throw e.message?.includes("command not found")?new Error("Codex CLI not found. Please install Codex first."):new Error(`Failed to add MCP server to Codex: ${e.message}`)}}function P(e){return`'${e.replace(/'/g,"'\\''")}'`}function _(e,r,o={}){let t;try{t=S(u)}catch(l){return console.error(`\u26A0 Could not parse ${u}: ${l.message}. Skipping Codex hook registration.`),!1}t.hooks??={},t.hooks[e]??=[];const s=t.hooks[e];if(!Array.isArray(s)||s.some(l=>Array.isArray(l?.hooks)&&l.hooks.some(g=>g?.type==="command"&&g?.command===r)))return!1;const c={hooks:[{type:"command",command:r}]};return o.matcher&&(c.matcher=o.matcher),typeof o.timeout=="number"&&(c.hooks[0].timeout=o.timeout),s.push(c),A(u,t),!0}function ee(){return _("SessionStart",a,{matcher:"startup|resume",timeout:30})}function re(){return _("UserPromptSubmit",m,{timeout:10})}function C(e,r,o=u){try{const s=S(o)?.hooks?.[e];return Array.isArray(s)?s.some(k=>Array.isArray(k?.hooks)&&k.hooks.some(c=>c?.type==="command"&&c?.command===r)):!1}catch{return!1}}function oe(e=u){return C("SessionStart",a,e)}function te(e=u){return C("UserPromptSubmit",m,e)}function se(e=j){try{if(!n.existsSync(e))return!1;const r=n.readFileSync(e,"utf-8");if(!r.trim())return!1;const o=JSON.parse(r);if(!o||typeof o!="object")return!1;const t=o.mcp?.borg;return!t||typeof t!="object"?!1:t.type==="local"}catch{return!1}}function ne(){try{const e=process.env.BORG_API_URL||"https://api.borgmcp.ai";d(`opencode mcp add borg --env BORG_SESSION=1 --env BORG_OPENCODE=1 --env BORG_API_URL=${P(e)} -- borg-mcp`,{stdio:"inherit"})}catch(e){throw e.message?.includes("command not found")?new Error("opencode CLI not found. Please install opencode first."):new Error(`Failed to add MCP server to opencode: ${e.message}`)}}export{Z as addCodexMcpServer,ee as addCodexSessionStartHook,re as addCodexUserPromptSubmitHook,Y as addMcpServer,ne as addOpenCodeMcpServer,D as addProjectSessionStartHook,$ as addSessionStartHook,W as addUserPromptSubmitHook,z as getBinaryPath,C as isCodexHookRegistered,q as isCodexMcpServerConfigured,oe as isCodexSessionStartHookRegistered,te as isCodexUserPromptSubmitHookRegistered,V as isMcpServerConfigured,se as isOpenCodeMcpServerConfigured,T as isProjectSessionStartHookRegistered,J as isSessionStartHookRegistered,K as isUserPromptSubmitHookRegistered,X as removeSessionStartHook,Q as removeUserPromptSubmitHook};
package/dist/index.js CHANGED
@@ -1,38 +1,38 @@
1
1
  #!/usr/bin/env node
2
- import{Server as Q}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Y}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as G,ListToolsRequestSchema as K,ListPromptsRequestSchema as X,GetPromptRequestSchema as Z}from"@modelcontextprotocol/sdk/types.js";import{assertRoleMatches as ee}from"./role-match.js";import{getCubeInfo as te,getRoleInfo as P,getRoleInfoByName as re,getRoster as oe,readLog as ne,appendLog as se,submitReport as ie,fetchReports as ae,ackLogEntry as ce,recordDecision as le,listDecisions as de,regen as N,listCubes as ue,createCube as pe,updateCube as j,deleteCube as me,createRole as be,updateRole as fe,patchRoleSection as k,patchTaxonomyClass as A,deleteRole as ge,reassignDrone as he,evictDrone as _e,getCube as w,checkSubscriptionStatus as ye,createBillingPortalSession as we,createSubscription as xe,syncRoles as $e,applyTemplate as ve,whoami as ke,roleRationale as Ee,getValidToken as Ce}from"./remote-client.js";import{startHealthBeatTick as Re}from"./health-beat.js";import{getTemplate as E,listTemplateNames as C,resolveCubeDirectiveForCreate as Se,resolveCubeDirectiveForApply as Ie,resolveMessageTaxonomyForCreate as Te}from"./templates.js";import{activeCubeWithFreshRegenIdentity as M,getActiveCube as h,setActiveCube as L,inboxPathForDrone as R}from"./cubes.js";import{addSessionStartHook as qe,addUserPromptSubmitHook as De}from"./config-utils.js";import{humanAgo as O,formatLogEntryMarkdown as Ue,formatRegenMarkdown as B,getDronePlaybook as Pe,getDronePlaybookChapter as Ne,nullTaxonomyTip as je,regenWakePathDroneLabel as Ae}from"./regen-format.js";import{startLogStream as Me,getStreamStatus as S}from"./log-stream.js";import{TOOL_MANIFEST as Le}from"./tool-manifest.js";import{DOCS_SECTIONS as Oe,matchDocsSections as Be,formatDocsIndex as Fe}from"./docs-sections.js";import{renderRoleList as He}from"./list-roles-render.js";import{filterToolsForRole as We}from"./tool-scope.js";import{getPackageVersion as x,getOnDiskVersion as Ve,handleVersionFlag as ze}from"./version.js";import{renderStreamStatus as Je,checkInboxMonitorHealthy as I,formatWakePathPrefix as Qe,shouldShowWakePathWarning as Ye}from"./stream-status.js";import{formatRoleAgentLabel as Ge,renderRoster as Ke}from"./roster-render.js";import{resolveDroneIdByLabel as Xe,isUuidShape as Ze}from"./evict-drone.js";import{authRecoveryMessage as et}from"./auth-recovery.js";import{DroneEvictedError as tt,DroneFrozenError as rt,formatEvictedToolResult as ot,formatFrozenToolResult as nt}from"./drone-lifecycle.js";import{classifyInSessionAssimilate as st,reattachOnlyRefusal as it,reattachFailureMessage as at}from"./assimilate-guard.js";import{gateAllowsActivation as ct,borgSessionToolNotice as lt}from"./launch-gate.js";import{renderSyncRolesResult as dt}from"./sync-roles-render.js";import{initConsolePrefix as ut,consolePrefix as $}from"./console-prefix.js";import{isCodexRemoteWakeEnabled as T,resolveSessionAgentKind as q,probeCodexBridgeArmed as pt}from"./codex-app-wake.js";import{connectOpenCodeDrone as mt,injectOpenCodeEntry as bt,probeOpenCodeDroneArmed as ft,computeOpenCodePort as gt}from"./opencode-drone.js";import{installBorgPlugin as ht}from"./opencode-plugin.js";import{setModuleInjectOpenCode as _t}from"./log-stream.js";import{lifecycleSignalForMessage as yt,recordLifecycleLog as F,shouldSuppressLifecycleLog as wt}from"./lifecycle-log-guard.js";import{normalizeDirectLogRecipients as xt}from"./direct-log.js";import H from"open";import $t from"os";function vt(){try{const p=$t.hostname();return p&&p.trim()?p.trim().slice(0,255):null}catch{return null}}async function W(p,y){return await ve(p,y.name)}async function _(){const p=await h();if(!p)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");return p}async function kt(){ze();try{qe()}catch{}try{De()}catch{}try{Me()}catch{}try{ht();const c=await h();if(c&&process.env.BORG_OPENCODE==="1"){const r=`http://127.0.0.1:${gt(c.droneId)}`;await mt({serverUrl:r,directory:process.cwd(),droneLabel:c.droneLabel,cubeName:c.name}),_t(bt)}}catch{}try{Re({getActiveCube:h,getStreamConnected:()=>S().connected,getInboxPath:c=>R(c.cubeId,c.droneId),checkMonitor:I,isCodexRemoteWake:T,probeBridgeArmed:c=>pt({cubeId:c.cubeId,droneId:c.droneId}),probeOpenCodeDrone:()=>ft(),resolveAgentKind:q,resolveHostname:vt,resolveVersion:x,getToken:Ce,fetchImpl:globalThis.fetch.bind(globalThis)})}catch{}const p=new Q({name:"borg-mcp-client",version:x()},{capabilities:{tools:{},prompts:{}}}),y=Le;p.setRequestHandler(K,async()=>{let c=null;try{const m=await h();m&&(c={roleName:m.roleName,roleClass:m.roleClass,isHumanSeat:m.isHumanSeat})}catch{c=null}return{tools:We(y,c)}}),p.setRequestHandler(G,async c=>{let{name:m,arguments:r}=c.params;if(m==="borg_describe-tool"){const e=typeof r?.name=="string"?r.name:"",t=y.find(o=>o.name===e);return t?{content:[{type:"text",text:JSON.stringify({name:t.name,description:t.description,inputSchema:t.inputSchema},null,2)}]}:{content:[{type:"text",text:`Unknown borg tool: ${e||"(none)"}. Pass { name: "<borg_tool>" }.`}],isError:!0}}if(m==="borg_tool"){const e=typeof r?.name=="string"?r.name:"";if(!e||e==="borg_tool"||e==="borg_describe-tool")return{content:[{type:"text",text:'borg_tool: pass { name: "<borg_tool>", arguments: {...} } naming a real borg tool (not the dispatcher itself).'}],isError:!0};r=r?.arguments&&typeof r.arguments=="object"?r.arguments:{},m=e}if(!ct(`tool ${m}`))return{content:[{type:"text",text:lt(m)}],isError:!0};try{switch(m){case"borg_regen":{const e=await h();if(!e)return{content:[{type:"text",text:'Not connected to a cube. Use `borg_assimilate cube_name="<name>"` to join one.'}]};const t=typeof r?.since=="string"?r.since:void 0,o=r?.mode==="lite"?"lite":"full",n=await N(e.sessionToken,e.apiUrl,{since:t}),s=M(e,n);s!==e&&await L(s);const i=S(),a=R(s.cubeId,s.droneId),l=q()==="opencode"||T()?!0:I(a),u=Ye(i,l)?Qe({inboxPath:a,droneLabel:Ae(n,s.droneLabel),cubeName:s.name}):"";let b="";try{const f=x(),d=Ve();if(f!=="unknown"&&d!=="unknown"&&d!==f){const[g,D,z]=f.split(".").map(Number),[v,U,J]=d.split(".").map(Number);(v>g||v===g&&U>D||v===g&&U===D&&J>z)&&(b=`## \u{1F504} borgmcp ${d} installed \u2014 run /mcp and reconnect (or restart Claude Code) to apply. Currently running ${f}.
2
+ import{Server as Y}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as G}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as K,ListToolsRequestSchema as X,ListPromptsRequestSchema as Z,GetPromptRequestSchema as ee}from"@modelcontextprotocol/sdk/types.js";import{assertRoleMatches as te}from"./role-match.js";import{getCubeInfo as oe,getRoleInfo as N,getRoleInfoByName as re,getRoster as ne,readLog as se,appendLog as ie,submitReport as ae,fetchReports as ce,ackLogEntry as le,recordDecision as de,listDecisions as ue,regen as j,listCubes as pe,createCube as me,updateCube as M,deleteCube as be,createRole as fe,updateRole as ge,patchRoleSection as k,patchTaxonomyClass as A,deleteRole as he,reassignDrone as _e,evictDrone as ye,getCube as w,checkSubscriptionStatus as we,createBillingPortalSession as xe,createSubscription as $e,syncRoles as ve,applyTemplate as ke,whoami as Ee,roleRationale as Ce,getValidToken as Re}from"./remote-client.js";import{startHealthBeatTick as Se}from"./health-beat.js";import{getTemplate as E,listTemplateNames as C,resolveCubeDirectiveForCreate as Ie,resolveCubeDirectiveForApply as Te,resolveMessageTaxonomyForCreate as qe}from"./templates.js";import{activeCubeWithFreshRegenIdentity as L,getActiveCube as h,setActiveCube as O,inboxPathForDrone as R}from"./cubes.js";import{addSessionStartHook as De,addUserPromptSubmitHook as Pe}from"./config-utils.js";import{humanAgo as B,formatLogEntryMarkdown as Ue,formatRegenMarkdown as F,getDronePlaybook as Ne,getDronePlaybookChapter as je,nullTaxonomyTip as Me,regenWakePathDroneLabel as Ae}from"./regen-format.js";import{startLogStream as Le,getStreamStatus as S}from"./log-stream.js";import{isMcpReadinessProbe as Oe}from"./readiness-probe.js";import{runMcpStartupServices as Be}from"./startup-services.js";import{TOOL_MANIFEST as Fe}from"./tool-manifest.js";import{DOCS_SECTIONS as He,matchDocsSections as We,formatDocsIndex as Ve}from"./docs-sections.js";import{renderRoleList as ze}from"./list-roles-render.js";import{filterToolsForRole as Je}from"./tool-scope.js";import{getPackageVersion as x,getOnDiskVersion as Qe,handleVersionFlag as Ye}from"./version.js";import{renderStreamStatus as Ge,checkInboxMonitorHealthy as I,formatWakePathPrefix as Ke,shouldShowWakePathWarning as Xe}from"./stream-status.js";import{formatRoleAgentLabel as Ze,renderRoster as et}from"./roster-render.js";import{resolveDroneIdByLabel as tt,isUuidShape as ot}from"./evict-drone.js";import{authRecoveryMessage as rt}from"./auth-recovery.js";import{DroneEvictedError as nt,DroneFrozenError as st,formatEvictedToolResult as it,formatFrozenToolResult as at}from"./drone-lifecycle.js";import{classifyInSessionAssimilate as ct,reattachOnlyRefusal as lt,reattachFailureMessage as dt}from"./assimilate-guard.js";import{gateAllowsActivation as ut,borgSessionToolNotice as pt}from"./launch-gate.js";import{renderSyncRolesResult as mt}from"./sync-roles-render.js";import{initConsolePrefix as bt,consolePrefix as $}from"./console-prefix.js";import{isCodexRemoteWakeEnabled as T,resolveSessionAgentKind as q,probeCodexBridgeArmed as ft}from"./codex-app-wake.js";import{connectOpenCodeDrone as gt,injectOpenCodeEntry as ht,probeOpenCodeDroneArmed as _t,computeOpenCodePort as yt}from"./opencode-drone.js";import{installBorgPlugin as wt}from"./opencode-plugin.js";import{setModuleInjectOpenCode as xt}from"./log-stream.js";import{lifecycleSignalForMessage as $t,recordLifecycleLog as H,shouldSuppressLifecycleLog as vt}from"./lifecycle-log-guard.js";import{normalizeDirectLogRecipients as kt}from"./direct-log.js";import W from"open";import Et from"os";function Ct(){try{const m=Et.hostname();return m&&m.trim()?m.trim().slice(0,255):null}catch{return null}}async function V(m,y){return await ke(m,y.name)}async function _(){const m=await h();if(!m)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");return m}async function Rt(){Ye();const m=Oe();await Be(m,{sessionStartHook:()=>{De()},auditHook:()=>{Pe()},sseStream:()=>{Le()},openCode:async()=>{wt();const d=await h();if(d&&process.env.BORG_OPENCODE==="1"){const o=`http://127.0.0.1:${yt(d.droneId)}`;await gt({serverUrl:o,directory:process.cwd(),droneLabel:d.droneLabel,cubeName:d.name}),xt(ht)}},healthBeat:()=>{Se({getActiveCube:h,getStreamConnected:()=>S().connected,getInboxPath:d=>R(d.cubeId,d.droneId),checkMonitor:I,isCodexRemoteWake:T,probeBridgeArmed:d=>ft({cubeId:d.cubeId,droneId:d.droneId}),probeOpenCodeDrone:()=>_t(),resolveAgentKind:q,resolveHostname:Ct,resolveVersion:x,getToken:Re,fetchImpl:globalThis.fetch.bind(globalThis)})}});const y=new Y({name:"borg-mcp-client",version:x()},{capabilities:{tools:{},prompts:{}}}),D=Fe;y.setRequestHandler(X,async()=>{let d=null;try{const p=await h();p&&(d={roleName:p.roleName,roleClass:p.roleClass,isHumanSeat:p.isHumanSeat})}catch{d=null}return{tools:Je(D,d)}}),y.setRequestHandler(K,async d=>{let{name:p,arguments:o}=d.params;if(p==="borg_describe-tool"){const e=typeof o?.name=="string"?o.name:"",t=D.find(r=>r.name===e);return t?{content:[{type:"text",text:JSON.stringify({name:t.name,description:t.description,inputSchema:t.inputSchema},null,2)}]}:{content:[{type:"text",text:`Unknown borg tool: ${e||"(none)"}. Pass { name: "<borg_tool>" }.`}],isError:!0}}if(p==="borg_tool"){const e=typeof o?.name=="string"?o.name:"";if(!e||e==="borg_tool"||e==="borg_describe-tool")return{content:[{type:"text",text:'borg_tool: pass { name: "<borg_tool>", arguments: {...} } naming a real borg tool (not the dispatcher itself).'}],isError:!0};o=o?.arguments&&typeof o.arguments=="object"?o.arguments:{},p=e}if(!ut(`tool ${p}`))return{content:[{type:"text",text:pt(p)}],isError:!0};try{switch(p){case"borg_regen":{const e=await h();if(!e)return{content:[{type:"text",text:'Not connected to a cube. Use `borg_assimilate cube_name="<name>"` to join one.'}]};const t=typeof o?.since=="string"?o.since:void 0,r=o?.mode==="lite"?"lite":"full",n=await j(e.sessionToken,e.apiUrl,{since:t}),s=L(e,n);s!==e&&await O(s);const i=S(),a=R(s.cubeId,s.droneId),c=q()==="opencode"||T()?!0:I(a),u=Xe(i,c)?Ke({inboxPath:a,droneLabel:Ae(n,s.droneLabel),cubeName:s.name}):"";let b="";try{const f=x(),l=Qe();if(f!=="unknown"&&l!=="unknown"&&l!==f){const[g,P,J]=f.split(".").map(Number),[v,U,Q]=l.split(".").map(Number);(v>g||v===g&&U>P||v===g&&U===P&&Q>J)&&(b=`## \u{1F504} borgmcp ${l} installed \u2014 run /mcp and reconnect (or restart Claude Code) to apply. Currently running ${f}.
3
3
 
4
- `)}}catch{}return{content:[{type:"text",text:b+u+B(n,{mode:o})}]}}case"borg_subscribe":return{content:[{type:"text",text:`Complete your subscription at: ${await xe()}`}]};case"borg_upgrade-subscription":{const e=await we();try{await H(e)}catch{}return{content:[{type:"text",text:`Manage your Borg MCP subscription at: ${e}`}]}}case"borg_subscription_status":{const e=await ye();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}case"borg_open_dashboard":{const e="https://borgmcp.ai/dashboard";return await H(e),{content:[{type:"text",text:`\u25FC Opened dashboard in browser: ${e}`}]}}case"borg_assimilate":{const e=r?.cube_name;if(!e)throw new Error("cube_name is required");const t=await h(),o=st(t,e);if(o.kind!=="reattach")return{content:[{type:"text",text:it(o,e)}],isError:!0};try{const n=await N(t.sessionToken,t.apiUrl,{}),s=M(t,n);return s!==t&&await L(s),{content:[{type:"text",text:[`# Re-attached to cube: ${s.name}`,"",`**Drone label:** ${s.droneLabel}`,"**Seat:** existing identity reused \u2014 no new drone minted (gh#780)","",""].join(`
5
- `)+B(n,{mode:"full"})}]}}catch(n){const s=at(n??{});if(!s)throw n;return{content:[{type:"text",text:s}],isError:!0}}}case"borg_version":return{content:[{type:"text",text:`borgmcp ${x()}`}]};case"borg_playbook":return{content:[{type:"text",text:Ne()}]};case"borg_docs":{const e=typeof r?.topic=="string"?r.topic.trim():"",t=e?Be(e):[],o=t.length>0?t:Oe;return{content:[{type:"text",text:`${e&&t.length>0?`Best-matching docs section(s) for "${e}" \u2014 WebFetch the URL for the full page:`:e?`No exact match for "${e}". Full Borg MCP docs index \u2014 WebFetch the URL you need:`:"Borg MCP docs index \u2014 WebFetch the URL of the section you need:"}
4
+ `)}}catch{}return{content:[{type:"text",text:b+u+F(n,{mode:r})}]}}case"borg_subscribe":return{content:[{type:"text",text:`Complete your subscription at: ${await $e()}`}]};case"borg_upgrade-subscription":{const e=await xe();try{await W(e)}catch{}return{content:[{type:"text",text:`Manage your Borg MCP subscription at: ${e}`}]}}case"borg_subscription_status":{const e=await we();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}case"borg_open_dashboard":{const e="https://borgmcp.ai/dashboard";return await W(e),{content:[{type:"text",text:`\u25FC Opened dashboard in browser: ${e}`}]}}case"borg_assimilate":{const e=o?.cube_name;if(!e)throw new Error("cube_name is required");const t=await h(),r=ct(t,e);if(r.kind!=="reattach")return{content:[{type:"text",text:lt(r,e)}],isError:!0};try{const n=await j(t.sessionToken,t.apiUrl,{}),s=L(t,n);return s!==t&&await O(s),{content:[{type:"text",text:[`# Re-attached to cube: ${s.name}`,"",`**Drone label:** ${s.droneLabel}`,"**Seat:** existing identity reused \u2014 no new drone minted (gh#780)","",""].join(`
5
+ `)+F(n,{mode:"full"})}]}}catch(n){const s=dt(n??{});if(!s)throw n;return{content:[{type:"text",text:s}],isError:!0}}}case"borg_version":return{content:[{type:"text",text:`borgmcp ${x()}`}]};case"borg_playbook":return{content:[{type:"text",text:je()}]};case"borg_docs":{const e=typeof o?.topic=="string"?o.topic.trim():"",t=e?We(e):[],r=t.length>0?t:He;return{content:[{type:"text",text:`${e&&t.length>0?`Best-matching docs section(s) for "${e}" \u2014 WebFetch the URL for the full page:`:e?`No exact match for "${e}". Full Borg MCP docs index \u2014 WebFetch the URL you need:`:"Borg MCP docs index \u2014 WebFetch the URL of the section you need:"}
6
6
 
7
- ${Fe(o)}`}]}}case"borg_whoami":{const e=await _(),t=await ke(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}case"borg_cube":{const e=await _(),[{cube:t,roles:o}]=await Promise.all([te(e.sessionToken,e.apiUrl),P(e.sessionToken,e.apiUrl)]),n=[];n.push(`# Cube: ${t.name}`),n.push(""),n.push("## Cube directive"),n.push(t.cube_directive||"_(none)_"),n.push("");const s=je(t.message_taxonomy);if(s&&(n.push(s),n.push("")),n.push("## Roles in this cube"),!o.length)n.push("_(no roles defined)_");else{for(const i of o){const a=[i.role_class==="queen"?"Queen":null,i.is_human_seat?"human-seat":null,i.is_default?"default":null].filter(Boolean).join(", "),l=a?` (${a})`:"",u=i.short_description||"_(no description)_";n.push(`- **${i.name}**${l} \u2014 ${u}`)}n.push(""),n.push("_(Coordinator-class drones can fetch role IDs via `borg_list-roles` for use with `borg_reassign-drone`.)_")}return n.push(""),n.push(Pe()),{content:[{type:"text",text:n.join(`
8
- `)}]}}case"borg_role":{const e=await _(),t=typeof r?.role=="string"?r.role.trim():"";if(t){const{role:s}=await re(e.sessionToken,e.apiUrl,t);return ee(t,s),{content:[{type:"text",text:[`# Role: ${s.name}`,"",s.detailed_description||"_(no detailed description set)_"].join(`
9
- `)}]}}const{role:o}=await P(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:[`# Your role: ${o.name}`,"",o.detailed_description||"_(no detailed description set)_"].join(`
10
- `)}]}}case"borg_role-rationale":{const e=await _(),t=typeof r?.role=="string"?r.role:"",o=typeof r?.section=="string"?r.section:"",n=await Ee(e.sessionToken,e.apiUrl,t,o);return{content:[{type:"text",text:[`# Role rationale: ${n.role} \u2014 ${n.section}`,"",n.body||"_(empty)_"].join(`
11
- `)}]}}case"borg_roster":{const e=await _(),t=typeof r?.since=="string"?r.since:void 0,{drones:o,roles:n,since:s}=await oe(e.sessionToken,e.apiUrl,t);return{content:[{type:"text",text:Ke({cubeName:e.name,drones:o,roles:n,resolvedSince:s??null,humanAgo:O})}]}}case"borg_stream-status":{const e=S(),t=await h(),o=t?R(t.cubeId,t.droneId):null,n=t&&q()==="opencode",s=t?n||T()?!0:I(o):null;let i="";e.runLoopHealth==="silent-inert"&&(i=`## \u26A0 SSE stream loop silent-inert \u2014 run /mcp and reconnect to restart
7
+ ${Ve(r)}`}]}}case"borg_whoami":{const e=await _(),t=await Ee(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}case"borg_cube":{const e=await _(),[{cube:t,roles:r}]=await Promise.all([oe(e.sessionToken,e.apiUrl),N(e.sessionToken,e.apiUrl)]),n=[];n.push(`# Cube: ${t.name}`),n.push(""),n.push("## Cube directive"),n.push(t.cube_directive||"_(none)_"),n.push("");const s=Me(t.message_taxonomy);if(s&&(n.push(s),n.push("")),n.push("## Roles in this cube"),!r.length)n.push("_(no roles defined)_");else{for(const i of r){const a=[i.role_class==="queen"?"Queen":null,i.is_human_seat?"human-seat":null,i.is_default?"default":null].filter(Boolean).join(", "),c=a?` (${a})`:"",u=i.short_description||"_(no description)_";n.push(`- **${i.name}**${c} \u2014 ${u}`)}n.push(""),n.push("_(Coordinator-class drones can fetch role IDs via `borg_list-roles` for use with `borg_reassign-drone`.)_")}return n.push(""),n.push(Ne()),{content:[{type:"text",text:n.join(`
8
+ `)}]}}case"borg_role":{const e=await _(),t=typeof o?.role=="string"?o.role.trim():"";if(t){const{role:s}=await re(e.sessionToken,e.apiUrl,t);return te(t,s),{content:[{type:"text",text:[`# Role: ${s.name}`,"",s.detailed_description||"_(no detailed description set)_"].join(`
9
+ `)}]}}const{role:r}=await N(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:[`# Your role: ${r.name}`,"",r.detailed_description||"_(no detailed description set)_"].join(`
10
+ `)}]}}case"borg_role-rationale":{const e=await _(),t=typeof o?.role=="string"?o.role:"",r=typeof o?.section=="string"?o.section:"",n=await Ce(e.sessionToken,e.apiUrl,t,r);return{content:[{type:"text",text:[`# Role rationale: ${n.role} \u2014 ${n.section}`,"",n.body||"_(empty)_"].join(`
11
+ `)}]}}case"borg_roster":{const e=await _(),t=typeof o?.since=="string"?o.since:void 0,{drones:r,roles:n,since:s}=await ne(e.sessionToken,e.apiUrl,t);return{content:[{type:"text",text:et({cubeName:e.name,drones:r,roles:n,resolvedSince:s??null,humanAgo:B})}]}}case"borg_stream-status":{const e=S(),t=await h(),r=t?R(t.cubeId,t.droneId):null,n=t&&q()==="opencode",s=t?n||T()?!0:I(r):null;let i="";e.runLoopHealth==="silent-inert"&&(i=`## \u26A0 SSE stream loop silent-inert \u2014 run /mcp and reconnect to restart
12
12
 
13
13
  The log-stream consumer started but never connected. This drone will not receive real-time cube events.
14
14
 
15
- `);const a=Je({status:e,inboxMonitorHealthy:s,inboxPath:o,droneLabel:t?.droneLabel??null,cubeName:t?.name??null,humanAgo:O});return{content:[{type:"text",text:i+a}]}}case"borg_read-log":{const e=await _(),t=typeof r?.since=="string"?r.since:void 0,o=typeof r?.limit=="number"?r.limit:void 0,n=r?.unread_only===!0||r?.unread_only==="true",{entries:s,drones:i,roles:a,behind_by:l,has_more:u}=await ne(e.sessionToken,e.apiUrl,{since:t,limit:o,unreadOnly:n}),b=new Map;for(const g of i)b.set(g.id,g);const f=new Map;for(const g of a)f.set(g.id,g);const d=[];if(d.push(`# Activity log: ${e.name}`),d.push(""),!s.length)d.push("_(no entries)_");else for(const g of s)d.push(Ue(g,b,f));return u===!0?(d.push(""),d.push("\u26A0 has_more: true \u2014 call `borg_read-log unread_only=true` again until has_more=false so you finish draining unread entries.")):typeof l=="number"&&l>0&&(d.push(""),d.push(`\u26A0 behind_by: ${l} more unread ${l===1?"entry":"entries"} addressed to you \u2014 call \`borg_read-log unread_only=true\` again until behind_by=0 so you don't skip messages.`)),{content:[{type:"text",text:d.join(`
16
- `)}]}}case"borg_log":{const e=r?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await h();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");if(yt(e)){const d=await wt(t,e);if(d.suppress)return await F(t,e),{content:[{type:"text",text:`Suppressed duplicate ${d.signal?.toUpperCase()} lifecycle log for ${t.droneLabel}; recent cube log already contains this signal.`}]}}const o=Object.prototype.hasOwnProperty.call(r??{},"to"),n=o?xt(r?.to):void 0,s=typeof r?.class=="string"?r.class:void 0,i=r?.visibility==="broadcast"||r?.visibility==="direct"?r.visibility:void 0,a={...s?{class:s}:{},...o?{to:n??[]}:{},...i?{visibility:i}:{}},l=await se(t.sessionToken,t.apiUrl,e,a);await F(t,e);const u=l.routing?.message?`
17
- ${l.routing.message}`:"",b=l.unreachableRecipients?.length?`
18
- \u26A0 ${l.unreachableRecipients.length} directed recipient(s) currently unreachable (wake-path:deaf): ${l.unreachableRecipients.map(d=>d.label).join(", ")}. Message delivered \u2014 they'll read it when they return.`:"";return{content:[{type:"text",text:`Logged to cube "${t.name}" as ${t.droneLabel}. (entry id: ${l.entry.id})${u}${b}`}]}}case"borg_report-friction":{const e=r?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await h();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");const o=r?.kind==="bug"?"bug":"friction",n=r?.metadata&&typeof r.metadata=="object"&&!Array.isArray(r.metadata)?r.metadata:void 0;return{content:[{type:"text",text:(await ie(t.sessionToken,t.apiUrl,{kind:o,message:e,metadata:n})).ok?"Report submitted \u2014 thank you. The borgmcp team will see it. (Write-only: you cannot read reports back.)":"Report did not submit. Try again, or raise it in the cube log."}]}}case"borg_reports":{const e=await ae();if(e.forbidden)return{content:[{type:"text",text:"Reports triage is builder/dogfooder-tier only. Your account is not on the dogfooder (builder) tier, so the friction-reports store is not readable. (Server-enforced gate.)"}]};if(!e.reports.length)return{content:[{type:"text",text:"No reports yet. Submissions via borg_report-friction will appear here, newest first."}]};const t=e.reports.map(o=>{const n=o.metadata&&Object.keys(o.metadata).length?" \xB7 "+Object.entries(o.metadata).map(([i,a])=>`${i}=${a}`).join(", "):"",s=o.redacted?" \xB7 [secrets-scrubbed]":"";return`**[${o.kind}]** ${o.created_at} \xB7 ${o.reporter_email}${n}${s}
19
- ${o.message}`});return{content:[{type:"text",text:`Reports (${e.reports.length}, newest first):
15
+ `);const a=Ge({status:e,inboxMonitorHealthy:s,inboxPath:r,droneLabel:t?.droneLabel??null,cubeName:t?.name??null,humanAgo:B});return{content:[{type:"text",text:i+a}]}}case"borg_read-log":{const e=await _(),t=typeof o?.since=="string"?o.since:void 0,r=typeof o?.limit=="number"?o.limit:void 0,n=o?.unread_only===!0||o?.unread_only==="true",{entries:s,drones:i,roles:a,behind_by:c,has_more:u}=await se(e.sessionToken,e.apiUrl,{since:t,limit:r,unreadOnly:n}),b=new Map;for(const g of i)b.set(g.id,g);const f=new Map;for(const g of a)f.set(g.id,g);const l=[];if(l.push(`# Activity log: ${e.name}`),l.push(""),!s.length)l.push("_(no entries)_");else for(const g of s)l.push(Ue(g,b,f));return u===!0?(l.push(""),l.push("\u26A0 has_more: true \u2014 call `borg_read-log unread_only=true` again until has_more=false so you finish draining unread entries.")):typeof c=="number"&&c>0&&(l.push(""),l.push(`\u26A0 behind_by: ${c} more unread ${c===1?"entry":"entries"} addressed to you \u2014 call \`borg_read-log unread_only=true\` again until behind_by=0 so you don't skip messages.`)),{content:[{type:"text",text:l.join(`
16
+ `)}]}}case"borg_log":{const e=o?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await h();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");if($t(e)){const l=await vt(t,e);if(l.suppress)return await H(t,e),{content:[{type:"text",text:`Suppressed duplicate ${l.signal?.toUpperCase()} lifecycle log for ${t.droneLabel}; recent cube log already contains this signal.`}]}}const r=Object.prototype.hasOwnProperty.call(o??{},"to"),n=r?kt(o?.to):void 0,s=typeof o?.class=="string"?o.class:void 0,i=o?.visibility==="broadcast"||o?.visibility==="direct"?o.visibility:void 0,a={...s?{class:s}:{},...r?{to:n??[]}:{},...i?{visibility:i}:{}},c=await ie(t.sessionToken,t.apiUrl,e,a);await H(t,e);const u=c.routing?.message?`
17
+ ${c.routing.message}`:"",b=c.unreachableRecipients?.length?`
18
+ \u26A0 ${c.unreachableRecipients.length} directed recipient(s) currently unreachable (wake-path:deaf): ${c.unreachableRecipients.map(l=>l.label).join(", ")}. Message delivered \u2014 they'll read it when they return.`:"";return{content:[{type:"text",text:`Logged to cube "${t.name}" as ${t.droneLabel}. (entry id: ${c.entry.id})${u}${b}`}]}}case"borg_report-friction":{const e=o?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await h();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");const r=o?.kind==="bug"?"bug":"friction",n=o?.metadata&&typeof o.metadata=="object"&&!Array.isArray(o.metadata)?o.metadata:void 0;return{content:[{type:"text",text:(await ae(t.sessionToken,t.apiUrl,{kind:r,message:e,metadata:n})).ok?"Report submitted \u2014 thank you. The borgmcp team will see it. (Write-only: you cannot read reports back.)":"Report did not submit. Try again, or raise it in the cube log."}]}}case"borg_reports":{const e=await ce();if(e.forbidden)return{content:[{type:"text",text:"Reports triage is builder/dogfooder-tier only. Your account is not on the dogfooder (builder) tier, so the friction-reports store is not readable. (Server-enforced gate.)"}]};if(!e.reports.length)return{content:[{type:"text",text:"No reports yet. Submissions via borg_report-friction will appear here, newest first."}]};const t=e.reports.map(r=>{const n=r.metadata&&Object.keys(r.metadata).length?" \xB7 "+Object.entries(r.metadata).map(([i,a])=>`${i}=${a}`).join(", "):"",s=r.redacted?" \xB7 [secrets-scrubbed]":"";return`**[${r.kind}]** ${r.created_at} \xB7 ${r.reporter_email}${n}${s}
19
+ ${r.message}`});return{content:[{type:"text",text:`Reports (${e.reports.length}, newest first):
20
20
 
21
21
  ${t.join(`
22
22
 
23
23
  ---
24
24
 
25
- `)}`}]}}case"borg_ack":{const e=r?.entry_id;if(!e||typeof e!="string")throw new Error("entry_id is required");const t=r?.kind==="claim"?"claim":"ack",o=await _();return await ce(o.sessionToken,o.apiUrl,e,t),{content:[{type:"text",text:t==="claim"?`Claimed entry ${e} in cube "${o.name}" (advisory \u2014 merge stays keyed on REVIEW-APPROVED).`:`Acked entry ${e} in cube "${o.name}".`}]}}case"borg_decide":{const e=r?.topic,t=r?.decision;if(!e||typeof e!="string")throw new Error("topic is required");if(!t||typeof t!="string")throw new Error("decision is required");const o=typeof r?.rationale=="string"?r.rationale:void 0,n=await _(),{decision:s}=await le(n.sessionToken,n.apiUrl,{topic:e,decision:t,...o!==void 0?{rationale:o}:{}}),i=s?.supersedes?" (superseded the prior decision on this topic)":"";return{content:[{type:"text",text:`Recorded ratified decision on "${e}" in cube "${n.name}"${i}. Cite it via borg_decisions; it surfaces in borg_regen.`}]}}case"borg_decisions":{const e=typeof r?.topic=="string"?r.topic:void 0,t=await _(),{decisions:o}=await de(t.sessionToken,t.apiUrl,e);return{content:[{type:"text",text:o.length===0?e?`No active ratified decision on "${e}" in cube "${t.name}".`:`No active ratified decisions in cube "${t.name}".`:o.map(s=>`**${s.topic}:** ${s.decision}${s.rationale?` \u2014 ${s.rationale}`:""}`).join(`
26
- `)}]}}case"borg_list-cubes":{const{cubes:e}=await ue();if(!e.length)return{content:[{type:"text",text:"No cubes yet. Use borg_create-cube to make your first one."}]};const t=e.map(o=>`- **${o.name}** (id: ${o.id})
27
- ${(o.cube_directive||"_(no directive set)_").split(`
25
+ `)}`}]}}case"borg_ack":{const e=o?.entry_id;if(!e||typeof e!="string")throw new Error("entry_id is required");const t=o?.kind==="claim"?"claim":"ack",r=await _();return await le(r.sessionToken,r.apiUrl,e,t),{content:[{type:"text",text:t==="claim"?`Claimed entry ${e} in cube "${r.name}" (advisory \u2014 merge stays keyed on REVIEW-APPROVED).`:`Acked entry ${e} in cube "${r.name}".`}]}}case"borg_decide":{const e=o?.topic,t=o?.decision;if(!e||typeof e!="string")throw new Error("topic is required");if(!t||typeof t!="string")throw new Error("decision is required");const r=typeof o?.rationale=="string"?o.rationale:void 0,n=await _(),{decision:s}=await de(n.sessionToken,n.apiUrl,{topic:e,decision:t,...r!==void 0?{rationale:r}:{}}),i=s?.supersedes?" (superseded the prior decision on this topic)":"";return{content:[{type:"text",text:`Recorded ratified decision on "${e}" in cube "${n.name}"${i}. Cite it via borg_decisions; it surfaces in borg_regen.`}]}}case"borg_decisions":{const e=typeof o?.topic=="string"?o.topic:void 0,t=await _(),{decisions:r}=await ue(t.sessionToken,t.apiUrl,e);return{content:[{type:"text",text:r.length===0?e?`No active ratified decision on "${e}" in cube "${t.name}".`:`No active ratified decisions in cube "${t.name}".`:r.map(s=>`**${s.topic}:** ${s.decision}${s.rationale?` \u2014 ${s.rationale}`:""}`).join(`
26
+ `)}]}}case"borg_list-cubes":{const{cubes:e}=await pe();if(!e.length)return{content:[{type:"text",text:"No cubes yet. Use borg_create-cube to make your first one."}]};const t=e.map(r=>`- **${r.name}** (id: ${r.id})
27
+ ${(r.cube_directive||"_(no directive set)_").split(`
28
28
  `)[0].slice(0,120)}`);return{content:[{type:"text",text:`Your cubes (${e.length}):
29
29
 
30
30
  ${t.join(`
31
31
 
32
- `)}`}]}}case"borg_create-cube":{const e=r?.name,t=r?.cube_directive,o=r?.template;if(!e)throw new Error("name is required");if(t===void 0)throw new Error("cube_directive is required (pass empty string if none)");let n=null;if(o&&(n=E(o),!n))throw new Error(`Unknown template "${o}". Available: ${C().join(", ")}`);const s=Se(t,n),i=Te(void 0,n),a=await pe(e,s,{message_taxonomy:i});if(n){const u=await W(a.id,n),b=s!==t?" Template cube directive applied (operator passed empty).":"";return{content:[{type:"text",text:`Created cube **${a.name}** (id: ${a.id}) with template **${o}** applied \u2014 ${u.created} role(s) created, ${u.updated} updated.${b} Use borg_assimilate ${a.name} to join as a drone.`}]}}return{content:[{type:"text",text:`Created cube **${a.name}** (id: ${a.id}). A default "Drone" role was seeded \u2014 rename or replace it via borg_update-role / borg_create-role / borg_delete-role. Use borg_assimilate ${a.name} to join as a drone.`}]}}case"borg_update-cube":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");const t={};if(typeof r?.name=="string"&&(t.name=r.name),typeof r?.cube_directive=="string"&&(t.cube_directive=r.cube_directive),Array.isArray(r?.message_taxonomy)&&(t.message_taxonomy=r.message_taxonomy),Object.keys(t).length===0)throw new Error("Pass at least one of: name, cube_directive, message_taxonomy.");const{cube:o}=await j(e,t);return{content:[{type:"text",text:`Updated cube **${o.name}** (id: ${o.id}).`}]}}case"borg_patch-taxonomy-class":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");const t=r?.action;if(t!=="add"&&t!=="replace"&&t!=="remove")throw new Error("action must be one of: add, replace, remove.");let o,n;if(t==="remove"){const i=r?.class;if(!i)throw new Error("class is required for remove.");({cube:o}=await A(e,{action:t,class:i})),n=i}else{const i=r?.class_def;if(i==null||typeof i!="object"||Array.isArray(i))throw new Error("class_def (object) is required for add/replace.");({cube:o}=await A(e,{action:t,class_def:i})),n=String(i.class??"")}return{content:[{type:"text",text:`${t==="add"?"Added":t==="replace"?"Replaced":"Removed"} taxonomy class **${n}** in cube **${o.name}** (id: ${o.id}).`}]}}case"borg_delete-cube":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");return await me(e),{content:[{type:"text",text:`Deleted cube ${e} (and all its roles, drones, log entries).`}]}}case"borg_create-role":{const e=r?.cube_id,t=r?.name,o=r?.short_description,n=r?.detailed_description;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("name is required");if(o===void 0)throw new Error("short_description is required (pass empty string if none)");if(n===void 0)throw new Error("detailed_description is required (pass empty string if none)");const s=r?.is_default===!0,i=r?.is_human_seat===!0,a=r?.can_broadcast===!0,l=r?.receives_all_direct===!0,{role:u}=await be(e,{name:t,short_description:o,detailed_description:n,is_default:s,is_human_seat:i,can_broadcast:a,receives_all_direct:l,...typeof r?.default_model=="string"?{default_model:r.default_model}:{}}),b=[u.role_class==="queen"?"Queen":null,u.is_human_seat?"human-seat":null,u.is_default?"default":null].filter(Boolean).join(", "),f=b?` (${b})`:"";return{content:[{type:"text",text:`Created role **${u.name}**${f} (id: ${u.id}) in cube ${e}.`}]}}case"borg_update-role":{const e=r?.role_id;if(!e)throw new Error("role_id is required");const t={};if(typeof r?.name=="string"&&(t.name=r.name),typeof r?.short_description=="string"&&(t.short_description=r.short_description),typeof r?.detailed_description=="string"&&(t.detailed_description=r.detailed_description),typeof r?.is_default=="boolean"&&(t.is_default=r.is_default),typeof r?.is_human_seat=="boolean"&&(t.is_human_seat=r.is_human_seat),typeof r?.can_broadcast=="boolean"&&(t.can_broadcast=r.can_broadcast),typeof r?.receives_all_direct=="boolean"&&(t.receives_all_direct=r.receives_all_direct),typeof r?.default_model=="string"&&(t.default_model=r.default_model),Object.keys(t).length===0)throw new Error("Pass at least one of: name, short_description, detailed_description, is_default, is_human_seat, can_broadcast, receives_all_direct, default_model.");const{role:o}=await fe(e,t),n=[o.role_class==="queen"?"Queen":null,o.is_human_seat?"human-seat":null,o.is_default?"default":null].filter(Boolean).join(", "),s=n?` (${n})`:"";return{content:[{type:"text",text:`Updated role **${o.name}**${s} (id: ${o.id}).`}]}}case"borg_patch-role-section":{const e=r?.role_id;if(!e)throw new Error("role_id is required");const t=r?.action;if(t!=="replace"&&t!=="insert"&&t!=="delete")throw new Error("action must be one of: replace, insert, delete.");const o=r?.heading;if(!o)throw new Error("heading is required");let n;if(t==="delete")({role:n}=await k(e,{action:t,heading:o}));else{const i=r?.body;if(typeof i!="string")throw new Error("body is required for replace/insert (pass empty string for an empty section).");if(t==="insert"){const a=typeof r?.after=="string"?r.after:null;({role:n}=await k(e,{action:t,heading:o,body:i,after:a}))}else({role:n}=await k(e,{action:t,heading:o,body:i}))}return{content:[{type:"text",text:`${t==="replace"?"Replaced":t==="insert"?"Inserted":"Deleted"} section **${o}** in role **${n.name}** (id: ${n.id}).`}]}}case"borg_delete-role":{const e=r?.role_id;if(!e)throw new Error("role_id is required");return await ge(e),{content:[{type:"text",text:`Deleted role ${e}.`}]}}case"borg_reassign-drone":{const e=r?.drone_id,t=r?.role_id;if(!e)throw new Error("drone_id is required");if(!t)throw new Error("role_id is required");const{drone:o}=await he(e,t);return{content:[{type:"text",text:`Reassigned drone ${o.label} (${o.id}) to role ${o.role_id}.`}]}}case"borg_evict-drone":{const e=r?.drone_id?.trim(),t=r?.label?.trim(),o=r?.cube_id?.trim();let n,s;if(e){if(!Ze(e))throw new Error(`drone_id "${e}" is not a UUID \u2014 if that's a drone label, pass it as label + cube_id instead.`);n=e,s=e}else if(t){if(!o)throw new Error("cube_id is required when evicting by label");const{drones:i}=await w(o),a=Xe(i,t);if(!a)throw new Error(`No active drone labelled "${t}" in cube ${o} (it may already be evicted; check borg_list-drones).`);n=a.id,s=a.label}else throw new Error("Provide drone_id, or label + cube_id, to identify the drone to evict");return await _e(n),{content:[{type:"text",text:`Evicted drone ${s} (${n}). Soft-deleted: removed from the roster and freed its seat; log history preserved with anonymized attribution.`}]}}case"borg_list-drones":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");const{drones:t,roles:o}=await w(e);if(!t.length)return{content:[{type:"text",text:"No drones in this cube yet."}]};const n=new Map(o.map(i=>[i.id,i])),s=t.map(i=>{const a=n.get(i.role_id),l=Ge(a?.name??"?",i.agent_kind),u=i.wake_path_alert_class&&i.wake_path_alert_class!=="independent"?` \u2014 wake-path-class: ${i.wake_path_alert_class}`:"";return`- **${i.label}** (id: ${i.id}) \u2014 role: ${l} (${i.role_id}) \u2014 last seen ${i.last_seen}${u}`});return{content:[{type:"text",text:`Drones in cube ${e} (${t.length}):
32
+ `)}`}]}}case"borg_create-cube":{const e=o?.name,t=o?.cube_directive,r=o?.template;if(!e)throw new Error("name is required");if(t===void 0)throw new Error("cube_directive is required (pass empty string if none)");let n=null;if(r&&(n=E(r),!n))throw new Error(`Unknown template "${r}". Available: ${C().join(", ")}`);const s=Ie(t,n),i=qe(void 0,n),a=await me(e,s,{message_taxonomy:i});if(n){const u=await V(a.id,n),b=s!==t?" Template cube directive applied (operator passed empty).":"";return{content:[{type:"text",text:`Created cube **${a.name}** (id: ${a.id}) with template **${r}** applied \u2014 ${u.created} role(s) created, ${u.updated} updated.${b} Use borg_assimilate ${a.name} to join as a drone.`}]}}return{content:[{type:"text",text:`Created cube **${a.name}** (id: ${a.id}). A default "Drone" role was seeded \u2014 rename or replace it via borg_update-role / borg_create-role / borg_delete-role. Use borg_assimilate ${a.name} to join as a drone.`}]}}case"borg_update-cube":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");const t={};if(typeof o?.name=="string"&&(t.name=o.name),typeof o?.cube_directive=="string"&&(t.cube_directive=o.cube_directive),Array.isArray(o?.message_taxonomy)&&(t.message_taxonomy=o.message_taxonomy),Object.keys(t).length===0)throw new Error("Pass at least one of: name, cube_directive, message_taxonomy.");const{cube:r}=await M(e,t);return{content:[{type:"text",text:`Updated cube **${r.name}** (id: ${r.id}).`}]}}case"borg_patch-taxonomy-class":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");const t=o?.action;if(t!=="add"&&t!=="replace"&&t!=="remove")throw new Error("action must be one of: add, replace, remove.");let r,n;if(t==="remove"){const i=o?.class;if(!i)throw new Error("class is required for remove.");({cube:r}=await A(e,{action:t,class:i})),n=i}else{const i=o?.class_def;if(i==null||typeof i!="object"||Array.isArray(i))throw new Error("class_def (object) is required for add/replace.");({cube:r}=await A(e,{action:t,class_def:i})),n=String(i.class??"")}return{content:[{type:"text",text:`${t==="add"?"Added":t==="replace"?"Replaced":"Removed"} taxonomy class **${n}** in cube **${r.name}** (id: ${r.id}).`}]}}case"borg_delete-cube":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");return await be(e),{content:[{type:"text",text:`Deleted cube ${e} (and all its roles, drones, log entries).`}]}}case"borg_create-role":{const e=o?.cube_id,t=o?.name,r=o?.short_description,n=o?.detailed_description;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("name is required");if(r===void 0)throw new Error("short_description is required (pass empty string if none)");if(n===void 0)throw new Error("detailed_description is required (pass empty string if none)");const s=o?.is_default===!0,i=o?.is_human_seat===!0,a=o?.can_broadcast===!0,c=o?.receives_all_direct===!0,{role:u}=await fe(e,{name:t,short_description:r,detailed_description:n,is_default:s,is_human_seat:i,can_broadcast:a,receives_all_direct:c,...typeof o?.default_model=="string"?{default_model:o.default_model}:{}}),b=[u.role_class==="queen"?"Queen":null,u.is_human_seat?"human-seat":null,u.is_default?"default":null].filter(Boolean).join(", "),f=b?` (${b})`:"";return{content:[{type:"text",text:`Created role **${u.name}**${f} (id: ${u.id}) in cube ${e}.`}]}}case"borg_update-role":{const e=o?.role_id;if(!e)throw new Error("role_id is required");const t={};if(typeof o?.name=="string"&&(t.name=o.name),typeof o?.short_description=="string"&&(t.short_description=o.short_description),typeof o?.detailed_description=="string"&&(t.detailed_description=o.detailed_description),typeof o?.is_default=="boolean"&&(t.is_default=o.is_default),typeof o?.is_human_seat=="boolean"&&(t.is_human_seat=o.is_human_seat),typeof o?.can_broadcast=="boolean"&&(t.can_broadcast=o.can_broadcast),typeof o?.receives_all_direct=="boolean"&&(t.receives_all_direct=o.receives_all_direct),typeof o?.default_model=="string"&&(t.default_model=o.default_model),Object.keys(t).length===0)throw new Error("Pass at least one of: name, short_description, detailed_description, is_default, is_human_seat, can_broadcast, receives_all_direct, default_model.");const{role:r}=await ge(e,t),n=[r.role_class==="queen"?"Queen":null,r.is_human_seat?"human-seat":null,r.is_default?"default":null].filter(Boolean).join(", "),s=n?` (${n})`:"";return{content:[{type:"text",text:`Updated role **${r.name}**${s} (id: ${r.id}).`}]}}case"borg_patch-role-section":{const e=o?.role_id;if(!e)throw new Error("role_id is required");const t=o?.action;if(t!=="replace"&&t!=="insert"&&t!=="delete")throw new Error("action must be one of: replace, insert, delete.");const r=o?.heading;if(!r)throw new Error("heading is required");let n;if(t==="delete")({role:n}=await k(e,{action:t,heading:r}));else{const i=o?.body;if(typeof i!="string")throw new Error("body is required for replace/insert (pass empty string for an empty section).");if(t==="insert"){const a=typeof o?.after=="string"?o.after:null;({role:n}=await k(e,{action:t,heading:r,body:i,after:a}))}else({role:n}=await k(e,{action:t,heading:r,body:i}))}return{content:[{type:"text",text:`${t==="replace"?"Replaced":t==="insert"?"Inserted":"Deleted"} section **${r}** in role **${n.name}** (id: ${n.id}).`}]}}case"borg_delete-role":{const e=o?.role_id;if(!e)throw new Error("role_id is required");return await he(e),{content:[{type:"text",text:`Deleted role ${e}.`}]}}case"borg_reassign-drone":{const e=o?.drone_id,t=o?.role_id;if(!e)throw new Error("drone_id is required");if(!t)throw new Error("role_id is required");const{drone:r}=await _e(e,t);return{content:[{type:"text",text:`Reassigned drone ${r.label} (${r.id}) to role ${r.role_id}.`}]}}case"borg_evict-drone":{const e=o?.drone_id?.trim(),t=o?.label?.trim(),r=o?.cube_id?.trim();let n,s;if(e){if(!ot(e))throw new Error(`drone_id "${e}" is not a UUID \u2014 if that's a drone label, pass it as label + cube_id instead.`);n=e,s=e}else if(t){if(!r)throw new Error("cube_id is required when evicting by label");const{drones:i}=await w(r),a=tt(i,t);if(!a)throw new Error(`No active drone labelled "${t}" in cube ${r} (it may already be evicted; check borg_list-drones).`);n=a.id,s=a.label}else throw new Error("Provide drone_id, or label + cube_id, to identify the drone to evict");return await ye(n),{content:[{type:"text",text:`Evicted drone ${s} (${n}). Soft-deleted: removed from the roster and freed its seat; log history preserved with anonymized attribution.`}]}}case"borg_list-drones":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");const{drones:t,roles:r}=await w(e);if(!t.length)return{content:[{type:"text",text:"No drones in this cube yet."}]};const n=new Map(r.map(i=>[i.id,i])),s=t.map(i=>{const a=n.get(i.role_id),c=Ze(a?.name??"?",i.agent_kind),u=i.wake_path_alert_class&&i.wake_path_alert_class!=="independent"?` \u2014 wake-path-class: ${i.wake_path_alert_class}`:"";return`- **${i.label}** (id: ${i.id}) \u2014 role: ${c} (${i.role_id}) \u2014 last seen ${i.last_seen}${u}`});return{content:[{type:"text",text:`Drones in cube ${e} (${t.length}):
33
33
 
34
34
  ${s.join(`
35
- `)}`}]}}case"borg_list-roles":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");const{roles:t}=await w(e);return{content:[{type:"text",text:He(t,e)}]}}case"borg_list-templates":return{content:[{type:"text",text:`Available templates:
35
+ `)}`}]}}case"borg_list-roles":{const e=o?.cube_id;if(!e)throw new Error("cube_id is required");const{roles:t}=await w(e);return{content:[{type:"text",text:ze(t,e)}]}}case"borg_list-templates":return{content:[{type:"text",text:`Available templates:
36
36
 
37
- ${C().map(o=>{const n=E(o);return`- **${o}**: ${n.description}`}).join(`
38
- `)}`}]};case"borg_sync-roles":{const e=r?.cube_id,t=r?.template_name||"software-dev",o=r?.apply===!0,n=r?.decisions&&typeof r.decisions=="object"?r.decisions:void 0;if(!e)throw new Error("cube_id is required");const s=await $e(e,t,o,n);return{content:[{type:"text",text:dt(s,t)}]}}case"borg_apply-template":{const e=r?.cube_id,t=r?.template_name;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("template_name is required");const o=E(t);if(!o)throw new Error(`Unknown template "${t}". Available: ${C().join(", ")}`);const n=await W(e,o);let s="";const i=await w(e),a=Ie(i.cube_directive,o);return a!==null&&(await j(e,{cube_directive:a}),s=" Template cube directive applied (cube directive was empty)."),{content:[{type:"text",text:`Applied template **${t}** to cube ${e} \u2014 ${n.created} role(s) created, ${n.updated} updated.${s}`}]}}default:throw new Error(`Unknown tool: ${m}`)}}catch(e){if(e instanceof tt)return{content:[{type:"text",text:ot(e.message)}],isError:!0};if(e instanceof rt)return{content:[{type:"text",text:nt(e.message)}],isError:!0};const t=et(e??{});return t?{content:[{type:"text",text:t}],isError:!0}:{content:[{type:"text",text:`Error: ${e.message}`}],isError:!0}}}),p.setRequestHandler(X,async()=>({prompts:[{name:"borg_subscribe",description:"Set up Borg MCP Cube tier subscription ($1/month per cube; each cube adds 8 pooled agent sessions + 1000 req/hr). Free tier is permanent (1 cube + 3 agent sessions + 100 req/hr); no trial."},{name:"dashboard",description:"Open Borg MCP dashboard to manage cubes"}]})),p.setRequestHandler(Z,async c=>{const{name:m}=c.params;switch(m){case"borg_subscribe":return{description:"Set up Borg MCP Cube tier subscription ($1/month per cube; each cube adds 8 pooled agent sessions + 1000 req/hr). Free tier is permanent (1 cube + 3 agent sessions + 100 req/hr); no trial.",messages:[{role:"user",content:{type:"text",text:"Please help me set up a Borg MCP subscription using the subscribe tool."}}]};case"dashboard":return{description:"Open Borg MCP dashboard to manage cubes",messages:[{role:"user",content:{type:"text",text:"Please open the Borg MCP dashboard using the borg_open_dashboard tool."}}]};default:throw new Error(`Unknown prompt: ${m}`)}});const V=new Y;await p.connect(V),await ut(),console.error(`${$()}\u25FC Borg MCP Client started`),console.error(`${$()}\u25FC Use borg_assimilate <cube-name> to join a cube as a drone`),console.error(`${$()}\u25FC Manage your cubes at https://borgmcp.ai/dashboard`)}kt().catch(p=>{console.error(`${$()}Fatal error:`,p),process.exit(1)});
37
+ ${C().map(r=>{const n=E(r);return`- **${r}**: ${n.description}`}).join(`
38
+ `)}`}]};case"borg_sync-roles":{const e=o?.cube_id,t=o?.template_name||"software-dev",r=o?.apply===!0,n=o?.decisions&&typeof o.decisions=="object"?o.decisions:void 0;if(!e)throw new Error("cube_id is required");const s=await ve(e,t,r,n);return{content:[{type:"text",text:mt(s,t)}]}}case"borg_apply-template":{const e=o?.cube_id,t=o?.template_name;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("template_name is required");const r=E(t);if(!r)throw new Error(`Unknown template "${t}". Available: ${C().join(", ")}`);const n=await V(e,r);let s="";const i=await w(e),a=Te(i.cube_directive,r);return a!==null&&(await M(e,{cube_directive:a}),s=" Template cube directive applied (cube directive was empty)."),{content:[{type:"text",text:`Applied template **${t}** to cube ${e} \u2014 ${n.created} role(s) created, ${n.updated} updated.${s}`}]}}default:throw new Error(`Unknown tool: ${p}`)}}catch(e){if(e instanceof nt)return{content:[{type:"text",text:it(e.message)}],isError:!0};if(e instanceof st)return{content:[{type:"text",text:at(e.message)}],isError:!0};const t=rt(e??{});return t?{content:[{type:"text",text:t}],isError:!0}:{content:[{type:"text",text:`Error: ${e.message}`}],isError:!0}}}),y.setRequestHandler(Z,async()=>({prompts:[{name:"borg_subscribe",description:"Set up Borg MCP Cube tier subscription ($1/month per cube; each cube adds 8 pooled agent sessions + 1000 req/hr). Free tier is permanent (1 cube + 3 agent sessions + 100 req/hr); no trial."},{name:"dashboard",description:"Open Borg MCP dashboard to manage cubes"}]})),y.setRequestHandler(ee,async d=>{const{name:p}=d.params;switch(p){case"borg_subscribe":return{description:"Set up Borg MCP Cube tier subscription ($1/month per cube; each cube adds 8 pooled agent sessions + 1000 req/hr). Free tier is permanent (1 cube + 3 agent sessions + 100 req/hr); no trial.",messages:[{role:"user",content:{type:"text",text:"Please help me set up a Borg MCP subscription using the subscribe tool."}}]};case"dashboard":return{description:"Open Borg MCP dashboard to manage cubes",messages:[{role:"user",content:{type:"text",text:"Please open the Borg MCP dashboard using the borg_open_dashboard tool."}}]};default:throw new Error(`Unknown prompt: ${p}`)}});const z=new G;await y.connect(z),await bt(),console.error(`${$()}\u25FC Borg MCP Client started`),console.error(`${$()}\u25FC Use borg_assimilate <cube-name> to join a cube as a drone`),console.error(`${$()}\u25FC Manage your cubes at https://borgmcp.ai/dashboard`)}Rt().catch(m=>{console.error(`${$()}Fatal error:`,m),process.exit(1)});
@@ -4,24 +4,18 @@ 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
- */
11
7
  export declare function connectOpenCodeDrone(deps: ConnectDeps): Promise<void>;
12
8
  /**
13
- * Inject the initial kickoff prompt into the TUI's session. Fills the
14
- * TUI input with the kickoff text and submits it, triggering the agent
15
- * to process it as its first task. Polls until the TUI server and a
16
- * session are available. Best-effort — never throws.
9
+ * Wait for the opencode HTTP server to be ready, then find the session
10
+ * created by the `--prompt` CLI arg. The kickoff was already submitted at
11
+ * process startup this just discovers the resulting session.
17
12
  */
18
- export declare function injectInitialKickoff(text: string): Promise<boolean>;
13
+ export declare function injectInitialKickoff(_kickoffText: string): Promise<boolean>;
19
14
  /**
20
- * Inject an SSE entry into the TUI's session as context (noReply).
21
- * Falls through silently on failure the caller falls back to inbox write.
15
+ * Inject a silent context entry (noReply) into our session.
16
+ * Falls through silently — caller falls back to inbox write.
22
17
  */
23
18
  export declare function injectOpenCodeEntry(text: string): Promise<boolean>;
24
- /** Check if the TUI server + session are healthy. */
25
19
  export declare function probeOpenCodeDroneArmed(): Promise<boolean | null>;
26
20
  export declare function disconnectOpenCodeDrone(): void;
27
21
  export declare function getOpenCodeConnectionState(): {
@@ -1,13 +1,2 @@
1
- import{createOpencodeClient as d}from"@opencode-ai/sdk";let e=null;async function f(t){try{e={client:d({baseUrl:t.serverUrl}),serverUrl:t.serverUrl,sessionId:null,directory:t.directory,connected:!0,totalEntriesInjected:0},process.stderr.write(`[borg] opencode drone connected to ${t.serverUrl} for dir ${t.directory}
2
- `)}catch(r){process.stderr.write(`[borg] opencode drone connect failed: ${r}
3
- `),e=null}}async function l(t=15,r=1e3){if(!e?.connected)return!1;for(let n=0;n<t;n++){try{if((await e.client.session.list()).response.status===200)return!0}catch(s){process.stderr.write(`[borg] waitForServer attempt ${n+1}: ${s}
4
- `)}await new Promise(s=>setTimeout(s,r))}return process.stderr.write(`[borg] server not ready after ${t} retries
5
- `),!1}async function c(){if(!e?.connected)return null;try{const r=(await e.client.session.list()).data??[],n=e.directory,s=n?r.filter(i=>i.directory===n):r;if(s.length===0)return null;const o=s.reduce((i,a)=>i.time.created>a.time.created?i:a);return process.stderr.write(`[borg] found session ${o.id.slice(0,8)}\u2026
6
- `),e.sessionId=o.id,o.id}catch{return null}}async function p(t){if(!e?.connected)return process.stderr.write(`[borg] injectInitialKickoff: not connected
7
- `),!1;try{if(!await l())return process.stderr.write(`[borg] injectInitialKickoff: server not ready
8
- `),!1;process.stderr.write(`[borg] appendPrompt text.length=${t.length}\u2026
9
- `),await e.client.tui.appendPrompt({body:{text:t}}),process.stderr.write(`[borg] submitPrompt\u2026
10
- `),await e.client.tui.submitPrompt({query:{directory:e.directory}});const n=await c();return n?(process.stderr.write(`[borg] kickoff injected, session ${n.slice(0,8)}\u2026
11
- `),e.totalEntriesInjected++,!0):(process.stderr.write(`[borg] injectInitialKickoff: session not created after submit
12
- `),!0)}catch(r){return process.stderr.write(`[borg] injectInitialKickoff error: ${r}
13
- `),!1}}async function y(t){if(!e?.connected)return!1;try{const r=e.sessionId??await c();return r?(await e.client.session.prompt({path:{id:r},body:{noReply:!0,parts:[{type:"text",text:t}]}}),e.totalEntriesInjected++,!0):!1}catch{return e.sessionId=null,!1}}async function w(){if(!e?.connected)return null;try{const t=e.sessionId??await c();return t?(await e.client.session.get({path:{id:t}})).response.status===200:!1}catch{return!1}}function b(){e=null}function I(){return{connected:e?.connected??!1,sessionId:e?.sessionId??null,totalEntriesInjected:e?.totalEntriesInjected??0}}function g(t,r=14096){let n=0;for(let s=0;s<t.length;s++){const o=t.charCodeAt(s);n=(n<<5)-n+o,n|=0}return r+Math.abs(n)%1024}export{g as computeOpenCodePort,f as connectOpenCodeDrone,b as disconnectOpenCodeDrone,I as getOpenCodeConnectionState,p as injectInitialKickoff,y as injectOpenCodeEntry,w as probeOpenCodeDroneArmed};
1
+ import{appendFileSync as p}from"fs";import{join as m}from"path";import{tmpdir as w}from"os";const h=m(w(),"borg-opencode-drone.log");function c(e){const t=`[${new Date().toISOString()}] ${e}
2
+ `;try{p(h,t)}catch{}}let r=null;async function O(e){r={serverUrl:e.serverUrl,sessionId:null,directory:e.directory,connected:!0,totalEntriesInjected:0},c(`connected url=${e.serverUrl} dir=${e.directory}`)}function a(e){return`${r.serverUrl.replace(/\/+$/,"")}${e}${e.includes("?")?"&":"?"}directory=${encodeURIComponent(r.directory)}`}const l=1e4;async function u(e){const t=a(e),n=new AbortController,o=setTimeout(()=>n.abort(),l);try{const s=await fetch(t,{signal:n.signal}),i=await s.text();return{status:s.status,body:i}}finally{clearTimeout(o)}}async function I(e,t){const n=a(e),o=new AbortController,s=setTimeout(()=>o.abort(),l);try{const i=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},signal:o.signal,body:JSON.stringify(t)}),y=await i.text();return{status:i.status,body:y}}finally{clearTimeout(s)}}async function f(){const{body:e}=await u("/session");return JSON.parse(e)}async function b(e){try{const{body:t}=await u(`/session/${e}`);return JSON.parse(t)}catch{return null}}async function k(e,t){const{status:n}=await I(`/session/${e}/prompt_async`,t);return n===200||n===204}async function d(){try{const t=(await f()).filter(o=>o.directory===r.directory);return t.length===0?null:t.reduce((o,s)=>o.time.created>s.time.created?o:s).id}catch{return null}}async function S(e){if(!r?.connected)return c("kickoff: not connected"),!1;try{for(let t=0;t<30;t++){try{await f(),c(`kickoff: server ready (attempt ${t+1})`);break}catch{}await new Promise(n=>setTimeout(n,1e3))}for(let t=0;t<30;t++){const n=await d();if(n)return r.sessionId=n,c(`kickoff: found session ${n.slice(0,8)}\u2026`),!0;await new Promise(o=>setTimeout(o,1e3))}return c("kickoff: no session found"),!1}catch(t){return c(`kickoff error: ${t}`),!1}}async function T(e){if(!r?.connected)return!1;try{if(!r.sessionId){const n=await d();if(!n)return!1;r.sessionId=n}const t=await k(r.sessionId,{parts:[{type:"text",text:e}]});return t&&r.totalEntriesInjected++,t}catch(t){return c(`entry error: ${t}`),!1}}async function x(){if(!r?.connected)return null;if(!r.sessionId)return!1;try{return await b(r.sessionId)!==null}catch{return!1}}function j(){r=null}function E(){return{connected:r?.connected??!1,sessionId:r?.sessionId??null,totalEntriesInjected:r?.totalEntriesInjected??0}}function U(e,t=14096){let n=0;for(let o=0;o<e.length;o++){const s=e.charCodeAt(o);n=(n<<5)-n+s,n|=0}return t+Math.abs(n)%1024}export{U as computeOpenCodePort,O as connectOpenCodeDrone,j as disconnectOpenCodeDrone,E as getOpenCodeConnectionState,S as injectInitialKickoff,T as injectOpenCodeEntry,x as probeOpenCodeDroneArmed};
@@ -0,0 +1,5 @@
1
+ /** Environment contract for the short-lived `borg assimilate` MCP readiness child. */
2
+ export declare const MCP_READINESS_PROBE_ENV = "BORG_MCP_READINESS_PROBE";
3
+ export declare function isMcpReadinessProbe(env?: NodeJS.ProcessEnv): boolean;
4
+ export declare function readinessProbeEnv(env?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
5
+ //# sourceMappingURL=readiness-probe.d.ts.map
@@ -0,0 +1 @@
1
+ const n="BORG_MCP_READINESS_PROBE";function r(e=process.env){return e[n]==="1"}function o(e=process.env){return{...e,[n]:"1"}}export{n as MCP_READINESS_PROBE_ENV,r as isMcpReadinessProbe,o as readinessProbeEnv};
@@ -0,0 +1,17 @@
1
+ export type McpStartupTask = () => void | Promise<void>;
2
+ /** Required named services: omission from index wiring is a type error. */
3
+ export interface McpStartupServices {
4
+ sessionStartHook: McpStartupTask;
5
+ auditHook: McpStartupTask;
6
+ sseStream: McpStartupTask;
7
+ openCode: McpStartupTask;
8
+ healthBeat: McpStartupTask;
9
+ }
10
+ /**
11
+ * Run normal long-lived MCP child startup work. The assimilation readiness
12
+ * child must reach the initialize response without acquiring leases, fetching
13
+ * SSE, mutating hooks, or starting timers because it is intentionally killed
14
+ * immediately after that response.
15
+ */
16
+ export declare function runMcpStartupServices(readinessProbe: boolean, services: McpStartupServices): Promise<void>;
17
+ //# sourceMappingURL=startup-services.d.ts.map
@@ -0,0 +1 @@
1
+ async function r(o,t){if(o)return;const a=[t.sessionStartHook,t.auditHook,t.sseStream,t.openCode,t.healthBeat];for(const n of a)try{await n()}catch{}}export{r as runMcpStartupServices};
@@ -1,4 +1,6 @@
1
1
  export declare const STREAM_OWNER_STALE_MS = 70000;
2
+ /** Grace window for mkdir→owner.json initialization before an empty lock is reclaimable. */
3
+ export declare const STREAM_OWNER_INIT_STALE_MS = 5000;
2
4
  export interface StreamOwnerRecord {
3
5
  schemaVersion: number;
4
6
  pid: number;
@@ -8,7 +10,7 @@ export interface StreamOwnerRecord {
8
10
  heartbeatAt: string;
9
11
  }
10
12
  export interface StreamOwnershipSnapshot {
11
- state: 'owner' | 'owned-by-other-process' | 'unowned';
13
+ state: 'owner' | 'owned-by-other-process' | 'initializing' | 'orphaned-initialization' | 'unowned';
12
14
  pid?: number;
13
15
  processNonce?: string;
14
16
  cwd?: string;
@@ -16,6 +18,8 @@ export interface StreamOwnershipSnapshot {
16
18
  heartbeatAt?: string;
17
19
  ageMs?: number;
18
20
  lockPath?: string;
21
+ /** Directory mtime used to compare-before-reap an orphaned initialization. */
22
+ lockMtimeMs?: number;
19
23
  }
20
24
  export interface StreamLease {
21
25
  lockPath: string;
@@ -32,6 +36,8 @@ export interface StreamOwnerDeps {
32
36
  processStartedAt?: string;
33
37
  isPidAlive?: (pid: number) => boolean;
34
38
  beforeTakeoverVerify?: (takeoverPath: string) => Promise<void>;
39
+ /** Initialization/refresh writer seam for failure-path regression tests. */
40
+ writeRecord?: (lockPath: string, record: StreamOwnerRecord) => Promise<void>;
35
41
  }
36
42
  export declare function streamLockPath(cubeId: string, droneId: string, locksDir?: string): string;
37
43
  export declare function acquireStreamLease(cubeId: string, droneId: string, staleMs?: number, deps?: StreamOwnerDeps): Promise<StreamLease | null>;
@@ -1,2 +1,2 @@
1
- import{randomUUID as E}from"node:crypto";import{promises as a}from"node:fs";import{homedir as g}from"node:os";import c from"node:path";const T=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,O=c.join(g(),".config","borgmcp","stream-locks"),w="owner.json",N=1,D=7e4,p=E(),_=new Date().toISOString();function I(e,t,n=O){return y("cubeId",e),y("droneId",t),c.join(n,e,`${t}.lock`)}async function x(e,t,n=D,r={}){const o=I(e,t,r.locksDir);await a.mkdir(c.dirname(o),{recursive:!0,mode:448});const s=await h(o,r);if(s)return s;const i=await M(e,t,r);if(i.state!=="owned-by-other-process")return null;const u=(i.ageMs??0)>n,f=typeof i.pid=="number"&&r.isPidAlive!==void 0&&!r.isPidAlive(i.pid);return!u&&!f||!await R(o,i,n,r)?null:h(o,r)}async function M(e,t,n={}){const r=I(e,t,n.locksDir);let o;try{o=await a.readFile(c.join(r,w),"utf8")}catch(m){if(m?.code==="ENOENT")return{state:"unowned",lockPath:r};throw m}let s;try{s=JSON.parse(o)}catch{return{state:"owned-by-other-process",lockPath:r,ageMs:Number.POSITIVE_INFINITY}}if(!b(s))return{state:"owned-by-other-process",lockPath:r,ageMs:Number.POSITIVE_INFINITY};const i=(n.now??(()=>new Date))(),u=Date.parse(s.heartbeatAt),f=Number.isFinite(u)?i.getTime()-u:Number.POSITIVE_INFINITY,l=n.pid??process.pid,A=n.processNonce??p;return{state:s.pid===l&&s.processNonce===A?"owner":"owned-by-other-process",pid:s.pid,processNonce:s.processNonce,cwd:s.cwd,startedAt:s.startedAt,heartbeatAt:s.heartbeatAt,ageMs:f,lockPath:r}}async function h(e,t){try{await a.mkdir(e,{mode:448})}catch(r){if(r?.code==="EEXIST")return null;throw r}const n=$(t);return await S(e,n),V(e,n,t)}async function R(e,t,n,r){const o=`${e}.takeover-${r.processNonce??p}-${Date.now()}`;try{await a.rename(e,o)}catch(i){if(i?.code==="ENOENT")return!1;throw i}await r.beforeTakeoverVerify?.(o);const s=await d(o);if(!F(t,s,n,r)){try{await a.rename(o,e)}catch(i){if(i?.code!=="EEXIST")throw i;await a.rm(o,{recursive:!0,force:!0})}return!1}return await a.rm(o,{recursive:!0,force:!0}),!0}function F(e,t,n,r){if(!t)return e.ageMs===Number.POSITIVE_INFINITY;if(e.pid!==t.pid||e.processNonce!==t.processNonce||e.heartbeatAt!==t.heartbeatAt)return!1;const o=(r.now??(()=>new Date))(),s=Date.parse(t.heartbeatAt),u=(Number.isFinite(s)?o.getTime()-s:Number.POSITIVE_INFINITY)>n,f=r.isPidAlive!==void 0&&!r.isPidAlive(t.pid);return u||f}function V(e,t,n){return{lockPath:e,record:t,async refresh(){const r=await d(e);if(!r||r.pid!==t.pid||r.processNonce!==t.processNonce)return!1;const o={...t,heartbeatAt:(n.now??(()=>new Date))().toISOString()};return await S(e,o),this.record=o,!0},async release(){const r=await d(e);r?.pid===t.pid&&r.processNonce===t.processNonce&&await a.rm(e,{recursive:!0,force:!0})}}}async function d(e){try{const t=await a.readFile(c.join(e,w),"utf8"),n=JSON.parse(t);return b(n)?n:null}catch{return null}}async function S(e,t){const n=c.join(e,w),r=c.join(e,`${w}.${t.processNonce}.tmp`);await a.writeFile(r,JSON.stringify(t,null,2)+`
2
- `,{mode:384}),await a.rename(r,n)}function $(e){const t=e.now??(()=>new Date);return{schemaVersion:N,pid:e.pid??process.pid,processNonce:e.processNonce??p,cwd:e.cwd??process.cwd(),startedAt:e.processStartedAt??_,heartbeatAt:t().toISOString()}}function b(e){return e!==null&&typeof e=="object"&&e.schemaVersion===N&&typeof e.pid=="number"&&Number.isInteger(e.pid)&&typeof e.processNonce=="string"&&typeof e.cwd=="string"&&typeof e.startedAt=="string"&&typeof e.heartbeatAt=="string"}function y(e,t){if(!T.test(t))throw new Error(`Invalid ${e}: ${t}`)}export{D as STREAM_OWNER_STALE_MS,x as acquireStreamLease,M as readOwnershipSnapshot,I as streamLockPath};
1
+ import{randomUUID as O}from"node:crypto";import{promises as a}from"node:fs";import{homedir as _}from"node:os";import u from"node:path";const D=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,R=u.join(_(),".config","borgmcp","stream-locks"),m="owner.json",S=1,F=7e4,$=5e3,l=O(),L=new Date().toISOString();function y(t,r,n=R){return A("cubeId",t),A("droneId",r),u.join(n,t,`${r}.lock`)}async function J(t,r,n=F,e={}){const i=y(t,r,e.locksDir);await a.mkdir(u.dirname(i),{recursive:!0,mode:448});const o=await d(i,e);if(o)return o;const c=await V(t,r,e);if(c.state==="unowned")return d(i,e);if(c.state==="initializing")return null;if(c.state==="orphaned-initialization")return await E(i,c,n,e)?d(i,e):null;if(c.state!=="owned-by-other-process")return null;const s=(c.ageMs??0)>n,w=typeof c.pid=="number"&&e.isPidAlive!==void 0&&!e.isPidAlive(c.pid);return!s&&!w||!await E(i,c,n,e)?null:d(i,e)}async function V(t,r,n={}){const e=y(t,r,n.locksDir);let i;try{i=await a.readFile(u.join(e,m),"utf8")}catch(h){if(h?.code==="ENOENT")try{const f=await a.stat(e),M=(n.now??(()=>new Date))(),I=Math.max(0,M.getTime()-f.mtimeMs);return{state:I>=$?"orphaned-initialization":"initializing",ageMs:I,lockPath:e,lockMtimeMs:f.mtimeMs}}catch(f){if(f?.code==="ENOENT")return{state:"unowned",lockPath:e};throw f}throw h}let o;try{o=JSON.parse(i)}catch{return{state:"owned-by-other-process",lockPath:e,ageMs:Number.POSITIVE_INFINITY}}if(!T(o))return{state:"owned-by-other-process",lockPath:e,ageMs:Number.POSITIVE_INFINITY};const c=(n.now??(()=>new Date))(),s=Date.parse(o.heartbeatAt),w=Number.isFinite(s)?c.getTime()-s:Number.POSITIVE_INFINITY,N=n.pid??process.pid,g=n.processNonce??l;return{state:o.pid===N&&o.processNonce===g?"owner":"owned-by-other-process",pid:o.pid,processNonce:o.processNonce,cwd:o.cwd,startedAt:o.startedAt,heartbeatAt:o.heartbeatAt,ageMs:w,lockPath:e}}async function d(t,r){try{await a.mkdir(t,{mode:448})}catch(e){if(e?.code==="EEXIST")return null;throw e}const n=x(r);try{await(r.writeRecord??b)(t,n)}catch(e){throw await j(t,n).catch(()=>{}),e}return k(t,n,r)}async function j(t,r){const n=`${t}.failed-${r.processNonce}-${Date.now()}`;try{await a.rename(t,n)}catch(i){if(i?.code==="ENOENT")return;throw i}const e=await p(n);if(e&&(e.pid!==r.pid||e.processNonce!==r.processNonce)){try{await a.rename(n,t)}catch(i){if(i?.code!=="EEXIST")throw i;await a.rm(n,{recursive:!0,force:!0})}return}await a.rm(n,{recursive:!0,force:!0})}async function E(t,r,n,e){const i=`${t}.takeover-${e.processNonce??l}-${Date.now()}`;try{await a.rename(t,i)}catch(s){if(s?.code==="ENOENT")return!1;throw s}await e.beforeTakeoverVerify?.(i);const o=await p(i),c=await a.stat(i).catch(()=>null);if(!v(r,o,c?.mtimeMs,n,e)){try{await a.rename(i,t)}catch(s){if(s?.code!=="EEXIST")throw s;await a.rm(i,{recursive:!0,force:!0})}return!1}return await a.rm(i,{recursive:!0,force:!0}),!0}function v(t,r,n,e,i){if(!r)return t.state==="orphaned-initialization"?t.lockMtimeMs!==void 0&&n===t.lockMtimeMs:t.ageMs===Number.POSITIVE_INFINITY;if(t.pid!==r.pid||t.processNonce!==r.processNonce||t.heartbeatAt!==r.heartbeatAt)return!1;const o=(i.now??(()=>new Date))(),c=Date.parse(r.heartbeatAt),w=(Number.isFinite(c)?o.getTime()-c:Number.POSITIVE_INFINITY)>e,N=i.isPidAlive!==void 0&&!i.isPidAlive(r.pid);return w||N}function k(t,r,n){return{lockPath:t,record:r,async refresh(){const e=await p(t);if(!e||e.pid!==r.pid||e.processNonce!==r.processNonce)return!1;const i={...r,heartbeatAt:(n.now??(()=>new Date))().toISOString()};return await(n.writeRecord??b)(t,i),this.record=i,!0},async release(){const e=await p(t);e?.pid===r.pid&&e.processNonce===r.processNonce&&await a.rm(t,{recursive:!0,force:!0})}}}async function p(t){try{const r=await a.readFile(u.join(t,m),"utf8"),n=JSON.parse(r);return T(n)?n:null}catch{return null}}async function b(t,r){const n=u.join(t,m),e=u.join(t,`${m}.${r.processNonce}.tmp`);await a.writeFile(e,JSON.stringify(r,null,2)+`
2
+ `,{mode:384}),await a.rename(e,n)}function x(t){const r=t.now??(()=>new Date);return{schemaVersion:S,pid:t.pid??process.pid,processNonce:t.processNonce??l,cwd:t.cwd??process.cwd(),startedAt:t.processStartedAt??L,heartbeatAt:r().toISOString()}}function T(t){return t!==null&&typeof t=="object"&&t.schemaVersion===S&&typeof t.pid=="number"&&Number.isInteger(t.pid)&&typeof t.processNonce=="string"&&typeof t.cwd=="string"&&typeof t.startedAt=="string"&&typeof t.heartbeatAt=="string"}function A(t,r){if(!D.test(r))throw new Error(`Invalid ${t}: ${r}`)}export{$ as STREAM_OWNER_INIT_STALE_MS,F as STREAM_OWNER_STALE_MS,J as acquireStreamLease,V as readOwnershipSnapshot,y as streamLockPath};
@@ -1,3 +1,3 @@
1
- import{spawnSync as p}from"node:child_process";import{statSync as m}from"node:fs";import{heartbeatPathFor as d,HEARTBEAT_STALE_MS as f}from"./inbox-monitor.js";function y(n){if(!n)return null;try{const t=p("pgrep",["-f",n],{encoding:"utf-8",timeout:2e3});return t.error?null:t.status===0&&t.stdout.trim().length>0?!b(n):t.status===1?!1:null}catch{return null}}function b(n){try{const t=m(d(n)).mtimeMs;return Date.now()-t>f}catch{return!1}}function _(n){const{status:t,inboxMonitorHealthy:s,inboxPath:r,droneLabel:l,cubeName:u,humanAgo:i}=n,h=t.reconnectAttempts===0&&t.lastWireActivityAt===null&&!t.connected,c=t.ownership?.state==="owned-by-other-process";let o;c?o="**Stream owned by another Borg MCP process.**":h?o="**Stream not started.**":t.connected?s===!1?o="**Stream connected (no inbox-Monitor \u2014 wake path broken).**":t.lastContentEventAt===null?o="**Stream connected, awaiting first content event.**":o=`**Stream connected, last content ${i(new Date(t.lastContentEventAt))}.**`:o=`**Stream disconnected (reconnect attempt ${t.reconnectAttempts}).**`;const e=[];if(e.push(o),e.push(""),e.push("# Log-stream status"),e.push(""),c?e.push("- **state**: _(stream owner is another local process)_"):h?e.push("- **state**: _(stream not started)_"):e.push(`- **connected**: ${t.connected}`),e.push(`- **last content event**: ${t.lastContentEventAt?`${t.lastContentEventAt} (${i(new Date(t.lastContentEventAt))})`:"_(none yet)_"}`),e.push(`- **last heartbeat at**: ${t.lastHeartbeatAt?`${t.lastHeartbeatAt} (${i(new Date(t.lastHeartbeatAt))})`:"_(none)_"}`),e.push(`- **last wire activity**: ${t.lastWireActivityAt?`${t.lastWireActivityAt} (${i(new Date(t.lastWireActivityAt))})`:"_(none)_"}`),e.push(`- **last persisted event id**: ${t.lastPersistedEventId??"_(none)_"}`),e.push(`- **reconnect attempts**: ${t.reconnectAttempts}`),c){const a=t.ownership;e.push(`- **stream owner pid**: ${a.pid??"_(unknown)_"}`),e.push(`- **stream owner cwd**: ${a.cwd??"_(unknown)_"}`),e.push(`- **stream owner heartbeat age**: ${typeof a.ageMs=="number"&&Number.isFinite(a.ageMs)?`${Math.max(0,Math.round(a.ageMs/1e3))}s`:"_(unknown)_"}`)}return t.connected&&s===!1&&(e.push("- **inbox-monitor**: _(no watcher detected \u2014 wake path broken)_"),r&&l&&u&&(e.push(""),e.push("## Real-time wake-up (do this now)"),e.push("No process is tailing this drone's inbox file \u2014 this session won't wake on real-time cube activity, only on its fallback timer, and will miss live coordination from other drones. Arm an inbox Monitor:"),e.push(""),e.push(`> Monitor command: \`tail -n 0 -F ${r}\` \u2014 persistent, 1h timeout, description "borg inbox for ${l} on cube ${u}".`))),e.join(`
2
- `)}function k(n,t){return n.connected&&t===!1}function v(n){const{inboxPath:t,droneLabel:s,cubeName:r}=n;return!t||!s||!r?"":["## \u26A0 Wake path broken \u2014 arm Monitor NOW","","No process is tailing this drone's inbox file. SSE delivery is healthy (entries reach disk), but Claude Code has no event source to wake on. Until you arm a Monitor, this session only wakes on the /loop fallback heartbeat and will miss live coordination from other drones:","",`> Monitor command: \`tail -n 0 -F ${t}\` \u2014 persistent, 1h timeout, description "borg inbox for ${s} on cube ${r}".`,"","---",""].join(`
3
- `)}export{y as checkInboxMonitorHealthy,v as formatWakePathPrefix,b as isHeartbeatStale,_ as renderStreamStatus,k as shouldShowWakePathWarning};
1
+ import{spawnSync as d}from"node:child_process";import{statSync as f}from"node:fs";import{heartbeatPathFor as w,HEARTBEAT_STALE_MS as b}from"./inbox-monitor.js";function A(n){if(!n)return null;try{const t=d("pgrep",["-f",n],{encoding:"utf-8",timeout:2e3});return t.error?null:t.status===0&&t.stdout.trim().length>0?!_(n):t.status===1?!1:null}catch{return null}}function _(n){try{const t=f(w(n)).mtimeMs;return Date.now()-t>b}catch{return!1}}function g(n){const{status:t,inboxMonitorHealthy:s,inboxPath:i,droneLabel:c,cubeName:h,humanAgo:a}=n,u=t.reconnectAttempts===0&&t.lastWireActivityAt===null&&!t.connected,l=t.ownership?.state==="owned-by-other-process",p=t.ownership?.state==="orphaned-initialization",m=t.ownership?.state==="initializing";let o;p?o="**Stream blocked by an orphaned initialization lock.**":l?o="**Stream owned by another Borg MCP process.**":u?o="**Stream not started.**":t.connected?s===!1?o="**Stream connected (no inbox-Monitor \u2014 wake path broken).**":t.lastContentEventAt===null?o="**Stream connected, awaiting first content event.**":o=`**Stream connected, last content ${a(new Date(t.lastContentEventAt))}.**`:o=`**Stream disconnected (reconnect attempt ${t.reconnectAttempts}).**`;const e=[];if(e.push(o),e.push(""),e.push("# Log-stream status"),e.push(""),p?e.push("- **state**: _(orphaned stream-owner initialization)_"):m?e.push("- **state**: _(stream-owner initialization in progress)_"):l?e.push("- **state**: _(stream owner is another local process)_"):u?e.push("- **state**: _(stream not started)_"):e.push(`- **connected**: ${t.connected}`),e.push(`- **last content event**: ${t.lastContentEventAt?`${t.lastContentEventAt} (${a(new Date(t.lastContentEventAt))})`:"_(none yet)_"}`),e.push(`- **last heartbeat at**: ${t.lastHeartbeatAt?`${t.lastHeartbeatAt} (${a(new Date(t.lastHeartbeatAt))})`:"_(none)_"}`),e.push(`- **last wire activity**: ${t.lastWireActivityAt?`${t.lastWireActivityAt} (${a(new Date(t.lastWireActivityAt))})`:"_(none)_"}`),e.push(`- **last persisted event id**: ${t.lastPersistedEventId??"_(none)_"}`),e.push(`- **reconnect attempts**: ${t.reconnectAttempts}`),e.push(`- **run-loop restarts**: ${t.runLoopRestartCount}`),e.push(`- **stream ownership**: ${t.ownership?.state??"_(unknown)_"}`),t.ownership?.lockPath&&e.push(`- **ownership lock path**: ${t.ownership.lockPath}`),l){const r=t.ownership;e.push(`- **stream owner pid**: ${r.pid??"_(unknown)_"}`),e.push(`- **stream owner cwd**: ${r.cwd??"_(unknown)_"}`),e.push(`- **stream owner heartbeat age**: ${typeof r.ageMs=="number"&&Number.isFinite(r.ageMs)?`${Math.max(0,Math.round(r.ageMs/1e3))}s`:"_(unknown)_"}`)}return t.connected&&s===!1&&(e.push("- **inbox-monitor**: _(no watcher detected \u2014 wake path broken)_"),i&&c&&h&&(e.push(""),e.push("## Real-time wake-up (do this now)"),e.push("No process is tailing this drone's inbox file \u2014 this session won't wake on real-time cube activity, only on its fallback timer, and will miss live coordination from other drones. Arm an inbox Monitor:"),e.push(""),e.push(`> Monitor command: \`tail -n 0 -F ${i}\` \u2014 persistent, 1h timeout, description "borg inbox for ${c} on cube ${h}".`))),e.join(`
2
+ `)}function v(n,t){return n.connected&&t===!1}function S(n){const{inboxPath:t,droneLabel:s,cubeName:i}=n;return!t||!s||!i?"":["## \u26A0 Wake path broken \u2014 arm Monitor NOW","","No process is tailing this drone's inbox file. SSE delivery is healthy (entries reach disk), but Claude Code has no event source to wake on. Until you arm a Monitor, this session only wakes on the /loop fallback heartbeat and will miss live coordination from other drones:","",`> Monitor command: \`tail -n 0 -F ${t}\` \u2014 persistent, 1h timeout, description "borg inbox for ${s} on cube ${i}".`,"","---",""].join(`
3
+ `)}export{A as checkInboxMonitorHealthy,S as formatWakePathPrefix,_ as isHeartbeatStale,g as renderStreamStatus,v as shouldShowWakePathWarning};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "borgmcp",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
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",
@@ -53,7 +53,6 @@
53
53
  "dependencies": {
54
54
  "@modelcontextprotocol/sdk": "^1.0.4",
55
55
  "@napi-rs/keyring": "^1.3.0",
56
- "@opencode-ai/sdk": "^1.17.13",
57
56
  "chalk": "^5.3.0",
58
57
  "open": "^10.0.0",
59
58
  "prompts": "^2.4.2",