borgmcp 1.0.60 → 1.1.1
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.d.ts +1 -1
- package/dist/assimilate-cmd.js +30 -30
- package/dist/bare-launch-menu.js +4 -4
- package/dist/claude.js +13 -13
- package/dist/cli-platform.d.ts +1 -0
- package/dist/cli-platform.js +4 -4
- package/dist/codex-app-wake.d.ts +2 -2
- package/dist/codex-app-wake.js +2 -2
- package/dist/codex-launch.js +2 -2
- package/dist/config-utils.d.ts +23 -0
- package/dist/config-utils.js +4 -3
- package/dist/cubes.d.ts +1 -1
- package/dist/health-beat.d.ts +5 -4
- package/dist/health-beat.js +1 -1
- package/dist/index.js +18 -18
- package/dist/launch-all-deps.d.ts +1 -1
- package/dist/log-stream.d.ts +8 -0
- package/dist/log-stream.js +8 -8
- package/dist/opencode-drone.d.ts +18 -0
- package/dist/opencode-drone.js +1 -0
- package/dist/opencode-plugin.d.ts +3 -0
- package/dist/opencode-plugin.js +37 -0
- package/dist/parse-launch-all-args.d.ts +1 -1
- package/dist/regen-format.d.ts +1 -1
- package/dist/regen.js +1 -1
- package/dist/remote-client.d.ts +1 -1
- package/dist/roster-render.d.ts +1 -1
- package/dist/setup-confirm.d.ts +4 -0
- package/dist/setup-confirm.js +2 -2
- package/dist/setup.js +24 -22
- package/package.json +2 -1
package/dist/assimilate-cmd.d.ts
CHANGED
|
@@ -88,7 +88,7 @@ export interface AssimilateDeps {
|
|
|
88
88
|
hostname?: string | null;
|
|
89
89
|
prior_drone_id?: string;
|
|
90
90
|
model?: string | null;
|
|
91
|
-
agent_kind?: 'claude' | 'codex' | null;
|
|
91
|
+
agent_kind?: 'claude' | 'codex' | 'opencode' | null;
|
|
92
92
|
}) => Promise<AssimilateResult>;
|
|
93
93
|
listTemplates: (apiUrl: string, token: string) => Promise<Array<{
|
|
94
94
|
name: string;
|
package/dist/assimilate-cmd.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{dirname as K,basename as
|
|
1
|
+
import{dirname as K,basename as R}from"node:path";import{randomUUID as oe}from"node:crypto";import{roleSlug as ie,matchRoleByName as ae,pickDefaultRole as se}from"./role-resolver.js";import{deriveCubeName as le,parseGitRemote as ce,sanitizeRemoteUrl as ue}from"./cube-name.js";import{validateName as q}from"./name-validator.js";import{renderAssimilationWelcome as me}from"./assimilate-welcome.js";import{shellEscape as de}from"./shell-escape.js";import{withCodexCwdArg as fe}from"./codex-remote.js";import{buildAgentKickoffPrompt as he,buildKickoffWakePathClause as ge,recordCodexWakeTarget as we,socketPathFromRemoteArgs as be}from"./codex-launch.js";import{perWorktreeBranchName as W,adoptWorktree as ke,computeWorktreePath as z,localBranchExists as J,isMerged as ye}from"./worktree-lifecycle.js";import{DroneEvictedError as ve}from"./drone-lifecycle.js";import{codexBorgSessionConfigArgs as pe}from"./launch-gate.js";import{inboxPathForDrone as $e}from"./cubes.js";import{resolveLaunchEnv as xe,resolveOllamaBaseUrl as Se,parseModel as Ce}from"./model-presets.js";import{unlinkSync as Re}from"node:fs";import{gcOrphanInboxesForCube as Ee,defaultListInboxLogs as _e,defaultInboxLivenessDeps as Ie,isInboxLive as Ae,ORPHAN_INBOX_STALE_MS as Ne}from"./gc-orphan-inboxes.js";import{installBorgPlugin as Pe}from"./opencode-plugin.js";import{ensureOpenCodeServerEnvConfig as Le}from"./config-utils.js";import{computeOpenCodePort as Te}from"./opencode-drone.js";async function lt(r,e){if(r.role!==void 0){const t=q(r.role);if(!t.ok)return e.stderr(t.error+`
|
|
2
2
|
`),1}if(r.flags.worktree!==void 0){const t=q(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 o=e.findProjectRoot(e.cwd());let i;if(r.flags.cubeName)i=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],o),n=t.status===0?t.stdout:null;if(i=
|
|
4
|
-
`)}}let
|
|
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 o=e.findProjectRoot(e.cwd());let i;if(r.flags.cubeName)i=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],o),n=t.status===0?t.stdout:null;if(i=le(o,n),n){const c=ue(n),m=c?ce(c):null;c&&!m&&i&&e.stderr(`couldn't parse git remote '${c}' \u2014 using directory name '${i}' as cube name
|
|
4
|
+
`)}}let s=null;if(i&&i.includes("@")&&i.includes(":")){const t=i.lastIndexOf(":");s={ownerEmail:i.substring(0,t),cubeName:i.substring(t+1)},i=s.cubeName}const E=e.cwd();e.stderr(`Checking your cubes\u2026
|
|
5
5
|
`);let I;try{I=await e.listCubes(a.apiUrl,a.token)}catch(t){const n=t instanceof Error?t.message:String(t);if(n.includes("Authentication required")||n.includes("Authentication expired"))e.stderr(`Re-authenticating...
|
|
6
|
-
`),a=await e.runSetup(),I=await e.listCubes(a.apiUrl,a.token);else throw t}const A=I.find(t=>t.name===i);if(!A&&
|
|
6
|
+
`),a=await e.runSetup(),I=await e.listCubes(a.apiUrl,a.token);else throw t}const A=I.find(t=>t.name===i);if(!A&&s)return e.stderr(`No cube named '${s.cubeName}' accessible to you owned by '${s.ownerEmail}'. Did you accept their invite? See borgmcp.ai/dashboard.
|
|
7
7
|
`),1;let u,N;if(A)u=await e.getCube(a.apiUrl,a.token,A.id),N=!1;else{let t;if(r.flags.template)t=r.flags.template;else if(r.flags.noTemplate)t=void 0;else if(e.isTTY())if(r.flags.yes)t="starter";else{const n=await e.listTemplates(a.apiUrl,a.token),c=["First drone joining a new cube. Apply a template?"];n.forEach((p,$)=>{const _=$===0?" (default)":"";c.push(` ${$+1}) ${p.name}${_} \u2014 ${p.description}`)}),c.push(` ${n.length+1}) skip \u2014 no template`);const m=(await e.prompt(c.join(`
|
|
8
8
|
`)+`
|
|
9
9
|
[1]: `)).trim(),y=m===""?1:parseInt(m,10);if(Number.isNaN(y)||y<1||y>n.length+1)return e.stderr(`invalid choice "${m}"
|
|
@@ -11,38 +11,38 @@ import{dirname as K,basename as C}from"node:path";import{randomUUID as oe}from"n
|
|
|
11
11
|
Pass --template <name>, --no-template, or --yes (defaults to starter).
|
|
12
12
|
`),1;t="starter"}e.stderr(i?`Creating cube '${i}'\u2026
|
|
13
13
|
`:`Creating your cube\u2026
|
|
14
|
-
`),u=await e.createCube(a.apiUrl,a.token,t?{name:i??void 0,template:t}:{name:i??void 0}),N=!0}let
|
|
14
|
+
`),u=await e.createCube(a.apiUrl,a.token,t?{name:i??void 0,template:t}:{name:i??void 0}),N=!0}let g;if(r.role!==void 0){if(g=ae(u.roles,r.role),!g){const t=u.roles.map(m=>m.name).join(", "),n=je(r.role,u.roles.map(m=>m.name)),c=n?` Did you mean "${n}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${u.name}". Available: ${t}.${c}
|
|
15
15
|
(Use --template <name> on first-drone setup or run \`borg_create-role\` from inside Claude.)
|
|
16
|
-
`),1}}else{const t=new Set((u.drones??[]).map(n=>n.role_id));if(
|
|
16
|
+
`),1}}else{const t=new Set((u.drones??[]).map(n=>n.role_id));if(g=se(u.roles,{isFirstDrone:N,occupiedRoleIds:t}),!g)return e.stderr(`cube "${u.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
17
|
`),1}const x=await e.getActiveCube();let S;if(x&&r.flags.here)if(x.cubeId===u.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 M=r.flags.worktree!==void 0||x!==null&&!r.flags.here,X=S??x?.droneId??null,P=M?null:await e.getLaunchModel(u.id,o,X),b=r.flags.model??P?.model??
|
|
19
|
-
`),1}const
|
|
20
|
-
`);let
|
|
18
|
+
`),1;const M=r.flags.worktree!==void 0||x!==null&&!r.flags.here,X=S??x?.droneId??null,P=M?null:await e.getLaunchModel(u.id,o,X),b=r.flags.model??P?.model??g.default_model??null,L=Se(process.env,b!=null&&b===P?.model?P?.ollamaBaseUrl:void 0);if(b){const t=await e.checkModelReachable(b,e.fetch,L);if(!t.ok)return e.stderr(`${t.message}
|
|
19
|
+
`),1}const d=await e.resolveCli(r.flags.cli);e.stderr(`Joining cube '${u.name}' as ${g.name}\u2026
|
|
20
|
+
`);let l;try{l=await e.assimilate(a.apiUrl,a.token,{cube_id:u.id,role_id:g.id,hostname:e.getHostname(),agent_kind:d,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
21
|
`),1;const n=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${n}
|
|
22
|
-
`),1}const k=u.roles.find(t=>t.id===
|
|
23
|
-
`):k.id!==
|
|
24
|
-
`);const Q=M;let
|
|
22
|
+
`),1}const k=u.roles.find(t=>t.id===l.role_id)??g;l.reattached?e.stderr(`re-attached to existing seat ${l.drone_label} (session token rotated, no new drone minted)
|
|
23
|
+
`):k.id!==g.id&&e.stderr(`Note: your invite didn't grant the "${g.name}" role \u2014 assimilated as "${k.name}" instead.
|
|
24
|
+
`);const Q=M;let w=null;if(Q){const t=e.runSync("git",["rev-parse","--verify","HEAD"],o);if(t.status!==0)return e.stderr(`sibling worktree spawn requires HEAD pointing at a commit.
|
|
25
25
|
Fix: create at least one commit (\`git commit --allow-empty -m "initial"\`)
|
|
26
26
|
OR: pass --here to skip the sibling spawn and use the current directory
|
|
27
27
|
`),1;e.runSync("git",["fetch","origin"],o);let n="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],o).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],o).status===0&&(n="origin/master");const m=t.stdout.trim(),y=e.runSync("git",["rev-parse",n],o).stdout.trim();m!==y&&e.stderr(`note: local HEAD (${m.slice(0,7)}) differs from ${n} (${y.slice(0,7)}); new worktree will start on ${n}
|
|
28
|
-
`);const p=
|
|
29
|
-
`),1;const _=e.homedir();let
|
|
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}try{const t=Ie(),n=K($e(
|
|
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 ee=e.getInboxPath(
|
|
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.",
|
|
40
|
-
Agent exited. You were working in ${
|
|
28
|
+
`);const p=R(o),$=r.flags.worktree??ie(k.name);if($.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 h=z(_,p,$),v=W(R(h),p),Y=2;for(;e.pathExists(h)||Ue(e,o,h)||J(e.runSync,o,v)&&!ye(e.runSync,o,v,n);)h=z(_,p,$,Y),v=W(R(h),p),Y++;e.mkdirp(K(h));const G=J(e.runSync,o,v)?e.runSync("git",["worktree","add",h,v],o):e.runSync("git",["worktree","add","-b",v,h,n],o);if(G.status!==0)return e.stderr(`git worktree add failed: ${V(G.stderr)}
|
|
30
|
+
`),1;e.stderr(`spawned sibling worktree at ${h} on branch ${v} (${n}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
|
|
31
|
+
`),e.chdir(h),e.stderr(De(h,v,o)),w=e.cwd()}try{await e.setActiveCube({cubeId:l.cube_id,droneId:l.drone_id,name:u.name,sessionToken:l.session_token,droneLabel:l.drone_label,apiUrl:a.apiUrl,roleName:k.name,isHumanSeat:k.is_human_seat,...k.role_class?{roleClass:k.role_class}:{}})}catch(t){const n=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${n}
|
|
32
|
+
`),w){const c=e.runSync("git",["worktree","remove","--force",w],o);c.status===0?e.stderr(`rolled back spawned worktree at ${w}
|
|
33
|
+
`):e.stderr(`manual cleanup needed: \`git worktree remove --force ${w}\` (rollback attempt failed: ${V(c.stderr).trim()||"unknown"})
|
|
34
|
+
`)}return 1}try{const t=Ie(),n=K($e(l.cube_id,l.drone_id));Ee({cubeInboxDir:n,selfDroneId:l.drone_id,deps:{listInboxLogs:_e,isLive:c=>Ae(c,t),droneState:()=>"absent",unlink:c=>Re(c),now:t.now,staleMs:Ne}})}catch{}e.setTerminalTitle(l.drone_label,u.name);const Z=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(me(k.name,u.name,Z));const f=e.cwd();try{e.installProjectSessionHook(f)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${f}; it will be re-attempted on the next borg launch
|
|
35
|
+
`)}if(!w){e.runSync("git",["fetch","origin","--prune"],f);const t=W(R(f),R(o)),n=ke(e.runSync,f,t,"origin/main");n.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
|
|
36
|
+
`),e.stderr(Oe(f,t))):n.message&&e.stderr(`worktree sync: ${n.message}
|
|
37
|
+
`)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${d} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
|
|
38
|
+
`);const ee=e.getInboxPath(l.cube_id,l.drone_id),T=d==="codex"?`borg-wake-${oe()}`:null,te=ge(d,d==="claude"?ee:null);let D,B=[],C,O=null,U=null;const H=e.findProjectRoot(f);b?await e.setLaunchModel(l.cube_id,H,{model:b,ollamaBaseUrl:Ce(b).kind==="ollama"?L:null}):await e.clearLaunchModel(l.cube_id,H);const F=xe(b,L),j={...process.env,...F.set,BORG_SESSION:"1"};for(const t of F.unset)delete j[t];if(d==="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.",B=t.args,Object.keys(t.env).length>0&&Object.assign(j,t.env),O=be(t.args),U=t.server?.cleanup??null}if(C=[he({cli:d,codexWakeNonce:T,monitorClause:te,codexWakePathClause:D})],d==="codex")C=[...pe(),...B,...fe(C,f)];else if(d==="opencode"){const t=Te(l.drone_id);Pe(),Le(t),C=[f,"--port",String(t),"--auto"]}const re=e.exec(d,C,f,j);d==="codex"&&O&&T&&we({deps:e,cubeId:l.cube_id,droneId:l.drone_id,socketPath:O,cwd:f,previewNeedle:T,launchedAtSeconds:Math.floor(Date.now()/1e3)});const ne=await re;if(U)try{U()}catch{}return w&&E!==w&&e.stderr(`
|
|
40
|
+
Agent exited. You were working in ${w}; your shell is back in ${E}.
|
|
41
41
|
To return:
|
|
42
|
-
cd ${de(
|
|
43
|
-
`),ne}function
|
|
42
|
+
cd ${de(w)}
|
|
43
|
+
`),ne}function De(r,e,a){return`
|
|
44
44
|
WORKTREE STEERING: You are in worktree ${r} on branch ${e}. Do ALL work HERE \u2014 cut your feature branch (fix/.../feat/...) off ${e} in THIS worktree, use relative paths / your cwd. NEVER \`git -C ${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
|
|
45
|
+
`}function Oe(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 V(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function
|
|
48
|
-
`).some(i=>i===`worktree ${a}`)}function
|
|
47
|
+
`}function V(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function Ue(r,e,a){const o=r.runSync("git",["worktree","list","--porcelain"],e);return o.status!==0?!1:o.stdout.split(`
|
|
48
|
+
`).some(i=>i===`worktree ${a}`)}function je(r,e){if(e.length===0)return null;const a=r.toLowerCase();let o=null;for(const i of e){const s=We(a,i.toLowerCase());s<=2&&(o===null||s<o.distance)&&(o={name:i,distance:s})}return o?o.name:null}function We(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),o=new Array(e.length+1);for(let i=0;i<=e.length;i++)a[i]=i;for(let i=1;i<=r.length;i++){o[0]=i;for(let s=1;s<=e.length;s++){const E=r[i-1]===e[s-1]?0:1;o[s]=Math.min(o[s-1]+1,a[s]+1,a[s-1]+E)}for(let s=0;s<=e.length;s++)a[s]=o[s]}return a[e.length]}export{lt as runAssimilate,V as safeStderr,je as suggestRoleName};
|
package/dist/bare-launch-menu.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const c={claude:"Claude",codex:"Codex"};function f(n){return n.extraArgs.length===0&&n.stdinIsTTY&&n.stdoutIsTTY}function x(n){return!n.explicitCli||!n.stdinIsTTY||!n.stdoutIsTTY||!n.hasActiveCube||!n.hasLaunchAllTargets?null:`borg --cli ${n.explicitCli} launches ${c[n.explicitCli]} directly; use bare borg for the launch menu or borg launch-all --cli ${n.explicitCli} for all drone worktrees.
|
|
2
|
-
`}function
|
|
1
|
+
const c={claude:"Claude",codex:"Codex",opencode:"OpenCode"};function f(n){return n.extraArgs.length===0&&n.stdinIsTTY&&n.stdoutIsTTY}function x(n){return!n.explicitCli||!n.stdinIsTTY||!n.stdoutIsTTY||!n.hasActiveCube||!n.hasLaunchAllTargets?null:`borg --cli ${n.explicitCli} launches ${c[n.explicitCli]} directly; use bare borg for the launch menu or borg launch-all --cli ${n.explicitCli} for all drone worktrees.
|
|
2
|
+
`}function p(n){return!!(n.explicitCli&&n.stdinIsTTY&&n.stdoutIsTTY&&n.hasActiveCube)}function h(n){const t=[{key:"1",label:`Launch (default \xB7 ${c[n.defaultCli]})`,action:{kind:"launch",cli:n.defaultCli}}];return n.otherInstalledCli&&t.push({key:String(t.length+1),label:`Launch with ${c[n.otherInstalledCli]} instead (one-shot)`,action:{kind:"launch",cli:n.otherInstalledCli}}),n.hasLaunchAllTargets&&t.push({key:String(t.length+1),label:"Launch all (this cube's drone worktrees)",action:{kind:"launch-all"}}),t}function s(n,t){const e=t.trim();if(e==="")return{ok:!0,action:n[0].action};const i=n.find(l=>l.key===e);return i?{ok:!0,action:i.action}:{ok:!1}}function d(n){return`borg \u2014 how do you want to launch?
|
|
3
3
|
${n.map(e=>` ${e.key}) ${e.label}`).join(`
|
|
4
4
|
`)}
|
|
5
|
-
[1]: `}async function
|
|
6
|
-
${u}`),r=s(i,a);if(r.ok)return r.action;e.warn?.(`invalid launch-menu selection: ${JSON.stringify(a.trim())}`)}return i[0].action}export{h as buildLaunchMenuOptions,x as explicitCliLaunchHint,d as renderLaunchMenu,s as resolveLaunchMenuChoice,
|
|
5
|
+
[1]: `}async function C(n,t,e={}){const i=h(n);if(i.length===1)return i[0].action;const l=e.maxAttempts??3,u=d(i);for(let o=0;o<l;o++){const a=await t(o===0?u:`Invalid choice.
|
|
6
|
+
${u}`),r=s(i,a);if(r.ok)return r.action;e.warn?.(`invalid launch-menu selection: ${JSON.stringify(a.trim())}`)}return i[0].action}export{h as buildLaunchMenuOptions,x as explicitCliLaunchHint,d as renderLaunchMenu,s as resolveLaunchMenuChoice,C as runBareLaunchMenu,p as shouldResolveExplicitCliLaunchHintTargets,f as shouldShowLaunchMenu};
|
package/dist/claude.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
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 r=
|
|
5
|
-
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await
|
|
6
|
-
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await
|
|
7
|
-
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=
|
|
8
|
-
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1)),
|
|
9
|
-
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const $=async e=>{const r=
|
|
10
|
-
\u25FC Failed to launch ${
|
|
11
|
-
`)}`)
|
|
12
|
-
\u25FC Failed to launch ${
|
|
13
|
-
`)}`)
|
|
14
|
-
\u25FC Error: ${
|
|
2
|
+
import{spawn as B}from"child_process";import{randomUUID as F}from"node:crypto";import{basename as _}from"node:path";import{createInterface as W}from"node:readline/promises";import i from"chalk";import{findProjectRoot as I,getActiveCube as G,getLaunchModel as Y,inboxPathForDrone as U,setCodexWakeTarget as V,pruneDeadCodexWakeTargets as K}from"./cubes.js";import{applyOllamaLaunchEnv as j,checkModelReachable as q}from"./model-presets.js";import{handleVersionFlag as X,getPackageVersion as h}from"./version.js";import{isHelpFlag as L,setupHelpText as z,topLevelHelpText as J,assimilateHelpText as Q}from"./cli-help.js";import{runSpawn as Z}from"./spawn.js";import{buildClaudeLaunchArgs as ee}from"./claude-launch-args.js";import{parseSyncArgs as re,runSync as oe}from"./sync.js";import{parseCleanupArgs as se,runCleanup as te}from"./cleanup-cmd.js";import{parseAssimilateArgs as ie}from"./parse-assimilate-args.js";import{runAssimilate as ae}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as ne}from"./assimilate-deps.js";import{parseLaunchAllArgs as R}from"./parse-launch-all-args.js";import{unknownSubcommand as ce}from"./unknown-subcommand.js";import{runLaunchAll as O}from"./launch-all-cmd.js";import{buildDefaultLaunchAllDeps as k}from"./launch-all-deps.js";import{discoverDroneCandidates as le}from"./launch-all-discovery.js";import{explicitCliLaunchHint as de,runBareLaunchMenu as pe,shouldResolveExplicitCliLaunchHintTargets as ue,shouldShowLaunchMenu as me}from"./bare-launch-menu.js";import{setTerminalTitle as fe}from"./terminal-title.js";import{initConsolePrefix as ge,consolePrefix as t}from"./console-prefix.js";import{initDebugFromArgv as he}from"./debug.js";import{fetchLatestBorgmcpVersion as we,compareVersionsForStaleness as Ce}from"./stale-version-check.js";import{defaultCliChoiceDeps as xe,detectCliAvailability as b,installedCliNames as D,parseCliFlag as ve,resolveCliChoice as ke}from"./cli-platform.js";import{getRefreshToken as be,getIdToken as $e}from"./config.js";import{composeGetStarted as Se,shouldShowGetStarted as ye}from"./get-started.js";import{prepareCodexRemoteLaunch as Ae,withCodexCwdArg as Te,defaultCodexRemoteDeps as Pe,checkCodexBridgeHealthy as Ie}from"./codex-remote.js";import{findLoadedCodexThread as Le}from"./codex-app-server.js";import{buildAgentKickoffPrompt as Re,buildKickoffWakePathClause as Oe,recordCodexWakeTarget as De,socketPathFromRemoteArgs as E}from"./codex-launch.js";import{codexBorgSessionConfigArgs as Ee}from"./launch-gate.js";import{addCodexMcpServer as Me,addCodexSessionStartHook as He,addCodexUserPromptSubmitHook as Ne,addMcpServer as Be,addOpenCodeMcpServer as Fe,addProjectSessionStartHook as _e,addUserPromptSubmitHook as We,ensureOpenCodeServerEnvConfig as Ge,isCodexMcpServerConfigured as Ye,isMcpServerConfigured as Ue,isOpenCodeMcpServerConfigured as Ve,removeSessionStartHook as Ke}from"./config-utils.js";import{installBorgPlugin as je}from"./opencode-plugin.js";import{computeOpenCodePort as qe}from"./opencode-drone.js";async function Xe(){he(process.argv),X(),await ge();const c=(async()=>{if(!process.stderr.isTTY)return;const e=h(),r=await we();if(!r)return;const a=Ce(e,r);a.stale&&a.message&&process.stderr.write(`${t()}${a.message}
|
|
3
|
+
`)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(J(h())),process.exit(0)),process.argv[2]==="setup"){L(process.argv[3])&&(process.stdout.write(z(h())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){process.argv.slice(3).some(L)&&(process.stdout.write(Q(h())),process.exit(0));const e=ie(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg assimilate: ${e.error}
|
|
4
|
+
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=ne(),a=await ae({role:e.role,flags:e.flags},r);process.exit(a)}if(process.argv[2]==="spawn"){const e=await Z();process.exit(e)}if(process.argv[2]==="sync"){const e=re(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg sync: ${e.error}
|
|
5
|
+
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await oe({},e.options);process.exit(r)}if(process.argv[2]==="cleanup"){const e=se(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg cleanup: ${e.error}
|
|
6
|
+
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=await te({},e.options);process.exit(r)}if(process.argv[2]==="launch-all"){const e=R(process.argv.slice(3));e.ok||(process.stderr.write(i.red(`${t()}\u25FC borg launch-all: ${e.error}
|
|
7
|
+
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const r=k(),a=await O(e.args,r);process.exit(a)}const n=ce(process.argv[2]);if(n!==null&&(process.stderr.write(i.red(`${t()}\u25FC unknown command: ${n}
|
|
8
|
+
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1)),ye(await be()!==null,await $e()!==null)){const e=D(b()).length>0;process.stdout.write(Se(e)),process.exit(0)}const d=ve(process.argv.slice(2));d.error&&(process.stderr.write(i.red(`${t()}\u25FC ${d.error}
|
|
9
|
+
`)),process.stderr.write("Run `borg --help` for usage.\n"),process.exit(1));const $=async e=>{const r=W({input:process.stdin,output:process.stdout});try{return await r.question(e)}finally{r.close()}};let o=await ke(d.cli,xe($,()=>process.stdin.isTTY===!0));ze();const s=await G();let w;const S=async()=>s?(w===void 0&&(w=(await le({targetCubeId:s.cubeId},k())).length>0),w):!1,C=process.stdin.isTTY===!0,x=process.stdout.isTTY===!0,y=de({explicitCli:d.cli,stdinIsTTY:C,stdoutIsTTY:x,hasActiveCube:s!==null,hasLaunchAllTargets:ue({explicitCli:d.cli,stdinIsTTY:C,stdoutIsTTY:x,hasActiveCube:s!==null})?await S():!1});if(y&&process.stderr.write(y),me({extraArgs:process.argv.slice(2),stdinIsTTY:C,stdoutIsTTY:x})){const e=D(b()).find(a=>a!==o)??null,r=await pe({defaultCli:o,otherInstalledCli:e,hasLaunchAllTargets:await S()},$);if(r.kind==="launch-all"){const a=R([]),N=a.ok?await O(a.args,k()):1;process.exit(N)}o=r.cli}const l=d.rest;fe(s?{label:s.droneLabel,cubeName:s.name}:null,_(process.cwd()));const M=Oe(o,s&&o==="claude"?U(s.cubeId,s.droneId):null);await Promise.race([c,new Promise(e=>setTimeout(e,2e3))]);const A=o==="codex"?`borg-wake-${F()}`:null;let f,T=[],p={...process.env,BORG_SESSION:"1"},u=null,m=null;if(o==="codex"&&!l.includes("--remote")){console.error(`${t()}${i.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await Ae(Pe());e.warning?(console.error(`${t()}${i.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.",T=e.args,p={...process.env,...e.env,BORG_SESSION:"1"},u=E(e.args),m=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.",u=E(l),u&&(p={...process.env,BORG_CODEX_REMOTE_WAKE:"1",BORG_SESSION:"1"}));if(s){const e=await Y(s.cubeId,I(),s.droneId),r=j(p,e,process.env);if(p=r.env,r.probe){const a=await q(r.probe.descriptor,fetch,r.probe.baseUrl);a.ok||console.error(`${t()}${i.yellow(`warning: ${a.message}`)}`)}}const v=Re({cli:o,codexWakeNonce:A,monitorClause:M,codexWakePathClause:f});let g;if(o==="codex")g=[...Ee(),...T,...Te([...l,v],process.cwd())];else if(o==="opencode"){const e=s?qe(s.droneId):14096;je(),Ge(e),g=[process.cwd(),"--port",String(e),"--auto",...l]}else g=ee(l,v);const H=o==="claude"?"Claude Code":o==="codex"?"Codex":"OpenCode";console.error(`${t()}${i.blue(`\u25FC Launching ${H}\u2026`)}`);const P=B(o,g,{stdio:"inherit",shell:!1,env:p});o==="codex"&&s&&u&&(De({deps:{setCodexWakeTarget:V,findLoadedCodexThread:Le},cubeId:s.cubeId,droneId:s.droneId,socketPath:u,passthroughArgs:l,previewNeedle:A??v.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),K(e=>Ie(e))),P.on("error",e=>{if(m)try{m()}catch{}if(e.code==="ENOENT"){console.error(`${t()}${i.red(`
|
|
10
|
+
\u25FC Failed to launch ${o}`)}`);const r=o==="opencode"?"opencode":o;console.error(`${t()}${i.gray(`Make sure ${r} is installed.
|
|
11
|
+
`)}`)}else console.error(`${t()}${i.red(`
|
|
12
|
+
\u25FC Failed to launch ${o}: ${e.message}
|
|
13
|
+
`)}`);process.exit(1)}),P.on("exit",e=>{if(m)try{m()}catch{}process.exit(e??0)})}function ze(){const c=b();if(c.claude)try{Ue()||Be(),_e(I(process.cwd())),Ke(),We()}catch(n){console.error(`${t()}${i.yellow(`warning: Claude Code integration check failed: ${n?.message??n}`)}`)}if(c.codex)try{Ye()||Me(),He(),Ne()}catch(n){console.error(`${t()}${i.yellow(`warning: Codex integration check failed: ${n?.message??n}`)}`)}if(c.opencode)try{Ve()||Fe()}catch(n){console.error(`${t()}${i.yellow(`warning: OpenCode integration check failed: ${n?.message??n}`)}`)}}Xe().catch(c=>{console.error(`${t()}${i.red(`
|
|
14
|
+
\u25FC Error: ${c.message}
|
|
15
15
|
`)}`),process.exit(1)});
|
package/dist/cli-platform.d.ts
CHANGED
package/dist/cli-platform.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
[1]: `)).trim()
|
|
1
|
+
import C from"which";import{getProjectCliPreference as p,setProjectCliPreference as w}from"./cubes.js";function m(){return{claude:f("claude"),codex:f("codex"),opencode:f("opencode")}}function f(r){try{return C.sync(r)}catch{return null}}function g(r){const e=[];return r.claude&&e.push("claude"),r.codex&&e.push("codex"),r.opencode&&e.push("opencode"),e}async function a(r,e){return await e.setPreference(r),r}async function I(r,e){const c=e.detectCli(),n=g(c);if(n.length===0)throw new Error("No supported agent CLI found (claude, codex, opencode). Install one of them, then run borg again.");if(r){if(!n.includes(r))throw new Error(`${r} CLI is not installed.`);return await e.setPreference(r),r}const t=await e.getPreference();if(t&&n.includes(t))return t;if(n.length===1)return await e.setPreference(n[0]),n[0];if(!e.isTTY())throw new Error("Multiple agent CLIs detected. Pass --cli claude, --cli codex, or --cli opencode to choose.");const o=n.map((s,h)=>` ${h+1}) ${s}`),i=(await e.prompt(`Use which CLI for this project?
|
|
2
|
+
${o.join(`
|
|
3
|
+
`)}
|
|
4
|
+
[1]: `)).trim();if(i===""||i==="1")return a(n[0],e);const l=parseInt(i,10);if(!Number.isNaN(l)&&l>=1&&l<=n.length)return a(n[l-1],e);const d=i.toLowerCase();for(const s of n)if(d===s)return a(s,e);throw new Error(`invalid CLI choice "${i}"`)}function L(r,e){return{detectCli:m,getPreference:p,setPreference:w,prompt:r,isTTY:e}}const u=["claude","codex","opencode"];function $(r){const e=[];let c;for(let n=0;n<r.length;n++){const t=r[n];if(t==="--cli"){const o=r[n+1];if(!o||!u.includes(o))return{rest:e,error:`--cli requires one of: ${u.join(", ")}`};c=o,n+=1}else if(t.startsWith("--cli=")){const o=t.slice(6);if(!u.includes(o))return{rest:e,error:`--cli requires one of: ${u.join(", ")}`};c=o}else e.push(t)}return{cli:c,rest:e}}export{L as defaultCliChoiceDeps,m as detectCliAvailability,g as installedCliNames,$ as parseCliFlag,I as resolveCliChoice};
|
package/dist/codex-app-wake.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare const CODEX_WAKE_PROMPT = "New Borg cube-log activity arrived.";
|
|
|
5
5
|
export declare function formatCodexWakePrompt(inboxLine: string): string;
|
|
6
6
|
export declare const CODEX_CATCHUP_PROMPT = "Borg cube activity arrived while you were busy. Wake triage: 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. Then handle actionable entries; if none, resume the prior interrupted work.";
|
|
7
7
|
export declare function isCodexRemoteWakeEnabled(env?: NodeJS.ProcessEnv): boolean;
|
|
8
|
-
export declare function resolveSessionAgentKind(env?: NodeJS.ProcessEnv): 'claude' | 'codex';
|
|
8
|
+
export declare function resolveSessionAgentKind(env?: NodeJS.ProcessEnv): 'claude' | 'codex' | 'opencode';
|
|
9
9
|
export interface CodexWakeTarget {
|
|
10
10
|
enabled: boolean;
|
|
11
11
|
}
|
|
@@ -80,7 +80,7 @@ export declare function fireCodexHeartbeatTick(deps?: CodexWakeDeps, cadenceMs?:
|
|
|
80
80
|
* not a codex session. Injectable agentKind/intervalMs/tick for tests.
|
|
81
81
|
*/
|
|
82
82
|
export declare function startCodexHeartbeat(opts?: {
|
|
83
|
-
agentKind?: 'claude' | 'codex';
|
|
83
|
+
agentKind?: 'claude' | 'codex' | 'opencode';
|
|
84
84
|
intervalMs?: number;
|
|
85
85
|
tick?: () => void;
|
|
86
86
|
}): ReturnType<typeof setInterval> | null;
|
package/dist/codex-app-wake.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{getActiveCube as C,getCodexWakeTarget as
|
|
2
|
-
${e}`}const D="Borg cube activity arrived while you were busy. Wake triage: 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. Then handle actionable entries; if none, resume the prior interrupted work.";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??
|
|
1
|
+
import{getActiveCube as C,getCodexWakeTarget as p,setCodexWakeTarget as R}from"./cubes.js";import{CodexAppServerClient as O}from"./codex-app-server.js";import{checkCodexBridgeHealthy as S}from"./codex-remote.js";import{recordEventReceipt as W}from"./health-beat.js";import{codexAppServerSocketFromEnv as K,pickFreshThread as B,wakeTargetChanged as M,wakeRetryBackoffMs as F,wakeRetryExpired as N,WAKE_RETRY_MAX_ATTEMPTS as H,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. Wake triage: 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. Then handle actionable entries; if none, resume the prior interrupted work.";function P(e=process.env){return e.BORG_CODEX_REMOTE_WAKE==="1"}function X(e=process.env){return P(e)?"codex":e.BORG_OPENCODE==="1"?"opencode":"claude"}function q(e=process.env){return P(e)?{enabled:!0}:{enabled:!1}}async function ce(e,t={}){try{const r=await(t.getCodexWakeTarget??p)(e.cubeId,e.droneId);return r?(t.checkBridge??S)(r.socketPath):!1}catch{return null}}let f=!1;const h=[],d=new Set,w=[],$=100;let g=!1,k=null;function se(){return k}function m(e){k=(e.now??Date.now)()}let y=!1,v=!1;function T(){return v?!1:(v=!0,!0)}function A(){v=!1}function G(e){return e?.code==="ENOENT"}function V(e){return new Promise(t=>setTimeout(t,e))}function x(e,t){return t.createClient?t.createClient(e):new O(e)}async function E(e,t){const n=K(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=B(i,{cwd:(t.cwd??(()=>process.cwd()))()});return c?(await Y(e,{socketPath:n,threadId:c},t),{socketPath:n,threadId:c}):null}finally{a.close()}}const r=await(t.getCodexWakeTarget??p)(e.cubeId,e.droneId);return r?{socketPath:r.socketPath,threadId:r.threadId}:null}async function Y(e,t,n){try{const r=n.getCodexWakeTarget??p,a=n.setCodexWakeTarget??R,o=await r(e.cubeId,e.droneId),i=o?{socketPath:o.socketPath,threadId:o.threadId}:null;M(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(!T()){b(t);return}try{const n=await(t.getActiveCube??C)();if(!n)return;const r=await E(n,t);if(!r)return;const{socketPath:a,threadId:o}=r,i=`${o}\0${e}`;if(d.has(i))return;const c=x(a,t);await c.connect();try{if((await c.readThread(o))?.status?.type==="active"){b(t);return}await c.startTurn(o,e),W(),Z(i),m(t)}finally{c.close()}}catch{b(t)}finally{A()}}function b(e){g||(g=!0,z(e).finally(()=>{g=!1}))}async function z(e){const t=e.sleep??V,n=e.now??Date.now,r=e.jitter??(()=>Math.random()*500),a=e.maxAttempts??H,o=n();let i=0;for(;!N(o,n())&&i<a;)if(await t(F(i,r())),i++,!!T())try{const c=await(e.getActiveCube??C)();if(!c)continue;const u=await E(c,e);if(!u)continue;const{socketPath:s,threadId:I}=u,l=x(s,e);await l.connect();try{if((await l.readThread(I))?.status?.type==="active")continue;await l.startTurn(I,D),W(),m(e);return}finally{l.close()}}catch{}finally{A()}}const _=20*6e4;async function J(e={},t=_){if(y)return;const n=(e.now??Date.now)();if(j(k,n,t)&&!(e.isStreamOwner&&!e.isStreamOwner())&&T()){y=!0;try{const r=await(e.getActiveCube??C)();if(!r)return;const a=await E(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),m(e)}finally{o.close()}}catch(r){G(r)&&e.onAppServerSocketDead?.()}finally{y=!1,A()}}}function de(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 le(){f=!1,h.length=0,d.clear(),w.length=0,g=!1,k=null,y=!1,v=!1}function Z(e){if(!d.has(e))for(d.add(e),w.push(e);w.length>$;){const t=w.shift();t&&d.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,le as resetCodexWakeForTests,q as resolveCodexWakeTarget,X as resolveSessionAgentKind,de as startCodexHeartbeat,ue as wakeCodexViaAppServer};
|
package/dist/codex-launch.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{wakePathArming as
|
|
2
|
-
Keep this Monitor armed for the whole session \u2014 NEVER TaskStop it (the generic /loop "TaskStop any Monitor you armed" step does NOT apply to the cube inbox Monitor; it targets throwaway loop-scratch watches only). The sole exception is a confirmed terminal eviction (410 DRONE_EVICTED).
|
|
1
|
+
import{wakePathArming as c}from"./regen-format.js";function i(e,t){return e==="claude"&&t?c("claude",t)+`
|
|
2
|
+
Keep this Monitor armed for the whole session \u2014 NEVER TaskStop it (the generic /loop "TaskStop any Monitor you armed" step does NOT apply to the cube inbox Monitor; it targets throwaway loop-scratch watches only). The sole exception is a confirmed terminal eviction (410 DRONE_EVICTED). `:""}function d(e){const t=e.codexWakeNonce?`Wake target nonce: ${e.codexWakeNonce}. `:"",r=e.codexWakePathClause??"Codex Borg wakeups use remote-control when available; if no wake arrives, run borg_regen manually when returning to the session.",a="OpenCode wakes: the inbox Monitor is not available yet for OpenCode; check activity by calling borg_read-log periodically.",o=e.cli==="claude"?"/loop ":"",n=e.cli==="claude"?e.monitorClause:e.cli==="codex"?r:a;return`${o}Call borg_regen and follow the playbook in its response. `+t+'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. '+n}function u(e){const t=e.indexOf("--remote");if(t<0)return null;const r=e[t+1];return r?.startsWith("unix://")?r.slice(7):null}function l(e){if(e[0]==="resume"&&e[1]&&!e[1].startsWith("-"))return e[1];const t=e.indexOf("--resume");return t>=0&&e[t+1]?e[t+1]:null}async function h(e){try{const t=e.passthroughArgs?l(e.passthroughArgs):null;if(t){await e.deps.setCodexWakeTarget(e.cubeId,e.droneId,{threadId:t,socketPath:e.socketPath});return}const r=Date.now()+15e3;for(;Date.now()<r;){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(o=>setTimeout(o,500))}}catch{}}export{d as buildAgentKickoffPrompt,i as buildKickoffWakePathClause,h as recordCodexWakeTarget,u as socketPathFromRemoteArgs,l as threadIdFromPassthroughArgs};
|
package/dist/config-utils.d.ts
CHANGED
|
@@ -109,4 +109,27 @@ export declare function isCodexSessionStartHookRegistered(hooksPath?: string): b
|
|
|
109
109
|
* registered. Non-mutating mirror of addCodexUserPromptSubmitHook.
|
|
110
110
|
*/
|
|
111
111
|
export declare function isCodexUserPromptSubmitHookRegistered(hooksPath?: string): boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Detect whether the borg MCP server is already registered in the opencode
|
|
114
|
+
* config (`~/.config/opencode/opencode.json` `mcp.borg`).
|
|
115
|
+
*
|
|
116
|
+
* Reads the config as JSON and checks for a `mcp.borg` entry with
|
|
117
|
+
* `type: "local"`. Safe-default: any read error returns `false`.
|
|
118
|
+
*/
|
|
119
|
+
export declare function isOpenCodeMcpServerConfigured(configPath?: string): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Add borg MCP server to OpenCode using `opencode mcp add` CLI.
|
|
122
|
+
* Pins BORG_SESSION=1, BORG_OPENCODE=1, and BORG_API_URL in the server
|
|
123
|
+
* environment so the MCP child inherits the activation gate + agent-kind
|
|
124
|
+
* signal (same approach as codex's pinned env — opencode MCP children
|
|
125
|
+
* only see pinned env, not parent process env).
|
|
126
|
+
*/
|
|
127
|
+
export declare function addOpenCodeMcpServer(): void;
|
|
128
|
+
/**
|
|
129
|
+
* Ensure the opencode MCP server config includes OPENCODE_SERVER_URL env var
|
|
130
|
+
* pointing at the local opencode HTTP server. Idempotent: updates in-place.
|
|
131
|
+
* Best-effort (failure is non-fatal — the MCP child discovers the server via
|
|
132
|
+
* port scan as a fallback).
|
|
133
|
+
*/
|
|
134
|
+
export declare function ensureOpenCodeServerEnvConfig(port: number): void;
|
|
112
135
|
//# sourceMappingURL=config-utils.d.ts.map
|
package/dist/config-utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import{execSync as
|
|
2
|
-
`,"utf-8")}function
|
|
3
|
-
`,"utf-8")}function
|
|
1
|
+
import{execSync as d}from"child_process";import n from"fs";import f from"os";import i from"path";import{fileURLToPath as v}from"url";import{dirname as R}from"path";const E=v(import.meta.url),U=R(E),a="borg-regen",u="borg-log-audit",j=i.join(f.homedir(),".claude.json"),H=i.join(f.homedir(),".codex","config.toml"),m=i.join(f.homedir(),".codex","hooks.json"),h=i.join(f.homedir(),".config","opencode","opencode.json"),M="borg";function S(){return i.join(f.homedir(),".claude","settings.json")}function p(){const e=S();if(!n.existsSync(e))return{};const o=n.readFileSync(e,"utf-8");return o.trim()?JSON.parse(o):{}}function g(e){const o=S();n.mkdirSync(i.dirname(o),{recursive:!0}),n.writeFileSync(o,JSON.stringify(e,null,2)+`
|
|
2
|
+
`,"utf-8")}function k(e){if(!n.existsSync(e))return{};const o=n.readFileSync(e,"utf-8");return o.trim()?JSON.parse(o):{}}function O(e,o){n.mkdirSync(i.dirname(e),{recursive:!0}),n.writeFileSync(e,JSON.stringify(o,null,2)+`
|
|
3
|
+
`,"utf-8")}function $(){return P(S())}function D(e){return P(x(e))}function J(e){return w(x(e))}function x(e){return i.join(e,".claude","settings.local.json")}function P(e){let o;try{o=k(e)}catch(t){return console.error(`\u26A0 Could not parse ${e}: ${t.message}. Skipping hook registration; you can add it manually.`),!1}return o.hooks??={},o.hooks.SessionStart??=[],o.hooks.SessionStart.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===a))?!1:(o.hooks.SessionStart.push({matcher:"*",hooks:[{type:"command",command:a}]}),O(e,o),!0)}function w(e){let o;try{o=k(e)}catch{return!1}const r=o?.hooks?.SessionStart;return Array.isArray(r)?r.some(t=>Array.isArray(t?.hooks)&&t.hooks.some(s=>s?.type==="command"&&s?.command===a)):!1}function T(){let e;try{e=p()}catch{return!1}const o=e?.hooks?.SessionStart;return Array.isArray(o)?o.some(r=>Array.isArray(r?.hooks)&&r.hooks.some(t=>t?.type==="command"&&t?.command===a)):!1}function K(){let e;try{e=p()}catch{return!1}const o=e?.hooks?.UserPromptSubmit;return Array.isArray(o)?o.some(r=>Array.isArray(r?.hooks)&&r.hooks.some(t=>t?.type==="command"&&t?.command===u)):!1}function X(){let e;try{e=p()}catch{return!1}if(!e?.hooks?.SessionStart)return!1;let o=!1;return e.hooks.SessionStart=e.hooks.SessionStart.map(r=>{if(!Array.isArray(r?.hooks))return r;const t=r.hooks.filter(s=>!(s?.type==="command"&&s?.command===a));return t.length!==r.hooks.length?(o=!0,{...r,hooks:t}):r}).filter(r=>Array.isArray(r?.hooks)&&r.hooks.length>0),e.hooks.SessionStart.length===0&&delete e.hooks.SessionStart,Object.keys(e.hooks).length===0&&delete e.hooks,o&&g(e),o}function W(){let e;try{e=p()}catch(r){return console.error(`\u26A0 Could not parse ${S()}: ${r.message}. Skipping audit hook registration.`),!1}return e.hooks??={},e.hooks.UserPromptSubmit??=[],e.hooks.UserPromptSubmit.some(r=>Array.isArray(r?.hooks)&&r.hooks.some(t=>t?.type==="command"&&t?.command===u))?!1:(e.hooks.UserPromptSubmit.push({matcher:"*",hooks:[{type:"command",command:u}]}),g(e),!0)}function V(){let e;try{e=p()}catch{return!1}if(!e?.hooks?.UserPromptSubmit)return!1;let o=!1;return e.hooks.UserPromptSubmit=e.hooks.UserPromptSubmit.map(r=>{if(!Array.isArray(r?.hooks))return r;const t=r.hooks.filter(s=>!(s?.type==="command"&&s?.command===u));return t.length!==r.hooks.length?(o=!0,{...r,hooks:t}):r}).filter(r=>Array.isArray(r?.hooks)&&r.hooks.length>0),e.hooks.UserPromptSubmit.length===0&&delete e.hooks.UserPromptSubmit,Object.keys(e.hooks).length===0&&delete e.hooks,o&&g(e),o}function Q(e=j){try{if(!n.existsSync(e))return!1;const o=n.readFileSync(e,"utf-8");if(!o.trim())return!1;const r=JSON.parse(o);if(!r||typeof r!="object")return!1;const t=r.mcpServers;return!t||typeof t!="object"||Array.isArray(t)?!1:M in t}catch{return!1}}function q(e=H){try{if(!n.existsSync(e))return!1;const o=n.readFileSync(e,"utf-8");return/^\s*\[mcp_servers\.borg\]\s*$/m.test(o)&&/^\s*BORG_CODEX_REMOTE_WAKE\s*=\s*"1"\s*$/m.test(o)}catch{return!1}}function z(){return i.join(U,"index.js")}function Y(){try{try{d("claude mcp remove --scope user borg",{stdio:"ignore"})}catch{}d("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(e){throw e.message?.includes("command not found")?new Error("Claude CLI not found. Please install Claude Code first."):new Error(`Failed to add MCP server: ${e.message}`)}}function Z(){try{try{d("codex mcp remove borg",{stdio:"ignore"})}catch{}d("codex mcp add borg --env BORG_API_URL="+_(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(e){throw e.message?.includes("command not found")?new Error("Codex CLI not found. Please install Codex first."):new Error(`Failed to add MCP server to Codex: ${e.message}`)}}function _(e){return`'${e.replace(/'/g,"'\\''")}'`}function C(e,o,r={}){let t;try{t=k(m)}catch(l){return console.error(`\u26A0 Could not parse ${m}: ${l.message}. Skipping Codex hook registration.`),!1}t.hooks??={},t.hooks[e]??=[];const s=t.hooks[e];if(!Array.isArray(s)||s.some(l=>Array.isArray(l?.hooks)&&l.hooks.some(A=>A?.type==="command"&&A?.command===o)))return!1;const c={hooks:[{type:"command",command:o}]};return r.matcher&&(c.matcher=r.matcher),typeof r.timeout=="number"&&(c.hooks[0].timeout=r.timeout),s.push(c),O(m,t),!0}function ee(){return C("SessionStart",a,{matcher:"startup|resume",timeout:30})}function re(){return C("UserPromptSubmit",u,{timeout:10})}function b(e,o,r=m){try{const s=k(r)?.hooks?.[e];return Array.isArray(s)?s.some(y=>Array.isArray(y?.hooks)&&y.hooks.some(c=>c?.type==="command"&&c?.command===o)):!1}catch{return!1}}function oe(e=m){return b("SessionStart",a,e)}function te(e=m){return b("UserPromptSubmit",u,e)}function se(e=h){try{if(!n.existsSync(e))return!1;const o=n.readFileSync(e,"utf-8");if(!o.trim())return!1;const r=JSON.parse(o);if(!r||typeof r!="object")return!1;const t=r.mcp?.borg;return!t||typeof t!="object"?!1:t.type==="local"}catch{return!1}}function ne(){try{const e=process.env.BORG_API_URL||"https://api.borgmcp.ai";d(`opencode mcp add borg --env BORG_SESSION=1 --env BORG_OPENCODE=1 --env BORG_API_URL=${_(e)} -- borg-mcp`,{stdio:"inherit"})}catch(e){throw e.message?.includes("command not found")?new Error("opencode CLI not found. Please install opencode first."):new Error(`Failed to add MCP server to opencode: ${e.message}`)}}function ie(e){try{if(!n.existsSync(h))return;const o=n.readFileSync(h,"utf-8");if(!o.trim())return;const r=JSON.parse(o);if(!r||typeof r!="object")return;const t=r.mcp?.borg;if(!t||typeof t!="object")return;t.env=t.env||{},t.env.OPENCODE_SERVER_URL=`http://127.0.0.1:${e}`,n.writeFileSync(h,JSON.stringify(r,null,2)+`
|
|
4
|
+
`,"utf-8")}catch{}}export{Z as addCodexMcpServer,ee as addCodexSessionStartHook,re as addCodexUserPromptSubmitHook,Y as addMcpServer,ne as addOpenCodeMcpServer,D as addProjectSessionStartHook,$ as addSessionStartHook,W as addUserPromptSubmitHook,ie as ensureOpenCodeServerEnvConfig,z as getBinaryPath,b as isCodexHookRegistered,q as isCodexMcpServerConfigured,oe as isCodexSessionStartHookRegistered,te as isCodexUserPromptSubmitHookRegistered,Q as isMcpServerConfigured,se as isOpenCodeMcpServerConfigured,J as isProjectSessionStartHookRegistered,T as isSessionStartHookRegistered,K as isUserPromptSubmitHookRegistered,X as removeSessionStartHook,V as removeUserPromptSubmitHook};
|
package/dist/cubes.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* to know which worker to talk to.
|
|
17
17
|
*/
|
|
18
18
|
import { writeFile, unlink, rename } from 'node:fs/promises';
|
|
19
|
-
export type BorgCli = 'claude' | 'codex';
|
|
19
|
+
export type BorgCli = 'claude' | 'codex' | 'opencode';
|
|
20
20
|
export interface ActiveCube {
|
|
21
21
|
cubeId: string;
|
|
22
22
|
droneId: string;
|
package/dist/health-beat.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ export interface HealthPayload {
|
|
|
48
48
|
* frozen at first-join. Beating it self-heals the column via recordDroneHealth
|
|
49
49
|
* COALESCE — same always-running-child self-heal pattern as wake_armed.
|
|
50
50
|
*/
|
|
51
|
-
agent_kind: 'claude' | 'codex';
|
|
51
|
+
agent_kind: 'claude' | 'codex' | 'opencode';
|
|
52
52
|
/**
|
|
53
53
|
* gh#408: live runtime hostname. Assimilate captures hostname only at first
|
|
54
54
|
* join; the beat carries the relaunched process's current machine name so
|
|
@@ -86,7 +86,7 @@ export declare function __resetHealthBeatStateForTest(): void;
|
|
|
86
86
|
* the design's false-deaf-avoidance posture — an undeterminable probe must not
|
|
87
87
|
* masquerade as a dead Monitor.
|
|
88
88
|
*/
|
|
89
|
-
export declare function buildHealthPayload(sseConnected: boolean, inboxMonitorHealthy: boolean | null, wakeArmed: boolean | null, agentKind: 'claude' | 'codex', hostname: string | null, version: string): HealthPayload;
|
|
89
|
+
export declare function buildHealthPayload(sseConnected: boolean, inboxMonitorHealthy: boolean | null, wakeArmed: boolean | null, agentKind: 'claude' | 'codex' | 'opencode', hostname: string | null, version: string): HealthPayload;
|
|
90
90
|
export interface BeatTransport {
|
|
91
91
|
fetchImpl: typeof fetch;
|
|
92
92
|
getToken: () => Promise<string>;
|
|
@@ -102,7 +102,7 @@ export declare function emitHealthBeat(active: HealthBeatActive, opts: {
|
|
|
102
102
|
sseConnected: boolean;
|
|
103
103
|
inboxMonitorHealthy: boolean | null;
|
|
104
104
|
wakeArmed: boolean | null;
|
|
105
|
-
agentKind: 'claude' | 'codex';
|
|
105
|
+
agentKind: 'claude' | 'codex' | 'opencode';
|
|
106
106
|
hostname: string | null;
|
|
107
107
|
version: string;
|
|
108
108
|
} & BeatTransport): Promise<void>;
|
|
@@ -113,7 +113,8 @@ export interface HealthBeatTickDeps extends BeatTransport {
|
|
|
113
113
|
checkMonitor: (inboxPath: string | null) => boolean | null;
|
|
114
114
|
isCodexRemoteWake: () => boolean;
|
|
115
115
|
probeBridgeArmed: (active: HealthBeatActive) => Promise<boolean | null>;
|
|
116
|
-
|
|
116
|
+
probeOpenCodeDrone?: (active: HealthBeatActive) => Promise<boolean | null>;
|
|
117
|
+
resolveAgentKind: () => 'claude' | 'codex' | 'opencode';
|
|
117
118
|
resolveHostname: () => string | null;
|
|
118
119
|
resolveVersion: () => string;
|
|
119
120
|
}
|
package/dist/health-beat.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let a=null,i=null,l=null;function x(e=new Date){a=e}function y(){return a}function k(){return i}function v(){return l}function A(){a=null,i=null,l=null}function u(e,t,n,o,r,c){return{sse_connected:e,inbox_monitor_armed:t!==!1,wake_armed:n!==!1,agent_kind:o,hostname:r,version:c,last_event_at:a?a.toISOString():null}}async function d(e,t,n){try{const o=await n.getToken();await n.fetchImpl(`${e.apiUrl}/api/drone/health`,{method:"POST",headers:{Authorization:`Bearer ${o}`,"X-Drone-Session":e.sessionToken,"Content-Type":"application/json"},body:JSON.stringify(t)})}catch{}}async function f(e,t){const n=u(t.sseConnected,t.inboxMonitorHealthy,t.wakeArmed,t.agentKind,t.hostname,t.version);await d(e,n,t)}async function m(e){try{const t=await e.getActiveCube();if(!t)return;const n=e.getStreamConnected(),o=e.checkMonitor(e.getInboxPath(t));i=o;const r=e.isCodexRemoteWake()?await e.probeBridgeArmed(t):o;l=
|
|
1
|
+
let a=null,i=null,l=null;function x(e=new Date){a=e}function y(){return a}function k(){return i}function v(){return l}function A(){a=null,i=null,l=null}function u(e,t,n,o,r,c){return{sse_connected:e,inbox_monitor_armed:t!==!1,wake_armed:n!==!1,agent_kind:o,hostname:r,version:c,last_event_at:a?a.toISOString():null}}async function d(e,t,n){try{const o=await n.getToken();await n.fetchImpl(`${e.apiUrl}/api/drone/health`,{method:"POST",headers:{Authorization:`Bearer ${o}`,"X-Drone-Session":e.sessionToken,"Content-Type":"application/json"},body:JSON.stringify(t)})}catch{}}async function f(e,t){const n=u(t.sseConnected,t.inboxMonitorHealthy,t.wakeArmed,t.agentKind,t.hostname,t.version);await d(e,n,t)}async function m(e){try{const t=await e.getActiveCube();if(!t)return;const n=e.getStreamConnected(),o=e.checkMonitor(e.getInboxPath(t));i=o;const r=e.resolveAgentKind(),c=r==="opencode"?await e.probeOpenCodeDrone?.(t)??null:e.isCodexRemoteWake()?await e.probeBridgeArmed(t):o;l=c;const h=e.resolveHostname(),s=e.resolveVersion();await f(t,{sseConnected:n,inboxMonitorHealthy:o,wakeArmed:c,agentKind:r,hostname:h,version:s,fetchImpl:e.fetchImpl,getToken:e.getToken})}catch{}}const g=6e4;function H(e,t=g){const n=setInterval(()=>{m(e)},t);return n.unref?.(),n}export{g as HEALTH_BEAT_INTERVAL_MS,A as __resetHealthBeatStateForTest,u as buildHealthPayload,f as emitHealthBeat,k as getCachedMonitorHealthy,v as getCachedWakeArmed,y as getLastEventReceivedAt,d as postHealthBeat,x as recordEventReceipt,m as runHealthBeatOnce,H as startHealthBeatTick};
|
package/dist/index.js
CHANGED
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Server as
|
|
2
|
+
import{Server as Q}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as Y}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as G,ListToolsRequestSchema as K,ListPromptsRequestSchema as X,GetPromptRequestSchema as Z}from"@modelcontextprotocol/sdk/types.js";import{assertRoleMatches as ee}from"./role-match.js";import{getCubeInfo as te,getRoleInfo as P,getRoleInfoByName as re,getRoster as oe,readLog as ne,appendLog as se,submitReport as ie,fetchReports as ae,ackLogEntry as ce,recordDecision as le,listDecisions as de,regen as N,listCubes as ue,createCube as pe,updateCube as j,deleteCube as me,createRole as be,updateRole as fe,patchRoleSection as k,patchTaxonomyClass as A,deleteRole as ge,reassignDrone as he,evictDrone as _e,getCube as w,checkSubscriptionStatus as ye,createBillingPortalSession as we,createSubscription as xe,syncRoles as $e,applyTemplate as ve,whoami as ke,roleRationale as Ee,getValidToken as Ce}from"./remote-client.js";import{startHealthBeatTick as Re}from"./health-beat.js";import{getTemplate as E,listTemplateNames as C,resolveCubeDirectiveForCreate as Se,resolveCubeDirectiveForApply as Ie,resolveMessageTaxonomyForCreate as Te}from"./templates.js";import{activeCubeWithFreshRegenIdentity as M,getActiveCube as h,setActiveCube as L,inboxPathForDrone as R}from"./cubes.js";import{addSessionStartHook as qe,addUserPromptSubmitHook as De}from"./config-utils.js";import{humanAgo as O,formatLogEntryMarkdown as Ue,formatRegenMarkdown as B,getDronePlaybook as Pe,getDronePlaybookChapter as Ne,nullTaxonomyTip as je,regenWakePathDroneLabel as Ae}from"./regen-format.js";import{startLogStream as Me,getStreamStatus as S}from"./log-stream.js";import{TOOL_MANIFEST as Le}from"./tool-manifest.js";import{DOCS_SECTIONS as Oe,matchDocsSections as Be,formatDocsIndex as Fe}from"./docs-sections.js";import{renderRoleList as He}from"./list-roles-render.js";import{filterToolsForRole as We}from"./tool-scope.js";import{getPackageVersion as x,getOnDiskVersion as Ve,handleVersionFlag as ze}from"./version.js";import{renderStreamStatus as Je,checkInboxMonitorHealthy as I,formatWakePathPrefix as Qe,shouldShowWakePathWarning as Ye}from"./stream-status.js";import{formatRoleAgentLabel as Ge,renderRoster as Ke}from"./roster-render.js";import{resolveDroneIdByLabel as Xe,isUuidShape as Ze}from"./evict-drone.js";import{authRecoveryMessage as et}from"./auth-recovery.js";import{DroneEvictedError as tt,DroneFrozenError as rt,formatEvictedToolResult as ot,formatFrozenToolResult as nt}from"./drone-lifecycle.js";import{classifyInSessionAssimilate as st,reattachOnlyRefusal as it,reattachFailureMessage as at}from"./assimilate-guard.js";import{gateAllowsActivation as ct,borgSessionToolNotice as lt}from"./launch-gate.js";import{renderSyncRolesResult as dt}from"./sync-roles-render.js";import{initConsolePrefix as ut,consolePrefix as $}from"./console-prefix.js";import{isCodexRemoteWakeEnabled as T,resolveSessionAgentKind as q,probeCodexBridgeArmed as pt}from"./codex-app-wake.js";import{connectOpenCodeDrone as mt,injectOpenCodeEntry as bt,probeOpenCodeDroneArmed as ft,computeOpenCodePort as gt}from"./opencode-drone.js";import{installBorgPlugin as ht}from"./opencode-plugin.js";import{setModuleInjectOpenCode as _t}from"./log-stream.js";import{lifecycleSignalForMessage as yt,recordLifecycleLog as F,shouldSuppressLifecycleLog as wt}from"./lifecycle-log-guard.js";import{normalizeDirectLogRecipients as xt}from"./direct-log.js";import H from"open";import $t from"os";function vt(){try{const p=$t.hostname();return p&&p.trim()?p.trim().slice(0,255):null}catch{return null}}async function W(p,y){return await ve(p,y.name)}async function _(){const p=await h();if(!p)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");return p}async function kt(){ze();try{qe()}catch{}try{De()}catch{}try{Me()}catch{}try{ht();const c=await h();if(c&&process.env.BORG_OPENCODE==="1"){const r=`http://127.0.0.1:${gt(c.droneId)}`;await mt({serverUrl:r,directory:process.cwd(),droneLabel:c.droneLabel,cubeName:c.name}),_t(bt)}}catch{}try{Re({getActiveCube:h,getStreamConnected:()=>S().connected,getInboxPath:c=>R(c.cubeId,c.droneId),checkMonitor:I,isCodexRemoteWake:T,probeBridgeArmed:c=>pt({cubeId:c.cubeId,droneId:c.droneId}),probeOpenCodeDrone:()=>ft(),resolveAgentKind:q,resolveHostname:vt,resolveVersion:x,getToken:Ce,fetchImpl:globalThis.fetch.bind(globalThis)})}catch{}const p=new Q({name:"borg-mcp-client",version:x()},{capabilities:{tools:{},prompts:{}}}),y=Le;p.setRequestHandler(K,async()=>{let c=null;try{const m=await h();m&&(c={roleName:m.roleName,roleClass:m.roleClass,isHumanSeat:m.isHumanSeat})}catch{c=null}return{tools:We(y,c)}}),p.setRequestHandler(G,async c=>{let{name:m,arguments:r}=c.params;if(m==="borg_describe-tool"){const e=typeof r?.name=="string"?r.name:"",t=y.find(o=>o.name===e);return t?{content:[{type:"text",text:JSON.stringify({name:t.name,description:t.description,inputSchema:t.inputSchema},null,2)}]}:{content:[{type:"text",text:`Unknown borg tool: ${e||"(none)"}. Pass { name: "<borg_tool>" }.`}],isError:!0}}if(m==="borg_tool"){const e=typeof r?.name=="string"?r.name:"";if(!e||e==="borg_tool"||e==="borg_describe-tool")return{content:[{type:"text",text:'borg_tool: pass { name: "<borg_tool>", arguments: {...} } naming a real borg tool (not the dispatcher itself).'}],isError:!0};r=r?.arguments&&typeof r.arguments=="object"?r.arguments:{},m=e}if(!ct(`tool ${m}`))return{content:[{type:"text",text:lt(m)}],isError:!0};try{switch(m){case"borg_regen":{const e=await h();if(!e)return{content:[{type:"text",text:'Not connected to a cube. Use `borg_assimilate cube_name="<name>"` to join one.'}]};const t=typeof r?.since=="string"?r.since:void 0,o=r?.mode==="lite"?"lite":"full",n=await N(e.sessionToken,e.apiUrl,{since:t}),s=M(e,n);s!==e&&await L(s);const i=S(),a=R(s.cubeId,s.droneId),l=q()==="opencode"||T()?!0:I(a),u=Ye(i,l)?Qe({inboxPath:a,droneLabel:Ae(n,s.droneLabel),cubeName:s.name}):"";let b="";try{const f=x(),d=Ve();if(f!=="unknown"&&d!=="unknown"&&d!==f){const[g,D,z]=f.split(".").map(Number),[v,U,J]=d.split(".").map(Number);(v>g||v===g&&U>D||v===g&&U===D&&J>z)&&(b=`## \u{1F504} borgmcp ${d} installed \u2014 run /mcp and reconnect (or restart Claude Code) to apply. Currently running ${f}.
|
|
3
3
|
|
|
4
|
-
`)}}catch{}return{content:[{type:"text",text:b+
|
|
5
|
-
`)+B(n,{mode:"full"})}]}}catch(n){const s=
|
|
4
|
+
`)}}catch{}return{content:[{type:"text",text:b+u+B(n,{mode:o})}]}}case"borg_subscribe":return{content:[{type:"text",text:`Complete your subscription at: ${await xe()}`}]};case"borg_upgrade-subscription":{const e=await we();try{await H(e)}catch{}return{content:[{type:"text",text:`Manage your Borg MCP subscription at: ${e}`}]}}case"borg_subscription_status":{const e=await ye();return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}case"borg_open_dashboard":{const e="https://borgmcp.ai/dashboard";return await H(e),{content:[{type:"text",text:`\u25FC Opened dashboard in browser: ${e}`}]}}case"borg_assimilate":{const e=r?.cube_name;if(!e)throw new Error("cube_name is required");const t=await h(),o=st(t,e);if(o.kind!=="reattach")return{content:[{type:"text",text:it(o,e)}],isError:!0};try{const n=await N(t.sessionToken,t.apiUrl,{}),s=M(t,n);return s!==t&&await L(s),{content:[{type:"text",text:[`# Re-attached to cube: ${s.name}`,"",`**Drone label:** ${s.droneLabel}`,"**Seat:** existing identity reused \u2014 no new drone minted (gh#780)","",""].join(`
|
|
5
|
+
`)+B(n,{mode:"full"})}]}}catch(n){const s=at(n??{});if(!s)throw n;return{content:[{type:"text",text:s}],isError:!0}}}case"borg_version":return{content:[{type:"text",text:`borgmcp ${x()}`}]};case"borg_playbook":return{content:[{type:"text",text:Ne()}]};case"borg_docs":{const e=typeof r?.topic=="string"?r.topic.trim():"",t=e?Be(e):[],o=t.length>0?t:Oe;return{content:[{type:"text",text:`${e&&t.length>0?`Best-matching docs section(s) for "${e}" \u2014 WebFetch the URL for the full page:`:e?`No exact match for "${e}". Full Borg MCP docs index \u2014 WebFetch the URL you need:`:"Borg MCP docs index \u2014 WebFetch the URL of the section you need:"}
|
|
6
6
|
|
|
7
|
-
${
|
|
8
|
-
`)}]}}case"borg_role":{const e=await
|
|
9
|
-
`)}]}}const{role:o}=await
|
|
10
|
-
`)}]}}case"borg_role-rationale":{const e=await
|
|
11
|
-
`)}]}}case"borg_roster":{const e=await
|
|
7
|
+
${Fe(o)}`}]}}case"borg_whoami":{const e=await _(),t=await ke(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}case"borg_cube":{const e=await _(),[{cube:t,roles:o}]=await Promise.all([te(e.sessionToken,e.apiUrl),P(e.sessionToken,e.apiUrl)]),n=[];n.push(`# Cube: ${t.name}`),n.push(""),n.push("## Cube directive"),n.push(t.cube_directive||"_(none)_"),n.push("");const s=je(t.message_taxonomy);if(s&&(n.push(s),n.push("")),n.push("## Roles in this cube"),!o.length)n.push("_(no roles defined)_");else{for(const i of o){const a=[i.role_class==="queen"?"Queen":null,i.is_human_seat?"human-seat":null,i.is_default?"default":null].filter(Boolean).join(", "),l=a?` (${a})`:"",u=i.short_description||"_(no description)_";n.push(`- **${i.name}**${l} \u2014 ${u}`)}n.push(""),n.push("_(Coordinator-class drones can fetch role IDs via `borg_list-roles` for use with `borg_reassign-drone`.)_")}return n.push(""),n.push(Pe()),{content:[{type:"text",text:n.join(`
|
|
8
|
+
`)}]}}case"borg_role":{const e=await _(),t=typeof r?.role=="string"?r.role.trim():"";if(t){const{role:s}=await re(e.sessionToken,e.apiUrl,t);return ee(t,s),{content:[{type:"text",text:[`# Role: ${s.name}`,"",s.detailed_description||"_(no detailed description set)_"].join(`
|
|
9
|
+
`)}]}}const{role:o}=await P(e.sessionToken,e.apiUrl);return{content:[{type:"text",text:[`# Your role: ${o.name}`,"",o.detailed_description||"_(no detailed description set)_"].join(`
|
|
10
|
+
`)}]}}case"borg_role-rationale":{const e=await _(),t=typeof r?.role=="string"?r.role:"",o=typeof r?.section=="string"?r.section:"",n=await Ee(e.sessionToken,e.apiUrl,t,o);return{content:[{type:"text",text:[`# Role rationale: ${n.role} \u2014 ${n.section}`,"",n.body||"_(empty)_"].join(`
|
|
11
|
+
`)}]}}case"borg_roster":{const e=await _(),t=typeof r?.since=="string"?r.since:void 0,{drones:o,roles:n,since:s}=await oe(e.sessionToken,e.apiUrl,t);return{content:[{type:"text",text:Ke({cubeName:e.name,drones:o,roles:n,resolvedSince:s??null,humanAgo:O})}]}}case"borg_stream-status":{const e=S(),t=await h(),o=t?R(t.cubeId,t.droneId):null,n=t&&q()==="opencode",s=t?n||T()?!0:I(o):null;let i="";e.runLoopHealth==="silent-inert"&&(i=`## \u26A0 SSE stream loop silent-inert \u2014 run /mcp and reconnect to restart
|
|
12
12
|
|
|
13
13
|
The log-stream consumer started but never connected. This drone will not receive real-time cube events.
|
|
14
14
|
|
|
15
|
-
`);const
|
|
16
|
-
`)}]}}case"borg_log":{const e=r?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await
|
|
17
|
-
${
|
|
18
|
-
\u26A0 ${
|
|
15
|
+
`);const a=Je({status:e,inboxMonitorHealthy:s,inboxPath:o,droneLabel:t?.droneLabel??null,cubeName:t?.name??null,humanAgo:O});return{content:[{type:"text",text:i+a}]}}case"borg_read-log":{const e=await _(),t=typeof r?.since=="string"?r.since:void 0,o=typeof r?.limit=="number"?r.limit:void 0,n=r?.unread_only===!0||r?.unread_only==="true",{entries:s,drones:i,roles:a,behind_by:l,has_more:u}=await ne(e.sessionToken,e.apiUrl,{since:t,limit:o,unreadOnly:n}),b=new Map;for(const g of i)b.set(g.id,g);const f=new Map;for(const g of a)f.set(g.id,g);const d=[];if(d.push(`# Activity log: ${e.name}`),d.push(""),!s.length)d.push("_(no entries)_");else for(const g of s)d.push(Ue(g,b,f));return u===!0?(d.push(""),d.push("\u26A0 has_more: true \u2014 call `borg_read-log unread_only=true` again until has_more=false so you finish draining unread entries.")):typeof l=="number"&&l>0&&(d.push(""),d.push(`\u26A0 behind_by: ${l} more unread ${l===1?"entry":"entries"} addressed to you \u2014 call \`borg_read-log unread_only=true\` again until behind_by=0 so you don't skip messages.`)),{content:[{type:"text",text:d.join(`
|
|
16
|
+
`)}]}}case"borg_log":{const e=r?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await h();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");if(yt(e)){const d=await wt(t,e);if(d.suppress)return await F(t,e),{content:[{type:"text",text:`Suppressed duplicate ${d.signal?.toUpperCase()} lifecycle log for ${t.droneLabel}; recent cube log already contains this signal.`}]}}const o=Object.prototype.hasOwnProperty.call(r??{},"to"),n=o?xt(r?.to):void 0,s=typeof r?.class=="string"?r.class:void 0,i=r?.visibility==="broadcast"||r?.visibility==="direct"?r.visibility:void 0,a={...s?{class:s}:{},...o?{to:n??[]}:{},...i?{visibility:i}:{}},l=await se(t.sessionToken,t.apiUrl,e,a);await F(t,e);const u=l.routing?.message?`
|
|
17
|
+
${l.routing.message}`:"",b=l.unreachableRecipients?.length?`
|
|
18
|
+
\u26A0 ${l.unreachableRecipients.length} directed recipient(s) currently unreachable (wake-path:deaf): ${l.unreachableRecipients.map(d=>d.label).join(", ")}. Message delivered \u2014 they'll read it when they return.`:"";return{content:[{type:"text",text:`Logged to cube "${t.name}" as ${t.droneLabel}. (entry id: ${l.entry.id})${u}${b}`}]}}case"borg_report-friction":{const e=r?.message;if(!e||typeof e!="string")throw new Error("message is required");const t=await h();if(!t)throw new Error("Not assimilated to a cube. Use borg_assimilate <cube-name> first.");const o=r?.kind==="bug"?"bug":"friction",n=r?.metadata&&typeof r.metadata=="object"&&!Array.isArray(r.metadata)?r.metadata:void 0;return{content:[{type:"text",text:(await ie(t.sessionToken,t.apiUrl,{kind:o,message:e,metadata:n})).ok?"Report submitted \u2014 thank you. The borgmcp team will see it. (Write-only: you cannot read reports back.)":"Report did not submit. Try again, or raise it in the cube log."}]}}case"borg_reports":{const e=await ae();if(e.forbidden)return{content:[{type:"text",text:"Reports triage is builder/dogfooder-tier only. Your account is not on the dogfooder (builder) tier, so the friction-reports store is not readable. (Server-enforced gate.)"}]};if(!e.reports.length)return{content:[{type:"text",text:"No reports yet. Submissions via borg_report-friction will appear here, newest first."}]};const t=e.reports.map(o=>{const n=o.metadata&&Object.keys(o.metadata).length?" \xB7 "+Object.entries(o.metadata).map(([i,a])=>`${i}=${a}`).join(", "):"",s=o.redacted?" \xB7 [secrets-scrubbed]":"";return`**[${o.kind}]** ${o.created_at} \xB7 ${o.reporter_email}${n}${s}
|
|
19
19
|
${o.message}`});return{content:[{type:"text",text:`Reports (${e.reports.length}, newest first):
|
|
20
20
|
|
|
21
21
|
${t.join(`
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
25
|
-
`)}`}]}}case"borg_ack":{const e=r?.entry_id;if(!e||typeof e!="string")throw new Error("entry_id is required");const t=r?.kind==="claim"?"claim":"ack",o=await
|
|
26
|
-
`)}]}}case"borg_list-cubes":{const{cubes:e}=await
|
|
25
|
+
`)}`}]}}case"borg_ack":{const e=r?.entry_id;if(!e||typeof e!="string")throw new Error("entry_id is required");const t=r?.kind==="claim"?"claim":"ack",o=await _();return await ce(o.sessionToken,o.apiUrl,e,t),{content:[{type:"text",text:t==="claim"?`Claimed entry ${e} in cube "${o.name}" (advisory \u2014 merge stays keyed on REVIEW-APPROVED).`:`Acked entry ${e} in cube "${o.name}".`}]}}case"borg_decide":{const e=r?.topic,t=r?.decision;if(!e||typeof e!="string")throw new Error("topic is required");if(!t||typeof t!="string")throw new Error("decision is required");const o=typeof r?.rationale=="string"?r.rationale:void 0,n=await _(),{decision:s}=await le(n.sessionToken,n.apiUrl,{topic:e,decision:t,...o!==void 0?{rationale:o}:{}}),i=s?.supersedes?" (superseded the prior decision on this topic)":"";return{content:[{type:"text",text:`Recorded ratified decision on "${e}" in cube "${n.name}"${i}. Cite it via borg_decisions; it surfaces in borg_regen.`}]}}case"borg_decisions":{const e=typeof r?.topic=="string"?r.topic:void 0,t=await _(),{decisions:o}=await de(t.sessionToken,t.apiUrl,e);return{content:[{type:"text",text:o.length===0?e?`No active ratified decision on "${e}" in cube "${t.name}".`:`No active ratified decisions in cube "${t.name}".`:o.map(s=>`**${s.topic}:** ${s.decision}${s.rationale?` \u2014 ${s.rationale}`:""}`).join(`
|
|
26
|
+
`)}]}}case"borg_list-cubes":{const{cubes:e}=await ue();if(!e.length)return{content:[{type:"text",text:"No cubes yet. Use borg_create-cube to make your first one."}]};const t=e.map(o=>`- **${o.name}** (id: ${o.id})
|
|
27
27
|
${(o.cube_directive||"_(no directive set)_").split(`
|
|
28
28
|
`)[0].slice(0,120)}`);return{content:[{type:"text",text:`Your cubes (${e.length}):
|
|
29
29
|
|
|
30
30
|
${t.join(`
|
|
31
31
|
|
|
32
|
-
`)}`}]}}case"borg_create-cube":{const e=r?.name,t=r?.cube_directive,o=r?.template;if(!e)throw new Error("name is required");if(t===void 0)throw new Error("cube_directive is required (pass empty string if none)");let n=null;if(o&&(n=E(o),!n))throw new Error(`Unknown template "${o}". Available: ${
|
|
32
|
+
`)}`}]}}case"borg_create-cube":{const e=r?.name,t=r?.cube_directive,o=r?.template;if(!e)throw new Error("name is required");if(t===void 0)throw new Error("cube_directive is required (pass empty string if none)");let n=null;if(o&&(n=E(o),!n))throw new Error(`Unknown template "${o}". Available: ${C().join(", ")}`);const s=Se(t,n),i=Te(void 0,n),a=await pe(e,s,{message_taxonomy:i});if(n){const u=await W(a.id,n),b=s!==t?" Template cube directive applied (operator passed empty).":"";return{content:[{type:"text",text:`Created cube **${a.name}** (id: ${a.id}) with template **${o}** applied \u2014 ${u.created} role(s) created, ${u.updated} updated.${b} Use borg_assimilate ${a.name} to join as a drone.`}]}}return{content:[{type:"text",text:`Created cube **${a.name}** (id: ${a.id}). A default "Drone" role was seeded \u2014 rename or replace it via borg_update-role / borg_create-role / borg_delete-role. Use borg_assimilate ${a.name} to join as a drone.`}]}}case"borg_update-cube":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");const t={};if(typeof r?.name=="string"&&(t.name=r.name),typeof r?.cube_directive=="string"&&(t.cube_directive=r.cube_directive),Array.isArray(r?.message_taxonomy)&&(t.message_taxonomy=r.message_taxonomy),Object.keys(t).length===0)throw new Error("Pass at least one of: name, cube_directive, message_taxonomy.");const{cube:o}=await j(e,t);return{content:[{type:"text",text:`Updated cube **${o.name}** (id: ${o.id}).`}]}}case"borg_patch-taxonomy-class":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");const t=r?.action;if(t!=="add"&&t!=="replace"&&t!=="remove")throw new Error("action must be one of: add, replace, remove.");let o,n;if(t==="remove"){const i=r?.class;if(!i)throw new Error("class is required for remove.");({cube:o}=await A(e,{action:t,class:i})),n=i}else{const i=r?.class_def;if(i==null||typeof i!="object"||Array.isArray(i))throw new Error("class_def (object) is required for add/replace.");({cube:o}=await A(e,{action:t,class_def:i})),n=String(i.class??"")}return{content:[{type:"text",text:`${t==="add"?"Added":t==="replace"?"Replaced":"Removed"} taxonomy class **${n}** in cube **${o.name}** (id: ${o.id}).`}]}}case"borg_delete-cube":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");return await me(e),{content:[{type:"text",text:`Deleted cube ${e} (and all its roles, drones, log entries).`}]}}case"borg_create-role":{const e=r?.cube_id,t=r?.name,o=r?.short_description,n=r?.detailed_description;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("name is required");if(o===void 0)throw new Error("short_description is required (pass empty string if none)");if(n===void 0)throw new Error("detailed_description is required (pass empty string if none)");const s=r?.is_default===!0,i=r?.is_human_seat===!0,a=r?.can_broadcast===!0,l=r?.receives_all_direct===!0,{role:u}=await be(e,{name:t,short_description:o,detailed_description:n,is_default:s,is_human_seat:i,can_broadcast:a,receives_all_direct:l,...typeof r?.default_model=="string"?{default_model:r.default_model}:{}}),b=[u.role_class==="queen"?"Queen":null,u.is_human_seat?"human-seat":null,u.is_default?"default":null].filter(Boolean).join(", "),f=b?` (${b})`:"";return{content:[{type:"text",text:`Created role **${u.name}**${f} (id: ${u.id}) in cube ${e}.`}]}}case"borg_update-role":{const e=r?.role_id;if(!e)throw new Error("role_id is required");const t={};if(typeof r?.name=="string"&&(t.name=r.name),typeof r?.short_description=="string"&&(t.short_description=r.short_description),typeof r?.detailed_description=="string"&&(t.detailed_description=r.detailed_description),typeof r?.is_default=="boolean"&&(t.is_default=r.is_default),typeof r?.is_human_seat=="boolean"&&(t.is_human_seat=r.is_human_seat),typeof r?.can_broadcast=="boolean"&&(t.can_broadcast=r.can_broadcast),typeof r?.receives_all_direct=="boolean"&&(t.receives_all_direct=r.receives_all_direct),typeof r?.default_model=="string"&&(t.default_model=r.default_model),Object.keys(t).length===0)throw new Error("Pass at least one of: name, short_description, detailed_description, is_default, is_human_seat, can_broadcast, receives_all_direct, default_model.");const{role:o}=await fe(e,t),n=[o.role_class==="queen"?"Queen":null,o.is_human_seat?"human-seat":null,o.is_default?"default":null].filter(Boolean).join(", "),s=n?` (${n})`:"";return{content:[{type:"text",text:`Updated role **${o.name}**${s} (id: ${o.id}).`}]}}case"borg_patch-role-section":{const e=r?.role_id;if(!e)throw new Error("role_id is required");const t=r?.action;if(t!=="replace"&&t!=="insert"&&t!=="delete")throw new Error("action must be one of: replace, insert, delete.");const o=r?.heading;if(!o)throw new Error("heading is required");let n;if(t==="delete")({role:n}=await k(e,{action:t,heading:o}));else{const i=r?.body;if(typeof i!="string")throw new Error("body is required for replace/insert (pass empty string for an empty section).");if(t==="insert"){const a=typeof r?.after=="string"?r.after:null;({role:n}=await k(e,{action:t,heading:o,body:i,after:a}))}else({role:n}=await k(e,{action:t,heading:o,body:i}))}return{content:[{type:"text",text:`${t==="replace"?"Replaced":t==="insert"?"Inserted":"Deleted"} section **${o}** in role **${n.name}** (id: ${n.id}).`}]}}case"borg_delete-role":{const e=r?.role_id;if(!e)throw new Error("role_id is required");return await ge(e),{content:[{type:"text",text:`Deleted role ${e}.`}]}}case"borg_reassign-drone":{const e=r?.drone_id,t=r?.role_id;if(!e)throw new Error("drone_id is required");if(!t)throw new Error("role_id is required");const{drone:o}=await he(e,t);return{content:[{type:"text",text:`Reassigned drone ${o.label} (${o.id}) to role ${o.role_id}.`}]}}case"borg_evict-drone":{const e=r?.drone_id?.trim(),t=r?.label?.trim(),o=r?.cube_id?.trim();let n,s;if(e){if(!Ze(e))throw new Error(`drone_id "${e}" is not a UUID \u2014 if that's a drone label, pass it as label + cube_id instead.`);n=e,s=e}else if(t){if(!o)throw new Error("cube_id is required when evicting by label");const{drones:i}=await w(o),a=Xe(i,t);if(!a)throw new Error(`No active drone labelled "${t}" in cube ${o} (it may already be evicted; check borg_list-drones).`);n=a.id,s=a.label}else throw new Error("Provide drone_id, or label + cube_id, to identify the drone to evict");return await _e(n),{content:[{type:"text",text:`Evicted drone ${s} (${n}). Soft-deleted: removed from the roster and freed its seat; log history preserved with anonymized attribution.`}]}}case"borg_list-drones":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");const{drones:t,roles:o}=await w(e);if(!t.length)return{content:[{type:"text",text:"No drones in this cube yet."}]};const n=new Map(o.map(i=>[i.id,i])),s=t.map(i=>{const a=n.get(i.role_id),l=Ge(a?.name??"?",i.agent_kind),u=i.wake_path_alert_class&&i.wake_path_alert_class!=="independent"?` \u2014 wake-path-class: ${i.wake_path_alert_class}`:"";return`- **${i.label}** (id: ${i.id}) \u2014 role: ${l} (${i.role_id}) \u2014 last seen ${i.last_seen}${u}`});return{content:[{type:"text",text:`Drones in cube ${e} (${t.length}):
|
|
33
33
|
|
|
34
34
|
${s.join(`
|
|
35
|
-
`)}`}]}}case"borg_list-roles":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");const{roles:t}=await w(e);return{content:[{type:"text",text:
|
|
35
|
+
`)}`}]}}case"borg_list-roles":{const e=r?.cube_id;if(!e)throw new Error("cube_id is required");const{roles:t}=await w(e);return{content:[{type:"text",text:He(t,e)}]}}case"borg_list-templates":return{content:[{type:"text",text:`Available templates:
|
|
36
36
|
|
|
37
|
-
${
|
|
38
|
-
`)}`}]};case"borg_sync-roles":{const e=r?.cube_id,t=r?.template_name||"software-dev",o=r?.apply===!0,n=r?.decisions&&typeof r.decisions=="object"?r.decisions:void 0;if(!e)throw new Error("cube_id is required");const s=await
|
|
37
|
+
${C().map(o=>{const n=E(o);return`- **${o}**: ${n.description}`}).join(`
|
|
38
|
+
`)}`}]};case"borg_sync-roles":{const e=r?.cube_id,t=r?.template_name||"software-dev",o=r?.apply===!0,n=r?.decisions&&typeof r.decisions=="object"?r.decisions:void 0;if(!e)throw new Error("cube_id is required");const s=await $e(e,t,o,n);return{content:[{type:"text",text:dt(s,t)}]}}case"borg_apply-template":{const e=r?.cube_id,t=r?.template_name;if(!e)throw new Error("cube_id is required");if(!t)throw new Error("template_name is required");const o=E(t);if(!o)throw new Error(`Unknown template "${t}". Available: ${C().join(", ")}`);const n=await W(e,o);let s="";const i=await w(e),a=Ie(i.cube_directive,o);return a!==null&&(await j(e,{cube_directive:a}),s=" Template cube directive applied (cube directive was empty)."),{content:[{type:"text",text:`Applied template **${t}** to cube ${e} \u2014 ${n.created} role(s) created, ${n.updated} updated.${s}`}]}}default:throw new Error(`Unknown tool: ${m}`)}}catch(e){if(e instanceof tt)return{content:[{type:"text",text:ot(e.message)}],isError:!0};if(e instanceof rt)return{content:[{type:"text",text:nt(e.message)}],isError:!0};const t=et(e??{});return t?{content:[{type:"text",text:t}],isError:!0}:{content:[{type:"text",text:`Error: ${e.message}`}],isError:!0}}}),p.setRequestHandler(X,async()=>({prompts:[{name:"borg_subscribe",description:"Set up Borg MCP Cube tier subscription ($1/month per cube; each cube adds 8 pooled agent sessions + 1000 req/hr). Free tier is permanent (1 cube + 3 agent sessions + 100 req/hr); no trial."},{name:"dashboard",description:"Open Borg MCP dashboard to manage cubes"}]})),p.setRequestHandler(Z,async c=>{const{name:m}=c.params;switch(m){case"borg_subscribe":return{description:"Set up Borg MCP Cube tier subscription ($1/month per cube; each cube adds 8 pooled agent sessions + 1000 req/hr). Free tier is permanent (1 cube + 3 agent sessions + 100 req/hr); no trial.",messages:[{role:"user",content:{type:"text",text:"Please help me set up a Borg MCP subscription using the subscribe tool."}}]};case"dashboard":return{description:"Open Borg MCP dashboard to manage cubes",messages:[{role:"user",content:{type:"text",text:"Please open the Borg MCP dashboard using the borg_open_dashboard tool."}}]};default:throw new Error(`Unknown prompt: ${m}`)}});const V=new Y;await p.connect(V),await ut(),console.error(`${$()}\u25FC Borg MCP Client started`),console.error(`${$()}\u25FC Use borg_assimilate <cube-name> to join a cube as a drone`),console.error(`${$()}\u25FC Manage your cubes at https://borgmcp.ai/dashboard`)}kt().catch(p=>{console.error(`${$()}Fatal error:`,p),process.exit(1)});
|
|
@@ -62,7 +62,7 @@ export interface LaunchAllDeps {
|
|
|
62
62
|
*/
|
|
63
63
|
probeSeat: (sessionToken: string, apiUrl: string) => Promise<SeatStatus>;
|
|
64
64
|
/** Saved CLI preference for a worktree path (launch.json). */
|
|
65
|
-
getCliPreferenceForPath: (projectPath: string) => Promise<'claude' | 'codex' | null>;
|
|
65
|
+
getCliPreferenceForPath: (projectPath: string) => Promise<'claude' | 'codex' | 'opencode' | null>;
|
|
66
66
|
/** All persisted project identities from cubes.json. */
|
|
67
67
|
readAllProjectIdentities: () => Promise<Array<{
|
|
68
68
|
projectPath: string;
|
package/dist/log-stream.d.ts
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
*/
|
|
26
26
|
import { getActiveCube } from './cubes.js';
|
|
27
27
|
import { acquireStreamLease, type StreamOwnershipSnapshot } from './stream-owner.js';
|
|
28
|
+
export declare function setModuleInjectOpenCode(fn: (text: string) => Promise<boolean>): void;
|
|
28
29
|
export declare const INBOX_TAIL_LINES_CAP = 512;
|
|
29
30
|
export declare const INBOX_TAIL_TRIM_THRESHOLD_LINES: number;
|
|
30
31
|
export type RunLoopHealth = 'connected' | 'reconnecting' | 'silent-inert' | 'never-started';
|
|
@@ -119,6 +120,13 @@ export interface StreamDeps {
|
|
|
119
120
|
* SSE stream's fetchImpl — below the agent classifier). Tests inject a spy.
|
|
120
121
|
*/
|
|
121
122
|
onInboxReceipt?: (active: ActiveCube, token: string) => void;
|
|
123
|
+
/**
|
|
124
|
+
* Optional opencode entry injector for autonomous drone processing.
|
|
125
|
+
* When provided AND the injection succeeds, the inbox file write is skipped
|
|
126
|
+
* (the drone processes the entry autonomously). On failure, falls through
|
|
127
|
+
* to the inbox write for backup.
|
|
128
|
+
*/
|
|
129
|
+
injectOpenCode?: (text: string) => Promise<boolean>;
|
|
122
130
|
}
|
|
123
131
|
/**
|
|
124
132
|
* Test-only injection seam for runLoop (gh#866 item 2). Production calls
|
package/dist/log-stream.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import{promises as E}from"node:fs";import
|
|
1
|
+
import{promises as E}from"node:fs";import ie from"node:os";import L from"node:path";import{getActiveCube as se,inboxPathForDrone as U}from"./cubes.js";import{DroneEvictedError as q,DRONE_EVICTED_CODE as ce,EVICTED_RESULT_MARKER as le,errorCodeFromBody as de}from"./drone-lifecycle.js";import{CODEX_HEARTBEAT_CADENCE_MS as z,fireCodexHeartbeatTick as ue,formatCodexWakePrompt as fe,resolveSessionAgentKind as pe,startCodexHeartbeat as me,wakeCodexViaAppServer as we}from"./codex-app-wake.js";import{getValidToken as he}from"./remote-client.js";import{recordEventReceipt as be,emitHealthBeat as ge,getCachedMonitorHealthy as Ie,getCachedWakeArmed as _e}from"./health-beat.js";import{getPackageVersion as ye}from"./version.js";import{acquireStreamLease as Y,readOwnershipSnapshot as S}from"./stream-owner.js";const Q=9e4,Ee=2e3,Se=500,Te=3e4;let P;function tt(e){P=e}const xe=50,W=512,nt=W*2;function Ae(){try{const e=ie.hostname();return e&&e.trim()?e.trim().slice(0,255):null}catch{return null}}const Ce=Date.now(),a={connected:!1,lastWireActivityAt:null,lastContentEventAt:null,lastHeartbeatAt:null,lastPersistedEventId:null,reconnectAttempts:0,runLoopRestartCount:0,ownership:{state:"unowned"}};function ke(e,t=Date.now()-Ce){return e.connected&&e.lastWireActivityAt?"connected":!e.connected&&e.reconnectAttempts>0?"reconnecting":!e.connected&&!e.lastWireActivityAt&&e.reconnectAttempts===0&&t>1e4?"silent-inert":"never-started"}function rt(){return{...a,runLoopHealth:ke(a)}}function ot(){a.connected=!1,a.lastWireActivityAt=null,a.lastContentEventAt=null,a.lastHeartbeatAt=null,a.lastPersistedEventId=null,a.reconnectAttempts=0,a.runLoopRestartCount=0,a.ownership={state:"unowned"}}let D=null,T=null,B=z;function j(e=He){D||(D=e())}function He(){return me({tick:()=>{ue({isStreamOwner:()=>a.ownership?.state==="owner",onAppServerSocketDead:De})}})}function De(){V(),ve()}function ve(e=B){T||(T=setTimeout(()=>{T=null,j()},e),T.unref?.())}function V(){D&&clearInterval(D),D=null,T&&clearTimeout(T),T=null}function at(){V(),B=z}function it(e){B=e}function Oe(){(async()=>{for(;;){try{await ee(),process.stderr.write(`[borg-mcp log stream] runLoop returned unexpectedly; restarting in 5s
|
|
2
2
|
`)}catch(e){process.stderr.write(`[borg-mcp log stream] runLoop threw: ${e?.message??e}; restarting in 5s
|
|
3
|
-
`)}a.runLoopRestartCount+=1,await
|
|
4
|
-
`);return}a.connected=!1;const
|
|
5
|
-
`),s+=1,a.reconnectAttempts=s,await r(
|
|
3
|
+
`)}a.runLoopRestartCount+=1,await oe(5e3)}})()}function st(e={}){j(),(e.runForever??Oe)()}const Z={fetchImpl:globalThis.fetch.bind(globalThis),appendLine:We,hasInboxEntryId:Ge,getToken:he,wakeCodex:we,heartbeatTimeoutMs:Q,hwmDivergenceGraceMs:Ee,abortSignal:new AbortController().signal,ownerDeps:{},ownerStaleMs:7e4,onInboxReceipt:Re,injectOpenCode:e=>P?P(e):Promise.resolve(!1)};function Re(e,t){be(),ge(e,{sseConnected:!0,inboxMonitorHealthy:Ie(),wakeArmed:_e(),agentKind:pe(),hostname:Ae(),version:ye(),getToken:async()=>t,fetchImpl:globalThis.fetch.bind(globalThis)})}async function ee(e={}){const t=e.getActiveCube??se,i=e.acquireStreamLease??Y,r=e.sleep??oe,c=e.maxIterations??1/0;let o=0,s=0,l=null,g=null,u=null,w=null;for(;o<c;){o+=1;const d=await t();if(!d){u&&(await u.release(),u=null,w=null),a.connected=!1,a.ownership={state:"unowned"},V(),await r(5e3);continue}j(),d.cubeId!==g&&(g=d.cubeId,l=null);const k=`${d.cubeId}:${d.droneId}`;if(u&&w!==k&&(await u.release(),u=null,w=null),u||(u=await i(d.cubeId,d.droneId),w=u?k:null),!u){a.connected=!1,a.ownership=await S(d.cubeId,d.droneId),await r(5e3);continue}a.ownership=await S(d.cubeId,d.droneId);let x=!1;try{const m=new AbortController,I=async()=>{try{await u.refresh()||(x=!0,m.abort(new Error("stream ownership lost")))}catch(h){x=!0,m.abort(h instanceof Error?h:new Error(String(h)))}},A=setInterval(()=>{I()},Math.max(1e3,Math.floor(Q/2)));try{await te(d,l,h=>{l=h},{abortSignal:m.signal})}finally{clearInterval(A)}if(x){u=null,w=null,a.connected=!1,a.ownership=await S(d.cubeId,d.droneId),await r(5e3);continue}s=0,a.reconnectAttempts=0}catch(m){if(x){u=null,w=null,a.connected=!1,a.ownership=await S(d.cubeId,d.droneId),await r(5e3);continue}if(m instanceof q){u&&await u.release().catch(()=>{}),u=null,w=null,a.connected=!1,a.ownership=await S(d.cubeId,d.droneId),process.stderr.write(`[borg-mcp log stream] drone evicted \u2014 stream terminated (no reconnect).
|
|
4
|
+
`);return}a.connected=!1;const I=Math.min(Se*2**s,Te)+Math.random()*500;process.stderr.write(`[borg-mcp log stream] reconnect in ${Math.round(I)}ms: ${m?.message??m}
|
|
5
|
+
`),s+=1,a.reconnectAttempts=s,await r(I)}}}function ct(e){return ee(e)}async function te(e,t,i,r={}){const{fetchImpl:c,appendLine:o,hasInboxEntryId:s,getToken:l,wakeCodex:g,heartbeatTimeoutMs:u,hwmDivergenceGraceMs:w,abortSignal:d,onInboxReceipt:k,injectOpenCode:x}={...Z,...r},m=await l(),I={Authorization:`Bearer ${m}`,"X-Drone-Session":e.sessionToken,Accept:"text/event-stream"};t&&(I["Last-Event-ID"]=t);const A=new AbortController,h=()=>{try{A.abort(d.reason??new Error("external abort"))}catch{}};d.aborted&&h(),d.addEventListener("abort",h,{once:!0});let b=null;const G=()=>{b&&clearTimeout(b),b=setTimeout(()=>{try{A.abort(new Error("heartbeat watchdog timeout"))}catch{}},u)};G();let K=t,p=null,_=null;const H=()=>{_&&(clearTimeout(_.timer),_=null)};let v=null;const O=(n,f)=>{const R={id:n,created_at:f};v&&f&&v.created_at&&C(R,v)<=0||(v=R,K=n,a.lastPersistedEventId=n,i(n))},M=n=>{n&&(p=!p||C(n,p)>0?n:p,_&&C(p,_.hwm)>=0&&H())},ae=n=>{if(_?.hwm.id===n.id)return;H();const f=setTimeout(()=>{if(p&&C(p,n)>=0){H();return}try{A.abort(new Error("hwm divergence \u2014 reconnect for catchup"))}catch{}},w);_={hwm:n,timer:f}},N=new Set,$=[];let X=t!==null;const J=async n=>{const f=Fe(Ne(n.data,n.id));return X&&await s(e.cubeId,e.droneId,n.id,f)?(O(n.id,n.data?.created_at??""),"persisted-skip"):await x(f)?(k(e,m),"written"):(await o(e.cubeId,e.droneId,f),g(fe(f)),k(e,m),"written")},F=n=>{for(N.add(n.id),$.push(n.id);$.length>xe;){const f=$.shift();f&&N.delete(f)}O(n.id,n.data?.created_at??""),M(re(n))};let y;try{y=await c(`${e.apiUrl}/api/drone/stream`,{method:"GET",headers:I,signal:A.signal})}catch(n){throw b&&clearTimeout(b),n}if(!y.ok||!y.body){if(b&&clearTimeout(b),y.status===410){const n=await y.text().catch(()=>"");if(de(n)===ce)throw new q}throw new Error(`stream HTTP ${y.status}`)}a.connected=!0;try{for await(const n of Le(y.body)){G();const f=new Date().toISOString();if(a.lastWireActivityAt=f,(n.type==="log"||n.type==="bookmark")&&(a.lastContentEventAt=f),n.type==="eviction"){a.lastContentEventAt=f;try{await o(e.cubeId,e.droneId,Pe(n.reason))}catch{}break}if(n.type==="heartbeat"){if(a.lastHeartbeatAt=f,n.hwm&&p===null){M(n.hwm),K===null&&O(n.hwm.id,n.hwm.created_at);continue}if(n.hwm&&p&&C(n.hwm,p)<=0){H();continue}n.hwm&&p&&C(n.hwm,p)>0&&ae(n.hwm);continue}if(n.type==="bookmark"){X=!1;continue}if(n.type==="log"){if(N.has(n.id)){O(n.id,n.data?.created_at??""),M(re(n));continue}const R=typeof n.data?.message=="string"&&n.data.message.startsWith("[HEARTBEAT-PING]");if(n.data?.kind==="ack"){if(n.data?.author_drone_id===e.droneId&&await J(n)==="persisted-skip")continue;F(n);continue}if(n.data?.drone_id===e.droneId&&!R){F(n);continue}if(await J(n)==="persisted-skip")continue;F(n)}}}finally{d.removeEventListener("abort",h),b&&clearTimeout(b),H(),a.connected=!1}}async function lt(e,t,i,r={}){const{ownerDeps:c,ownerStaleMs:o}={...Z,...r},s=await Y(e.cubeId,e.droneId,o,c);if(!s)return a.connected=!1,a.ownership=await S(e.cubeId,e.droneId,c),"skipped";a.ownership=await S(e.cubeId,e.droneId,c);try{return await te(e,t,i,r),"streamed"}finally{await s.release()}}async function*Le(e){const t=e.getReader(),i=new TextDecoder;let r="";try{for(;;){const{value:c,done:o}=await t.read();if(o){if(r.trim()){const l=ne(r);l&&(yield l)}return}r+=i.decode(c,{stream:!0});let s;for(;(s=r.indexOf(`
|
|
6
6
|
|
|
7
|
-
`))!==-1;){const l=r.slice(0,s);r=r.slice(s+2);const g=
|
|
7
|
+
`))!==-1;){const l=r.slice(0,s);r=r.slice(s+2);const g=ne(l);g&&(yield g)}}}finally{try{t.releaseLock()}catch{}}}function ne(e){let t=null,i=null,r=[];for(const o of e.split(`
|
|
8
8
|
`))o.startsWith("event:")?t=o.slice(6).trim():o.startsWith("id:")?i=o.slice(3).trim():o.startsWith("data:")&&r.push(o.slice(5).trim());const c=r.join(`
|
|
9
|
-
`);if(!t)return null;if(t==="log"){if(!i)return null;let o;try{o=JSON.parse(c)}catch{return null}return{type:"log",id:i,data:o}}if(t==="heartbeat"){let o=null,s=null;try{const l=JSON.parse(c);o=typeof l.ts=="string"?l.ts:null,s=
|
|
10
|
-
`,"utf-8"),await
|
|
9
|
+
`);if(!t)return null;if(t==="log"){if(!i)return null;let o;try{o=JSON.parse(c)}catch{return null}return{type:"log",id:i,data:o}}if(t==="heartbeat"){let o=null,s=null;try{const l=JSON.parse(c);o=typeof l.ts=="string"?l.ts:null,s=Me(l.hwm)}catch{}return{type:"heartbeat",ts:o,hwm:s}}if(t==="bookmark"){let o=null;try{const s=JSON.parse(c);o=typeof s.as_of=="string"?s.as_of:null}catch{}return{type:"bookmark",as_of:o}}if(t==="eviction"){let o=null,s=null;try{const l=JSON.parse(c);o=typeof l.cube_id=="string"?l.cube_id:null,s=typeof l.reason=="string"?l.reason:null}catch{}return{type:"eviction",cube_id:o,reason:s}}return{type:"unknown",raw:e}}function Me(e){if(!e||typeof e!="object")return null;const t=e;return typeof t.id=="string"&&t.id.length>0&&typeof t.created_at=="string"&&t.created_at.length>0?{id:t.id,created_at:t.created_at}:null}function C(e,t){const i=Date.parse(e.created_at),r=Date.parse(t.created_at);return Number.isFinite(i)&&Number.isFinite(r)&&i!==r?i-r:e.created_at!==t.created_at?e.created_at<t.created_at?-1:1:e.id.localeCompare(t.id)}function re(e){if(e.data?.visibility==="direct"||e.data?.kind==="ack")return null;const t=e.data?.created_at;return typeof t=="string"&&t.length>0?{id:e.id,created_at:t}:null}function Ne(e,t){return!e||typeof e!="object"?{id:t}:typeof e.id=="string"&&e.id.length>0?e:{...e,id:t}}function $e(...e){for(const t of e)if(typeof t=="string"&&t.length>0)return t;return""}function Fe(e){const t=typeof e.created_at=="string"?new Date(e.created_at).toISOString():new Date().toISOString(),i=e.drone_label??"?",r=e.role_name??"?",c=typeof e.message=="string"?e.message:"",o=$e(e.id,e.entry_id),s=o?`[entry_id: ${o}] `:"",l=c.replace(/\r\n|\r|\n/g," \u23CE ");return`${t} ${i} (${r}): ${s}${l}`}function Pe(e){const t=new Date().toISOString(),i=e&&e.trim().length>0?e:"evicted from cube";return`${t} SYSTEM (eviction): ${le} ${i} \u2014 confirm with any borg_* call; on DRONE_EVICTED (410) shut down: print the terminal message, TaskStop the inbox Monitor, do NOT reschedule /loop.`}async function We(e,t,i){const r=U(e,t);await Be(r,i,W)}async function Be(e,t,i=W,r=i*2){await E.mkdir(L.dirname(e),{recursive:!0}),await E.appendFile(e,t+`
|
|
10
|
+
`,"utf-8"),await je(e,i,r)}async function je(e,t,i=t){if(!Number.isInteger(t)||t<1)throw new Error("maxLines must be a positive integer");if(!Number.isInteger(i)||i<t)throw new Error("trimThresholdLines must be an integer >= maxLines");const c=(await E.readFile(e,"utf-8")).split(/\r?\n/);if(c.at(-1)===""&&c.pop(),c.length<=i)return;const o=c.slice(-t),s=L.join(L.dirname(e),`.${L.basename(e)}.${process.pid}.${Date.now()}.tmp`);try{await E.writeFile(s,o.join(`
|
|
11
11
|
`)+`
|
|
12
|
-
`,"utf-8"),await E.rename(s,e)}catch(l){throw await E.rm(s,{force:!0}),l}}function
|
|
12
|
+
`,"utf-8"),await E.rename(s,e)}catch(l){throw await E.rm(s,{force:!0}),l}}function Ve(e,t,i){if(t&&e.includes(`[entry_id: ${t}]`))return!0;const r=t?i.replace(`[entry_id: ${t}] `,""):i;return!!(r&&e.split(/\r?\n/).includes(r))}async function Ge(e,t,i,r){const c=U(e,t);let o;try{o=await E.readFile(c,"utf-8")}catch(s){if(s?.code==="ENOENT")return!1;throw s}return Ve(o,i,r)}function oe(e){return new Promise(t=>setTimeout(t,e))}export{W as INBOX_TAIL_LINES_CAP,nt as INBOX_TAIL_TRIM_THRESHOLD_LINES,at as __resetCodexHeartbeatForTest,ot as __resetStreamStateForTest,ct as __runLoopForTest,it as __setCodexReArmDelayForTest,Be as appendCappedInboxLine,ke as classifyRunLoopHealth,C as compareBroadcastHwm,j as ensureCodexHeartbeatStarted,Pe as formatEvictionSentinelLine,Fe as formatInboxLine,rt as getStreamStatus,Ve as inboxRawHasEntry,Le as parseSSE,tt as setModuleInjectOpenCode,st as startLogStream,V as stopCodexHeartbeat,te as streamOnce,lt as streamOnceIfOwner,je as trimInboxFileToRecentLines};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface ConnectDeps {
|
|
2
|
+
serverUrl: string;
|
|
3
|
+
directory: string;
|
|
4
|
+
droneLabel: string;
|
|
5
|
+
cubeName: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function connectOpenCodeDrone(deps: ConnectDeps): Promise<void>;
|
|
8
|
+
export declare function injectOpenCodeEntry(text: string): Promise<boolean>;
|
|
9
|
+
export declare function probeOpenCodeDroneArmed(): Promise<boolean | null>;
|
|
10
|
+
export declare function disconnectOpenCodeDrone(): void;
|
|
11
|
+
export declare function getOpenCodeConnectionState(): {
|
|
12
|
+
connected: boolean;
|
|
13
|
+
sessionId: string | null;
|
|
14
|
+
totalEntriesInjected: number;
|
|
15
|
+
};
|
|
16
|
+
export declare function computeOpenCodePort(droneId: string, base?: number): number;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=opencode-drone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createOpencodeClient as s}from"@opencode-ai/sdk";let e=null;async function i(t){try{const r=s({baseUrl:t.serverUrl}),n=await r.session.create({body:{title:`borg-drone: ${t.droneLabel} on ${t.cubeName}`}});if(!n.data){e=null;return}e={client:r,sessionId:n.data.id,directory:t.directory,connected:!0,totalEntriesInjected:0}}catch{e=null}}async function l(t){if(!e?.connected)return!1;try{return await e.client.session.prompt({path:{id:e.sessionId},body:{parts:[{type:"text",text:t}]}}),e.totalEntriesInjected++,!0}catch{return e.connected=!1,!1}}async function d(){if(!e?.connected)return null;try{return(await e.client.path.get()).response.status===200}catch{return!1}}function u(){e=null}function p(){return{connected:e?.connected??!1,sessionId:e?.sessionId??null,totalEntriesInjected:e?.totalEntriesInjected??0}}function f(t,r=14096){let n=0;for(let o=0;o<t.length;o++){const c=t.charCodeAt(o);n=(n<<5)-n+c,n|=0}return r+Math.abs(n)%1024}export{f as computeOpenCodePort,i as connectOpenCodeDrone,u as disconnectOpenCodeDrone,p as getOpenCodeConnectionState,l as injectOpenCodeEntry,d as probeOpenCodeDroneArmed};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const BORG_PLUGIN_SOURCE = "\n// Borg MCP orientation plugin - installed by borg assimilate\nexport default async function (ctx) {\n const serverUrl = ctx.serverUrl?.toString() ?? '';\n\n if (serverUrl) {\n try {\n const homeDir = process.env.HOME || process.env.USERPROFILE || '';\n const markerDir = homeDir + '/.config/borgmcp';\n const marker = markerDir + '/opencode-server';\n const encoder = new TextEncoder();\n ctx.$.write(marker, encoder.encode(serverUrl));\n } catch {}\n }\n\n return {\n event: async ({ event }) => {\n if (event.type === 'session.created') {\n const sessionId = event.properties.info?.id;\n if (!sessionId) return;\n try {\n await ctx.client.session.prompt({\n path: { id: sessionId },\n body: {\n noReply: true,\n parts: [{ type: 'text', text: '## Borg Cube Context\\nYou are operating in a borgmcp.ai multi-agent cube. Use borg_regen to get full context and recent activity.' }]\n }\n });\n } catch {}\n }\n },\n 'experimental.session.compacting': async (_input, output) => {\n output.context.push('## Borg Cube\\nYou are in a borgmcp.ai multi-agent cube. Use borg_regen to refresh context and see recent activity.');\n },\n };\n}\n";
|
|
2
|
+
export declare function installBorgPlugin(): void;
|
|
3
|
+
//# sourceMappingURL=opencode-plugin.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import e from"node:fs";import i from"node:os";import n from"node:path";const o=`
|
|
2
|
+
// Borg MCP orientation plugin - installed by borg assimilate
|
|
3
|
+
export default async function (ctx) {
|
|
4
|
+
const serverUrl = ctx.serverUrl?.toString() ?? '';
|
|
5
|
+
|
|
6
|
+
if (serverUrl) {
|
|
7
|
+
try {
|
|
8
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
|
|
9
|
+
const markerDir = homeDir + '/.config/borgmcp';
|
|
10
|
+
const marker = markerDir + '/opencode-server';
|
|
11
|
+
const encoder = new TextEncoder();
|
|
12
|
+
ctx.$.write(marker, encoder.encode(serverUrl));
|
|
13
|
+
} catch {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
event: async ({ event }) => {
|
|
18
|
+
if (event.type === 'session.created') {
|
|
19
|
+
const sessionId = event.properties.info?.id;
|
|
20
|
+
if (!sessionId) return;
|
|
21
|
+
try {
|
|
22
|
+
await ctx.client.session.prompt({
|
|
23
|
+
path: { id: sessionId },
|
|
24
|
+
body: {
|
|
25
|
+
noReply: true,
|
|
26
|
+
parts: [{ type: 'text', text: '## Borg Cube Context\\nYou are operating in a borgmcp.ai multi-agent cube. Use borg_regen to get full context and recent activity.' }]
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
} catch {}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
'experimental.session.compacting': async (_input, output) => {
|
|
33
|
+
output.context.push('## Borg Cube\\nYou are in a borgmcp.ai multi-agent cube. Use borg_regen to refresh context and see recent activity.');
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
`;function u(){const r=n.join(i.homedir(),".config","opencode","plugins"),t=n.join(r,"borg-orient.js");try{if(e.existsSync(t)&&e.readFileSync(t,"utf-8")===o)return;e.mkdirSync(r,{recursive:!0}),e.writeFileSync(t,o,"utf-8")}catch{}}export{o as BORG_PLUGIN_SOURCE,u as installBorgPlugin};
|
package/dist/regen-format.d.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function parseHookSource(raw: string): string | null;
|
|
25
25
|
/** The agent runtime a session runs under — drives the wake-path branch. */
|
|
26
|
-
export type AgentKind = 'claude' | 'codex';
|
|
26
|
+
export type AgentKind = 'claude' | 'codex' | 'opencode';
|
|
27
27
|
/**
|
|
28
28
|
* The agent-branched WAKE-PATH ARMING sub-block (gh#929/gh#927) — the single
|
|
29
29
|
* shared "re-establish your wake path" instruction reused by the launch
|
package/dist/regen.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{regen as i,listCubes as a}from"./remote-client.js";import{getActiveCube as c,inboxPathForDrone as u}from"./cubes.js";import{parseHookSource as h,formatLeanOrientation as l,resolveLeanIdentity as p}from"./regen-format.js";import{
|
|
2
|
+
import{regen as i,listCubes as a}from"./remote-client.js";import{getActiveCube as c,inboxPathForDrone as u}from"./cubes.js";import{parseHookSource as h,formatLeanOrientation as l,resolveLeanIdentity as p}from"./regen-format.js";import{resolveSessionAgentKind as b}from"./codex-app-wake.js";import{handleVersionFlag as g}from"./version.js";import{gateAllowsActivation as m}from"./launch-gate.js";async function d(){if(process.stdin.isTTY)return"";try{const e=[];for await(const t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf-8")}catch{return""}}async function f(){if(g(),!m("borg-regen SessionStart hook"))return;const e=h(await d()),t=await c();if(!t){await w();return}const o=u(t.cubeId,t.droneId),n=b();let s=null;try{s=await i(t.sessionToken,t.apiUrl)}catch{s=null}const r=p(t,s);process.stdout.write(l({...r,inboxPath:o,agentKind:n,source:e})+`
|
|
3
3
|
`)}async function w(){const e=[];e.push("# Borg MCP \u2014 not connected to a cube"),e.push(""),e.push("The borg MCP server is installed in this project but no cube has been joined here yet."),e.push(""),e.push("**Drone instructions:**"),e.push("1. **Tell the user** which cubes exist on their account (see the list below) and ask them which one to join, or whether to create a new cube. This is the first thing you should surface in this session."),e.push("2. **Do not** call `borg_assimilate`, `borg_create-cube`, or any other state-changing borg tool until the user has chosen."),e.push("3. The user may also decline to use borg in this project at all \u2014 that's a valid choice; just stop suggesting it."),e.push("");try{const{cubes:t}=await a();if(t.length>0){e.push("## Cubes on your account");for(const o of t)e.push(`- **${o.name}** (id: ${o.id})`)}else e.push("## Cubes on your account"),e.push("_(none yet \u2014 offer to create one via `borg_create-cube` once the user confirms)_")}catch(t){e.push("## Cubes on your account"),e.push("_(could not list \u2014 "+(t?.message??String(t))+")_")}e.push(""),e.push("## Tools you can call once the user has chosen"),e.push('- Join an existing cube: `borg_assimilate cube_name="<their choice>"`'),e.push('- Create a new cube: `borg_create-cube name="<name>" cube_directive="<markdown>"` (optionally `template="software-dev"`)'),e.push("- See available templates: `borg_list-templates`"),process.stdout.write(e.join(`
|
|
4
4
|
`)+`
|
|
5
5
|
`)}f().catch(e=>{const t=e?.message??String(e);process.stderr.write(`borg-regen: ${t}
|
package/dist/remote-client.d.ts
CHANGED
|
@@ -87,7 +87,7 @@ export declare function assimilate(cubeNameOrSelector: string | {
|
|
|
87
87
|
role_name?: string;
|
|
88
88
|
prior_drone_id?: string;
|
|
89
89
|
model?: string | null;
|
|
90
|
-
}, apiUrl?: string, hostname?: string | null, agentKind?: 'claude' | 'codex' | null): Promise<{
|
|
90
|
+
}, apiUrl?: string, hostname?: string | null, agentKind?: 'claude' | 'codex' | 'opencode' | null): Promise<{
|
|
91
91
|
cube: {
|
|
92
92
|
id: string;
|
|
93
93
|
owner_id: string;
|
package/dist/roster-render.d.ts
CHANGED
|
@@ -26,7 +26,7 @@ export interface RosterDrone {
|
|
|
26
26
|
* joined before the column existed or via a launcher path that didn't
|
|
27
27
|
* forward the kind. Rendered as `(Role, kind)` when present.
|
|
28
28
|
*/
|
|
29
|
-
agent_kind?: 'claude' | 'codex' | null;
|
|
29
|
+
agent_kind?: 'claude' | 'codex' | 'opencode' | null;
|
|
30
30
|
regen_count?: number | null;
|
|
31
31
|
wake_path?: 'live' | 'degraded' | 'deaf' | null;
|
|
32
32
|
wake_path_alert_class?: 'dead' | 'post-blocked' | 'presumed-dead' | 'systemic-post-block' | 'wake-path-deaf' | 'independent' | null;
|
package/dist/setup-confirm.d.ts
CHANGED
|
@@ -26,10 +26,12 @@ export interface ConfigMutationTarget {
|
|
|
26
26
|
* agent CLIs. Paths mirror `config-utils.ts`:
|
|
27
27
|
* Claude Code: ~/.claude.json (MCP server) + ~/.claude/settings.json (hook)
|
|
28
28
|
* Codex: ~/.codex/config.toml (MCP server) + ~/.codex/hooks.json (hooks)
|
|
29
|
+
* OpenCode: ~/.config/opencode/opencode.json (MCP server)
|
|
29
30
|
*/
|
|
30
31
|
export declare function configMutationTargets(deps: {
|
|
31
32
|
claude: boolean;
|
|
32
33
|
codex: boolean;
|
|
34
|
+
opencode?: boolean;
|
|
33
35
|
}): ConfigMutationTarget[];
|
|
34
36
|
/**
|
|
35
37
|
* Disclosure text: the files Step-1 will write + an undo note. Pure so SR
|
|
@@ -96,8 +98,10 @@ export declare function parseYesFlag(argv: string[]): boolean;
|
|
|
96
98
|
export declare function setupMutationPending(deps: {
|
|
97
99
|
claude: boolean;
|
|
98
100
|
codex: boolean;
|
|
101
|
+
opencode: boolean;
|
|
99
102
|
claudeMcpConfigured: boolean;
|
|
100
103
|
codexMcpConfigured: boolean;
|
|
104
|
+
opencodeMcpConfigured: boolean;
|
|
101
105
|
claudeHookPending: boolean;
|
|
102
106
|
codexHookPending: boolean;
|
|
103
107
|
}): boolean;
|
package/dist/setup-confirm.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function r(e){const o=[];return e.claude&&(o.push({file:"~/.claude.json",change:"registers the borg MCP server"}),o.push({file:"~/.claude/settings.json",change:"adds a UserPromptSubmit hook"})),e.codex&&(o.push({file:"~/.codex/config.toml",change:"registers the borg MCP server"}),o.push({file:"~/.codex/hooks.json",change:"adds SessionStart + UserPromptSubmit hooks"})),o}function t(e){const o=[];o.push("borg setup will register the borg MCP server in your agent config:");for(const n of e)o.push(` \u2022 ${n.file} (${n.change})`);return o.push('These changes are additive and reversible \u2014 remove the "borg" entries to undo.'),o.join(`
|
|
2
|
-
`)}async function i(e){return!e.isTTY||e.yes||await e.confirm()?"proceed":"abort"}function
|
|
1
|
+
function r(e){const o=[];return e.claude&&(o.push({file:"~/.claude.json",change:"registers the borg MCP server"}),o.push({file:"~/.claude/settings.json",change:"adds a UserPromptSubmit hook"})),e.codex&&(o.push({file:"~/.codex/config.toml",change:"registers the borg MCP server"}),o.push({file:"~/.codex/hooks.json",change:"adds SessionStart + UserPromptSubmit hooks"})),e.opencode&&o.push({file:"~/.config/opencode/opencode.json",change:"registers the borg MCP server (with BORG_SESSION activation)"}),o}function t(e){const o=[];o.push("borg setup will register the borg MCP server in your agent config:");for(const n of e)o.push(` \u2022 ${n.file} (${n.change})`);return o.push('These changes are additive and reversible \u2014 remove the "borg" entries to undo.'),o.join(`
|
|
2
|
+
`)}async function i(e){return!e.isTTY||e.yes||await e.confirm()?"proceed":"abort"}function c(e){return e.includes("--yes")||e.includes("-y")}function s(e){return e.claude&&(!e.claudeMcpConfigured||e.claudeHookPending)||e.codex&&(!e.codexMcpConfigured||e.codexHookPending)||e.opencode&&!e.opencodeMcpConfigured}export{r as configMutationTargets,i as confirmConfigMutation,t as formatConfigMutationDisclosure,c as parseYesFlag,s as setupMutationPending};
|
package/dist/setup.js
CHANGED
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
\u25FC Borg MCP Setup Wizard \u25FC`));const
|
|
4
|
-
`)),console.error(e.yellow("Please install Claude Code or
|
|
5
|
-
`)),process.exit(1)),console.log(e.blue("\u25FC Agent CLI Integration"));const
|
|
2
|
+
import x from"prompts";import e from"chalk";import v from"open";import p from"which";import{authenticateWithGoogle as A}from"./auth.js";import{checkSubscriptionStatus as i,createSubscription as M,probeSession as H}from"./remote-client.js";import{setupActionForSession as R}from"./setup-action.js";import{confirmConfigMutation as O,configMutationTargets as U,formatConfigMutationDisclosure as D,parseYesFlag as T,setupMutationPending as I}from"./setup-confirm.js";import{retrySubscriptionCheck as F}from"./subscription-retry.js";import{addMcpServer as L,addUserPromptSubmitHook as q,addCodexMcpServer as Y,addCodexSessionStartHook as N,addCodexUserPromptSubmitHook as W,addOpenCodeMcpServer as j,isMcpServerConfigured as B,isCodexMcpServerConfigured as E,isOpenCodeMcpServerConfigured as G,isSessionStartHookRegistered as z,isUserPromptSubmitHookRegistered as Q,isCodexSessionStartHookRegistered as V,isCodexUserPromptSubmitHookRegistered as J,removeSessionStartHook as K}from"./config-utils.js";import{handleVersionFlag as X}from"./version.js";import{initDebugFromArgv as Z}from"./debug.js";async function _(){Z(process.argv),X(),console.log(e.blue.bold(`
|
|
3
|
+
\u25FC Borg MCP Setup Wizard \u25FC`));const a=process.argv.includes("--no-browser")||process.argv.includes("--device");let t=null,n=null,s=null;try{t=p.sync("claude")}catch{}try{n=p.sync("codex")}catch{}try{s=p.sync("opencode")}catch{}t&&console.log(e.gray(`Found Claude CLI: ${t}`)),n&&console.log(e.gray(`Found Codex CLI: ${n}`)),s&&console.log(e.gray(`Found OpenCode CLI: ${s}`)),(t||n||s)&&console.log(""),!t&&!n&&!s&&(console.error(e.red(`\u25FC No supported agent CLI found
|
|
4
|
+
`)),console.error(e.yellow("Please install Claude Code, Codex, or OpenCode first:")),console.error(e.gray(" Claude Code: https://claude.ai/download")),console.error(e.gray(" Codex: https://developers.openai.com/codex")),console.error(e.gray(` OpenCode: https://opencode.ai
|
|
5
|
+
`)),process.exit(1)),console.log(e.blue("\u25FC Agent CLI Integration"));const $=T(process.argv),l=t!==null,d=n!==null,h=s!==null,m=B(),b=E(),f=G(),y=l&&z(),C=l&&!Q(),k=d&&!V(),w=d&&!J();if(I({claude:l,codex:d,opencode:h,claudeMcpConfigured:m,codexMcpConfigured:b,opencodeMcpConfigured:f,claudeHookPending:y||C,codexHookPending:k||w})&&(console.log(D(U({claude:l,codex:d,opencode:h}))),await O({isTTY:process.stdin.isTTY===!0,yes:$,confirm:async()=>{const{proceed:o}=await x({type:"confirm",name:"proceed",message:"Continue with these changes?",initial:!0});return o===!0}})==="abort"&&(console.log(e.yellow(`
|
|
6
6
|
\u25FC Setup cancelled \u2014 no changes made.
|
|
7
|
-
`)),process.exit(0))),console.log(""),t)try{
|
|
7
|
+
`)),process.exit(0))),console.log(""),t)try{m||L(),y&&K(),C&&q(),console.log(e.green("\u25FC borg configured for Claude Code"))}catch(r){console.error(e.red(`
|
|
8
8
|
\u25FC Failed to configure Claude Code: ${r.message}
|
|
9
|
-
`)),process.exit(1)}if(n)try{
|
|
9
|
+
`)),process.exit(1)}if(n)try{b||Y(),k&&N(),w&&W(),console.log(e.green("\u25FC borg configured for Codex"))}catch(r){console.error(e.red(`
|
|
10
10
|
\u25FC Failed to configure Codex: ${r.message}
|
|
11
|
-
`)),process.exit(1)}
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
`)),process.exit(1)}if(s)try{f||j(),console.log(e.green("\u25FC borg configured for OpenCode"))}catch(r){console.error(e.red(`
|
|
12
|
+
\u25FC Failed to configure OpenCode: ${r.message}
|
|
13
|
+
`)),process.exit(1)}console.log(""),console.log(e.blue("\u25FC Google Authentication"));const S=R(await H());if(S==="skip")console.log(e.green(`\u25FC Already signed in
|
|
14
|
+
`));else if(S==="retry")console.error(e.yellow(`
|
|
15
|
+
\u25FC Could not reach Google to verify your session (network issue).`)),console.error(e.yellow("Re-run `borg setup` when your connection is back.\n")),process.exit(1);else try{await A(a?{noBrowser:!0}:void 0)}catch(r){console.error(e.red(`
|
|
14
16
|
\u25FC Authentication failed: ${r.message}
|
|
15
|
-
`)),console.error(e.yellow("Re-run `borg setup` to try again.\n")),process.exit(1)}console.log(e.blue("\u25FC Subscription Check"));let
|
|
17
|
+
`)),console.error(e.yellow("Re-run `borg setup` to try again.\n")),process.exit(1)}console.log(e.blue("\u25FC Subscription Check"));let c;try{c=await i()}catch(r){console.error(e.yellow(`
|
|
16
18
|
\u25FC Subscription check failed: ${r.message}`)),console.error(e.gray(`\u25FC Retrying before falling back to the Free tier...
|
|
17
|
-
`)),
|
|
19
|
+
`)),c={hasAccess:!1}}if(c=await F(c,{check:i,sleep:r=>new Promise(o=>setTimeout(o,r)),onRetry:(r,o)=>console.log(e.gray(`\u25FC Checking subscription... (attempt ${r}/${o})`))}),c.hasAccess)if(console.log(e.green("\u25FC Active subscription found")),c.expiresAt){const r=new Date(c.expiresAt);console.log(e.gray(` Expires: ${r.toLocaleDateString()}
|
|
18
20
|
`))}else console.log("");else{console.log(e.green("\u25FC You're on the Free tier \u2014 permanent, no card needed: 1 cube + 3 agent sessions + 100 req/hr.")),console.log(e.gray(`\u25FC Start using borgmcp right now. Upgrade any time: $1/month per cube, each cube adds 8 pooled agent sessions + 1000 req/hr.
|
|
19
|
-
`));const{subscribeMethod:r}=await
|
|
21
|
+
`));const{subscribeMethod:r}=await x({type:"select",name:"subscribeMethod",message:"You're ready on the Free tier. Want to do more?",choices:[{title:"\u25FC Continue on the Free tier (recommended)",value:"skip",description:"Start now \u2014 1 cube, 3 agent sessions, 100 req/hr. No payment required."},{title:"\u25FC Upgrade to Cube tier \u2014 $1/month per cube",value:"web",description:"Each cube adds 8 pooled agent sessions + 1000 req/hr. Opens the subscribe page in your browser."},{title:"\u25FC Quick Stripe checkout",value:"stripe",description:"Fast upgrade checkout in the browser"},{title:"\u25FC I already subscribed \u2014 re-check",value:"recheck",description:"Re-check now \u2014 a just-completed subscription can take a moment to activate"}]});switch(r===void 0&&console.log(e.yellow(`
|
|
20
22
|
\u25FC No subscription option selected \u2014 continuing on the Free tier.
|
|
21
23
|
`)),r){case"web":console.log(e.blue(`
|
|
22
|
-
\u25FC Opening: https://borgmcp.ai/subscribe`));try{await
|
|
23
|
-
`)),await
|
|
24
|
+
\u25FC Opening: https://borgmcp.ai/subscribe`));try{await v("https://borgmcp.ai/subscribe"),console.log(e.gray(`\u25FC Waiting for subscription (checking every 5s for 2 min)...
|
|
25
|
+
`)),await P()}catch(o){console.error(e.yellow(`
|
|
24
26
|
\u25FC ${o.message}`)),console.log(e.green(`\u25FC Continuing on the Free tier. Upgrade any time from https://borgmcp.ai/subscribe.
|
|
25
|
-
`))}break;case"stripe":try{const o=await
|
|
26
|
-
\u25FC Opening Stripe: ${o}`)),await
|
|
27
|
-
`)),await
|
|
27
|
+
`))}break;case"stripe":try{const o=await M();console.log(e.blue(`
|
|
28
|
+
\u25FC Opening Stripe: ${o}`)),await v(o),console.log(e.gray(`\u25FC Waiting for subscription...
|
|
29
|
+
`)),await P()}catch(o){console.error(e.red(`
|
|
28
30
|
\u25FC Failed to create checkout: ${o.message}
|
|
29
31
|
`)),console.log(e.green(`\u25FC Continuing on the Free tier. Upgrade any time from https://borgmcp.ai/subscribe.
|
|
30
|
-
`))}break;case"recheck":try{let o;try{o=await i()}catch{o={hasAccess:!1}}o=await
|
|
32
|
+
`))}break;case"recheck":try{let o;try{o=await i()}catch{o={hasAccess:!1}}o=await F(o,{check:i,sleep:g=>new Promise(u=>setTimeout(u,g)),onRetry:(g,u)=>console.log(e.gray(`\u25FC Re-checking subscription... (attempt ${g}/${u})`))}),o.hasAccess?console.log(e.green(`
|
|
31
33
|
\u25FC Subscription found!
|
|
32
34
|
`)):console.log(e.yellow(`
|
|
33
35
|
\u25FC No subscription found \u2014 continuing on the Free tier.
|
|
@@ -37,9 +39,9 @@ import k from"prompts";import e from"chalk";import w from"open";import C from"wh
|
|
|
37
39
|
`))}break;case"skip":console.log(e.green(`
|
|
38
40
|
\u25FC You're all set on the Free tier: 1 cube, 3 agent sessions, 100 req/hr.
|
|
39
41
|
`));break}}console.log(e.green.bold(`Setup complete!
|
|
40
|
-
`)),console.log(e.yellow(`\u{1F504} Restart Claude Code/Codex (or open a new session) for the changes to take effect.
|
|
42
|
+
`)),console.log(e.yellow(`\u{1F504} Restart Claude Code / Codex / OpenCode (or open a new session) for the changes to take effect.
|
|
41
43
|
`)),console.log(e.gray("\u25FC Next steps:")),console.log(e.gray('1. cd into your project, then run "borg assimilate" to join a cube')),console.log(e.gray(" (this creates/joins the cube and launches your agent)")),console.log(e.gray(`2. Manage cubes and subscription at https://borgmcp.ai/dashboard
|
|
42
|
-
`))}async function
|
|
43
|
-
`));return}}catch{}}throw new Error("Timeout - Run setup again after subscribing")}
|
|
44
|
-
\u25FC Setup failed: ${
|
|
44
|
+
`))}async function P(){for(let t=0;t<24;t++){await new Promise(n=>setTimeout(n,5e3));try{if((await i()).hasAccess){console.log(e.green(`\u25FC Subscription activated!
|
|
45
|
+
`));return}}catch{}}throw new Error("Timeout - Run setup again after subscribing")}_().catch(a=>{console.error(e.red(`
|
|
46
|
+
\u25FC Setup failed: ${a.message}
|
|
45
47
|
`)),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Coordinate AI coding agents in shared cubes. Works with Claude Code and Codex. Create projects, assign roles, and share a live activity log.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
|
55
55
|
"@napi-rs/keyring": "^1.3.0",
|
|
56
|
+
"@opencode-ai/sdk": "^1.17.13",
|
|
56
57
|
"chalk": "^5.3.0",
|
|
57
58
|
"open": "^10.0.0",
|
|
58
59
|
"prompts": "^2.4.2",
|