borgmcp 1.0.45 → 1.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,48 +1,48 @@
1
- import{dirname as re,basename as E}from"node:path";import{randomUUID as ne}from"node:crypto";import{roleSlug as oe,matchRoleByName as ie,pickDefaultRole as ae}from"./role-resolver.js";import{deriveCubeName as le,parseGitRemote as se,sanitizeRemoteUrl as ce}from"./cube-name.js";import{validateName as G}from"./name-validator.js";import{renderAssimilationWelcome as ue}from"./assimilate-welcome.js";import{shellEscape as me}from"./shell-escape.js";import{withCodexCwdArg as de}from"./codex-remote.js";import{buildAgentKickoffPrompt as fe,buildKickoffWakePathClause as he,recordCodexWakeTarget as ge,socketPathFromRemoteArgs as we}from"./codex-launch.js";import{perWorktreeBranchName as K,adoptWorktree as be,computeWorktreePath as q}from"./worktree-lifecycle.js";import{DroneEvictedError as ke}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as ye}from"./launch-gate.js";import{resolveLaunchEnv as ve,resolveOllamaBaseUrl as $e,parseModel as pe}from"./model-presets.js";async function Ye(r,e){if(r.role!==void 0){const t=G(r.role);if(!t.ok)return e.stderr(t.error+`
2
- `),1}if(r.flags.worktree!==void 0){const t=G(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
3
- `),1}let i=await e.getCachedAuth();if(!i){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;i=await e.runSetup()}const a=e.findProjectRoot(e.cwd());let n;if(r.flags.cubeName)n=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],a),o=t.status===0?t.stdout:null;if(n=le(a,o),o){const u=ce(o),m=u?se(u):null;u&&!m&&n&&e.stderr(`couldn't parse git remote '${u}' \u2014 using directory name '${n}' as cube name
4
- `)}}let l=null;if(n&&n.includes("@")&&n.includes(":")){const t=n.lastIndexOf(":");l={ownerEmail:n.substring(0,t),cubeName:n.substring(t+1)},n=l.cubeName}const C=e.cwd();e.stderr(`Checking your cubes\u2026
5
- `);let _;try{_=await e.listCubes(i.apiUrl,i.token)}catch(t){const o=t instanceof Error?t.message:String(t);if(o.includes("Authentication required")||o.includes("Authentication expired"))e.stderr(`Re-authenticating...
6
- `),i=await e.runSetup(),_=await e.listCubes(i.apiUrl,i.token);else throw t}const A=_.find(t=>t.name===n);if(!A&&l)return e.stderr(`No cube named '${l.cubeName}' accessible to you owned by '${l.ownerEmail}'. Did you accept their invite? See borgmcp.ai/dashboard.
7
- `),1;let s,N;if(A)s=await e.getCube(i.apiUrl,i.token,A.id),N=!1;else{let t;if(r.flags.template)t=r.flags.template;else if(r.flags.noTemplate)t=void 0;else if(e.isTTY())if(r.flags.yes)t="starter";else{const o=await e.listTemplates(i.apiUrl,i.token),u=["First drone joining a new cube. Apply a template?"];o.forEach((v,$)=>{const S=$===0?" (default)":"";u.push(` ${$+1}) ${v.name}${S} \u2014 ${v.description}`)}),u.push(` ${o.length+1}) skip \u2014 no template`);const m=(await e.prompt(u.join(`
1
+ import{dirname as ne,basename as C}from"node:path";import{randomUUID as oe}from"node:crypto";import{roleSlug as ie,matchRoleByName as ae,pickDefaultRole as le}from"./role-resolver.js";import{deriveCubeName as se,parseGitRemote as ce,sanitizeRemoteUrl as ue}from"./cube-name.js";import{validateName as K}from"./name-validator.js";import{renderAssimilationWelcome as me}from"./assimilate-welcome.js";import{shellEscape as fe}from"./shell-escape.js";import{withCodexCwdArg as de}from"./codex-remote.js";import{buildAgentKickoffPrompt as he,buildKickoffWakePathClause as ge,recordCodexWakeTarget as we,socketPathFromRemoteArgs as be}from"./codex-launch.js";import{perWorktreeBranchName as O,adoptWorktree as ke,computeWorktreePath as q,localBranchExists as z,isMerged as ye}from"./worktree-lifecycle.js";import{DroneEvictedError as ve}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as $e}from"./launch-gate.js";import{resolveLaunchEnv as pe,resolveOllamaBaseUrl as xe,parseModel as Se}from"./model-presets.js";async function Ke(r,e){if(r.role!==void 0){const t=K(r.role);if(!t.ok)return e.stderr(t.error+`
2
+ `),1}if(r.flags.worktree!==void 0){const t=K(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 n=e.findProjectRoot(e.cwd());let o;if(r.flags.cubeName)o=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],n),i=t.status===0?t.stdout:null;if(o=se(n,i),i){const u=ue(i),m=u?ce(u):null;u&&!m&&o&&e.stderr(`couldn't parse git remote '${u}' \u2014 using directory name '${o}' as cube name
4
+ `)}}let l=null;if(o&&o.includes("@")&&o.includes(":")){const t=o.lastIndexOf(":");l={ownerEmail:o.substring(0,t),cubeName:o.substring(t+1)},o=l.cubeName}const R=e.cwd();e.stderr(`Checking your cubes\u2026
5
+ `);let A;try{A=await e.listCubes(a.apiUrl,a.token)}catch(t){const i=t instanceof Error?t.message:String(t);if(i.includes("Authentication required")||i.includes("Authentication expired"))e.stderr(`Re-authenticating...
6
+ `),a=await e.runSetup(),A=await e.listCubes(a.apiUrl,a.token);else throw t}const N=A.find(t=>t.name===o);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 s,P;if(N)s=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 i=await e.listTemplates(a.apiUrl,a.token),u=["First drone joining a new cube. Apply a template?"];i.forEach(($,p)=>{const _=p===0?" (default)":"";u.push(` ${p+1}) ${$.name}${_} \u2014 ${$.description}`)}),u.push(` ${i.length+1}) skip \u2014 no template`);const m=(await e.prompt(u.join(`
8
8
  `)+`
9
- [1]: `)).trim(),y=m===""?1:parseInt(m,10);if(Number.isNaN(y)||y<1||y>o.length+1)return e.stderr(`invalid choice "${m}"
10
- `),1;t=y<=o.length?o[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(),y=m===""?1:parseInt(m,10);if(Number.isNaN(y)||y<1||y>i.length+1)return e.stderr(`invalid choice "${m}"
10
+ `),1;t=y<=i.length?i[y-1].name:void 0}else{if(!r.flags.yes)return e.stderr(`cube creation needs a template choice but stdin is non-interactive.
11
11
  Pass --template <name>, --no-template, or --yes (defaults to starter).
12
- `),1;t="starter"}e.stderr(n?`Creating cube '${n}'\u2026
12
+ `),1;t="starter"}e.stderr(o?`Creating cube '${o}'\u2026
13
13
  `:`Creating your cube\u2026
14
- `),s=await e.createCube(i.apiUrl,i.token,t?{name:n??void 0,template:t}:{name:n??void 0}),N=!0}let d;if(r.role!==void 0){if(d=ie(s.roles,r.role),!d){const t=s.roles.map(m=>m.name).join(", "),o=Se(r.role,s.roles.map(m=>m.name)),u=o?` Did you mean "${o}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${s.name}". Available: ${t}.${u}
14
+ `),s=await e.createCube(a.apiUrl,a.token,t?{name:o??void 0,template:t}:{name:o??void 0}),P=!0}let d;if(r.role!==void 0){if(d=ae(s.roles,r.role),!d){const t=s.roles.map(m=>m.name).join(", "),i=_e(r.role,s.roles.map(m=>m.name)),u=i?` Did you mean "${i}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${s.name}". Available: ${t}.${u}
15
15
  (Use --template <name> on first-drone setup or run \`borg_create-role\` from inside Claude.)
16
- `),1}}else if(d=ae(s.roles,{isFirstDrone:N}),!d)return e.stderr(`cube "${s.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 p=await e.getActiveCube();let x;if(p&&r.flags.here)if(p.cubeId===s.id)x=p.droneId;else return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
18
- `),1;const O=r.flags.worktree!==void 0||p!==null&&!r.flags.here,J=x??p?.droneId??null,P=O?null:await e.getLaunchModel(s.id,a,J),w=r.flags.model??P?.model??d.default_model??null,T=$e(process.env,w!=null&&w===P?.model?P?.ollamaBaseUrl:void 0);if(w){const t=await e.checkModelReachable(w,e.fetch,T);if(!t.ok)return e.stderr(`${t.message}
19
- `),1}const f=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${s.name}' as ${d.name}\u2026
20
- `);let c;try{c=await e.assimilate(i.apiUrl,i.token,{cube_id:s.id,role_id:d.id,hostname:e.getHostname(),agent_kind:f,model:w,...x?{prior_drone_id:x}:{}})}catch(t){if(t instanceof ke&&x!=null)return e.stderr(`seat evicted \u2014 this worktree's saved seat was evicted from the cube. Re-assimilate fresh from a terminal, or remove this worktree.
21
- `),1;const o=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${o}
22
- `),1}const b=s.roles.find(t=>t.id===c.role_id)??d;c.reattached?e.stderr(`re-attached to existing seat ${c.drone_label} (session token rotated, no new drone minted)
23
- `):b.id!==d.id&&e.stderr(`Note: your invite didn't grant the "${d.name}" role \u2014 assimilated as "${b.name}" instead.
24
- `);const V=O;let h=null;if(V){const t=e.runSync("git",["rev-parse","--verify","HEAD"],a);if(t.status!==0)return e.stderr(`sibling worktree spawn requires HEAD pointing at a commit.
16
+ `),1}}else if(d=le(s.roles,{isFirstDrone:P}),!d)return e.stderr(`cube "${s.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===s.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 H=r.flags.worktree!==void 0||x!==null&&!r.flags.here,V=S??x?.droneId??null,T=H?null:await e.getLaunchModel(s.id,n,V),b=r.flags.model??T?.model??d.default_model??null,I=xe(process.env,b!=null&&b===T?.model?T?.ollamaBaseUrl:void 0);if(b){const t=await e.checkModelReachable(b,e.fetch,I);if(!t.ok)return e.stderr(`${t.message}
19
+ `),1}const h=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${s.name}' as ${d.name}\u2026
20
+ `);let c;try{c=await e.assimilate(a.apiUrl,a.token,{cube_id:s.id,role_id:d.id,hostname:e.getHostname(),agent_kind:h,model:b,...S?{prior_drone_id:S}:{}})}catch(t){if(t instanceof ve&&S!=null)return e.stderr(`seat evicted \u2014 this worktree's saved seat was evicted from the cube. Re-assimilate fresh from a terminal, or remove this worktree.
21
+ `),1;const i=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${i}
22
+ `),1}const k=s.roles.find(t=>t.id===c.role_id)??d;c.reattached?e.stderr(`re-attached to existing seat ${c.drone_label} (session token rotated, no new drone minted)
23
+ `):k.id!==d.id&&e.stderr(`Note: your invite didn't grant the "${d.name}" role \u2014 assimilated as "${k.name}" instead.
24
+ `);const Q=H;let g=null;if(Q){const t=e.runSync("git",["rev-parse","--verify","HEAD"],n);if(t.status!==0)return e.stderr(`sibling worktree spawn requires HEAD pointing at a commit.
25
25
  Fix: create at least one commit (\`git commit --allow-empty -m "initial"\`)
26
26
  OR: pass --here to skip the sibling spawn and use the current directory
27
- `),1;e.runSync("git",["fetch","origin"],a);let o="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],a).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],a).status===0&&(o="origin/master");const m=t.stdout.trim(),y=e.runSync("git",["rev-parse",o],a).stdout.trim();m!==y&&e.stderr(`note: local HEAD (${m.slice(0,7)}) differs from ${o} (${y.slice(0,7)}); new worktree will start on ${o}
28
- `);const v=E(a),$=r.flags.worktree??oe(b.name);if($.length===0)return e.stderr(`cannot derive a worktree name from role "${b.name}"; pass an explicit --worktree <name>
29
- `),1;const S=e.homedir();let k=q(S,v,$),F=2;for(;e.pathExists(k)||Re(e,a,k);)k=q(S,v,$,F),F++;e.mkdirp(re(k));const W=K(E(k),v),Y=e.runSync("git",["worktree","add","-b",W,k,o],a);if(Y.status!==0)return e.stderr(`git worktree add failed: ${z(Y.stderr)}
30
- `),1;e.stderr(`spawned sibling worktree at ${k} on branch ${W} (${o}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
31
- `),e.chdir(k),e.stderr(xe(k,W,a)),h=e.cwd()}try{await e.setActiveCube({cubeId:c.cube_id,droneId:c.drone_id,name:s.name,sessionToken:c.session_token,droneLabel:c.drone_label,apiUrl:i.apiUrl,roleName:b.name,isHumanSeat:b.is_human_seat,...b.role_class?{roleClass:b.role_class}:{}})}catch(t){const o=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${o}
32
- `),h){const u=e.runSync("git",["worktree","remove","--force",h],a);u.status===0?e.stderr(`rolled back spawned worktree at ${h}
33
- `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${h}\` (rollback attempt failed: ${z(u.stderr).trim()||"unknown"})
34
- `)}return 1}e.setTerminalTitle(c.drone_label,s.name);const Q=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(ue(b.name,s.name,Q));const g=e.cwd();try{e.installProjectSessionHook(g)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${g}; it will be re-attempted on the next borg launch
35
- `)}if(!h){e.runSync("git",["fetch","origin","--prune"],g);const t=K(E(g),E(a)),o=be(e.runSync,g,t,"origin/main");o.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
36
- `),e.stderr(Ce(g,t))):o.message&&e.stderr(`worktree sync: ${o.message}
37
- `)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${f} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
38
- `);const X=e.getInboxPath(c.cube_id,c.drone_id),I=f==="codex"?`borg-wake-${ne()}`:null,Z=he(f==="codex"?"codex":"claude",f==="claude"?X:null);let U,H=[],R,D=null,L=null;const M=e.findProjectRoot(g);w?await e.setLaunchModel(c.cube_id,M,{model:w,ollamaBaseUrl:pe(w).kind==="ollama"?T:null}):await e.clearLaunchModel(c.cube_id,M);const B=ve(w,T),j={...process.env,...B.set,BORG_SESSION:"1"};for(const t of B.unset)delete j[t];if(f==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
39
- `),U="\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."):U="Codex wake-path capability check passed: remote-control socket established for this session.",H=t.args,Object.keys(t.env).length>0&&Object.assign(j,t.env),D=we(t.args),L=t.server?.cleanup??null}R=[fe({cli:f,codexWakeNonce:I,monitorClause:Z,codexWakePathClause:U})],f==="codex"&&(R=[...ye(),...H,...de(R,g)]);const ee=e.exec(f,R,g,j);f==="codex"&&D&&I&&ge({deps:e,cubeId:c.cube_id,droneId:c.drone_id,socketPath:D,cwd:g,previewNeedle:I,launchedAtSeconds:Math.floor(Date.now()/1e3)});const te=await ee;if(L)try{L()}catch{}return h&&C!==h&&e.stderr(`
40
- Agent exited. You were working in ${h}; your shell is back in ${C}.
27
+ `),1;e.runSync("git",["fetch","origin"],n);let i="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],n).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],n).status===0&&(i="origin/master");const m=t.stdout.trim(),y=e.runSync("git",["rev-parse",i],n).stdout.trim();m!==y&&e.stderr(`note: local HEAD (${m.slice(0,7)}) differs from ${i} (${y.slice(0,7)}); new worktree will start on ${i}
28
+ `);const $=C(n),p=r.flags.worktree??ie(k.name);if(p.length===0)return e.stderr(`cannot derive a worktree name from role "${k.name}"; pass an explicit --worktree <name>
29
+ `),1;const _=e.homedir();let f=q(_,$,p),v=O(C(f),$),Y=2;for(;e.pathExists(f)||Ee(e,n,f)||z(e.runSync,n,v)&&!ye(e.runSync,n,v,i);)f=q(_,$,p,Y),v=O(C(f),$),Y++;e.mkdirp(ne(f));const G=z(e.runSync,n,v)?e.runSync("git",["worktree","add",f,v],n):e.runSync("git",["worktree","add","-b",v,f,i],n);if(G.status!==0)return e.stderr(`git worktree add failed: ${J(G.stderr)}
30
+ `),1;e.stderr(`spawned sibling worktree at ${f} on branch ${v} (${i}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
31
+ `),e.chdir(f),e.stderr(Ce(f,v,n)),g=e.cwd()}try{await e.setActiveCube({cubeId:c.cube_id,droneId:c.drone_id,name:s.name,sessionToken:c.session_token,droneLabel:c.drone_label,apiUrl:a.apiUrl,roleName:k.name,isHumanSeat:k.is_human_seat,...k.role_class?{roleClass:k.role_class}:{}})}catch(t){const i=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${i}
32
+ `),g){const u=e.runSync("git",["worktree","remove","--force",g],n);u.status===0?e.stderr(`rolled back spawned worktree at ${g}
33
+ `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${g}\` (rollback attempt failed: ${J(u.stderr).trim()||"unknown"})
34
+ `)}return 1}e.setTerminalTitle(c.drone_label,s.name);const X=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(me(k.name,s.name,X));const w=e.cwd();try{e.installProjectSessionHook(w)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${w}; it will be re-attempted on the next borg launch
35
+ `)}if(!g){e.runSync("git",["fetch","origin","--prune"],w);const t=O(C(w),C(n)),i=ke(e.runSync,w,t,"origin/main");i.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
36
+ `),e.stderr(Re(w,t))):i.message&&e.stderr(`worktree sync: ${i.message}
37
+ `)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${h} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
38
+ `);const Z=e.getInboxPath(c.cube_id,c.drone_id),U=h==="codex"?`borg-wake-${oe()}`:null,ee=ge(h==="codex"?"codex":"claude",h==="claude"?Z:null);let D,M=[],E,L=null,j=null;const B=e.findProjectRoot(w);b?await e.setLaunchModel(c.cube_id,B,{model:b,ollamaBaseUrl:Se(b).kind==="ollama"?I:null}):await e.clearLaunchModel(c.cube_id,B);const F=pe(b,I),W={...process.env,...F.set,BORG_SESSION:"1"};for(const t of F.unset)delete W[t];if(h==="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.",M=t.args,Object.keys(t.env).length>0&&Object.assign(W,t.env),L=be(t.args),j=t.server?.cleanup??null}E=[he({cli:h,codexWakeNonce:U,monitorClause:ee,codexWakePathClause:D})],h==="codex"&&(E=[...$e(),...M,...de(E,w)]);const te=e.exec(h,E,w,W);h==="codex"&&L&&U&&we({deps:e,cubeId:c.cube_id,droneId:c.drone_id,socketPath:L,cwd:w,previewNeedle:U,launchedAtSeconds:Math.floor(Date.now()/1e3)});const re=await te;if(j)try{j()}catch{}return g&&R!==g&&e.stderr(`
40
+ Agent exited. You were working in ${g}; your shell is back in ${R}.
41
41
  To return:
