borgmcp 1.0.28 → 1.0.29

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.
@@ -102,10 +102,19 @@ export interface AssimilateDeps {
102
102
  updatedAfter: number;
103
103
  }) => Promise<string | null>;
104
104
  fetch: typeof fetch;
105
- checkBackendReachable: (descriptor: string | null, fetchImpl: typeof fetch) => Promise<{
105
+ checkBackendReachable: (descriptor: string | null, fetchImpl: typeof fetch, ollamaBaseUrl?: string) => Promise<{
106
106
  ok: boolean;
107
107
  message?: string;
108
108
  }>;
109
+ getLaunchBackend: (cubeId: string, droneId: string) => Promise<{
110
+ backend: string;
111
+ ollamaBaseUrl: string | null;
112
+ } | null>;
113
+ setLaunchBackend: (cubeId: string, droneId: string, record: {
114
+ backend: string;
115
+ ollamaBaseUrl: string | null;
116
+ }) => Promise<void>;
117
+ clearLaunchBackend: (cubeId: string, droneId: string) => Promise<void>;
109
118
  }
110
119
  export declare function runAssimilate(args: AssimilateArgs, deps: AssimilateDeps): Promise<number>;
111
120
  /**
@@ -1,47 +1,47 @@
1
- import{dirname as Q,basename as S}from"node:path";import{randomUUID as X}from"node:crypto";import{roleSlug as Z,matchRoleByName as ee,pickDefaultRole as te}from"./role-resolver.js";import{deriveCubeName as re,parseGitRemote as ne,sanitizeRemoteUrl as oe}from"./cube-name.js";import{validateName as B}from"./name-validator.js";import{renderAssimilationWelcome as ie}from"./assimilate-welcome.js";import{shellEscape as ae}from"./shell-escape.js";import{withCodexCwdArg as se}from"./codex-remote.js";import{buildAgentKickoffPrompt as le,recordCodexWakeTarget as ce,socketPathFromRemoteArgs as ue}from"./codex-launch.js";import{perWorktreeBranchName as F,adoptWorktree as me,computeWorktreePath as M}from"./worktree-lifecycle.js";import{codexBorgSessionConfigArgs as fe}from"./launch-gate.js";import{resolveLaunchEnv as de}from"./backend-presets.js";async function je(r,e){if(r.role!==void 0){const t=B(r.role);if(!t.ok)return e.stderr(t.error+`
2
- `),1}if(r.flags.worktree!==void 0){const t=B(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=re(a,o),o){const c=oe(o),m=c?ne(c):null;c&&!m&&n&&e.stderr(`couldn't parse git remote '${c}' \u2014 using directory name '${n}' as cube name
4
- `)}}let s=null;if(n&&n.includes("@")&&n.includes(":")){const t=n.lastIndexOf(":");s={ownerEmail:n.substring(0,t),cubeName:n.substring(t+1)},n=s.cubeName}const v=e.cwd();e.stderr(`Checking your cubes\u2026
5
- `);let E;try{E=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(),E=await e.listCubes(i.apiUrl,i.token);else throw t}const A=E.find(t=>t.name===n);if(!A&&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 l,N;if(A)l=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),c=["First drone joining a new cube. Apply a template?"];o.forEach((k,y)=>{const R=y===0?" (default)":"";c.push(` ${y+1}) ${k.name}${R} \u2014 ${k.description}`)}),c.push(` ${o.length+1}) skip \u2014 no template`);const m=(await e.prompt(c.join(`
1
+ import{dirname as Z,basename as E}from"node:path";import{randomUUID as ee}from"node:crypto";import{roleSlug as te,matchRoleByName as re,pickDefaultRole as ne}from"./role-resolver.js";import{deriveCubeName as oe,parseGitRemote as ie,sanitizeRemoteUrl as ae}from"./cube-name.js";import{validateName as M}from"./name-validator.js";import{renderAssimilationWelcome as se}from"./assimilate-welcome.js";import{shellEscape as le}from"./shell-escape.js";import{withCodexCwdArg as ce}from"./codex-remote.js";import{buildAgentKickoffPrompt as ue,recordCodexWakeTarget as me,socketPathFromRemoteArgs as de}from"./codex-launch.js";import{perWorktreeBranchName as Y,adoptWorktree as fe,computeWorktreePath as G}from"./worktree-lifecycle.js";import{codexBorgSessionConfigArgs as he}from"./launch-gate.js";import{resolveLaunchEnv as ge,resolveOllamaBaseUrl as be,parseBackend as we}from"./backend-presets.js";async function De(r,e){if(r.role!==void 0){const t=M(r.role);if(!t.ok)return e.stderr(t.error+`
2
+ `),1}if(r.flags.worktree!==void 0){const t=M(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=oe(a,o),o){const u=ae(o),m=u?ie(u):null;u&&!m&&n&&e.stderr(`couldn't parse git remote '${u}' \u2014 using directory name '${n}' as cube name
4
+ `)}}let s=null;if(n&&n.includes("@")&&n.includes(":")){const t=n.lastIndexOf(":");s={ownerEmail:n.substring(0,t),cubeName:n.substring(t+1)},n=s.cubeName}const x=e.cwd();e.stderr(`Checking your cubes\u2026
5
+ `);let A;try{A=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(),A=await e.listCubes(i.apiUrl,i.token);else throw t}const N=A.find(t=>t.name===n);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 c,_;if(N)c=await e.getCube(i.apiUrl,i.token,N.id),_=!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((y,v)=>{const S=v===0?" (default)":"";u.push(` ${v+1}) ${y.name}${S} \u2014 ${y.description}`)}),u.push(` ${o.length+1}) skip \u2014 no template`);const m=(await e.prompt(u.join(`
8
8
  `)+`
9
- [1]: `)).trim(),b=m===""?1:parseInt(m,10);if(Number.isNaN(b)||b<1||b>o.length+1)return e.stderr(`invalid choice "${m}"
10
- `),1;t=b<=o.length?o[b-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(),k=m===""?1:parseInt(m,10);if(Number.isNaN(k)||k<1||k>o.length+1)return e.stderr(`invalid choice "${m}"
10
+ `),1;t=k<=o.length?o[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
12
  `),1;t="starter"}e.stderr(n?`Creating cube '${n}'\u2026
13
13
  `:`Creating your cube\u2026
14
- `),l=await e.createCube(i.apiUrl,i.token,t?{name:n??void 0,template:t}:{name:n??void 0}),N=!0}let f;if(r.role!==void 0){if(f=ee(l.roles,r.role),!f){const t=l.roles.map(m=>m.name).join(", "),o=be(r.role,l.roles.map(m=>m.name)),c=o?` Did you mean "${o}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${l.name}". Available: ${t}.${c}
14
+ `),c=await e.createCube(i.apiUrl,i.token,t?{name:n??void 0,template:t}:{name:n??void 0}),_=!0}let d;if(r.role!==void 0){if(d=re(c.roles,r.role),!d){const t=c.roles.map(m=>m.name).join(", "),o=$e(r.role,c.roles.map(m=>m.name)),u=o?` Did you mean "${o}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${c.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(f=te(l.roles,{isFirstDrone:N}),!f)return e.stderr(`cube "${l.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 T;if(p&&r.flags.here)if(p.cubeId===l.id)T=p.droneId;else return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
18
- `),1;const x=r.flags.backend??f.default_backend??null;if(x){const t=await e.checkBackendReachable(x,e.fetch);if(!t.ok)return e.stderr(`${t.message}
19
- `),1}e.stderr(`Joining cube '${l.name}' as ${f.name}\u2026
20
- `);let u;try{u=await e.assimilate(i.apiUrl,i.token,{cube_id:l.id,role_id:f.id,hostname:e.getHostname(),backend:x,...T?{prior_drone_id:T}:{}})}catch(t){const o=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${o}
21
- `),1}const $=l.roles.find(t=>t.id===u.role_id)??f;u.reattached?e.stderr(`re-attached to existing seat ${u.drone_label} (session token rotated, no new drone minted)
22
- `):$.id!==f.id&&e.stderr(`Note: your invite didn't grant the "${f.name}" role \u2014 assimilated as "${$.name}" instead.
23
- `);const G=r.flags.worktree!==void 0||p!==null&&!r.flags.here;let d=null;if(G){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=ne(c.roles,{isFirstDrone:_}),!d)return e.stderr(`cube "${c.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 C=await e.getActiveCube();let $;if(C&&r.flags.here)if(C.cubeId===c.id)$=C.droneId;else return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
18
+ `),1;const T=$!=null?await e.getLaunchBackend(c.id,$):null,h=r.flags.backend??T?.backend??d.default_backend??null,P=be(process.env,h!=null&&h===T?.backend?T?.ollamaBaseUrl:void 0);if(h){const t=await e.checkBackendReachable(h,e.fetch,P);if(!t.ok)return e.stderr(`${t.message}
19
+ `),1}e.stderr(`Joining cube '${c.name}' as ${d.name}\u2026
20
+ `);let l;try{l=await e.assimilate(i.apiUrl,i.token,{cube_id:c.id,role_id:d.id,hostname:e.getHostname(),backend:h,...$?{prior_drone_id:$}:{}})}catch(t){const o=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${o}
21
+ `),1}const p=c.roles.find(t=>t.id===l.role_id)??d;l.reattached?e.stderr(`re-attached to existing seat ${l.drone_label} (session token rotated, no new drone minted)
22
+ `):p.id!==d.id&&e.stderr(`Note: your invite didn't grant the "${d.name}" role \u2014 assimilated as "${p.name}" instead.
23
+ `);const z=r.flags.worktree!==void 0||C!==null&&!r.flags.here;let f=null;if(z){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.
24
24
  Fix: create at least one commit (\`git commit --allow-empty -m "initial"\`)
25
25
  OR: pass --here to skip the sibling spawn and use the current directory
26
- `),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(),b=e.runSync("git",["rev-parse",o],a).stdout.trim();m!==b&&e.stderr(`note: local HEAD (${m.slice(0,7)}) differs from ${o} (${b.slice(0,7)}); new worktree will start on ${o}
27
- `);const k=S(a),y=r.flags.worktree??Z($.name);if(y.length===0)return e.stderr(`cannot derive a worktree name from role "${$.name}"; pass an explicit --worktree <name>
28
- `),1;const R=e.homedir();let g=M(R,k,y),O=2;for(;e.pathExists(g)||we(e,a,g);)g=M(R,k,y,O),O++;e.mkdirp(Q(g));const U=F(S(g),k),H=e.runSync("git",["worktree","add","-b",U,g,o],a);if(H.status!==0)return e.stderr(`git worktree add failed: ${Y(H.stderr)}
29
- `),1;e.stderr(`spawned sibling worktree at ${g} on branch ${U} (${o}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
30
- `),e.chdir(g),e.stderr(he(g,U,a)),d=e.cwd()}try{await e.setActiveCube({cubeId:u.cube_id,droneId:u.drone_id,name:l.name,sessionToken:u.session_token,droneLabel:u.drone_label,apiUrl:i.apiUrl})}catch(t){const o=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${o}
31
- `),d){const c=e.runSync("git",["worktree","remove","--force",d],a);c.status===0?e.stderr(`rolled back spawned worktree at ${d}
32
- `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${d}\` (rollback attempt failed: ${Y(c.stderr).trim()||"unknown"})
33
- `)}return 1}e.setTerminalTitle(u.drone_label,l.name);const q=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(ie($.name,l.name,q));const w=await e.resolveCli(r.flags.cli),h=e.cwd();try{e.installProjectSessionHook(h)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${h}; it will be re-attempted on the next borg launch
34
- `)}if(!d){e.runSync("git",["fetch","origin","--prune"],h);const t=F(S(h),S(a)),o=me(e.runSync,h,t,"origin/main");o.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
35
- `),e.stderr(ge(h,t))):o.message&&e.stderr(`worktree sync: ${o.message}
26
+ `),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(),k=e.runSync("git",["rev-parse",o],a).stdout.trim();m!==k&&e.stderr(`note: local HEAD (${m.slice(0,7)}) differs from ${o} (${k.slice(0,7)}); new worktree will start on ${o}
27
+ `);const y=E(a),v=r.flags.worktree??te(p.name);if(v.length===0)return e.stderr(`cannot derive a worktree name from role "${p.name}"; pass an explicit --worktree <name>
28
+ `),1;const S=e.homedir();let b=G(S,y,v),H=2;for(;e.pathExists(b)||ve(e,a,b);)b=G(S,y,v,H),H++;e.mkdirp(Z(b));const D=Y(E(b),y),F=e.runSync("git",["worktree","add","-b",D,b,o],a);if(F.status!==0)return e.stderr(`git worktree add failed: ${q(F.stderr)}
29
+ `),1;e.stderr(`spawned sibling worktree at ${b} on branch ${D} (${o}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
30
+ `),e.chdir(b),e.stderr(ke(b,D,a)),f=e.cwd()}try{await e.setActiveCube({cubeId:l.cube_id,droneId:l.drone_id,name:c.name,sessionToken:l.session_token,droneLabel:l.drone_label,apiUrl:i.apiUrl})}catch(t){const o=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${o}
31
+ `),f){const u=e.runSync("git",["worktree","remove","--force",f],a);u.status===0?e.stderr(`rolled back spawned worktree at ${f}
32
+ `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${f}\` (rollback attempt failed: ${q(u.stderr).trim()||"unknown"})
33
+ `)}return 1}e.setTerminalTitle(l.drone_label,c.name);const K=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(se(p.name,c.name,K));const w=await e.resolveCli(r.flags.cli),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
34
+ `)}if(!f){e.runSync("git",["fetch","origin","--prune"],g);const t=Y(E(g),E(a)),o=fe(e.runSync,g,t,"origin/main");o.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
35
+ `),e.stderr(ye(g,t))):o.message&&e.stderr(`worktree sync: ${o.message}
36
36
  `)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${w} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
37
- `);const z=e.getInboxPath(u.cube_id,u.drone_id),_=w==="codex"?`borg-wake-${X()}`:null,K=w==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${z}\` so each event's task-notification title summarizes the new cube log entry (drone label, role, and first ~80 chars of the message body) \u2014 letting you triage events without reading the full body. `:"";let P,L=[],C,I=null,j=null;const W=de(x),D={...process.env,...W.set,BORG_SESSION:"1"};for(const t of W.unset)delete D[t];if(w==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
38
- `),P="\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."):P="Codex wake-path capability check passed: remote-control socket established for this session.",L=t.args,Object.keys(t.env).length>0&&Object.assign(D,t.env),I=ue(t.args),j=t.server?.cleanup??null}C=[le({cli:w,codexWakeNonce:_,monitorClause:K,codexWakePathClause:P})],w==="codex"&&(C=[...fe(),...L,...se(C,h)]);const J=e.exec(w,C,h,D);w==="codex"&&I&&_&&ce({deps:e,cubeId:u.cube_id,droneId:u.drone_id,socketPath:I,cwd:h,previewNeedle:_,launchedAtSeconds:Math.floor(Date.now()/1e3)});const V=await J;if(j)try{j()}catch{}return d&&v!==d&&e.stderr(`
39
- Agent exited. You were working in ${d}; your shell is back in ${v}.
37
+ `);const J=e.getInboxPath(l.cube_id,l.drone_id),I=w==="codex"?`borg-wake-${ee()}`:null,V=w==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${J}\` so each event's task-notification title summarizes the new cube log entry (drone label, role, and first ~80 chars of the message body) \u2014 letting you triage events without reading the full body. `:"";let U,O=[],R,B=null,L=null;h?await e.setLaunchBackend(l.cube_id,l.drone_id,{backend:h,ollamaBaseUrl:we(h).kind==="ollama"?P:null}):await e.clearLaunchBackend(l.cube_id,l.drone_id);const W=ge(h,P),j={...process.env,...W.set,BORG_SESSION:"1"};for(const t of W.unset)delete j[t];if(w==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
38
+ `),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.",O=t.args,Object.keys(t.env).length>0&&Object.assign(j,t.env),B=de(t.args),L=t.server?.cleanup??null}R=[ue({cli:w,codexWakeNonce:I,monitorClause:V,codexWakePathClause:U})],w==="codex"&&(R=[...he(),...O,...ce(R,g)]);const Q=e.exec(w,R,g,j);w==="codex"&&B&&I&&me({deps:e,cubeId:l.cube_id,droneId:l.drone_id,socketPath:B,cwd:g,previewNeedle:I,launchedAtSeconds:Math.floor(Date.now()/1e3)});const X=await Q;if(L)try{L()}catch{}return f&&x!==f&&e.stderr(`
39
+ Agent exited. You were working in ${f}; your shell is back in ${x}.
40
40
  To return:
41
- cd ${ae(d)}
42
- `),V}function he(r,e,i){return`
41
+ cd ${le(f)}
42
+ `),X}function ke(r,e,i){return`
43
43
  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).
44
- `}function ge(r,e){return`
44
+ `}function ye(r,e){return`
45
45
  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.
46
- `}function Y(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function we(r,e,i){const a=r.runSync("git",["worktree","list","--porcelain"],e);return a.status!==0?!1:a.stdout.split(`
47
- `).some(n=>n===`worktree ${i}`)}function be(r,e){if(e.length===0)return null;const i=r.toLowerCase();let a=null;for(const n of e){const s=ke(i,n.toLowerCase());s<=2&&(a===null||s<a.distance)&&(a={name:n,distance:s})}return a?a.name:null}function ke(r,e){if(r===e)return 0;if(r.length===0)return e.length;if(e.length===0)return r.length;const 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 s=1;s<=e.length;s++){const v=r[n-1]===e[s-1]?0:1;a[s]=Math.min(a[s-1]+1,i[s]+1,i[s-1]+v)}for(let s=0;s<=e.length;s++)i[s]=a[s]}return i[e.length]}export{je as runAssimilate,Y as safeStderr,be as suggestRoleName};
46
+ `}function q(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function ve(r,e,i){const a=r.runSync("git",["worktree","list","--porcelain"],e);return a.status!==0?!1:a.stdout.split(`
47
+ `).some(n=>n===`worktree ${i}`)}function $e(r,e){if(e.length===0)return null;const i=r.toLowerCase();let a=null;for(const n of e){const s=pe(i,n.toLowerCase());s<=2&&(a===null||s<a.distance)&&(a={name:n,distance:s})}return a?a.name:null}function pe(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 s=1;s<=e.length;s++){const x=r[n-1]===e[s-1]?0:1;a[s]=Math.min(a[s-1]+1,i[s]+1,i[s-1]+x)}for(let s=0;s<=e.length;s++)i[s]=a[s]}return i[e.length]}export{De as runAssimilate,q as safeStderr,$e as suggestRoleName};
@@ -1,3 +1,3 @@
1
- import{spawnSync as m,spawn as l}from"node:child_process";import{existsSync as f,mkdirSync as b}from"node:fs";import{hostname as h,homedir as C}from"node:os";import{createInterface as d}from"node:readline/promises";import{API_URL as u,getValidToken as p,listCubes as _,getCube as k,createCube as T,assimilate as y,listTemplates as w}from"./remote-client.js";import{findProjectRoot as g,getActiveCube as x,setActiveCube as v,inboxPathForDrone as R,setCodexWakeTarget as S}from"./cubes.js";import{authenticateWithGoogle as A}from"./auth.js";import{addProjectSessionStartHook as P}from"./config-utils.js";import{setTerminalTitle as U}from"./terminal-title.js";import{defaultCliChoiceDeps as j,resolveCliChoice as L}from"./cli-platform.js";import{prepareCodexRemoteLaunch as D,defaultCodexRemoteDeps as H}from"./codex-remote.js";import{findLoadedCodexThread as I}from"./codex-app-server.js";import{checkBackendReachable as G}from"./backend-presets.js";function Q(){return{runSync:(e,r,o)=>{const t=m(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:()=>C(),mkdirp:e=>b(e,{recursive:!0}),exec:(e,r,o,t)=>new Promise((i,s)=>{const a=l(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)=>{U({label:e,cubeName:r},r)},getActiveCube:()=>x(),setActiveCube:e=>v(e),findProjectRoot:e=>g(e),installProjectSessionHook:e=>{P(e)},getCachedAuth:async()=>{try{return{token:await p(),apiUrl:u}}catch{return null}},runSetup:async()=>(await A(),{token:await p(),apiUrl:u}),listCubes:async(e,r)=>{const{cubes:o}=await _();return o.map(t=>({id:t.id,name:t.name}))},getCube:async(e,r,o)=>{const t=await k(o);return{id:t.id,name:t.name,roles:t.roles}},createCube:async(e,r,o)=>{const t=await T(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 y({cube_id:o.cube_id,role_id:o.role_id,...o.prior_drone_id?{prior_drone_id:o.prior_drone_id}:{},...o.backend!=null?{backend:o.backend}:{}},void 0,o.hostname??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 w();return o.map(t=>({name:t.name,description:t.description}))},getInboxPath:(e,r)=>R(e,r),probeMcpReady:()=>new Promise(e=>{const r=l("borg-mcp",[],{stdio:["pipe","pipe","pipe"],shell:!1});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 c of o.split(`
2
- `))if(c.includes('"protocolVersion"')&&c.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=>L(e,j(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:()=>D(H()),setCodexWakeTarget:S,findLoadedCodexThread:I,fetch,checkBackendReachable:G}}export{Q as buildDefaultAssimilateDeps};
1
+ import{spawnSync as m,spawn as l}from"node:child_process";import{existsSync as h,mkdirSync as f}from"node:fs";import{hostname as b,homedir as k}from"node:os";import{createInterface as d}from"node:readline/promises";import{API_URL as u,getValidToken as p,listCubes as C,getCube as _,createCube as T,assimilate as y,listTemplates as w}from"./remote-client.js";import{findProjectRoot as g,getActiveCube as L,setActiveCube as x,inboxPathForDrone as S,setCodexWakeTarget as v,getLaunchBackend as R,setLaunchBackend as B,clearLaunchBackend as A}from"./cubes.js";import{authenticateWithGoogle as P}from"./auth.js";import{addProjectSessionStartHook as U}from"./config-utils.js";import{setTerminalTitle as j}from"./terminal-title.js";import{defaultCliChoiceDeps as D,resolveCliChoice as G}from"./cli-platform.js";import{prepareCodexRemoteLaunch as H,defaultCodexRemoteDeps as I}from"./codex-remote.js";import{findLoadedCodexThread as q}from"./codex-app-server.js";import{checkBackendReachable as V}from"./backend-presets.js";function $(){return{runSync:(e,t,r)=>{const o=m(e,t,{cwd:r,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:()=>k(),mkdirp:e=>f(e,{recursive:!0}),exec:(e,t,r,o)=>new Promise((s,i)=>{const a=l(e,t,{cwd:r,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 t=d({input:process.stdin,output:process.stdout});try{return await t.question(e)}finally{t.close()}},isTTY:()=>process.stdin.isTTY===!0,getHostname:()=>b(),setTerminalTitle:(e,t)=>{j({label:e,cubeName:t},t)},getActiveCube:()=>L(),setActiveCube:e=>x(e),findProjectRoot:e=>g(e),installProjectSessionHook:e=>{U(e)},getCachedAuth:async()=>{try{return{token:await p(),apiUrl:u}}catch{return null}},runSetup:async()=>(await P(),{token:await p(),apiUrl:u}),listCubes:async(e,t)=>{const{cubes:r}=await C();return r.map(o=>({id:o.id,name:o.name}))},getCube:async(e,t,r)=>{const o=await _(r);return{id:o.id,name:o.name,roles:o.roles}},createCube:async(e,t,r)=>{const o=await T(r.name,"",r.template?{template:r.template}:void 0);return{id:o.id,name:o.name,roles:o.roles}},assimilate:async(e,t,r)=>{const o=await y({cube_id:r.cube_id,role_id:r.role_id,...r.prior_drone_id?{prior_drone_id:r.prior_drone_id}:{},...r.backend!=null?{backend:r.backend}:{}},void 0,r.hostname??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,t)=>{const{templates:r}=await w();return r.map(o=>({name:o.name,description:o.description}))},getInboxPath:(e,t)=>S(e,t),probeMcpReady:()=>new Promise(e=>{const t=l("borg-mcp",[],{stdio:["pipe","pipe","pipe"],shell:!1});let r="",o=!1;const s=n=>{if(!o){o=!0;try{t.kill("SIGTERM")}catch{}e(n)}},i=setTimeout(()=>s(!1),2e3);t.on("error",()=>{clearTimeout(i),s(!1)}),t.on("exit",()=>{clearTimeout(i),s(o)}),t.stdout?.on("data",n=>{r+=n.toString("utf-8");for(const c of r.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{t.stdin?.write(a+`
3
+ `)}catch{clearTimeout(i),s(!1)}}),resolveCli:e=>G(e,D(async t=>{const r=d({input:process.stdin,output:process.stdout});try{return await r.question(t)}finally{r.close()}},()=>process.stdin.isTTY===!0)),prepareCodexRemoteLaunch:()=>H(I()),setCodexWakeTarget:v,findLoadedCodexThread:q,fetch,checkBackendReachable:V,getLaunchBackend:(e,t)=>R(e,t),setLaunchBackend:(e,t,r)=>B(e,t,r),clearLaunchBackend:(e,t)=>A(e,t)}}export{$ as buildDefaultAssimilateDeps};
@@ -15,7 +15,26 @@ export declare const OLLAMA_DEFAULT_BASE_URL = "http://localhost:11434";
15
15
  * Ollama drone point at a model server on another host, e.g.
16
16
  * BORG_OLLAMA_BASE_URL=http://Mac-Studio.local:11434 borg assimilate ...
17
17
  */
