borgmcp 1.1.12 → 1.1.14

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,4 +1,4 @@
1
- import type { Role } from './role-resolver.js';
1
+ import type { Role, RoleOccupant } from './role-resolver.js';
2
2
  import { type CodexRemoteLaunch } from './codex-remote.js';
3
3
  import type { BorgCli } from './cubes.js';
4
4
  export interface AssimilateFlags {
@@ -23,9 +23,7 @@ export interface CubeDetail {
23
23
  id: string;
24
24
  name: string;
25
25
  roles: Role[];
26
- drones?: Array<{
27
- role_id: string;
28
- }>;
26
+ drones?: RoleOccupant[];
29
27
  }
30
28
  export interface AssimilateResult {
31
29
  cube_id: string;
@@ -108,20 +106,6 @@ export interface AssimilateDeps {
108
106
  previewIncludes: string;
109
107
  updatedAfter: number;
110
108
  }) => Promise<string | null>;
111
- fetch: typeof fetch;
112
- checkModelReachable: (descriptor: string | null, fetchImpl: typeof fetch, ollamaBaseUrl?: string) => Promise<{
113
- ok: boolean;
114
- message?: string;
115
- }>;
116
- getLaunchModel: (cubeId: string, worktreePath: string, priorDroneId: string | null) => Promise<{
117
- model: string;
118
- ollamaBaseUrl: string | null;
119
- } | null>;
120
- setLaunchModel: (cubeId: string, worktreePath: string, record: {
121
- model: string;
122
- ollamaBaseUrl: string | null;
123
- }) => Promise<void>;
124
- clearLaunchModel: (cubeId: string, worktreePath: string) => Promise<void>;
125
109
  }
126
110
  export declare function runAssimilate(args: AssimilateArgs, deps: AssimilateDeps): Promise<number>;
