borgmcp 1.0.33 → 1.0.34

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.
@@ -107,15 +107,15 @@ export interface AssimilateDeps {
107
107
  ok: boolean;
108
108
  message?: string;
109
109
  }>;
110
- getLaunchModel: (cubeId: string, droneId: string) => Promise<{
110
+ getLaunchModel: (cubeId: string, worktreePath: string, priorDroneId: string | null) => Promise<{
111
111
  model: string;
112
112
  ollamaBaseUrl: string | null;
113
113
  } | null>;
114
- setLaunchModel: (cubeId: string, droneId: string, record: {
114
+ setLaunchModel: (cubeId: string, worktreePath: string, record: {
115
115
  model: string;
116
116
  ollamaBaseUrl: string | null;
117
117
  }) => Promise<void>;
118
- clearLaunchModel: (cubeId: string, droneId: string) => Promise<void>;
118
+ clearLaunchModel: (cubeId: string, worktreePath: string) => Promise<void>;
119
119
  }
120
120
  export declare function runAssimilate(args: AssimilateArgs, deps: AssimilateDeps): Promise<number>;
121
121
  /**
@@ -1,48 +1,48 @@
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 F}from"./name-validator.js";import{renderAssimilationWelcome as le}from"./assimilate-welcome.js";import{shellEscape as se}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{DroneEvictedError as he}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as ge}from"./launch-gate.js";import{resolveLaunchEnv as we,resolveOllamaBaseUrl as be,parseModel as ke}from"./model-presets.js";async function Me(r,e){if(r.role!==void 0){const t=F(r.role);if(!t.ok)return e.stderr(t.error+`
2
- `),1}if(r.flags.worktree!==void 0){const t=F(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 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 x=e.cwd();e.stderr(`Checking your cubes\u2026
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 fe}from"./codex-remote.js";import{buildAgentKickoffPrompt as de,recordCodexWakeTarget as he,socketPathFromRemoteArgs as ge}from"./codex-launch.js";import{perWorktreeBranchName as q,adoptWorktree as we,computeWorktreePath as z}from"./worktree-lifecycle.js";import{DroneEvictedError as be}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as ke}from"./launch-gate.js";import{resolveLaunchEnv as ye,resolveOllamaBaseUrl as ve,parseModel as $e}from"./model-presets.js";async function Fe(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 R=e.cwd();e.stderr(`Checking your cubes\u2026
5
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 _=A.find(t=>t.name===n);if(!_&&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 c,N;if(_)c=await e.getCube(i.apiUrl,i.token,_.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(`
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&&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,_;if(N)s=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
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
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
- `),c=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=re(c.roles,r.role),!d){const t=c.roles.map(m=>m.name).join(", "),o=pe(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}
14
+ `),s=await e.createCube(i.apiUrl,i.token,t?{name:n??void 0,template:t}:{name:n??void 0}),_=!0}let f;if(r.role!==void 0){if(f=ie(s.roles,r.role),!f){const t=s.roles.map(m=>m.name).join(", "),o=Ce(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}
15
15
  (Use --template <name> on first-drone setup or run \`borg_create-role\` from inside Claude.)
16
- `),1}}else if(d=ne(c.roles,{isFirstDrone:N}),!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 y;if(C&&r.flags.here)if(C.cubeId===c.id)y=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=y!=null?await e.getLaunchModel(c.id,y):null,h=r.flags.model??T?.model??d.default_model??null,P=be(process.env,h!=null&&h===T?.model?T?.ollamaBaseUrl:void 0);if(h){const t=await e.checkModelReachable(h,e.fetch,P);if(!t.ok)return e.stderr(`${t.message}
19
- `),1}const g=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${c.name}' as ${d.name}\u2026
20
- `);let s;try{s=await e.assimilate(i.apiUrl,i.token,{cube_id:c.id,role_id:d.id,hostname:e.getHostname(),agent_kind:g,model:h,...y?{prior_drone_id:y}:{}})}catch(t){if(t instanceof he&&y!=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.
16
+ `),1}}else if(f=ae(s.roles,{isFirstDrone:_}),!f)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 $=await e.getActiveCube();let p;if($&&r.flags.here)if($.cubeId===s.id)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||$!==null&&!r.flags.here,J=p??$?.droneId??null,I=O?null:await e.getLaunchModel(s.id,a,J),g=r.flags.model??I?.model??f.default_model??null,P=ve(process.env,g!=null&&g===I?.model?I?.ollamaBaseUrl:void 0);if(g){const t=await e.checkModelReachable(g,e.fetch,P);if(!t.ok)return e.stderr(`${t.message}
19
+ `),1}const w=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${s.name}' as ${f.name}\u2026
20
+ `);let c;try{c=await e.assimilate(i.apiUrl,i.token,{cube_id:s.id,role_id:f.id,hostname:e.getHostname(),agent_kind:w,model:g,...p?{prior_drone_id:p}:{}})}catch(t){if(t instanceof be&&p!=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
21
  `),1;const o=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${o}
22
- `),1}const p=c.roles.find(t=>t.id===s.role_id)??d;s.reattached?e.stderr(`re-attached to existing seat ${s.drone_label} (session token rotated, no new drone minted)
23
- `):p.id!==d.id&&e.stderr(`Note: your invite didn't grant the "${d.name}" role \u2014 assimilated as "${p.name}" instead.
24
- `);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.
22
+ `),1}const x=s.roles.find(t=>t.id===c.role_id)??f;c.reattached?e.stderr(`re-attached to existing seat ${c.drone_label} (session token rotated, no new drone minted)
23
+ `):x.id!==f.id&&e.stderr(`Note: your invite didn't grant the "${f.name}" role \u2014 assimilated as "${x.name}" instead.
24
+ `);const V=O;let d=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.
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
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(),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}
28
- `);const v=E(a),$=r.flags.worktree??te(p.name);if($.length===0)return e.stderr(`cannot derive a worktree name from role "${p.name}"; pass an explicit --worktree <name>
29
- `),1;const S=e.homedir();let b=G(S,v,$),H=2;for(;e.pathExists(b)||$e(e,a,b);)b=G(S,v,$,H),H++;e.mkdirp(Z(b));const O=Y(E(b),v),B=e.runSync("git",["worktree","add","-b",O,b,o],a);if(B.status!==0)return e.stderr(`git worktree add failed: ${q(B.stderr)}
30
- `),1;e.stderr(`spawned sibling worktree at ${b} on branch ${O} (${o}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
31
- `),e.chdir(b),e.stderr(ye(b,O,a)),f=e.cwd()}try{await e.setActiveCube({cubeId:s.cube_id,droneId:s.drone_id,name:c.name,sessionToken:s.session_token,droneLabel:s.drone_label,apiUrl:i.apiUrl})}catch(t){const o=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${o}
32
- `),f){const u=e.runSync("git",["worktree","remove","--force",f],a);u.status===0?e.stderr(`rolled back spawned worktree at ${f}
33
- `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${f}\` (rollback attempt failed: ${q(u.stderr).trim()||"unknown"})
34
- `)}return 1}e.setTerminalTitle(s.drone_label,c.name);const K=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(le(p.name,c.name,K));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(!f){e.runSync("git",["fetch","origin","--prune"],w);const t=Y(E(w),E(a)),o=fe(e.runSync,w,t,"origin/main");o.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
36
- `),e.stderr(ve(w,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 ${g} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
38
- `);const J=e.getInboxPath(s.cube_id,s.drone_id),I=g==="codex"?`borg-wake-${ee()}`:null,V=g==="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,W=[],R,L=null,D=null;h?await e.setLaunchModel(s.cube_id,s.drone_id,{model:h,ollamaBaseUrl:ke(h).kind==="ollama"?P:null}):await e.clearLaunchModel(s.cube_id,s.drone_id);const M=we(h,P),j={...process.env,...M.set,BORG_SESSION:"1"};for(const t of M.unset)delete j[t];if(g==="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.",W=t.args,Object.keys(t.env).length>0&&Object.assign(j,t.env),L=de(t.args),D=t.server?.cleanup??null}R=[ue({cli:g,codexWakeNonce:I,monitorClause:V,codexWakePathClause:U})],g==="codex"&&(R=[...ge(),...W,...ce(R,w)]);const Q=e.exec(g,R,w,j);g==="codex"&&L&&I&&me({deps:e,cubeId:s.cube_id,droneId:s.drone_id,socketPath:L,cwd:w,previewNeedle:I,launchedAtSeconds:Math.floor(Date.now()/1e3)});const X=await Q;if(D)try{D()}catch{}return f&&x!==f&&e.stderr(`
40
- Agent exited. You were working in ${f}; your shell is back in ${x}.
28
+ `);const y=E(a),v=r.flags.worktree??oe(x.name);if(v.length===0)return e.stderr(`cannot derive a worktree name from role "${x.name}"; pass an explicit --worktree <name>
29
+ `),1;const S=e.homedir();let b=z(S,y,v),F=2;for(;e.pathExists(b)||Re(e,a,b);)b=z(S,y,v,F),F++;e.mkdirp(re(b));const W=q(E(b),y),Y=e.runSync("git",["worktree","add","-b",W,b,o],a);if(Y.status!==0)return e.stderr(`git worktree add failed: ${K(Y.stderr)}
30
+ `),1;e.stderr(`spawned sibling worktree at ${b} on branch ${W} (${o}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
31
+ `),e.chdir(b),e.stderr(pe(b,W,a)),d=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})}catch(t){const o=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${o}
32
+ `),d){const u=e.runSync("git",["worktree","remove","--force",d],a);u.status===0?e.stderr(`rolled back spawned worktree at ${d}
33
+ `):e.stderr(`manual cleanup needed: \`git worktree remove --force ${d}\` (rollback attempt failed: ${K(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(x.name,s.name,Q));const 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
35
+ `)}if(!d){e.runSync("git",["fetch","origin","--prune"],h);const t=q(E(h),E(a)),o=we(e.runSync,h,t,"origin/main");o.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
36
+ `),e.stderr(xe(h,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 ${w} 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),T=w==="codex"?`borg-wake-${ne()}`:null,Z=w==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${X}\` 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,M=[],C,D=null,L=null;const H=e.findProjectRoot(h);g?await e.setLaunchModel(c.cube_id,H,{model:g,ollamaBaseUrl:$e(g).kind==="ollama"?P:null}):await e.clearLaunchModel(c.cube_id,H);const B=ye(g,P),j={...process.env,...B.set,BORG_SESSION:"1"};for(const t of B.unset)delete j[t];if(w==="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.",M=t.args,Object.keys(t.env).length>0&&Object.assign(j,t.env),D=ge(t.args),L=t.server?.cleanup??null}C=[de({cli:w,codexWakeNonce:T,monitorClause:Z,codexWakePathClause:U})],w==="codex"&&(C=[...ke(),...M,...fe(C,h)]);const ee=e.exec(w,C,h,j);w==="codex"&&D&&T&&he({deps:e,cubeId:c.cube_id,droneId:c.drone_id,socketPath:D,cwd:h,previewNeedle:T,launchedAtSeconds:Math.floor(Date.now()/1e3)});const te=await ee;if(L)try{L()}catch{}return d&&R!==d&&e.stderr(`
40
+ Agent exited. You were working in ${d}; your shell is back in ${R}.
41
41
  To return:
42
- cd ${se(f)}
43
- `),X}function ye(r,e,i){return`
42
+ cd ${me(d)}
43
+ `),te}function pe(r,e,i){return`
44
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 ve(r,e){return`
45
+ `}function xe(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 q(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function $e(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 pe(r,e){if(e.length===0)return null;const i=r.toLowerCase();let a=null;for(const n of e){const l=xe(i,n.toLowerCase());l<=2&&(a===null||l<a.distance)&&(a={name:n,distance:l})}return a?a.name:null}function xe(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 x=r[n-1]===e[l-1]?0:1;a[l]=Math.min(a[l-1]+1,i[l]+1,i[l-1]+x)}for(let l=0;l<=e.length;l++)i[l]=a[l]}return i[e.length]}export{Me as runAssimilate,q as safeStderr,pe as suggestRoleName};
47
+ `}function K(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 Ce(r,e){if(e.length===0)return null;const i=r.toLowerCase();let a=null;for(const n of e){const l=Se(i,n.toLowerCase());l<=2&&(a===null||l<a.distance)&&(a={name:n,distance:l})}return a?a.name:null}function Se(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 R=r[n-1]===e[l-1]?0:1;a[l]=Math.min(a[l-1]+1,i[l]+1,i[l-1]+R)}for(let l=0;l<=e.length;l++)i[l]=a[l]}return i[e.length]}export{Fe as runAssimilate,K as safeStderr,Ce as suggestRoleName};
@@ -1,3 +1,3 @@
1
- import{spawnSync as p,spawn as l}from"node:child_process";import{existsSync as h,mkdirSync as f}from"node:fs";import{hostname as b,homedir as C}from"node:os";import{createInterface as d}from"node:readline/promises";import{API_URL as u,getValidToken as m,listCubes as _,getCube as T,createCube as y,assimilate as k,listTemplates as w}from"./remote-client.js";import{findProjectRoot as g,getActiveCube as L,setActiveCube as x,inboxPathForDrone as M,setCodexWakeTarget as S,getLaunchModel as v,setLaunchModel as R,clearLaunchModel 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{checkModelReachable as V}from"./model-presets.js";function $(){return{runSync:(e,t,o)=>{const r=p(e,t,{cwd:o,encoding:"utf-8"});return{status:r.status,stdout:r.stdout??"",stderr:r.stderr??""}},pathExists:e=>h(e),cwd:()=>process.cwd(),chdir:e=>process.chdir(e),homedir:()=>C(),mkdirp:e=>f(e,{recursive:!0}),exec:(e,t,o,r)=>new Promise((s,i)=>{const a=l(e,t,{cwd:o,stdio:"inherit",shell:!1,env:r??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 m(),apiUrl:u}}catch{return null}},runSetup:async()=>(await P(),{token:await m(),apiUrl:u}),listCubes:async(e,t)=>{const{cubes:o}=await _();return o.map(r=>({id:r.id,name:r.name}))},getCube:async(e,t,o)=>{const r=await T(o);return{id:r.id,name:r.name,roles:r.roles}},createCube:async(e,t,o)=>{const r=await y(o.name,"",o.template?{template:o.template}:void 0);return{id:r.id,name:r.name,roles:r.roles}},assimilate:async(e,t,o)=>{const r=await k({cube_id:o.cube_id,role_id:o.role_id,...o.prior_drone_id?{prior_drone_id:o.prior_drone_id}:{},...o.model!=null?{model:o.model}:{}},void 0,o.hostname??null,o.agent_kind??null);return{cube_id:r.cube.id,drone_id:r.drone.id,drone_label:r.drone.label,session_token:r.sessionToken,role_id:r.role.id,reattached:r.reattached===!0}},listTemplates:async(e,t)=>{const{templates:o}=await w();return o.map(r=>({name:r.name,description:r.description}))},getInboxPath:(e,t)=>M(e,t),probeMcpReady:()=>new Promise(e=>{const t=l("borg-mcp",[],{stdio:["pipe","pipe","pipe"],shell:!1});let o="",r=!1;const s=n=>{if(!r){r=!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(r)}),t.stdout?.on("data",n=>{o+=n.toString("utf-8");for(const c of o.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 o=d({input:process.stdin,output:process.stdout});try{return await o.question(t)}finally{o.close()}},()=>process.stdin.isTTY===!0)),prepareCodexRemoteLaunch:()=>H(I()),setCodexWakeTarget:S,findLoadedCodexThread:q,fetch,checkModelReachable:V,getLaunchModel:(e,t)=>v(e,t),setLaunchModel:(e,t,o)=>R(e,t,o),clearLaunchModel:(e,t)=>A(e,t)}}export{$ as buildDefaultAssimilateDeps};
1
+ import{spawnSync as p,spawn as l}from"node:child_process";import{existsSync as h,mkdirSync as f}from"node:fs";import{hostname as b,homedir as C}from"node:os";import{createInterface as d}from"node:readline/promises";import{API_URL as u,getValidToken as m,listCubes as _,getCube as T,createCube as y,assimilate as k,listTemplates as w}from"./remote-client.js";import{findProjectRoot as g,getActiveCube as L,setActiveCube as x,inboxPathForDrone as M,setCodexWakeTarget as S,getLaunchModel as v,setLaunchModel as R,clearLaunchModel 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{checkModelReachable as V}from"./model-presets.js";function $(){return{runSync:(e,o,t)=>{const r=p(e,o,{cwd:t,encoding:"utf-8"});return{status:r.status,stdout:r.stdout??"",stderr:r.stderr??""}},pathExists:e=>h(e),cwd:()=>process.cwd(),chdir:e=>process.chdir(e),homedir:()=>C(),mkdirp:e=>f(e,{recursive:!0}),exec:(e,o,t,r)=>new Promise((s,i)=>{const a=l(e,o,{cwd:t,stdio:"inherit",shell:!1,env:r??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 o=d({input:process.stdin,output:process.stdout});try{return await o.question(e)}finally{o.close()}},isTTY:()=>process.stdin.isTTY===!0,getHostname:()=>b(),setTerminalTitle:(e,o)=>{j({label:e,cubeName:o},o)},getActiveCube:()=>L(),setActiveCube:e=>x(e),findProjectRoot:e=>g(e),installProjectSessionHook:e=>{U(e)},getCachedAuth:async()=>{try{return{token:await m(),apiUrl:u}}catch{return null}},runSetup:async()=>(await P(),{token:await m(),apiUrl:u}),listCubes:async(e,o)=>{const{cubes:t}=await _();return t.map(r=>({id:r.id,name:r.name}))},getCube:async(e,o,t)=>{const r=await T(t);return{id:r.id,name:r.name,roles:r.roles}},createCube:async(e,o,t)=>{const r=await y(t.name,"",t.template?{template:t.template}:void 0);return{id:r.id,name:r.name,roles:r.roles}},assimilate:async(e,o,t)=>{const r=await k({cube_id:t.cube_id,role_id:t.role_id,...t.prior_drone_id?{prior_drone_id:t.prior_drone_id}:{},...t.model!=null?{model:t.model}:{}},void 0,t.hostname??null,t.agent_kind??null);return{cube_id:r.cube.id,drone_id:r.drone.id,drone_label:r.drone.label,session_token:r.sessionToken,role_id:r.role.id,reattached:r.reattached===!0}},listTemplates:async(e,o)=>{const{templates:t}=await w();return t.map(r=>({name:r.name,description:r.description}))},getInboxPath:(e,o)=>M(e,o),probeMcpReady:()=>new Promise(e=>{const o=l("borg-mcp",[],{stdio:["pipe","pipe","pipe"],shell:!1});let t="",r=!1;const s=n=>{if(!r){r=!0;try{o.kill("SIGTERM")}catch{}e(n)}},i=setTimeout(()=>s(!1),2e3);o.on("error",()=>{clearTimeout(i),s(!1)}),o.on("exit",()=>{clearTimeout(i),s(r)}),o.stdout?.on("data",n=>{t+=n.toString("utf-8");for(const c of t.split(`
2
+ `))if(c.includes('"protocolVersion"')&&c.includes('"result"')){clearTimeout(i),s(!0);return}});const a=JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"borg-assimilate-probe",version:"0.9.3"}}});try{o.stdin?.write(a+`
3
+ `)}catch{clearTimeout(i),s(!1)}}),resolveCli:e=>G(e,D(async o=>{const t=d({input:process.stdin,output:process.stdout});try{return await t.question(o)}finally{t.close()}},()=>process.stdin.isTTY===!0)),prepareCodexRemoteLaunch:()=>H(I()),setCodexWakeTarget:S,findLoadedCodexThread:q,fetch,checkModelReachable:V,getLaunchModel:(e,o,t)=>v(e,o,t),setLaunchModel:(e,o,t)=>R(e,o,t),clearLaunchModel:(e,o)=>A(e,o)}}export{$ as buildDefaultAssimilateDeps};
@@ -7,6 +7,10 @@ export interface TmuxOpts {
7
7
  attachMode: 'attach' | 'switch' | 'none';
8
8
  /** ISO-8601 captured before the first send-keys (lock-marker launchedAt). */
9
9
  launchedAtISO: string;
10
+ /** Stagger between drone launches (ms) to avoid the rate limiter; 0 disables. */
11
+ launchDelayMs: number;
12
+ /** Injectable sleep (real setTimeout in prod; no-op spy in tests). */
13
+ sleep: (ms: number) => Promise<void>;
10
14
  }
11
15
  export declare function runTmuxBackend(candidates: DroneCandidate[], opts: TmuxOpts, deps: LaunchAllDeps): Promise<void>;
12
16
  //# sourceMappingURL=launch-all-tmux.d.ts.map
@@ -1 +1 @@
1
- import{buildLaunchCommand as h}from"../launch-all-command.js";import{writeLockMarker as d}from"../launch-all-locks.js";async function k(o,a,t){const{sessionName:e,borgPath:s,attachMode:c,launchedAtISO:u}=a,m=t.runSyncExitCode("tmux",["has-session","-t",e])===0;for(let r=0;r<o.length;r++){const n=o[r];let i;r===0&&!m?i=t.runSync("tmux",["new-session","-d","-P","-F","#{window_id}","-s",e,"-c",n.worktreeDir]).trim():i=t.runSync("tmux",["new-window","-P","-F","#{window_id}","-t",e,"-c",n.worktreeDir]).trim(),t.runSync("tmux",["rename-window","-t",i,n.droneLabel]);const w=h(n.worktreeDir,s,{keepOpenOnFail:!0});t.runSync("tmux",["send-keys","-t",i,w,"Enter"]),d(t,n.cubeId,n.droneLabel,n.worktreeDir,u)}c==="switch"?t.attachInteractive("tmux",["switch-client","-t",e]):c==="attach"&&t.attachInteractive("tmux",["attach-session","-t",e])}export{k as runTmuxBackend};
1
+ import{buildLaunchCommand as d}from"../launch-all-command.js";import{writeLockMarker as x}from"../launch-all-locks.js";async function y(o,s,t){const{sessionName:e,borgPath:u,attachMode:c,launchedAtISO:w,launchDelayMs:a,sleep:m}=s,l=t.runSyncExitCode("tmux",["has-session","-t",e])===0;for(let r=0;r<o.length;r++){const n=o[r];r>0&&a>0&&await m(a);let i;r===0&&!l?i=t.runSync("tmux",["new-session","-d","-P","-F","#{window_id}","-s",e,"-c",n.worktreeDir]).trim():i=t.runSync("tmux",["new-window","-P","-F","#{window_id}","-t",e,"-c",n.worktreeDir]).trim(),t.runSync("tmux",["rename-window","-t",i,n.droneLabel]);const h=d(n.worktreeDir,u,{keepOpenOnFail:!0});t.runSync("tmux",["send-keys","-t",i,h,"Enter"]),x(t,n.cubeId,n.droneLabel,n.worktreeDir,w)}c==="switch"?t.attachInteractive("tmux",["switch-client","-t",e]):c==="attach"&&t.attachInteractive("tmux",["attach-session","-t",e])}export{y as runTmuxBackend};
@@ -4,6 +4,10 @@ export interface WindowsOpts {
4
4
  borgPath: string;
5
5
  platform: NodeJS.Platform;
6
6
  launchedAtISO: string;
7
+ /** Stagger between drone launches (ms) to avoid the rate limiter; 0 disables. */
8
+ launchDelayMs: number;
9
+ /** Injectable sleep (real setTimeout in prod; no-op spy in tests). */
10
+ sleep: (ms: number) => Promise<void>;
7
11
  }
8
12
  export declare function runWindowsBackend(candidates: DroneCandidate[], opts: WindowsOpts, deps: LaunchAllDeps): Promise<void>;
9
13
  //# sourceMappingURL=launch-all-windows.d.ts.map
@@ -1,12 +1,12 @@
1
- import{buildLaunchCommand as l}from"../launch-all-command.js";import{writeLockMarker as m}from"../launch-all-locks.js";function s(o){return o.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}class u extends Error{}function f(o,t,e){const a=e.pathExists("/Applications/iTerm.app"),i=e.pathExists("/Applications/Terminal.app");if(!a&&!i)throw new u(`borg launch-all: --mode windows requires a compatible terminal app.
1
+ import{buildLaunchCommand as m}from"../launch-all-command.js";import{writeLockMarker as u}from"../launch-all-locks.js";function s(t){return t.replace(/\\/g,"\\\\").replace(/"/g,'\\"')}class f extends Error{}async function w(t,e,r){const i=r.pathExists("/Applications/iTerm.app"),o=r.pathExists("/Applications/Terminal.app");if(!i&&!o)throw new f(`borg launch-all: --mode windows requires a compatible terminal app.
2
2
  Not found: iTerm.app, Terminal.app
3
3
  Install iTerm2 (https://iterm2.com) or use --mode tmux (brew install tmux).
4
- `);for(const n of o){const r=l(n.worktreeDir,t.borgPath),c=a?`tell application "iTerm"
5
- tell current window to create tab with default profile command "${s(r)}"
4
+ `);for(let a=0;a<t.length;a++){const n=t[a];a>0&&e.launchDelayMs>0&&await e.sleep(e.launchDelayMs);const l=m(n.worktreeDir,e.borgPath),c=i?`tell application "iTerm"
5
+ tell current window to create tab with default profile command "${s(l)}"
6
6
  end tell`:`tell application "Terminal"
7
- do script "${s(r)}"
7
+ do script "${s(l)}"
8
8
  activate
9
- end tell`;e.runSync("osascript",["-e",c]),m(e,n.cubeId,n.droneLabel,n.worktreeDir,t.launchedAtISO)}}function p(o,t,e){const a=e.getEnv("BORG_TERMINAL")||e.getEnv("TERMINAL"),i=["gnome-terminal","konsole","kitty","wezterm","xterm"];let n=a;if(!n){for(const r of i)if(e.runSyncExitCode("which",[r])===0){n=r;break}}if(!n)throw new u(`borg launch-all: --mode windows requires a terminal emulator.
9
+ end tell`;r.runSync("osascript",["-e",c]),u(r,n.cubeId,n.droneLabel,n.worktreeDir,e.launchedAtISO)}}async function h(t,e,r){const i=r.getEnv("BORG_TERMINAL")||r.getEnv("TERMINAL"),o=["gnome-terminal","konsole","kitty","wezterm","xterm"];let a=i;if(!a){for(const n of o)if(r.runSyncExitCode("which",[n])===0){a=n;break}}if(!a)throw new f(`borg launch-all: --mode windows requires a terminal emulator.
10
10
  Not found. Set $BORG_TERMINAL=<path> or use --mode tmux.
11
- `);for(const r of o){const c=l(r.worktreeDir,t.borgPath,{keepOpenOnFail:!0});e.runSync(n,["-e","sh","-c",c]),m(e,r.cubeId,r.droneLabel,r.worktreeDir,t.launchedAtISO)}}const d=/[\x00-\x1f\x7f]/;async function b(o,t,e){const a=o.filter(i=>d.test(i.worktreeDir)?(e.stderr(`skipping ${i.droneLabel} (${JSON.stringify(i.worktreeDir)}): worktree path contains a control character \u2014 unsafe for --mode windows; use --mode tmux instead.
12
- `),!1):!0);t.platform==="darwin"?f(a,t,e):p(a,t,e)}export{b as runWindowsBackend};
11
+ `);for(let n=0;n<t.length;n++){const l=t[n];n>0&&e.launchDelayMs>0&&await e.sleep(e.launchDelayMs);const c=m(l.worktreeDir,e.borgPath,{keepOpenOnFail:!0});r.runSync(a,["-e","sh","-c",c]),u(r,l.cubeId,l.droneLabel,l.worktreeDir,e.launchedAtISO)}}const p=/[\x00-\x1f\x7f]/;async function k(t,e,r){const i=t.filter(o=>p.test(o.worktreeDir)?(r.stderr(`skipping ${o.droneLabel} (${JSON.stringify(o.worktreeDir)}): worktree path contains a control character \u2014 unsafe for --mode windows; use --mode tmux instead.
12
+ `),!1):!0);e.platform==="darwin"?await w(i,e,r):await h(i,e,r)}export{k as runWindowsBackend};
package/dist/claude.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as D}from"child_process";import{randomUUID as L}from"node:crypto";import{basename as M}from"node:path";import{createInterface as E}from"node:readline/promises";import t from"chalk";import{findProjectRoot as O,getActiveCube as H,getLaunchModel as F,inboxPathForDrone as N,setCodexWakeTarget as B,pruneDeadCodexWakeTargets as _}from"./cubes.js";import{applyOllamaLaunchEnv as G,checkModelReachable as W}from"./model-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{parseCleanupArgs as X,runCleanup as J}from"./cleanup-cmd.js";import{parseAssimilateArgs as Q}from"./parse-assimilate-args.js";import{runAssimilate as Z}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as ee}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 re}from"./launch-all-discovery.js";import{runBareLaunchMenu as oe,shouldShowLaunchMenu as se}from"./bare-launch-menu.js";import{setTerminalTitle as te}from"./terminal-title.js";import{initConsolePrefix as ae,consolePrefix as o}from"./console-prefix.js";import{initDebugFromArgv as ie}from"./debug.js";import{fetchLatestBorgmcpVersion as ne,compareVersionsForStaleness as ce}from"./stale-version-check.js";import{defaultCliChoiceDeps as le,detectCliAvailability as x,installedCliNames as A,parseCliFlag as de,resolveCliChoice as pe}from"./cli-platform.js";import{getRefreshToken as ue,getIdToken as me}from"./config.js";import{composeGetStarted as fe,shouldShowGetStarted as ge}from"./get-started.js";import{prepareCodexRemoteLaunch as he,withCodexCwdArg as we,defaultCodexRemoteDeps as xe,checkCodexBridgeHealthy as ve}from"./codex-remote.js";import{findLoadedCodexThread as be}from"./codex-app-server.js";import{buildAgentKickoffPrompt as Ce,recordCodexWakeTarget as ke,socketPathFromRemoteArgs as I}from"./codex-launch.js";import{codexBorgSessionConfigArgs as $e}from"./launch-gate.js";import{addCodexMcpServer as ye,addCodexSessionStartHook as Se,addCodexUserPromptSubmitHook as Te,addMcpServer as Ae,addProjectSessionStartHook as Ie,addUserPromptSubmitHook as Pe,isCodexMcpServerConfigured as Re,isMcpServerConfigured as De,removeSessionStartHook as Le}from"./config-utils.js";async function Me(){ie(process.argv),Y(),await ae();const c=(async()=>{if(!process.stderr.isTTY)return;const e=g(),r=await ne();if(!r)return;const i=ce(e,r);i.stale&&i.message&&process.stderr.write(`${o()}${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=Q(process.argv.slice(3));e.ok||(process.stderr.write(t.red(`${o()}\u25FC borg assimilate: ${e.error}
2
+ import{spawn as L}from"child_process";import{randomUUID as M}from"node:crypto";import{basename as E}from"node:path";import{createInterface as O}from"node:readline/promises";import t from"chalk";import{findProjectRoot as y,getActiveCube as H,getLaunchModel as F,inboxPathForDrone as N,setCodexWakeTarget as B,pruneDeadCodexWakeTargets as _}from"./cubes.js";import{applyOllamaLaunchEnv as G,checkModelReachable as W}from"./model-presets.js";import{handleVersionFlag as Y,getPackageVersion as g}from"./version.js";import{isHelpFlag as S,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{parseCleanupArgs as X,runCleanup as J}from"./cleanup-cmd.js";import{parseAssimilateArgs as Q}from"./parse-assimilate-args.js";import{runAssimilate as Z}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as ee}from"./assimilate-deps.js";import{parseLaunchAllArgs as T}from"./parse-launch-all-args.js";import{runLaunchAll as A}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as w}from"./launch-all-deps.js";import{discoverDroneCandidates as re}from"./launch-all-discovery.js";import{runBareLaunchMenu as oe,shouldShowLaunchMenu as se}from"./bare-launch-menu.js";import{setTerminalTitle as te}from"./terminal-title.js";import{initConsolePrefix as ae,consolePrefix as o}from"./console-prefix.js";import{initDebugFromArgv as ie}from"./debug.js";import{fetchLatestBorgmcpVersion as ne,compareVersionsForStaleness as ce}from"./stale-version-check.js";import{defaultCliChoiceDeps as le,detectCliAvailability as x,installedCliNames as I,parseCliFlag as de,resolveCliChoice as pe}from"./cli-platform.js";import{getRefreshToken as ue,getIdToken as me}from"./config.js";import{composeGetStarted as fe,shouldShowGetStarted as ge}from"./get-started.js";import{prepareCodexRemoteLaunch as he,withCodexCwdArg as we,defaultCodexRemoteDeps as xe,checkCodexBridgeHealthy as ve}from"./codex-remote.js";import{findLoadedCodexThread as be}from"./codex-app-server.js";import{buildAgentKickoffPrompt as Ce,recordCodexWakeTarget as ke,socketPathFromRemoteArgs as P}from"./codex-launch.js";import{codexBorgSessionConfigArgs as $e}from"./launch-gate.js";import{addCodexMcpServer as ye,addCodexSessionStartHook as Se,addCodexUserPromptSubmitHook as Te,addMcpServer as Ae,addProjectSessionStartHook as Ie,addUserPromptSubmitHook as Pe,isCodexMcpServerConfigured as Re,isMcpServerConfigured as De,removeSessionStartHook as Le}from"./config-utils.js";async function Me(){ie(process.argv),Y(),await ae();const c=(async()=>{if(!process.stderr.isTTY)return;const e=g(),r=await ne();if(!r)return;const i=ce(e,r);i.stale&&i.message&&process.stderr.write(`${o()}${i.message}
3
+ `)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(V(g())),process.exit(0)),process.argv[2]==="setup"){S(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(S)&&(process.stdout.write(j(g())),process.exit(0));const e=Q(process.argv.slice(3));e.ok||(process.stderr.write(t.red(`${o()}\u25FC borg assimilate: ${e.error}
4
4
  `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=ee(),i=await Z({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(t.red(`${o()}\u25FC borg sync: ${e.error}
5
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]==="cleanup"){const e=X(process.argv.slice(3));e.ok||(process.stderr.write(t.red(`${o()}\u25FC borg cleanup: ${e.error}
6
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await J({},e.options);process.exit(r)}if(process.argv[2]==="launch-all"){const e=S(process.argv.slice(3));e.ok||(process.stderr.write(t.red(`${o()}\u25FC borg launch-all: ${e.error}
7
- `)),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(ge(await ue()!==null,await me()!==null)){const e=A(x()).length>0;process.stdout.write(fe(e)),process.exit(0)}const n=de(process.argv.slice(2));n.error&&(process.stderr.write(t.red(`${o()}\u25FC ${n.error}
8
- `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const v=async e=>{const r=E({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let s=await pe(n.cli,le(v,()=>process.stdin.isTTY===!0));Ee();const a=await H();if(se({extraArgs:process.argv.slice(2),stdinIsTTY:process.stdin.isTTY===!0,stdoutIsTTY:process.stdout.isTTY===!0})){const e=A(x()).find(m=>m!==s)??null;let r=!1;a&&(r=(await re({targetCubeId:a.cubeId},w())).length>0);const i=await oe({defaultCli:s,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)}s=i.cli}const l=n.rest;te(a?{label:a.droneLabel,cubeName:a.name}:null,M(process.cwd()));const P=a&&s==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${N(a.cubeId,a.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 b=s==="codex"?`borg-wake-${L()}`:null;let f,C=[],d={...process.env,BORG_SESSION:"1"},p=null,u=null;if(s==="codex"&&!l.includes("--remote")){console.error(`${o()}${t.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await he(xe());e.warning?(console.error(`${o()}${t.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.",C=e.args,d={...process.env,...e.env,BORG_SESSION:"1"},p=I(e.args),u=e.server?.cleanup??null}else s==="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(a){const e=await F(a.cubeId,a.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(`${o()}${t.yellow(`warning: ${i.message}`)}`)}}const k=Ce({cli:s,codexWakeNonce:b,monitorClause:P,codexWakePathClause:f});let h=[...l,k];s==="codex"&&(h=[...$e(),...C,...we(h,process.cwd())]),console.error(`${o()}${t.blue(`\u25FC Launching ${s==="claude"?"Claude Code":"Codex"}\u2026`)}`);const $=D(s,h,{stdio:"inherit",shell:!1,env:d});s==="codex"&&a&&p&&(ke({deps:{setCodexWakeTarget:B,findLoadedCodexThread:be},cubeId:a.cubeId,droneId:a.droneId,socketPath:p,passthroughArgs:l,previewNeedle:b??k.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),_(e=>ve(e))),$.on("error",e=>{if(u)try{u()}catch{}e.code==="ENOENT"?(console.error(`${o()}${t.red(`
6
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await J({},e.options);process.exit(r)}if(process.argv[2]==="launch-all"){const e=T(process.argv.slice(3));e.ok||(process.stderr.write(t.red(`${o()}\u25FC borg launch-all: ${e.error}
7
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=w(),i=await A(e.args,r);process.exit(i)}if(ge(await ue()!==null,await me()!==null)){const e=I(x()).length>0;process.stdout.write(fe(e)),process.exit(0)}const n=de(process.argv.slice(2));n.error&&(process.stderr.write(t.red(`${o()}\u25FC ${n.error}
8
+ `)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const v=async e=>{const r=O({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let s=await pe(n.cli,le(v,()=>process.stdin.isTTY===!0));Ee();const a=await H();if(se({extraArgs:process.argv.slice(2),stdinIsTTY:process.stdin.isTTY===!0,stdoutIsTTY:process.stdout.isTTY===!0})){const e=I(x()).find(m=>m!==s)??null;let r=!1;a&&(r=(await re({targetCubeId:a.cubeId},w())).length>0);const i=await oe({defaultCli:s,otherInstalledCli:e,hasLaunchAllTargets:r},v);if(i.kind==="launch-all"){const m=T([]),D=m.ok?await A(m.args,w()):1;process.exit(D)}s=i.cli}const l=n.rest;te(a?{label:a.droneLabel,cubeName:a.name}:null,E(process.cwd()));const R=a&&s==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${N(a.cubeId,a.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 b=s==="codex"?`borg-wake-${M()}`:null;let f,C=[],d={...process.env,BORG_SESSION:"1"},p=null,u=null;if(s==="codex"&&!l.includes("--remote")){console.error(`${o()}${t.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await he(xe());e.warning?(console.error(`${o()}${t.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.",C=e.args,d={...process.env,...e.env,BORG_SESSION:"1"},p=P(e.args),u=e.server?.cleanup??null}else s==="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=P(l),p&&(d={...process.env,BORG_CODEX_REMOTE_WAKE:"1",BORG_SESSION:"1"}));if(a){const e=await F(a.cubeId,y(),a.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(`${o()}${t.yellow(`warning: ${i.message}`)}`)}}const k=Ce({cli:s,codexWakeNonce:b,monitorClause:R,codexWakePathClause:f});let h=[...l,k];s==="codex"&&(h=[...$e(),...C,...we(h,process.cwd())]),console.error(`${o()}${t.blue(`\u25FC Launching ${s==="claude"?"Claude Code":"Codex"}\u2026`)}`);const $=L(s,h,{stdio:"inherit",shell:!1,env:d});s==="codex"&&a&&p&&(ke({deps:{setCodexWakeTarget:B,findLoadedCodexThread:be},cubeId:a.cubeId,droneId:a.droneId,socketPath:p,passthroughArgs:l,previewNeedle:b??k.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),_(e=>ve(e))),$.on("error",e=>{if(u)try{u()}catch{}e.code==="ENOENT"?(console.error(`${o()}${t.red(`
9
9
  \u25FC Failed to launch ${s}`)}`),console.error(`${o()}${t.gray(`Make sure ${s} is installed.
10
10
  `)}`)):console.error(`${o()}${t.red(`
11
11
  \u25FC Failed to launch ${s}: ${e.message}
12
- `)}`),process.exit(1)}),$.on("exit",e=>{if(u)try{u()}catch{}process.exit(e??0)})}function Ee(){const c=x();if(c.claude)try{De()||Ae(),Ie(O(process.cwd())),Le(),Pe()}catch(n){console.error(`${o()}${t.yellow(`warning: Claude Code integration check failed: ${n?.message??n}`)}`)}if(c.codex)try{Re()||ye(),Se(),Te()}catch(n){console.error(`${o()}${t.yellow(`warning: Codex integration check failed: ${n?.message??n}`)}`)}}Me().catch(c=>{console.error(`${o()}${t.red(`
12
+ `)}`),process.exit(1)}),$.on("exit",e=>{if(u)try{u()}catch{}process.exit(e??0)})}function Ee(){const c=x();if(c.claude)try{De()||Ae(),Ie(y(process.cwd())),Le(),Pe()}catch(n){console.error(`${o()}${t.yellow(`warning: Claude Code integration check failed: ${n?.message??n}`)}`)}if(c.codex)try{Re()||ye(),Se(),Te()}catch(n){console.error(`${o()}${t.yellow(`warning: Codex integration check failed: ${n?.message??n}`)}`)}}Me().catch(c=>{console.error(`${o()}${t.red(`
13
13
  \u25FC Error: ${c.message}
14
14
  `)}`),process.exit(1)});
package/dist/cubes.d.ts CHANGED
@@ -98,7 +98,15 @@ export interface LaunchModelRecord {
98
98
  /** Resolved Ollama host for an ollama model; null for claude. */
99
99
  ollamaBaseUrl: string | null;
100
100
  }
101
- export declare function setLaunchModel(cubeId: string, droneId: string, record: LaunchModelRecord, file?: string): Promise<void>;
102
- export declare function getLaunchModel(cubeId: string, droneId: string, file?: string): Promise<LaunchModelRecord | null>;
103
- export declare function clearLaunchModel(cubeId: string, droneId: string, file?: string): Promise<void>;
101
+ export declare function setLaunchModel(cubeId: string, worktreePath: string, record: LaunchModelRecord, file?: string): Promise<void>;
102
+ /**
103
+ * Read the launch model for a worktree. On a worktree-key miss, if `priorDroneId`
104
+ * is supplied (the seat that previously occupied this worktree, derived from
105
+ * cubes.json), a legacy droneId-keyed entry is migrated ON READ to the worktree
106
+ * key and the old key dropped (a move, not a copy — no silent loss). Without a
107
+ * prior droneId the legacy entry is unreachable but LEFT INTACT for a one-time
108
+ * operator re-pin (HARD constraint: never silently destroy a stored entry).
109
+ */
110
+ export declare function getLaunchModel(cubeId: string, worktreePath: string, priorDroneId?: string | null, file?: string): Promise<LaunchModelRecord | null>;
111
+ export declare function clearLaunchModel(cubeId: string, worktreePath: string, file?: string): Promise<void>;
104
112
  //# sourceMappingURL=cubes.d.ts.map
package/dist/cubes.js CHANGED
@@ -1,5 +1,5 @@
1
- import{existsSync as k}from"node:fs";import{mkdir as u,readFile as f,writeFile as d,unlink as N}from"node:fs/promises";import{homedir as A}from"node:os";import{dirname as s,join as i,resolve as O}from"node:path";import{pruneDeadWakeTargets as S}from"./codex-wake-resolve.js";import{MODEL_DESCRIPTOR_REGEX as T}from"./model-presets.js";const l=i(A(),".config","borgmcp"),p=i(l,"cubes.json"),w=i(l,"launch.json"),g=i(l,"codex-wake-targets.json"),h=i(l,"launch-models.json"),P=i(l,"inboxes");function c(e=process.cwd()){let t=O(e);for(;;){if(k(i(t,".git")))return t;const r=s(t);if(r===t)return O(e);t=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 X(e,t){if(!a.test(e))throw new Error(`Invalid cubeId: ${e}`);if(!a.test(t))throw new Error(`Invalid droneId: ${t}`);return i(P,e,`${t}.log`)}function _(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 _(t)?t:null}async function C(e){await u(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 m(){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 D(e){await u(s(w),{recursive:!0}),await d(w,JSON.stringify(e,null,2)+`
3
- `,{mode:384})}function F(e,t){if(!a.test(e))throw new Error(`Invalid cubeId: ${e}`);if(!a.test(t))throw new Error(`Invalid droneId: ${t}`);return`${e}:${t}`}function U(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 U(t)?t:null}catch{return null}}async function I(e){await u(s(g),{recursive:!0}),await d(g,JSON.stringify(e,null,2)+`
4
- `,{mode:384})}async function G(){const e=await y();if(!e)return null;const t=c(),r=e.projects[t];return!r||typeof r.cubeId!="string"||!r.cubeId||typeof r.droneId!="string"||!r.droneId?null:r}async function H(e){const t=await y()??{projects:{}};t.projects[c()]=e,await C(t)}function q(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 z(){const e=await y();if(!e)return;const t=c();if(t in e.projects){if(delete e.projects[t],Object.keys(e.projects).length===0){try{await N(p)}catch(r){if(r?.code!=="ENOENT")throw r}return}await C(e)}}async function Q(){const e=await m();if(!e)return null;const t=e.projects[c()];return t?.cli==="claude"||t?.cli==="codex"?t.cli:null}async function V(e){const t=await m();if(!t)return null;const r=t.projects[c(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 m()??{projects:{}};t.projects[c()]={cli:e},await D(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 x(e,t){if(!a.test(e))throw new Error(`Invalid cubeId: ${e}`);if(!a.test(t))throw new Error(`Invalid droneId: ${t}`);return`${e}:${t}`}function v(e){return e!==null&&typeof e=="object"&&typeof e.models=="object"&&e.models!==null&&!Array.isArray(e.models)}async function E(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 v(r)?r:null}catch{return null}}async function L(e,t){await u(s(e),{recursive:!0}),await d(e,JSON.stringify(t,null,2)+`
5
- `,{mode:384})}async function ne(e,t,r,n=h){const o=await E(n)??{models:{}};o.models[x(e,t)]={model:r.model,ollamaBaseUrl:r.ollamaBaseUrl},await L(n,o)}async function oe(e,t,r=h){const n=await E(r);if(!n)return null;const o=n.models[x(e,t)];return!o||typeof o.model!="string"||!T.test(o.model)?null:{model:o.model,ollamaBaseUrl:typeof o.ollamaBaseUrl=="string"?o.ollamaBaseUrl:null}}async function ie(e,t,r=h){const n=await E(r);if(!n)return;const o=x(e,t);if(o in n.models){if(delete n.models[o],Object.keys(n.models).length===0){try{await N(r)}catch(b){if(b?.code!=="ENOENT")throw b}return}await L(r,n)}}export{q as activeCubeWithFreshRegenIdentity,z as clearActiveCube,ie as clearLaunchModel,c as findProjectRoot,G as getActiveCube,te as getCodexWakeTarget,oe as getLaunchModel,Q as getProjectCliPreference,V as getProjectCliPreferenceForPath,X as inboxPathForDrone,re as pruneDeadCodexWakeTargets,Y as readAllProjectIdentities,H as setActiveCube,ee as setCodexWakeTarget,ne as setLaunchModel,Z as setProjectCliPreference};
1
+ import{existsSync as $}from"node:fs";import{mkdir as f,readFile as d,writeFile as p,unlink as L}from"node:fs/promises";import{homedir as v}from"node:os";import{dirname as l,join as i,resolve as I}from"node:path";import{pruneDeadWakeTargets as _}from"./codex-wake-resolve.js";import{MODEL_DESCRIPTOR_REGEX as U}from"./model-presets.js";const u=i(v(),".config","borgmcp"),y=i(u,"cubes.json"),m=i(u,"launch.json"),h=i(u,"codex-wake-targets.json"),j=i(u,"launch-models.json"),R=i(u,"inboxes");function c(t=process.cwd()){let e=I(t);for(;;){if($(i(e,".git")))return e;const r=l(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 Q(t,e){if(!a.test(t))throw new Error(`Invalid cubeId: ${t}`);if(!a.test(e))throw new Error(`Invalid droneId: ${e}`);return i(R,t,`${e}.log`)}function W(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 d(y,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}let e;try{e=JSON.parse(t)}catch{return null}return W(e)?e:null}async function S(t){await f(l(y),{recursive:!0}),await p(y,JSON.stringify(t,null,2)+`
2
+ `,{mode:384})}function D(t){return t!==null&&typeof t=="object"&&typeof t.projects=="object"&&t.projects!==null&&!Array.isArray(t.projects)}async function x(){let t;try{t=await d(m,"utf8")}catch(e){if(e?.code==="ENOENT")return null;throw e}try{const e=JSON.parse(t);return D(e)?e:null}catch{return null}}async function P(t){await f(l(m),{recursive:!0}),await p(m,JSON.stringify(t,null,2)+`
3
+ `,{mode:384})}function A(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 B(t){return t!==null&&typeof t=="object"&&typeof t.targets=="object"&&t.targets!==null&&!Array.isArray(t.targets)}async function E(){let t;try{t=await d(h,"utf8")}catch(e){if(e?.code==="ENOENT")return null;throw e}try{const e=JSON.parse(t);return B(e)?e:null}catch{return null}}async function T(t){await f(l(h),{recursive:!0}),await p(h,JSON.stringify(t,null,2)+`
4
+ `,{mode:384})}async function V(){const t=await w();if(!t)return null;const e=c(),r=t.projects[e];return!r||typeof r.cubeId!="string"||!r.cubeId||typeof r.droneId!="string"||!r.droneId?null:r}async function Y(t){const e=await w()??{projects:{}};e.projects[c()]=t,await S(e)}function Z(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 tt(){const t=await w();if(!t)return;const e=c();if(e in t.projects){if(delete t.projects[e],Object.keys(t.projects).length===0){try{await L(y)}catch(r){if(r?.code!=="ENOENT")throw r}return}await S(t)}}async function et(){const t=await x();if(!t)return null;const e=t.projects[c()];return e?.cli==="claude"||e?.cli==="codex"?e.cli:null}async function rt(t){const e=await x();if(!e)return null;const r=e.projects[c(t)];return r?.cli==="claude"||r?.cli==="codex"?r.cli:null}async function nt(){const t=await w();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 ot(t){const e=await x()??{projects:{}};e.projects[c()]={cli:t},await P(e)}async function it(t,e,r){const n=await E()??{targets:{}};n.targets[A(t,e)]={...r,updatedAt:new Date().toISOString()},await T(n)}async function at(t,e){const r=await E();if(!r)return null;const n=r.targets[A(t,e)];return!n||typeof n.threadId!="string"||typeof n.socketPath!="string"?null:n}async function ct(t){const e=await E();if(!e)return;const{targets:r,changed:n}=_(e.targets,t);n&&await T({...e,targets:r})}function b(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 J(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||typeof t.model!="string"||!U.test(t.model)?null:{model:t.model,ollamaBaseUrl:typeof t.ollamaBaseUrl=="string"?t.ollamaBaseUrl:null}}function M(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 d(t,"utf8")}catch(r){if(r?.code==="ENOENT")return null;throw r}try{const r=JSON.parse(e);return M(r)?r:null}catch{return null}}async function O(t,e){await f(l(t),{recursive:!0}),await p(t,JSON.stringify(e,null,2)+`
5
+ `,{mode:384})}async function st(t,e,r,n=j){const o=await N(n)??{models:{}};o.models[b(t,e)]={model:r.model,ollamaBaseUrl:r.ollamaBaseUrl},await O(n,o)}async function lt(t,e,r=null,n=j){const o=await N(n);if(!o)return null;const s=b(t,e),C=k(o.models[s]);if(C)return C;if(!r||!a.test(r))return null;const F=J(t,r),g=k(o.models[F]);return g?(o.models[s]=g,delete o.models[F],await O(n,o),g):null}async function ut(t,e,r=j){const n=await N(r);if(!n)return;const o=b(t,e);if(o in n.models){if(delete n.models[o],Object.keys(n.models).length===0){try{await L(r)}catch(s){if(s?.code!=="ENOENT")throw s}return}await O(r,n)}}export{Z as activeCubeWithFreshRegenIdentity,tt as clearActiveCube,ut as clearLaunchModel,c as findProjectRoot,V as getActiveCube,at as getCodexWakeTarget,lt as getLaunchModel,et as getProjectCliPreference,rt as getProjectCliPreferenceForPath,Q as inboxPathForDrone,ct as pruneDeadCodexWakeTargets,nt as readAllProjectIdentities,Y as setActiveCube,it as setCodexWakeTarget,st as setLaunchModel,ot as setProjectCliPreference};
@@ -1,5 +1,13 @@
1
1
  import type { LaunchAllArgs } from './parse-launch-all-args.js';
2
2
  import type { LaunchAllDeps } from './launch-all-deps.js';
3
+ /**
4
+ * Default ms to wait BETWEEN each drone launch, so a fleet's agents don't all
5
+ * bootstrap at once and trip the per-user/IP rate limiter. Override per-run with
6
+ * `--launch-delay <ms>` or persistently with `$BORG_LAUNCH_DELAY_MS`; 0 disables.
7
+ */
8
+ export declare const DEFAULT_LAUNCH_DELAY_MS = 2000;
9
+ /** Resolve the inter-launch stagger: flag > env > default (each must be a non-negative integer to win). */
10
+ export declare function resolveLaunchDelayMs(flag: number | undefined, env: string | undefined): number;
3
11
  export interface RunLaunchAllOptions {
4
12
  /** Injectable clock/sleep for deterministic reconciliation tests. */
5
13
  now?: () => number;
@@ -1,33 +1,33 @@
1
- import{discoverDroneCandidates as y}from"./launch-all-discovery.js";import{resolveBorgPath as x}from"./launch-all-command.js";import{sweepStaleLocks as L,isLockLive as I}from"./launch-all-locks.js";import{runTmuxBackend as S}from"./backends/launch-all-tmux.js";import{runWindowsBackend as A}from"./backends/launch-all-windows.js";import{runPastelistBackend as T}from"./backends/launch-all-pastelist.js";const v=`borg launch-all: tmux not found.
1
+ import{discoverDroneCandidates as x}from"./launch-all-discovery.js";import{resolveBorgPath as A}from"./launch-all-command.js";import{sweepStaleLocks as I,isLockLive as S}from"./launch-all-locks.js";import{runTmuxBackend as D}from"./backends/launch-all-tmux.js";import{runWindowsBackend as T}from"./backends/launch-all-windows.js";import{runPastelistBackend as N}from"./backends/launch-all-pastelist.js";const L=`borg launch-all: tmux not found.
2
2
  macOS: brew install tmux
3
3
  Debian: sudo apt install tmux
4
4
  Fedora: sudo dnf install tmux
5
- `;function D(t){try{return t.runSync("tmux",["-V"]),!0}catch{return!1}}function N(t){try{return/microsoft|wsl/i.test(t.runSync("uname",["-r"]))}catch{return!1}}function p(t){return/^drone-\d+$/i.test(t)||t.toLowerCase()==="drone"}async function C(t,e){if(t.cubeName!==void 0){const s=(await e.readAllProjectIdentities()).filter(l=>l.cube.name===t.cubeName);return s.length===0?{error:`no cube named '${t.cubeName}' found in cubes.json \u2014 has any drone assimilated into it?`}:{cubeId:s[0].cube.cubeId,name:t.cubeName}}const o=await e.getActiveCube();return o?{cubeId:o.cubeId,name:o.name}:{error:"no active cube in this directory; run `borg assimilate` first, or pass a cube name explicitly"}}function O(t,e){const o=t.flags.mode;if(e.platform()==="win32"&&!N(e))return e.stderr(`native Windows is not supported for interactive launch; using pastelist mode instead (WSL + tmux is the recommended Windows path)
6
- `),{backend:"pastelist"};if(o==="windows")return{backend:"windows"};if(o==="pastelist")return{backend:"pastelist"};const s=D(e);return o==="tmux"?s?{backend:"tmux"}:{hardFail:v}:s?{backend:"tmux"}:(e.stderr(v+`Falling back to pastelist mode (paste the commands below).
7
- `),{backend:"pastelist"})}function W(t,e){return t.flags.noAttach||!e.isTTY()?"none":e.getEnv("TMUX")?"switch":"attach"}function F(t,e){e.stdout(` tmux attach -t ${t} # re-attach later
8
- `),e.stdout(` tmux list-windows -t ${t} # list all drone windows
9
- `),e.stdout(` tmux kill-session -t ${t} # stop all drones
10
- `)}function P(t){return`borg-${t.replace(/[^a-zA-Z0-9_-]/g,"-")}`}async function U(t,e,o,f,s,l=Date.now,m=a=>new Promise(i=>setTimeout(i,a))){const a=new Map;for(const u of s)a.set(u,"unconfirmed");const i=l()+1e4;for(let u=0;u<20&&!(l()>=i);u++){let d;try{d=await t.getRoster(e,o,f)}catch{break}for(const r of d.drones)a.get(r.id)==="unconfirmed"&&r.seen_since===!0&&a.set(r.id,"verified");if([...a.values()].every(r=>r==="verified"))break;await m(500)}return a}async function j(t,e,o={}){const f=o.now??Date.now,s=o.nowISO??(()=>new Date().toISOString()),l=o.borgPath??x(),m=await C(t,e);if("error"in m)return e.stderr(`borg launch-all: ${m.error}
11
- `),1;const{cubeId:a,name:i}=m;L(e,a,f());const u=await y({targetCubeId:a,only:t.flags.only},e);if(u.length===0)return t.flags.only!==void 0?(e.stdout(`No worktrees matched --only '${t.flags.only}' for cube '${i}'
12
- `),p(t.flags.only)||e.stderr(`note: --only '${t.flags.only}' is matched best-effort by drone label; role-name matching needs a drone session and is not available here.
13
- `)):e.stdout(`No worktrees found for cube '${i}' \u2014 have you run \`borg assimilate --worktree\` to create any drone seats?
14
- `),0;const d=[];for(const n of u){const c=I(e,a,n.worktreeDir,f());if(c.live&&!t.flags.force){e.stderr(`skipping ${n.droneLabel} (${n.worktreeDir}): appears live. Use --force to re-launch.
15
- `);continue}c.live&&t.flags.force&&e.stderr(`--force: re-launching ${n.droneLabel} (${n.worktreeDir}); the running session's token will be displaced.
16
- `),d.push(n)}if(d.length===0)return e.stdout(`All ${u.length} drone(s) for cube '${i}' appear live; nothing to launch (use --force to re-launch).
17
- `),0;const r=[];for(const n of d){let c;try{c=await e.probeSeat(n.sessionToken,n.apiUrl)}catch{c="indeterminate"}if(c==="evicted"){e.stderr(`skipping ${n.droneLabel} (${n.worktreeDir}): seat no longer in cube (evicted) \u2014 run \`borg cleanup --prune\` to remove the worktree, or \`borg assimilate\` to re-seat fresh.
18
- `);continue}if(c==="frozen"){e.stderr(`skipping ${n.droneLabel} (${n.worktreeDir}): seat frozen (subscription downgrade) \u2014 paused, not relaunching; it resumes automatically when billing is restored.
19
- `);continue}c==="indeterminate"&&e.stderr(`note: could not confirm ${n.droneLabel}'s seat is live (network/transient) \u2014 launching anyway.
20
- `),r.push(n)}if(r.length===0)return e.stdout(`All ${d.length} discovered drone(s) for cube '${i}' have evicted/frozen seats; nothing to launch.
21
- `),0;if(t.flags.dryRun){e.stdout(`borg launch-all (dry-run): would launch ${r.length} drone(s) for cube '${i}':
22
- `);for(const n of r)e.stdout(` ${n.droneLabel} ${n.worktreeDir}
23
- `);return 0}if(r.length>6&&!t.flags.yes&&(await e.prompt(`About to launch ${r.length} drones for cube '${i}'. Continue? [y/N]: `)).trim().toLowerCase()!=="y")return e.stdout(`Aborted.
24
- `),0;const h=O(t,e);if("hardFail"in h)return e.stderr(h.hardFail),1;const b=P(i),w=s();try{if(h.backend==="tmux"){const n=W(t,e);await S(r,{sessionName:b,borgPath:l,attachMode:n,launchedAtISO:w},e),n==="none"&&(e.isTTY()||e.stderr(`Launching in detached mode \u2014 stdout is non-TTY. Attach manually with: tmux attach -t ${b}
25
- `),F(b,e))}else if(h.backend==="windows")await A(r,{borgPath:l,platform:e.platform(),launchedAtISO:w},e);else return T(r,l,e),0}catch(n){return e.stderr(`borg launch-all: ${n instanceof Error?n.message:String(n)}
26
- `),1}const k=r[0].sessionToken,$=r[0].apiUrl;let g=null;k&&$?g=await U(e,k,$,w,r.map(n=>n.droneId),o.now,o.sleep):e.stderr(`roster reconciliation skipped \u2014 no session token available
5
+ `;function p(n){try{return n.runSync("tmux",["-V"]),!0}catch{return!1}}function _(n){try{return/microsoft|wsl/i.test(n.runSync("uname",["-r"]))}catch{return!1}}function C(n){return/^drone-\d+$/i.test(n)||n.toLowerCase()==="drone"}async function M(n,e){if(n.cubeName!==void 0){const u=(await e.readAllProjectIdentities()).filter(d=>d.cube.name===n.cubeName);return u.length===0?{error:`no cube named '${n.cubeName}' found in cubes.json \u2014 has any drone assimilated into it?`}:{cubeId:u[0].cube.cubeId,name:n.cubeName}}const r=await e.getActiveCube();return r?{cubeId:r.cubeId,name:r.name}:{error:"no active cube in this directory; run `borg assimilate` first, or pass a cube name explicitly"}}function U(n,e){const r=n.flags.mode;if(e.platform()==="win32"&&!_(e))return e.stderr(`native Windows is not supported for interactive launch; using pastelist mode instead (WSL + tmux is the recommended Windows path)
6
+ `),{backend:"pastelist"};if(r==="windows")return{backend:"windows"};if(r==="pastelist")return{backend:"pastelist"};const u=p(e);return r==="tmux"?u?{backend:"tmux"}:{hardFail:L}:u?{backend:"tmux"}:(e.stderr(L+`Falling back to pastelist mode (paste the commands below).
7
+ `),{backend:"pastelist"})}function E(n,e){return n.flags.noAttach||!e.isTTY()?"none":e.getEnv("TMUX")?"switch":"attach"}function O(n,e){e.stdout(` tmux attach -t ${n} # re-attach later
8
+ `),e.stdout(` tmux list-windows -t ${n} # list all drone windows
9
+ `),e.stdout(` tmux kill-session -t ${n} # stop all drones
10
+ `)}function F(n){return`borg-${n.replace(/[^a-zA-Z0-9_-]/g,"-")}`}async function P(n,e,r,s,u,d=Date.now,h=c=>new Promise(m=>setTimeout(m,c))){const c=new Map;for(const f of u)c.set(f,"unconfirmed");const m=d()+1e4;for(let f=0;f<20&&!(d()>=m);f++){let a;try{a=await n.getRoster(e,r,s)}catch{break}for(const l of a.drones)c.get(l.id)==="unconfirmed"&&l.seen_since===!0&&c.set(l.id,"verified");if([...c.values()].every(l=>l==="verified"))break;await h(500)}return c}const W=2e3;function B(n,e){if(n!==void 0&&Number.isInteger(n)&&n>=0)return n;const r=e===void 0?"":e.trim(),s=r===""?NaN:Number(r);return Number.isInteger(s)&&s>=0?s:W}async function X(n,e,r={}){const s=r.now??Date.now,u=r.nowISO??(()=>new Date().toISOString()),d=r.borgPath??A(),h=r.sleep??(t=>new Promise(i=>setTimeout(i,t))),c=B(n.flags.launchDelayMs,e.getEnv("BORG_LAUNCH_DELAY_MS")),m=await M(n,e);if("error"in m)return e.stderr(`borg launch-all: ${m.error}
11
+ `),1;const{cubeId:f,name:a}=m;I(e,f,s());const l=await x({targetCubeId:f,only:n.flags.only},e);if(l.length===0)return n.flags.only!==void 0?(e.stdout(`No worktrees matched --only '${n.flags.only}' for cube '${a}'
12
+ `),C(n.flags.only)||e.stderr(`note: --only '${n.flags.only}' is matched best-effort by drone label; role-name matching needs a drone session and is not available here.
13
+ `)):e.stdout(`No worktrees found for cube '${a}' \u2014 have you run \`borg assimilate --worktree\` to create any drone seats?
14
+ `),0;const b=[];for(const t of l){const i=S(e,f,t.worktreeDir,s());if(i.live&&!n.flags.force){e.stderr(`skipping ${t.droneLabel} (${t.worktreeDir}): appears live. Use --force to re-launch.
15
+ `);continue}i.live&&n.flags.force&&e.stderr(`--force: re-launching ${t.droneLabel} (${t.worktreeDir}); the running session's token will be displaced.
16
+ `),b.push(t)}if(b.length===0)return e.stdout(`All ${l.length} drone(s) for cube '${a}' appear live; nothing to launch (use --force to re-launch).
17
+ `),0;const o=[];for(const t of b){let i;try{i=await e.probeSeat(t.sessionToken,t.apiUrl)}catch{i="indeterminate"}if(i==="evicted"){e.stderr(`skipping ${t.droneLabel} (${t.worktreeDir}): seat no longer in cube (evicted) \u2014 run \`borg cleanup --prune\` to remove the worktree, or \`borg assimilate\` to re-seat fresh.
18
+ `);continue}if(i==="frozen"){e.stderr(`skipping ${t.droneLabel} (${t.worktreeDir}): seat frozen (subscription downgrade) \u2014 paused, not relaunching; it resumes automatically when billing is restored.
19
+ `);continue}i==="indeterminate"&&e.stderr(`note: could not confirm ${t.droneLabel}'s seat is live (network/transient) \u2014 launching anyway.
20
+ `),o.push(t)}if(o.length===0)return e.stdout(`All ${b.length} discovered drone(s) for cube '${a}' have evicted/frozen seats; nothing to launch.
21
+ `),0;if(n.flags.dryRun){e.stdout(`borg launch-all (dry-run): would launch ${o.length} drone(s) for cube '${a}':
22
+ `);for(const t of o)e.stdout(` ${t.droneLabel} ${t.worktreeDir}
23
+ `);return 0}if(o.length>6&&!n.flags.yes&&(await e.prompt(`About to launch ${o.length} drones for cube '${a}'. Continue? [y/N]: `)).trim().toLowerCase()!=="y")return e.stdout(`Aborted.
24
+ `),0;const w=U(n,e);if("hardFail"in w)return e.stderr(w.hardFail),1;const g=F(a),k=u();try{if(w.backend==="tmux"){const t=E(n,e);await D(o,{sessionName:g,borgPath:d,attachMode:t,launchedAtISO:k,launchDelayMs:c,sleep:h},e),t==="none"&&(e.isTTY()||e.stderr(`Launching in detached mode \u2014 stdout is non-TTY. Attach manually with: tmux attach -t ${g}
25
+ `),O(g,e))}else if(w.backend==="windows")await T(o,{borgPath:d,platform:e.platform(),launchedAtISO:k,launchDelayMs:c,sleep:h},e);else return N(o,d,e),0}catch(t){return e.stderr(`borg launch-all: ${t instanceof Error?t.message:String(t)}
26
+ `),1}const v=o[0].sessionToken,y=o[0].apiUrl;let $=null;v&&y?$=await P(e,v,y,k,o.map(t=>t.droneId),r.now,r.sleep):e.stderr(`roster reconciliation skipped \u2014 no session token available
27
27
  `),e.stdout(`
28
- borg launch-all: launched ${r.length} drones for cube '${i}'
28
+ borg launch-all: launched ${o.length} drones for cube '${a}'
29
29
 
30
- `);for(const n of r){const c=g?g.get(n.droneId)==="verified"?"VERIFIED":"unconfirmed (may still be joining)":"launched";e.stdout(` ${n.droneLabel} ${n.worktreeDir} ${c}
30
+ `);for(const t of o){const i=$?$.get(t.droneId)==="verified"?"VERIFIED":"unconfirmed (may still be joining)":"launched";e.stdout(` ${t.droneLabel} ${t.worktreeDir} ${i}
31
31
  `)}return e.stdout(`
32
- Attach: tmux attach -t ${b}
33
- `),0}export{j as runLaunchAll};
32
+ Attach: tmux attach -t ${g}
33
+ `),0}export{W as DEFAULT_LAUNCH_DELAY_MS,B as resolveLaunchDelayMs,X as runLaunchAll};
@@ -6,6 +6,13 @@ export interface LaunchAllFlags {
6
6
  noAttach?: boolean;
7
7
  yes?: boolean;
8
8
  force?: boolean;
9
+ /**
10
+ * Milliseconds to wait BETWEEN each drone launch, to avoid the per-user/IP
11
+ * rate limiter when a fleet's worth of agents all bootstrap at once
12
+ * (assimilate + regen + roster). 0 disables. Default + env override: see
13
+ * launch-all-cmd (DEFAULT_LAUNCH_DELAY_MS / BORG_LAUNCH_DELAY_MS).
14
+ */
15
+ launchDelayMs?: number;
9
16
  }
10
17
  export interface LaunchAllArgs {
11
18
  cubeName?: string;
@@ -1 +1 @@
1
- const s="--mode <tmux|windows|pastelist>, --only <name>, --dry-run, --cli <claude|codex>, --no-attach, --yes/-y, --force";function c(o){const r={};let n;for(let t=0;t<o.length;t++){const a=o[t];switch(a){case"--mode":{const e=o[++t];if(e!=="tmux"&&e!=="windows"&&e!=="pastelist")return{ok:!1,error:`--mode must be one of tmux|windows|pastelist (got: ${e??"<missing>"})`};r.mode=e;break}case"--only":{const e=o[++t];if(e===void 0||e.startsWith("--"))return{ok:!1,error:"--only requires a value (role name or drone label)"};r.only=e;break}case"--cli":{const e=o[++t];if(e!=="claude"&&e!=="codex")return{ok:!1,error:`--cli must be one of claude|codex (got: ${e??"<missing>"})`};r.cli=e;break}case"--dry-run":r.dryRun=!0;break;case"--no-attach":r.noAttach=!0;break;case"--yes":case"-y":r.yes=!0;break;case"--force":r.force=!0;break;default:if(a.startsWith("-"))return{ok:!1,error:`unknown flag: ${a}. Supported: ${s}`};if(n!==void 0)return{ok:!1,error:`unexpected extra argument: ${a}`};n=a;break}}return{ok:!0,args:{cubeName:n,flags:r}}}export{c as parseLaunchAllArgs};
1
+ const c="--mode <tmux|windows|pastelist>, --only <name>, --dry-run, --cli <claude|codex>, --no-attach, --yes/-y, --force, --launch-delay <ms>";function u(t){const r={};let o;for(let n=0;n<t.length;n++){const a=t[n];switch(a){case"--mode":{const e=t[++n];if(e!=="tmux"&&e!=="windows"&&e!=="pastelist")return{ok:!1,error:`--mode must be one of tmux|windows|pastelist (got: ${e??"<missing>"})`};r.mode=e;break}case"--only":{const e=t[++n];if(e===void 0||e.startsWith("--"))return{ok:!1,error:"--only requires a value (role name or drone label)"};r.only=e;break}case"--cli":{const e=t[++n];if(e!=="claude"&&e!=="codex")return{ok:!1,error:`--cli must be one of claude|codex (got: ${e??"<missing>"})`};r.cli=e;break}case"--launch-delay":{const e=t[++n],s=e===void 0?NaN:Number(e);if(!Number.isInteger(s)||s<0)return{ok:!1,error:`--launch-delay requires a non-negative integer (milliseconds); got: ${e??"<missing>"}`};r.launchDelayMs=s;break}case"--dry-run":r.dryRun=!0;break;case"--no-attach":r.noAttach=!0;break;case"--yes":case"-y":r.yes=!0;break;case"--force":r.force=!0;break;default:if(a.startsWith("-"))return{ok:!1,error:`unknown flag: ${a}. Supported: ${c}`};if(o!==void 0)return{ok:!1,error:`unexpected extra argument: ${a}`};o=a;break}}return{ok:!0,args:{cubeName:o,flags:r}}}export{u as parseLaunchAllArgs};
@@ -86,6 +86,7 @@ export declare function assimilate(cubeNameOrSelector: string | {
86
86
  role_id?: string;
87
87
  role_name?: string;
88
88
  prior_drone_id?: string;
89
+ model?: string | null;
89
90
  }, apiUrl?: string, hostname?: string | null, agentKind?: 'claude' | 'codex' | null): Promise<{
90
91
  cube: {
91
92
  id: string;
@@ -1 +1 @@
1
- import{getIdToken as f,getRefreshToken as w,clearTokens as m}from"./config.js";import{refreshIdToken as j,RefreshTokenInvalidError as g,RefreshTransientError as T}from"./auth.js";import{consolePrefix as R}from"./console-prefix.js";import{debugLog as b}from"./debug.js";import{assertUuidShape as $}from"./evict-drone.js";import{DroneEvictedError as k,DroneFrozenError as C,DRONE_EVICTED_CODE as O,DRONE_FROZEN_CODE as P,errorCodeFromBody as I}from"./drone-lifecycle.js";const A=process.env.BORG_API_URL||"https://api.borgmcp.ai",D=3,L=6e4;let l=null;function x(e){return l||(l=j(e).finally(()=>{l=null}),l)}function J(e){if(e==null)return null;const n=e.trim();return/^\d+$/.test(n)?parseInt(n,10)*1e3:null}function v(e,n,t=L,o=()=>Math.random()*500){const s=e??1e3*(n+1);return Math.min(s,t)+o()}function G(e){const n=(t,o)=>`${t}${/[.:!?]$/.test(t)?"":":"} ${o}`;try{const t=JSON.parse(e);if(typeof t?.error=="string")return typeof t.details=="string"?n(t.error,t.details):t.error;if(t?.error&&typeof t.error=="object"){const o=t.error.message,s=t.error.details??t.details;if(typeof o=="string"&&typeof s=="string")return n(o,s);if(typeof o=="string")return o}if(typeof t?.message=="string"&&typeof t?.details=="string")return n(t.message,t.details);if(typeof t?.message=="string")return t.message}catch{}return e}async function M(e,n,t){const o=t.maxRetries??D;let s=e,a=0;for(;s.status===429&&a<o;){const d=v(J(s.headers.get("Retry-After")),a,t.capMs,t.jitter);t.log?.(`rate limited (429); retrying in ${Math.round(d)}ms (attempt ${a+1}/${o})`),await t.sleep(d),a++,s=await n()}return s}function U(e){return new Promise(n=>setTimeout(n,e))}async function N(){let e=await f();if(!e){const n=await w(),t=n!=null;if(n)try{await x(n),e=await f()}catch(o){if(o instanceof g)await m();else throw o instanceof T?o:new T(`Token refresh failed unexpectedly (your saved login was NOT cleared \u2014 retry; if it persists, restart the borg session): ${o?.message??"unknown"}`)}if(!e)throw new Error(t?"Authentication expired \u2014 your saved login has expired. Run: borg setup":"Authentication required \u2014 you are not signed in. Run: borg setup")}return e}async function H(){const e=await w();if(!e)return null;try{return await x(e),await f()}catch(n){if(n instanceof g&&await m(),n instanceof T)throw n;return null}}async function V(){if(await f())return"valid";const n=await w();if(!n)return"dead";try{return await x(n),await f()?"valid":"transient"}catch(t){return t instanceof g?(await m(),"dead"):"transient"}}async function r(e,n={}){let t=await N();const{droneSession:o,apiUrl:s,headers:a,...d}=n,E=s??A,y=(d.method??"GET").toUpperCase(),h=async c=>{const p={Authorization:`Bearer ${c}`,...a};o&&(p["X-Drone-Session"]=o),b(`\u2192 ${y} ${e}`);const u=await fetch(`${E}${e}`,{...d,headers:p});return b(`\u2190 ${u.status} ${y} ${e}`),u};let i=await h(t);if(i.status===401){const c=await H();c&&(t=c,i=await h(t))}if(i.status===401)throw new Error("Authentication required. Run: borg setup");if(i.status===429&&(i=await M(i,()=>h(t),{sleep:U,log:c=>console.error(`${R()}${c}`)})),!i.ok){const c=await i.text();b(`\u2717 ${i.status} ${y} ${e}: ${c}`);const p=G(c),u=I(c);if(i.status===410&&u===O)throw new k(p);if(i.status===423&&u===P)throw new C(p);if(i.status===429){const _=i.headers.get("Retry-After"),S=_?` (retry after ${_}s)`:"";throw new Error(`HTTP 429: rate limited${S}: ${p}`)}throw new Error(`HTTP ${i.status}: ${p}`)}return i}async function Z(e,n,t,o){const s={hostname:t??null};return(o==="claude"||o==="codex")&&(s.agent_kind=o),typeof e=="string"?s.cube_name=e:(e.cube_id&&(s.cube_id=e.cube_id),e.cube_name&&(s.cube_name=e.cube_name),e.role_id&&(s.role_id=e.role_id),e.role_name&&(s.role_name=e.role_name),e.prior_drone_id&&(s.prior_drone_id=e.prior_drone_id)),await(await r("/api/assimilate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s),apiUrl:n})).json()}async function Q(e,n){return await(await r("/api/drone/cube",{method:"GET",droneSession:e,apiUrl:n})).json()}async function Y(e,n){return await(await r("/api/drone/role",{method:"GET",droneSession:e,apiUrl:n})).json()}async function K(e,n){return await(await r("/api/drone/whoami",{method:"GET",droneSession:e,apiUrl:n})).json()}async function ee(e,n,t){const o=t?`?since=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/roster${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function te(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since),t.limit!==void 0&&o.set("limit",String(t.limit)),t.unreadOnly&&o.set("unread_only","true");const s=o.toString();return await(await r(`/api/drone/log${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function ne(e,n,t){await r(`/api/drone/log/${t}/ack`,{method:"POST",body:JSON.stringify({kind:"ack"}),droneSession:e,apiUrl:n})}async function oe(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since);const s=o.toString();return await(await r(`/api/drone/regen${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function se(e,n,t,o){const s=new URLSearchParams({role:t,section:o});return await(await r(`/api/drone/role-rationale?${s.toString()}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function re(e,n,t,o={}){const s={message:t,...o.visibility?{visibility:o.visibility}:{},...o.recipientDroneIds?{recipientDroneIds:o.recipientDroneIds}:{},...o.class?{class:o.class}:{},...o.to?{to:o.to}:{}};return await(await r("/api/drone/log",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(s)})).json()}async function ae(e,n,t){const o={kind:t.kind??"friction",message:t.message,...t.metadata?{metadata:t.metadata}:{}};return await(await r("/api/drone/report",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(o)})).json()}async function ie(){return await(await r("/api/cubes",{method:"GET"})).json()}async function ce(){return await(await r("/api/templates",{method:"GET"})).json()}async function pe(e,n,t){const o={cube_directive:n};e&&(o.name=e),t?.template&&(o.template=t.template),t&&Object.prototype.hasOwnProperty.call(t,"message_taxonomy")&&(o.message_taxonomy=t.message_taxonomy??null);const a=await(await r("/api/cubes",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json();return a.cube?{...a.cube,roles:a.roles??[],drones:a.drones??[]}:a}async function de(e,n){return await(await r(`/api/cubes/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function ue(e,n){return await(await r(`/api/cubes/${e}/taxonomy-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function fe(e){await r(`/api/cubes/${e}`,{method:"DELETE"})}async function le(e,n){return await(await r(`/api/cubes/${e}/roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function ye(e,n){return await(await r(`/api/roles/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function he(e,n){return await(await r(`/api/roles/${e}/section-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function we(e){await r(`/api/roles/${e}`,{method:"DELETE"})}async function me(e,n){return $(e,"drone_id"),await(await r(`/api/drones/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({role_id:n})})).json()}async function ge(e){$(e,"drone_id"),await r(`/api/drones/${e}`,{method:"DELETE"})}async function Te(e){const t=await(await r(`/api/cubes/${e}`,{method:"GET"})).json();return t.cube?{...t.cube,roles:t.roles??[],drones:t.drones??[]}:t}async function be(e,n){return await(await r(`/api/cubes/${e}/apply-template`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n})})).json()}async function xe(){return await(await r("/api/subscription/status",{method:"GET"})).json()}async function _e(e,n="software-dev",t=!1,o){return await(await r(`/api/cubes/${e}/sync-roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n,apply:t,...o?{decisions:o}:{}})})).json()}async function $e(){const n=await(await r("/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.checkout_url)throw new Error("No checkout URL in response");return n.checkout_url}async function Ee(){const n=await(await r("/api/subscription/portal",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.portal_url)throw new Error(n.message||"No portal URL in response");return n.portal_url}export{A as API_URL,ne as ackLogEntry,re as appendLog,be as applyTemplate,Z as assimilate,xe as checkSubscriptionStatus,Ee as createBillingPortalSession,pe as createCube,le as createRole,$e as createSubscription,fe as deleteCube,we as deleteRole,ge as evictDrone,G as extractHttpErrorMessage,Te as getCube,Q as getCubeInfo,Y as getRoleInfo,ee as getRoster,N as getValidToken,ie as listCubes,ce as listTemplates,J as parseRetryAfterMs,he as patchRoleSection,ue as patchTaxonomyClass,V as probeSession,v as rateLimitWaitMs,te as readLog,me as reassignDrone,oe as regen,M as retryOn429,se as roleRationale,ae as submitReport,_e as syncRoles,de as updateCube,ye as updateRole,K as whoami};
1
+ import{getIdToken as f,getRefreshToken as w,clearTokens as m}from"./config.js";import{refreshIdToken as S,RefreshTokenInvalidError as g,RefreshTransientError as T}from"./auth.js";import{consolePrefix as R}from"./console-prefix.js";import{debugLog as b}from"./debug.js";import{assertUuidShape as $}from"./evict-drone.js";import{DroneEvictedError as k,DroneFrozenError as C,DRONE_EVICTED_CODE as P,DRONE_FROZEN_CODE as O,errorCodeFromBody as I}from"./drone-lifecycle.js";const A=process.env.BORG_API_URL||"https://api.borgmcp.ai",D=3,L=6e4;let l=null;function x(e){return l||(l=S(e).finally(()=>{l=null}),l)}function J(e){if(e==null)return null;const n=e.trim();return/^\d+$/.test(n)?parseInt(n,10)*1e3:null}function v(e,n,t=L,o=()=>Math.random()*500){const s=e??1e3*(n+1);return Math.min(s,t)+o()}function G(e){const n=(t,o)=>`${t}${/[.:!?]$/.test(t)?"":":"} ${o}`;try{const t=JSON.parse(e);if(typeof t?.error=="string")return typeof t.details=="string"?n(t.error,t.details):t.error;if(t?.error&&typeof t.error=="object"){const o=t.error.message,s=t.error.details??t.details;if(typeof o=="string"&&typeof s=="string")return n(o,s);if(typeof o=="string")return o}if(typeof t?.message=="string"&&typeof t?.details=="string")return n(t.message,t.details);if(typeof t?.message=="string")return t.message}catch{}return e}async function M(e,n,t){const o=t.maxRetries??D;let s=e,a=0;for(;s.status===429&&a<o;){const d=v(J(s.headers.get("Retry-After")),a,t.capMs,t.jitter);t.log?.(`rate limited (429); retrying in ${Math.round(d)}ms (attempt ${a+1}/${o})`),await t.sleep(d),a++,s=await n()}return s}function U(e){return new Promise(n=>setTimeout(n,e))}async function H(){let e=await f();if(!e){const n=await w(),t=n!=null;if(n)try{await x(n),e=await f()}catch(o){if(o instanceof g)await m();else throw o instanceof T?o:new T(`Token refresh failed unexpectedly (your saved login was NOT cleared \u2014 retry; if it persists, restart the borg session): ${o?.message??"unknown"}`)}if(!e)throw new Error(t?"Authentication expired \u2014 your saved login has expired. Run: borg setup":"Authentication required \u2014 you are not signed in. Run: borg setup")}return e}async function N(){const e=await w();if(!e)return null;try{return await x(e),await f()}catch(n){if(n instanceof g&&await m(),n instanceof T)throw n;return null}}async function V(){if(await f())return"valid";const n=await w();if(!n)return"dead";try{return await x(n),await f()?"valid":"transient"}catch(t){return t instanceof g?(await m(),"dead"):"transient"}}async function r(e,n={}){let t=await H();const{droneSession:o,apiUrl:s,headers:a,...d}=n,E=s??A,y=(d.method??"GET").toUpperCase(),h=async c=>{const p={Authorization:`Bearer ${c}`,...a};o&&(p["X-Drone-Session"]=o),b(`\u2192 ${y} ${e}`);const u=await fetch(`${E}${e}`,{...d,headers:p});return b(`\u2190 ${u.status} ${y} ${e}`),u};let i=await h(t);if(i.status===401){const c=await N();c&&(t=c,i=await h(t))}if(i.status===401)throw new Error("Authentication required. Run: borg setup");if(i.status===429&&(i=await M(i,()=>h(t),{sleep:U,log:c=>console.error(`${R()}${c}`)})),!i.ok){const c=await i.text();b(`\u2717 ${i.status} ${y} ${e}: ${c}`);const p=G(c),u=I(c);if(i.status===410&&u===P)throw new k(p);if(i.status===423&&u===O)throw new C(p);if(i.status===429){const _=i.headers.get("Retry-After"),j=_?` (retry after ${_}s)`:"";throw new Error(`HTTP 429: rate limited${j}: ${p}`)}throw new Error(`HTTP ${i.status}: ${p}`)}return i}async function Z(e,n,t,o){const s={hostname:t??null};return(o==="claude"||o==="codex")&&(s.agent_kind=o),typeof e=="string"?s.cube_name=e:(e.cube_id&&(s.cube_id=e.cube_id),e.cube_name&&(s.cube_name=e.cube_name),e.role_id&&(s.role_id=e.role_id),e.role_name&&(s.role_name=e.role_name),e.prior_drone_id&&(s.prior_drone_id=e.prior_drone_id),e.model!=null&&(s.model=e.model)),await(await r("/api/assimilate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s),apiUrl:n})).json()}async function Q(e,n){return await(await r("/api/drone/cube",{method:"GET",droneSession:e,apiUrl:n})).json()}async function Y(e,n){return await(await r("/api/drone/role",{method:"GET",droneSession:e,apiUrl:n})).json()}async function K(e,n){return await(await r("/api/drone/whoami",{method:"GET",droneSession:e,apiUrl:n})).json()}async function ee(e,n,t){const o=t?`?since=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/roster${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function te(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since),t.limit!==void 0&&o.set("limit",String(t.limit)),t.unreadOnly&&o.set("unread_only","true");const s=o.toString();return await(await r(`/api/drone/log${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function ne(e,n,t){await r(`/api/drone/log/${t}/ack`,{method:"POST",body:JSON.stringify({kind:"ack"}),droneSession:e,apiUrl:n})}async function oe(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since);const s=o.toString();return await(await r(`/api/drone/regen${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function se(e,n,t,o){const s=new URLSearchParams({role:t,section:o});return await(await r(`/api/drone/role-rationale?${s.toString()}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function re(e,n,t,o={}){const s={message:t,...o.visibility?{visibility:o.visibility}:{},...o.recipientDroneIds?{recipientDroneIds:o.recipientDroneIds}:{},...o.class?{class:o.class}:{},...o.to?{to:o.to}:{}};return await(await r("/api/drone/log",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(s)})).json()}async function ae(e,n,t){const o={kind:t.kind??"friction",message:t.message,...t.metadata?{metadata:t.metadata}:{}};return await(await r("/api/drone/report",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(o)})).json()}async function ie(){return await(await r("/api/cubes",{method:"GET"})).json()}async function ce(){return await(await r("/api/templates",{method:"GET"})).json()}async function pe(e,n,t){const o={cube_directive:n};e&&(o.name=e),t?.template&&(o.template=t.template),t&&Object.prototype.hasOwnProperty.call(t,"message_taxonomy")&&(o.message_taxonomy=t.message_taxonomy??null);const a=await(await r("/api/cubes",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json();return a.cube?{...a.cube,roles:a.roles??[],drones:a.drones??[]}:a}async function de(e,n){return await(await r(`/api/cubes/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function ue(e,n){return await(await r(`/api/cubes/${e}/taxonomy-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function fe(e){await r(`/api/cubes/${e}`,{method:"DELETE"})}async function le(e,n){return await(await r(`/api/cubes/${e}/roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function ye(e,n){return await(await r(`/api/roles/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function he(e,n){return await(await r(`/api/roles/${e}/section-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function we(e){await r(`/api/roles/${e}`,{method:"DELETE"})}async function me(e,n){return $(e,"drone_id"),await(await r(`/api/drones/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({role_id:n})})).json()}async function ge(e){$(e,"drone_id"),await r(`/api/drones/${e}`,{method:"DELETE"})}async function Te(e){const t=await(await r(`/api/cubes/${e}`,{method:"GET"})).json();return t.cube?{...t.cube,roles:t.roles??[],drones:t.drones??[]}:t}async function be(e,n){return await(await r(`/api/cubes/${e}/apply-template`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n})})).json()}async function xe(){return await(await r("/api/subscription/status",{method:"GET"})).json()}async function _e(e,n="software-dev",t=!1,o){return await(await r(`/api/cubes/${e}/sync-roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n,apply:t,...o?{decisions:o}:{}})})).json()}async function $e(){const n=await(await r("/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.checkout_url)throw new Error("No checkout URL in response");return n.checkout_url}async function Ee(){const n=await(await r("/api/subscription/portal",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.portal_url)throw new Error(n.message||"No portal URL in response");return n.portal_url}export{A as API_URL,ne as ackLogEntry,re as appendLog,be as applyTemplate,Z as assimilate,xe as checkSubscriptionStatus,Ee as createBillingPortalSession,pe as createCube,le as createRole,$e as createSubscription,fe as deleteCube,we as deleteRole,ge as evictDrone,G as extractHttpErrorMessage,Te as getCube,Q as getCubeInfo,Y as getRoleInfo,ee as getRoster,H as getValidToken,ie as listCubes,ce as listTemplates,J as parseRetryAfterMs,he as patchRoleSection,ue as patchTaxonomyClass,V as probeSession,v as rateLimitWaitMs,te as readLog,me as reassignDrone,oe as regen,M as retryOn429,se as roleRationale,ae as submitReport,_e as syncRoles,de as updateCube,ye as updateRole,K as whoami};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "borgmcp",
3
- "version": "1.0.33",
3
+ "version": "1.0.34",
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",