42
- cd ${me(h)}
43
- `),te}function xe(r,e,i){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 ${i}\` or operate on the primary checkout ${i}: 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 Ce(r,e){return`
42
+ cd ${fe(g)}
43
+ `),re}function Ce(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 Re(r,e){return`
46
46
  WORKTREE STEERING: This checkout is now on branch ${e}. Do ALL work HERE in ${r} \u2014 cut your feature branch (fix/.../feat/...) off ${e}, use relative paths / your cwd.
47
- `}function z(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function Re(r,e,i){const a=r.runSync("git",["worktree","list","--porcelain"],e);return a.status!==0?!1:a.stdout.split(`
48
- `).some(n=>n===`worktree ${i}`)}function Se(r,e){if(e.length===0)return null;const i=r.toLowerCase();let a=null;for(const n of e){const l=Ee(i,n.toLowerCase());l<=2&&(a===null||l<a.distance)&&(a={name:n,distance:l})}return a?a.name:null}function Ee(r,e){if(r===e)return 0;if(r.length===0)return e.length;if(e.length===0)return r.length;const i=new Array(e.length+1),a=new Array(e.length+1);for(let n=0;n<=e.length;n++)i[n]=n;for(let n=1;n<=r.length;n++){a[0]=n;for(let l=1;l<=e.length;l++){const C=r[n-1]===e[l-1]?0:1;a[l]=Math.min(a[l-1]+1,i[l]+1,i[l-1]+C)}for(let l=0;l<=e.length;l++)i[l]=a[l]}return i[e.length]}export{Ye as runAssimilate,z as safeStderr,Se as suggestRoleName};
47
+ `}function J(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function Ee(r,e,a){const n=r.runSync("git",["worktree","list","--porcelain"],e);return n.status!==0?!1:n.stdout.split(`
48
+ `).some(o=>o===`worktree ${a}`)}function _e(r,e){if(e.length===0)return null;const a=r.toLowerCase();let n=null;for(const o of e){const l=Ae(a,o.toLowerCase());l<=2&&(n===null||l<n.distance)&&(n={name:o,distance:l})}return n?n.name:null}function Ae(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),n=new Array(e.length+1);for(let o=0;o<=e.length;o++)a[o]=o;for(let o=1;o<=r.length;o++){n[0]=o;for(let l=1;l<=e.length;l++){const R=r[o-1]===e[l-1]?0:1;n[l]=Math.min(n[l-1]+1,a[l]+1,a[l-1]+R)}for(let l=0;l<=e.length;l++)a[l]=n[l]}return a[e.length]}export{Ke as runAssimilate,J as safeStderr,_e as suggestRoleName};
@@ -1,13 +1,14 @@
1
- import{spawnSync as P}from"node:child_process";import{realpathSync as V}from"node:fs";import{sep as R}from"node:path";import{homedir as I}from"node:os";import c from"chalk";import{classifyDirty as A,isMerged as D,worktreesHome as L}from"./worktree-lifecycle.js";import{readAllProjectIdentities as W}from"./cubes.js";import{defaultProbeSeat as x}from"./seat-probe.js";const $="origin/main",_=new Set(["node_modules","dist","build",".next","coverage",".wrangler",".playwright-mcp",".claude"]),C=new Set([".DS_Store","worker-configuration.d.ts"]),F=[".log",".tsbuildinfo",".tmp"];function O(r){const n=r.replace(/\/+$/,""),t=n.split("/").filter(s=>s.length>0);if(t.some(s=>_.has(s)))return!0;const e=t[t.length-1]??n;return!!(C.has(e)||F.some(s=>e.endsWith(s)))}function j(r,n){const t=r("git",["status","--porcelain","--ignored"],n);if(t.status!==0)return["<git status --ignored failed \u2014 cannot verify clean>"];const e=[];for(const s of t.stdout.split(`
2
- `)){if(!s.startsWith("!!"))continue;const i=s.slice(3).trim();i.length!==0&&(O(i)||e.push(i))}return e}const G={runSync:(r,n,t)=>{const e=P(r,n,{cwd:t,encoding:"utf-8"});return{status:e.status,stdout:e.stdout??"",stderr:e.stderr??""}},homeDir:()=>I(),cwd:()=>process.cwd(),listSeats:()=>W(),probeSeat:x,realpath:r=>V(r),stdout:r=>process.stdout.write(r),stderr:r=>process.stderr.write(r)};function H(r,n,t){let e,s;try{e=r(n),s=r(t)}catch{return!1}if(s===e)return!1;const i=e.endsWith(R)?e:e+R;return s.startsWith(i)}function ee(r){return v(r).map(n=>n.path)}function v(r){const n=[];let t=null;for(const e of r.split(`
3
- `))e.startsWith("worktree ")?(t&&n.push(t),t={path:e.slice(9).trim(),branch:null}):e.startsWith("branch ")&&t&&(t.branch=e.slice(7).trim().replace(/^refs\/heads\//,""));return t&&n.push(t),n}async function M(r,n,t,e){const{runSync:s}=r,i=A(s,n),l=i.staged.length+i.unstaged.length+i.untracked.length;if(l>0)return{reason:"SURVIVES-dirty",detail:`${l} uncommitted file(s)`};const u=j(s,n);if(u.length>0)return{reason:"SURVIVES-clobber",detail:`gitignored local state: ${u.slice(0,3).join(", ")}${u.length>3?` (+${u.length-3})`:""}`};if(t===null)return{reason:"SURVIVES-detached",detail:"detached HEAD \u2014 cannot verify merged"};if(!D(s,n,t,$))return{reason:"SURVIVES-unmerged",detail:`${t} not merged into ${$}`};switch(await r.probeSeat(e.sessionToken,e.apiUrl)){case"evicted":return{reason:"PRUNABLE",detail:"410 DRONE_EVICTED (clean + merged)"};case"frozen":return{reason:"SURVIVES-frozen",detail:"423 DRONE_FROZEN (reversible)"};case"live":return{reason:"SURVIVES-live",detail:"seat resolves (drone alive)"};default:return{reason:"UNKNOWN-indeterminate",detail:"probe returned 401/network/transient (or gh#877 not yet deployed) \u2014 not deleting"}}}async function T(r){const{runSync:n,realpath:t}=r,e=r.cwd();if(n("git",["rev-parse","--show-toplevel"],e).status!==0)return{rows:[],error:`not in a git repository (cwd: ${e})`};const s=n("git",["fetch","origin","--prune"],e);if(s.status!==0)return{rows:[],error:`git fetch origin failed: ${s.stderr.trim()}`};const i=n("git",["worktree","list","--porcelain"],e);if(i.status!==0)return{rows:[],error:`git worktree list failed: ${i.stderr.trim()}`};const l=v(i.stdout),u=new Map(l.map(a=>[a.path,a.branch])),d=l.map(a=>a.path),b=d[0],w=r.homeDir(),p=L(w),o=y(t,e),f=b?y(t,b):null,E=await r.listSeats(),S=new Map;for(const{projectPath:a,cube:h}of E){const g=y(t,a);g&&S.set(g,h)}const m=[];for(const a of d){const h=y(t,a);if(!h||f&&h===f)continue;const g=S.get(h),U=H(t,p,a);if(h===o){m.push({worktreePath:a,wtBranch:null,reason:"SURVIVES-self",detail:"current worktree"});continue}if(!U){g&&m.push({worktreePath:a,wtBranch:null,reason:"LEGACY-manual-review",detail:"borg seat outside worktreesHome (pre-gh#556 sibling)"});continue}if(!g){m.push({worktreePath:a,wtBranch:null,reason:"UNKNOWN-no-seat",detail:"no cubes.json seat \u2014 manual review"});continue}const k=u.get(a)??null,{reason:B,detail:N}=await M(r,a,k,g);m.push({worktreePath:a,wtBranch:k,reason:B,detail:N})}return{rows:m}}function y(r,n){try{return r(n)}catch{return null}}async function te(r={},n={prune:!1}){const t={...G,...r},{stdout:e,stderr:s,runSync:i}=t,{rows:l,error:u}=await T(t);if(u)return s(c.red(`\u25FC borg cleanup: ${u}
4
- `)),1;const d=l.filter(o=>o.reason==="PRUNABLE"),b=l.filter(o=>o.reason!=="PRUNABLE");if(l.length===0)return e(c.blue(`\u25FC borg cleanup: no borg-managed worktrees found.
5
- `)),0;e(c.bold(`\u25FC borg cleanup report:
6
- `));for(const o of l){const f=o.reason==="PRUNABLE"?c.yellow(o.reason):c.gray(o.reason);e(` ${f} ${o.worktreePath}${o.detail?c.gray(` \u2014 ${o.detail}`):""}
7
- `)}if(e(c.gray(`\u25FC ${d.length} prunable, ${b.length} kept.
8
- `)),!n.prune)return d.length>0&&e(c.gray("\u25FC Dry-run \u2014 nothing deleted. Re-run with `--prune` to remove the PRUNABLE worktree(s).\n")),0;if(d.length===0)return e(c.blue(`\u25FC Nothing to prune.
9
- `)),0;let w=0,p=0;for(const o of d){const f=i("git",["worktree","remove",o.worktreePath],t.cwd());if(f.status!==0){o.prune="remove-failed",p++,s(c.red(` \u2717 worktree remove ${o.worktreePath}: ${f.stderr.trim()}
10
- `));continue}if(o.wtBranch){const E=i("git",["branch","-d",o.wtBranch],t.cwd());if(E.status!==0){o.prune="branch-delete-failed",s(c.yellow(` \u26A0 removed worktree but \`git branch -d ${o.wtBranch}\` refused: ${E.stderr.trim()}
11
- `)),w++;continue}}o.prune="removed",w++,e(c.blue(` \u2713 pruned ${o.worktreePath}${o.wtBranch?` + branch ${o.wtBranch}`:""}
12
- `))}return e(c.gray(`\u25FC Pruned ${w} worktree(s)${p>0?`, ${p} failed`:""}.
13
- `)),p>0?1:0}function re(r){let n=!1;for(const t of r)if(t==="--prune")n=!0;else return{ok:!1,error:`unexpected argument: ${t}. Usage: borg cleanup [--prune]`};return{ok:!0,options:{prune:n}}}export{T as buildCleanupReport,j as clobberClassIgnored,O as isRegenerableIgnored,H as isStrictlyUnder,re as parseCleanupArgs,v as parseWorktreeEntries,ee as parseWorktreeList,te as runCleanup};
1
+ import{spawnSync as I}from"node:child_process";import{realpathSync as W}from"node:fs";import{sep as R,basename as v,dirname as A}from"node:path";import{homedir as D}from"node:os";import c from"chalk";import{classifyDirty as L,isMerged as B,localBranchExists as x,perWorktreeBranchName as _,worktreesHome as C}from"./worktree-lifecycle.js";import{readAllProjectIdentities as F}from"./cubes.js";import{defaultProbeSeat as O}from"./seat-probe.js";const S="origin/main",j=new Set(["node_modules","dist","build",".next","coverage",".wrangler",".playwright-mcp",".claude"]),G=new Set([".DS_Store","worker-configuration.d.ts"]),H=[".log",".tsbuildinfo",".tmp"];function M(r){const n=r.replace(/\/+$/,""),e=n.split("/").filter(s=>s.length>0);if(e.some(s=>j.has(s)))return!0;const t=e[e.length-1]??n;return!!(G.has(t)||H.some(s=>t.endsWith(s)))}function T(r,n){const e=r("git",["status","--porcelain","--ignored"],n);if(e.status!==0)return["<git status --ignored failed \u2014 cannot verify clean>"];const t=[];for(const s of e.stdout.split(`
2
+ `)){if(!s.startsWith("!!"))continue;const a=s.slice(3).trim();a.length!==0&&(M(a)||t.push(a))}return t}const z={runSync:(r,n,e)=>{const t=I(r,n,{cwd:e,encoding:"utf-8"});return{status:t.status,stdout:t.stdout??"",stderr:t.stderr??""}},homeDir:()=>D(),cwd:()=>process.cwd(),listSeats:()=>F(),probeSeat:O,realpath:r=>W(r),stdout:r=>process.stdout.write(r),stderr:r=>process.stderr.write(r)};function K(r,n,e){let t,s;try{t=r(n),s=r(e)}catch{return!1}if(s===t)return!1;const a=t.endsWith(R)?t:t+R;return s.startsWith(a)}function oe(r){return N(r).map(n=>n.path)}function N(r){const n=[];let e=null;for(const t of r.split(`
3
+ `))t.startsWith("worktree ")?(e&&n.push(e),e={path:t.slice(9).trim(),branch:null}):t.startsWith("branch ")&&e&&(e.branch=t.slice(7).trim().replace(/^refs\/heads\//,""));return e&&n.push(e),n}async function X(r,n,e,t){const{runSync:s}=r,a=L(s,n),u=a.staged.length+a.unstaged.length+a.untracked.length;if(u>0)return{reason:"SURVIVES-dirty",detail:`${u} uncommitted file(s)`};const d=T(s,n);if(d.length>0)return{reason:"SURVIVES-clobber",detail:`gitignored local state: ${d.slice(0,3).join(", ")}${d.length>3?` (+${d.length-3})`:""}`};if(e===null)return{reason:"SURVIVES-detached",detail:"detached HEAD \u2014 cannot verify merged"};if(!B(s,n,e,S))return{reason:"SURVIVES-unmerged",detail:`${e} not merged into ${S}`};switch(await r.probeSeat(t.sessionToken,t.apiUrl)){case"evicted":return{reason:"PRUNABLE",detail:"410 DRONE_EVICTED (clean + merged)"};case"frozen":return{reason:"SURVIVES-frozen",detail:"423 DRONE_FROZEN (reversible)"};case"live":return{reason:"SURVIVES-live",detail:"seat resolves (drone alive)"};default:return{reason:"UNKNOWN-indeterminate",detail:"probe returned 401/network/transient (or gh#877 not yet deployed) \u2014 not deleting"}}}async function Y(r){const{runSync:n,realpath:e}=r,t=r.cwd();if(n("git",["rev-parse","--show-toplevel"],t).status!==0)return{rows:[],error:`not in a git repository (cwd: ${t})`};const s=n("git",["fetch","origin","--prune"],t);if(s.status!==0)return{rows:[],error:`git fetch origin failed: ${s.stderr.trim()}`};const a=n("git",["worktree","list","--porcelain"],t);if(a.status!==0)return{rows:[],error:`git worktree list failed: ${a.stderr.trim()}`};const u=N(a.stdout),d=new Map(u.map(i=>[i.path,i.branch])),h=u.map(i=>i.path),E=h[0],b=r.homeDir(),g=C(b),o=k(e,t),p=E?k(e,E):null,y=await r.listSeats(),f=new Map;for(const{projectPath:i,cube:w}of y){const m=k(e,i);m&&f.set(m,w)}const l=[];for(const i of h){const w=k(e,i);if(!w||p&&w===p)continue;const m=f.get(w),U=K(e,g,i);if(w===o){l.push({worktreePath:i,wtBranch:null,reason:"SURVIVES-self",detail:"current worktree"});continue}if(!U){m&&l.push({worktreePath:i,wtBranch:null,reason:"LEGACY-manual-review",detail:"borg seat outside worktreesHome (pre-gh#556 sibling)"});continue}if(!m){l.push({worktreePath:i,wtBranch:null,reason:"UNKNOWN-no-seat",detail:"no cubes.json seat \u2014 manual review"});continue}const $=d.get(i)??null,{reason:P,detail:V}=await X(r,i,$,m);l.push({worktreePath:i,wtBranch:$,reason:P,detail:V})}return{rows:l}}function k(r,n){try{return r(n)}catch{return null}}async function se(r={},n={prune:!1}){const e={...z,...r},{stdout:t,stderr:s,runSync:a}=e,{rows:u,error:d}=await Y(e);if(d)return s(c.red(`\u25FC borg cleanup: ${d}
4
+ `)),1;const h=u.filter(o=>o.reason==="PRUNABLE"),E=u.filter(o=>o.reason!=="PRUNABLE");if(u.length===0)return t(c.blue(`\u25FC borg cleanup: no borg-managed worktrees found.
5
+ `)),0;t(c.bold(`\u25FC borg cleanup report:
6
+ `));for(const o of u){const p=o.reason==="PRUNABLE"?c.yellow(o.reason):c.gray(o.reason);t(` ${p} ${o.worktreePath}${o.detail?c.gray(` \u2014 ${o.detail}`):""}
7
+ `)}if(t(c.gray(`\u25FC ${h.length} prunable, ${E.length} kept.
8
+ `)),!n.prune)return h.length>0&&t(c.gray("\u25FC Dry-run \u2014 nothing deleted. Re-run with `--prune` to remove the PRUNABLE worktree(s).\n")),0;if(h.length===0)return t(c.blue(`\u25FC Nothing to prune.
9
+ `)),0;let b=0,g=0;for(const o of h){const p=a("git",["worktree","remove",o.worktreePath],e.cwd());if(p.status!==0){o.prune="remove-failed",g++,s(c.red(` \u2717 worktree remove ${o.worktreePath}: ${p.stderr.trim()}
10
+ `));continue}if(o.wtBranch){const l=a("git",["branch","-d",o.wtBranch],e.cwd());if(l.status!==0){o.prune="branch-delete-failed",s(c.yellow(` \u26A0 removed worktree but \`git branch -d ${o.wtBranch}\` refused: ${l.stderr.trim()}
11
+ `)),b++;continue}}let y="";const f=_(v(o.worktreePath),v(A(o.worktreePath)));if(f!==o.wtBranch&&x(a,e.cwd(),f)&&B(a,e.cwd(),f,S)){const l=a("git",["branch","-d",f],e.cwd());l.status===0?y=` + base ${f}`:s(c.yellow(` \u26A0 left dangling base branch ${f}: \`git branch -d\` refused: ${l.stderr.trim()}
12
+ `))}o.prune="removed",b++,t(c.blue(` \u2713 pruned ${o.worktreePath}${o.wtBranch?` + branch ${o.wtBranch}`:""}${y}
13
+ `))}return t(c.gray(`\u25FC Pruned ${b} worktree(s)${g>0?`, ${g} failed`:""}.
14
+ `)),g>0?1:0}function ae(r){let n=!1;for(const e of r)if(e==="--prune")n=!0;else return{ok:!1,error:`unexpected argument: ${e}. Usage: borg cleanup [--prune]`};return{ok:!0,options:{prune:n}}}export{Y as buildCleanupReport,T as clobberClassIgnored,M as isRegenerableIgnored,K as isStrictlyUnder,ae as parseCleanupArgs,N as parseWorktreeEntries,oe as parseWorktreeList,se as runCleanup};
@@ -30,6 +30,12 @@ export declare function isProjectSessionStartHookRegistered(projectRoot: string)
30
30
  * mutating settings. Returns false on any read error (safe-default).