127
111
  /**
@@ -1,49 +1,48 @@
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(`
1
+ import{dirname as G,basename as x}from"node:path";import{randomUUID as oe}from"node:crypto";import{roleSlug as ie,matchRoleByName as ae,occupiedRoleIdsForAutoRole as se,pickDefaultRole as le}from"./role-resolver.js";import{deriveCubeName as ce,parseGitRemote as ue,sanitizeRemoteUrl as me}from"./cube-name.js";import{validateName as q}from"./name-validator.js";import{renderAssimilationWelcome as fe}from"./assimilate-welcome.js";import{shellEscape as de}from"./shell-escape.js";import{withCodexCwdArg as ge}from"./codex-remote.js";import{buildAgentKickoffPrompt as he,buildKickoffWakePathClause as we,recordCodexWakeTarget as be,socketPathFromRemoteArgs as ke}from"./codex-launch.js";import{perWorktreeBranchName as U,adoptWorktree as pe,computeWorktreePath as z,localBranchExists as J,isMerged as ye}from"./worktree-lifecycle.js";import{DroneEvictedError as ve}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as $e}from"./launch-gate.js";import{codexAgentKindConfigArgs as xe,codexRemoteWakeConfigArgs as Ce,withAgentRuntimeEnv as Se}from"./agent-runtime.js";import{inboxPathForDrone as Re}from"./cubes.js";import{monitorStateRootForWorktree as Ee}from"./inbox-monitor.js";import{resolveLaunchEnv as Ae}from"./model-presets.js";import{unlinkSync as _e}from"node:fs";import{gcOrphanInboxesForCube as Ie,defaultListInboxLogs as Ne,defaultInboxLivenessDeps as Pe,isInboxLive as Te,ORPHAN_INBOX_STALE_MS as Le}from"./gc-orphan-inboxes.js";import{installBorgPlugin as De}from"./opencode-plugin.js";import{computeOpenCodePort as Oe,connectOpenCodeDrone as We,createOpenCodeLaunchKickoff as je,injectInitialKickoff as Ue}from"./opencode-drone.js";import{ensureCliMcpConfigured as He}from"./ensure-mcp-config.js";async function ht(r,e){if(r.role!==void 0){const t=q(r.role);if(!t.ok)return e.stderr(t.error+`
2
+ `),1}if(r.flags.worktree!==void 0){const t=q(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
3
+ `),1}let a=await e.getCachedAuth();if(!a){if(!e.isTTY()&&!r.flags.yes)return e.stderr("borg setup required and stdin is non-interactive. Run `borg setup` first in an interactive terminal, then `borg assimilate`.\n"),1;a=await e.runSetup()}const i=e.findProjectRoot(e.cwd());let o;if(r.flags.cubeName)o=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],i),n=t.status===0?t.stdout:null;if(o=ce(i,n),n){const c=me(n),d=c?ue(c):null;c&&!d&&o&&e.stderr(`couldn't parse git remote '${c}' \u2014 using directory name '${o}' as cube name
4
+ `)}}let s=null;if(o&&o.includes("@")&&o.includes(":")){const t=o.lastIndexOf(":");s={ownerEmail:o.substring(0,t),cubeName:o.substring(t+1)},o=s.cubeName}const C=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===o);if(!N&&s)return e.stderr(`No cube named '${s.cubeName}' accessible to you owned by '${s.ownerEmail}'. Did you accept their invite? See borgmcp.ai/dashboard.
7
+ `),1;let u,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((y,v)=>{const _=v===0?" (default)":"";c.push(` ${v+1}) ${y.name}${_} \u2014 ${y.description}`)}),c.push(` ${n.length+1}) skip \u2014 no template`);const d=(await e.prompt(c.join(`
8
8
  `)+`
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.
9
+ [1]: `)).trim(),k=d===""?1:parseInt(d,10);if(Number.isNaN(k)||k<1||k>n.length+1)return e.stderr(`invalid choice "${d}"
10
+ `),1;t=k<=n.length?n[k-1].name:void 0}else{if(!r.flags.yes)return e.stderr(`cube creation needs a template choice but stdin is non-interactive.
11
11
  Pass --template <name>, --no-template, or --yes (defaults to starter).
12
- `),1;t="starter"}e.stderr(i?`Creating cube '${i}'\u2026
12
+ `),1;t="starter"}e.stderr(o?`Creating cube '${o}'\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}),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}
14
+ `),u=await e.createCube(a.apiUrl,a.token,t?{name:o??void 0,template:t}:{name:o??void 0}),P=!0}let w;if(r.role!==void 0){if(w=ae(u.roles,r.role),!w){const t=u.roles.map(d=>d.name).join(", "),n=Be(r.role,u.roles.map(d=>d.name)),c=n?` Did you mean "${n}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${u.name}". Available: ${t}.${c}
15
15
  (Use --template <name> on first-drone setup or run \`borg_create-role\` from inside Claude.)
16
- `),1}}else{const t=new Set((u.drones??[]).map(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.
16
+ `),1}}else{const t=se(u.drones??[]);if(w=le(u.roles,{isFirstDrone:P,occupiedRoleIds:t}),!w)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 R;if(S&&r.flags.here)if(S.cubeId===u.id)R=S.droneId;else return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
18
+ `),1;const H=r.flags.model??null,m=await e.resolveCli(r.flags.cli);try{He(m)}catch(t){const n=t instanceof Error?t.message:String(t);return e.stderr(`${m} MCP configuration failed: ${n}
19
+ `),1}e.stderr(`Joining cube '${u.name}' as ${w.name}\u2026
20
+ `);let l;try{l=await e.assimilate(a.apiUrl,a.token,{cube_id:u.id,role_id:w.id,hostname:e.getHostname(),agent_kind:m,model:H,...R?{prior_drone_id:R}:{}})}catch(t){if(t instanceof ve&&R!=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 b=u.roles.find(t=>t.id===l.role_id)??w;l.reattached?e.stderr(`re-attached to existing seat ${l.drone_label} (session token rotated, no new drone minted)
23
+ `):b.id!==w.id&&e.stderr(`Note: your invite didn't grant the "${w.name}" role \u2014 assimilated as "${b.name}" instead.
24
+ `);const X=r.flags.worktree!==void 0||S!==null&&!r.flags.here;let h=null;if(X){const t=e.runSync("git",["rev-parse","--verify","HEAD"],i);if(t.status!==0)return e.stderr(`sibling worktree spawn requires HEAD pointing at a commit.
26
25
  Fix: create at least one commit (\`git commit --allow-empty -m "initial"\`)
27
26
  OR: pass --here to skip the sibling spawn and use the current directory
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}
33
- `),w){const c=e.runSync("git",["worktree","remove","--force",w],o);c.status===0?e.stderr(`rolled back spawned worktree at ${w}
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}
27
+ `),1;e.runSync("git",["fetch","origin"],i);let n="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],i).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],i).status===0&&(n="origin/master");const d=t.stdout.trim(),k=e.runSync("git",["rev-parse",n],i).stdout.trim();d!==k&&e.stderr(`note: local HEAD (${d.slice(0,7)}) differs from ${n} (${k.slice(0,7)}); new worktree will start on ${n}
28
+ `);const y=x(i),v=r.flags.worktree??ie(b.name);if(v.length===0)return e.stderr(`cannot derive a worktree name from role "${b.name}"; pass an explicit --worktree <name>
29
+ `),1;const _=e.homedir();let g=z(_,y,v),p=U(x(g),y),B=2;for(;e.pathExists(g)||Ke(e,i,g)||J(e.runSync,i,p)&&!ye(e.runSync,i,p,n);)g=z(_,y,v,B),p=U(x(g),y),B++;e.mkdirp(G(g));const Y=J(e.runSync,i,p)?e.runSync("git",["worktree","add",g,p],i):e.runSync("git",["worktree","add","-b",p,g,n],i);if(Y.status!==0)return e.stderr(`git worktree add failed: ${V(Y.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,i)),h=e.cwd()}try{await e.setActiveCube({cubeId:l.cube_id,droneId:l.drone_id,name:u.name,sessionToken:l.session_token,droneLabel:l.drone_label,apiUrl:a.apiUrl,roleName:b.name,isHumanSeat:b.is_human_seat,...b.role_class?{roleClass:b.role_class}:{}})}catch(t){const n=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${n}
32
+ `),h){const c=e.runSync("git",["worktree","remove","--force",h],i);c.status===0?e.stderr(`rolled back spawned worktree at ${h}
33
+ `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${h}\` (rollback attempt failed: ${V(c.stderr).trim()||"unknown"})
34
+ `)}return 1}const f=e.cwd(),Q=e.findProjectRoot(f),T=Ee(Q);try{const t=Pe(),n=G(Re(l.cube_id,l.drone_id));Ie({cubeInboxDir:n,selfDroneId:l.drone_id,deps:{listInboxLogs:Ne,isLive:c=>Te(c,t,T),droneState:()=>"absent",unlink:c=>_e(c),now:t.now,staleMs:Le},monitorStateRoot:T})}catch{}e.setTerminalTitle(l.drone_label,u.name);const Z=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(fe(b.name,u.name,Z));try{e.installProjectSessionHook(f)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${f}; it will be re-attempted on the next borg launch
35
+ `)}if(!h){e.runSync("git",["fetch","origin","--prune"],f);const t=U(x(f),x(i)),n=pe(e.runSync,f,t,"origin/main");n.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
36
+ `),e.stderr(Me(f,t))):n.message&&e.stderr(`worktree sync: ${n.message}
38
37
  `)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${m} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
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}.
38
+ `);const ee=e.getInboxPath(l.cube_id,l.drone_id),L=m==="codex"?`borg-wake-${oe()}`:null,te=we(m,m==="claude"?ee:null,m==="claude"?T:null);let D,F=[],$,E=null,O=null;const M=Ae(H),W={...Se(process.env,m),...M.set,BORG_SESSION:"1"};for(const t of M.unset)delete W[t];if(m==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
39
+ `),D="\u26A0 Codex wake-path capability check failed: remote-control is unavailable for this session. Run borg_regen manually whenever you return, and expect only fallback wakeups until relaunch."):D="Codex wake-path capability check passed: remote-control socket established for this session.",F=t.args,Object.keys(t.env).length>0&&Object.assign(W,t.env),E=ke(t.args),O=t.server?.cleanup??null}const K=he({cli:m,codexWakeNonce:L,monitorClause:te,codexWakePathClause:D});let A=null,j;if($=[K],m==="codex")$=[...$e(),...xe(),...Ce(E!==null),...F,...ge($,f)];else if(m==="opencode"){j=Oe(l.drone_id),De();const t=f;A=je(K),$=[t,"--port",String(j),"--auto","--prompt",A.prompt]}const re=e.exec(m,$,f,W);if(m==="codex"&&E&&L&&be({deps:e,cubeId:l.cube_id,droneId:l.drone_id,socketPath:E,cwd:f,previewNeedle:L,launchedAtSeconds:Math.floor(Date.now()/1e3)}),m==="opencode"&&A){const t=A,n=`http://127.0.0.1:${j}`;We({serverUrl:n,directory:f,droneLabel:l.drone_label,cubeName:o??"borg"}).then(()=>Ue(t)).catch(()=>{})}const ne=await re;if(O)try{O()}catch{}return h&&C!==h&&e.stderr(`
40
+ Agent exited. You were working in ${h}; your shell is back in ${C}.
42
41
  To return:
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};
42
+ cd ${de(h)}
43
+ `),ne}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 Me(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 V(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function Ke(r,e,a){const i=r.runSync("git",["worktree","list","--porcelain"],e);return i.status!==0?!1:i.stdout.split(`
48
+ `).some(o=>o===`worktree ${a}`)}function Be(r,e){if(e.length===0)return null;const a=r.toLowerCase();let i=null;for(const o of e){const s=Ye(a,o.toLowerCase());s<=2&&(i===null||s<i.distance)&&(i={name:o,distance:s})}return i?i.name:null}function Ye(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),i=new Array(e.length+1);for(let o=0;o<=e.length;o++)a[o]=o;for(let o=1;o<=r.length;o++){i[0]=o;for(let s=1;s<=e.length;s++){const C=r[o-1]===e[s-1]?0:1;i[s]=Math.min(i[s-1]+1,a[s]+1,a[s-1]+C)}for(let s=0;s<=e.length;s++)a[s]=i[s]}return a[e.length]}export{ht as runAssimilate,V as safeStderr,Be as suggestRoleName};
@@ -1,3 +1,3 @@
1
- import{spawnSync as p,spawn as l}from"node:child_process";import{existsSync as h,mkdirSync as f}from"node:fs";import{hostname as b,homedir as _}from"node:os";import{createInterface as d}from"node:readline/promises";import{readinessProbeEnv as C}from"./readiness-probe.js";import{API_URL as u,getValidToken as m,listCubes as T,getCube as y,createCube as k,assimilate as w,listTemplates as g}from"./remote-client.js";import{findProjectRoot as L,getActiveCube as v,setActiveCube as x,inboxPathForDrone as M,setCodexWakeTarget as S,getLaunchModel as R,setLaunchModel as P,clearLaunchModel as A}from"./cubes.js";import{authenticateWithGoogle as U}from"./auth.js";import{addProjectSessionStartHook as j}from"./config-utils.js";import{setTerminalTitle as D}from"./terminal-title.js";import{defaultCliChoiceDeps as G,resolveCliChoice as H}from"./cli-platform.js";import{prepareCodexRemoteLaunch as I,defaultCodexRemoteDeps as q}from"./codex-remote.js";import{findLoadedCodexThread as E}from"./codex-app-server.js";import{checkModelReachable as V}from"./model-presets.js";function te(){return{runSync:(e,r,t)=>{const o=p(e,r,{cwd:t,encoding:"utf-8"});return{status:o.status,stdout:o.stdout??"",stderr:o.stderr??""}},pathExists:e=>h(e),cwd:()=>process.cwd(),chdir:e=>process.chdir(e),homedir:()=>_(),mkdirp:e=>f(e,{recursive:!0}),exec:(e,r,t,o)=>new Promise((s,i)=>{const a=l(e,r,{cwd:t,stdio:"inherit",shell:!1,env:o??process.env});a.on("error",i),a.on("exit",n=>s(n??0))}),stderr:e=>process.stderr.write(e),stdout:e=>process.stdout.write(e),prompt:async e=>{const r=d({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}},isTTY:()=>process.stdin.isTTY===!0,getHostname:()=>b(),setTerminalTitle:(e,r)=>{D({label:e,cubeName:r},r)},getActiveCube:()=>v(),setActiveCube:e=>x(e),findProjectRoot:e=>L(e),installProjectSessionHook:e=>{j(e)},getCachedAuth:async()=>{try{return{token:await m(),apiUrl:u}}catch{return null}},runSetup:async()=>(await U(),{token:await m(),apiUrl:u}),listCubes:async(e,r)=>{const{cubes:t}=await T();return t.map(o=>({id:o.id,name:o.name}))},getCube:async(e,r,t)=>{const o=await y(t);return{id:o.id,name:o.name,roles:o.roles,drones:(o.drones??[]).map(s=>({role_id:s.role_id}))}},createCube:async(e,r,t)=>{const o=await k(t.name,"",t.template?{template:t.template}:void 0);return{id:o.id,name:o.name,roles:o.roles}},assimilate:async(e,r,t)=>{const o=await w({cube_id:t.cube_id,role_id:t.role_id,...t.prior_drone_id?{prior_drone_id:t.prior_drone_id}:{},...t.model!=null?{model:t.model}:{}},void 0,t.hostname??null,t.agent_kind??null);return{cube_id:o.cube.id,drone_id:o.drone.id,drone_label:o.drone.label,session_token:o.sessionToken,role_id:o.role.id,reattached:o.reattached===!0}},listTemplates:async(e,r)=>{const{templates:t}=await g();return t.map(o=>({name:o.name,description:o.description}))},getInboxPath:(e,r)=>M(e,r),probeMcpReady:()=>new Promise(e=>{const r=l("borg-mcp",[],{stdio:["pipe","pipe","pipe"],shell:!1,env:C()});let t="",o=!1;const s=n=>{if(!o){o=!0;try{r.kill("SIGTERM")}catch{}e(n)}},i=setTimeout(()=>s(!1),2e3);r.on("error",()=>{clearTimeout(i),s(!1)}),r.on("exit",()=>{clearTimeout(i),s(o)}),r.stdout?.on("data",n=>{t+=n.toString("utf-8");for(const c of t.split(`
2
- `))if(c.includes('"protocolVersion"')&&c.includes('"result"')){clearTimeout(i),s(!0);return}});const a=JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"borg-assimilate-probe",version:"0.9.3"}}});try{r.stdin?.write(a+`
3
- `)}catch{clearTimeout(i),s(!1)}}),resolveCli:e=>H(e,G(async r=>{const t=d({input:process.stdin,output:process.stdout});try{return await t.question(r)}finally{t.close()}},()=>process.stdin.isTTY===!0)),prepareCodexRemoteLaunch:()=>I(q()),setCodexWakeTarget:S,findLoadedCodexThread:E,fetch,checkModelReachable:V,getLaunchModel:(e,r,t)=>R(e,r,t),setLaunchModel:(e,r,t)=>P(e,r,t),clearLaunchModel:(e,r)=>A(e,r)}}export{te as buildDefaultAssimilateDeps};
1
+ import{spawnSync as p,spawn as c}from"node:child_process";import{existsSync as f,mkdirSync as b}from"node:fs";import{hostname as h,homedir as _}from"node:os";import{createInterface as d}from"node:readline/promises";import{readinessProbeEnv as C}from"./readiness-probe.js";import{API_URL as u,getValidToken as m,listCubes as T,getCube as y,createCube as w,assimilate as k,listTemplates as g}from"./remote-client.js";import{findProjectRoot as v,getActiveCube as x,setActiveCube as S,inboxPathForDrone as P,setCodexWakeTarget as A}from"./cubes.js";import{authenticateWithGoogle as R}from"./auth.js";import{addProjectSessionStartHook as U}from"./config-utils.js";import{setTerminalTitle as j}from"./terminal-title.js";import{defaultCliChoiceDeps as L,resolveCliChoice as D}from"./cli-platform.js";import{prepareCodexRemoteLaunch as H,defaultCodexRemoteDeps as I}from"./codex-remote.js";import{findLoadedCodexThread as G}from"./codex-app-server.js";function Q(){return{runSync:(e,r,o)=>{const t=p(e,r,{cwd:o,encoding:"utf-8"});return{status:t.status,stdout:t.stdout??"",stderr:t.stderr??""}},pathExists:e=>f(e),cwd:()=>process.cwd(),chdir:e=>process.chdir(e),homedir:()=>_(),mkdirp:e=>b(e,{recursive:!0}),exec:(e,r,o,t)=>new Promise((i,s)=>{const a=c(e,r,{cwd:o,stdio:"inherit",shell:!1,env:t??process.env});a.on("error",s),a.on("exit",n=>i(n??0))}),stderr:e=>process.stderr.write(e),stdout:e=>process.stdout.write(e),prompt:async e=>{const r=d({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}},isTTY:()=>process.stdin.isTTY===!0,getHostname:()=>h(),setTerminalTitle:(e,r)=>{j({label:e,cubeName:r},r)},getActiveCube:()=>x(),setActiveCube:e=>S(e),findProjectRoot:e=>v(e),installProjectSessionHook:e=>{U(e)},getCachedAuth:async()=>{try{return{token:await m(),apiUrl:u}}catch{return null}},runSetup:async()=>(await R(),{token:await m(),apiUrl:u}),listCubes:async(e,r)=>{const{cubes:o}=await T();return o.map(t=>({id:t.id,name:t.name}))},getCube:async(e,r,o)=>{const t=await y(o);return{id:t.id,name:t.name,roles:t.roles,drones:(t.drones??[]).map(i=>({role_id:i.role_id}))}},createCube:async(e,r,o)=>{const t=await w(o.name,"",o.template?{template:o.template}:void 0);return{id:t.id,name:t.name,roles:t.roles}},assimilate:async(e,r,o)=>{const t=await k({cube_id:o.cube_id,role_id:o.role_id,...o.prior_drone_id?{prior_drone_id:o.prior_drone_id}:{},...o.model!=null?{model:o.model}:{}},void 0,o.hostname??null,o.agent_kind??null);return{cube_id:t.cube.id,drone_id:t.drone.id,drone_label:t.drone.label,session_token:t.sessionToken,role_id:t.role.id,reattached:t.reattached===!0}},listTemplates:async(e,r)=>{const{templates:o}=await g();return o.map(t=>({name:t.name,description:t.description}))},getInboxPath:(e,r)=>P(e,r),probeMcpReady:()=>new Promise(e=>{const r=c("borg-mcp",[],{stdio:["pipe","pipe","pipe"],shell:!1,env:C()});let o="",t=!1;const i=n=>{if(!t){t=!0;try{r.kill("SIGTERM")}catch{}e(n)}},s=setTimeout(()=>i(!1),2e3);r.on("error",()=>{clearTimeout(s),i(!1)}),r.on("exit",()=>{clearTimeout(s),i(t)}),r.stdout?.on("data",n=>{o+=n.toString("utf-8");for(const l of o.split(`
2
+ `))if(l.includes('"protocolVersion"')&&l.includes('"result"')){clearTimeout(s),i(!0);return}});const a=JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"borg-assimilate-probe",version:"0.9.3"}}});try{r.stdin?.write(a+`
3
+ `)}catch{clearTimeout(s),i(!1)}}),resolveCli:e=>D(e,L(async r=>{const o=d({input:process.stdin,output:process.stdout});try{return await o.question(r)}finally{o.close()}},()=>process.stdin.isTTY===!0)),prepareCodexRemoteLaunch:()=>H(I()),setCodexWakeTarget:A,findLoadedCodexThread:G}}export{Q as buildDefaultAssimilateDeps};
package/dist/claude.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
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(`
2
+ import{spawn as G}from"child_process";import{randomUUID as M}from"node:crypto";import{basename as Y}from"node:path";import{createInterface as U}from"node:readline/promises";import i from"chalk";import{findProjectRoot as D,getActiveCube as V,inboxPathForDrone as j,setCodexWakeTarget as q,pruneDeadCodexWakeTargets as X}from"./cubes.js";import{monitorStateRootForWorktree as z}from"./inbox-monitor.js";import{handleVersionFlag as J,getPackageVersion as w}from"./version.js";import{isHelpFlag as O,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 ne}from"./cleanup-cmd.js";import{parseAssimilateArgs as ae}from"./parse-assimilate-args.js";import{runAssimilate as ce}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as le}from"./assimilate-deps.js";import{parseLaunchAllArgs as E}from"./parse-launch-all-args.js";import{unknownSubcommand as de}from"./unknown-subcommand.js";import{runLaunchAll as H}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as v}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 ke}from"./stale-version-check.js";import{defaultCliChoiceDeps as be,detectCliAvailability as $,installedCliNames as N,parseCliFlag as ve,resolveCliChoice as $e}from"./cli-platform.js";import{getRefreshToken as Ae,getIdToken as Se}from"./config.js";import{composeGetStarted as ye,shouldShowGetStarted as Te}from"./get-started.js";import{prepareCodexRemoteLaunch as Pe,withCodexCwdArg as Re,defaultCodexRemoteDeps as Ie,checkCodexBridgeHealthy as Le}from"./codex-remote.js";import{BORG_CODEX_REMOTE_WAKE_ENV as De,codexAgentKindConfigArgs as Oe,codexRemoteWakeConfigArgs as Ee,withAgentRuntimeEnv as A}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 F}from"./codex-launch.js";import{codexBorgSessionConfigArgs as We}from"./launch-gate.js";import{addCodexSessionStartHook as _e,addCodexUserPromptSubmitHook as Ke,addProjectSessionStartHook as Ge,addUserPromptSubmitHook as Me,removeSessionStartHook as Ye}from"./config-utils.js";import{ensureCliMcpConfigured as S}from"./ensure-mcp-config.js";import{installBorgPlugin as Ue}from"./opencode-plugin.js";import{connectOpenCodeDrone as Ve,computeOpenCodePort as B,createOpenCodeLaunchKickoff as je,injectInitialKickoff as qe}from"./opencode-drone.js";async function Xe(){Ce(process.argv),J(),await we();const c=(async()=>{if(!process.stderr.isTTY)return;const e=w(),s=await xe();if(!s)return;const n=ke(e,s);n.stale&&n.message&&process.stderr.write(`${t()}${n.message}
3
+ `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(Z(w())),process.exit(0)),process.argv[2]==="setup"){O(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(O)&&(process.stdout.write(ee(w())),process.exit(0));const e=ae(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg assimilate: ${e.error}
4
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const s=le(),n=await ce({role:e.role,flags:e.flags},s);process.exit(n)}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 s=await te({},e.options);process.exit(s)}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 s=await ne({},e.options);process.exit(s)}if(process.argv[2]==="launch-all"){const e=E(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 s=v(),n=await H(e.args,s);process.exit(n)}const a=de(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)),Te(await Ae()!==null,await Se()!==null)){const e=N($()).length>0;process.stdout.write(ye(e)),process.exit(0)}const d=ve(process.argv.slice(2));d.error&&(process.stderr.write(i.red(`${t()}\u25FC ${d.error}
9
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const y=async e=>{const s=U({input:process.stdin,output:process.stdout});try{return await s.question(e)}finally{s.close()}};let r=await $e(d.cli,be(y,()=>process.stdin.isTTY===!0));ze();const o=await V();let C;const T=async()=>o?(C===void 0&&(C=(await pe({targetCubeId:o.cubeId},v())).length>0),C):!1,x=process.stdin.isTTY===!0,k=process.stdout.isTTY===!0,P=ue({explicitCli:d.cli,stdinIsTTY:x,stdoutIsTTY:k,hasActiveCube:o!==null,hasLaunchAllTargets:fe({explicitCli:d.cli,stdinIsTTY:x,stdoutIsTTY:k,hasActiveCube:o!==null})?await T():!1});if(P&&process.stderr.write(P),ge({extraArgs:process.argv.slice(2),stdinIsTTY:x,stdoutIsTTY:k})){const e=N($()).filter(n=>n!==r),s=await me({defaultCli:r,otherInstalledClis:e,hasLaunchAllTargets:await T()},y);if(s.kind==="launch-all"){const n=E([]),K=n.ok?await H(n.args,v()):1;process.exit(K)}r=s.cli}const l=d.rest;he(o?{label:o.droneLabel,cubeName:o.name}:null,Y(process.cwd()));const W=Fe(r,o&&r==="claude"?j(o.cubeId,o.droneId):null,o&&r==="claude"?z(D(process.cwd())):null);await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const R=r==="codex"?`borg-wake-${M()}`:null;let m,I=[],b={...A(process.env,r),BORG_SESSION:"1"},p=null,u=null;if(r==="codex"&&!l.includes("--remote")){console.error(`${t()}${i.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await Pe(Ie());e.warning?(console.error(`${t()}${i.yellow(`warning: ${e.warning}`)}`),m="\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."):m="Codex wake-path capability check passed: remote-control socket established for this session.",I=e.args,b={...A(process.env,r),...e.env,BORG_SESSION:"1"},p=F(e.args),u=e.server?.cleanup??null}else r==="codex"&&l.includes("--remote")&&(m="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&&(b={...A(process.env,r),[De]:"1",BORG_SESSION:"1"}));const f=Ne({cli:r,codexWakeNonce:R,monitorClause:W,codexWakePathClause:m});let g=null,h;if(r==="codex")h=[...We(),...Oe(),...Ee(p!==null),...I,...Re([...l,f],process.cwd())];else if(r==="opencode"){const e=o?B(o.droneId):14096;Ue(),g=je(f),h=[process.cwd(),"--port",String(e),"--auto","--prompt",g.prompt,...l]}else h=oe(l,f);const _=r==="claude"?"Claude Code":r==="codex"?"Codex":"OpenCode";console.error(`${t()}${i.blue(`\u25FC Launching ${_}\u2026`)}`);const L=G(r,h,{stdio:"inherit",shell:!1,env:b});if(r==="opencode"&&g){const e=g,n=`http://127.0.0.1:${o?B(o.droneId):14096}`;Ve({serverUrl:n,directory:process.cwd(),droneLabel:o?.droneLabel??"opencode",cubeName:o?.name??"borg"}).then(()=>qe(e)).catch(()=>{})}r==="codex"&&o&&p&&(Be({deps:{setCodexWakeTarget:q,findLoadedCodexThread:He},cubeId:o.cubeId,droneId:o.droneId,socketPath:p,passthroughArgs:l,previewNeedle:R??f.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),X(e=>Le(e))),L.on("error",e=>{if(u)try{u()}catch{}if(e.code==="ENOENT"){console.error(`${t()}${i.red(`
10
+ \u25FC Failed to launch ${r}`)}`);const s=r==="opencode"?"opencode":r;console.error(`${t()}${i.gray(`Make sure ${s} is installed.
11
+ `)}`)}else console.error(`${t()}${i.red(`
12
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(`
13
+ `)}`);process.exit(1)}),L.on("exit",e=>{if(u)try{u()}catch{}process.exit(e??0)})}function ze(){const c=$();if(c.claude)try{S("claude"),Ge(D(process.cwd())),Ye(),Me()}catch(a){console.error(`${t()}${i.yellow(`warning: Claude Code integration check failed: ${a?.message??a}`)}`)}if(c.codex)try{S("codex"),_e(),Ke()}catch(a){console.error(`${t()}${i.yellow(`warning: Codex integration check failed: ${a?.message??a}`)}`)}if(c.opencode)try{S("opencode")}catch(a){console.error(`${t()}${i.yellow(`warning: OpenCode integration check failed: ${a?.message??a}`)}`)}}Xe().catch(c=>{console.error(`${t()}${i.red(`
14
14
  \u25FC Error: ${c.message}
15
15
  `)}`),process.exit(1)});
@@ -0,0 +1,12 @@
1
+ export declare const CLEAR_REWAKE_REMINDER = "Post-/clear recovery: full regen, drain the unread log; handle actionable cube entries, otherwise resume the work from before this wake.";
2
+ export interface ClearRewakeResult {
3
+ exitCode: 0 | 2;
4
+ stderr: string;
5
+ }
6
+ /**
7
+ * Resolve the Claude `/clear` async-rewake response without consulting cube
8
+ * state. The reminder is deliberately static: hook input can contain local
9
+ * paths and session metadata, none of which may cross into stderr.
10
+ */
11
+ export declare function evaluateClearRewake(raw: string, env?: NodeJS.ProcessEnv): ClearRewakeResult;
12
+ //# sourceMappingURL=clear-rewake-core.d.ts.map
@@ -0,0 +1,2 @@
1
+ import{isBorgSession as o}from"./launch-gate.js";const n="Post-/clear recovery: full regen, drain the unread log; handle actionable cube entries, otherwise resume the work from before this wake.";function i(r,t=process.env){if(!o(t))return{exitCode:0,stderr:""};let e;try{e=r.trim()?JSON.parse(r):null}catch{return{exitCode:0,stderr:""}}return e?.hook_event_name!=="SessionStart"||e?.source!=="clear"?{exitCode:0,stderr:""}:{exitCode:2,stderr:`${n}
2
+ `}}export{n as CLEAR_REWAKE_REMINDER,i as evaluateClearRewake};
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=clear-rewake.d.ts.map
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{evaluateClearRewake as s}from"./clear-rewake-core.js";async function n(){if(process.stdin.isTTY)return"";try{const t=[];for await(const e of process.stdin)t.push(e);return Buffer.concat(t).toString("utf-8")}catch{return""}}const r=s(await n());r.stderr&&process.stderr.write(r.stderr),process.exitCode=r.exitCode;
@@ -16,8 +16,7 @@ export declare function isHelpFlag(arg: string | undefined): boolean;
16
16
  export declare function topLevelHelpText(version: string): string;