18
- export declare function resolveOllamaBaseUrl(env?: Record<string, string | undefined>): string;
18
+ export declare function resolveOllamaBaseUrl(env?: Record<string, string | undefined>, storedUrl?: string | null): string;
19
+ /**
20
+ * Restore a drone's persisted launch backend onto a base environment — the
21
+ * bare-`borg` relaunch path. Returns the merged child env plus, for ollama
22
+ * backends, a `{ descriptor, baseUrl }` the caller can use for a best-effort
23
+ * reachability warning. A null/absent stored backend leaves the env untouched
24
+ * (backward compatible — no stored entry ⇒ today's behavior). Base-URL
25
+ * precedence is BORG_OLLAMA_BASE_URL ▸ stored ▸ default (the pairing rule: the
26
+ * stored host travels with the stored descriptor).
27
+ */
28
+ export declare function applyOllamaLaunchEnv(baseEnv: Record<string, string | undefined>, stored: {
29
+ backend: string;
30
+ ollamaBaseUrl?: string | null;
31
+ } | null, processEnv?: Record<string, string | undefined>): {
32
+ env: Record<string, string | undefined>;
33
+ probe: {
34
+ descriptor: string;
35
+ baseUrl: string;
36
+ } | null;
37
+ };
19
38
  export declare function parseBackend(descriptor: string): {
20
39
  kind: 'claude' | 'ollama';
21
40
  model: string;
@@ -1 +1 @@
1
- const p=/^(claude|ollama):[A-Za-z0-9._:\/-]+$/,O="http://localhost:11434";function m(t=process.env){const n=t.BORG_OLLAMA_BASE_URL?.trim();return n?(/^[a-z][a-z0-9+.-]*:\/\//i.test(n)?n:`http://${n}`).replace(/\/+$/,""):O}function f(t){const n=t.indexOf(":");if(n<0)throw new Error(`invalid backend descriptor: ${t} (expected <kind>:<model>)`);const e=t.substring(0,n),a=t.substring(n+1);if(e!=="claude"&&e!=="ollama")throw new Error(`invalid backend kind: ${e} (expected claude or ollama)`);return{kind:e,model:a}}function _(t,n=m()){if(!t)return{set:{},unset:[]};const{kind:e,model:a}=f(t);return e==="claude"?{set:{ANTHROPIC_MODEL:a},unset:[]}:{set:{ANTHROPIC_BASE_URL:n,ANTHROPIC_MODEL:a,ANTHROPIC_AUTH_TOKEN:"ollama"},unset:["ANTHROPIC_API_KEY"]}}function k(t,n){const e=t.toLowerCase(),r=e.slice(e.lastIndexOf("/")+1).includes(":")?e:`${e}:latest`;return n.some(u=>{const c=u.toLowerCase();return c===r||c===e})}async function L(t,n,e=m()){if(!t)return{ok:!0};const{kind:a,model:r}=f(t);if(a==="claude")return{ok:!0};const c=e.includes("localhost")||e.includes("127.0.0.1")||e.includes("[::1]")?"start it (ollama serve)":"check the host is up and Ollama is listening there (OLLAMA_HOST=0.0.0.0)",d={ok:!1,message:`Ollama not reachable at ${e} \u2014 ${c}, or assimilate with a Claude backend.`},h=new AbortController,A=setTimeout(()=>h.abort(),3e3);try{const i=await n(`${e}/api/tags`,{signal:h.signal});if(!i.ok)return d;let l=null;try{const s=await i.json();s&&Array.isArray(s.models)&&(l=s.models.map(o=>(o?.name??o?.model??"").replace(/[\x00-\x1F\x7F]/g,"")).filter(o=>o.length>0))}catch{}if(l&&!k(r,l)){const s=l.length===0?"no models are pulled there":`available: ${l.slice(0,12).map(o=>o.length>64?`${o.slice(0,64)}\u2026`:o).join(", ")}${l.length>12?`, \u2026 (${l.length} total)`:""}`;return{ok:!1,message:`Ollama model '${r}' not found at ${e} (${s}) \u2014 pull it with: ollama pull ${r}`}}return{ok:!0}}catch{return d}finally{clearTimeout(A)}}export{p as BACKEND_DESCRIPTOR_REGEX,O as OLLAMA_DEFAULT_BASE_URL,L as checkBackendReachable,k as ollamaModelMatches,f as parseBackend,_ as resolveLaunchEnv,m as resolveOllamaBaseUrl};
1
+ const b=/^(claude|ollama):[A-Za-z0-9._:\/-]+$/,k="http://localhost:11434";function h(n=process.env,t){const e=n.BORG_OLLAMA_BASE_URL?.trim()||t?.trim()||"";return e?(/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`).replace(/\/+$/,""):k}function _(n,t,e=process.env){if(!t||!t.backend)return{env:n,probe:null};const{kind:a}=f(t.backend),l=h(e,t.ollamaBaseUrl??void 0),{set:u,unset:c}=A(t.backend,l),s={...n,...u};for(const d of c)delete s[d];return{env:s,probe:a==="ollama"?{descriptor:t.backend,baseUrl:l}:null}}function f(n){const t=n.indexOf(":");if(t<0)throw new Error(`invalid backend descriptor: ${n} (expected <kind>:<model>)`);const e=n.substring(0,t),a=n.substring(t+1);if(e!=="claude"&&e!=="ollama")throw new Error(`invalid backend kind: ${e} (expected claude or ollama)`);return{kind:e,model:a}}function A(n,t=h()){if(!n)return{set:{},unset:[]};const{kind:e,model:a}=f(n);return e==="claude"?{set:{ANTHROPIC_MODEL:a},unset:[]}:{set:{ANTHROPIC_BASE_URL:t,ANTHROPIC_MODEL:a,ANTHROPIC_AUTH_TOKEN:"ollama"},unset:["ANTHROPIC_API_KEY"]}}function O(n,t){const e=n.toLowerCase(),l=e.slice(e.lastIndexOf("/")+1).includes(":")?e:`${e}:latest`;return t.some(u=>{const c=u.toLowerCase();return c===l||c===e})}async function L(n,t,e=h()){if(!n)return{ok:!0};const{kind:a,model:l}=f(n);if(a==="claude")return{ok:!0};const c=e.includes("localhost")||e.includes("127.0.0.1")||e.includes("[::1]")?"start it (ollama serve)":"check the host is up and Ollama is listening there (OLLAMA_HOST=0.0.0.0)",s={ok:!1,message:`Ollama not reachable at ${e} \u2014 ${c}, or assimilate with a Claude backend.`},d=new AbortController,p=setTimeout(()=>d.abort(),3e3);try{const m=await t(`${e}/api/tags`,{signal:d.signal});if(!m.ok)return s;let r=null;try{const i=await m.json();i&&Array.isArray(i.models)&&(r=i.models.map(o=>(o?.name??o?.model??"").replace(/[\x00-\x1F\x7F]/g,"")).filter(o=>o.length>0))}catch{}if(r&&!O(l,r)){const i=r.length===0?"no models are pulled there":`available: ${r.slice(0,12).map(o=>o.length>64?`${o.slice(0,64)}\u2026`:o).join(", ")}${r.length>12?`, \u2026 (${r.length} total)`:""}`;return{ok:!1,message:`Ollama model '${l}' not found at ${e} (${i}) \u2014 pull it with: ollama pull ${l}`}}return{ok:!0}}catch{return s}finally{clearTimeout(p)}}export{b as BACKEND_DESCRIPTOR_REGEX,k as OLLAMA_DEFAULT_BASE_URL,_ as applyOllamaLaunchEnv,L as checkBackendReachable,O as ollamaModelMatches,f as parseBackend,A as resolveLaunchEnv,h as resolveOllamaBaseUrl};
package/dist/claude.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as D}from"child_process";import{randomUUID as L}from"node:crypto";import{basename as E}from"node:path";import{createInterface as H}from"node:readline/promises";import t from"chalk";import{findProjectRoot as M,getActiveCube as O,inboxPathForDrone as F,setCodexWakeTarget as N,pruneDeadCodexWakeTargets as B}from"./cubes.js";import{handleVersionFlag as _,getPackageVersion as f}from"./version.js";import{isHelpFlag as S,setupHelpText as G,topLevelHelpText as W,assimilateHelpText as Y}from"./cli-help.js";import{runSpawn as U}from"./spawn.js";import{parseSyncArgs as V,runSync as j}from"./sync.js";import{parseAssimilateArgs as K}from"./parse-assimilate-args.js";import{runAssimilate as q}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as z}from"./assimilate-deps.js";import{parseLaunchAllArgs as y}from"./parse-launch-all-args.js";import{runLaunchAll as T}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as w}from"./launch-all-deps.js";import{discoverDroneCandidates as X}from"./launch-all-discovery.js";import{runBareLaunchMenu as J,shouldShowLaunchMenu as Q}from"./bare-launch-menu.js";import{setTerminalTitle as Z}from"./terminal-title.js";import{initConsolePrefix as ee,consolePrefix as s}from"./console-prefix.js";import{initDebugFromArgv as re}from"./debug.js";import{fetchLatestBorgmcpVersion as oe,compareVersionsForStaleness as se}from"./stale-version-check.js";import{defaultCliChoiceDeps as te,detectCliAvailability as x,installedCliNames as A,parseCliFlag as ie,resolveCliChoice as ae}from"./cli-platform.js";import{getRefreshToken as ne,getIdToken as ce}from"./config.js";import{composeGetStarted as le,shouldShowGetStarted as de}from"./get-started.js";import{prepareCodexRemoteLaunch as pe,withCodexCwdArg as ue,defaultCodexRemoteDeps as me,checkCodexBridgeHealthy as fe}from"./codex-remote.js";import{findLoadedCodexThread as ge}from"./codex-app-server.js";import{buildAgentKickoffPrompt as he,recordCodexWakeTarget as we,socketPathFromRemoteArgs as I}from"./codex-launch.js";import{codexBorgSessionConfigArgs as xe}from"./launch-gate.js";import{addCodexMcpServer as Ce,addCodexSessionStartHook as ve,addCodexUserPromptSubmitHook as ke,addMcpServer as be,addProjectSessionStartHook as $e,addUserPromptSubmitHook as Se,isCodexMcpServerConfigured as ye,isMcpServerConfigured as Te,removeSessionStartHook as Ae}from"./config-utils.js";async function Ie(){re(process.argv),_(),await ee();const c=(async()=>{if(!process.stderr.isTTY)return;const e=f(),o=await oe();if(!o)return;const n=se(e,o);n.stale&&n.message&&process.stderr.write(`${s()}${n.message}
3
- `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(W(f())),process.exit(0)),process.argv[2]==="setup"){S(process.argv[3])&&(process.stdout.write(G(f())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(S)&&(process.stdout.write(Y(f())),process.exit(0));const e=K(process.argv.slice(3));e.ok||(process.stderr.write(t.red(`${s()}\u25FC borg assimilate: ${e.error}
4
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const o=z(),n=await q({role:e.role,flags:e.flags},o);process.exit(n)}if(process.argv[2]==="spawn"){const e=await U();process.exit(e)}if(process.argv[2]==="sync"){const e=V(process.argv.slice(3));e.ok||(process.stderr.write(t.red(`${s()}\u25FC borg sync: ${e.error}
5
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const o=await j({},e.options);process.exit(o)}if(process.argv[2]==="launch-all"){const e=y(process.argv.slice(3));e.ok||(process.stderr.write(t.red(`${s()}\u25FC borg launch-all: ${e.error}
6
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const o=w(),n=await T(e.args,o);process.exit(n)}if(de(await ne()!==null,await ce()!==null)){const e=A(x()).length>0;process.stdout.write(le(e)),process.exit(0)}const a=ie(process.argv.slice(2));a.error&&(process.stderr.write(t.red(`${s()}\u25FC ${a.error}
7
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const C=async e=>{const o=H({input:process.stdin,output:process.stdout});try{return await o.question(e)}finally{o.close()}};let r=await ae(a.cli,te(C,()=>process.stdin.isTTY===!0));Pe();const i=await O();if(Q({extraArgs:process.argv.slice(2),stdinIsTTY:process.stdin.isTTY===!0,stdoutIsTTY:process.stdout.isTTY===!0})){const e=A(x()).find(u=>u!==r)??null;let o=!1;i&&(o=(await X({targetCubeId:i.cubeId},w())).length>0);const n=await J({defaultCli:r,otherInstalledCli:e,hasLaunchAllTargets:o},C);if(n.kind==="launch-all"){const u=y([]),R=u.ok?await T(u.args,w()):1;process.exit(R)}r=n.cli}const l=a.rest;Z(i?{label:i.droneLabel,cubeName:i.name}:null,E(process.cwd()));const P=i&&r==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${F(i.cubeId,i.droneId)}\` so each event's task-notification title summarizes the new cube log entry (drone label, role, and first ~80 chars of the message body) \u2014 letting you triage events without reading the full body. `:"";await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const v=r==="codex"?`borg-wake-${L()}`:null;let m,k=[],g={...process.env,BORG_SESSION:"1"},d=null,p=null;if(r==="codex"&&!l.includes("--remote")){console.error(`${s()}${t.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await pe(me());e.warning?(console.error(`${s()}${t.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.",k=e.args,g={...process.env,...e.env,BORG_SESSION:"1"},d=I(e.args),p=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.",d=I(l),d&&(g={...process.env,BORG_CODEX_REMOTE_WAKE:"1",BORG_SESSION:"1"}));const b=he({cli:r,codexWakeNonce:v,monitorClause:P,codexWakePathClause:m});let h=[...l,b];r==="codex"&&(h=[...xe(),...k,...ue(h,process.cwd())]),console.error(`${s()}${t.blue(`\u25FC Launching ${r==="claude"?"Claude Code":"Codex"}\u2026`)}`);const $=D(r,h,{stdio:"inherit",shell:!1,env:g});r==="codex"&&i&&d&&(we({deps:{setCodexWakeTarget:N,findLoadedCodexThread:ge},cubeId:i.cubeId,droneId:i.droneId,socketPath:d,passthroughArgs:l,previewNeedle:v??b.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),B(e=>fe(e))),$.on("error",e=>{if(p)try{p()}catch{}e.code==="ENOENT"?(console.error(`${s()}${t.red(`
8
- \u25FC Failed to launch ${r}`)}`),console.error(`${s()}${t.gray(`Make sure ${r} is installed.
9
- `)}`)):console.error(`${s()}${t.red(`
10
- \u25FC Failed to launch ${r}: ${e.message}
11
- `)}`),process.exit(1)}),$.on("exit",e=>{if(p)try{p()}catch{}process.exit(e??0)})}function Pe(){const c=x();if(c.claude)try{Te()||be(),$e(M(process.cwd())),Ae(),Se()}catch(a){console.error(`${s()}${t.yellow(`warning: Claude Code integration check failed: ${a?.message??a}`)}`)}if(c.codex)try{ye()||Ce(),ve(),ke()}catch(a){console.error(`${s()}${t.yellow(`warning: Codex integration check failed: ${a?.message??a}`)}`)}}Ie().catch(c=>{console.error(`${s()}${t.red(`
2
+ import{spawn as D}from"child_process";import{randomUUID as L}from"node:crypto";import{basename as E}from"node:path";import{createInterface as B}from"node:readline/promises";import a from"chalk";import{findProjectRoot as O,getActiveCube as H,getLaunchBackend as M,inboxPathForDrone as F,setCodexWakeTarget as N,pruneDeadCodexWakeTargets as _}from"./cubes.js";import{applyOllamaLaunchEnv as G,checkBackendReachable as W}from"./backend-presets.js";import{handleVersionFlag as Y,getPackageVersion as g}from"./version.js";import{isHelpFlag as y,setupHelpText as U,topLevelHelpText as V,assimilateHelpText as j}from"./cli-help.js";import{runSpawn as K}from"./spawn.js";import{parseSyncArgs as q,runSync as z}from"./sync.js";import{parseAssimilateArgs as X}from"./parse-assimilate-args.js";import{runAssimilate as J}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as Q}from"./assimilate-deps.js";import{parseLaunchAllArgs as S}from"./parse-launch-all-args.js";import{runLaunchAll as T}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as w}from"./launch-all-deps.js";import{discoverDroneCandidates as Z}from"./launch-all-discovery.js";import{runBareLaunchMenu as ee,shouldShowLaunchMenu as re}from"./bare-launch-menu.js";import{setTerminalTitle as oe}from"./terminal-title.js";import{initConsolePrefix as se,consolePrefix as s}from"./console-prefix.js";import{initDebugFromArgv as te}from"./debug.js";import{fetchLatestBorgmcpVersion as ae,compareVersionsForStaleness as ie}from"./stale-version-check.js";import{defaultCliChoiceDeps as ne,detectCliAvailability as x,installedCliNames as A,parseCliFlag as ce,resolveCliChoice as le}from"./cli-platform.js";import{getRefreshToken as de,getIdToken as pe}from"./config.js";import{composeGetStarted as ue,shouldShowGetStarted as me}from"./get-started.js";import{prepareCodexRemoteLaunch as fe,withCodexCwdArg as ge,defaultCodexRemoteDeps as he,checkCodexBridgeHealthy as we}from"./codex-remote.js";import{findLoadedCodexThread as xe}from"./codex-app-server.js";import{buildAgentKickoffPrompt as ve,recordCodexWakeTarget as ke,socketPathFromRemoteArgs as I}from"./codex-launch.js";import{codexBorgSessionConfigArgs as be}from"./launch-gate.js";import{addCodexMcpServer as Ce,addCodexSessionStartHook as $e,addCodexUserPromptSubmitHook as ye,addMcpServer as Se,addProjectSessionStartHook as Te,addUserPromptSubmitHook as Ae,isCodexMcpServerConfigured as Ie,isMcpServerConfigured as Pe,removeSessionStartHook as Re}from"./config-utils.js";async function De(){te(process.argv),Y(),await se();const c=(async()=>{if(!process.stderr.isTTY)return;const e=g(),r=await ae();if(!r)return;const i=ie(e,r);i.stale&&i.message&&process.stderr.write(`${s()}${i.message}
3
+ `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(V(g())),process.exit(0)),process.argv[2]==="setup"){y(process.argv[3])&&(process.stdout.write(U(g())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(y)&&(process.stdout.write(j(g())),process.exit(0));const e=X(process.argv.slice(3));e.ok||(process.stderr.write(a.red(`${s()}\u25FC borg assimilate: ${e.error}
4
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=Q(),i=await J({role:e.role,flags:e.flags},r);process.exit(i)}if(process.argv[2]==="spawn"){const e=await K();process.exit(e)}if(process.argv[2]==="sync"){const e=q(process.argv.slice(3));e.ok||(process.stderr.write(a.red(`${s()}\u25FC borg sync: ${e.error}
5
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await z({},e.options);process.exit(r)}if(process.argv[2]==="launch-all"){const e=S(process.argv.slice(3));e.ok||(process.stderr.write(a.red(`${s()}\u25FC borg launch-all: ${e.error}
6
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=w(),i=await T(e.args,r);process.exit(i)}if(me(await de()!==null,await pe()!==null)){const e=A(x()).length>0;process.stdout.write(ue(e)),process.exit(0)}const n=ce(process.argv.slice(2));n.error&&(process.stderr.write(a.red(`${s()}\u25FC ${n.error}
7
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const v=async e=>{const r=B({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let o=await le(n.cli,ne(v,()=>process.stdin.isTTY===!0));Le();const t=await H();if(re({extraArgs:process.argv.slice(2),stdinIsTTY:process.stdin.isTTY===!0,stdoutIsTTY:process.stdout.isTTY===!0})){const e=A(x()).find(m=>m!==o)??null;let r=!1;t&&(r=(await Z({targetCubeId:t.cubeId},w())).length>0);const i=await ee({defaultCli:o,otherInstalledCli:e,hasLaunchAllTargets:r},v);if(i.kind==="launch-all"){const m=S([]),R=m.ok?await T(m.args,w()):1;process.exit(R)}o=i.cli}const l=n.rest;oe(t?{label:t.droneLabel,cubeName:t.name}:null,E(process.cwd()));const P=t&&o==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${F(t.cubeId,t.droneId)}\` so each event's task-notification title summarizes the new cube log entry (drone label, role, and first ~80 chars of the message body) \u2014 letting you triage events without reading the full body. `:"";await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const k=o==="codex"?`borg-wake-${L()}`:null;let f,b=[],d={...process.env,BORG_SESSION:"1"},p=null,u=null;if(o==="codex"&&!l.includes("--remote")){console.error(`${s()}${a.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await fe(he());e.warning?(console.error(`${s()}${a.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.",b=e.args,d={...process.env,...e.env,BORG_SESSION:"1"},p=I(e.args),u=e.server?.cleanup??null}else o==="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=I(l),p&&(d={...process.env,BORG_CODEX_REMOTE_WAKE:"1",BORG_SESSION:"1"}));if(t){const e=await M(t.cubeId,t.droneId),r=G(d,e,process.env);if(d=r.env,r.probe){const i=await W(r.probe.descriptor,fetch,r.probe.baseUrl);i.ok||console.error(`${s()}${a.yellow(`warning: ${i.message}`)}`)}}const C=ve({cli:o,codexWakeNonce:k,monitorClause:P,codexWakePathClause:f});let h=[...l,C];o==="codex"&&(h=[...be(),...b,...ge(h,process.cwd())]),console.error(`${s()}${a.blue(`\u25FC Launching ${o==="claude"?"Claude Code":"Codex"}\u2026`)}`);const $=D(o,h,{stdio:"inherit",shell:!1,env:d});o==="codex"&&t&&p&&(ke({deps:{setCodexWakeTarget:N,findLoadedCodexThread:xe},cubeId:t.cubeId,droneId:t.droneId,socketPath:p,passthroughArgs:l,previewNeedle:k??C.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),_(e=>we(e))),$.on("error",e=>{if(u)try{u()}catch{}e.code==="ENOENT"?(console.error(`${s()}${a.red(`
8
+ \u25FC Failed to launch ${o}`)}`),console.error(`${s()}${a.gray(`Make sure ${o} is installed.
9
+ `)}`)):console.error(`${s()}${a.red(`
10
+ \u25FC Failed to launch ${o}: ${e.message}
11
+ `)}`),process.exit(1)}),$.on("exit",e=>{if(u)try{u()}catch{}process.exit(e??0)})}function Le(){const c=x();if(c.claude)try{Pe()||Se(),Te(O(process.cwd())),Re(),Ae()}catch(n){console.error(`${s()}${a.yellow(`warning: Claude Code integration check failed: ${n?.message??n}`)}`)}if(c.codex)try{Ie()||Ce(),$e(),ye()}catch(n){console.error(`${s()}${a.yellow(`warning: Codex integration check failed: ${n?.message??n}`)}`)}}De().catch(c=>{console.error(`${s()}${a.red(`
12
12
  \u25FC Error: ${c.message}
13
13
  `)}`),process.exit(1)});
package/dist/cubes.d.ts CHANGED
@@ -92,4 +92,13 @@ export declare function getCodexWakeTarget(cubeId: string, droneId: string): Pro
92
92
  * cubes.ts stays free of the codex-remote dependency.
93
93
  */
94
94
  export declare function pruneDeadCodexWakeTargets(socketLiveness: (socketPath: string) => boolean | null): Promise<void>;
95
+ export interface LaunchBackendRecord {
96
+ /** Backend descriptor, e.g. `ollama:qwen3-coder-next` or `claude:claude-opus-4-8`. */
97
+ backend: string;
98
+ /** Resolved Ollama host for an ollama backend; null for claude. */
99
+ ollamaBaseUrl: string | null;
100
+ }
101
+ export declare function setLaunchBackend(cubeId: string, droneId: string, record: LaunchBackendRecord, file?: string): Promise<void>;
102
+ export declare function getLaunchBackend(cubeId: string, droneId: string, file?: string): Promise<LaunchBackendRecord | null>;
103
+ export declare function clearLaunchBackend(cubeId: string, droneId: string, file?: string): Promise<void>;
95
104
  //# sourceMappingURL=cubes.d.ts.map
package/dist/cubes.js CHANGED
@@ -1,4 +1,5 @@
1
- import{existsSync as E}from"node:fs";import{mkdir as f,readFile as p,writeFile as y,unlink as C}from"node:fs/promises";import{homedir as I}from"node:os";import{dirname as c,join as o,resolve as x}from"node:path";import{pruneDeadWakeTargets as F}from"./codex-wake-resolve.js";const a=o(I(),".config","borgmcp"),s=o(a,"cubes.json"),d=o(a,"launch.json"),g=o(a,"codex-wake-targets.json"),N=o(a,"inboxes");function i(t=process.cwd()){let e=x(t);for(;;){if(E(o(e,".git")))return e;const r=c(e);if(r===e)return x(t);e=r}}const u=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function _(t,e){if(!u.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!u.test(e))throw new Error(`Invalid droneId: ${e}`);return o(N,t,`${e}.log`)}function k(t){return t!==null&&typeof t=="object"&&typeof t.projects=="object"&&t.projects!==null&&!Array.isArray(t.projects)}async function l(){let t;try{t=await p(s,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}let e;try{e=JSON.parse(t)}catch{return null}return k(e)?e:null}async function h(t){await f(c(s),{recursive:!0}),await y(s,JSON.stringify(t,null,2)+`
2
- `,{mode:384})}function O(t){return t!==null&&typeof t=="object"&&typeof t.projects=="object"&&t.projects!==null&&!Array.isArray(t.projects)}async function w(){let t;try{t=await p(d,"utf8")}catch(e){if(e?.code==="ENOENT")return null;throw e}try{const e=JSON.parse(t);return O(e)?e:null}catch{return null}}async function A(t){await f(c(d),{recursive:!0}),await y(d,JSON.stringify(t,null,2)+`
3
- `,{mode:384})}function b(t,e){if(!u.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!u.test(e))throw new Error(`Invalid droneId: ${e}`);return`${t}:${e}`}function T(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 p(g,"utf8")}catch(e){if(e?.code==="ENOENT")return null;throw e}try{const e=JSON.parse(t);return T(e)?e:null}catch{return null}}async function m(t){await f(c(g),{recursive:!0}),await y(g,JSON.stringify(t,null,2)+`
4
- `,{mode:384})}async function $(){const t=await l();if(!t)return null;const e=i(),r=t.projects[e];return!r||typeof r.cubeId!="string"||!r.cubeId||typeof r.droneId!="string"||!r.droneId?null:r}async function v(t){const e=await l()??{projects:{}};e.projects[i()]=t,await h(e)}function J(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 R(){const t=await l();if(!t)return;const e=i();if(e in t.projects){if(delete t.projects[e],Object.keys(t.projects).length===0){try{await C(s)}catch(r){if(r?.code!=="ENOENT")throw r}return}await h(t)}}async function U(){const t=await w();if(!t)return null;const e=t.projects[i()];return e?.cli==="claude"||e?.cli==="codex"?e.cli:null}async function B(t){const e=await w();if(!e)return null;const r=e.projects[i(t)];return r?.cli==="claude"||r?.cli==="codex"?r.cli:null}async function K(){const t=await l();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 X(t){const e=await w()??{projects:{}};e.projects[i()]={cli:t},await A(e)}async function G(t,e,r){const n=await j()??{targets:{}};n.targets[b(t,e)]={...r,updatedAt:new Date().toISOString()},await m(n)}async function H(t,e){const r=await j();if(!r)return null;const n=r.targets[b(t,e)];return!n||typeof n.threadId!="string"||typeof n.socketPath!="string"?null:n}async function q(t){const e=await j();if(!e)return;const{targets:r,changed:n}=F(e.targets,t);n&&await m({...e,targets:r})}export{J as activeCubeWithFreshRegenIdentity,R as clearActiveCube,i as findProjectRoot,$ as getActiveCube,H as getCodexWakeTarget,U as getProjectCliPreference,B as getProjectCliPreferenceForPath,_ as inboxPathForDrone,q as pruneDeadCodexWakeTargets,K as readAllProjectIdentities,v as setActiveCube,G as setCodexWakeTarget,X as setProjectCliPreference};
1
+ import{existsSync as A}from"node:fs";import{mkdir as l,readFile as f,writeFile as d,unlink as m}from"node:fs/promises";import{homedir as L}from"node:os";import{dirname as s,join as c,resolve as N}from"node:path";import{pruneDeadWakeTargets as S}from"./codex-wake-resolve.js";import{BACKEND_DESCRIPTOR_REGEX as T}from"./backend-presets.js";const u=c(L(),".config","borgmcp"),p=c(u,"cubes.json"),w=c(u,"launch.json"),g=c(u,"codex-wake-targets.json"),b=c(u,"launch-backends.json"),B=c(u,"inboxes");function a(e=process.cwd()){let t=N(e);for(;;){if(A(c(t,".git")))return t;const r=s(t);if(r===t)return N(e);t=r}}const i=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function G(e,t){if(!i.test(e))throw new Error(`Invalid cubeId: ${e}`);if(!i.test(t))throw new Error(`Invalid droneId: ${t}`);return c(B,e,`${t}.log`)}function P(e){return e!==null&&typeof e=="object"&&typeof e.projects=="object"&&e.projects!==null&&!Array.isArray(e.projects)}async function y(){let e;try{e=await f(p,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}let t;try{t=JSON.parse(e)}catch{return null}return P(t)?t:null}async function C(e){await l(s(p),{recursive:!0}),await d(p,JSON.stringify(e,null,2)+`
2
+ `,{mode:384})}function _(e){return e!==null&&typeof e=="object"&&typeof e.projects=="object"&&e.projects!==null&&!Array.isArray(e.projects)}async function h(){let e;try{e=await f(w,"utf8")}catch(t){if(t?.code==="ENOENT")return null;throw t}try{const t=JSON.parse(e);return _(t)?t:null}catch{return null}}async function $(e){await l(s(w),{recursive:!0}),await d(w,JSON.stringify(e,null,2)+`
3
+ `,{mode:384})}function F(e,t){if(!i.test(e))throw new Error(`Invalid cubeId: ${e}`);if(!i.test(t))throw new Error(`Invalid droneId: ${t}`);return`${e}:${t}`}function D(e){return e!==null&&typeof e=="object"&&typeof e.targets=="object"&&e.targets!==null&&!Array.isArray(e.targets)}async function j(){let e;try{e=await f(g,"utf8")}catch(t){if(t?.code==="ENOENT")return null;throw t}try{const t=JSON.parse(e);return D(t)?t:null}catch{return null}}async function I(e){await l(s(g),{recursive:!0}),await d(g,JSON.stringify(e,null,2)+`
4
+ `,{mode:384})}async function H(){const e=await y();if(!e)return null;const t=a(),r=e.projects[t];return!r||typeof r.cubeId!="string"||!r.cubeId||typeof r.droneId!="string"||!r.droneId?null:r}async function q(e){const t=await y()??{projects:{}};t.projects[a()]=e,await C(t)}function z(e,t){const r=t.cube?.name??e.name,n=t.drone?.label??e.droneLabel;return r===e.name&&n===e.droneLabel?e:{...e,name:r,droneLabel:n}}async function M(){const e=await y();if(!e)return;const t=a();if(t in e.projects){if(delete e.projects[t],Object.keys(e.projects).length===0){try{await m(p)}catch(r){if(r?.code!=="ENOENT")throw r}return}await C(e)}}async function Q(){const e=await h();if(!e)return null;const t=e.projects[a()];return t?.cli==="claude"||t?.cli==="codex"?t.cli:null}async function V(e){const t=await h();if(!t)return null;const r=t.projects[a(e)];return r?.cli==="claude"||r?.cli==="codex"?r.cli:null}async function Y(){const e=await y();return e?Object.entries(e.projects).filter(([,t])=>t!==null&&typeof t=="object"&&typeof t.cubeId=="string"&&t.cubeId.length>0&&typeof t.droneId=="string"&&t.droneId.length>0).map(([t,r])=>({projectPath:t,cube:r})):[]}async function Z(e){const t=await h()??{projects:{}};t.projects[a()]={cli:e},await $(t)}async function ee(e,t,r){const n=await j()??{targets:{}};n.targets[F(e,t)]={...r,updatedAt:new Date().toISOString()},await I(n)}async function te(e,t){const r=await j();if(!r)return null;const n=r.targets[F(e,t)];return!n||typeof n.threadId!="string"||typeof n.socketPath!="string"?null:n}async function re(e){const t=await j();if(!t)return;const{targets:r,changed:n}=S(t.targets,e);n&&await I({...t,targets:r})}function k(e,t){if(!i.test(e))throw new Error(`Invalid cubeId: ${e}`);if(!i.test(t))throw new Error(`Invalid droneId: ${t}`);return`${e}:${t}`}function U(e){return e!==null&&typeof e=="object"&&typeof e.backends=="object"&&e.backends!==null&&!Array.isArray(e.backends)}async function x(e){let t;try{t=await f(e,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}try{const r=JSON.parse(t);return U(r)?r:null}catch{return null}}async function O(e,t){await l(s(e),{recursive:!0}),await d(e,JSON.stringify(t,null,2)+`
5
+ `,{mode:384})}async function ne(e,t,r,n=b){const o=await x(n)??{backends:{}};o.backends[k(e,t)]={backend:r.backend,ollamaBaseUrl:r.ollamaBaseUrl},await O(n,o)}async function oe(e,t,r=b){const n=await x(r);if(!n)return null;const o=n.backends[k(e,t)];return!o||typeof o.backend!="string"||!T.test(o.backend)?null:{backend:o.backend,ollamaBaseUrl:typeof o.ollamaBaseUrl=="string"?o.ollamaBaseUrl:null}}async function ce(e,t,r=b){const n=await x(r);if(!n)return;const o=k(e,t);if(o in n.backends){if(delete n.backends[o],Object.keys(n.backends).length===0){try{await m(r)}catch(E){if(E?.code!=="ENOENT")throw E}return}await O(r,n)}}export{z as activeCubeWithFreshRegenIdentity,M as clearActiveCube,ce as clearLaunchBackend,a as findProjectRoot,H as getActiveCube,te as getCodexWakeTarget,oe as getLaunchBackend,Q as getProjectCliPreference,V as getProjectCliPreferenceForPath,G as inboxPathForDrone,re as pruneDeadCodexWakeTargets,Y as readAllProjectIdentities,q as setActiveCube,ee as setCodexWakeTarget,ne as setLaunchBackend,Z as setProjectCliPreference};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "borgmcp",
3
- "version": "1.0.28",
3
+ "version": "1.0.29",
4
4
  "description": "Coordinate AI coding agents in shared cubes. Works with Claude Code and Codex. Create projects, assign roles, and share a live activity log.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",