31
31
  */
32
32
  export declare function isSessionStartHookRegistered(): boolean;
33
+ /**
34
+ * Peek: true iff the Claude UserPromptSubmit audit hook (`borg-log-audit`) is
35
+ * registered. Non-mutating mirror of addUserPromptSubmitHook's idempotency
36
+ * check; used by isClaudeHookConfigPending (gh#844).
37
+ */
38
+ export declare function isUserPromptSubmitHookRegistered(): boolean;
33
39
  /**
34
40
  * Inverse of addSessionStartHook: remove any SessionStart hook entry whose
35
41
  * inner hooks array contains a `borg-regen` command. If multiple commands
@@ -92,4 +98,15 @@ export declare function addCodexMcpServer(): void;
92
98
  export declare function addCodexSessionStartHook(): boolean;
93
99
  export declare function addCodexUserPromptSubmitHook(): boolean;
94
100
  export declare function isCodexHookRegistered(eventName: 'SessionStart' | 'UserPromptSubmit' | 'Stop', command: string, hooksPath?: string): boolean;
101
+ /**
102
+ * Peek: true iff the Codex SessionStart orientation hook (`borg-regen`) is
103
+ * registered. Non-mutating mirror of addCodexSessionStartHook; used to gate
104
+ * that writer + the gh#844 disclosure on whether it would actually mutate.
105
+ */
106
+ export declare function isCodexSessionStartHookRegistered(hooksPath?: string): boolean;
107
+ /**
108
+ * Peek: true iff the Codex UserPromptSubmit audit hook (`borg-log-audit`) is
109
+ * registered. Non-mutating mirror of addCodexUserPromptSubmitHook.
110
+ */
111
+ export declare function isCodexUserPromptSubmitHookRegistered(hooksPath?: string): boolean;
95
112
  //# sourceMappingURL=config-utils.d.ts.map