17
17
  /**
18
18
  * Help text for `borg assimilate --help` — the home for the full assimilate flag
19
- * set, including `--model` and remote-Ollama config, which the concise
20
- * top-level help only hints at.
19
+ * set. Model/provider configuration belongs to the selected agent CLI.
21
20
  */
22
21
  export declare function assimilateHelpText(version: string): string;
23
22
  /**
package/dist/cli-help.js CHANGED
@@ -12,7 +12,6 @@ Usage:
12
12
  borg setup --no-browser Set up from SSH/headless terminals
13
13
  borg assimilate [role] Join a cube (creates one if needed)
14
14
  borg assimilate --worktree <name> Spawn a worktree drone (in ~/.borg/worktrees/<repo>/<name>)
15
- borg assimilate --model ollama:<model> Run the drone on an Ollama model (see \`borg assimilate --help\`)
16
15
  borg sync [--prune] Sync this worktree's branch to origin/main
17
16
  borg cleanup [--prune] Report (or --prune) worktrees orphaned by evicted drones
18
17
  borg launch-all [cube] Launch all drone worktrees of a cube (default: active cube)
@@ -22,7 +21,7 @@ Usage:
22
21
  borg --version Show installed version
23
22
 
24
23
  All other arguments are passed through to the selected agent CLI.
25
- `}function t(e){return`borg assimilate (borgmcp ${e}) \u2014 join a cube under a role (creates the cube if needed)
24
+ `}function r(e){return`borg assimilate (borgmcp ${e}) \u2014 join a cube under a role (creates the cube if needed)
26
25
 
27
26
  Usage:
28
27
  borg assimilate [role] Join the active cube under [role] (default role if omitted)
@@ -38,19 +37,12 @@ Flags:
38
37
  --template <name> Bootstrap a new cube from a bundled role template
39
38
  --no-template Create the cube with no template roles
40
39
  --cli claude|codex|opencode Agent CLI to launch (default: claude)
41
- --model <kind>:<model> Model for this drone (default: your Claude config)
40
+ --model claude:<model> Legacy Claude model override (configure models in the agent CLI)
42
41
  --yes, -y Skip confirmation prompts
43
42
 
44
- Models (--model <kind>:<model>):
45
- claude:<model> e.g. --model claude:claude-opus-4-8
46
- ollama:<model> Run on an Ollama model, e.g. --model ollama:qwen3-coder-next
47
- The model must be pulled (ollama pull <model>); borg verifies it
48
- against the Ollama server before assimilating.
49
-
50
- Remote Ollama: set BORG_OLLAMA_BASE_URL (default http://localhost:11434), e.g.
51
- BORG_OLLAMA_BASE_URL=http://Mac-Studio.local:11434 \\
52
- borg assimilate build --model ollama:qwen3-coder-next
53
- `}function r(e){return`borg setup (borgmcp ${e}) \u2014 set up OAuth + register the borg MCP server
43
+ For local or provider-specific models, configure the selected agent CLI directly.
44
+ OpenCode supports Ollama and other providers through its own model configuration.
45
+ `}function t(e){return`borg setup (borgmcp ${e}) \u2014 set up OAuth + register the borg MCP server
54
46
 
55
47
  Borg MCP needs Claude Code, Codex, or OpenCode installed first.
56
48
 
@@ -61,4 +53,4 @@ Usage:
61
53
  for SSH / headless / container terminals. Alias: --device.
62
54
  Auto-detected on SSH/headless; this forces it.
63
55
  borg setup --help Show this help
64
- `}export{t as assimilateHelpText,n as isHelpFlag,r as setupHelpText,o as topLevelHelpText};
56
+ `}export{r as assimilateHelpText,n as isHelpFlag,t as setupHelpText,o as topLevelHelpText};
@@ -50,9 +50,10 @@ export interface CodexWakeDeps {
50
50
  }
51
51
  export declare function wakeCodexViaAppServer(reason?: string, env?: NodeJS.ProcessEnv, deps?: CodexWakeDeps): void;
52
52
  /**
53
- * gh#857 WI-2: codex /loop-equivalent heartbeat cadence. Tighter than claude's
54
- * ~60-min /loop ScheduleWakeup because codex has no per-entry inbox-Monitor
55
- * backstop this periodic drain IS the independent second safety net.
53
+ * gh#857 WI-2: codex /loop-equivalent heartbeat cadence. Codex retains this
54
+ * independent 20-minute drain because it has no Claude-style per-entry inbox
55
+ * Monitor. Claude recovery is adaptive: 3h ±30m while the Monitor is healthy
56
+ * or indeterminate, and 15m ±3m only while it is explicitly broken.
56
57
  */
