borgmcp 1.1.9 → 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.
@@ -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 ge}from"./assimilate-welcome.js";import{shellEscape as he}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 xe}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as Se}from"./launch-gate.js";import{codexAgentKindConfigArgs as Ce,codexRemoteWakeConfigArgs as Re,withAgentRuntimeEnv as _e}from"./agent-runtime.js";import{inboxPathForDrone as Ee}from"./cubes.js";import{monitorStateRootForWorktree as Ae}from"./inbox-monitor.js";import{resolveLaunchEnv as Ie,resolveOllamaBaseUrl as Ne,parseModel as Pe}from"./model-presets.js";import{unlinkSync as Le}from"node:fs";import{gcOrphanInboxesForCube as Te,defaultListInboxLogs as De,defaultInboxLivenessDeps as Ue,isInboxLive as Oe,ORPHAN_INBOX_STALE_MS as We}from"./gc-orphan-inboxes.js";import{installBorgPlugin as je}from"./opencode-plugin.js";import{computeOpenCodePort as Me,connectOpenCodeDrone as Be,injectInitialKickoff as He}from"./opencode-drone.js";async function wt(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,P;if(N)u=await e.getCube(a.apiUrl,a.token,N.id),P=!1;else{let t;if(r.flags.template)t=r.flags.template;else if(r.flags.noTemplate)t=void 0;else if(e.isTTY())if(r.flags.yes)t="starter";else{const n=await e.listTemplates(a.apiUrl,a.token),c=["First drone joining a new cube. Apply a template?"];n.forEach((v,$)=>{const A=$===0?" (default)":"";c.push(` ${$+1}) ${v.name}${A} \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}),P=!0}let h;if(r.role!==void 0){if(h=ce(u.roles,r.role),!h){const t=u.roles.map(f=>f.name).join(", "),n=Ge(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(h=ue(u.roles,{isFirstDrone:P,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&&r.flags.here)if(x.cubeId===u.id)S=x.droneId;else return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
18
- `),1;const K=r.flags.worktree!==void 0||x!==null&&!r.flags.here,ee=S??x?.droneId??null,L=K?null:await e.getLaunchModel(u.id,o,ee),b=r.flags.model??L?.model??h.default_model??null,T=Ne(process.env,b!=null&&b===L?.model?L?.ollamaBaseUrl:void 0);if(b){const t=await e.checkModelReachable(b,e.fetch,T);if(!t.ok)return e.stderr(`${t.message}
19
- `),1}const m=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${u.name}' as ${h.name}\u2026
20
- `);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 xe&&S!=null)return e.stderr(`seat evicted \u2014 this worktree's saved seat was evicted from the cube. Re-assimilate fresh from a terminal, or remove this worktree.
21
- `),1;const n=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${n}
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}
22
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)
23
24
  `):k.id!==h.id&&e.stderr(`Note: your invite didn't grant the "${h.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.
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 A=e.homedir();let g=X(A,v,$),p=F(R(g),v),q=2;for(;e.pathExists(g)||Ye(e,o,g)||Q(e.runSync,o,p)&&!$e(e.runSync,o,p,n);)g=X(A,v,$,q),p=F(R(g),v),q++;e.mkdirp(J(g));const z=Q(e.runSync,o,p)?e.runSync("git",["worktree","add",g,p],o):e.runSync("git",["worktree","add","-b",p,g,n],o);if(z.status!==0)return e.stderr(`git worktree add failed: ${Z(z.stderr)}
30
- `),1;e.stderr(`spawned sibling worktree at ${g} on branch ${p} (${n}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
31
- `),e.chdir(g),e.stderr(Fe(g,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(),D=e.findProjectRoot(d),U=Ae(D);try{const t=Ue(),n=J(Ee(s.cube_id,s.drone_id));Te({cubeInboxDir:n,selfDroneId:s.drone_id,deps:{listInboxLogs:De,isLive:c=>Oe(c,t,U),droneState:()=>"absent",unlink:c=>Le(c),now:t.now,staleMs:We},monitorStateRoot:U})}catch{}e.setTerminalTitle(s.drone_label,u.name);const re=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(ge(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(Ke(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),O=m==="codex"?`borg-wake-${le()}`:null,oe=ke(m,m==="claude"?ne:null,m==="claude"?U:null);let W,Y=[],C,E=null,j=null;b?await e.setLaunchModel(s.cube_id,D,{model:b,ollamaBaseUrl:Pe(b).kind==="ollama"?T:null}):await e.clearLaunchModel(s.cube_id,D);const G=Ie(b,T),M={..._e(process.env,m),...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
- `),W="\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."):W="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),E=pe(t.args),j=t.server?.cleanup??null}const B=be({cli:m,codexWakeNonce:O,monitorClause:oe,codexWakePathClause:W});let H;C=[B],m==="codex"?C=[...Se(),...Ce(),...Re(E!==null),...Y,...we(C,d)]:m==="opencode"&&(H=Me(s.drone_id),je(),C=[d,"--port",String(H),"--auto","--prompt",B]);const ie=e.exec(m,C,d,M);if(m==="codex"&&E&&O&&ye({deps:e,cubeId:s.cube_id,droneId:s.drone_id,socketPath:E,cwd:d,previewNeedle:O,launchedAtSeconds:Math.floor(Date.now()/1e3)}),m==="opencode"){const t=`http://127.0.0.1:${H}`;Be({serverUrl:t,directory:d,droneLabel:s.drone_label,cubeName:i??"borg"}).then(()=>He(B)).catch(()=>{})}const ae=await ie;if(j)try{j()}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 ${he(w)}
43
- `),ae}function Fe(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 Ke(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 Ye(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 Ge(r,e){if(e.length===0)return null;const a=r.toLowerCase();let o=null;for(const i of e){const l=qe(a,i.toLowerCase());l<=2&&(o===null||l<o.distance)&&(o={name:i,distance:l})}return o?o.name:null}function qe(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{wt as runAssimilate,Z as safeStderr,Ge 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 W}from"child_process";import{randomUUID as _}from"node:crypto";import{basename as G}from"node:path";import{createInterface as U}from"node:readline/promises";import i from"chalk";import{findProjectRoot as b,getActiveCube as Y,getLaunchModel as K,inboxPathForDrone as V,setCodexWakeTarget as j,pruneDeadCodexWakeTargets as q}from"./cubes.js";import{monitorStateRootForWorktree as X}from"./inbox-monitor.js";import{applyOllamaLaunchEnv as z,checkModelReachable as J}from"./model-presets.js";import{handleVersionFlag as Q,getPackageVersion as w}from"./version.js";import{isHelpFlag as L,setupHelpText as Z,topLevelHelpText as ee,assimilateHelpText as re}from"./cli-help.js";import{runSpawn as oe}from"./spawn.js";import{buildClaudeLaunchArgs as se}from"./claude-launch-args.js";import{parseSyncArgs as te,runSync as ie}from"./sync.js";import{parseCleanupArgs as ne,runCleanup as ae}from"./cleanup-cmd.js";import{parseAssimilateArgs as ce}from"./parse-assimilate-args.js";import{runAssimilate as le}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as de}from"./assimilate-deps.js";import{parseLaunchAllArgs as O}from"./parse-launch-all-args.js";import{unknownSubcommand as pe}from"./unknown-subcommand.js";import{runLaunchAll as D}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as k}from"./launch-all-deps.js";import{discoverDroneCandidates as ue}from"./launch-all-discovery.js";import{explicitCliLaunchHint as me,runBareLaunchMenu as fe,shouldResolveExplicitCliLaunchHintTargets as ge,shouldShowLaunchMenu as he}from"./bare-launch-menu.js";import{setTerminalTitle as we}from"./terminal-title.js";import{initConsolePrefix as Ce,consolePrefix as t}from"./console-prefix.js";import{initDebugFromArgv as xe}from"./debug.js";import{fetchLatestBorgmcpVersion as ve,compareVersionsForStaleness as be}from"./stale-version-check.js";import{defaultCliChoiceDeps as ke,detectCliAvailability as $,installedCliNames as E,parseCliFlag as $e,resolveCliChoice as Se}from"./cli-platform.js";import{getRefreshToken as Ae,getIdToken as ye}from"./config.js";import{composeGetStarted as Te,shouldShowGetStarted as Pe}from"./get-started.js";import{prepareCodexRemoteLaunch as Ie,withCodexCwdArg as Re,defaultCodexRemoteDeps as Le,checkCodexBridgeHealthy as Oe}from"./codex-remote.js";import{BORG_CODEX_REMOTE_WAKE_ENV as De,codexAgentKindConfigArgs as Ee,codexRemoteWakeConfigArgs as Me,withAgentRuntimeEnv as S}from"./agent-runtime.js";import{findLoadedCodexThread as He}from"./codex-app-server.js";import{buildAgentKickoffPrompt as Ne,buildKickoffWakePathClause as Fe,recordCodexWakeTarget as Be,socketPathFromRemoteArgs as M}from"./codex-launch.js";import{codexBorgSessionConfigArgs as We}from"./launch-gate.js";import{addCodexMcpServer as _e,addCodexSessionStartHook as Ge,addCodexUserPromptSubmitHook as Ue,addMcpServer as Ye,addOpenCodeMcpServer as Ke,addProjectSessionStartHook as Ve,addUserPromptSubmitHook as je,isCodexMcpServerConfigured as qe,isMcpServerConfigured as Xe,isOpenCodeMcpServerConfigured as ze,removeSessionStartHook as Je}from"./config-utils.js";import{installBorgPlugin as Qe}from"./opencode-plugin.js";import{connectOpenCodeDrone as Ze,computeOpenCodePort as H,injectInitialKickoff as er}from"./opencode-drone.js";async function rr(){xe(process.argv),Q(),await Ce();const c=(async()=>{if(!process.stderr.isTTY)return;const e=w(),r=await ve();if(!r)return;const n=be(e,r);n.stale&&n.message&&process.stderr.write(`${t()}${n.message}
3
- `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(ee(w())),process.exit(0)),process.argv[2]==="setup"){L(process.argv[3])&&(process.stdout.write(Z(w())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(L)&&(process.stdout.write(re(w())),process.exit(0));const e=ce(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=de(),n=await le({role:e.role,flags:e.flags},r);process.exit(n)}if(process.argv[2]==="spawn"){const e=await oe();process.exit(e)}if(process.argv[2]==="sync"){const e=te(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 ie({},e.options);process.exit(r)}if(process.argv[2]==="cleanup"){const e=ne(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=O(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(),n=await D(e.args,r);process.exit(n)}const a=pe(process.argv[2]);if(a!==null&&(process.stderr.write(i.red(`${t()}\u25FC unknown command: ${a}
8
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1)),Pe(await Ae()!==null,await ye()!==null)){const e=E($()).length>0;process.stdout.write(Te(e)),process.exit(0)}const d=$e(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 A=async e=>{const r=U({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let o=await Se(d.cli,ke(A,()=>process.stdin.isTTY===!0));or();const s=await Y();let C;const y=async()=>s?(C===void 0&&(C=(await ue({targetCubeId:s.cubeId},k())).length>0),C):!1,x=process.stdin.isTTY===!0,v=process.stdout.isTTY===!0,T=me({explicitCli:d.cli,stdinIsTTY:x,stdoutIsTTY:v,hasActiveCube:s!==null,hasLaunchAllTargets:ge({explicitCli:d.cli,stdinIsTTY:x,stdoutIsTTY:v,hasActiveCube:s!==null})?await y():!1});if(T&&process.stderr.write(T),he({extraArgs:process.argv.slice(2),stdinIsTTY:x,stdoutIsTTY:v})){const e=E($()).filter(n=>n!==o),r=await fe({defaultCli:o,otherInstalledClis:e,hasLaunchAllTargets:await y()},A);if(r.kind==="launch-all"){const n=O([]),B=n.ok?await D(n.args,k()):1;process.exit(B)}o=r.cli}const l=d.rest;we(s?{label:s.droneLabel,cubeName:s.name}:null,G(process.cwd()));const N=Fe(o,s&&o==="claude"?V(s.cubeId,s.droneId):null,s&&o==="claude"?X(b(process.cwd())):null);await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const P=o==="codex"?`borg-wake-${_()}`:null;let g,I=[],u={...S(process.env,o),BORG_SESSION:"1"},p=null,m=null;if(o==="codex"&&!l.includes("--remote")){console.error(`${t()}${i.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await Ie(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.",I=e.args,u={...S(process.env,o),...e.env,BORG_SESSION:"1"},p=M(e.args),m=e.server?.cleanup??null}else o==="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.",p=M(l),p&&(u={...S(process.env,o),[De]:"1",BORG_SESSION:"1"}));if(s){const e=await K(s.cubeId,b(),s.droneId),r=z(u,e,process.env);if(u=r.env,r.probe){const n=await J(r.probe.descriptor,fetch,r.probe.baseUrl);n.ok||console.error(`${t()}${i.yellow(`warning: ${n.message}`)}`)}}const f=Ne({cli:o,codexWakeNonce:P,monitorClause:N,codexWakePathClause:g});let h;if(o==="codex")h=[...We(),...Ee(),...Me(p!==null),...I,...Re([...l,f],process.cwd())];else if(o==="opencode"){const e=s?H(s.droneId):14096;Qe(),h=[process.cwd(),"--port",String(e),"--auto","--prompt",f,...l]}else h=se(l,f);const F=o==="claude"?"Claude Code":o==="codex"?"Codex":"OpenCode";console.error(`${t()}${i.blue(`\u25FC Launching ${F}\u2026`)}`);const R=W(o,h,{stdio:"inherit",shell:!1,env:u});if(o==="opencode"){const r=`http://127.0.0.1:${s?H(s.droneId):14096}`;Ze({serverUrl:r,directory:process.cwd(),droneLabel:s?.droneLabel??"opencode",cubeName:s?.name??"borg"}).then(()=>er(f)).catch(()=>{})}o==="codex"&&s&&p&&(Be({deps:{setCodexWakeTarget:j,findLoadedCodexThread:He},cubeId:s.cubeId,droneId:s.droneId,socketPath:p,passthroughArgs:l,previewNeedle:P??f.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),q(e=>Oe(e))),R.on("error",e=>{if(m)try{m()}catch{}if(e.code==="ENOENT"){console.error(`${t()}${i.red(`
10
- \u25FC Failed to launch ${o}`)}`);const r=o==="opencode"?"opencode":o;console.error(`${t()}${i.gray(`Make sure ${r} is installed.
11
- `)}`)}else console.error(`${t()}${i.red(`
12
- \u25FC Failed to launch ${o}: ${e.message}
13
- `)}`);process.exit(1)}),R.on("exit",e=>{if(m)try{m()}catch{}process.exit(e??0)})}function or(){const c=$();if(c.claude)try{Xe()||Ye(),Ve(b(process.cwd())),Je(),je()}catch(a){console.error(`${t()}${i.yellow(`warning: Claude Code integration check failed: ${a?.message??a}`)}`)}if(c.codex)try{qe()||_e(),Ge(),Ue()}catch(a){console.error(`${t()}${i.yellow(`warning: Codex integration check failed: ${a?.message??a}`)}`)}if(c.opencode)try{ze()||Ke()}catch(a){console.error(`${t()}${i.yellow(`warning: OpenCode integration check failed: ${a?.message??a}`)}`)}}rr().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)});
@@ -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};
@@ -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};
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.9",
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",