@@ -1,3 +1,3 @@
1
- import{execSync as u}from"child_process";import n from"fs";import d from"os";import i from"path";import{fileURLToPath as O}from"url";import{dirname as C}from"path";const b=O(import.meta.url),R=C(b),c="borg-regen",f="borg-log-audit",v=i.join(d.homedir(),".claude.json"),E=i.join(d.homedir(),".codex","config.toml"),l=i.join(d.homedir(),".codex","hooks.json"),U="borg";function h(){return i.join(d.homedir(),".claude","settings.json")}function p(){const r=h();if(!n.existsSync(r))return{};const e=n.readFileSync(r,"utf-8");return e.trim()?JSON.parse(e):{}}function y(r){const e=h();n.mkdirSync(i.dirname(e),{recursive:!0}),n.writeFileSync(e,JSON.stringify(r,null,2)+`
2
- `,"utf-8")}function k(r){if(!n.existsSync(r))return{};const e=n.readFileSync(r,"utf-8");return e.trim()?JSON.parse(e):{}}function A(r,e){n.mkdirSync(i.dirname(r),{recursive:!0}),n.writeFileSync(r,JSON.stringify(e,null,2)+`
3
- `,"utf-8")}function B(){return _(h())}function L(r){return _(x(r))}function D(r){return H(x(r))}function x(r){return i.join(r,".claude","settings.local.json")}function _(r){let e;try{e=k(r)}catch(t){return console.error(`\u26A0 Could not parse ${r}: ${t.message}. Skipping hook registration; you can add it manually.`),!1}return e.hooks??={},e.hooks.SessionStart??=[],e.hooks.SessionStart.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===c))?!1:(e.hooks.SessionStart.push({matcher:"*",hooks:[{type:"command",command:c}]}),A(r,e),!0)}function H(r){let e;try{e=k(r)}catch{return!1}const o=e?.hooks?.SessionStart;return Array.isArray(o)?o.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===c)):!1}function N(){let r;try{r=p()}catch{return!1}const e=r?.hooks?.SessionStart;return Array.isArray(e)?e.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===c)):!1}function T(){let r;try{r=p()}catch{return!1}if(!r?.hooks?.SessionStart)return!1;let e=!1;return r.hooks.SessionStart=r.hooks.SessionStart.map(o=>{if(!Array.isArray(o?.hooks))return o;const t=o.hooks.filter(s=>!(s?.type==="command"&&s?.command===c));return t.length!==o.hooks.length?(e=!0,{...o,hooks:t}):o}).filter(o=>Array.isArray(o?.hooks)&&o.hooks.length>0),r.hooks.SessionStart.length===0&&delete r.hooks.SessionStart,Object.keys(r.hooks).length===0&&delete r.hooks,e&&y(r),e}function J(){let r;try{r=p()}catch(o){return console.error(`\u26A0 Could not parse ${h()}: ${o.message}. Skipping audit hook registration.`),!1}return r.hooks??={},r.hooks.UserPromptSubmit??=[],r.hooks.UserPromptSubmit.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===f))?!1:(r.hooks.UserPromptSubmit.push({matcher:"*",hooks:[{type:"command",command:f}]}),y(r),!0)}function K(){let r;try{r=p()}catch{return!1}if(!r?.hooks?.UserPromptSubmit)return!1;let e=!1;return r.hooks.UserPromptSubmit=r.hooks.UserPromptSubmit.map(o=>{if(!Array.isArray(o?.hooks))return o;const t=o.hooks.filter(s=>!(s?.type==="command"&&s?.command===f));return t.length!==o.hooks.length?(e=!0,{...o,hooks:t}):o}).filter(o=>Array.isArray(o?.hooks)&&o.hooks.length>0),r.hooks.UserPromptSubmit.length===0&&delete r.hooks.UserPromptSubmit,Object.keys(r.hooks).length===0&&delete r.hooks,e&&y(r),e}function X(r=v){try{if(!n.existsSync(r))return!1;const e=n.readFileSync(r,"utf-8");if(!e.trim())return!1;const o=JSON.parse(e);if(!o||typeof o!="object")return!1;const t=o.mcpServers;return!t||typeof t!="object"||Array.isArray(t)?!1:U in t}catch{return!1}}function W(r=E){try{if(!n.existsSync(r))return!1;const e=n.readFileSync(r,"utf-8");return/^\s*\[mcp_servers\.borg\]\s*$/m.test(e)&&/^\s*BORG_CODEX_REMOTE_WAKE\s*=\s*"1"\s*$/m.test(e)}catch{return!1}}function Q(){return i.join(R,"index.js")}function V(){try{try{u("claude mcp remove --scope user borg",{stdio:"ignore"})}catch{}u("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(r){throw r.message?.includes("command not found")?new Error("Claude CLI not found. Please install Claude Code first."):new Error(`Failed to add MCP server: ${r.message}`)}}function q(){try{try{u("codex mcp remove borg",{stdio:"ignore"})}catch{}u("codex mcp add borg --env BORG_API_URL="+j(process.env.BORG_API_URL||"https://api.borgmcp.ai")+" --env BORG_CODEX_REMOTE_WAKE=1 -- borg-mcp",{stdio:"inherit",env:{...process.env,BORG_API_URL:process.env.BORG_API_URL||"https://api.borgmcp.ai",BORG_CODEX_REMOTE_WAKE:"1"}})}catch(r){throw r.message?.includes("command not found")?new Error("Codex CLI not found. Please install Codex first."):new Error(`Failed to add MCP server to Codex: ${r.message}`)}}function j(r){return`'${r.replace(/'/g,"'\\''")}'`}function P(r,e,o={}){let t;try{t=k(l)}catch(m){return console.error(`\u26A0 Could not parse ${l}: ${m.message}. Skipping Codex hook registration.`),!1}t.hooks??={},t.hooks[r]??=[];const s=t.hooks[r];if(!Array.isArray(s)||s.some(m=>Array.isArray(m?.hooks)&&m.hooks.some(g=>g?.type==="command"&&g?.command===e)))return!1;const a={hooks:[{type:"command",command:e}]};return o.matcher&&(a.matcher=o.matcher),typeof o.timeout=="number"&&(a.hooks[0].timeout=o.timeout),s.push(a),A(l,t),!0}function z(){return P("SessionStart",c,{matcher:"startup|resume",timeout:30})}function Y(){return P("UserPromptSubmit",f,{timeout:10})}function Z(r,e,o=l){try{const s=k(o)?.hooks?.[r];return Array.isArray(s)?s.some(S=>Array.isArray(S?.hooks)&&S.hooks.some(a=>a?.type==="command"&&a?.command===e)):!1}catch{return!1}}export{q as addCodexMcpServer,z as addCodexSessionStartHook,Y as addCodexUserPromptSubmitHook,V as addMcpServer,L as addProjectSessionStartHook,B as addSessionStartHook,J as addUserPromptSubmitHook,Q as getBinaryPath,Z as isCodexHookRegistered,W as isCodexMcpServerConfigured,X as isMcpServerConfigured,D as isProjectSessionStartHookRegistered,N as isSessionStartHookRegistered,T as removeSessionStartHook,K as removeUserPromptSubmitHook};
1
+ import{execSync as l}from"child_process";import n from"fs";import h from"os";import i from"path";import{fileURLToPath as b}from"url";import{dirname as O}from"path";const R=b(import.meta.url),U=O(R),a="borg-regen",m="borg-log-audit",v=i.join(h.homedir(),".claude.json"),E=i.join(h.homedir(),".codex","config.toml"),u=i.join(h.homedir(),".codex","hooks.json"),H="borg";function p(){return i.join(h.homedir(),".claude","settings.json")}function d(){const r=p();if(!n.existsSync(r))return{};const e=n.readFileSync(r,"utf-8");return e.trim()?JSON.parse(e):{}}function y(r){const e=p();n.mkdirSync(i.dirname(e),{recursive:!0}),n.writeFileSync(e,JSON.stringify(r,null,2)+`
2
+ `,"utf-8")}function S(r){if(!n.existsSync(r))return{};const e=n.readFileSync(r,"utf-8");return e.trim()?JSON.parse(e):{}}function A(r,e){n.mkdirSync(i.dirname(r),{recursive:!0}),n.writeFileSync(r,JSON.stringify(e,null,2)+`
3
+ `,"utf-8")}function L(){return P(p())}function D(r){return P(x(r))}function N(r){return j(x(r))}function x(r){return i.join(r,".claude","settings.local.json")}function P(r){let e;try{e=S(r)}catch(t){return console.error(`\u26A0 Could not parse ${r}: ${t.message}. Skipping hook registration; you can add it manually.`),!1}return e.hooks??={},e.hooks.SessionStart??=[],e.hooks.SessionStart.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===a))?!1:(e.hooks.SessionStart.push({matcher:"*",hooks:[{type:"command",command:a}]}),A(r,e),!0)}function j(r){let e;try{e=S(r)}catch{return!1}const o=e?.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 T(){let r;try{r=d()}catch{return!1}const e=r?.hooks?.SessionStart;return Array.isArray(e)?e.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===a)):!1}function J(){let r;try{r=d()}catch{return!1}const e=r?.hooks?.UserPromptSubmit;return Array.isArray(e)?e.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===m)):!1}function K(){let r;try{r=d()}catch{return!1}if(!r?.hooks?.SessionStart)return!1;let e=!1;return r.hooks.SessionStart=r.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?(e=!0,{...o,hooks:t}):o}).filter(o=>Array.isArray(o?.hooks)&&o.hooks.length>0),r.hooks.SessionStart.length===0&&delete r.hooks.SessionStart,Object.keys(r.hooks).length===0&&delete r.hooks,e&&y(r),e}function X(){let r;try{r=d()}catch(o){return console.error(`\u26A0 Could not parse ${p()}: ${o.message}. Skipping audit hook registration.`),!1}return r.hooks??={},r.hooks.UserPromptSubmit??=[],r.hooks.UserPromptSubmit.some(o=>Array.isArray(o?.hooks)&&o.hooks.some(t=>t?.type==="command"&&t?.command===m))?!1:(r.hooks.UserPromptSubmit.push({matcher:"*",hooks:[{type:"command",command:m}]}),y(r),!0)}function W(){let r;try{r=d()}catch{return!1}if(!r?.hooks?.UserPromptSubmit)return!1;let e=!1;return r.hooks.UserPromptSubmit=r.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?(e=!0,{...o,hooks:t}):o}).filter(o=>Array.isArray(o?.hooks)&&o.hooks.length>0),r.hooks.UserPromptSubmit.length===0&&delete r.hooks.UserPromptSubmit,Object.keys(r.hooks).length===0&&delete r.hooks,e&&y(r),e}function Q(r=v){try{if(!n.existsSync(r))return!1;const e=n.readFileSync(r,"utf-8");if(!e.trim())return!1;const o=JSON.parse(e);if(!o||typeof o!="object")return!1;const t=o.mcpServers;return!t||typeof t!="object"||Array.isArray(t)?!1:H in t}catch{return!1}}function V(r=E){try{if(!n.existsSync(r))return!1;const e=n.readFileSync(r,"utf-8");return/^\s*\[mcp_servers\.borg\]\s*$/m.test(e)&&/^\s*BORG_CODEX_REMOTE_WAKE\s*=\s*"1"\s*$/m.test(e)}catch{return!1}}function q(){return i.join(U,"index.js")}function z(){try{try{l("claude mcp remove --scope user borg",{stdio:"ignore"})}catch{}l("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(r){throw r.message?.includes("command not found")?new Error("Claude CLI not found. Please install Claude Code first."):new Error(`Failed to add MCP server: ${r.message}`)}}function Y(){try{try{l("codex mcp remove borg",{stdio:"ignore"})}catch{}l("codex mcp add borg --env BORG_API_URL="+M(process.env.BORG_API_URL||"https://api.borgmcp.ai")+" --env BORG_CODEX_REMOTE_WAKE=1 -- borg-mcp",{stdio:"inherit",env:{...process.env,BORG_API_URL:process.env.BORG_API_URL||"https://api.borgmcp.ai",BORG_CODEX_REMOTE_WAKE:"1"}})}catch(r){throw r.message?.includes("command not found")?new Error("Codex CLI not found. Please install Codex first."):new Error(`Failed to add MCP server to Codex: ${r.message}`)}}function M(r){return`'${r.replace(/'/g,"'\\''")}'`}function _(r,e,o={}){let t;try{t=S(u)}catch(f){return console.error(`\u26A0 Could not parse ${u}: ${f.message}. Skipping Codex hook registration.`),!1}t.hooks??={},t.hooks[r]??=[];const s=t.hooks[r];if(!Array.isArray(s)||s.some(f=>Array.isArray(f?.hooks)&&f.hooks.some(g=>g?.type==="command"&&g?.command===e)))return!1;const c={hooks:[{type:"command",command:e}]};return o.matcher&&(c.matcher=o.matcher),typeof o.timeout=="number"&&(c.hooks[0].timeout=o.timeout),s.push(c),A(u,t),!0}function Z(){return _("SessionStart",a,{matcher:"startup|resume",timeout:30})}function rr(){return _("UserPromptSubmit",m,{timeout:10})}function C(r,e,o=u){try{const s=S(o)?.hooks?.[r];return Array.isArray(s)?s.some(k=>Array.isArray(k?.hooks)&&k.hooks.some(c=>c?.type==="command"&&c?.command===e)):!1}catch{return!1}}function er(r=u){return C("SessionStart",a,r)}function or(r=u){return C("UserPromptSubmit",m,r)}export{Y as addCodexMcpServer,Z as addCodexSessionStartHook,rr as addCodexUserPromptSubmitHook,z as addMcpServer,D as addProjectSessionStartHook,L as addSessionStartHook,X as addUserPromptSubmitHook,q as getBinaryPath,C as isCodexHookRegistered,V as isCodexMcpServerConfigured,er as isCodexSessionStartHookRegistered,or as isCodexUserPromptSubmitHookRegistered,Q as isMcpServerConfigured,N as isProjectSessionStartHookRegistered,T as isSessionStartHookRegistered,J as isUserPromptSubmitHookRegistered,K as removeSessionStartHook,W as removeUserPromptSubmitHook};
package/dist/cubes.js CHANGED
@@ -1,5 +1,5 @@
1
- import{existsSync as v}from"node:fs";import{mkdir as w,readFile as f,writeFile as g,unlink as m,rename as _}from"node:fs/promises";import{homedir as U}from"node:os";import{dirname as d,join as c,resolve as I}from"node:path";import{pruneDeadWakeTargets as W}from"./codex-wake-resolve.js";import{MODEL_DESCRIPTOR_REGEX as R}from"./model-presets.js";const l=c(U(),".config","borgmcp"),h=c(l,"cubes.json"),j=c(l,"launch.json"),x=c(l,"codex-wake-targets.json"),E=c(l,"launch-models.json"),D=c(l,"inboxes");function s(t=process.cwd()){let e=I(t);for(;;){if(v(c(e,".git")))return e;const r=d(e);if(r===e)return I(t);e=r}}const a=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function Z(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!a.test(e))throw new Error(`Invalid droneId: ${e}`);return c(D,t,`${e}.log`)}function B(t){return t!==null&&typeof t=="object"&&typeof t.projects=="object"&&t.projects!==null&&!Array.isArray(t.projects)}async function p(){let t;try{t=await f(h,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}let e;try{e=JSON.parse(t)}catch{return null}return B(e)?e:null}async function $(t){await S(h,JSON.stringify(t,null,2)+`
2
- `)}let J=0;async function S(t,e,r={}){const n=r.io??{writeFile:g,rename:_,unlink:m},o=r.mode??384;await w(d(t),{recursive:!0});const i=`${t}.${process.pid}.${J++}.tmp`;try{await n.writeFile(i,e,{mode:o}),await n.rename(i,t)}catch(u){try{await n.unlink(i)}catch{}throw u}}function M(t){return t!==null&&typeof t=="object"&&typeof t.projects=="object"&&t.projects!==null&&!Array.isArray(t.projects)}async function b(){let t;try{t=await f(j,"utf8")}catch(e){if(e?.code==="ENOENT")return null;throw e}try{const e=JSON.parse(t);return M(e)?e:null}catch{return null}}async function P(t){await w(d(j),{recursive:!0}),await g(j,JSON.stringify(t,null,2)+`
3
- `,{mode:384})}function T(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!a.test(e))throw new Error(`Invalid droneId: ${e}`);return`${t}:${e}`}function K(t){return t!==null&&typeof t=="object"&&typeof t.targets=="object"&&t.targets!==null&&!Array.isArray(t.targets)}async function C(){let t;try{t=await f(x,"utf8")}catch(e){if(e?.code==="ENOENT")return null;throw e}try{const e=JSON.parse(t);return K(e)?e:null}catch{return null}}async function k(t){await w(d(x),{recursive:!0}),await g(x,JSON.stringify(t,null,2)+`
4
- `,{mode:384})}async function tt(){const t=await p();if(!t)return null;const e=s(),r=t.projects[e];return!r||typeof r.cubeId!="string"||!r.cubeId||typeof r.droneId!="string"||!r.droneId?null:r}async function et(t){const e=await p()??{projects:{}};e.projects[s()]=t,await $(e)}function rt(t,e){const r=e.cube?.name??t.name,n=e.drone?.label??t.droneLabel;return r===t.name&&n===t.droneLabel?t:{...t,name:r,droneLabel:n}}async function nt(){const t=await p();if(!t)return;const e=s();if(e in t.projects){if(delete t.projects[e],Object.keys(t.projects).length===0){try{await m(h)}catch(r){if(r?.code!=="ENOENT")throw r}return}await $(t)}}async function ot(){const t=await b();if(!t)return null;const e=t.projects[s()];return e?.cli==="claude"||e?.cli==="codex"?e.cli:null}async function it(t){const e=await b();if(!e)return null;const r=e.projects[s(t)];return r?.cli==="claude"||r?.cli==="codex"?r.cli:null}async function ct(){const t=await p();return t?Object.entries(t.projects).filter(([,e])=>e!==null&&typeof e=="object"&&typeof e.cubeId=="string"&&e.cubeId.length>0&&typeof e.droneId=="string"&&e.droneId.length>0).map(([e,r])=>({projectPath:e,cube:r})):[]}async function at(t){const e=await b()??{projects:{}};e.projects[s()]={cli:t},await P(e)}async function st(t,e,r){const n=await C()??{targets:{}};n.targets[T(t,e)]={...r,updatedAt:new Date().toISOString()},await k(n)}async function lt(t,e){const r=await C();if(!r)return null;const n=r.targets[T(t,e)];return!n||typeof n.threadId!="string"||typeof n.socketPath!="string"?null:n}async function ut(t){const e=await C();if(!e)return;const{targets:r,changed:n}=W(e.targets,t);n&&await k({...e,targets:r})}function F(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(typeof e!="string"||e.length===0)throw new Error(`Invalid worktree path: ${e}`);return`${t}:${e}`}function X(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!a.test(e))throw new Error(`Invalid droneId: ${e}`);return`${t}:${e}`}function A(t){return!t||typeof t.model!="string"||!R.test(t.model)?null:{model:t.model,ollamaBaseUrl:typeof t.ollamaBaseUrl=="string"?t.ollamaBaseUrl:null}}function G(t){return t!==null&&typeof t=="object"&&typeof t.models=="object"&&t.models!==null&&!Array.isArray(t.models)}async function N(t){let e;try{e=await f(t,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}try{const r=JSON.parse(e);return G(r)?r:null}catch{return null}}async function O(t,e){await S(t,JSON.stringify(e,null,2)+`
5
- `)}async function ft(t,e,r,n=E){const o=await N(n)??{models:{}};o.models[F(t,e)]={model:r.model,ollamaBaseUrl:r.ollamaBaseUrl},await O(n,o)}async function dt(t,e,r=null,n=E){const o=await N(n);if(!o)return null;const i=F(t,e),u=A(o.models[i]);if(u)return u;if(!r||!a.test(r))return null;const L=X(t,r),y=A(o.models[L]);return y?(o.models[i]=y,delete o.models[L],await O(n,o),y):null}async function pt(t,e,r=E){const n=await N(r);if(!n)return;const o=F(t,e);if(o in n.models){if(delete n.models[o],Object.keys(n.models).length===0){try{await m(r)}catch(i){if(i?.code!=="ENOENT")throw i}return}await O(r,n)}}export{rt as activeCubeWithFreshRegenIdentity,S as atomicWriteFile,nt as clearActiveCube,pt as clearLaunchModel,s as findProjectRoot,tt as getActiveCube,lt as getCodexWakeTarget,dt as getLaunchModel,ot as getProjectCliPreference,it as getProjectCliPreferenceForPath,Z as inboxPathForDrone,ut as pruneDeadCodexWakeTargets,ct as readAllProjectIdentities,et as setActiveCube,st as setCodexWakeTarget,ft as setLaunchModel,at as setProjectCliPreference};
1
+ import{existsSync as k}from"node:fs";import{mkdir as A,readFile as f,writeFile as _,unlink as w,rename as U}from"node:fs/promises";import{homedir as W}from"node:os";import{dirname as F,join as c,resolve as N}from"node:path";import{pruneDeadWakeTargets as v}from"./codex-wake-resolve.js";import{MODEL_DESCRIPTOR_REGEX as R}from"./model-presets.js";const l=c(W(),".config","borgmcp"),g=c(l,"cubes.json"),O=c(l,"launch.json"),L=c(l,"codex-wake-targets.json"),m=c(l,"launch-models.json"),D=c(l,"inboxes");function s(t=process.cwd()){let e=N(t);for(;;){if(k(c(e,".git")))return e;const r=F(e);if(r===e)return N(t);e=r}}const a=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function Z(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!a.test(e))throw new Error(`Invalid droneId: ${e}`);return c(D,t,`${e}.log`)}function B(t){return t!==null&&typeof t=="object"&&typeof t.projects=="object"&&t.projects!==null&&!Array.isArray(t.projects)}async function p(){let t;try{t=await f(g,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}let e;try{e=JSON.parse(t)}catch{return null}return B(e)?e:null}async function I(t){await d(g,JSON.stringify(t,null,2)+`
2
+ `)}let J=0;async function d(t,e,r={}){const n=r.io??{writeFile:_,rename:U,unlink:w},o=r.mode??384;await A(F(t),{recursive:!0});const i=`${t}.${process.pid}.${J++}.tmp`;try{await n.writeFile(i,e,{mode:o}),await n.rename(i,t)}catch(u){try{await n.unlink(i)}catch{}throw u}}function M(t){return t!==null&&typeof t=="object"&&typeof t.projects=="object"&&t.projects!==null&&!Array.isArray(t.projects)}async function h(){let t;try{t=await f(O,"utf8")}catch(e){if(e?.code==="ENOENT")return null;throw e}try{const e=JSON.parse(t);return M(e)?e:null}catch{return null}}async function P(t){await d(O,JSON.stringify(t,null,2)+`
3
+ `)}function $(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!a.test(e))throw new Error(`Invalid droneId: ${e}`);return`${t}:${e}`}function K(t){return t!==null&&typeof t=="object"&&typeof t.targets=="object"&&t.targets!==null&&!Array.isArray(t.targets)}async function j(){let t;try{t=await f(L,"utf8")}catch(e){if(e?.code==="ENOENT")return null;throw e}try{const e=JSON.parse(t);return K(e)?e:null}catch{return null}}async function S(t){await d(L,JSON.stringify(t,null,2)+`
4
+ `)}async function tt(){const t=await p();if(!t)return null;const e=s(),r=t.projects[e];return!r||typeof r.cubeId!="string"||!r.cubeId||typeof r.droneId!="string"||!r.droneId?null:r}async function et(t){const e=await p()??{projects:{}};e.projects[s()]=t,await I(e)}function rt(t,e){const r=e.cube?.name??t.name,n=e.drone?.label??t.droneLabel;return r===t.name&&n===t.droneLabel?t:{...t,name:r,droneLabel:n}}async function nt(){const t=await p();if(!t)return;const e=s();if(e in t.projects){if(delete t.projects[e],Object.keys(t.projects).length===0){try{await w(g)}catch(r){if(r?.code!=="ENOENT")throw r}return}await I(t)}}async function ot(){const t=await h();if(!t)return null;const e=t.projects[s()];return e?.cli==="claude"||e?.cli==="codex"?e.cli:null}async function it(t){const e=await h();if(!e)return null;const r=e.projects[s(t)];return r?.cli==="claude"||r?.cli==="codex"?r.cli:null}async function ct(){const t=await p();return t?Object.entries(t.projects).filter(([,e])=>e!==null&&typeof e=="object"&&typeof e.cubeId=="string"&&e.cubeId.length>0&&typeof e.droneId=="string"&&e.droneId.length>0).map(([e,r])=>({projectPath:e,cube:r})):[]}async function at(t){const e=await h()??{projects:{}};e.projects[s()]={cli:t},await P(e)}async function st(t,e,r){const n=await j()??{targets:{}};n.targets[$(t,e)]={...r,updatedAt:new Date().toISOString()},await S(n)}async function lt(t,e){const r=await j();if(!r)return null;const n=r.targets[$(t,e)];return!n||typeof n.threadId!="string"||typeof n.socketPath!="string"?null:n}async function ut(t){const e=await j();if(!e)return;const{targets:r,changed:n}=v(e.targets,t);n&&await S({...e,targets:r})}function x(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(typeof e!="string"||e.length===0)throw new Error(`Invalid worktree path: ${e}`);return`${t}:${e}`}function X(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!a.test(e))throw new Error(`Invalid droneId: ${e}`);return`${t}:${e}`}function T(t){return!t||typeof t.model!="string"||!R.test(t.model)?null:{model:t.model,ollamaBaseUrl:typeof t.ollamaBaseUrl=="string"?t.ollamaBaseUrl:null}}function G(t){return t!==null&&typeof t=="object"&&typeof t.models=="object"&&t.models!==null&&!Array.isArray(t.models)}async function E(t){let e;try{e=await f(t,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}try{const r=JSON.parse(e);return G(r)?r:null}catch{return null}}async function b(t,e){await d(t,JSON.stringify(e,null,2)+`
5
+ `)}async function ft(t,e,r,n=m){const o=await E(n)??{models:{}};o.models[x(t,e)]={model:r.model,ollamaBaseUrl:r.ollamaBaseUrl},await b(n,o)}async function pt(t,e,r=null,n=m){const o=await E(n);if(!o)return null;const i=x(t,e),u=T(o.models[i]);if(u)return u;if(!r||!a.test(r))return null;const C=X(t,r),y=T(o.models[C]);return y?(o.models[i]=y,delete o.models[C],await b(n,o),y):null}async function dt(t,e,r=m){const n=await E(r);if(!n)return;const o=x(t,e);if(o in n.models){if(delete n.models[o],Object.keys(n.models).length===0){try{await w(r)}catch(i){if(i?.code!=="ENOENT")throw i}return}await b(r,n)}}export{rt as activeCubeWithFreshRegenIdentity,d as atomicWriteFile,nt as clearActiveCube,dt as clearLaunchModel,s as findProjectRoot,tt as getActiveCube,lt as getCodexWakeTarget,pt as getLaunchModel,ot as getProjectCliPreference,it as getProjectCliPreferenceForPath,Z as inboxPathForDrone,ut as pruneDeadCodexWakeTargets,ct as readAllProjectIdentities,et as setActiveCube,st as setCodexWakeTarget,ft as setLaunchModel,at as setProjectCliPreference};