57
58
  export declare const CODEX_HEARTBEAT_CADENCE_MS: number;
58
59
  /**
@@ -4,8 +4,8 @@
4
4
  * borg owns a per-launch DIRECT Codex app-server (`codex app-server --listen
5
5
  * unix://<socket>`) as the primary wake path, instead of the standalone
6
6
  * `codex app-server daemon start` (which only exists for standalone-installer
7
- * Codex — npm-managed Codex has no daemon, so those sessions had NO push-wake
8
- * and silently fell back to the ~30min /loop heartbeat). Per-launch direct
7
+ * Codex — npm-managed Codex has no daemon, so those sessions had no push-wake
8
+ * and relied on periodic log-drain catch-up). Per-launch direct
9
9
  * app-server works for both install kinds and starts fresh each launch (so it
10
10
  * always loads the current borgmcp MCP — no daemon-restart version refresh
11
11
  * needed). The TUI is then launched with `codex --remote unix://<socket>` and
@@ -27,6 +27,15 @@ export interface CodexRemoteLaunch {
27
27
  export interface CodexChild {
28
28
  pid: number | undefined;
29
29
  kill: () => void;
30
+ /** Snapshot app-server exit state + bounded stderr captured by the production spawn. */
31
+ diagnostics?: () => CodexChildDiagnostics;
32
+ }
33
+ export interface CodexChildDiagnostics {
34
+ exited: boolean;
35
+ exitCode: number | null;
36
+ signal: string | null;
37
+ error?: string | null;
38
+ stderr: string;
30
39
  }
