borgmcp 1.1.8 → 1.1.11

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.
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Agent CLI identity is independent from both the selected model and the
3
+ * Codex remote-wake transport. The child MCP process reports this value on its
4
+ * health beat so a relaunch of an existing seat can repair the server-side
5
+ * agent_kind without re-assimilating.
6
+ */
7
+ export type AgentKind = 'claude' | 'codex' | 'opencode';
8
+ /** Pinned into MCP-child environments by Borg launch paths. */
9
+ export declare const BORG_AGENT_KIND_ENV = "BORG_AGENT_KIND";
10
+ /** Transport capability only — never use it as the primary CLI identity. */
11
+ export declare const BORG_CODEX_REMOTE_WAKE_ENV = "BORG_CODEX_REMOTE_WAKE";
12
+ /** Legacy OpenCode runtime marker, retained for installed-config compatibility. */
13
+ export declare const BORG_OPENCODE_ENV = "BORG_OPENCODE";
14
+ /**
15
+ * Resolve the current MCP child's agent CLI. New Borg launches pin
16
+ * BORG_AGENT_KIND; the older wake-transport markers remain a fallback for
17
+ * already-installed clients.
18
+ */
19
+ export declare function resolveSessionAgentKind(env?: NodeJS.ProcessEnv): AgentKind;
20
+ /**
21
+ * Produce a clean agent-launch environment. Clearing stale transport markers
22
+ * is essential for a Codex → Claude relaunch: an inherited marker must not
23
+ * make the new Claude MCP child report Codex.
24
+ */
25
+ export declare function withAgentRuntimeEnv(env: NodeJS.ProcessEnv, agentKind: AgentKind): NodeJS.ProcessEnv;
26
+ /** Pin the selected Codex CLI identity into Codex's MCP-child env overlay. */
27
+ export declare function codexAgentKindConfigArgs(): string[];
28
+ /**
29
+ * Pin the remote-wake transport capability separately from the CLI identity.
30
+ *
31
+ * Codex MCP children read their configured env rather than the wrapper's
32
+ * inherited process env. Explicitly pinning "0" on a no-socket launch is
33
+ * therefore necessary to override legacy static configs that used to persist
34
+ * BORG_CODEX_REMOTE_WAKE="1" as an identity marker.
35
+ */
36
+ export declare function codexRemoteWakeConfigArgs(enabled?: boolean): string[];
37
+ //# sourceMappingURL=agent-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ const o="BORG_AGENT_KIND",t="BORG_CODEX_REMOTE_WAKE",r="BORG_OPENCODE";function E(e){return e==="claude"||e==="codex"||e==="opencode"}function i(e=process.env){return E(e[o])?e[o]:e[r]==="1"?"opencode":e[t]==="1"?"codex":"claude"}function _(e,c){const n={...e};return delete n[o],delete n[t],delete n[r],n[o]=c,c==="opencode"&&(n[r]="1"),n}function s(){return["-c",`mcp_servers.borg.env.${o}="codex"`]}function d(e=!0){return["-c",`mcp_servers.borg.env.${t}="${e?"1":"0"}"`]}export{o as BORG_AGENT_KIND_ENV,t as BORG_CODEX_REMOTE_WAKE_ENV,r as BORG_OPENCODE_ENV,s as codexAgentKindConfigArgs,d as codexRemoteWakeConfigArgs,i as resolveSessionAgentKind,_ as withAgentRuntimeEnv};
@@ -1,48 +1,49 @@
1
- import{dirname as J,basename as R}from"node:path";import{randomUUID as le}from"node:crypto";import{roleSlug as se,matchRoleByName as ce,pickDefaultRole as ue}from"./role-resolver.js";import{deriveCubeName as me,parseGitRemote as de,sanitizeRemoteUrl as fe}from"./cube-name.js";import{validateName as V}from"./name-validator.js";import{renderAssimilationWelcome as he}from"./assimilate-welcome.js";import{shellEscape as ge}from"./shell-escape.js";import{withCodexCwdArg as we}from"./codex-remote.js";import{buildAgentKickoffPrompt as be,buildKickoffWakePathClause as ke,recordCodexWakeTarget as ye,socketPathFromRemoteArgs as pe}from"./codex-launch.js";import{perWorktreeBranchName as F,adoptWorktree as ve,computeWorktreePath as X,localBranchExists as Q,isMerged as $e}from"./worktree-lifecycle.js";import{DroneEvictedError as Se}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as xe}from"./launch-gate.js";import{inboxPathForDrone as Ce}from"./cubes.js";import{monitorStateRootForWorktree as Re}from"./inbox-monitor.js";import{resolveLaunchEnv as _e,resolveOllamaBaseUrl as Ee,parseModel as Ie}from"./model-presets.js";import{unlinkSync as Ne}from"node:fs";import{gcOrphanInboxesForCube as Ae,defaultListInboxLogs as Pe,defaultInboxLivenessDeps as Le,isInboxLive as Te,ORPHAN_INBOX_STALE_MS as De}from"./gc-orphan-inboxes.js";import{installBorgPlugin as Ue}from"./opencode-plugin.js";import{computeOpenCodePort as Oe,connectOpenCodeDrone as je,injectInitialKickoff as We}from"./opencode-drone.js";async function dt(r,e){if(r.role!==void 0){const t=V(r.role);if(!t.ok)return e.stderr(t.error+`
2
- `),1}if(r.flags.worktree!==void 0){const t=V(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
3
- `),1}let a=await e.getCachedAuth();if(!a){if(!e.isTTY()&&!r.flags.yes)return e.stderr("borg setup required and stdin is non-interactive. Run `borg setup` first in an interactive terminal, then `borg assimilate`.\n"),1;a=await e.runSetup()}const o=e.findProjectRoot(e.cwd());let i;if(r.flags.cubeName)i=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],o),n=t.status===0?t.stdout:null;if(i=me(o,n),n){const c=fe(n),f=c?de(c):null;c&&!f&&i&&e.stderr(`couldn't parse git remote '${c}' \u2014 using directory name '${i}' as cube name
4
- `)}}let l=null;if(i&&i.includes("@")&&i.includes(":")){const t=i.lastIndexOf(":");l={ownerEmail:i.substring(0,t),cubeName:i.substring(t+1)},i=l.cubeName}const _=e.cwd();e.stderr(`Checking your cubes\u2026
5
- `);let 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(`
1
+ import{dirname as V,basename as R}from"node:path";import{randomUUID as se}from"node:crypto";import{roleSlug as ce,matchRoleByName as ue,pickDefaultRole as me}from"./role-resolver.js";import{deriveCubeName as de,parseGitRemote as fe,sanitizeRemoteUrl as ge}from"./cube-name.js";import{validateName as X}from"./name-validator.js";import{renderAssimilationWelcome as he}from"./assimilate-welcome.js";import{shellEscape as we}from"./shell-escape.js";import{withCodexCwdArg as be}from"./codex-remote.js";import{buildAgentKickoffPrompt as ke,buildKickoffWakePathClause as pe,recordCodexWakeTarget as ye,socketPathFromRemoteArgs as ve}from"./codex-launch.js";import{perWorktreeBranchName as F,adoptWorktree as $e,computeWorktreePath as Q,localBranchExists as Z,isMerged as xe}from"./worktree-lifecycle.js";import{DroneEvictedError as Se}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as Ce}from"./launch-gate.js";import{codexAgentKindConfigArgs as Re,codexRemoteWakeConfigArgs as Ee,withAgentRuntimeEnv as _e}from"./agent-runtime.js";import{inboxPathForDrone as Ae}from"./cubes.js";import{monitorStateRootForWorktree as Ie}from"./inbox-monitor.js";import{resolveLaunchEnv as Ne,resolveOllamaBaseUrl as Pe,parseModel as Le}from"./model-presets.js";import{unlinkSync as Te}from"node:fs";import{gcOrphanInboxesForCube as De,defaultListInboxLogs as Oe,defaultInboxLivenessDeps as Ue,isInboxLive as We,ORPHAN_INBOX_STALE_MS as je}from"./gc-orphan-inboxes.js";import{installBorgPlugin as Me}from"./opencode-plugin.js";import{computeOpenCodePort as Be,connectOpenCodeDrone as He,createOpenCodeLaunchKickoff as Fe,injectInitialKickoff as Ke}from"./opencode-drone.js";import{ensureCliMcpConfigured as Ye}from"./ensure-mcp-config.js";async function yt(n,e){if(n.role!==void 0){const t=X(n.role);if(!t.ok)return e.stderr(t.error+`
2
+ `),1}if(n.flags.worktree!==void 0){const t=X(n.flags.worktree);if(!t.ok)return e.stderr(t.error+`
3
+ `),1}let a=await e.getCachedAuth();if(!a){if(!e.isTTY()&&!n.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(n.flags.cubeName)i=n.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],o),r=t.status===0?t.stdout:null;if(i=de(o,r),r){const c=ge(r),f=c?fe(c):null;c&&!f&&i&&e.stderr(`couldn't parse git remote '${c}' \u2014 using directory name '${i}' as cube name
4
+ `)}}let l=null;if(i&&i.includes("@")&&i.includes(":")){const t=i.lastIndexOf(":");l={ownerEmail:i.substring(0,t),cubeName:i.substring(t+1)},i=l.cubeName}const E=e.cwd();e.stderr(`Checking your cubes\u2026
5
+ `);let N;try{N=await e.listCubes(a.apiUrl,a.token)}catch(t){const r=t instanceof Error?t.message:String(t);if(r.includes("Authentication required")||r.includes("Authentication expired"))e.stderr(`Re-authenticating...
6
+ `),a=await e.runSetup(),N=await e.listCubes(a.apiUrl,a.token);else throw t}const P=N.find(t=>t.name===i);if(!P&&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,L;if(P)u=await e.getCube(a.apiUrl,a.token,P.id),L=!1;else{let t;if(n.flags.template)t=n.flags.template;else if(n.flags.noTemplate)t=void 0;else if(e.isTTY())if(n.flags.yes)t="starter";else{const r=await e.listTemplates(a.apiUrl,a.token),c=["First drone joining a new cube. Apply a template?"];r.forEach((v,$)=>{const I=$===0?" (default)":"";c.push(` ${$+1}) ${v.name}${I} \u2014 ${v.description}`)}),c.push(` ${r.length+1}) skip \u2014 no template`);const f=(await e.prompt(c.join(`
8
8
  `)+`
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
- `),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.
9
+ [1]: `)).trim(),p=f===""?1:parseInt(f,10);if(Number.isNaN(p)||p<1||p>r.length+1)return e.stderr(`invalid choice "${f}"
10
+ `),1;t=p<=r.length?r[p-1].name:void 0}else{if(!n.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}),A=!0}let g;if(r.role!==void 0){if(g=ce(u.roles,r.role),!g){const t=u.roles.map(f=>f.name).join(", "),n=Fe(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}),L=!0}let h;if(n.role!==void 0){if(h=ue(u.roles,n.role),!h){const t=u.roles.map(f=>f.name).join(", "),r=Je(n.role,u.roles.map(f=>f.name)),c=r?` Did you mean "${r}"?`:"";return e.stderr(`no role matching "${n.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=ue(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
- `),1}const S=await e.getActiveCube();let x;if(S&&r.flags.here)if(S.cubeId===u.id)x=S.droneId;else return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
18
- `),1;const K=r.flags.worktree!==void 0||S!==null&&!r.flags.here,ee=x??S?.droneId??null,P=K?null:await e.getLaunchModel(u.id,o,ee),b=r.flags.model??P?.model??g.default_model??null,L=Ee(process.env,b!=null&&b===P?.model?P?.ollamaBaseUrl:void 0);if(b){const t=await e.checkModelReachable(b,e.fetch,L);if(!t.ok)return e.stderr(`${t.message}
19
- `),1}const m=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${u.name}' as ${g.name}\u2026
20
- `);let s;try{s=await e.assimilate(a.apiUrl,a.token,{cube_id:u.id,role_id:g.id,hostname:e.getHostname(),agent_kind:m,model:b,...x?{prior_drone_id:x}:{}})}catch(t){if(t instanceof Se&&x!=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
- `),1;const n=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${n}
22
- `),1}const k=u.roles.find(t=>t.id===s.role_id)??g;s.reattached?e.stderr(`re-attached to existing seat ${s.drone_label} (session token rotated, no new drone minted)
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 te=K;let w=null;if(te){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.
16
+ `),1}}else{const t=new Set((u.drones??[]).map(r=>r.role_id));if(h=me(u.roles,{isFirstDrone:L,occupiedRoleIds:t}),!h)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
+ `),1}const x=await e.getActiveCube();let S;if(x&&n.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 K=n.flags.worktree!==void 0||x!==null&&!n.flags.here,te=S??x?.droneId??null,T=K?null:await e.getLaunchModel(u.id,o,te),b=n.flags.model??T?.model??h.default_model??null,D=Pe(process.env,b!=null&&b===T?.model?T?.ollamaBaseUrl:void 0);if(b){const t=await e.checkModelReachable(b,e.fetch,D);if(!t.ok)return e.stderr(`${t.message}
19
+ `),1}const m=await e.resolveCli(n.flags.cli);try{Ye(m)}catch(t){const r=t instanceof Error?t.message:String(t);return e.stderr(`${m} MCP configuration failed: ${r}
20
+ `),1}e.stderr(`Joining cube '${u.name}' as ${h.name}\u2026
21
+ `);let s;try{s=await e.assimilate(a.apiUrl,a.token,{cube_id:u.id,role_id:h.id,hostname:e.getHostname(),agent_kind:m,model:b,...S?{prior_drone_id:S}:{}})}catch(t){if(t instanceof Se&&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.
22
+ `),1;const r=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${r}
23
+ `),1}const k=u.roles.find(t=>t.id===s.role_id)??h;s.reattached?e.stderr(`re-attached to existing seat ${s.drone_label} (session token rotated, no new drone minted)
24
+ `):k.id!==h.id&&e.stderr(`Note: your invite didn't grant the "${h.name}" role \u2014 assimilated as "${k.name}" instead.
25
+ `);const re=K;let w=null;if(re){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
26
  Fix: create at least one commit (\`git commit --allow-empty -m "initial"\`)
26
27
  OR: pass --here to skip the sibling spawn and use the current directory
27
- `),1;e.runSync("git",["fetch","origin"],o);let n="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],o).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],o).status===0&&(n="origin/master");const f=t.stdout.trim(),y=e.runSync("git",["rev-parse",n],o).stdout.trim();f!==y&&e.stderr(`note: local HEAD (${f.slice(0,7)}) differs from ${n} (${y.slice(0,7)}); new worktree will start on ${n}
28
- `);const v=R(o),$=r.flags.worktree??se(k.name);if($.length===0)return e.stderr(`cannot derive a worktree name from role "${k.name}"; pass an explicit --worktree <name>
29
- `),1;const E=e.homedir();let h=X(E,v,$),p=F(R(h),v),q=2;for(;e.pathExists(h)||He(e,o,h)||Q(e.runSync,o,p)&&!$e(e.runSync,o,p,n);)h=X(E,v,$,q),p=F(R(h),v),q++;e.mkdirp(J(h));const z=Q(e.runSync,o,p)?e.runSync("git",["worktree","add",h,p],o):e.runSync("git",["worktree","add","-b",p,h,n],o);if(z.status!==0)return e.stderr(`git worktree add failed: ${Z(z.stderr)}
30
- `),1;e.stderr(`spawned sibling worktree at ${h} on branch ${p} (${n}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
31
- `),e.chdir(h),e.stderr(Me(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}
28
+ `),1;e.runSync("git",["fetch","origin"],o);let r="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],o).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],o).status===0&&(r="origin/master");const f=t.stdout.trim(),p=e.runSync("git",["rev-parse",r],o).stdout.trim();f!==p&&e.stderr(`note: local HEAD (${f.slice(0,7)}) differs from ${r} (${p.slice(0,7)}); new worktree will start on ${r}
29
+ `);const v=R(o),$=n.flags.worktree??ce(k.name);if($.length===0)return e.stderr(`cannot derive a worktree name from role "${k.name}"; pass an explicit --worktree <name>
30
+ `),1;const I=e.homedir();let g=Q(I,v,$),y=F(R(g),v),z=2;for(;e.pathExists(g)||ze(e,o,g)||Z(e.runSync,o,y)&&!xe(e.runSync,o,y,r);)g=Q(I,v,$,z),y=F(R(g),v),z++;e.mkdirp(V(g));const J=Z(e.runSync,o,y)?e.runSync("git",["worktree","add",g,y],o):e.runSync("git",["worktree","add","-b",y,g,r],o);if(J.status!==0)return e.stderr(`git worktree add failed: ${ee(J.stderr)}
31
+ `),1;e.stderr(`spawned sibling worktree at ${g} on branch ${y} (${r}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
32
+ `),e.chdir(g),e.stderr(Ge(g,y,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 r=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${r}
32
33
  `),w){const c=e.runSync("git",["worktree","remove","--force",w],o);c.status===0?e.stderr(`rolled back spawned worktree at ${w}
33
- `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${w}\` (rollback attempt failed: ${Z(c.stderr).trim()||"unknown"})
34
- `)}return 1}const d=e.cwd(),T=e.findProjectRoot(d),D=Re(T);try{const t=Le(),n=J(Ce(s.cube_id,s.drone_id));Ae({cubeInboxDir:n,selfDroneId:s.drone_id,deps:{listInboxLogs:Pe,isLive:c=>Te(c,t,D),droneState:()=>"absent",unlink:c=>Ne(c),now:t.now,staleMs:De},monitorStateRoot:D})}catch{}e.setTerminalTitle(s.drone_label,u.name);const re=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(he(k.name,u.name,re));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=F(R(d),R(o)),n=ve(e.runSync,d,t,"origin/main");n.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
36
- `),e.stderr(Be(d,t))):n.message&&e.stderr(`worktree sync: ${n.message}
34
+ `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${w}\` (rollback attempt failed: ${ee(c.stderr).trim()||"unknown"})
35
+ `)}return 1}const d=e.cwd(),O=e.findProjectRoot(d),U=Ie(O);try{const t=Ue(),r=V(Ae(s.cube_id,s.drone_id));De({cubeInboxDir:r,selfDroneId:s.drone_id,deps:{listInboxLogs:Oe,isLive:c=>We(c,t,U),droneState:()=>"absent",unlink:c=>Te(c),now:t.now,staleMs:je},monitorStateRoot:U})}catch{}e.setTerminalTitle(s.drone_label,u.name);const ne=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(he(k.name,u.name,ne));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
36
+ `)}if(!w){e.runSync("git",["fetch","origin","--prune"],d);const t=F(R(d),R(o)),r=$e(e.runSync,d,t,"origin/main");r.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
37
+ `),e.stderr(qe(d,t))):r.message&&e.stderr(`worktree sync: ${r.message}
37
38
  `)}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 ne=e.getInboxPath(s.cube_id,s.drone_id),U=m==="codex"?`borg-wake-${le()}`:null,oe=ke(m,m==="claude"?ne:null,m==="claude"?D:null);let O,Y=[],C,j=null,W=null;b?await e.setLaunchModel(s.cube_id,T,{model:b,ollamaBaseUrl:Ie(b).kind==="ollama"?L:null}):await e.clearLaunchModel(s.cube_id,T);const G=_e(b,L),M={...process.env,...G.set,BORG_SESSION:"1"};for(const t of G.unset)delete M[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.",Y=t.args,Object.keys(t.env).length>0&&Object.assign(M,t.env),j=pe(t.args),W=t.server?.cleanup??null}const B=be({cli:m,codexWakeNonce:U,monitorClause:oe,codexWakePathClause:O});let H;C=[B],m==="codex"?C=[...xe(),...Y,...we(C,d)]:m==="opencode"&&(H=Oe(s.drone_id),Ue(),C=[d,"--port",String(H),"--auto","--prompt",B]);const ie=e.exec(m,C,d,M);if(m==="codex"&&j&&U&&ye({deps:e,cubeId:s.cube_id,droneId:s.drone_id,socketPath:j,cwd:d,previewNeedle:U,launchedAtSeconds:Math.floor(Date.now()/1e3)}),m==="opencode"){const t=`http://127.0.0.1:${H}`;je({serverUrl:t,directory:d,droneLabel:s.drone_label,cubeName:i??"borg"}).then(()=>We(B)).catch(()=>{})}const ae=await ie;if(W)try{W()}catch{}return w&&_!==w&&e.stderr(`
40
- Agent exited. You were working in ${w}; your shell is back in ${_}.
39
+ `);const oe=e.getInboxPath(s.cube_id,s.drone_id),W=m==="codex"?`borg-wake-${se()}`:null,ie=pe(m,m==="claude"?oe:null,m==="claude"?U:null);let j,Y=[],C,_=null,M=null;b?await e.setLaunchModel(s.cube_id,O,{model:b,ollamaBaseUrl:Le(b).kind==="ollama"?D:null}):await e.clearLaunchModel(s.cube_id,O);const G=Ne(b,D),B={..._e(process.env,m),...G.set,BORG_SESSION:"1"};for(const t of G.unset)delete B[t];if(m==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
40
+ `),j="\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."):j="Codex wake-path capability check passed: remote-control socket established for this session.",Y=t.args,Object.keys(t.env).length>0&&Object.assign(B,t.env),_=ve(t.args),M=t.server?.cleanup??null}const q=ke({cli:m,codexWakeNonce:W,monitorClause:ie,codexWakePathClause:j});let A=null,H;if(C=[q],m==="codex")C=[...Ce(),...Re(),...Ee(_!==null),...Y,...be(C,d)];else if(m==="opencode"){H=Be(s.drone_id),Me();const t=d;A=Fe(q),C=[t,"--port",String(H),"--auto","--prompt",A.prompt]}const ae=e.exec(m,C,d,B);if(m==="codex"&&_&&W&&ye({deps:e,cubeId:s.cube_id,droneId:s.drone_id,socketPath:_,cwd:d,previewNeedle:W,launchedAtSeconds:Math.floor(Date.now()/1e3)}),m==="opencode"&&A){const t=A,r=`http://127.0.0.1:${H}`;He({serverUrl:r,directory:d,droneLabel:s.drone_label,cubeName:i??"borg"}).then(()=>Ke(t)).catch(()=>{})}const le=await ae;if(M)try{M()}catch{}return w&&E!==w&&e.stderr(`
41
+ Agent exited. You were working in ${w}; your shell is back in ${E}.
41
42
  To return:
42
- cd ${ge(w)}
43
- `),ae}function Me(r,e,a){return`
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 Be(r,e){return`
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 Z(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function He(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 Fe(r,e){if(e.length===0)return null;const a=r.toLowerCase();let o=null;for(const i of e){const l=Ke(a,i.toLowerCase());l<=2&&(o===null||l<o.distance)&&(o={name:i,distance:l})}return o?o.name:null}function Ke(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{dt as runAssimilate,Z as safeStderr,Fe as suggestRoleName};
43
+ cd ${we(w)}
44
+ `),le}function Ge(n,e,a){return`
45
+ WORKTREE STEERING: You are in worktree ${n} 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).
46
+ `}function qe(n,e){return`
47
+ WORKTREE STEERING: This checkout is now on branch ${e}. Do ALL work HERE in ${n} \u2014 cut your feature branch (fix/.../feat/...) off ${e}, use relative paths / your cwd.
48
+ `}function ee(n){return n.replace(/[\x00-\x1F\x7F]/g,"")}function ze(n,e,a){const o=n.runSync("git",["worktree","list","--porcelain"],e);return o.status!==0?!1:o.stdout.split(`
49
+ `).some(i=>i===`worktree ${a}`)}function Je(n,e){if(e.length===0)return null;const a=n.toLowerCase();let o=null;for(const i of e){const l=Ve(a,i.toLowerCase());l<=2&&(o===null||l<o.distance)&&(o={name:i,distance:l})}return o?o.name:null}function Ve(n,e){if(n===e)return 0;if(n.length===0)return e.length;if(e.length===0)return n.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<=n.length;i++){o[0]=i;for(let l=1;l<=e.length;l++){const E=n[i-1]===e[l-1]?0:1;o[l]=Math.min(o[l-1]+1,a[l]+1,a[l-1]+E)}for(let l=0;l<=e.length;l++)a[l]=o[l]}return a[e.length]}export{yt as runAssimilate,ee as safeStderr,Je as suggestRoleName};
package/dist/claude.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as B}from"child_process";import{randomUUID as W}from"node:crypto";import{basename as _}from"node:path";import{createInterface as G}from"node:readline/promises";import i from"chalk";import{findProjectRoot as b,getActiveCube as U,getLaunchModel as Y,inboxPathForDrone as K,setCodexWakeTarget as V,pruneDeadCodexWakeTargets as j}from"./cubes.js";import{monitorStateRootForWorktree as q}from"./inbox-monitor.js";import{applyOllamaLaunchEnv as X,checkModelReachable as z}from"./model-presets.js";import{handleVersionFlag as J,getPackageVersion as w}from"./version.js";import{isHelpFlag as L,setupHelpText as Q,topLevelHelpText as Z,assimilateHelpText as ee}from"./cli-help.js";import{runSpawn as re}from"./spawn.js";import{buildClaudeLaunchArgs as oe}from"./claude-launch-args.js";import{parseSyncArgs as se,runSync as te}from"./sync.js";import{parseCleanupArgs as ie,runCleanup as ae}from"./cleanup-cmd.js";import{parseAssimilateArgs as ne}from"./parse-assimilate-args.js";import{runAssimilate as ce}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as le}from"./assimilate-deps.js";import{parseLaunchAllArgs as R}from"./parse-launch-all-args.js";import{unknownSubcommand as de}from"./unknown-subcommand.js";import{runLaunchAll as O}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as k}from"./launch-all-deps.js";import{discoverDroneCandidates as pe}from"./launch-all-discovery.js";import{explicitCliLaunchHint as ue,runBareLaunchMenu as me,shouldResolveExplicitCliLaunchHintTargets as fe,shouldShowLaunchMenu as ge}from"./bare-launch-menu.js";import{setTerminalTitle as he}from"./terminal-title.js";import{initConsolePrefix as we,consolePrefix as t}from"./console-prefix.js";import{initDebugFromArgv as Ce}from"./debug.js";import{fetchLatestBorgmcpVersion as xe,compareVersionsForStaleness as ve}from"./stale-version-check.js";import{defaultCliChoiceDeps as be,detectCliAvailability as $,installedCliNames as D,parseCliFlag as ke,resolveCliChoice as $e}from"./cli-platform.js";import{getRefreshToken as Se,getIdToken as ye}from"./config.js";import{composeGetStarted as Ae,shouldShowGetStarted as Te}from"./get-started.js";import{prepareCodexRemoteLaunch as Pe,withCodexCwdArg as Ie,defaultCodexRemoteDeps as Le,checkCodexBridgeHealthy as Re}from"./codex-remote.js";import{findLoadedCodexThread as Oe}from"./codex-app-server.js";import{buildAgentKickoffPrompt as De,buildKickoffWakePathClause as Me,recordCodexWakeTarget as Ee,socketPathFromRemoteArgs as M}from"./codex-launch.js";import{codexBorgSessionConfigArgs as He}from"./launch-gate.js";import{addCodexMcpServer as Ne,addCodexSessionStartHook as Fe,addCodexUserPromptSubmitHook as Be,addMcpServer as We,addOpenCodeMcpServer as _e,addProjectSessionStartHook as Ge,addUserPromptSubmitHook 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 E,injectInitialKickoff as ze}from"./opencode-drone.js";async function Je(){Ce(process.argv),J(),await we();const c=(async()=>{if(!process.stderr.isTTY)return;const e=w(),r=await xe();if(!r)return;const a=ve(e,r);a.stale&&a.message&&process.stderr.write(`${t()}${a.message}
3
- `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(Z(w())),process.exit(0)),process.argv[2]==="setup"){L(process.argv[3])&&(process.stdout.write(Q(w())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(L)&&(process.stdout.write(ee(w())),process.exit(0));const e=ne(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg assimilate: ${e.error}
4
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=le(),a=await ce({role:e.role,flags:e.flags},r);process.exit(a)}if(process.argv[2]==="spawn"){const e=await re();process.exit(e)}if(process.argv[2]==="sync"){const e=se(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg sync: ${e.error}
5
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await te({},e.options);process.exit(r)}if(process.argv[2]==="cleanup"){const e=ie(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg cleanup: ${e.error}
6
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await ae({},e.options);process.exit(r)}if(process.argv[2]==="launch-all"){const e=R(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg launch-all: ${e.error}
7
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=k(),a=await O(e.args,r);process.exit(a)}const n=de(process.argv[2]);if(n!==null&&(process.stderr.write(i.red(`${t()}\u25FC unknown command: ${n}
8
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1)),Te(await Se()!==null,await ye()!==null)){const e=D($()).length>0;process.stdout.write(Ae(e)),process.exit(0)}const d=ke(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 S=async e=>{const r=G({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let s=await $e(d.cli,be(S,()=>process.stdin.isTTY===!0));Qe();const o=await U();let C;const y=async()=>o?(C===void 0&&(C=(await pe({targetCubeId:o.cubeId},k())).length>0),C):!1,x=process.stdin.isTTY===!0,v=process.stdout.isTTY===!0,A=ue({explicitCli:d.cli,stdinIsTTY:x,stdoutIsTTY:v,hasActiveCube:o!==null,hasLaunchAllTargets:fe({explicitCli:d.cli,stdinIsTTY:x,stdoutIsTTY:v,hasActiveCube:o!==null})?await y():!1});if(A&&process.stderr.write(A),ge({extraArgs:process.argv.slice(2),stdinIsTTY:x,stdoutIsTTY:v})){const e=D($()).filter(a=>a!==s),r=await me({defaultCli:s,otherInstalledClis:e,hasLaunchAllTargets:await y()},S);if(r.kind==="launch-all"){const a=R([]),F=a.ok?await O(a.args,k()):1;process.exit(F)}s=r.cli}const l=d.rest;he(o?{label:o.droneLabel,cubeName:o.name}:null,_(process.cwd()));const H=Me(s,o&&s==="claude"?K(o.cubeId,o.droneId):null,o&&s==="claude"?q(b(process.cwd())):null);await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const T=s==="codex"?`borg-wake-${W()}`:null;let g,P=[],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 Pe(Le());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.",P=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,b(),o.droneId),r=X(p,e,process.env);if(p=r.env,r.probe){const a=await z(r.probe.descriptor,fetch,r.probe.baseUrl);a.ok||console.error(`${t()}${i.yellow(`warning: ${a.message}`)}`)}}const f=De({cli:s,codexWakeNonce:T,monitorClause:H,codexWakePathClause:g});let h;if(s==="codex")h=[...He(),...P,...Ie([...l,f],process.cwd())];else if(s==="opencode"){const e=o?E(o.droneId):14096;qe(),h=[process.cwd(),"--port",String(e),"--auto","--prompt",f,...l]}else h=oe(l,f);const N=s==="claude"?"Claude Code":s==="codex"?"Codex":"OpenCode";console.error(`${t()}${i.blue(`\u25FC Launching ${N}\u2026`)}`);const I=B(s,h,{stdio:"inherit",shell:!1,env:p});if(s==="opencode"){const r=`http://127.0.0.1:${o?E(o.droneId):14096}`;Xe({serverUrl:r,directory:process.cwd(),droneLabel:o?.droneLabel??"opencode",cubeName:o?.name??"borg"}).then(()=>ze(f)).catch(()=>{})}s==="codex"&&o&&u&&(Ee({deps:{setCodexWakeTarget:V,findLoadedCodexThread:Oe},cubeId:o.cubeId,droneId:o.droneId,socketPath:u,passthroughArgs:l,previewNeedle:T??f.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),j(e=>Re(e))),I.on("error",e=>{if(m)try{m()}catch{}if(e.code==="ENOENT"){console.error(`${t()}${i.red(`
10
- \u25FC Failed to launch ${s}`)}`);const r=s==="opencode"?"opencode":s;console.error(`${t()}${i.gray(`Make sure ${r} is installed.
11
- `)}`)}else console.error(`${t()}${i.red(`
12
- \u25FC Failed to launch ${s}: ${e.message}
13
- `)}`);process.exit(1)}),I.on("exit",e=>{if(m)try{m()}catch{}process.exit(e??0)})}function Qe(){const c=$();if(c.claude)try{Ke()||We(),Ge(b(process.cwd())),je(),Ue()}catch(n){console.error(`${t()}${i.yellow(`warning: Claude Code integration check failed: ${n?.message??n}`)}`)}if(c.codex)try{Ye()||Ne(),Fe(),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(`
2
+ import{spawn as K}from"child_process";import{randomUUID as G}from"node:crypto";import{basename as U}from"node:path";import{createInterface as Y}from"node:readline/promises";import n from"chalk";import{findProjectRoot as v,getActiveCube as V,getLaunchModel as j,inboxPathForDrone as q,setCodexWakeTarget as X,pruneDeadCodexWakeTargets as z}from"./cubes.js";import{monitorStateRootForWorktree as J}from"./inbox-monitor.js";import{applyOllamaLaunchEnv as Q,checkModelReachable as Z}from"./model-presets.js";import{handleVersionFlag as ee,getPackageVersion as C}from"./version.js";import{isHelpFlag as O,setupHelpText as re,topLevelHelpText as oe,assimilateHelpText as se}from"./cli-help.js";import{runSpawn as te}from"./spawn.js";import{buildClaudeLaunchArgs as ie}from"./claude-launch-args.js";import{parseSyncArgs as ne,runSync as ae}from"./sync.js";import{parseCleanupArgs as ce,runCleanup as le}from"./cleanup-cmd.js";import{parseAssimilateArgs as de}from"./parse-assimilate-args.js";import{runAssimilate as pe}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as ue}from"./assimilate-deps.js";import{parseLaunchAllArgs as E}from"./parse-launch-all-args.js";import{unknownSubcommand as me}from"./unknown-subcommand.js";import{runLaunchAll as H}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as $}from"./launch-all-deps.js";import{discoverDroneCandidates as fe}from"./launch-all-discovery.js";import{explicitCliLaunchHint as ge,runBareLaunchMenu as he,shouldResolveExplicitCliLaunchHintTargets as we,shouldShowLaunchMenu as Ce}from"./bare-launch-menu.js";import{setTerminalTitle as xe}from"./terminal-title.js";import{initConsolePrefix as ke,consolePrefix as t}from"./console-prefix.js";import{initDebugFromArgv as be}from"./debug.js";import{fetchLatestBorgmcpVersion as ve,compareVersionsForStaleness as $e}from"./stale-version-check.js";import{defaultCliChoiceDeps as Ae,detectCliAvailability as A,installedCliNames as N,parseCliFlag as ye,resolveCliChoice as Se}from"./cli-platform.js";import{getRefreshToken as Te,getIdToken as Pe}from"./config.js";import{composeGetStarted as Ie,shouldShowGetStarted as Le}from"./get-started.js";import{prepareCodexRemoteLaunch as Re,withCodexCwdArg as De,defaultCodexRemoteDeps as Oe,checkCodexBridgeHealthy as Ee}from"./codex-remote.js";import{BORG_CODEX_REMOTE_WAKE_ENV as He,codexAgentKindConfigArgs as Ne,codexRemoteWakeConfigArgs as Fe,withAgentRuntimeEnv as y}from"./agent-runtime.js";import{findLoadedCodexThread as Be}from"./codex-app-server.js";import{buildAgentKickoffPrompt as Me,buildKickoffWakePathClause as We,recordCodexWakeTarget as _e,socketPathFromRemoteArgs as F}from"./codex-launch.js";import{codexBorgSessionConfigArgs as Ke}from"./launch-gate.js";import{addCodexSessionStartHook as Ge,addCodexUserPromptSubmitHook as Ue,addProjectSessionStartHook as Ye,addUserPromptSubmitHook as Ve,removeSessionStartHook as je}from"./config-utils.js";import{ensureCliMcpConfigured as S}from"./ensure-mcp-config.js";import{installBorgPlugin as qe}from"./opencode-plugin.js";import{connectOpenCodeDrone as Xe,computeOpenCodePort as B,createOpenCodeLaunchKickoff as ze,injectInitialKickoff as Je}from"./opencode-drone.js";async function Qe(){be(process.argv),ee(),await ke();const c=(async()=>{if(!process.stderr.isTTY)return;const e=C(),o=await ve();if(!o)return;const i=$e(e,o);i.stale&&i.message&&process.stderr.write(`${t()}${i.message}
3
+ `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(oe(C())),process.exit(0)),process.argv[2]==="setup"){O(process.argv[3])&&(process.stdout.write(re(C())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(O)&&(process.stdout.write(se(C())),process.exit(0));const e=de(process.argv.slice(3));e.ok||(process.stderr.write(n.red(`${t()}\u25FC borg assimilate: ${e.error}
4
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const o=ue(),i=await pe({role:e.role,flags:e.flags},o);process.exit(i)}if(process.argv[2]==="spawn"){const e=await te();process.exit(e)}if(process.argv[2]==="sync"){const e=ne(process.argv.slice(3));e.ok||(process.stderr.write(n.red(`${t()}\u25FC borg sync: ${e.error}
5
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const o=await ae({},e.options);process.exit(o)}if(process.argv[2]==="cleanup"){const e=ce(process.argv.slice(3));e.ok||(process.stderr.write(n.red(`${t()}\u25FC borg cleanup: ${e.error}
6
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const o=await le({},e.options);process.exit(o)}if(process.argv[2]==="launch-all"){const e=E(process.argv.slice(3));e.ok||(process.stderr.write(n.red(`${t()}\u25FC borg launch-all: ${e.error}
7
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const o=$(),i=await H(e.args,o);process.exit(i)}const a=me(process.argv[2]);if(a!==null&&(process.stderr.write(n.red(`${t()}\u25FC unknown command: ${a}
8
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1)),Le(await Te()!==null,await Pe()!==null)){const e=N(A()).length>0;process.stdout.write(Ie(e)),process.exit(0)}const d=ye(process.argv.slice(2));d.error&&(process.stderr.write(n.red(`${t()}\u25FC ${d.error}
9
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const T=async e=>{const o=Y({input:process.stdin,output:process.stdout});try{return await o.question(e)}finally{o.close()}};let r=await Se(d.cli,Ae(T,()=>process.stdin.isTTY===!0));Ze();const s=await V();let x;const P=async()=>s?(x===void 0&&(x=(await fe({targetCubeId:s.cubeId},$())).length>0),x):!1,k=process.stdin.isTTY===!0,b=process.stdout.isTTY===!0,I=ge({explicitCli:d.cli,stdinIsTTY:k,stdoutIsTTY:b,hasActiveCube:s!==null,hasLaunchAllTargets:we({explicitCli:d.cli,stdinIsTTY:k,stdoutIsTTY:b,hasActiveCube:s!==null})?await P():!1});if(I&&process.stderr.write(I),Ce({extraArgs:process.argv.slice(2),stdinIsTTY:k,stdoutIsTTY:b})){const e=N(A()).filter(i=>i!==r),o=await he({defaultCli:r,otherInstalledClis:e,hasLaunchAllTargets:await P()},T);if(o.kind==="launch-all"){const i=E([]),_=i.ok?await H(i.args,$()):1;process.exit(_)}r=o.cli}const l=d.rest;xe(s?{label:s.droneLabel,cubeName:s.name}:null,U(process.cwd()));const M=We(r,s&&r==="claude"?q(s.cubeId,s.droneId):null,s&&r==="claude"?J(v(process.cwd())):null);await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const L=r==="codex"?`borg-wake-${G()}`:null;let f,R=[],u={...y(process.env,r),BORG_SESSION:"1"},p=null,m=null;if(r==="codex"&&!l.includes("--remote")){console.error(`${t()}${n.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await Re(Oe());e.warning?(console.error(`${t()}${n.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.",R=e.args,u={...y(process.env,r),...e.env,BORG_SESSION:"1"},p=F(e.args),m=e.server?.cleanup??null}else r==="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.",p=F(l),p&&(u={...y(process.env,r),[He]:"1",BORG_SESSION:"1"}));if(s){const e=await j(s.cubeId,v(),s.droneId),o=Q(u,e,process.env);if(u=o.env,o.probe){const i=await Z(o.probe.descriptor,fetch,o.probe.baseUrl);i.ok||console.error(`${t()}${n.yellow(`warning: ${i.message}`)}`)}}const g=Me({cli:r,codexWakeNonce:L,monitorClause:M,codexWakePathClause:f});let h=null,w;if(r==="codex")w=[...Ke(),...Ne(),...Fe(p!==null),...R,...De([...l,g],process.cwd())];else if(r==="opencode"){const e=s?B(s.droneId):14096;qe(),h=ze(g),w=[process.cwd(),"--port",String(e),"--auto","--prompt",h.prompt,...l]}else w=ie(l,g);const W=r==="claude"?"Claude Code":r==="codex"?"Codex":"OpenCode";console.error(`${t()}${n.blue(`\u25FC Launching ${W}\u2026`)}`);const D=K(r,w,{stdio:"inherit",shell:!1,env:u});if(r==="opencode"&&h){const e=h,i=`http://127.0.0.1:${s?B(s.droneId):14096}`;Xe({serverUrl:i,directory:process.cwd(),droneLabel:s?.droneLabel??"opencode",cubeName:s?.name??"borg"}).then(()=>Je(e)).catch(()=>{})}r==="codex"&&s&&p&&(_e({deps:{setCodexWakeTarget:X,findLoadedCodexThread:Be},cubeId:s.cubeId,droneId:s.droneId,socketPath:p,passthroughArgs:l,previewNeedle:L??g.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),z(e=>Ee(e))),D.on("error",e=>{if(m)try{m()}catch{}if(e.code==="ENOENT"){console.error(`${t()}${n.red(`
10
+ \u25FC Failed to launch ${r}`)}`);const o=r==="opencode"?"opencode":r;console.error(`${t()}${n.gray(`Make sure ${o} is installed.
11
+ `)}`)}else console.error(`${t()}${n.red(`
12
+ \u25FC Failed to launch ${r}: ${e.message}
13
+ `)}`);process.exit(1)}),D.on("exit",e=>{if(m)try{m()}catch{}process.exit(e??0)})}function Ze(){const c=A();if(c.claude)try{S("claude"),Ye(v(process.cwd())),je(),Ve()}catch(a){console.error(`${t()}${n.yellow(`warning: Claude Code integration check failed: ${a?.message??a}`)}`)}if(c.codex)try{S("codex"),Ge(),Ue()}catch(a){console.error(`${t()}${n.yellow(`warning: Codex integration check failed: ${a?.message??a}`)}`)}if(c.opencode)try{S("opencode")}catch(a){console.error(`${t()}${n.yellow(`warning: OpenCode integration check failed: ${a?.message??a}`)}`)}}Qe().catch(c=>{console.error(`${t()}${n.red(`
14
14
  \u25FC Error: ${c.message}
15
15
  `)}`),process.exit(1)});
@@ -5,7 +5,7 @@ export declare const CODEX_WAKE_PROMPT = "New Borg cube-log activity arrived.";
5
5
  export declare function formatCodexWakePrompt(inboxLine: string): string;
6
6
  export declare const CODEX_CATCHUP_PROMPT = "Borg cube activity arrived while you were busy. Wake triage: run `borg_read-log unread_only=true` and DRAIN \u2014 repeat until the returned page is under the limit and behind_by is 0 \u2014 so no entries are skipped. Then handle actionable entries; if none, resume the prior interrupted work.";
7
7
  export declare function isCodexRemoteWakeEnabled(env?: NodeJS.ProcessEnv): boolean;
8
- export declare function resolveSessionAgentKind(env?: NodeJS.ProcessEnv): 'claude' | 'codex' | 'opencode';
8
+ export { resolveSessionAgentKind } from './agent-runtime.js';
9
9
  export interface CodexWakeTarget {
10
10
  enabled: boolean;
11
11
  }
@@ -73,14 +73,17 @@ export declare function fireCodexHeartbeatTick(deps?: CodexWakeDeps, cadenceMs?:
73
73
  * fireCodexHeartbeatTick every cadence. CODEX-ONLY: claude wakes via the tail-F
74
74
  * inbox Monitor + /loop ScheduleWakeup and has NO app-server socket to inject
75
75
  * into, so the heartbeat is intrinsically a codex mechanism. The gate reads
76
- * agentKind LOCALLY from this child's own env (resolveSessionAgentKind →
77
- * BORG_CODEX_REMOTE_WAKE), never a mutable/server-recorded field, so a mislabel
78
- * can't silently defeat the backstop (gh#633 lesson). The timer is unref'd so the
79
- * heartbeat alone never keeps the process alive. Returns the timer, or null when
80
- * not a codex session. Injectable agentKind/intervalMs/tick for tests.
76
+ * agentKind and remote-wake capability LOCALLY from this child's own env,
77
+ * never a mutable/server-recorded field, so a mislabel can't silently defeat
78
+ * the backstop (gh#633 lesson). Agent CLI identity and remote transport are
79
+ * separate: a Codex CLI without a live remote transport has no app-server
80
+ * heartbeat to run. The timer is unref'd so it never keeps the process alive.
81
+ * Returns the timer, or null when this is not a remotely-wakeable Codex
82
+ * session. Injectable for tests.
81
83
  */
82
84
  export declare function startCodexHeartbeat(opts?: {
83
85
  agentKind?: 'claude' | 'codex' | 'opencode';
86
+ remoteWakeEnabled?: boolean;
84
87
  intervalMs?: number;
85
88
  tick?: () => void;
86
89
  }): ReturnType<typeof setInterval> | null;
@@ -1,2 +1,2 @@
1
- import{getActiveCube as C,getCodexWakeTarget as p,setCodexWakeTarget as R}from"./cubes.js";import{CodexAppServerClient as O}from"./codex-app-server.js";import{checkCodexBridgeHealthy as S}from"./codex-remote.js";import{recordEventReceipt as W}from"./health-beat.js";import{codexAppServerSocketFromEnv as K,pickFreshThread as B,wakeTargetChanged as M,wakeRetryBackoffMs as F,wakeRetryExpired as N,WAKE_RETRY_MAX_ATTEMPTS as H,shouldFireHeartbeat as j}from"./codex-wake-resolve.js";const L="New Borg cube-log activity arrived.";function ie(e){return`New Borg cube-log activity arrived:
2
- ${e}`}const D="Borg cube activity arrived while you were busy. Wake triage: run `borg_read-log unread_only=true` and DRAIN \u2014 repeat until the returned page is under the limit and behind_by is 0 \u2014 so no entries are skipped. Then handle actionable entries; if none, resume the prior interrupted work.";function P(e=process.env){return e.BORG_CODEX_REMOTE_WAKE==="1"}function X(e=process.env){return P(e)?"codex":e.BORG_OPENCODE==="1"?"opencode":"claude"}function q(e=process.env){return P(e)?{enabled:!0}:{enabled:!1}}async function ce(e,t={}){try{const r=await(t.getCodexWakeTarget??p)(e.cubeId,e.droneId);return r?(t.checkBridge??S)(r.socketPath):!1}catch{return null}}let f=!1;const h=[],d=new Set,w=[],$=100;let g=!1,k=null;function se(){return k}function m(e){k=(e.now??Date.now)()}let y=!1,v=!1;function T(){return v?!1:(v=!0,!0)}function A(){v=!1}function G(e){return e?.code==="ENOENT"}function V(e){return new Promise(t=>setTimeout(t,e))}function x(e,t){return t.createClient?t.createClient(e):new O(e)}async function E(e,t){const n=K(t.env??process.env);if(n){const a=x(n,t);await a.connect();try{const o=await a.loadedThreadIds(),i=[];for(const u of o){const s=await a.readThread(u);s&&i.push({id:s.id,cwd:s.cwd,updatedAt:s.updatedAt})}const c=B(i,{cwd:(t.cwd??(()=>process.cwd()))()});return c?(await Y(e,{socketPath:n,threadId:c},t),{socketPath:n,threadId:c}):null}finally{a.close()}}const r=await(t.getCodexWakeTarget??p)(e.cubeId,e.droneId);return r?{socketPath:r.socketPath,threadId:r.threadId}:null}async function Y(e,t,n){try{const r=n.getCodexWakeTarget??p,a=n.setCodexWakeTarget??R,o=await r(e.cubeId,e.droneId),i=o?{socketPath:o.socketPath,threadId:o.threadId}:null;M(i,t)&&await a(e.cubeId,e.droneId,t)}catch{}}function ue(e=L,t=process.env,n={}){q(t).enabled&&(h.push({reason:e,deps:n}),!f&&(f=!0,Q().finally(()=>{f=!1})))}async function Q(){for(;h.length>0;){const e=h.shift();await U(e.reason,e.deps)}}async function U(e,t){if(!T()){b(t);return}try{const n=await(t.getActiveCube??C)();if(!n)return;const r=await E(n,t);if(!r)return;const{socketPath:a,threadId:o}=r,i=`${o}\0${e}`;if(d.has(i))return;const c=x(a,t);await c.connect();try{if((await c.readThread(o))?.status?.type==="active"){b(t);return}await c.startTurn(o,e),W(),Z(i),m(t)}finally{c.close()}}catch{b(t)}finally{A()}}function b(e){g||(g=!0,z(e).finally(()=>{g=!1}))}async function z(e){const t=e.sleep??V,n=e.now??Date.now,r=e.jitter??(()=>Math.random()*500),a=e.maxAttempts??H,o=n();let i=0;for(;!N(o,n())&&i<a;)if(await t(F(i,r())),i++,!!T())try{const c=await(e.getActiveCube??C)();if(!c)continue;const u=await E(c,e);if(!u)continue;const{socketPath:s,threadId:I}=u,l=x(s,e);await l.connect();try{if((await l.readThread(I))?.status?.type==="active")continue;await l.startTurn(I,D),W(),m(e);return}finally{l.close()}}catch{}finally{A()}}const _=20*6e4;async function J(e={},t=_){if(y)return;const n=(e.now??Date.now)();if(j(k,n,t)&&!(e.isStreamOwner&&!e.isStreamOwner())&&T()){y=!0;try{const r=await(e.getActiveCube??C)();if(!r)return;const a=await E(r,e);if(!a)return;const o=x(a.socketPath,e);await o.connect();try{if((await o.readThread(a.threadId))?.status?.type==="active")return;await o.startTurn(a.threadId,D),m(e)}finally{o.close()}}catch(r){G(r)&&e.onAppServerSocketDead?.()}finally{y=!1,A()}}}function de(e={}){if((e.agentKind??X())!=="codex")return null;const n=e.intervalMs??_,r=e.tick??(()=>{J()}),a=setInterval(r,n);return a.unref?.(),a}function le(){f=!1,h.length=0,d.clear(),w.length=0,g=!1,k=null,y=!1,v=!1}function Z(e){if(!d.has(e))for(d.add(e),w.push(e);w.length>$;){const t=w.shift();t&&d.delete(t)}}export{D as CODEX_CATCHUP_PROMPT,_ as CODEX_HEARTBEAT_CADENCE_MS,L as CODEX_WAKE_PROMPT,J as fireCodexHeartbeatTick,ie as formatCodexWakePrompt,se as getLastDeliveredAt,P as isCodexRemoteWakeEnabled,ce as probeCodexBridgeArmed,le as resetCodexWakeForTests,q as resolveCodexWakeTarget,X as resolveSessionAgentKind,de as startCodexHeartbeat,ue as wakeCodexViaAppServer};
1
+ import{getActiveCube as x,getCodexWakeTarget as C,setCodexWakeTarget as R}from"./cubes.js";import{CodexAppServerClient as S}from"./codex-app-server.js";import{checkCodexBridgeHealthy as K}from"./codex-remote.js";import{recordEventReceipt as W}from"./health-beat.js";import{BORG_CODEX_REMOTE_WAKE_ENV as O,resolveSessionAgentKind as M}from"./agent-runtime.js";import{codexAppServerSocketFromEnv as B,pickFreshThread as F,wakeTargetChanged as N,wakeRetryBackoffMs as H,wakeRetryExpired as j,WAKE_RETRY_MAX_ATTEMPTS as L,shouldFireHeartbeat as X}from"./codex-wake-resolve.js";const q="New Borg cube-log activity arrived.";function se(e){return`New Borg cube-log activity arrived:
2
+ ${e}`}const _="Borg cube activity arrived while you were busy. Wake triage: run `borg_read-log unread_only=true` and DRAIN \u2014 repeat until the returned page is under the limit and behind_by is 0 \u2014 so no entries are skipped. Then handle actionable entries; if none, resume the prior interrupted work.";function D(e=process.env){return e[O]==="1"}import{resolveSessionAgentKind as ke}from"./agent-runtime.js";function V(e=process.env){return D(e)?{enabled:!0}:{enabled:!1}}async function le(e,t={}){try{const r=await(t.getCodexWakeTarget??C)(e.cubeId,e.droneId);return r?(t.checkBridge??K)(r.socketPath):!1}catch{return null}}let f=!1;const h=[],u=new Set,w=[],$=100;let g=!1,k=null;function ue(){return k}function T(e){k=(e.now??Date.now)()}let v=!1,y=!1;function p(){return y?!1:(y=!0,!0)}function A(){y=!1}function Y(e){return e?.code==="ENOENT"}function G(e){return new Promise(t=>setTimeout(t,e))}function m(e,t){return t.createClient?t.createClient(e):new S(e)}async function b(e,t){const n=B(t.env??process.env);if(n){const o=m(n,t);await o.connect();try{const a=await o.loadedThreadIds(),i=[];for(const l of a){const s=await o.readThread(l);s&&i.push({id:s.id,cwd:s.cwd,updatedAt:s.updatedAt})}const c=F(i,{cwd:(t.cwd??(()=>process.cwd()))()});return c?(await Q(e,{socketPath:n,threadId:c},t),{socketPath:n,threadId:c}):null}finally{o.close()}}const r=await(t.getCodexWakeTarget??C)(e.cubeId,e.droneId);return r?{socketPath:r.socketPath,threadId:r.threadId}:null}async function Q(e,t,n){try{const r=n.getCodexWakeTarget??C,o=n.setCodexWakeTarget??R,a=await r(e.cubeId,e.droneId),i=a?{socketPath:a.socketPath,threadId:a.threadId}:null;N(i,t)&&await o(e.cubeId,e.droneId,t)}catch{}}function de(e=q,t=process.env,n={}){V(t).enabled&&(h.push({reason:e,deps:n}),!f&&(f=!0,U().finally(()=>{f=!1})))}async function U(){for(;h.length>0;){const e=h.shift();await z(e.reason,e.deps)}}async function z(e,t){if(!p()){E(t);return}try{const n=await(t.getActiveCube??x)();if(!n)return;const r=await b(n,t);if(!r)return;const{socketPath:o,threadId:a}=r,i=`${a}\0${e}`;if(u.has(i))return;const c=m(o,t);await c.connect();try{if((await c.readThread(a))?.status?.type==="active"){E(t);return}await c.startTurn(a,e),W(),ee(i),T(t)}finally{c.close()}}catch{E(t)}finally{A()}}function E(e){g||(g=!0,J(e).finally(()=>{g=!1}))}async function J(e){const t=e.sleep??G,n=e.now??Date.now,r=e.jitter??(()=>Math.random()*500),o=e.maxAttempts??L,a=n();let i=0;for(;!j(a,n())&&i<o;)if(await t(H(i,r())),i++,!!p())try{const c=await(e.getActiveCube??x)();if(!c)continue;const l=await b(c,e);if(!l)continue;const{socketPath:s,threadId:I}=l,d=m(s,e);await d.connect();try{if((await d.readThread(I))?.status?.type==="active")continue;await d.startTurn(I,_),W(),T(e);return}finally{d.close()}}catch{}finally{A()}}const P=20*6e4;async function Z(e={},t=P){if(v)return;const n=(e.now??Date.now)();if(X(k,n,t)&&!(e.isStreamOwner&&!e.isStreamOwner())&&p()){v=!0;try{const r=await(e.getActiveCube??x)();if(!r)return;const o=await b(r,e);if(!o)return;const a=m(o.socketPath,e);await a.connect();try{if((await a.readThread(o.threadId))?.status?.type==="active")return;await a.startTurn(o.threadId,_),T(e)}finally{a.close()}}catch(r){Y(r)&&e.onAppServerSocketDead?.()}finally{v=!1,A()}}}function fe(e={}){const t=e.agentKind??M(),n=e.remoteWakeEnabled??D();if(t!=="codex"||!n)return null;const r=e.intervalMs??P,o=e.tick??(()=>{Z()}),a=setInterval(o,r);return a.unref?.(),a}function he(){f=!1,h.length=0,u.clear(),w.length=0,g=!1,k=null,v=!1,y=!1}function ee(e){if(!u.has(e))for(u.add(e),w.push(e);w.length>$;){const t=w.shift();t&&u.delete(t)}}export{_ as CODEX_CATCHUP_PROMPT,P as CODEX_HEARTBEAT_CADENCE_MS,q as CODEX_WAKE_PROMPT,Z as fireCodexHeartbeatTick,se as formatCodexWakePrompt,ue as getLastDeliveredAt,D as isCodexRemoteWakeEnabled,le as probeCodexBridgeArmed,he as resetCodexWakeForTests,V as resolveCodexWakeTarget,ke as resolveSessionAgentKind,fe as startCodexHeartbeat,de as wakeCodexViaAppServer};
@@ -1 +1 @@
1
- import{mkdirSync as v,chmodSync as w,readdirSync as g,rmSync as C,writeFileSync as k,readFileSync as y}from"node:fs";import{homedir as S}from"node:os";import{join as u}from"node:path";import{randomBytes as b}from"node:crypto";import{spawn as A}from"node:child_process";import{CodexAppServerClient as E}from"./codex-app-server.js";import{codexBorgSessionConfigArgs as R,BORG_SESSION_ENV as _}from"./launch-gate.js";import{codexAppServerSocketConfigArgs as $}from"./codex-wake-resolve.js";const N=u(S(),".config","borgmcp","codex-remote");function G(e,r){return I(e)?e:["--cd",r,...e]}function I(e){return e.some(r=>r==="--cd"||r.startsWith("--cd=")||r==="-C")}function x(e){try{return process.kill(e,0),!0}catch(r){return r?.code==="EPERM"}}function X(e,r={}){if(!e)return null;const a=r.isAlive??x,i=r.readPidFile??(o=>y(o,"utf-8")),c=e.replace(/\.sock$/,".pid");try{const o=Number.parseInt(i(c).trim(),10);return Number.isNaN(o)?null:a(o)}catch{return null}}function s(e){try{C(e,{force:!0})}catch{}}function M(e,r){let a;try{a=g(e)}catch{return}for(const i of a){if(!i.endsWith(".pid"))continue;const c=u(e,i),o=u(e,i.replace(/\.pid$/,".sock"));let t;try{t=Number.parseInt(y(c,"utf-8").trim(),10)}catch{s(c);continue}(Number.isNaN(t)||!r(t))&&(s(o),s(c))}}function p(e){return{args:[],env:{},warning:e}}async function j(e){const r=e.runtimeDir??N,a=e.isAlive??x,i=e.readyTimeoutMs??8e3,c=e.pollIntervalMs??250;try{v(r,{recursive:!0,mode:448}),w(r,448),M(r,a)}catch(n){return p(`Codex remote-wake disabled: could not prepare ${r} (${n?.message??n}); run borg_regen manually.`)}const o=(e.socketId??(()=>b(16).toString("hex")))(),t=u(r,`${o}.sock`),m=u(r,`${o}.pid`);let l;try{l=e.spawnAppServer(t)}catch(n){return s(t),p(`Codex remote-wake disabled: could not start \`codex app-server\` (${n?.message??n}) \u2014 is Codex installed + up to date? This session only wakes on the ~30min /loop fallback; run borg_regen manually.`)}if(l.pid!=null)try{k(m,String(l.pid))}catch{}const f=()=>{try{l.kill()}catch{}s(t),s(m)},h=Math.max(1,Math.ceil(i/c));let d=!1;for(let n=0;n<h&&!d;n++){try{d=await e.probeReady(t)}catch{d=!1}!d&&n<h-1&&await e.sleep(c)}return d?{args:["--remote",`unix://${t}`],env:{BORG_CODEX_REMOTE_WAKE:"1"},server:{pid:l.pid,socketPath:t,cleanup:f}}:(f(),p(`Codex remote-wake disabled: could not reach a Codex app-server at ${t} within ${i}ms (is Codex up to date? \`codex app-server --listen\` is required). This session only wakes on the ~30min /loop fallback \u2014 run borg_regen manually when you return.`))}function q(){return{spawnAppServer:e=>{const r=A("codex",["app-server",...R(),...$(e),"--listen",`unix://${e}`],{stdio:"ignore",shell:!1,env:{...process.env,[_]:"1"}});return{pid:r.pid,kill:()=>{try{r.kill()}catch{}}}},probeReady:async e=>{const r=new E(e);try{return await r.connect(),await r.loadedThreadIds(),!0}catch{return!1}finally{try{r.close()}catch{}}},sleep:e=>new Promise(r=>setTimeout(r,e))}}export{N as DEFAULT_CODEX_REMOTE_DIR,X as checkCodexBridgeHealthy,q as defaultCodexRemoteDeps,x as defaultIsAlive,j as prepareCodexRemoteLaunch,G as withCodexCwdArg};
1
+ import{mkdirSync as C,chmodSync as v,readdirSync as w,rmSync as k,writeFileSync as A,readFileSync as y}from"node:fs";import{homedir as S}from"node:os";import{join as u}from"node:path";import{randomBytes as b}from"node:crypto";import{spawn as E}from"node:child_process";import{CodexAppServerClient as _}from"./codex-app-server.js";import{codexBorgSessionConfigArgs as N,BORG_SESSION_ENV as R}from"./launch-gate.js";import{codexAppServerSocketConfigArgs as $}from"./codex-wake-resolve.js";import{BORG_AGENT_KIND_ENV as I,BORG_CODEX_REMOTE_WAKE_ENV as x,codexAgentKindConfigArgs as T,codexRemoteWakeConfigArgs as M}from"./agent-runtime.js";const O=u(S(),".config","borgmcp","codex-remote");function q(e,r){return P(e)?e:["--cd",r,...e]}function P(e){return e.some(r=>r==="--cd"||r.startsWith("--cd=")||r==="-C")}function g(e){try{return process.kill(e,0),!0}catch(r){return r?.code==="EPERM"}}function H(e,r={}){if(!e)return null;const a=r.isAlive??g,i=r.readPidFile??(o=>y(o,"utf-8")),c=e.replace(/\.sock$/,".pid");try{const o=Number.parseInt(i(c).trim(),10);return Number.isNaN(o)?null:a(o)}catch{return null}}function s(e){try{k(e,{force:!0})}catch{}}function B(e,r){let a;try{a=w(e)}catch{return}for(const i of a){if(!i.endsWith(".pid"))continue;const c=u(e,i),o=u(e,i.replace(/\.pid$/,".sock"));let t;try{t=Number.parseInt(y(c,"utf-8").trim(),10)}catch{s(c);continue}(Number.isNaN(t)||!r(t))&&(s(o),s(c))}}function p(e){return{args:[],env:{},warning:e}}async function U(e){const r=e.runtimeDir??O,a=e.isAlive??g,i=e.readyTimeoutMs??8e3,c=e.pollIntervalMs??250;try{C(r,{recursive:!0,mode:448}),v(r,448),B(r,a)}catch(n){return p(`Codex remote-wake disabled: could not prepare ${r} (${n?.message??n}); run borg_regen manually.`)}const o=(e.socketId??(()=>b(16).toString("hex")))(),t=u(r,`${o}.sock`),m=u(r,`${o}.pid`);let l;try{l=e.spawnAppServer(t)}catch(n){return s(t),p(`Codex remote-wake disabled: could not start \`codex app-server\` (${n?.message??n}) \u2014 is Codex installed + up to date? This session only wakes on the ~30min /loop fallback; run borg_regen manually.`)}if(l.pid!=null)try{A(m,String(l.pid))}catch{}const f=()=>{try{l.kill()}catch{}s(t),s(m)},h=Math.max(1,Math.ceil(i/c));let d=!1;for(let n=0;n<h&&!d;n++){try{d=await e.probeReady(t)}catch{d=!1}!d&&n<h-1&&await e.sleep(c)}return d?{args:["--remote",`unix://${t}`],env:{[x]:"1"},server:{pid:l.pid,socketPath:t,cleanup:f}}:(f(),p(`Codex remote-wake disabled: could not reach a Codex app-server at ${t} within ${i}ms (is Codex up to date? \`codex app-server --listen\` is required). This session only wakes on the ~30min /loop fallback \u2014 run borg_regen manually when you return.`))}function z(){return{spawnAppServer:e=>{const r=E("codex",["app-server",...N(),...T(),...M(),...$(e),"--listen",`unix://${e}`],{stdio:"ignore",shell:!1,env:{...process.env,[R]:"1",[I]:"codex",[x]:"1"}});return{pid:r.pid,kill:()=>{try{r.kill()}catch{}}}},probeReady:async e=>{const r=new _(e);try{return await r.connect(),await r.loadedThreadIds(),!0}catch{return!1}finally{try{r.close()}catch{}}},sleep:e=>new Promise(r=>setTimeout(r,e))}}export{O as DEFAULT_CODEX_REMOTE_DIR,H as checkCodexBridgeHealthy,z as defaultCodexRemoteDeps,g as defaultIsAlive,U as prepareCodexRemoteLaunch,q as withCodexCwdArg};
@@ -119,10 +119,12 @@ export declare function isCodexUserPromptSubmitHookRegistered(hooksPath?: string
119
119
  export declare function isOpenCodeMcpServerConfigured(configPath?: string): boolean;
120
120
  /**
121
121
  * Add borg MCP server to OpenCode using `opencode mcp add` CLI.
122
- * Pins BORG_SESSION=1, BORG_OPENCODE=1, and BORG_API_URL in the server
123
- * environment so the MCP child inherits the activation gate + agent-kind
124
- * signal (same approach as codex's pinned env — opencode MCP children
125
- * only see pinned env, not parent process env).
122
+ * Pins BORG_SESSION=1, BORG_AGENT_KIND=opencode, the legacy BORG_OPENCODE=1,
123
+ * and BORG_API_URL in the server environment so the MCP child inherits the
124
+ * activation gate + explicit agent-kind signal (same approach as Codex's
125
+ * pinned env — OpenCode MCP children only see pinned env, not parent process
126
+ * env). Existing configs with BORG_OPENCODE remain supported by the runtime
127
+ * fallback.
126
128
  */
127
129
  export declare function addOpenCodeMcpServer(): void;
128
130
  //# sourceMappingURL=config-utils.d.ts.map
@@ -1,3 +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 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};
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";import{BORG_AGENT_KIND_ENV as A,BORG_CODEX_REMOTE_WAKE_ENV as E,withAgentRuntimeEnv as U}from"./agent-runtime.js";const H=v(import.meta.url),j=R(H),a="borg-regen",m="borg-log-audit",N=i.join(f.homedir(),".claude.json"),w=i.join(f.homedir(),".codex","config.toml"),u=i.join(f.homedir(),".codex","hooks.json"),I=i.join(f.homedir(),".config","opencode","opencode.json"),G="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 x(e,r){n.mkdirSync(i.dirname(e),{recursive:!0}),n.writeFileSync(e,JSON.stringify(r,null,2)+`
3
+ `,"utf-8")}function K(){return C(h())}function V(e){return C(P(e))}function X(e){return $(P(e))}function P(e){return i.join(e,".claude","settings.local.json")}function C(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}]}),x(e,r),!0)}function $(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 W(){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 Q(){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 q(){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 z(){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 Y(){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 Z(e=N){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:G in t}catch{return!1}}function ee(e=w){try{if(!n.existsSync(e))return!1;const r=n.readFileSync(e,"utf-8"),o=/^\s*\[mcp_servers\.borg\]\s*$/m.test(r),t=new RegExp(`^\\s*${A}\\s*=\\s*"codex"\\s*$`,"m").test(r),s=new RegExp(`^\\s*${E}\\s*=\\s*"1"\\s*$`,"m").test(r);return o&&(t||s)}catch{return!1}}function re(){return i.join(j,"index.js")}function oe(){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 te(){try{try{d("codex mcp remove borg",{stdio:"ignore"})}catch{}const e=process.env.BORG_API_URL||"https://api.borgmcp.ai",r=U(process.env,"codex");d("codex mcp add borg --env BORG_API_URL="+_(e)+` --env ${A}=codex -- borg-mcp`,{stdio:"inherit",env:{...r,BORG_API_URL:e}})}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 O(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),x(u,t),!0}function se(){return O("SessionStart",a,{matcher:"startup|resume",timeout:30})}function ne(){return O("UserPromptSubmit",m,{timeout:10})}function b(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 ie(e=u){return b("SessionStart",a,e)}function ae(e=u){return b("UserPromptSubmit",m,e)}function ce(e=I){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 me(){try{const e=process.env.BORG_API_URL||"https://api.borgmcp.ai";d(`opencode mcp add borg --env BORG_SESSION=1 --env BORG_AGENT_KIND=opencode --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}`)}}export{te as addCodexMcpServer,se as addCodexSessionStartHook,ne as addCodexUserPromptSubmitHook,oe as addMcpServer,me as addOpenCodeMcpServer,V as addProjectSessionStartHook,K as addSessionStartHook,z as addUserPromptSubmitHook,re as getBinaryPath,b as isCodexHookRegistered,ee as isCodexMcpServerConfigured,ie as isCodexSessionStartHookRegistered,ae as isCodexUserPromptSubmitHookRegistered,Z as isMcpServerConfigured,ce as isOpenCodeMcpServerConfigured,X as isProjectSessionStartHookRegistered,W as isSessionStartHookRegistered,Q as isUserPromptSubmitHookRegistered,q as removeSessionStartHook,Y as removeUserPromptSubmitHook};
@@ -0,0 +1,18 @@
1
+ import type { BorgCli } from './cubes.js';
2
+ export interface EnsureMcpConfigDeps {
3
+ isClaudeConfigured: () => boolean;
4
+ addClaude: () => void;
5
+ isCodexConfigured: () => boolean;
6
+ addCodex: () => void;
7
+ isOpenCodeConfigured: () => boolean;
8
+ addOpenCode: () => void;
9
+ }
10
+ /**
11
+ * Ensure borg is registered as an MCP server for one selected agent CLI.
12
+ *
13
+ * Returns true when this call added the registration and false when it was
14
+ * already present. Errors from an adder intentionally propagate so callers
15
+ * can fail the launch with the CLI-specific remediation message.
16
+ */
17
+ export declare function ensureCliMcpConfigured(cli: BorgCli, deps?: EnsureMcpConfigDeps): boolean;
18
+ //# sourceMappingURL=ensure-mcp-config.d.ts.map
@@ -0,0 +1 @@
1
+ import{addCodexMcpServer as r,addMcpServer as o,addOpenCodeMcpServer as n,isCodexMcpServerConfigured as u,isMcpServerConfigured as C,isOpenCodeMcpServerConfigured as i}from"./config-utils.js";const a={isClaudeConfigured:C,addClaude:o,isCodexConfigured:u,addCodex:r,isOpenCodeConfigured:i,addOpenCode:n};function c(d,e=a){switch(d){case"claude":return e.isClaudeConfigured()?!1:(e.addClaude(),!0);case"codex":return e.isCodexConfigured()?!1:(e.addCodex(),!0);case"opencode":return e.isOpenCodeConfigured()?!1:(e.addOpenCode(),!0)}}export{c as ensureCliMcpConfigured};
@@ -43,10 +43,9 @@ export interface HealthPayload {
43
43
  wake_armed: boolean;
44
44
  /**
45
45
  * gh#634: the drone's agent_kind from LIVE runtime detection
46
- * (resolveSessionAgentKind). agent_kind is written only by the assimilate
47
- * INSERT (no update path), so a relaunched drone's recorded agent_kind is
48
- * frozen at first-join. Beating it self-heals the column via recordDroneHealth
49
- * COALESCE — same always-running-child self-heal pattern as wake_armed.
46
+ * (resolveSessionAgentKind). Assimilation captures agent_kind only at join,
47
+ * so a relaunch of an existing seat needs this always-running child to
48
+ * refresh the recorded value through recordDroneHealth's COALESCE update.
50
49
  */
51
50
  agent_kind: 'claude' | 'codex' | 'opencode';
52
51
  /**
@@ -1 +1 @@
1
- let a=null,i=null,l=null;function x(e=new Date){a=e}function y(){return a}function k(){return i}function v(){return l}function A(){a=null,i=null,l=null}function u(e,t,n,o,r,c){return{sse_connected:e,inbox_monitor_armed:t!==!1,wake_armed:n!==!1,agent_kind:o,hostname:r,version:c,last_event_at:a?a.toISOString():null}}async function d(e,t,n){try{const o=await n.getToken();await n.fetchImpl(`${e.apiUrl}/api/drone/health`,{method:"POST",headers:{Authorization:`Bearer ${o}`,"X-Drone-Session":e.sessionToken,"Content-Type":"application/json"},body:JSON.stringify(t)})}catch{}}async function f(e,t){const n=u(t.sseConnected,t.inboxMonitorHealthy,t.wakeArmed,t.agentKind,t.hostname,t.version);await d(e,n,t)}async function m(e){try{const t=await e.getActiveCube();if(!t)return;const n=e.getStreamConnected(),o=e.checkMonitor(e.getInboxPath(t));i=o;const r=e.resolveAgentKind(),c=r==="opencode"?await e.probeOpenCodeDrone?.(t)??null:e.isCodexRemoteWake()?await e.probeBridgeArmed(t):o;l=c;const h=e.resolveHostname(),s=e.resolveVersion();await f(t,{sseConnected:n,inboxMonitorHealthy:o,wakeArmed:c,agentKind:r,hostname:h,version:s,fetchImpl:e.fetchImpl,getToken:e.getToken})}catch{}}const g=6e4;function H(e,t=g){const n=setInterval(()=>{m(e)},t);return n.unref?.(),n}export{g as HEALTH_BEAT_INTERVAL_MS,A as __resetHealthBeatStateForTest,u as buildHealthPayload,f as emitHealthBeat,k as getCachedMonitorHealthy,v as getCachedWakeArmed,y as getLastEventReceivedAt,d as postHealthBeat,x as recordEventReceipt,m as runHealthBeatOnce,H as startHealthBeatTick};
1
+ let r=null,i=null,l=null;function g(e=new Date){r=e}function v(){return r}function y(){return i}function k(){return l}function A(){r=null,i=null,l=null}function d(e,t,n,o,a,c){return{sse_connected:e,inbox_monitor_armed:t!==!1,wake_armed:n!==!1,agent_kind:o,hostname:a,version:c,last_event_at:r?r.toISOString():null}}async function f(e,t,n){try{const o=await n.getToken();await n.fetchImpl(`${e.apiUrl}/api/drone/health`,{method:"POST",headers:{Authorization:`Bearer ${o}`,"X-Drone-Session":e.sessionToken,"Content-Type":"application/json"},body:JSON.stringify(t)})}catch{}}async function m(e,t){const n=d(t.sseConnected,t.inboxMonitorHealthy,t.wakeArmed,t.agentKind,t.hostname,t.version);await f(e,n,t)}async function h(e){try{const t=await e.getActiveCube();if(!t)return;const n=e.getStreamConnected(),o=e.checkMonitor(e.getInboxPath(t));i=o;const a=e.resolveAgentKind(),c=a==="opencode"?await e.probeOpenCodeDrone?.(t)??null:a==="codex"?e.isCodexRemoteWake()?await e.probeBridgeArmed(t):!1:o;l=c;const s=e.resolveHostname(),u=e.resolveVersion();await m(t,{sseConnected:n,inboxMonitorHealthy:o,wakeArmed:c,agentKind:a,hostname:s,version:u,fetchImpl:e.fetchImpl,getToken:e.getToken})}catch{}}const x=6e4;function H(e,t=x){h(e);const n=setInterval(()=>{h(e)},t);return n.unref?.(),n}export{x as HEALTH_BEAT_INTERVAL_MS,A as __resetHealthBeatStateForTest,d as buildHealthPayload,m as emitHealthBeat,y as getCachedMonitorHealthy,k as getCachedWakeArmed,v as getLastEventReceivedAt,f as postHealthBeat,g as recordEventReceipt,h as runHealthBeatOnce,H as startHealthBeatTick};
package/dist/index.js CHANGED
@@ -1,38 +1,38 @@
1
1
  #!/usr/bin/env node
2
- import{Server as X}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Z}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as ee,ListToolsRequestSchema as te,ListPromptsRequestSchema as oe,GetPromptRequestSchema as re}from"@modelcontextprotocol/sdk/types.js";import{assertRoleMatches as ne}from"./role-match.js";import{getCubeInfo as se,getRoleInfo as A,getRoleInfoByName as ie,getRoster as ae,readLog as ce,appendLog as le,submitReport as de,fetchReports as ue,ackLogEntry as pe,recordDecision as me,listDecisions as be,regen as L,listCubes as fe,createCube as ge,updateCube as O,deleteCube as he,createRole as _e,updateRole as ye,patchRoleSection as E,patchTaxonomyClass as B,deleteRole as we,reassignDrone as xe,evictDrone as $e,getCube as w,checkSubscriptionStatus as ve,createBillingPortalSession as ke,createSubscription as Ee,syncRoles as Ce,applyTemplate as Re,whoami as Se,roleRationale as Ie,getValidToken as Te}from"./remote-client.js";import{startHealthBeatTick as qe}from"./health-beat.js";import{getTemplate as C,listTemplateNames as R,resolveCubeDirectiveForCreate as De,resolveCubeDirectiveForApply as Pe,resolveMessageTaxonomyForCreate as Ue}from"./templates.js";import{activeCubeWithFreshRegenIdentity as F,getActiveCube as h,setActiveCube as H,findProjectRoot as S,inboxPathForDrone as I}from"./cubes.js";import{monitorStateRootForWorktree as T}from"./inbox-monitor.js";import{addSessionStartHook as je,addUserPromptSubmitHook as Ne}from"./config-utils.js";import{humanAgo as W,formatLogEntryMarkdown as Me,formatRegenMarkdown as V,getDronePlaybook as Ae,getDronePlaybookChapter as Le,nullTaxonomyTip as Oe,regenWakePathDroneLabel as Be}from"./regen-format.js";import{startLogStream as Fe,getStreamStatus as q}from"./log-stream.js";import{isMcpReadinessProbe as He}from"./readiness-probe.js";import{runMcpStartupServices as We}from"./startup-services.js";import{TOOL_MANIFEST as Ve}from"./tool-manifest.js";import{DOCS_SECTIONS as ze,matchDocsSections as Je,formatDocsIndex as Qe}from"./docs-sections.js";import{renderRoleList as Ye}from"./list-roles-render.js";import{filterToolsForRole as Ge}from"./tool-scope.js";import{getPackageVersion as x,getOnDiskVersion as Ke,handleVersionFlag as Xe}from"./version.js";import{renderStreamStatus as Ze,checkInboxMonitorHealthy as D,formatWakePathPrefix as et,shouldShowWakePathWarning as tt}from"./stream-status.js";import{formatRoleAgentLabel as ot,renderRoster as rt}from"./roster-render.js";import{resolveDroneIdByLabel as nt,isUuidShape as st}from"./evict-drone.js";import{authRecoveryMessage as it}from"./auth-recovery.js";import{DroneEvictedError as at,DroneFrozenError as ct,formatEvictedToolResult as lt,formatFrozenToolResult as dt}from"./drone-lifecycle.js";import{classifyInSessionAssimilate as ut,reattachOnlyRefusal as pt,reattachFailureMessage as mt}from"./assimilate-guard.js";import{gateAllowsActivation as bt,borgSessionToolNotice as ft}from"./launch-gate.js";import{renderSyncRolesResult as gt}from"./sync-roles-render.js";import{initConsolePrefix as ht,consolePrefix as $}from"./console-prefix.js";import{isCodexRemoteWakeEnabled as P,resolveSessionAgentKind as U,probeCodexBridgeArmed as _t}from"./codex-app-wake.js";import{connectOpenCodeDrone as yt,injectOpenCodeEntry as wt,probeOpenCodeDroneArmed as xt,computeOpenCodePort as $t}from"./opencode-drone.js";import{installBorgPlugin as vt}from"./opencode-plugin.js";import{setModuleInjectOpenCode as kt}from"./log-stream.js";import{lifecycleSignalForMessage as Et,recordLifecycleLog as z,shouldSuppressLifecycleLog as Ct}from"./lifecycle-log-guard.js";import{normalizeDirectLogRecipients as Rt}from"./direct-log.js";import J from"open";import St from"os";function It(){try{const m=St.hostname();return m&&m.trim()?m.trim().slice(0,255):null}catch{return null}}async function Q(m,y){return await Re(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 Tt(){Xe();const m=He();await We(m,{sessionStartHook:()=>{je()},auditHook:()=>{Ne()},sseStream:()=>{Fe()},openCode:async()=>{vt();const l=await h();if(l&&process.env.BORG_OPENCODE==="1"){const o=`http://127.0.0.1:${$t(l.droneId)}`;await yt({serverUrl:o,directory:process.cwd(),droneLabel:l.droneLabel,cubeName:l.name}),kt(wt)}},healthBeat:()=>{qe({getActiveCube:h,getStreamConnected:()=>q().connected,getInboxPath:l=>I(l.cubeId,l.droneId),checkMonitor:l=>D(l,T(S())),isCodexRemoteWake:P,probeBridgeArmed:l=>_t({cubeId:l.cubeId,droneId:l.droneId}),probeOpenCodeDrone:()=>xt(),resolveAgentKind:U,resolveHostname:It,resolveVersion:x,getToken:Te,fetchImpl:globalThis.fetch.bind(globalThis)})}});const y=new X({name:"borg-mcp-client",version:x()},{capabilities:{tools:{},prompts:{}}}),j=Ve;y.setRequestHandler(te,async()=>{let l=null;try{const p=await h();p&&(l={roleName:p.roleName,roleClass:p.roleClass,isHumanSeat:p.isHumanSeat})}catch{l=null}return{tools:Ge(j,l)}}),y.setRequestHandler(ee,async l=>{let{name:p,arguments:o}=l.params;if(p==="borg_describe-tool"){const e=typeof o?.name=="string"?o.name:"",t=j.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(!bt(`tool ${p}`))return{content:[{type:"text",text:ft(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 L(e.sessionToken,e.apiUrl,{since:t}),s=F(e,n);s!==e&&await H(s);const i=q(),a=I(s.cubeId,s.droneId),c=T(S()),u=U()==="opencode"||P()?!0:D(a,c),f=tt(i,u)?et({inboxPath:a,monitorStateRoot:c,droneLabel:Be(n,s.droneLabel),cubeName:s.name}):"";let g="";try{const d=x(),b=Ke();if(d!=="unknown"&&b!=="unknown"&&b!==d){const[v,N,G]=d.split(".").map(Number),[k,M,K]=b.split(".").map(Number);(k>v||k===v&&M>N||k===v&&M===N&&K>G)&&(g=`## \u{1F504} borgmcp ${b} installed \u2014 run /mcp and reconnect (or restart Claude Code) to apply. Currently running ${d}.
2
+ import{Server as X}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Z}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as ee,ListToolsRequestSchema as te,ListPromptsRequestSchema as oe,GetPromptRequestSchema as re}from"@modelcontextprotocol/sdk/types.js";import{assertRoleMatches as ne}from"./role-match.js";import{getCubeInfo as se,getRoleInfo as A,getRoleInfoByName as ie,getRoster as ae,readLog as ce,appendLog as le,submitReport as de,fetchReports as ue,ackLogEntry as pe,recordDecision as me,listDecisions as be,regen as L,listCubes as fe,createCube as ge,updateCube as O,deleteCube as he,createRole as _e,updateRole as ye,patchRoleSection as C,patchTaxonomyClass as B,deleteRole as we,reassignDrone as xe,evictDrone as $e,getCube as x,checkSubscriptionStatus as ve,createBillingPortalSession as ke,createSubscription as Ee,syncRoles as Ce,applyTemplate as Re,whoami as Se,roleRationale as Ie,getValidToken as Te}from"./remote-client.js";import{startHealthBeatTick as qe}from"./health-beat.js";import{getTemplate as R,listTemplateNames as S,resolveCubeDirectiveForCreate as De,resolveCubeDirectiveForApply as Pe,resolveMessageTaxonomyForCreate as Ue}from"./templates.js";import{activeCubeWithFreshRegenIdentity as F,getActiveCube as g,setActiveCube as H,findProjectRoot as I,inboxPathForDrone as T}from"./cubes.js";import{monitorStateRootForWorktree as q}from"./inbox-monitor.js";import{addSessionStartHook as je,addUserPromptSubmitHook as Ne}from"./config-utils.js";import{humanAgo as W,formatLogEntryMarkdown as Me,formatRegenMarkdown as V,getDronePlaybook as Ae,getDronePlaybookChapter as Le,nullTaxonomyTip as Oe,regenWakePathDroneLabel as Be}from"./regen-format.js";import{startLogStream as Fe,getStreamStatus as D}from"./log-stream.js";import{isMcpReadinessProbe as He}from"./readiness-probe.js";import{runMcpStartupServices as We}from"./startup-services.js";import{TOOL_MANIFEST as Ve}from"./tool-manifest.js";import{DOCS_SECTIONS as ze,matchDocsSections as Ke,formatDocsIndex as Je}from"./docs-sections.js";import{renderRoleList as Qe}from"./list-roles-render.js";import{filterToolsForRole as Ye}from"./tool-scope.js";import{getPackageVersion as $,getOnDiskVersion as Ge,handleVersionFlag as Xe}from"./version.js";import{renderStreamStatus as Ze,checkInboxMonitorHealthy as P,formatWakePathPrefix as et,shouldShowWakePathWarning as tt}from"./stream-status.js";import{formatRoleAgentLabel as ot,renderRoster as rt}from"./roster-render.js";import{resolveDroneIdByLabel as nt,isUuidShape as st}from"./evict-drone.js";import{authRecoveryMessage as it}from"./auth-recovery.js";import{DroneEvictedError as at,DroneFrozenError as ct,formatEvictedToolResult as lt,formatFrozenToolResult as dt}from"./drone-lifecycle.js";import{classifyInSessionAssimilate as ut,reattachOnlyRefusal as pt,reattachFailureMessage as mt}from"./assimilate-guard.js";import{gateAllowsActivation as bt,borgSessionToolNotice as ft}from"./launch-gate.js";import{renderSyncRolesResult as gt}from"./sync-roles-render.js";import{initConsolePrefix as ht,consolePrefix as v}from"./console-prefix.js";import{isCodexRemoteWakeEnabled as _t,resolveSessionAgentKind as U,probeCodexBridgeArmed as yt}from"./codex-app-wake.js";import{connectOpenCodeDrone as wt,injectOpenCodeEntry as xt,probeOpenCodeDroneArmed as $t,computeOpenCodePort as vt}from"./opencode-drone.js";import{installBorgPlugin as kt}from"./opencode-plugin.js";import{setModuleInjectOpenCode as Et}from"./log-stream.js";import{lifecycleSignalForMessage as Ct,recordLifecycleLog as z,shouldSuppressLifecycleLog as Rt}from"./lifecycle-log-guard.js";import{normalizeDirectLogRecipients as St}from"./direct-log.js";import K from"open";import It from"os";function Tt(){try{const m=It.hostname();return m&&m.trim()?m.trim().slice(0,255):null}catch{return null}}async function J(m,y){return await Re(m,y.name)}async function h(){const m=await g();if(!m)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");return m}async function qt(){Xe();const m=He();await We(m,{sessionStartHook:()=>{je()},auditHook:()=>{Ne()},sseStream:()=>{Fe()},openCode:async()=>{kt();const l=await g();if(l&&process.env.BORG_OPENCODE==="1"){const o=`http://127.0.0.1:${vt(l.droneId)}`;await wt({serverUrl:o,directory:process.cwd(),droneLabel:l.droneLabel,cubeName:l.name}),Et(xt)}},healthBeat:()=>{qe({getActiveCube:g,getStreamConnected:()=>D().connected,getInboxPath:l=>T(l.cubeId,l.droneId),checkMonitor:l=>P(l,q(I())),isCodexRemoteWake:_t,probeBridgeArmed:l=>yt({cubeId:l.cubeId,droneId:l.droneId}),probeOpenCodeDrone:()=>$t(),resolveAgentKind:U,resolveHostname:Tt,resolveVersion:$,getToken:Te,fetchImpl:globalThis.fetch.bind(globalThis)})}});const y=new X({name:"borg-mcp-client",version:$()},{capabilities:{tools:{},prompts:{}}}),j=Ve;y.setRequestHandler(te,async()=>{let l=null;try{const p=await g();p&&(l={roleName:p.roleName,roleClass:p.roleClass,isHumanSeat:p.isHumanSeat})}catch{l=null}return{tools:Ye(j,l)}}),y.setRequestHandler(ee,async l=>{let{name:p,arguments:o}=l.params;if(p==="borg_describe-tool"){const e=typeof o?.name=="string"?o.name:"",t=j.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(!bt(`tool ${p}`))return{content:[{type:"text",text:ft(p)}],isError:!0};try{switch(p){case"borg_regen":{const e=await g();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 L(e.sessionToken,e.apiUrl,{since:t}),s=F(e,n);s!==e&&await H(s);const i=D(),a=T(s.cubeId,s.droneId),c=q(I()),f=U()==="claude"?P(a,c):!0,_=tt(i,f)?et({inboxPath:a,monitorStateRoot:c,droneLabel:Be(n,s.droneLabel),cubeName:s.name}):"";let u="";try{const b=$(),w=Ge();if(b!=="unknown"&&w!=="unknown"&&w!==b){const[k,N,Y]=b.split(".").map(Number),[E,M,G]=w.split(".").map(Number);(E>k||E===k&&M>N||E===k&&M===N&&G>Y)&&(u=`## \u{1F504} borgmcp ${w} installed \u2014 run /mcp and reconnect (or restart Claude Code) to apply. Currently running ${b}.
3
3
 
4
- `)}}catch{}return{content:[{type:"text",text:g+f+V(n,{mode:r})}]}}case"borg_subscribe":return{content:[{type:"text",text:`Complete your subscription at: ${await Ee()}`}]};case"borg_upgrade-subscription":{const e=await ke();try{await J(e)}catch{}return{content:[{type:"text",text:`Manage your Borg MCP subscription at: ${e}`}]}}case"borg_subscription_status":{const e=await ve();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}case"borg_open_dashboard":{const e="https://borgmcp.ai/dashboard";return await J(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=ut(t,e);if(r.kind!=="reattach")return{content:[{type:"text",text:pt(r,e)}],isError:!0};try{const n=await L(t.sessionToken,t.apiUrl,{}),s=F(t,n);return s!==t&&await H(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
- `)+V(n,{mode:"full"})}]}}catch(n){const s=mt(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:Le()}]};case"borg_docs":{const e=typeof o?.topic=="string"?o.topic.trim():"",t=e?Je(e):[],r=t.length>0?t:ze;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:u+_+V(n,{mode:r})}]}}case"borg_subscribe":return{content:[{type:"text",text:`Complete your subscription at: ${await Ee()}`}]};case"borg_upgrade-subscription":{const e=await ke();try{await K(e)}catch{}return{content:[{type:"text",text:`Manage your Borg MCP subscription at: ${e}`}]}}case"borg_subscription_status":{const e=await ve();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}case"borg_open_dashboard":{const e="https://borgmcp.ai/dashboard";return await K(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 g(),r=ut(t,e);if(r.kind!=="reattach")return{content:[{type:"text",text:pt(r,e)}],isError:!0};try{const n=await L(t.sessionToken,t.apiUrl,{}),s=F(t,n);return s!==t&&await H(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
+ `)+V(n,{mode:"full"})}]}}catch(n){const s=mt(n??{});if(!s)throw n;return{content:[{type:"text",text:s}],isError:!0}}}case"borg_version":return{content:[{type:"text",text:`borgmcp ${$()}`}]};case"borg_playbook":return{content:[{type:"text",text:Le()}]};case"borg_docs":{const e=typeof o?.topic=="string"?o.topic.trim():"",t=e?Ke(e):[],r=t.length>0?t:ze;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
- ${Qe(r)}`}]}}case"borg_whoami":{const e=await _(),t=await Se(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([se(e.sessionToken,e.apiUrl),A(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=Oe(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(Ae()),{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 ie(e.sessionToken,e.apiUrl,t);return ne(t,s),{content:[{type:"text",text:[`# Role: ${s.name}`,"",s.detailed_description||"_(no detailed description set)_"].join(`
7
+ ${Je(r)}`}]}}case"borg_whoami":{const e=await h(),t=await Se(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}case"borg_cube":{const e=await h(),[{cube:t,roles:r}]=await Promise.all([se(e.sessionToken,e.apiUrl),A(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=Oe(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})`:"",d=i.short_description||"_(no description)_";n.push(`- **${i.name}**${c} \u2014 ${d}`)}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(Ae()),{content:[{type:"text",text:n.join(`
8
+ `)}]}}case"borg_role":{const e=await h(),t=typeof o?.role=="string"?o.role.trim():"";if(t){const{role:s}=await ie(e.sessionToken,e.apiUrl,t);return ne(t,s),{content:[{type:"text",text:[`# Role: ${s.name}`,"",s.detailed_description||"_(no detailed description set)_"].join(`
9
9
  `)}]}}const{role:r}=await A(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 Ie(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 ae(e.sessionToken,e.apiUrl,t);return{content:[{type:"text",text:rt({cubeName:e.name,drones:r,roles:n,resolvedSince:s??null,humanAgo:W})}]}}case"borg_stream-status":{const e=q(),t=await h(),r=t?I(t.cubeId,t.droneId):null,n=t?T(S()):null,s=t&&U()==="opencode",i=t?s||P()?!0:D(r,n):null;let a="";e.runLoopHealth==="silent-inert"&&(a=`## \u26A0 SSE stream loop silent-inert \u2014 run /mcp and reconnect to restart
10
+ `)}]}}case"borg_role-rationale":{const e=await h(),t=typeof o?.role=="string"?o.role:"",r=typeof o?.section=="string"?o.section:"",n=await Ie(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 h(),t=typeof o?.since=="string"?o.since:void 0,{drones:r,roles:n,since:s}=await ae(e.sessionToken,e.apiUrl,t);return{content:[{type:"text",text:rt({cubeName:e.name,drones:r,roles:n,resolvedSince:s??null,humanAgo:W})}]}}case"borg_stream-status":{const e=D(),t=await g(),r=t?T(t.cubeId,t.droneId):null,n=t?q(I()):null,s=t&&U()!=="claude",i=t?s?!0:P(r,n):null;let a="";e.runLoopHealth==="silent-inert"&&(a=`## \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 c=Ze({status:e,inboxMonitorHealthy:i,inboxPath:r,monitorStateRoot:n,droneLabel:t?.droneLabel??null,cubeName:t?.name??null,humanAgo:W});return{content:[{type:"text",text:a+c}]}}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 ce(e.sessionToken,e.apiUrl,{since:t,limit:r,unreadOnly:n}),f=new Map;for(const b of i)f.set(b.id,b);const g=new Map;for(const b of a)g.set(b.id,b);const d=[];if(d.push(`# Activity log: ${e.name}`),d.push(""),!s.length)d.push("_(no entries)_");else for(const b of s)d.push(Me(b,f,g));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 c=="number"&&c>0&&(d.push(""),d.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:d.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(Et(e)){const d=await Ct(t,e);if(d.suppress)return await z(t,e),{content:[{type:"text",text:`Suppressed duplicate ${d.signal?.toUpperCase()} lifecycle log for ${t.droneLabel}; recent cube log already contains this signal.`}]}}const r=Object.prototype.hasOwnProperty.call(o??{},"to"),n=r?Rt(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 le(t.sessionToken,t.apiUrl,e,a);await z(t,e);const u=c.routing?.message?`
15
+ `);const c=Ze({status:e,inboxMonitorHealthy:i,inboxPath:r,monitorStateRoot:n,droneLabel:t?.droneLabel??null,cubeName:t?.name??null,humanAgo:W});return{content:[{type:"text",text:a+c}]}}case"borg_read-log":{const e=await h(),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:d}=await ce(e.sessionToken,e.apiUrl,{since:t,limit:r,unreadOnly:n}),f=new Map;for(const b of i)f.set(b.id,b);const _=new Map;for(const b of a)_.set(b.id,b);const u=[];if(u.push(`# Activity log: ${e.name}`),u.push(""),!s.length)u.push("_(no entries)_");else for(const b of s)u.push(Me(b,f,_));return d===!0?(u.push(""),u.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&&(u.push(""),u.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:u.join(`
16
+ `)}]}}case"borg_log":{const e=o?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await g();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");if(Ct(e)){const u=await Rt(t,e);if(u.suppress)return await z(t,e),{content:[{type:"text",text:`Suppressed duplicate ${u.signal?.toUpperCase()} lifecycle log for ${t.droneLabel}; recent cube log already contains this signal.`}]}}const r=Object.prototype.hasOwnProperty.call(o??{},"to"),n=r?St(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 le(t.sessionToken,t.apiUrl,e,a);await z(t,e);const d=c.routing?.message?`
17
17
  ${c.routing.message}`:"",f=c.unreachableRecipients?.length?`
18
- \u26A0 ${c.unreachableRecipients.length} directed recipient(s) currently unreachable (wake-path:deaf): ${c.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: ${c.entry.id})${u}${f}`}]}}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 de(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 ue();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}
18
+ \u26A0 ${c.unreachableRecipients.length} directed recipient(s) currently unreachable (wake-path:deaf): ${c.unreachableRecipients.map(u=>u.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})${d}${f}`}]}}case"borg_report-friction":{const e=o?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await g();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 de(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 ue();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
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=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 pe(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 me(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 be(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(`
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 h();return await pe(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 h(),{decision:s}=await me(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 h(),{decisions:r}=await be(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
26
  `)}]}}case"borg_list-cubes":{const{cubes:e}=await fe();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
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=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=C(r),!n))throw new Error(`Unknown template "${r}". Available: ${R().join(", ")}`);const s=De(t,n),i=Ue(void 0,n),a=await ge(e,s,{message_taxonomy:i});if(n){const u=await Q(a.id,n),f=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.${f} 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 O(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 B(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 B(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 he(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 _e(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}:{}}),f=[u.role_class==="queen"?"Queen":null,u.is_human_seat?"human-seat":null,u.is_default?"default":null].filter(Boolean).join(", "),g=f?` (${f})`:"";return{content:[{type:"text",text:`Created role **${u.name}**${g} (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 ye(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 E(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 E(e,{action:t,heading:r,body:i,after:a}))}else({role:n}=await E(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 we(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 xe(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(!st(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=nt(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 $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=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=ot(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}):
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=R(r),!n))throw new Error(`Unknown template "${r}". Available: ${S().join(", ")}`);const s=De(t,n),i=Ue(void 0,n),a=await ge(e,s,{message_taxonomy:i});if(n){const d=await J(a.id,n),f=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 ${d.created} role(s) created, ${d.updated} updated.${f} 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 O(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 B(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 B(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 he(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:d}=await _e(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}:{}}),f=[d.role_class==="queen"?"Queen":null,d.is_human_seat?"human-seat":null,d.is_default?"default":null].filter(Boolean).join(", "),_=f?` (${f})`:"";return{content:[{type:"text",text:`Created role **${d.name}**${_} (id: ${d.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 ye(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 C(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 C(e,{action:t,heading:r,body:i,after:a}))}else({role:n}=await C(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 we(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 xe(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(!st(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 x(r),a=nt(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 $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=o?.cube_id;if(!e)throw new Error("cube_id is required");const{drones:t,roles:r}=await x(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=ot(a?.name??"?",i.agent_kind),d=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}${d}`});return{content:[{type:"text",text:`Drones in cube ${e} (${t.length}):
33
33
 
34
34
  ${s.join(`
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:Ye(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 x(e);return{content:[{type:"text",text:Qe(t,e)}]}}case"borg_list-templates":return{content:[{type:"text",text:`Available templates:
36
36
 
37
- ${R().map(r=>{const n=C(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 Ce(e,t,r,n);return{content:[{type:"text",text:gt(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=C(t);if(!r)throw new Error(`Unknown template "${t}". Available: ${R().join(", ")}`);const n=await Q(e,r);let s="";const i=await w(e),a=Pe(i.cube_directive,r);return a!==null&&(await O(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 at)return{content:[{type:"text",text:lt(e.message)}],isError:!0};if(e instanceof ct)return{content:[{type:"text",text:dt(e.message)}],isError:!0};const t=it(e??{});return t?{content:[{type:"text",text:t}],isError:!0}:{content:[{type:"text",text:`Error: ${e.message}`}],isError:!0}}}),y.setRequestHandler(oe,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(re,async l=>{const{name:p}=l.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 Y=new Z;await y.connect(Y),await ht(),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`)}Tt().catch(m=>{console.error(`${$()}Fatal error:`,m),process.exit(1)});
37
+ ${S().map(r=>{const n=R(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 Ce(e,t,r,n);return{content:[{type:"text",text:gt(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=R(t);if(!r)throw new Error(`Unknown template "${t}". Available: ${S().join(", ")}`);const n=await J(e,r);let s="";const i=await x(e),a=Pe(i.cube_directive,r);return a!==null&&(await O(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 at)return{content:[{type:"text",text:lt(e.message)}],isError:!0};if(e instanceof ct)return{content:[{type:"text",text:dt(e.message)}],isError:!0};const t=it(e??{});return t?{content:[{type:"text",text:t}],isError:!0}:{content:[{type:"text",text:`Error: ${e.message}`}],isError:!0}}}),y.setRequestHandler(oe,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(re,async l=>{const{name:p}=l.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 Q=new Z;await y.connect(Q),await ht(),console.error(`${v()}\u25FC Borg MCP Client started`),console.error(`${v()}\u25FC Use borg_assimilate <cube-name> to join a cube as a drone`),console.error(`${v()}\u25FC Manage your cubes at https://borgmcp.ai/dashboard`)}qt().catch(m=>{console.error(`${v()}Fatal error:`,m),process.exit(1)});
@@ -4,13 +4,24 @@ interface ConnectDeps {
4
4
  droneLabel: string;
5
5
  cubeName: string;
6
6
  }
7
+ export interface OpenCodeLaunchKickoff {
8
+ prompt: string;
9
+ nonce: string;
10
+ }
11
+ /**
12
+ * Add a launch-unique identity to the OpenCode-only copy of the shared
13
+ * kickoff. The prompt is what OpenCode records as its first user message, so
14
+ * the launcher can later bind the MCP child to this precise launch instead of
15
+ * guessing from a repeated kickoff's text or timestamp.
16
+ */
17
+ export declare function createOpenCodeLaunchKickoff(kickoff: string, nonce?: string): OpenCodeLaunchKickoff;
7
18
  export declare function connectOpenCodeDrone(deps: ConnectDeps): Promise<void>;
8
19
  /**
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.
20
+ * Wait for the OpenCode HTTP server, then capture the session that received
21
+ * this launch's nonce-bearing `--prompt` kickoff. The binding survives the separate
22
+ * MCP-child process, which must never fall back to a newest-session heuristic.
12
23
  */
13
- export declare function injectInitialKickoff(_kickoffText: string): Promise<boolean>;
24
+ export declare function injectInitialKickoff(launch: OpenCodeLaunchKickoff): Promise<boolean>;
14
25
  /**
15
26
  * Inject a silent context entry (noReply) into our session.
16
27
  * Falls through silently — caller falls back to inbox write.
@@ -24,5 +35,7 @@ export declare function getOpenCodeConnectionState(): {
24
35
  totalEntriesInjected: number;
25
36
  };
26
37
  export declare function computeOpenCodePort(droneId: string, base?: number): number;
38
+ /** Test-only cleanup for module state and the local cross-process binding. */
39
+ export declare function __resetOpenCodeDroneForTests(): void;
27
40
  export {};
28
41
  //# sourceMappingURL=opencode-drone.d.ts.map
@@ -1,2 +1,4 @@
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};
1
+ import{appendFileSync as v,existsSync as A,readFileSync as T,renameSync as L,unlinkSync as I,writeFileSync as U}from"fs";import{createHash as x,randomUUID as E}from"crypto";import{join as h}from"path";import{tmpdir as S}from"os";const j=h(S(),"borg-opencode-drone.log");function a(e){const n=`[${new Date().toISOString()}] ${e}
2
+ `;try{v(j,n)}catch{}}let s=null;const b="borg-opencode-correlation:";function Q(e,n=E()){return{prompt:`${e}
3
+
4
+ <!-- ${b}${n} -->`,nonce:n}}const f=new Set;async function V(e){s={serverUrl:e.serverUrl,sessionId:null,sessionCreatedAt:null,knownRootSessionIds:[],directory:e.directory,droneLabel:e.droneLabel,cubeName:e.cubeName,connected:!0,totalEntriesInjected:0},a(`connected url=${e.serverUrl} dir=${e.directory}`)}function k(e){return`${s.serverUrl.replace(/\/+$/,"")}${e}${e.includes("?")?"&":"?"}directory=${encodeURIComponent(s.directory)}`}const g=1e4;async function y(e){const n=k(e),t=new AbortController,o=setTimeout(()=>t.abort(),g);try{const i=await fetch(n,{signal:t.signal}),r=await i.text();return{status:i.status,body:r}}finally{clearTimeout(o)}}async function _(e,n){const t=k(e),o=new AbortController,i=setTimeout(()=>o.abort(),g);try{const r=await fetch(t,{method:"POST",headers:{"Content-Type":"application/json"},signal:o.signal,body:JSON.stringify(n)}),c=await r.text();return{status:r.status,body:c}}finally{clearTimeout(i)}}async function m(){const{status:e,body:n}=await y("/session");if(e!==200)throw new Error(`OpenCode sessions request failed (${e})`);return JSON.parse(n)}async function C(e){try{const{status:n,body:t}=await y(`/session/${e}`);return n!==200?null:JSON.parse(t)}catch{return null}}async function P(e){const{status:n,body:t}=await y(`/session/${e}/message`);if(n!==200)throw new Error(`OpenCode session messages request failed (${n})`);return JSON.parse(t)}async function $(e,n){const{status:t}=await _(`/session/${e}/prompt_async`,n);return t===200||t===204}function p(){const e=s,n=[e.serverUrl,e.directory,e.cubeName,e.droneLabel].join("\0"),t=x("sha256").update(n).digest("hex").slice(0,24),o=h(S(),`borg-opencode-session-${t}.json`);return f.add(o),o}function D(e){const n=s;return e.version===2&&e.serverUrl===n.serverUrl&&e.directory===n.directory&&e.droneLabel===n.droneLabel&&e.cubeName===n.cubeName&&typeof e.sessionId=="string"&&typeof e.sessionCreatedAt=="number"&&Array.isArray(e.knownRootSessionIds)&&e.knownRootSessionIds.every(t=>typeof t=="string")}function F(){try{const e=p();if(!A(e))return null;const n=JSON.parse(T(e,"utf-8"));return D(n)?n:null}catch{return null}}function u(){if(!s)return;const e=p();s.sessionId=null,s.sessionCreatedAt=null,s.knownRootSessionIds=[];try{I(e)}catch{}}function d(e,n){const t=s,o={version:2,sessionId:e.id,sessionCreatedAt:e.time.created,knownRootSessionIds:n,serverUrl:t.serverUrl,directory:t.directory,droneLabel:t.droneLabel,cubeName:t.cubeName};t.sessionId=o.sessionId,t.sessionCreatedAt=o.sessionCreatedAt,t.knownRootSessionIds=o.knownRootSessionIds;try{const i=p(),r=`${i}.${process.pid}.tmp`;U(r,JSON.stringify(o),{mode:384}),L(r,i)}catch(i){a(`session binding write failed: ${i}`)}}function O(){if(!s)return null;if(s.sessionId&&s.sessionCreatedAt!==null)return{version:2,sessionId:s.sessionId,sessionCreatedAt:s.sessionCreatedAt,knownRootSessionIds:s.knownRootSessionIds,serverUrl:s.serverUrl,directory:s.directory,droneLabel:s.droneLabel,cubeName:s.cubeName};const e=F();return e?(s.sessionId=e.sessionId,s.sessionCreatedAt=e.sessionCreatedAt,s.knownRootSessionIds=e.knownRootSessionIds,e):null}function R(e,n){return e.id===n.sessionId&&e.directory===s.directory}function N(e){return!e.parentID}async function w(e){try{const t=(await m()).filter(r=>r.directory===s.directory&&N(r)),o=t.filter(r=>!e.includes(r.id));return o.length===0?null:{session:o.reduce((r,c)=>r.time.created>c.time.created?r:c),knownRootSessionIds:t.map(r=>r.id)}}catch{return null}}function M(e,n){let t=null;for(const o of e){if(o.info?.role&&o.info.role!=="user"||!o.parts?.some(c=>c.type==="text"&&c.text?.includes(`${b}${n}`)))continue;const r=o.info?.time?.created??0;t=t===null?r:Math.max(t,r)}return t}async function J(e){try{const n=(await m()).filter(c=>c.directory===s.directory),t=n.filter(N).map(c=>c.id),i=(await Promise.all(n.map(async c=>{try{const l=M(await P(c.id),e);return l===null?null:{session:c,messageTime:l}}catch{return null}}))).filter(c=>c!==null);return i.length===0?null:{session:i.reduce((c,l)=>l.messageTime>c.messageTime?l:c).session,knownRootSessionIds:t}}catch{return null}}async function B(){const e=O();if(!e)return null;const n=await C(e.sessionId);if(!n||!R(n,e)){u();const o=await w(e.knownRootSessionIds);return o?(d(o.session,o.knownRootSessionIds),o.session):null}const t=await w(e.knownRootSessionIds);return t?(d(t.session,t.knownRootSessionIds),t.session):n}async function H(e){const n=await w(e);return n?(d(n.session,n.knownRootSessionIds),n.session):null}async function W(e){if(!s?.connected)return a("kickoff: not connected"),!1;try{for(let n=0;n<30;n++){try{await m(),a(`kickoff: server ready (attempt ${n+1})`);break}catch{}await new Promise(t=>setTimeout(t,1e3))}for(let n=0;n<30;n++){const t=await J(e.nonce);if(t)return d(t.session,t.knownRootSessionIds),a(`kickoff: bound session ${t.session.id.slice(0,8)}\u2026`),!0;await new Promise(o=>setTimeout(o,1e3))}return a("kickoff: no session found"),!1}catch(n){return a(`kickoff error: ${n}`),!1}}async function X(e){if(!s?.connected)return!1;try{const n=await B();if(!n)return!1;const t={parts:[{type:"text",text:e}]};if(await $(n.id,t))return s.totalEntriesInjected++,!0;const o=[...s.knownRootSessionIds];u();const i=await H(o);return i?await $(i.id,t)?(s.totalEntriesInjected++,!0):(u(),!1):!1}catch(n){return a(`entry error: ${n}`),u(),!1}}async function Y(){if(!s?.connected)return null;const e=O();if(!e)return!1;try{const n=await C(e.sessionId);return n&&R(n,e)?!0:(u(),!1)}catch{return u(),!1}}function Z(){s=null}function ee(){return{connected:s?.connected??!1,sessionId:s?.sessionId??null,totalEntriesInjected:s?.totalEntriesInjected??0}}function ne(e,n=14096){let t=0;for(let o=0;o<e.length;o++){const i=e.charCodeAt(o);t=(t<<5)-t+i,t|=0}return n+Math.abs(t)%1024}function te(){s=null;for(const e of f)try{I(e)}catch{}f.clear()}export{te as __resetOpenCodeDroneForTests,ne as computeOpenCodePort,V as connectOpenCodeDrone,Q as createOpenCodeLaunchKickoff,Z as disconnectOpenCodeDrone,ee as getOpenCodeConnectionState,W as injectInitialKickoff,X as injectOpenCodeEntry,Y as probeOpenCodeDroneArmed};
@@ -24,7 +24,7 @@ export interface RosterDrone {
24
24
  /**
25
25
  * gh#370 — which AI agent is running this drone. Null for drones that
26
26
  * joined before the column existed or via a launcher path that didn't
27
- * forward the kind. Rendered as `(Role, kind)` when present.
27
+ * forward the kind. This is the agent CLI, not a model descriptor.
28
28
  */
29
29
  agent_kind?: 'claude' | 'codex' | 'opencode' | null;
30
30
  regen_count?: number | null;
@@ -32,7 +32,10 @@ export interface RosterDrone {
32
32
  wake_path_alert_class?: 'dead' | 'post-blocked' | 'presumed-dead' | 'systemic-post-block' | 'wake-path-deaf' | 'independent' | null;
33
33
  /**
34
34
  * Model descriptor (e.g., "claude:claude-opus-4-8" or "ollama:qwen3-coder-next:q4_K_M").
35
- * Null for drones with no explicit model (use cube/role default).
35
+ * This is the configured launch setting captured at assimilation, not a live
36
+ * runtime model observation. It may remain after a later CLI relaunch.
37
+ * Null is not enough information to distinguish an inherited default from
38
+ * a legacy/missing report, so renderers must not infer an agent CLI from it.
36
39
  */
37
40
  model?: string | null;
38
41
  }
@@ -1,3 +1,3 @@
1
- import{formatDroneAddressToken as f}from"./drone-address.js";const m={degraded:"unverified",deaf:"unresponsive"};function g(t,o){return o?`${t}, ${o}`:t}function M(t){const{cubeName:o,drones:a,roles:k,resolvedSince:s,humanAgo:r}=t,l=new Map;for(const e of k)l.set(e.id,e);const n=[];if(n.push(`# Drones in cube: ${o}`),n.push(""),s&&(n.push(`_Liveness probe since ${s} (${r(s)}). \`awake\` = drone posted to the cube log after that point._`),n.push("")),!a.length)return n.push("_(no drones connected)_"),n.join(`
2
- `);for(const e of a){const h=l.get(e.role_id)?.name??"unknown",c=g(h,e.agent_kind),d=e.id?` ${f(e.id)}`:"",$=r(e.last_seen),i=e.model?` \xB7 \`${e.model}\``:"",p=e.wake_path&&e.wake_path!=="live"?` \xB7 \`wake-path:${m[e.wake_path]??e.wake_path}\``:"",u=e.wake_path_alert_class&&e.wake_path_alert_class!=="independent"?` \xB7 \`wake-path-class:${e.wake_path_alert_class}\``:"",_=typeof e.regen_count=="number"?` \xB7 \`regen-count:${e.regen_count}\``:"";if(s){const w=e.seen_since===!0?"`awake`":"`stale`";n.push(`- **${e.label}**${d} (${c}) \u2014 last seen ${$} \xB7 ${w}${_}${p}${u}${i}`)}else n.push(`- **${e.label}**${d} (${c}) \u2014 last seen ${$}${_}${p}${u}${i}`)}return n.join(`
3
- `)}export{g as formatRoleAgentLabel,M as renderRoster};
1
+ import{formatDroneAddressToken as w}from"./drone-address.js";const m={degraded:"unverified",deaf:"unresponsive"};function g(a,o){return`Role: ${a} \xB7 Agent CLI: ${o==="claude"?"Claude Code":o==="codex"?"Codex":o==="opencode"?"OpenCode":"unknown"}`}function v(a){const{cubeName:o,drones:s,roles:k,resolvedSince:t,humanAgo:r}=a,l=new Map;for(const e of k)l.set(e.id,e);const n=[];if(n.push(`# Drones in cube: ${o}`),n.push(""),t&&(n.push(`_Liveness probe since ${t} (${r(t)}). \`awake\` = drone posted to the cube log after that point._`),n.push("")),!s.length)return n.push("_(no drones connected)_"),n.join(`
2
+ `);for(const e of s){const h=l.get(e.role_id)?.name??"unknown",c=g(h,e.agent_kind),d=e.id?` ${w(e.id)}`:"",u=r(e.last_seen),i=e.model?` \xB7 \`Configured model: ${e.model}\``:" \xB7 `Configured model: not reported`",p=e.wake_path&&e.wake_path!=="live"?` \xB7 \`wake-path:${m[e.wake_path]??e.wake_path}\``:"",$=e.wake_path_alert_class&&e.wake_path_alert_class!=="independent"?` \xB7 \`wake-path-class:${e.wake_path_alert_class}\``:"",_=typeof e.regen_count=="number"?` \xB7 \`regen-count:${e.regen_count}\``:"";if(t){const f=e.seen_since===!0?"`awake`":"`stale`";n.push(`- **${e.label}**${d} (${c}) \u2014 last seen ${u} \xB7 ${f}${_}${p}${$}${i}`)}else n.push(`- **${e.label}**${d} (${c}) \u2014 last seen ${u}${_}${p}${$}${i}`)}return n.join(`
3
+ `)}export{g as formatRoleAgentLabel,v as renderRoster};
package/dist/setup.js CHANGED
@@ -1,35 +1,35 @@
1
1
  #!/usr/bin/env node
2
- import x from"prompts";import e from"chalk";import v from"open";import p from"which";import{authenticateWithGoogle as A}from"./auth.js";import{checkSubscriptionStatus as i,createSubscription as M,probeSession as H}from"./remote-client.js";import{setupActionForSession as R}from"./setup-action.js";import{confirmConfigMutation as O,configMutationTargets as U,formatConfigMutationDisclosure as D,parseYesFlag as T,setupMutationPending as I}from"./setup-confirm.js";import{retrySubscriptionCheck as F}from"./subscription-retry.js";import{addMcpServer as L,addUserPromptSubmitHook as q,addCodexMcpServer as Y,addCodexSessionStartHook as N,addCodexUserPromptSubmitHook as W,addOpenCodeMcpServer as j,isMcpServerConfigured as B,isCodexMcpServerConfigured as E,isOpenCodeMcpServerConfigured as G,isSessionStartHookRegistered as z,isUserPromptSubmitHookRegistered as Q,isCodexSessionStartHookRegistered as V,isCodexUserPromptSubmitHookRegistered as J,removeSessionStartHook as K}from"./config-utils.js";import{handleVersionFlag as X}from"./version.js";import{initDebugFromArgv as Z}from"./debug.js";async function _(){Z(process.argv),X(),console.log(e.blue.bold(`
2
+ import w from"prompts";import e from"chalk";import x from"open";import p from"which";import{authenticateWithGoogle as H}from"./auth.js";import{checkSubscriptionStatus as i,createSubscription as M,probeSession as R}from"./remote-client.js";import{setupActionForSession as U}from"./setup-action.js";import{confirmConfigMutation as O,configMutationTargets as D,formatConfigMutationDisclosure as T,parseYesFlag as I,setupMutationPending as L}from"./setup-confirm.js";import{retrySubscriptionCheck as S}from"./subscription-retry.js";import{addUserPromptSubmitHook as q,addCodexSessionStartHook as Y,addCodexUserPromptSubmitHook as N,isMcpServerConfigured as W,isCodexMcpServerConfigured as j,isOpenCodeMcpServerConfigured as B,isSessionStartHookRegistered as E,isUserPromptSubmitHookRegistered as G,isCodexSessionStartHookRegistered as z,isCodexUserPromptSubmitHookRegistered as Q,removeSessionStartHook as V}from"./config-utils.js";import{ensureCliMcpConfigured as h}from"./ensure-mcp-config.js";import{handleVersionFlag as J}from"./version.js";import{initDebugFromArgv as K}from"./debug.js";async function X(){K(process.argv),J(),console.log(e.blue.bold(`
3
3
  \u25FC Borg MCP Setup Wizard \u25FC`));const a=process.argv.includes("--no-browser")||process.argv.includes("--device");let t=null,n=null,s=null;try{t=p.sync("claude")}catch{}try{n=p.sync("codex")}catch{}try{s=p.sync("opencode")}catch{}t&&console.log(e.gray(`Found Claude CLI: ${t}`)),n&&console.log(e.gray(`Found Codex CLI: ${n}`)),s&&console.log(e.gray(`Found OpenCode CLI: ${s}`)),(t||n||s)&&console.log(""),!t&&!n&&!s&&(console.error(e.red(`\u25FC No supported agent CLI found
4
4
  `)),console.error(e.yellow("Please install Claude Code, Codex, or OpenCode first:")),console.error(e.gray(" Claude Code: https://claude.ai/download")),console.error(e.gray(" Codex: https://developers.openai.com/codex")),console.error(e.gray(` OpenCode: https://opencode.ai
5
- `)),process.exit(1)),console.log(e.blue("\u25FC Agent CLI Integration"));const $=T(process.argv),l=t!==null,d=n!==null,h=s!==null,m=B(),b=E(),f=G(),y=l&&z(),C=l&&!Q(),k=d&&!V(),w=d&&!J();if(I({claude:l,codex:d,opencode:h,claudeMcpConfigured:m,codexMcpConfigured:b,opencodeMcpConfigured:f,claudeHookPending:y||C,codexHookPending:k||w})&&(console.log(D(U({claude:l,codex:d,opencode:h}))),await O({isTTY:process.stdin.isTTY===!0,yes:$,confirm:async()=>{const{proceed:o}=await x({type:"confirm",name:"proceed",message:"Continue with these changes?",initial:!0});return o===!0}})==="abort"&&(console.log(e.yellow(`
5
+ `)),process.exit(1)),console.log(e.blue("\u25FC Agent CLI Integration"));const v=I(process.argv),l=t!==null,g=n!==null,m=s!==null,P=W(),$=j(),A=B(),b=l&&E(),f=l&&!G(),y=g&&!z(),C=g&&!Q();if(L({claude:l,codex:g,opencode:m,claudeMcpConfigured:P,codexMcpConfigured:$,opencodeMcpConfigured:A,claudeHookPending:b||f,codexHookPending:y||C})&&(console.log(T(D({claude:l,codex:g,opencode:m}))),await O({isTTY:process.stdin.isTTY===!0,yes:v,confirm:async()=>{const{proceed:o}=await w({type:"confirm",name:"proceed",message:"Continue with these changes?",initial:!0});return o===!0}})==="abort"&&(console.log(e.yellow(`
6
6
  \u25FC Setup cancelled \u2014 no changes made.
7
- `)),process.exit(0))),console.log(""),t)try{m||L(),y&&K(),C&&q(),console.log(e.green("\u25FC borg configured for Claude Code"))}catch(r){console.error(e.red(`
7
+ `)),process.exit(0))),console.log(""),t)try{h("claude"),b&&V(),f&&q(),console.log(e.green("\u25FC borg configured for Claude Code"))}catch(r){console.error(e.red(`
8
8
  \u25FC Failed to configure Claude Code: ${r.message}
9
- `)),process.exit(1)}if(n)try{b||Y(),k&&N(),w&&W(),console.log(e.green("\u25FC borg configured for Codex"))}catch(r){console.error(e.red(`
9
+ `)),process.exit(1)}if(n)try{h("codex"),y&&Y(),C&&N(),console.log(e.green("\u25FC borg configured for Codex"))}catch(r){console.error(e.red(`
10
10
  \u25FC Failed to configure Codex: ${r.message}
11
- `)),process.exit(1)}if(s)try{f||j(),console.log(e.green("\u25FC borg configured for OpenCode"))}catch(r){console.error(e.red(`
11
+ `)),process.exit(1)}if(s)try{h("opencode"),console.log(e.green("\u25FC borg configured for OpenCode"))}catch(r){console.error(e.red(`
12
12
  \u25FC Failed to configure OpenCode: ${r.message}
13
- `)),process.exit(1)}console.log(""),console.log(e.blue("\u25FC Google Authentication"));const S=R(await H());if(S==="skip")console.log(e.green(`\u25FC Already signed in
14
- `));else if(S==="retry")console.error(e.yellow(`
15
- \u25FC Could not reach Google to verify your session (network issue).`)),console.error(e.yellow("Re-run `borg setup` when your connection is back.\n")),process.exit(1);else try{await A(a?{noBrowser:!0}:void 0)}catch(r){console.error(e.red(`
13
+ `)),process.exit(1)}console.log(""),console.log(e.blue("\u25FC Google Authentication"));const k=U(await R());if(k==="skip")console.log(e.green(`\u25FC Already signed in
14
+ `));else if(k==="retry")console.error(e.yellow(`
15
+ \u25FC Could not reach Google to verify your session (network issue).`)),console.error(e.yellow("Re-run `borg setup` when your connection is back.\n")),process.exit(1);else try{await H(a?{noBrowser:!0}:void 0)}catch(r){console.error(e.red(`
16
16
  \u25FC Authentication failed: ${r.message}
17
17
  `)),console.error(e.yellow("Re-run `borg setup` to try again.\n")),process.exit(1)}console.log(e.blue("\u25FC Subscription Check"));let c;try{c=await i()}catch(r){console.error(e.yellow(`
18
18
  \u25FC Subscription check failed: ${r.message}`)),console.error(e.gray(`\u25FC Retrying before falling back to the Free tier...
19
- `)),c={hasAccess:!1}}if(c=await F(c,{check:i,sleep:r=>new Promise(o=>setTimeout(o,r)),onRetry:(r,o)=>console.log(e.gray(`\u25FC Checking subscription... (attempt ${r}/${o})`))}),c.hasAccess)if(console.log(e.green("\u25FC Active subscription found")),c.expiresAt){const r=new Date(c.expiresAt);console.log(e.gray(` Expires: ${r.toLocaleDateString()}
19
+ `)),c={hasAccess:!1}}if(c=await S(c,{check:i,sleep:r=>new Promise(o=>setTimeout(o,r)),onRetry:(r,o)=>console.log(e.gray(`\u25FC Checking subscription... (attempt ${r}/${o})`))}),c.hasAccess)if(console.log(e.green("\u25FC Active subscription found")),c.expiresAt){const r=new Date(c.expiresAt);console.log(e.gray(` Expires: ${r.toLocaleDateString()}
20
20
  `))}else console.log("");else{console.log(e.green("\u25FC You're on the Free tier \u2014 permanent, no card needed: 1 cube + 3 agent sessions + 100 req/hr.")),console.log(e.gray(`\u25FC Start using borgmcp right now. Upgrade any time: $1/month per cube, each cube adds 8 pooled agent sessions + 1000 req/hr.
21
- `));const{subscribeMethod:r}=await x({type:"select",name:"subscribeMethod",message:"You're ready on the Free tier. Want to do more?",choices:[{title:"\u25FC Continue on the Free tier (recommended)",value:"skip",description:"Start now \u2014 1 cube, 3 agent sessions, 100 req/hr. No payment required."},{title:"\u25FC Upgrade to Cube tier \u2014 $1/month per cube",value:"web",description:"Each cube adds 8 pooled agent sessions + 1000 req/hr. Opens the subscribe page in your browser."},{title:"\u25FC Quick Stripe checkout",value:"stripe",description:"Fast upgrade checkout in the browser"},{title:"\u25FC I already subscribed \u2014 re-check",value:"recheck",description:"Re-check now \u2014 a just-completed subscription can take a moment to activate"}]});switch(r===void 0&&console.log(e.yellow(`
21
+ `));const{subscribeMethod:r}=await w({type:"select",name:"subscribeMethod",message:"You're ready on the Free tier. Want to do more?",choices:[{title:"\u25FC Continue on the Free tier (recommended)",value:"skip",description:"Start now \u2014 1 cube, 3 agent sessions, 100 req/hr. No payment required."},{title:"\u25FC Upgrade to Cube tier \u2014 $1/month per cube",value:"web",description:"Each cube adds 8 pooled agent sessions + 1000 req/hr. Opens the subscribe page in your browser."},{title:"\u25FC Quick Stripe checkout",value:"stripe",description:"Fast upgrade checkout in the browser"},{title:"\u25FC I already subscribed \u2014 re-check",value:"recheck",description:"Re-check now \u2014 a just-completed subscription can take a moment to activate"}]});switch(r===void 0&&console.log(e.yellow(`
22
22
  \u25FC No subscription option selected \u2014 continuing on the Free tier.
23
23
  `)),r){case"web":console.log(e.blue(`
24
- \u25FC Opening: https://borgmcp.ai/subscribe`));try{await v("https://borgmcp.ai/subscribe"),console.log(e.gray(`\u25FC Waiting for subscription (checking every 5s for 2 min)...
25
- `)),await P()}catch(o){console.error(e.yellow(`
24
+ \u25FC Opening: https://borgmcp.ai/subscribe`));try{await x("https://borgmcp.ai/subscribe"),console.log(e.gray(`\u25FC Waiting for subscription (checking every 5s for 2 min)...
25
+ `)),await F()}catch(o){console.error(e.yellow(`
26
26
  \u25FC ${o.message}`)),console.log(e.green(`\u25FC Continuing on the Free tier. Upgrade any time from https://borgmcp.ai/subscribe.
27
27
  `))}break;case"stripe":try{const o=await M();console.log(e.blue(`
28
- \u25FC Opening Stripe: ${o}`)),await v(o),console.log(e.gray(`\u25FC Waiting for subscription...
29
- `)),await P()}catch(o){console.error(e.red(`
28
+ \u25FC Opening Stripe: ${o}`)),await x(o),console.log(e.gray(`\u25FC Waiting for subscription...
29
+ `)),await F()}catch(o){console.error(e.red(`
30
30
  \u25FC Failed to create checkout: ${o.message}
31
31
  `)),console.log(e.green(`\u25FC Continuing on the Free tier. Upgrade any time from https://borgmcp.ai/subscribe.
32
- `))}break;case"recheck":try{let o;try{o=await i()}catch{o={hasAccess:!1}}o=await F(o,{check:i,sleep:g=>new Promise(u=>setTimeout(u,g)),onRetry:(g,u)=>console.log(e.gray(`\u25FC Re-checking subscription... (attempt ${g}/${u})`))}),o.hasAccess?console.log(e.green(`
32
+ `))}break;case"recheck":try{let o;try{o=await i()}catch{o={hasAccess:!1}}o=await S(o,{check:i,sleep:d=>new Promise(u=>setTimeout(u,d)),onRetry:(d,u)=>console.log(e.gray(`\u25FC Re-checking subscription... (attempt ${d}/${u})`))}),o.hasAccess?console.log(e.green(`
33
33
  \u25FC Subscription found!
34
34
  `)):console.log(e.yellow(`
35
35
  \u25FC No subscription found \u2014 continuing on the Free tier.
@@ -41,7 +41,7 @@ import x from"prompts";import e from"chalk";import v from"open";import p from"wh
41
41
  `));break}}console.log(e.green.bold(`Setup complete!
42
42
  `)),console.log(e.yellow(`\u{1F504} Restart Claude Code / Codex / OpenCode (or open a new session) for the changes to take effect.
43
43
  `)),console.log(e.gray("\u25FC Next steps:")),console.log(e.gray('1. cd into your project, then run "borg assimilate" to join a cube')),console.log(e.gray(" (this creates/joins the cube and launches your agent)")),console.log(e.gray(`2. Manage cubes and subscription at https://borgmcp.ai/dashboard
44
- `))}async function P(){for(let t=0;t<24;t++){await new Promise(n=>setTimeout(n,5e3));try{if((await i()).hasAccess){console.log(e.green(`\u25FC Subscription activated!
45
- `));return}}catch{}}throw new Error("Timeout - Run setup again after subscribing")}_().catch(a=>{console.error(e.red(`
44
+ `))}async function F(){for(let t=0;t<24;t++){await new Promise(n=>setTimeout(n,5e3));try{if((await i()).hasAccess){console.log(e.green(`\u25FC Subscription activated!
45
+ `));return}}catch{}}throw new Error("Timeout - Run setup again after subscribing")}X().catch(a=>{console.error(e.red(`
46
46
  \u25FC Setup failed: ${a.message}
47
47
  `)),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "borgmcp",
3
- "version": "1.1.8",
3
+ "version": "1.1.11",
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",