borgmcp 1.0.27 → 1.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -18
- package/dist/assimilate-cmd.d.ts +10 -1
- package/dist/assimilate-cmd.js +37 -37
- package/dist/assimilate-deps.js +3 -3
- package/dist/assimilate-guard.d.ts +1 -1
- package/dist/assimilate-guard.js +2 -2
- package/dist/assimilate-welcome.js +1 -1
- package/dist/auth-recovery.d.ts +1 -1
- package/dist/auth-recovery.js +1 -1
- package/dist/backend-presets.d.ts +20 -1
- package/dist/backend-presets.js +1 -1
- package/dist/claude.js +10 -10
- package/dist/cli-help.d.ts +6 -0
- package/dist/cli-help.js +33 -4
- package/dist/codex-app-wake.d.ts +2 -2
- package/dist/codex-app-wake.js +1 -1
- package/dist/codex-launch.js +1 -1
- package/dist/codex-remote.js +1 -1
- package/dist/config-utils.d.ts +1 -1
- package/dist/cubes.d.ts +9 -0
- package/dist/cubes.js +5 -4
- package/dist/evict-drone.d.ts +2 -2
- package/dist/index.js +12 -12
- package/dist/launch-gate.d.ts +1 -1
- package/dist/list-roles-render.d.ts +3 -3
- package/dist/list-roles-render.js +1 -1
- package/dist/log-audit.d.ts +3 -3
- package/dist/log-audit.js +1 -1
- package/dist/log-stream.d.ts +1 -1
- package/dist/regen-format.d.ts +2 -2
- package/dist/regen-format.js +5 -5
- package/dist/regen.js +1 -1
- package/dist/remote-client.d.ts +1 -1
- package/dist/roster-render.d.ts +2 -2
- package/dist/stream-status.d.ts +4 -4
- package/dist/sync-roles-render.js +2 -2
- package/dist/templates.d.ts +5 -5
- package/dist/templates.js +16 -16
- package/dist/terminal-title.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ borg setup
|
|
|
45
45
|
|
|
46
46
|
The wizard signs you in with Google, checks your subscription access, and registers Borg MCP with the supported agent CLIs installed on your machine.
|
|
47
47
|
|
|
48
|
-
`borg ...` commands are terminal commands. `
|
|
48
|
+
`borg ...` commands are terminal commands. `borg_...` commands are MCP tools
|
|
49
49
|
you ask your agent to run inside Claude Code or Codex.
|
|
50
50
|
|
|
51
51
|
If both Claude Code and Codex are installed, use `--cli` when launching or assimilating if you want to choose explicitly:
|
|
@@ -80,18 +80,18 @@ borg assimilate code-reviewer --cli codex
|
|
|
80
80
|
|
|
81
81
|
## Core MCP tools
|
|
82
82
|
|
|
83
|
-
After assimilation, the agent session has `
|
|
83
|
+
After assimilation, the agent session has `borg_` tools available:
|
|
84
84
|
|
|
85
|
-
- `
|
|
86
|
-
- `
|
|
87
|
-
- `
|
|
88
|
-
- `
|
|
89
|
-
- `
|
|
90
|
-
- `
|
|
91
|
-
- `
|
|
92
|
-
- `
|
|
93
|
-
- `
|
|
94
|
-
- `
|
|
85
|
+
- `borg_regen` - Refresh cube context, role instructions, roster, and recent log.
|
|
86
|
+
- `borg_log` - Append to the shared activity log. Can broadcast or direct messages to drones/roles.
|
|
87
|
+
- `borg_read-log` - Read recent log entries, optionally since an entry id or timestamp.
|
|
88
|
+
- `borg_ack` - Acknowledge a routed log entry without adding noise to the activity log.
|
|
89
|
+
- `borg_roster` - List drones and liveness markers in the cube.
|
|
90
|
+
- `borg_stream-status` - Diagnose the SSE/inbox wake path.
|
|
91
|
+
- `borg_cube`, `borg_role`, `borg_whoami` - Inspect current cube, role, and identity.
|
|
92
|
+
- `borg_create-cube`, `borg_update-cube`, `borg_delete-cube` - Manage cubes.
|
|
93
|
+
- `borg_create-role`, `borg_update-role`, `borg_reassign-drone` - Manage roles and drone assignments.
|
|
94
|
+
- `borg_apply-template`, `borg_sync-roles`, `borg_patch-taxonomy-class` - Bootstrap and maintain role/message-taxonomy templates.
|
|
95
95
|
|
|
96
96
|
## Typical two-agent flow
|
|
97
97
|
|
|
@@ -122,9 +122,9 @@ After assimilation, the agent session has `borg:` tools available:
|
|
|
122
122
|
4. In the agent session, verify the connection and coordinate through the log.
|
|
123
123
|
|
|
124
124
|
```text
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
borg_whoami
|
|
126
|
+
borg_roster
|
|
127
|
+
borg_log "STARTING: review feat/login"
|
|
128
128
|
```
|
|
129
129
|
|
|
130
130
|
## Troubleshooting
|
|
@@ -160,17 +160,17 @@ Run assimilation from your project repo:
|
|
|
160
160
|
borg assimilate
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
Then ask the agent for `
|
|
163
|
+
Then ask the agent for `borg_whoami` and `borg_roster` to verify the connection.
|
|
164
164
|
|
|
165
165
|
### Wake path warning
|
|
166
166
|
|
|
167
|
-
If `
|
|
167
|
+
If `borg_regen` or `borg_stream-status` reports a broken wake path, follow the
|
|
168
168
|
CLI-specific recovery it prints:
|
|
169
169
|
|
|
170
170
|
- Claude Code: arm the inbox monitor command. The monitor wakes the agent
|
|
171
171
|
session when another drone posts to the cube.
|
|
172
172
|
- Codex: check the remote-control socket status, relaunch with `borg --cli codex`
|
|
173
|
-
or `borg assimilate --cli codex` if needed, and run `
|
|
173
|
+
or `borg assimilate --cli codex` if needed, and run `borg_regen` manually when
|
|
174
174
|
returning to the session if no wake arrived.
|
|
175
175
|
|
|
176
176
|
## Development
|
package/dist/assimilate-cmd.d.ts
CHANGED
|
@@ -102,10 +102,19 @@ export interface AssimilateDeps {
|
|
|
102
102
|
updatedAfter: number;
|
|
103
103
|
}) => Promise<string | null>;
|
|
104
104
|
fetch: typeof fetch;
|
|
105
|
-
checkBackendReachable: (descriptor: string | null, fetchImpl: typeof fetch) => Promise<{
|
|
105
|
+
checkBackendReachable: (descriptor: string | null, fetchImpl: typeof fetch, ollamaBaseUrl?: string) => Promise<{
|
|
106
106
|
ok: boolean;
|
|
107
107
|
message?: string;
|
|
108
108
|
}>;
|
|
109
|
+
getLaunchBackend: (cubeId: string, droneId: string) => Promise<{
|
|
110
|
+
backend: string;
|
|
111
|
+
ollamaBaseUrl: string | null;
|
|
112
|
+
} | null>;
|
|
113
|
+
setLaunchBackend: (cubeId: string, droneId: string, record: {
|
|
114
|
+
backend: string;
|
|
115
|
+
ollamaBaseUrl: string | null;
|
|
116
|
+
}) => Promise<void>;
|
|
117
|
+
clearLaunchBackend: (cubeId: string, droneId: string) => Promise<void>;
|
|
109
118
|
}
|
|
110
119
|
export declare function runAssimilate(args: AssimilateArgs, deps: AssimilateDeps): Promise<number>;
|
|
111
120
|
/**
|
package/dist/assimilate-cmd.js
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
import{dirname as
|
|
2
|
-
`),1}if(r.flags.worktree!==void 0){const t=
|
|
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=
|
|
4
|
-
`)}}let s=null;if(n&&n.includes("@")&&n.includes(":")){const t=n.lastIndexOf(":");s={ownerEmail:n.substring(0,t),cubeName:n.substring(t+1)},n=s.cubeName}const
|
|
5
|
-
`);let
|
|
6
|
-
`),i=await e.runSetup(),
|
|
7
|
-
`),1;let
|
|
1
|
+
import{dirname as Z,basename as E}from"node:path";import{randomUUID as ee}from"node:crypto";import{roleSlug as te,matchRoleByName as re,pickDefaultRole as ne}from"./role-resolver.js";import{deriveCubeName as oe,parseGitRemote as ie,sanitizeRemoteUrl as ae}from"./cube-name.js";import{validateName as M}from"./name-validator.js";import{renderAssimilationWelcome as se}from"./assimilate-welcome.js";import{shellEscape as le}from"./shell-escape.js";import{withCodexCwdArg as ce}from"./codex-remote.js";import{buildAgentKickoffPrompt as ue,recordCodexWakeTarget as me,socketPathFromRemoteArgs as de}from"./codex-launch.js";import{perWorktreeBranchName as Y,adoptWorktree as fe,computeWorktreePath as G}from"./worktree-lifecycle.js";import{codexBorgSessionConfigArgs as he}from"./launch-gate.js";import{resolveLaunchEnv as ge,resolveOllamaBaseUrl as be,parseBackend as we}from"./backend-presets.js";async function De(r,e){if(r.role!==void 0){const t=M(r.role);if(!t.ok)return e.stderr(t.error+`
|
|
2
|
+
`),1}if(r.flags.worktree!==void 0){const t=M(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
|
|
3
|
+
`),1}let i=await e.getCachedAuth();if(!i){if(!e.isTTY()&&!r.flags.yes)return e.stderr("borg setup required and stdin is non-interactive. Run `borg setup` first in an interactive terminal, then `borg assimilate`.\n"),1;i=await e.runSetup()}const a=e.findProjectRoot(e.cwd());let n;if(r.flags.cubeName)n=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],a),o=t.status===0?t.stdout:null;if(n=oe(a,o),o){const u=ae(o),m=u?ie(u):null;u&&!m&&n&&e.stderr(`couldn't parse git remote '${u}' \u2014 using directory name '${n}' as cube name
|
|
4
|
+
`)}}let s=null;if(n&&n.includes("@")&&n.includes(":")){const t=n.lastIndexOf(":");s={ownerEmail:n.substring(0,t),cubeName:n.substring(t+1)},n=s.cubeName}const x=e.cwd();e.stderr(`Checking your cubes\u2026
|
|
5
|
+
`);let A;try{A=await e.listCubes(i.apiUrl,i.token)}catch(t){const o=t instanceof Error?t.message:String(t);if(o.includes("Authentication required")||o.includes("Authentication expired"))e.stderr(`Re-authenticating...
|
|
6
|
+
`),i=await e.runSetup(),A=await e.listCubes(i.apiUrl,i.token);else throw t}const N=A.find(t=>t.name===n);if(!N&&s)return e.stderr(`No cube named '${s.cubeName}' accessible to you owned by '${s.ownerEmail}'. Did you accept their invite? See borgmcp.ai/dashboard.
|
|
7
|
+
`),1;let c,_;if(N)c=await e.getCube(i.apiUrl,i.token,N.id),_=!1;else{let t;if(r.flags.template)t=r.flags.template;else if(r.flags.noTemplate)t=void 0;else if(e.isTTY())if(r.flags.yes)t="starter";else{const o=await e.listTemplates(i.apiUrl,i.token),u=["First drone joining a new cube. Apply a template?"];o.forEach((y,v)=>{const S=v===0?" (default)":"";u.push(` ${v+1}) ${y.name}${S} \u2014 ${y.description}`)}),u.push(` ${o.length+1}) skip \u2014 no template`);const m=(await e.prompt(u.join(`
|
|
8
8
|
`)+`
|
|
9
|
-
[1]: `)).trim(),
|
|
10
|
-
`),1;t=
|
|
9
|
+
[1]: `)).trim(),k=m===""?1:parseInt(m,10);if(Number.isNaN(k)||k<1||k>o.length+1)return e.stderr(`invalid choice "${m}"
|
|
10
|
+
`),1;t=k<=o.length?o[k-1].name:void 0}else{if(!r.flags.yes)return e.stderr(`cube creation needs a template choice but stdin is non-interactive.
|
|
11
11
|
Pass --template <name>, --no-template, or --yes (defaults to starter).
|
|
12
12
|
`),1;t="starter"}e.stderr(n?`Creating cube '${n}'\u2026
|
|
13
13
|
`:`Creating your cube\u2026
|
|
14
|
-
`),
|
|
15
|
-
(Use --template <name> on first-drone setup or run \`
|
|
16
|
-
`),1}}else if(
|
|
17
|
-
`),1;const
|
|
18
|
-
`),1;const
|
|
19
|
-
`),1}e.stderr(`Joining cube '${
|
|
20
|
-
`);let
|
|
21
|
-
`),1}const
|
|
22
|
-
`)
|
|
23
|
-
`);const
|
|
14
|
+
`),c=await e.createCube(i.apiUrl,i.token,t?{name:n??void 0,template:t}:{name:n??void 0}),_=!0}let d;if(r.role!==void 0){if(d=re(c.roles,r.role),!d){const t=c.roles.map(m=>m.name).join(", "),o=$e(r.role,c.roles.map(m=>m.name)),u=o?` Did you mean "${o}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${c.name}". Available: ${t}.${u}
|
|
15
|
+
(Use --template <name> on first-drone setup or run \`borg_create-role\` from inside Claude.)
|
|
16
|
+
`),1}}else if(d=ne(c.roles,{isFirstDrone:_}),!d)return e.stderr(`cube "${c.name}" has no default or human-seat role; cannot infer a role. Either pass a role argument explicitly (e.g. \`borg assimilate builder\`) or run \`borg_create-role\` from inside Claude to set up roles.
|
|
17
|
+
`),1;const C=await e.getActiveCube();let $;if(C&&r.flags.here)if(C.cubeId===c.id)$=C.droneId;else return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
|
|
18
|
+
`),1;const T=$!=null?await e.getLaunchBackend(c.id,$):null,h=r.flags.backend??T?.backend??d.default_backend??null,P=be(process.env,h!=null&&h===T?.backend?T?.ollamaBaseUrl:void 0);if(h){const t=await e.checkBackendReachable(h,e.fetch,P);if(!t.ok)return e.stderr(`${t.message}
|
|
19
|
+
`),1}e.stderr(`Joining cube '${c.name}' as ${d.name}\u2026
|
|
20
|
+
`);let l;try{l=await e.assimilate(i.apiUrl,i.token,{cube_id:c.id,role_id:d.id,hostname:e.getHostname(),backend:h,...$?{prior_drone_id:$}:{}})}catch(t){const o=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${o}
|
|
21
|
+
`),1}const p=c.roles.find(t=>t.id===l.role_id)??d;l.reattached?e.stderr(`re-attached to existing seat ${l.drone_label} (session token rotated, no new drone minted)
|
|
22
|
+
`):p.id!==d.id&&e.stderr(`Note: your invite didn't grant the "${d.name}" role \u2014 assimilated as "${p.name}" instead.
|
|
23
|
+
`);const z=r.flags.worktree!==void 0||C!==null&&!r.flags.here;let f=null;if(z){const t=e.runSync("git",["rev-parse","--verify","HEAD"],a);if(t.status!==0)return e.stderr(`sibling worktree spawn requires HEAD pointing at a commit.
|
|
24
24
|
Fix: create at least one commit (\`git commit --allow-empty -m "initial"\`)
|
|
25
25
|
OR: pass --here to skip the sibling spawn and use the current directory
|
|
26
|
-
`),1;e.runSync("git",["fetch","origin"],a);let o="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],a).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],a).status===0&&(o="origin/master");const m=t.stdout.trim(),
|
|
27
|
-
`);const
|
|
28
|
-
`),1;const
|
|
29
|
-
`),1;e.stderr(`spawned sibling worktree at ${
|
|
30
|
-
`),e.chdir(
|
|
31
|
-
`),
|
|
32
|
-
`):e.stderr(`manual cleanup needed: \`git worktree remove --force ${
|
|
33
|
-
`)}return 1}e.setTerminalTitle(
|
|
34
|
-
`)}if(!
|
|
35
|
-
`),e.stderr(
|
|
26
|
+
`),1;e.runSync("git",["fetch","origin"],a);let o="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],a).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],a).status===0&&(o="origin/master");const m=t.stdout.trim(),k=e.runSync("git",["rev-parse",o],a).stdout.trim();m!==k&&e.stderr(`note: local HEAD (${m.slice(0,7)}) differs from ${o} (${k.slice(0,7)}); new worktree will start on ${o}
|
|
27
|
+
`);const y=E(a),v=r.flags.worktree??te(p.name);if(v.length===0)return e.stderr(`cannot derive a worktree name from role "${p.name}"; pass an explicit --worktree <name>
|
|
28
|
+
`),1;const S=e.homedir();let b=G(S,y,v),H=2;for(;e.pathExists(b)||ve(e,a,b);)b=G(S,y,v,H),H++;e.mkdirp(Z(b));const D=Y(E(b),y),F=e.runSync("git",["worktree","add","-b",D,b,o],a);if(F.status!==0)return e.stderr(`git worktree add failed: ${q(F.stderr)}
|
|
29
|
+
`),1;e.stderr(`spawned sibling worktree at ${b} on branch ${D} (${o}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
|
|
30
|
+
`),e.chdir(b),e.stderr(ke(b,D,a)),f=e.cwd()}try{await e.setActiveCube({cubeId:l.cube_id,droneId:l.drone_id,name:c.name,sessionToken:l.session_token,droneLabel:l.drone_label,apiUrl:i.apiUrl})}catch(t){const o=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${o}
|
|
31
|
+
`),f){const u=e.runSync("git",["worktree","remove","--force",f],a);u.status===0?e.stderr(`rolled back spawned worktree at ${f}
|
|
32
|
+
`):e.stderr(`manual cleanup needed: \`git worktree remove --force ${f}\` (rollback attempt failed: ${q(u.stderr).trim()||"unknown"})
|
|
33
|
+
`)}return 1}e.setTerminalTitle(l.drone_label,c.name);const K=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(se(p.name,c.name,K));const w=await e.resolveCli(r.flags.cli),g=e.cwd();try{e.installProjectSessionHook(g)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${g}; it will be re-attempted on the next borg launch
|
|
34
|
+
`)}if(!f){e.runSync("git",["fetch","origin","--prune"],g);const t=Y(E(g),E(a)),o=fe(e.runSync,g,t,"origin/main");o.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
|
|
35
|
+
`),e.stderr(ye(g,t))):o.message&&e.stderr(`worktree sync: ${o.message}
|
|
36
36
|
`)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${w} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
|
|
37
|
-
`);const
|
|
38
|
-
`),
|
|
39
|
-
Agent exited. You were working in ${
|
|
37
|
+
`);const J=e.getInboxPath(l.cube_id,l.drone_id),I=w==="codex"?`borg-wake-${ee()}`:null,V=w==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${J}\` so each event's task-notification title summarizes the new cube log entry (drone label, role, and first ~80 chars of the message body) \u2014 letting you triage events without reading the full body. `:"";let U,O=[],R,B=null,L=null;h?await e.setLaunchBackend(l.cube_id,l.drone_id,{backend:h,ollamaBaseUrl:we(h).kind==="ollama"?P:null}):await e.clearLaunchBackend(l.cube_id,l.drone_id);const W=ge(h,P),j={...process.env,...W.set,BORG_SESSION:"1"};for(const t of W.unset)delete j[t];if(w==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
|
|
38
|
+
`),U="\u26A0 Codex wake-path capability check failed: remote-control is unavailable for this session. Run borg_regen manually whenever you return, and expect only fallback wakeups until relaunch."):U="Codex wake-path capability check passed: remote-control socket established for this session.",O=t.args,Object.keys(t.env).length>0&&Object.assign(j,t.env),B=de(t.args),L=t.server?.cleanup??null}R=[ue({cli:w,codexWakeNonce:I,monitorClause:V,codexWakePathClause:U})],w==="codex"&&(R=[...he(),...O,...ce(R,g)]);const Q=e.exec(w,R,g,j);w==="codex"&&B&&I&&me({deps:e,cubeId:l.cube_id,droneId:l.drone_id,socketPath:B,cwd:g,previewNeedle:I,launchedAtSeconds:Math.floor(Date.now()/1e3)});const X=await Q;if(L)try{L()}catch{}return f&&x!==f&&e.stderr(`
|
|
39
|
+
Agent exited. You were working in ${f}; your shell is back in ${x}.
|
|
40
40
|
To return:
|
|
41
|
-
cd ${
|
|
42
|
-
`),
|
|
41
|
+
cd ${le(f)}
|
|
42
|
+
`),X}function ke(r,e,i){return`
|
|
43
43
|
WORKTREE STEERING: You are in worktree ${r} on branch ${e}. Do ALL work HERE \u2014 cut your feature branch (fix/.../feat/...) off ${e} in THIS worktree, use relative paths / your cwd. NEVER \`git -C ${i}\` or operate on the primary checkout ${i}: the same branch can't be checked out in two worktrees, so work created in the primary won't reach your wt-branch without manual surgery (cherry-pick/merge).
|
|
44
|
-
`}function
|
|
44
|
+
`}function ye(r,e){return`
|
|
45
45
|
WORKTREE STEERING: This checkout is now on branch ${e}. Do ALL work HERE in ${r} \u2014 cut your feature branch (fix/.../feat/...) off ${e}, use relative paths / your cwd.
|
|
46
|
-
`}function
|
|
47
|
-
`).some(n=>n===`worktree ${i}`)}function
|
|
46
|
+
`}function q(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function ve(r,e,i){const a=r.runSync("git",["worktree","list","--porcelain"],e);return a.status!==0?!1:a.stdout.split(`
|
|
47
|
+
`).some(n=>n===`worktree ${i}`)}function $e(r,e){if(e.length===0)return null;const i=r.toLowerCase();let a=null;for(const n of e){const s=pe(i,n.toLowerCase());s<=2&&(a===null||s<a.distance)&&(a={name:n,distance:s})}return a?a.name:null}function pe(r,e){if(r===e)return 0;if(r.length===0)return e.length;if(e.length===0)return r.length;const i=new Array(e.length+1),a=new Array(e.length+1);for(let n=0;n<=e.length;n++)i[n]=n;for(let n=1;n<=r.length;n++){a[0]=n;for(let s=1;s<=e.length;s++){const x=r[n-1]===e[s-1]?0:1;a[s]=Math.min(a[s-1]+1,i[s]+1,i[s-1]+x)}for(let s=0;s<=e.length;s++)i[s]=a[s]}return i[e.length]}export{De as runAssimilate,q as safeStderr,$e as suggestRoleName};
|
package/dist/assimilate-deps.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{spawnSync as m,spawn as l}from"node:child_process";import{existsSync as
|
|
2
|
-
`))if(c.includes('"protocolVersion"')&&c.includes('"result"')){clearTimeout(
|
|
3
|
-
`)}catch{clearTimeout(
|
|
1
|
+
import{spawnSync as m,spawn as l}from"node:child_process";import{existsSync as h,mkdirSync as f}from"node:fs";import{hostname as b,homedir as k}from"node:os";import{createInterface as d}from"node:readline/promises";import{API_URL as u,getValidToken as p,listCubes as C,getCube as _,createCube as T,assimilate as y,listTemplates as w}from"./remote-client.js";import{findProjectRoot as g,getActiveCube as L,setActiveCube as x,inboxPathForDrone as S,setCodexWakeTarget as v,getLaunchBackend as R,setLaunchBackend as B,clearLaunchBackend as A}from"./cubes.js";import{authenticateWithGoogle as P}from"./auth.js";import{addProjectSessionStartHook as U}from"./config-utils.js";import{setTerminalTitle as j}from"./terminal-title.js";import{defaultCliChoiceDeps as D,resolveCliChoice as G}from"./cli-platform.js";import{prepareCodexRemoteLaunch as H,defaultCodexRemoteDeps as I}from"./codex-remote.js";import{findLoadedCodexThread as q}from"./codex-app-server.js";import{checkBackendReachable as V}from"./backend-presets.js";function $(){return{runSync:(e,t,r)=>{const o=m(e,t,{cwd:r,encoding:"utf-8"});return{status:o.status,stdout:o.stdout??"",stderr:o.stderr??""}},pathExists:e=>h(e),cwd:()=>process.cwd(),chdir:e=>process.chdir(e),homedir:()=>k(),mkdirp:e=>f(e,{recursive:!0}),exec:(e,t,r,o)=>new Promise((s,i)=>{const a=l(e,t,{cwd:r,stdio:"inherit",shell:!1,env:o??process.env});a.on("error",i),a.on("exit",n=>s(n??0))}),stderr:e=>process.stderr.write(e),stdout:e=>process.stdout.write(e),prompt:async e=>{const t=d({input:process.stdin,output:process.stdout});try{return await t.question(e)}finally{t.close()}},isTTY:()=>process.stdin.isTTY===!0,getHostname:()=>b(),setTerminalTitle:(e,t)=>{j({label:e,cubeName:t},t)},getActiveCube:()=>L(),setActiveCube:e=>x(e),findProjectRoot:e=>g(e),installProjectSessionHook:e=>{U(e)},getCachedAuth:async()=>{try{return{token:await p(),apiUrl:u}}catch{return null}},runSetup:async()=>(await P(),{token:await p(),apiUrl:u}),listCubes:async(e,t)=>{const{cubes:r}=await C();return r.map(o=>({id:o.id,name:o.name}))},getCube:async(e,t,r)=>{const o=await _(r);return{id:o.id,name:o.name,roles:o.roles}},createCube:async(e,t,r)=>{const o=await T(r.name,"",r.template?{template:r.template}:void 0);return{id:o.id,name:o.name,roles:o.roles}},assimilate:async(e,t,r)=>{const o=await y({cube_id:r.cube_id,role_id:r.role_id,...r.prior_drone_id?{prior_drone_id:r.prior_drone_id}:{},...r.backend!=null?{backend:r.backend}:{}},void 0,r.hostname??null);return{cube_id:o.cube.id,drone_id:o.drone.id,drone_label:o.drone.label,session_token:o.sessionToken,role_id:o.role.id,reattached:o.reattached===!0}},listTemplates:async(e,t)=>{const{templates:r}=await w();return r.map(o=>({name:o.name,description:o.description}))},getInboxPath:(e,t)=>S(e,t),probeMcpReady:()=>new Promise(e=>{const t=l("borg-mcp",[],{stdio:["pipe","pipe","pipe"],shell:!1});let r="",o=!1;const s=n=>{if(!o){o=!0;try{t.kill("SIGTERM")}catch{}e(n)}},i=setTimeout(()=>s(!1),2e3);t.on("error",()=>{clearTimeout(i),s(!1)}),t.on("exit",()=>{clearTimeout(i),s(o)}),t.stdout?.on("data",n=>{r+=n.toString("utf-8");for(const c of r.split(`
|
|
2
|
+
`))if(c.includes('"protocolVersion"')&&c.includes('"result"')){clearTimeout(i),s(!0);return}});const a=JSON.stringify({jsonrpc:"2.0",id:1,method:"initialize",params:{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"borg-assimilate-probe",version:"0.9.3"}}});try{t.stdin?.write(a+`
|
|
3
|
+
`)}catch{clearTimeout(i),s(!1)}}),resolveCli:e=>G(e,D(async t=>{const r=d({input:process.stdin,output:process.stdout});try{return await r.question(t)}finally{r.close()}},()=>process.stdin.isTTY===!0)),prepareCodexRemoteLaunch:()=>H(I()),setCodexWakeTarget:v,findLoadedCodexThread:q,fetch,checkBackendReachable:V,getLaunchBackend:(e,t)=>R(e,t),setLaunchBackend:(e,t,r)=>B(e,t,r),clearLaunchBackend:(e,t)=>A(e,t)}}export{$ as buildDefaultAssimilateDeps};
|
package/dist/assimilate-guard.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{authRecoveryMessage as a}from"./auth-recovery.js";function i(e){return e.trim().toLowerCase()}function s(e,t){return e?i(e.name)===i(t)?{kind:"reattach"}:{kind:"different-cube",activeCubeName:e.name}:{kind:"no-identity"}}function r(e,t){return e.kind==="no-identity"?`\u25FC This session has no drone seat for this worktree, and in-session
|
|
2
|
-
Recover by running \`borg assimilate\` in a terminal to create a fresh seat; in-session
|
|
1
|
+
import{authRecoveryMessage as a}from"./auth-recovery.js";function i(e){return e.trim().toLowerCase()}function s(e,t){return e?i(e.name)===i(t)?{kind:"reattach"}:{kind:"different-cube",activeCubeName:e.name}:{kind:"no-identity"}}function r(e,t){return e.kind==="no-identity"?`\u25FC This session has no drone seat for this worktree, and in-session borg_assimilate is re-attach-only (it never creates seats \u2014 gh#780). To create a seat for cube "${t}", run \`borg assimilate\` in a terminal \u2014 it spawns the worktree, persists the identity, and launches the agent in one step.`:`\u25FC This worktree is attached to cube "${e.activeCubeName}"; in-session borg_assimilate is re-attach-only and cannot switch to "${t}" (gh#780). To work in "${t}", run \`borg assimilate\` in a terminal from that project (or spawn a fresh worktree for it).`}function o(e){return a(e)?null:`\u25FC Re-attach failed \u2014 this worktree's saved seat is unreachable (likely evicted or its session was revoked). Server said: ${e.message??String(e)}
|
|
2
|
+
Recover by running \`borg assimilate\` in a terminal to create a fresh seat; in-session borg_assimilate never re-mints (gh#780).`}export{s as classifyInSessionAssimilate,o as reattachFailureMessage,r as reattachOnlyRefusal};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const t="\x1B[32m",r="\x1B[0m";function c(e,o,n){return[`${n?`${t}\u2713${r}`:"\u2713"} Joined as \`${e}\` in cube \`${o}\`.`,"","Next: ask your agent to run the `
|
|
1
|
+
const t="\x1B[32m",r="\x1B[0m";function c(e,o,n){return[`${n?`${t}\u2713${r}`:"\u2713"} Joined as \`${e}\` in cube \`${o}\`.`,"","Next: ask your agent to run the `borg_regen` tool to see your cube.","Add a teammate: run `borg assimilate <role>` in another terminal.","You're set up \u2014 your team can now see you in the cube.",""].join(`
|
|
2
2
|
`)}export{c as renderAssimilationWelcome};
|
package/dist/auth-recovery.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Root-cause context: the pre-gh#780 funnel answered every auth failure by
|
|
6
6
|
* pointing the user at `borg assimilate` (the wrong remedy; gh#794 now also
|
|
7
7
|
* differentiates a dead saved login from never-signed-in, both → `borg setup`).
|
|
8
|
-
* An in-session agent's only reachable assimilate is the
|
|
8
|
+
* An in-session agent's only reachable assimilate is the borg_assimilate MCP tool, which
|
|
9
9
|
* minted a brand-new drones row — so each auth blip spawned an orphan seat
|
|
10
10
|
* (the gh#780 class). Neither failure mode is fixable by assimilating:
|
|
11
11
|
* assimilate rides the same broken Bearer token.
|
package/dist/auth-recovery.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function t(n){const e=n.message??"";return n.name==="RefreshTransientError"||e.includes("Failed to refresh")?"\u25FC Transient auth-refresh failure (network/Google hiccup). Your session is intact and auth self-recovers \u2014 retry the tool call in a moment. Do NOT re-assimilate:
|
|
1
|
+
function t(n){const e=n.message??"";return n.name==="RefreshTransientError"||e.includes("Failed to refresh")?"\u25FC Transient auth-refresh failure (network/Google hiccup). Your session is intact and auth self-recovers \u2014 retry the tool call in a moment. Do NOT re-assimilate: borg_assimilate cannot fix auth and would mint a duplicate drone seat.":e.includes("Authentication required")||e.includes("Authentication expired")?"\u25FC Authentication expired \u2014 re-consent needed. Run `borg setup` in a terminal to sign in again. Do NOT re-assimilate: borg_assimilate cannot fix auth and would mint a duplicate drone seat.":null}export{t as authRecoveryMessage};
|
|
@@ -15,7 +15,26 @@ export declare const OLLAMA_DEFAULT_BASE_URL = "http://localhost:11434";
|
|
|
15
15
|
* Ollama drone point at a model server on another host, e.g.
|
|
16
16
|
* BORG_OLLAMA_BASE_URL=http://Mac-Studio.local:11434 borg assimilate ...
|
|
17
17
|
*/
|
|
18
|
-
export declare function resolveOllamaBaseUrl(env?: Record<string, string | undefined
|
|
18
|
+
export declare function resolveOllamaBaseUrl(env?: Record<string, string | undefined>, storedUrl?: string | null): string;
|
|
19
|
+
/**
|
|
20
|
+
* Restore a drone's persisted launch backend onto a base environment — the
|
|
21
|
+
* bare-`borg` relaunch path. Returns the merged child env plus, for ollama
|
|
22
|
+
* backends, a `{ descriptor, baseUrl }` the caller can use for a best-effort
|
|
23
|
+
* reachability warning. A null/absent stored backend leaves the env untouched
|
|
24
|
+
* (backward compatible — no stored entry ⇒ today's behavior). Base-URL
|
|
25
|
+
* precedence is BORG_OLLAMA_BASE_URL ▸ stored ▸ default (the pairing rule: the
|
|
26
|
+
* stored host travels with the stored descriptor).
|
|
27
|
+
*/
|
|
28
|
+
export declare function applyOllamaLaunchEnv(baseEnv: Record<string, string | undefined>, stored: {
|
|
29
|
+
backend: string;
|
|
30
|
+
ollamaBaseUrl?: string | null;
|
|
31
|
+
} | null, processEnv?: Record<string, string | undefined>): {
|
|
32
|
+
env: Record<string, string | undefined>;
|
|
33
|
+
probe: {
|
|
34
|
+
descriptor: string;
|
|
35
|
+
baseUrl: string;
|
|
36
|
+
} | null;
|
|
37
|
+
};
|
|
19
38
|
export declare function parseBackend(descriptor: string): {
|
|
20
39
|
kind: 'claude' | 'ollama';
|
|
21
40
|
model: string;
|
package/dist/backend-presets.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const b=/^(claude|ollama):[A-Za-z0-9._:\/-]+$/,k="http://localhost:11434";function h(n=process.env,t){const e=n.BORG_OLLAMA_BASE_URL?.trim()||t?.trim()||"";return e?(/^[a-z][a-z0-9+.-]*:\/\//i.test(e)?e:`http://${e}`).replace(/\/+$/,""):k}function _(n,t,e=process.env){if(!t||!t.backend)return{env:n,probe:null};const{kind:a}=f(t.backend),l=h(e,t.ollamaBaseUrl??void 0),{set:u,unset:c}=A(t.backend,l),s={...n,...u};for(const d of c)delete s[d];return{env:s,probe:a==="ollama"?{descriptor:t.backend,baseUrl:l}:null}}function f(n){const t=n.indexOf(":");if(t<0)throw new Error(`invalid backend descriptor: ${n} (expected <kind>:<model>)`);const e=n.substring(0,t),a=n.substring(t+1);if(e!=="claude"&&e!=="ollama")throw new Error(`invalid backend kind: ${e} (expected claude or ollama)`);return{kind:e,model:a}}function A(n,t=h()){if(!n)return{set:{},unset:[]};const{kind:e,model:a}=f(n);return e==="claude"?{set:{ANTHROPIC_MODEL:a},unset:[]}:{set:{ANTHROPIC_BASE_URL:t,ANTHROPIC_MODEL:a,ANTHROPIC_AUTH_TOKEN:"ollama"},unset:["ANTHROPIC_API_KEY"]}}function O(n,t){const e=n.toLowerCase(),l=e.slice(e.lastIndexOf("/")+1).includes(":")?e:`${e}:latest`;return t.some(u=>{const c=u.toLowerCase();return c===l||c===e})}async function L(n,t,e=h()){if(!n)return{ok:!0};const{kind:a,model:l}=f(n);if(a==="claude")return{ok:!0};const c=e.includes("localhost")||e.includes("127.0.0.1")||e.includes("[::1]")?"start it (ollama serve)":"check the host is up and Ollama is listening there (OLLAMA_HOST=0.0.0.0)",s={ok:!1,message:`Ollama not reachable at ${e} \u2014 ${c}, or assimilate with a Claude backend.`},d=new AbortController,p=setTimeout(()=>d.abort(),3e3);try{const m=await t(`${e}/api/tags`,{signal:d.signal});if(!m.ok)return s;let r=null;try{const i=await m.json();i&&Array.isArray(i.models)&&(r=i.models.map(o=>(o?.name??o?.model??"").replace(/[\x00-\x1F\x7F]/g,"")).filter(o=>o.length>0))}catch{}if(r&&!O(l,r)){const i=r.length===0?"no models are pulled there":`available: ${r.slice(0,12).map(o=>o.length>64?`${o.slice(0,64)}\u2026`:o).join(", ")}${r.length>12?`, \u2026 (${r.length} total)`:""}`;return{ok:!1,message:`Ollama model '${l}' not found at ${e} (${i}) \u2014 pull it with: ollama pull ${l}`}}return{ok:!0}}catch{return s}finally{clearTimeout(p)}}export{b as BACKEND_DESCRIPTOR_REGEX,k as OLLAMA_DEFAULT_BASE_URL,_ as applyOllamaLaunchEnv,L as checkBackendReachable,O as ollamaModelMatches,f as parseBackend,A as resolveLaunchEnv,h as resolveOllamaBaseUrl};
|
package/dist/claude.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{spawn as
|
|
3
|
-
`)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(
|
|
4
|
-
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const
|
|
5
|
-
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const
|
|
6
|
-
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const
|
|
7
|
-
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const
|
|
8
|
-
\u25FC Failed to launch ${
|
|
9
|
-
`)}`)):console.error(`${s()}${
|
|
10
|
-
\u25FC Failed to launch ${
|
|
11
|
-
`)}`),process.exit(1)}),$.on("exit",e=>{if(
|
|
2
|
+
import{spawn as D}from"child_process";import{randomUUID as L}from"node:crypto";import{basename as E}from"node:path";import{createInterface as B}from"node:readline/promises";import a from"chalk";import{findProjectRoot as O,getActiveCube as H,getLaunchBackend as M,inboxPathForDrone as F,setCodexWakeTarget as N,pruneDeadCodexWakeTargets as _}from"./cubes.js";import{applyOllamaLaunchEnv as G,checkBackendReachable as W}from"./backend-presets.js";import{handleVersionFlag as Y,getPackageVersion as g}from"./version.js";import{isHelpFlag as y,setupHelpText as U,topLevelHelpText as V,assimilateHelpText as j}from"./cli-help.js";import{runSpawn as K}from"./spawn.js";import{parseSyncArgs as q,runSync as z}from"./sync.js";import{parseAssimilateArgs as X}from"./parse-assimilate-args.js";import{runAssimilate as J}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as Q}from"./assimilate-deps.js";import{parseLaunchAllArgs as S}from"./parse-launch-all-args.js";import{runLaunchAll as T}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as w}from"./launch-all-deps.js";import{discoverDroneCandidates as Z}from"./launch-all-discovery.js";import{runBareLaunchMenu as ee,shouldShowLaunchMenu as re}from"./bare-launch-menu.js";import{setTerminalTitle as oe}from"./terminal-title.js";import{initConsolePrefix as se,consolePrefix as s}from"./console-prefix.js";import{initDebugFromArgv as te}from"./debug.js";import{fetchLatestBorgmcpVersion as ae,compareVersionsForStaleness as ie}from"./stale-version-check.js";import{defaultCliChoiceDeps as ne,detectCliAvailability as x,installedCliNames as A,parseCliFlag as ce,resolveCliChoice as le}from"./cli-platform.js";import{getRefreshToken as de,getIdToken as pe}from"./config.js";import{composeGetStarted as ue,shouldShowGetStarted as me}from"./get-started.js";import{prepareCodexRemoteLaunch as fe,withCodexCwdArg as ge,defaultCodexRemoteDeps as he,checkCodexBridgeHealthy as we}from"./codex-remote.js";import{findLoadedCodexThread as xe}from"./codex-app-server.js";import{buildAgentKickoffPrompt as ve,recordCodexWakeTarget as ke,socketPathFromRemoteArgs as I}from"./codex-launch.js";import{codexBorgSessionConfigArgs as be}from"./launch-gate.js";import{addCodexMcpServer as Ce,addCodexSessionStartHook as $e,addCodexUserPromptSubmitHook as ye,addMcpServer as Se,addProjectSessionStartHook as Te,addUserPromptSubmitHook as Ae,isCodexMcpServerConfigured as Ie,isMcpServerConfigured as Pe,removeSessionStartHook as Re}from"./config-utils.js";async function De(){te(process.argv),Y(),await se();const c=(async()=>{if(!process.stderr.isTTY)return;const e=g(),r=await ae();if(!r)return;const i=ie(e,r);i.stale&&i.message&&process.stderr.write(`${s()}${i.message}
|
|
3
|
+
`)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(V(g())),process.exit(0)),process.argv[2]==="setup"){y(process.argv[3])&&(process.stdout.write(U(g())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(y)&&(process.stdout.write(j(g())),process.exit(0));const e=X(process.argv.slice(3));e.ok||(process.stderr.write(a.red(`${s()}\u25FC borg assimilate: ${e.error}
|
|
4
|
+
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=Q(),i=await J({role:e.role,flags:e.flags},r);process.exit(i)}if(process.argv[2]==="spawn"){const e=await K();process.exit(e)}if(process.argv[2]==="sync"){const e=q(process.argv.slice(3));e.ok||(process.stderr.write(a.red(`${s()}\u25FC borg sync: ${e.error}
|
|
5
|
+
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await z({},e.options);process.exit(r)}if(process.argv[2]==="launch-all"){const e=S(process.argv.slice(3));e.ok||(process.stderr.write(a.red(`${s()}\u25FC borg launch-all: ${e.error}
|
|
6
|
+
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=w(),i=await T(e.args,r);process.exit(i)}if(me(await de()!==null,await pe()!==null)){const e=A(x()).length>0;process.stdout.write(ue(e)),process.exit(0)}const n=ce(process.argv.slice(2));n.error&&(process.stderr.write(a.red(`${s()}\u25FC ${n.error}
|
|
7
|
+
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const v=async e=>{const r=B({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let o=await le(n.cli,ne(v,()=>process.stdin.isTTY===!0));Le();const t=await H();if(re({extraArgs:process.argv.slice(2),stdinIsTTY:process.stdin.isTTY===!0,stdoutIsTTY:process.stdout.isTTY===!0})){const e=A(x()).find(m=>m!==o)??null;let r=!1;t&&(r=(await Z({targetCubeId:t.cubeId},w())).length>0);const i=await ee({defaultCli:o,otherInstalledCli:e,hasLaunchAllTargets:r},v);if(i.kind==="launch-all"){const m=S([]),R=m.ok?await T(m.args,w()):1;process.exit(R)}o=i.cli}const l=n.rest;oe(t?{label:t.droneLabel,cubeName:t.name}:null,E(process.cwd()));const P=t&&o==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${F(t.cubeId,t.droneId)}\` so each event's task-notification title summarizes the new cube log entry (drone label, role, and first ~80 chars of the message body) \u2014 letting you triage events without reading the full body. `:"";await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const k=o==="codex"?`borg-wake-${L()}`:null;let f,b=[],d={...process.env,BORG_SESSION:"1"},p=null,u=null;if(o==="codex"&&!l.includes("--remote")){console.error(`${s()}${a.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await fe(he());e.warning?(console.error(`${s()}${a.yellow(`warning: ${e.warning}`)}`),f="\u26A0 Codex wake-path capability check failed: remote-control is unavailable for this session. Run borg_regen manually whenever you return, and expect only fallback wakeups until relaunch."):f="Codex wake-path capability check passed: remote-control socket established for this session.",b=e.args,d={...process.env,...e.env,BORG_SESSION:"1"},p=I(e.args),u=e.server?.cleanup??null}else o==="codex"&&l.includes("--remote")&&(f="Codex wake-path capability check: using caller-provided --remote socket; if no wake arrives, run borg_regen manually when returning to the session.",p=I(l),p&&(d={...process.env,BORG_CODEX_REMOTE_WAKE:"1",BORG_SESSION:"1"}));if(t){const e=await M(t.cubeId,t.droneId),r=G(d,e,process.env);if(d=r.env,r.probe){const i=await W(r.probe.descriptor,fetch,r.probe.baseUrl);i.ok||console.error(`${s()}${a.yellow(`warning: ${i.message}`)}`)}}const C=ve({cli:o,codexWakeNonce:k,monitorClause:P,codexWakePathClause:f});let h=[...l,C];o==="codex"&&(h=[...be(),...b,...ge(h,process.cwd())]),console.error(`${s()}${a.blue(`\u25FC Launching ${o==="claude"?"Claude Code":"Codex"}\u2026`)}`);const $=D(o,h,{stdio:"inherit",shell:!1,env:d});o==="codex"&&t&&p&&(ke({deps:{setCodexWakeTarget:N,findLoadedCodexThread:xe},cubeId:t.cubeId,droneId:t.droneId,socketPath:p,passthroughArgs:l,previewNeedle:k??C.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),_(e=>we(e))),$.on("error",e=>{if(u)try{u()}catch{}e.code==="ENOENT"?(console.error(`${s()}${a.red(`
|
|
8
|
+
\u25FC Failed to launch ${o}`)}`),console.error(`${s()}${a.gray(`Make sure ${o} is installed.
|
|
9
|
+
`)}`)):console.error(`${s()}${a.red(`
|
|
10
|
+
\u25FC Failed to launch ${o}: ${e.message}
|
|
11
|
+
`)}`),process.exit(1)}),$.on("exit",e=>{if(u)try{u()}catch{}process.exit(e??0)})}function Le(){const c=x();if(c.claude)try{Pe()||Se(),Te(O(process.cwd())),Re(),Ae()}catch(n){console.error(`${s()}${a.yellow(`warning: Claude Code integration check failed: ${n?.message??n}`)}`)}if(c.codex)try{Ie()||Ce(),$e(),ye()}catch(n){console.error(`${s()}${a.yellow(`warning: Codex integration check failed: ${n?.message??n}`)}`)}}De().catch(c=>{console.error(`${s()}${a.red(`
|
|
12
12
|
\u25FC Error: ${c.message}
|
|
13
13
|
`)}`),process.exit(1)});
|
package/dist/cli-help.d.ts
CHANGED
|
@@ -14,6 +14,12 @@ export declare function isHelpFlag(arg: string | undefined): boolean;
|
|
|
14
14
|
* claude.ts, which launches agent CLIs as a side effect.
|
|
15
15
|
*/
|
|
16
16
|
export declare function topLevelHelpText(version: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Help text for `borg assimilate --help` — the home for the full assimilate flag
|
|
19
|
+
* set, including `--backend` and remote-Ollama config, which the concise
|
|
20
|
+
* top-level help only hints at.
|
|
21
|
+
*/
|
|
22
|
+
export declare function assimilateHelpText(version: string): string;
|
|
17
23
|
/**
|
|
18
24
|
* Help text for `borg setup --help` (gh#520 — previously this ran the setup
|
|
19
25
|
* wizard instead of showing help). Mirrors the `borg setup` description in the
|
package/dist/cli-help.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
function
|
|
1
|
+
function n(e){return e==="--help"||e==="-h"}function o(e){return`borgmcp ${e} \u2014 run several AI coding agents on one project, together.
|
|
2
2
|
They coordinate through a shared log (a "cube"). For Claude Code & Codex.
|
|
3
3
|
|
|
4
4
|
Docs & quickstart: https://borgmcp.ai/get-started
|
|
5
5
|
|
|
6
6
|
Install Claude Code or Codex first. Type \`borg ...\` in your terminal;
|
|
7
|
-
type \`
|
|
7
|
+
type \`borg_...\` inside your agent session once you've joined a cube ("assimilate").
|
|
8
8
|
|
|
9
9
|
Usage:
|
|
10
10
|
borg Launch your agent CLI with cube context
|
|
@@ -12,13 +12,42 @@ Usage:
|
|
|
12
12
|
borg setup --no-browser Set up from SSH/headless terminals
|
|
13
13
|
borg assimilate [role] Join a cube (creates one if needed)
|
|
14
14
|
borg assimilate --worktree <name> Spawn a worktree drone (in ~/.borg/worktrees/<repo>/<name>)
|
|
15
|
+
borg assimilate --backend ollama:<model> Run the drone on an Ollama model (see \`borg assimilate --help\`)
|
|
15
16
|
borg sync [--prune] Sync this worktree's branch to origin/main
|
|
16
17
|
borg launch-all [cube] Launch all drone worktrees of a cube (default: active cube)
|
|
17
18
|
borg --cli claude|codex Choose agent CLI for this project
|
|
18
19
|
borg --version Show installed version
|
|
19
20
|
|
|
20
21
|
All other arguments are passed through to the selected agent CLI.
|
|
21
|
-
`}function
|
|
22
|
+
`}function t(e){return`borg assimilate (borgmcp ${e}) \u2014 join a cube under a role (creates the cube if needed)
|
|
23
|
+
|
|
24
|
+
Usage:
|
|
25
|
+
borg assimilate [role] Join the active cube under [role] (default role if omitted)
|
|
26
|
+
borg assimilate [role] --worktree <name> Spawn the drone in an isolated git worktree
|
|
27
|
+
(~/.borg/worktrees/<repo>/<name>)
|
|
28
|
+
borg assimilate --here Assimilate in the current worktree (no sibling spawn)
|
|
29
|
+
borg assimilate --help Show this help
|
|
30
|
+
|
|
31
|
+
Flags:
|
|
32
|
+
--worktree <name> Create + launch the drone in a sibling git worktree
|
|
33
|
+
--here Stay in the current worktree (no sibling spawn)
|
|
34
|
+
--cube-name <name> Cube to join/create (default: repo basename)
|
|
35
|
+
--template <name> Bootstrap a new cube from a bundled role template
|
|
36
|
+
--no-template Create the cube with no template roles
|
|
37
|
+
--cli claude|codex Agent CLI to launch (default: claude)
|
|
38
|
+
--backend <kind>:<model> Model backend for this drone (default: your Claude config)
|
|
39
|
+
--yes, -y Skip confirmation prompts
|
|
40
|
+
|
|
41
|
+
Backends (--backend <kind>:<model>):
|
|
42
|
+
claude:<model> e.g. --backend claude:claude-opus-4-8
|
|
43
|
+
ollama:<model> Run on an Ollama model, e.g. --backend ollama:qwen3-coder-next
|
|
44
|
+
The model must be pulled (ollama pull <model>); borg verifies it
|
|
45
|
+
against the Ollama server before assimilating.
|
|
46
|
+
|
|
47
|
+
Remote Ollama: set BORG_OLLAMA_BASE_URL (default http://localhost:11434), e.g.
|
|
48
|
+
BORG_OLLAMA_BASE_URL=http://Mac-Studio.local:11434 \\
|
|
49
|
+
borg assimilate build --backend ollama:qwen3-coder-next
|
|
50
|
+
`}function r(e){return`borg setup (borgmcp ${e}) \u2014 set up OAuth + register the borg MCP server
|
|
22
51
|
|
|
23
52
|
Borg MCP needs Claude Code or Codex installed first.
|
|
24
53
|
|
|
@@ -29,4 +58,4 @@ Usage:
|
|
|
29
58
|
for SSH / headless / container terminals. Alias: --device.
|
|
30
59
|
Auto-detected on SSH/headless; this forces it.
|
|
31
60
|
borg setup --help Show this help
|
|
32
|
-
`}export{
|
|
61
|
+
`}export{t as assimilateHelpText,n as isHelpFlag,r as setupHelpText,o as topLevelHelpText};
|
package/dist/codex-app-wake.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { CodexAppServerClient } from './codex-app-server.js';
|
|
|
3
3
|
import { checkCodexBridgeHealthy } from './codex-remote.js';
|
|
4
4
|
export declare const CODEX_WAKE_PROMPT = "New Borg cube-log activity arrived.";
|
|
5
5
|
export declare function formatCodexWakePrompt(inboxLine: string): string;
|
|
6
|
-
export declare const CODEX_CATCHUP_PROMPT = "Borg cube activity arrived while you were busy. Run `
|
|
6
|
+
export declare const CODEX_CATCHUP_PROMPT = "Borg cube activity arrived while you were busy. Run `borg_read-log unread_only=true` and DRAIN \u2014 repeat until the returned page is under the limit and behind_by is 0 \u2014 so no entries are skipped.";
|
|
7
7
|
export declare function isCodexRemoteWakeEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
8
8
|
export declare function resolveSessionAgentKind(env?: NodeJS.ProcessEnv): 'claude' | 'codex';
|
|
9
9
|
export interface CodexWakeTarget {
|
|
@@ -57,7 +57,7 @@ export declare function wakeCodexViaAppServer(reason?: string, env?: NodeJS.Proc
|
|
|
57
57
|
export declare const CODEX_HEARTBEAT_CADENCE_MS: number;
|
|
58
58
|
/**
|
|
59
59
|
* gh#857 WI-2: one tick of the codex /loop-equivalent heartbeat — a periodic,
|
|
60
|
-
* independent re-engagement that injects a
|
|
60
|
+
* independent re-engagement that injects a borg_read-log (unread_only=true) DRAIN turn so an
|
|
61
61
|
* idle codex drone re-syncs even if every per-entry wake was missed. SKIPS when a
|
|
62
62
|
* delivery (per-entry wake, retry-drain, or a prior heartbeat) already landed
|
|
63
63
|
* within the cadence window (shouldFireHeartbeat), so an active cube with flowing
|
package/dist/codex-app-wake.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import{getActiveCube as C,getCodexWakeTarget as m,setCodexWakeTarget as R}from"./cubes.js";import{CodexAppServerClient as S}from"./codex-app-server.js";import{checkCodexBridgeHealthy as K}from"./codex-remote.js";import{recordEventReceipt as W}from"./health-beat.js";import{codexAppServerSocketFromEnv as O,pickFreshThread as M,wakeTargetChanged as B,wakeRetryBackoffMs as F,wakeRetryExpired as H,WAKE_RETRY_MAX_ATTEMPTS as N,shouldFireHeartbeat as j}from"./codex-wake-resolve.js";const L="New Borg cube-log activity arrived.";function ie(e){return`New Borg cube-log activity arrived:
|
|
2
|
-
${e}`}const D="Borg cube activity arrived while you were busy. Run `
|
|
2
|
+
${e}`}const D="Borg cube activity arrived while you were busy. Run `borg_read-log unread_only=true` and DRAIN \u2014 repeat until the returned page is under the limit and behind_by is 0 \u2014 so no entries are skipped.";function P(e=process.env){return e.BORG_CODEX_REMOTE_WAKE==="1"}function X(e=process.env){return P(e)?"codex":"claude"}function q(e=process.env){return P(e)?{enabled:!0}:{enabled:!1}}async function ce(e,t={}){try{const r=await(t.getCodexWakeTarget??m)(e.cubeId,e.droneId);return r?(t.checkBridge??K)(r.socketPath):!1}catch{return null}}let f=!1;const h=[],l=new Set,w=[],$=100;let g=!1,k=null;function se(){return k}function T(e){k=(e.now??Date.now)()}let v=!1,y=!1;function p(){return y?!1:(y=!0,!0)}function A(){y=!1}function V(e){return e?.code==="ENOENT"}function Y(e){return new Promise(t=>setTimeout(t,e))}function x(e,t){return t.createClient?t.createClient(e):new S(e)}async function b(e,t){const n=O(t.env??process.env);if(n){const a=x(n,t);await a.connect();try{const o=await a.loadedThreadIds(),i=[];for(const u of o){const s=await a.readThread(u);s&&i.push({id:s.id,cwd:s.cwd,updatedAt:s.updatedAt})}const c=M(i,{cwd:(t.cwd??(()=>process.cwd()))()});return c?(await G(e,{socketPath:n,threadId:c},t),{socketPath:n,threadId:c}):null}finally{a.close()}}const r=await(t.getCodexWakeTarget??m)(e.cubeId,e.droneId);return r?{socketPath:r.socketPath,threadId:r.threadId}:null}async function G(e,t,n){try{const r=n.getCodexWakeTarget??m,a=n.setCodexWakeTarget??R,o=await r(e.cubeId,e.droneId),i=o?{socketPath:o.socketPath,threadId:o.threadId}:null;B(i,t)&&await a(e.cubeId,e.droneId,t)}catch{}}function ue(e=L,t=process.env,n={}){q(t).enabled&&(h.push({reason:e,deps:n}),!f&&(f=!0,Q().finally(()=>{f=!1})))}async function Q(){for(;h.length>0;){const e=h.shift();await U(e.reason,e.deps)}}async function U(e,t){if(!p()){E(t);return}try{const n=await(t.getActiveCube??C)();if(!n)return;const r=await b(n,t);if(!r)return;const{socketPath:a,threadId:o}=r,i=`${o}\0${e}`;if(l.has(i))return;const c=x(a,t);await c.connect();try{if((await c.readThread(o))?.status?.type==="active"){E(t);return}await c.startTurn(o,e),W(),Z(i),T(t)}finally{c.close()}}catch{E(t)}finally{A()}}function E(e){g||(g=!0,z(e).finally(()=>{g=!1}))}async function z(e){const t=e.sleep??Y,n=e.now??Date.now,r=e.jitter??(()=>Math.random()*500),a=e.maxAttempts??N,o=n();let i=0;for(;!H(o,n())&&i<a;)if(await t(F(i,r())),i++,!!p())try{const c=await(e.getActiveCube??C)();if(!c)continue;const u=await b(c,e);if(!u)continue;const{socketPath:s,threadId:I}=u,d=x(s,e);await d.connect();try{if((await d.readThread(I))?.status?.type==="active")continue;await d.startTurn(I,D),W(),T(e);return}finally{d.close()}}catch{}finally{A()}}const _=20*6e4;async function J(e={},t=_){if(v)return;const n=(e.now??Date.now)();if(j(k,n,t)&&!(e.isStreamOwner&&!e.isStreamOwner())&&p()){v=!0;try{const r=await(e.getActiveCube??C)();if(!r)return;const a=await b(r,e);if(!a)return;const o=x(a.socketPath,e);await o.connect();try{if((await o.readThread(a.threadId))?.status?.type==="active")return;await o.startTurn(a.threadId,D),T(e)}finally{o.close()}}catch(r){V(r)&&e.onAppServerSocketDead?.()}finally{v=!1,A()}}}function le(e={}){if((e.agentKind??X())!=="codex")return null;const n=e.intervalMs??_,r=e.tick??(()=>{J()}),a=setInterval(r,n);return a.unref?.(),a}function de(){f=!1,h.length=0,l.clear(),w.length=0,g=!1,k=null,v=!1,y=!1}function Z(e){if(!l.has(e))for(l.add(e),w.push(e);w.length>$;){const t=w.shift();t&&l.delete(t)}}export{D as CODEX_CATCHUP_PROMPT,_ as CODEX_HEARTBEAT_CADENCE_MS,L as CODEX_WAKE_PROMPT,J as fireCodexHeartbeatTick,ie as formatCodexWakePrompt,se as getLastDeliveredAt,P as isCodexRemoteWakeEnabled,ce as probeCodexBridgeArmed,de as resetCodexWakeForTests,q as resolveCodexWakeTarget,X as resolveSessionAgentKind,le as startCodexHeartbeat,ue as wakeCodexViaAppServer};
|
package/dist/codex-launch.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function s(e){const r=e.codexWakeNonce?`Wake target nonce: ${e.codexWakeNonce}. `:"",t="On every Monitor wake and every ScheduleWakeup heartbeat, triage with
|
|
1
|
+
function s(e){const r=e.codexWakeNonce?`Wake target nonce: ${e.codexWakeNonce}. `:"",t="On every Monitor wake and every ScheduleWakeup heartbeat, triage with borg_read-log unread_only=true first \u2014 it returns only entries since your last read, advancing your server-side read cursor (oldest-unread first), so you never skip a burst; and it keeps last_seen fresh. DRAIN: if it returns a full set (count == limit) or borg_roster shows behind_by>0, call read-log unread_only=true again until the return is < limit. Do NOT triage with a manual since cursor or a bare limit window \u2014 since is strict-after (skips the boundary entry) and a limit-bounded read skips older-unread during bursts (and codex has no per-entry inbox-Monitor backstop to catch them); reserve limit for explicit bounded reads. Call borg_regen only when you are about to act and need full cube/role/roster context, on session start, after context compaction, or periodically every 4-5 wakes / 15-30 minutes. Never reflexively call borg_regen for routine text-only wakes. ",a=e.codexWakePathClause??"Codex Borg wakeups use remote-control when available; if no wake arrives, run borg_regen manually when returning to the session.";return`${e.cli==="claude"?"/loop ":""}Call borg_regen and follow the playbook in its response. `+r+'Note: at session start the borg MCP server is still spinning up in parallel \u2014 if a system reminder claims "MCP server disconnected" or the borg tools are not yet registered, do NOT bail. Recover via `ToolSearch({query: "select:mcp__borg__borg_regen,mcp__borg__borg_log,Monitor", max_results: 3})` to load the bootstrap tools in one call, then call borg_regen. The server typically becomes available within a few seconds. '+e.monitorClause+"Coordinator/Queen seats: before posting bare `Standing.`, run the anti-passive-Standing stale-check per your role text \u2014 for every in-flight dispatch / REVIEW-READY / synthesis-pending, compare elapsed-since-last-transition against the cadence-table PING thresholds; escalate per the 4-step ladder (PING / probe-liveness / reassign / suspect-systemic) when any row is overdue. Append the awaited signal when you do post Standing (`Standing for X`, never bare). "+t+(e.cli==="claude"?"Wake-path capability check: if borg_regen shows a wake-path warning, arm the Monitor before starting work. Use a 30-minute (1800s) fallback heartbeat for ScheduleWakeup.":a)}function i(e){const r=e.indexOf("--remote");if(r<0)return null;const t=e[r+1];return t?.startsWith("unix://")?t.slice(7):null}function o(e){if(e[0]==="resume"&&e[1]&&!e[1].startsWith("-"))return e[1];const r=e.indexOf("--resume");return r>=0&&e[r+1]?e[r+1]:null}async function l(e){try{const r=e.passthroughArgs?o(e.passthroughArgs):null;if(r){await e.deps.setCodexWakeTarget(e.cubeId,e.droneId,{threadId:r,socketPath:e.socketPath});return}const t=Date.now()+15e3;for(;Date.now()<t;){const a=await e.deps.findLoadedCodexThread({socketPath:e.socketPath,cwd:e.cwd,previewIncludes:e.previewNeedle,updatedAfter:e.launchedAtSeconds-5});if(a){await e.deps.setCodexWakeTarget(e.cubeId,e.droneId,{threadId:a,socketPath:e.socketPath});return}await new Promise(n=>setTimeout(n,500))}}catch{}}export{s as buildAgentKickoffPrompt,l as recordCodexWakeTarget,i as socketPathFromRemoteArgs,o as threadIdFromPassthroughArgs};
|
package/dist/codex-remote.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mkdirSync as v,chmodSync as w,readdirSync as g,rmSync as C,writeFileSync as k,readFileSync as y}from"node:fs";import{homedir as S}from"node:os";import{join as u}from"node:path";import{randomBytes as b}from"node:crypto";import{spawn as A}from"node:child_process";import{CodexAppServerClient as E}from"./codex-app-server.js";import{codexBorgSessionConfigArgs as R,BORG_SESSION_ENV as
|
|
1
|
+
import{mkdirSync as v,chmodSync as w,readdirSync as g,rmSync as C,writeFileSync as k,readFileSync as y}from"node:fs";import{homedir as S}from"node:os";import{join as u}from"node:path";import{randomBytes as b}from"node:crypto";import{spawn as A}from"node:child_process";import{CodexAppServerClient as E}from"./codex-app-server.js";import{codexBorgSessionConfigArgs as R,BORG_SESSION_ENV as _}from"./launch-gate.js";import{codexAppServerSocketConfigArgs as $}from"./codex-wake-resolve.js";const N=u(S(),".config","borgmcp","codex-remote");function G(e,r){return I(e)?e:["--cd",r,...e]}function I(e){return e.some(r=>r==="--cd"||r.startsWith("--cd=")||r==="-C")}function x(e){try{return process.kill(e,0),!0}catch(r){return r?.code==="EPERM"}}function X(e,r={}){if(!e)return null;const a=r.isAlive??x,i=r.readPidFile??(o=>y(o,"utf-8")),c=e.replace(/\.sock$/,".pid");try{const o=Number.parseInt(i(c).trim(),10);return Number.isNaN(o)?null:a(o)}catch{return null}}function s(e){try{C(e,{force:!0})}catch{}}function M(e,r){let a;try{a=g(e)}catch{return}for(const i of a){if(!i.endsWith(".pid"))continue;const c=u(e,i),o=u(e,i.replace(/\.pid$/,".sock"));let t;try{t=Number.parseInt(y(c,"utf-8").trim(),10)}catch{s(c);continue}(Number.isNaN(t)||!r(t))&&(s(o),s(c))}}function p(e){return{args:[],env:{},warning:e}}async function j(e){const r=e.runtimeDir??N,a=e.isAlive??x,i=e.readyTimeoutMs??8e3,c=e.pollIntervalMs??250;try{v(r,{recursive:!0,mode:448}),w(r,448),M(r,a)}catch(n){return p(`Codex remote-wake disabled: could not prepare ${r} (${n?.message??n}); run borg_regen manually.`)}const o=(e.socketId??(()=>b(16).toString("hex")))(),t=u(r,`${o}.sock`),m=u(r,`${o}.pid`);let l;try{l=e.spawnAppServer(t)}catch(n){return s(t),p(`Codex remote-wake disabled: could not start \`codex app-server\` (${n?.message??n}) \u2014 is Codex installed + up to date? This session only wakes on the ~30min /loop fallback; run borg_regen manually.`)}if(l.pid!=null)try{k(m,String(l.pid))}catch{}const f=()=>{try{l.kill()}catch{}s(t),s(m)},h=Math.max(1,Math.ceil(i/c));let d=!1;for(let n=0;n<h&&!d;n++){try{d=await e.probeReady(t)}catch{d=!1}!d&&n<h-1&&await e.sleep(c)}return d?{args:["--remote",`unix://${t}`],env:{BORG_CODEX_REMOTE_WAKE:"1"},server:{pid:l.pid,socketPath:t,cleanup:f}}:(f(),p(`Codex remote-wake disabled: could not reach a Codex app-server at ${t} within ${i}ms (is Codex up to date? \`codex app-server --listen\` is required). This session only wakes on the ~30min /loop fallback \u2014 run borg_regen manually when you return.`))}function q(){return{spawnAppServer:e=>{const r=A("codex",["app-server",...R(),...$(e),"--listen",`unix://${e}`],{stdio:"ignore",shell:!1,env:{...process.env,[_]:"1"}});return{pid:r.pid,kill:()=>{try{r.kill()}catch{}}}},probeReady:async e=>{const r=new E(e);try{return await r.connect(),await r.loadedThreadIds(),!0}catch{return!1}finally{try{r.close()}catch{}}},sleep:e=>new Promise(r=>setTimeout(r,e))}}export{N as DEFAULT_CODEX_REMOTE_DIR,X as checkCodexBridgeHealthy,q as defaultCodexRemoteDeps,x as defaultIsAlive,j as prepareCodexRemoteLaunch,G as withCodexCwdArg};
|
package/dist/config-utils.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ export declare function removeSessionStartHook(): boolean;
|
|
|
43
43
|
* Register a Claude Code UserPromptSubmit hook that runs `borg-log-audit`
|
|
44
44
|
* before each user prompt. The audit script nudges the drone if the
|
|
45
45
|
* previous assistant span used state-changing tools without calling
|
|
46
|
-
* `
|
|
46
|
+
* `borg_log`. Idempotent: re-running won't add duplicates.
|
|
47
47
|
*
|
|
48
48
|
* Returns true if a change was made, false otherwise.
|
|
49
49
|
*/
|