31
40
  export interface PrepareCodexRemoteDeps {
32
41
  /** Spawn the long-lived `codex app-server --listen unix://<socketPath>` child. */
@@ -39,7 +48,7 @@ export interface PrepareCodexRemoteDeps {
39
48
  runtimeDir?: string;
40
49
  /** Unique socket id generator (default 32-hex). Injected for deterministic tests. */
41
50
  socketId?: () => string;
42
- /** Readiness timeout (default 8000ms) + poll interval (default 250ms). */
51
+ /** Readiness timeout (default 30000ms) + poll interval (default 250ms). */
43
52
  readyTimeoutMs?: number;
44
53
  pollIntervalMs?: number;
45
54
  /** Whether a pid is alive (default process.kill(pid, 0)). Injected for tests. */
@@ -1 +1 @@
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};
1
+ import{mkdirSync as R,chmodSync as k,readdirSync as D,rmSync as S,writeFileSync as v,readFileSync as _}from"node:fs";import{homedir as $}from"node:os";import{join as m}from"node:path";import{randomBytes as T}from"node:crypto";import{spawn as w}from"node:child_process";import{CodexAppServerClient as N}from"./codex-app-server.js";import{codexBorgSessionConfigArgs as M,BORG_SESSION_ENV as P}from"./launch-gate.js";import{codexAppServerSocketConfigArgs as O}from"./codex-wake-resolve.js";import{BORG_AGENT_KIND_ENV as I,BORG_CODEX_REMOTE_WAKE_ENV as y,codexAgentKindConfigArgs as z,codexRemoteWakeConfigArgs as B}from"./agent-runtime.js";const F=m($(),".config","borgmcp","codex-remote"),H=3e4,A=16384;function d(r){let e=String(r).replace(/\x1B\[[0-?]*[ -/]*[@-~]/g,"");return e=e.replace(/\b((?:authorization\s*:\s*)?bearer)\s+[^\s,;]+/gi,"$1 <REDACTED>"),e=e.replace(/\b([a-z][a-z0-9+.-]*:\/\/)([^/\s:@]+):([^@\s/]+)@/gi,"$1<REDACTED>@"),e=e.replace(/([?&](?:access_token|auth_token|id_token|refresh_token|session_token|token|api_key|apikey|secret|password)=)[^&#\s]*/gi,"$1<REDACTED>"),e=e.replace(/\b((?:[a-z0-9_-]*(?:token|secret|password)|api[_-]?key)\s*[:=]\s*)(?:"[^"\r\n]*"|'[^'\r\n]*'|[^\s,;]+)/gi,"$1<REDACTED>"),e=e.replace(/\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/g,"<REDACTED>"),e=e.replace(/(^|[\s("'=])(?:file:\/\/)?\/(?:Users|home|private|tmp|var|etc|opt|Volumes)\/[^\s"'`<>)}\]]+/gm,"$1<REDACTED_PATH>"),e=e.replace(/\b[A-Za-z]:\\(?:Users|Documents and Settings|ProgramData|Temp)\\[^\s"'`<>)}\]]+/g,"<REDACTED_PATH>"),e.slice(-A)}function Q(r,e){return U(r)?r:["--cd",e,...r]}function U(r){return r.some(e=>e==="--cd"||e.startsWith("--cd=")||e==="-C")}function C(r){try{return process.kill(r,0),!0}catch(e){return e?.code==="EPERM"}}function ee(r,e={}){if(!r)return null;const s=e.isAlive??C,o=e.readPidFile??(i=>_(i,"utf-8")),a=r.replace(/\.sock$/,".pid");try{const i=Number.parseInt(o(a).trim(),10);return Number.isNaN(i)?null:s(i)}catch{return null}}function p(r){try{S(r,{force:!0})}catch{}}function V(r,e){let s;try{s=D(r)}catch{return}for(const o of s){if(!o.endsWith(".pid"))continue;const a=m(r,o),i=m(r,o.replace(/\.pid$/,".sock"));let t;try{t=Number.parseInt(_(a,"utf-8").trim(),10)}catch{p(a);continue}(Number.isNaN(t)||!e(t))&&(p(i),p(a))}}function f(r){return{args:[],env:{},warning:r}}function X(r){return r.error?`process error: ${d(r.error)}`:r.exitCode!=null?`exit code ${r.exitCode}`:r.signal?`signal ${r.signal}`:"unknown exit status"}function b(r){const e=d(r).trim();return e?` Stderr: ${e}`:""}async function re(r){const e=r.runtimeDir??F,s=r.isAlive??C,o=r.readyTimeoutMs??H,a=r.pollIntervalMs??250;try{R(e,{recursive:!0,mode:448}),k(e,448),V(e,s)}catch(n){return f(`Codex remote-wake disabled: could not prepare ${e} (${n?.message??n}); run borg_regen manually.`)}const i=(r.socketId??(()=>T(16).toString("hex")))(),t=m(e,`${i}.sock`),l=m(e,`${i}.pid`);let c;try{c=r.spawnAppServer(t)}catch(n){return p(t),f(`Codex remote-wake disabled: could not start \`codex app-server\` (${d(String(n?.message??n))}) \u2014 confirm the Codex executable is installed and available on PATH. Remote wake is unavailable for this session; run borg_regen manually to catch up.`)}if(c.pid!=null)try{v(l,String(c.pid))}catch{}const g=()=>{try{c.kill()}catch{}p(t),p(l)},x=Math.max(1,Math.ceil(o/a));let u=!1,h;for(let n=0;n<x&&!u;n++){try{u=await r.probeReady(t)}catch{u=!1}if(!u){const E=c.diagnostics?.();if(E?.exited){h=E;break}}!u&&n<x-1&&await r.sleep(a)}if(!u){const n=h??c.diagnostics?.();return g(),n?.exited?f(`Codex remote-wake disabled: \`codex app-server\` exited before becoming ready (${X(n)}).${b(n.stderr)} Run borg_regen manually to catch up.`):f(`Codex remote-wake disabled: \`codex app-server\` remained running but did not become ready at ${d(t)} within ${o}ms.${b(n?.stderr??"")} Run borg_regen manually to catch up.`)}return{args:["--remote",`unix://${t}`],env:{[y]:"1"},server:{pid:c.pid,socketPath:t,cleanup:g}}}function te(){return{spawnAppServer:r=>{const e=w("codex",["app-server",...M(),...z(),...B(),...O(r),"--listen",`unix://${r}`],{stdio:["ignore","ignore","pipe"],shell:!1,env:{...process.env,[P]:"1",[I]:"codex",[y]:"1"}});let s="",o=!1,a=null,i=null,t=null;return e.stderr?.setEncoding("utf8"),e.stderr?.on("data",l=>{s=`${s}${String(l)}`.slice(-A)}),e.once("close",(l,c)=>{o=!0,a=l,i=c}),e.once("error",l=>{o=!0,t=d(l.message)}),{pid:e.pid,kill:()=>{try{e.kill()}catch{}},diagnostics:()=>({exited:o,exitCode:a,signal:i,error:t==null?null:d(t),stderr:d(s)})}},probeReady:async r=>{const e=new N(r);try{return await e.connect(),await e.loadedThreadIds(),!0}catch{return!1}finally{try{e.close()}catch{}}},sleep:r=>new Promise(e=>setTimeout(e,r))}}export{F as DEFAULT_CODEX_REMOTE_DIR,ee as checkCodexBridgeHealthy,te as defaultCodexRemoteDeps,C as defaultIsAlive,re as prepareCodexRemoteLaunch,Q as withCodexCwdArg};
@@ -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 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};
1
+ import{execSync as f}from"child_process";import n from"fs";import l from"os";import i from"path";import{fileURLToPath as w}from"url";import{dirname as H}from"path";import{BORG_AGENT_KIND_ENV as x,BORG_CODEX_REMOTE_WAKE_ENV as j,withAgentRuntimeEnv as N}from"./agent-runtime.js";const I=w(import.meta.url),M=H(I),a="borg-regen",C="borg-clear-rewake",m="borg-log-audit",G=i.join(l.homedir(),".claude.json"),$=i.join(l.homedir(),".codex","config.toml"),u=i.join(l.homedir(),".codex","hooks.json"),B=i.join(l.homedir(),".config","opencode","opencode.json"),F="borg";function k(){return i.join(l.homedir(),".claude","settings.json")}function h(){const e=k();if(!n.existsSync(e))return{};const t=n.readFileSync(e,"utf-8");return t.trim()?JSON.parse(t):{}}function g(e){const t=k();n.mkdirSync(i.dirname(t),{recursive:!0}),n.writeFileSync(t,JSON.stringify(e,null,2)+`
2
+ `,"utf-8")}function S(e){if(!n.existsSync(e))return{};const t=n.readFileSync(e,"utf-8");return t.trim()?JSON.parse(t):{}}function O(e,t){n.mkdirSync(i.dirname(e),{recursive:!0}),n.writeFileSync(e,JSON.stringify(t,null,2)+`
3
+ `,"utf-8")}function Q(){return P(k())}function q(e){return P(_(e),!0)}function Y(e){return D(_(e),!0)}function _(e){return i.join(e,".claude","settings.local.json")}function P(e,t=!1){let o;try{o=S(e)}catch(s){return console.error(`\u26A0 Could not parse ${e}: ${s.message}. Skipping hook registration; you can add it manually.`),!1}o.hooks??={},o.hooks.SessionStart??=[];let r=!1;if(b(o.hooks.SessionStart,a)||(o.hooks.SessionStart.push({matcher:"*",hooks:[{type:"command",command:a}]}),r=!0),t){const s=R(o.hooks.SessionStart);s&&(o.hooks.SessionStart=s,r=!0)}return r?(O(e,o),!0):!1}function b(e,t){return e.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(r=>r?.type==="command"&&r?.command===t))}function y(e){return e?.type==="command"&&e?.command===C}function L(e){return e?.matcher==="clear"&&Array.isArray(e?.hooks)&&e.hooks.length===1&&y(e.hooks[0])&&e.hooks[0].asyncRewake===!0}function R(e){if(e.reduce((r,s)=>r+(Array.isArray(s?.hooks)?s.hooks.filter(y).length:0),0)===1&&e.some(L))return null;const o=e.flatMap(r=>{if(!Array.isArray(r?.hooks))return[r];const s=r.hooks.filter(d=>!y(d));return s.length>0?[{...r,hooks:s}]:[]});return o.push({matcher:"clear",hooks:[{type:"command",command:C,asyncRewake:!0}]}),o}function D(e,t=!1){let o;try{o=S(e)}catch{return!1}const r=o?.hooks?.SessionStart;return Array.isArray(r)?b(r,a)&&(!t||R(r)===null):!1}function Z(){let e;try{e=h()}catch{return!1}const t=e?.hooks?.SessionStart;return Array.isArray(t)?t.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(r=>r?.type==="command"&&r?.command===a)):!1}function ee(){let e;try{e=h()}catch{return!1}const t=e?.hooks?.UserPromptSubmit;return Array.isArray(t)?t.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(r=>r?.type==="command"&&r?.command===m)):!1}function oe(){let e;try{e=h()}catch{return!1}if(!e?.hooks?.SessionStart)return!1;let t=!1;return e.hooks.SessionStart=e.hooks.SessionStart.map(o=>{if(!Array.isArray(o?.hooks))return o;const r=o.hooks.filter(s=>!(s?.type==="command"&&s?.command===a));return r.length!==o.hooks.length?(t=!0,{...o,hooks:r}):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,t&&g(e),t}function re(){let e;try{e=h()}catch(o){return console.error(`\u26A0 Could not parse ${k()}: ${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(r=>r?.type==="command"&&r?.command===m))?!1:(e.hooks.UserPromptSubmit.push({matcher:"*",hooks:[{type:"command",command:m}]}),g(e),!0)}function te(){let e;try{e=h()}catch{return!1}if(!e?.hooks?.UserPromptSubmit)return!1;let t=!1;return e.hooks.UserPromptSubmit=e.hooks.UserPromptSubmit.map(o=>{if(!Array.isArray(o?.hooks))return o;const r=o.hooks.filter(s=>!(s?.type==="command"&&s?.command===m));return r.length!==o.hooks.length?(t=!0,{...o,hooks:r}):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,t&&g(e),t}function se(e=G){try{if(!n.existsSync(e))return!1;const t=n.readFileSync(e,"utf-8");if(!t.trim())return!1;const o=JSON.parse(t);if(!o||typeof o!="object")return!1;const r=o.mcpServers;return!r||typeof r!="object"||Array.isArray(r)?!1:F in r}catch{return!1}}function ne(e=$){try{if(!n.existsSync(e))return!1;const t=n.readFileSync(e,"utf-8"),o=/^\s*\[mcp_servers\.borg\]\s*$/m.test(t),r=new RegExp(`^\\s*${x}\\s*=\\s*"codex"\\s*$`,"m").test(t),s=new RegExp(`^\\s*${j}\\s*=\\s*"1"\\s*$`,"m").test(t);return o&&(r||s)}catch{return!1}}function ie(){return i.join(M,"index.js")}function ae(){try{try{f("claude mcp remove --scope user borg",{stdio:"ignore"})}catch{}f("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 ce(){try{try{f("codex mcp remove borg",{stdio:"ignore"})}catch{}const e=process.env.BORG_API_URL||"https://api.borgmcp.ai",t=N(process.env,"codex");f("codex mcp add borg --env BORG_API_URL="+v(e)+` --env ${x}=codex -- borg-mcp`,{stdio:"inherit",env:{...t,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 v(e){return`'${e.replace(/'/g,"'\\''")}'`}function E(e,t,o={}){let r;try{r=S(u)}catch(p){return console.error(`\u26A0 Could not parse ${u}: ${p.message}. Skipping Codex hook registration.`),!1}r.hooks??={},r.hooks[e]??=[];const s=r.hooks[e];if(!Array.isArray(s)||s.some(p=>Array.isArray(p?.hooks)&&p.hooks.some(A=>A?.type==="command"&&A?.command===t)))return!1;const c={hooks:[{type:"command",command:t}]};return o.matcher&&(c.matcher=o.matcher),typeof o.timeout=="number"&&(c.hooks[0].timeout=o.timeout),s.push(c),O(u,r),!0}function me(){return E("SessionStart",a,{matcher:"startup|resume",timeout:30})}function ue(){return E("UserPromptSubmit",m,{timeout:10})}function U(e,t,o=u){try{const s=S(o)?.hooks?.[e];return Array.isArray(s)?s.some(d=>Array.isArray(d?.hooks)&&d.hooks.some(c=>c?.type==="command"&&c?.command===t)):!1}catch{return!1}}function de(e=u){return U("SessionStart",a,e)}function fe(e=u){return U("UserPromptSubmit",m,e)}function le(e=B){try{if(!n.existsSync(e))return!1;const t=n.readFileSync(e,"utf-8");if(!t.trim())return!1;const o=JSON.parse(t);if(!o||typeof o!="object")return!1;const r=o.mcp?.borg;return!r||typeof r!="object"?!1:r.type==="local"}catch{return!1}}function he(){try{const e=process.env.BORG_API_URL||"https://api.borgmcp.ai";f(`opencode mcp add borg --env BORG_SESSION=1 --env BORG_AGENT_KIND=opencode --env BORG_OPENCODE=1 --env BORG_API_URL=${v(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{ce as addCodexMcpServer,me as addCodexSessionStartHook,ue as addCodexUserPromptSubmitHook,ae as addMcpServer,he as addOpenCodeMcpServer,q as addProjectSessionStartHook,Q as addSessionStartHook,re as addUserPromptSubmitHook,ie as getBinaryPath,U as isCodexHookRegistered,ne as isCodexMcpServerConfigured,de as isCodexSessionStartHookRegistered,fe as isCodexUserPromptSubmitHookRegistered,se as isMcpServerConfigured,le as isOpenCodeMcpServerConfigured,Y as isProjectSessionStartHookRegistered,Z as isSessionStartHookRegistered,ee as isUserPromptSubmitHookRegistered,oe as removeSessionStartHook,te as removeUserPromptSubmitHook};
package/dist/cubes.d.ts CHANGED
@@ -104,21 +104,4 @@ export declare function getCodexWakeTarget(cubeId: string, droneId: string): Pro
104
104
  * cubes.ts stays free of the codex-remote dependency.
105
105
  */
106
106
  export declare function pruneDeadCodexWakeTargets(socketLiveness: (socketPath: string) => boolean | null): Promise<void>;
107
- export interface LaunchModelRecord {
108
- /** Model descriptor, e.g. `ollama:qwen3-coder-next` or `claude:claude-opus-4-8`. */
109
- model: string;
110
- /** Resolved Ollama host for an ollama model; null for claude. */
111
- ollamaBaseUrl: string | null;
112
- }
113
- export declare function setLaunchModel(cubeId: string, worktreePath: string, record: LaunchModelRecord, file?: string): Promise<void>;
114
- /**
115
- * Read the launch model for a worktree. On a worktree-key miss, if `priorDroneId`
116
- * is supplied (the seat that previously occupied this worktree, derived from
117
- * cubes.json), a legacy droneId-keyed entry is migrated ON READ to the worktree
118
- * key and the old key dropped (a move, not a copy — no silent loss). Without a
119
- * prior droneId the legacy entry is unreachable but LEFT INTACT for a one-time
120
- * operator re-pin (HARD constraint: never silently destroy a stored entry).
121
- */
122
- export declare function getLaunchModel(cubeId: string, worktreePath: string, priorDroneId?: string | null, file?: string): Promise<LaunchModelRecord | null>;
123
- export declare function clearLaunchModel(cubeId: string, worktreePath: string, file?: string): Promise<void>;
124
107
  //# sourceMappingURL=cubes.d.ts.map