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