borgmcp 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assimilate-cmd.js +32 -28
- package/dist/assimilate-welcome.js +2 -2
- package/dist/auth.js +8 -8
- package/dist/claude.js +5 -5
- package/dist/get-started.d.ts +20 -0
- package/dist/get-started.js +2 -0
- package/dist/postinstall-banner.d.ts +2 -0
- package/dist/postinstall-banner.js +2 -0
- package/dist/postinstall.d.ts +4 -1
- package/dist/postinstall.js +2 -5
- package/dist/setup.js +1 -1
- package/dist/token-store.js +1 -1
- package/package.json +1 -1
package/dist/assimilate-cmd.js
CHANGED
|
@@ -1,41 +1,45 @@
|
|
|
1
|
-
import{dirname as
|
|
1
|
+
import{dirname as J,basename as S,join as H}from"node:path";import{randomUUID as V}from"node:crypto";import{roleSlug as Q,matchRoleByName as X,pickDefaultRole as Z}from"./role-resolver.js";import{deriveCubeName as ee,parseGitRemote as te,sanitizeRemoteUrl as re}from"./cube-name.js";import{validateName as O}from"./name-validator.js";import{renderAssimilationWelcome as ne}from"./assimilate-welcome.js";import{shellEscape as oe}from"./shell-escape.js";import{withCodexCwdArg as ie}from"./codex-remote.js";import{buildAgentKickoffPrompt as ae,recordCodexWakeTarget as se,socketPathFromRemoteArgs as le}from"./codex-launch.js";import{perWorktreeBranchName as F,adoptWorktree as ce}from"./worktree-lifecycle.js";import{codexBorgSessionConfigArgs as ue}from"./launch-gate.js";async function Te(r,e){if(r.role!==void 0){const t=O(r.role);if(!t.ok)return e.stderr(t.error+`
|
|
2
2
|
`),1}if(r.flags.worktree!==void 0){const t=O(r.flags.worktree);if(!t.ok)return e.stderr(t.error+`
|
|
3
|
-
`),1}let
|
|
4
|
-
`)}}let s=null;if(
|
|
5
|
-
`)
|
|
6
|
-
`),
|
|
3
|
+
`),1}let i=await e.getCachedAuth();if(!i){if(!e.isTTY()&&!r.flags.yes)return e.stderr("borg setup required and stdin is non-interactive. Run `borg setup` first in an interactive terminal, then `borg assimilate`.\n"),1;i=await e.runSetup()}const a=e.findProjectRoot(e.cwd());let n;if(r.flags.cubeName)n=r.flags.cubeName;else{const t=e.runSync("git",["remote","get-url","origin"],a),o=t.status===0?t.stdout:null;if(n=ee(a,o),o){const c=re(o),m=c?te(c):null;c&&!m&&n&&e.stderr(`couldn't parse git remote '${c}' \u2014 using directory name '${n}' as cube name
|
|
4
|
+
`)}}let s=null;if(n&&n.includes("@")&&n.includes(":")){const t=n.lastIndexOf(":");s={ownerEmail:n.substring(0,t),cubeName:n.substring(t+1)},n=s.cubeName}const $=e.cwd();e.stderr(`Checking your cubes\u2026
|
|
5
|
+
`);let R;try{R=await e.listCubes(i.apiUrl,i.token)}catch(t){const o=t instanceof Error?t.message:String(t);if(o.includes("Authentication required")||o.includes("Authentication expired"))e.stderr(`Re-authenticating...
|
|
6
|
+
`),i=await e.runSetup(),R=await e.listCubes(i.apiUrl,i.token);else throw t}const E=R.find(t=>t.name===n);if(!E&&s)return e.stderr(`No cube named '${s.cubeName}' accessible to you owned by '${s.ownerEmail}'. Did you accept their invite? See borgmcp.ai/dashboard.
|
|
7
|
+
`),1;let l,A;if(E)l=await e.getCube(i.apiUrl,i.token,E.id),A=!1;else{let t;if(r.flags.template)t=r.flags.template;else if(r.flags.noTemplate)t=void 0;else if(e.isTTY())if(r.flags.yes)t="starter";else{const o=await e.listTemplates(i.apiUrl,i.token),c=["First drone joining a new cube. Apply a template?"];o.forEach((y,k)=>{const C=k===0?" (default)":"";c.push(` ${k+1}) ${y.name}${C} \u2014 ${y.description}`)}),c.push(` ${o.length+1}) skip \u2014 no template`);const m=(await e.prompt(c.join(`
|
|
7
8
|
`)+`
|
|
8
|
-
[1]: `)).trim(),w=m===""?1:parseInt(m,10);if(Number.isNaN(w)||w<1||w>
|
|
9
|
-
`),1;t=w<=
|
|
9
|
+
[1]: `)).trim(),w=m===""?1:parseInt(m,10);if(Number.isNaN(w)||w<1||w>o.length+1)return e.stderr(`invalid choice "${m}"
|
|
10
|
+
`),1;t=w<=o.length?o[w-1].name:void 0}else{if(!r.flags.yes)return e.stderr(`cube creation needs a template choice but stdin is non-interactive.
|
|
10
11
|
Pass --template <name>, --no-template, or --yes (defaults to starter).
|
|
11
|
-
`),1;t="starter"}
|
|
12
|
+
`),1;t="starter"}e.stderr(n?`Creating cube '${n}'\u2026
|
|
13
|
+
`:`Creating your cube\u2026
|
|
14
|
+
`),l=await e.createCube(i.apiUrl,i.token,t?{name:n??void 0,template:t}:{name:n??void 0}),A=!0}let d;if(r.role!==void 0){if(d=X(l.roles,r.role),!d){const t=l.roles.map(m=>m.name).join(", "),o=ge(r.role,l.roles.map(m=>m.name)),c=o?` Did you mean "${o}"?`:"";return e.stderr(`no role matching "${r.role}" in cube "${l.name}". Available: ${t}.${c}
|
|
12
15
|
(Use --template <name> on first-drone setup or run \`borg:create-role\` from inside Claude.)
|
|
13
|
-
`),1}}else if(
|
|
14
|
-
`),1;const
|
|
15
|
-
`),1;
|
|
16
|
-
`)
|
|
17
|
-
`)
|
|
18
|
-
`)
|
|
16
|
+
`),1}}else if(d=Z(l.roles,{isFirstDrone:A}),!d)return e.stderr(`cube "${l.name}" has no default or human-seat role; cannot infer a role. Either pass a role argument explicitly (e.g. \`borg assimilate builder\`) or run \`borg:create-role\` from inside Claude to set up roles.
|
|
17
|
+
`),1;const p=await e.getActiveCube();let N;if(p&&r.flags.here)if(p.cubeId===l.id)N=p.droneId;else return e.stderr(`this directory already hosts an active drone; remove --here or run from a fresh worktree
|
|
18
|
+
`),1;e.stderr(`Joining cube '${l.name}' as ${d.name}\u2026
|
|
19
|
+
`);let u;try{u=await e.assimilate(i.apiUrl,i.token,{cube_id:l.id,role_id:d.id,hostname:e.getHostname(),...N?{prior_drone_id:N}:{}})}catch(t){const o=t instanceof Error?t.message:String(t);return e.stderr(`assimilate failed: ${o}
|
|
20
|
+
`),1}const v=l.roles.find(t=>t.id===u.role_id)??d;u.reattached?e.stderr(`re-attached to existing seat ${u.drone_label} (session token rotated, no new drone minted)
|
|
21
|
+
`):v.id!==d.id&&e.stderr(`Note: your invite didn't grant the "${d.name}" role \u2014 assimilated as "${v.name}" instead.
|
|
22
|
+
`);const M=r.flags.worktree!==void 0||p!==null&&!r.flags.here;let f=null;if(M){const t=e.runSync("git",["rev-parse","--verify","HEAD"],a);if(t.status!==0)return e.stderr(`sibling worktree spawn requires HEAD pointing at a commit.
|
|
19
23
|
Fix: create at least one commit (\`git commit --allow-empty -m "initial"\`)
|
|
20
24
|
OR: pass --here to skip the sibling spawn and use the current directory
|
|
21
|
-
`),1;e.runSync("git",["fetch","origin"],a);let
|
|
22
|
-
`);const y=
|
|
23
|
-
`),1;e.stderr(`spawned sibling worktree at ${b} on branch ${j} (${
|
|
24
|
-
`),e.chdir(b),e.stderr(me(b,j,a)),f=e.cwd()}try{await e.setActiveCube({cubeId:u.cube_id,droneId:u.drone_id,name:l.name,sessionToken:u.session_token,droneLabel:u.drone_label,apiUrl:
|
|
25
|
+
`),1;e.runSync("git",["fetch","origin"],a);let o="origin/main";e.runSync("git",["rev-parse","--verify","origin/main"],a).status!==0&&e.runSync("git",["rev-parse","--verify","origin/master"],a).status===0&&(o="origin/master");const m=t.stdout.trim(),w=e.runSync("git",["rev-parse",o],a).stdout.trim();m!==w&&e.stderr(`note: local HEAD (${m.slice(0,7)}) differs from ${o} (${w.slice(0,7)}); new worktree will start on ${o}
|
|
26
|
+
`);const y=J(a),k=S(a),C=r.flags.worktree??Q(v.name);let b=H(y,`${k}-${C}`),L=2;for(;e.pathExists(b)||de(e,a,b);)b=H(y,`${k}-${C}-${L}`),L++;const j=F(S(b),k),W=e.runSync("git",["worktree","add","-b",j,b,o],a);if(W.status!==0)return e.stderr(`git worktree add failed: ${B(W.stderr)}
|
|
27
|
+
`),1;e.stderr(`spawned sibling worktree at ${b} on branch ${j} (${o}); original dir is registered as active (edit ~/.config/borgmcp/cubes.json if stale).
|
|
28
|
+
`),e.chdir(b),e.stderr(me(b,j,a)),f=e.cwd()}try{await e.setActiveCube({cubeId:u.cube_id,droneId:u.drone_id,name:l.name,sessionToken:u.session_token,droneLabel:u.drone_label,apiUrl:i.apiUrl})}catch(t){const o=t instanceof Error?t.message:String(t);if(e.stderr(`setActiveCube failed: ${o}
|
|
25
29
|
`),f){const c=e.runSync("git",["worktree","remove","--force",f],a);c.status===0?e.stderr(`rolled back spawned worktree at ${f}
|
|
26
30
|
`):e.stderr(`manual cleanup needed: \`git worktree remove --force ${f}\` (rollback attempt failed: ${B(c.stderr).trim()||"unknown"})
|
|
27
|
-
`)}return 1}e.setTerminalTitle(u.drone_label,l.name);const Y=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(ne(v.name,l.name,Y));const h=await e.resolveCli(r.flags.cli),
|
|
28
|
-
`)}if(!f){e.runSync("git",["fetch","origin","--prune"],
|
|
29
|
-
`),e.stderr(fe(
|
|
31
|
+
`)}return 1}e.setTerminalTitle(u.drone_label,l.name);const Y=e.isTTY()&&!process.env.NO_COLOR&&!process.env.CI;e.stdout(ne(v.name,l.name,Y));const h=await e.resolveCli(r.flags.cli),g=e.cwd();try{e.installProjectSessionHook(g)}catch{e.stderr(`warning: could not install the project-local SessionStart hook in ${g}; it will be re-attempted on the next borg launch
|
|
32
|
+
`)}if(!f){e.runSync("git",["fetch","origin","--prune"],g);const t=F(S(g),S(a)),o=ce(e.runSync,g,t,"origin/main");o.action==="adopted"?(e.stderr(`worktree: adopted branch ${t} at origin/main
|
|
33
|
+
`),e.stderr(fe(g,t))):o.message&&e.stderr(`worktree sync: ${o.message}
|
|
30
34
|
`)}await e.probeMcpReady()||e.stderr(`warning: borg-mcp readiness probe did not complete within the timeout; launching ${h} anyway \u2014 the kickoff prompt's ToolSearch fallback will recover if the MCP server takes longer to start.
|
|
31
|
-
`);const G=e.getInboxPath(u.cube_id,u.drone_id),T=h==="codex"?`borg-wake-${
|
|
32
|
-
`),I="\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."):I="Codex wake-path capability check passed: remote-control socket established for this session.",U=t.args,D=Object.keys(t.env).length>0?t.env:void 0,P=le(t.args),_=t.server?.cleanup??null}x=[ae({cli:h,codexWakeNonce:T,monitorClause:q,codexWakePathClause:I})],h==="codex"&&(x=[...ue(),...U,...ie(x,
|
|
33
|
-
Agent exited. You were working in ${f}; your shell is back in ${
|
|
35
|
+
`);const G=e.getInboxPath(u.cube_id,u.drone_id),T=h==="codex"?`borg-wake-${V()}`:null,q=h==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${G}\` so each event's task-notification title summarizes the new cube log entry (drone label, role, and first ~80 chars of the message body) \u2014 letting you triage events without reading the full body. `:"";let I,U=[],x,D,P=null,_=null;if(h==="codex"){const t=await e.prepareCodexRemoteLaunch();t.warning?(e.stderr(`warning: ${t.warning}
|
|
36
|
+
`),I="\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."):I="Codex wake-path capability check passed: remote-control socket established for this session.",U=t.args,D=Object.keys(t.env).length>0?t.env:void 0,P=le(t.args),_=t.server?.cleanup??null}x=[ae({cli:h,codexWakeNonce:T,monitorClause:q,codexWakePathClause:I})],h==="codex"&&(x=[...ue(),...U,...ie(x,g)]);const z=e.exec(h,x,g,{...D??{},BORG_SESSION:"1"});h==="codex"&&P&&T&&se({deps:e,cubeId:u.cube_id,droneId:u.drone_id,socketPath:P,cwd:g,previewNeedle:T,launchedAtSeconds:Math.floor(Date.now()/1e3)});const K=await z;if(_)try{_()}catch{}return f&&$!==f&&e.stderr(`
|
|
37
|
+
Agent exited. You were working in ${f}; your shell is back in ${$}.
|
|
34
38
|
To return:
|
|
35
39
|
cd ${oe(f)}
|
|
36
|
-
`),K}function me(r,e,
|
|
37
|
-
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 ${
|
|
40
|
+
`),K}function me(r,e,i){return`
|
|
41
|
+
WORKTREE STEERING: You are in worktree ${r} on branch ${e}. Do ALL work HERE \u2014 cut your feature branch (fix/.../feat/...) off ${e} in THIS worktree, use relative paths / your cwd. NEVER \`git -C ${i}\` or operate on the primary checkout ${i}: the same branch can't be checked out in two worktrees, so work created in the primary won't reach your wt-branch without manual surgery (cherry-pick/merge).
|
|
38
42
|
`}function fe(r,e){return`
|
|
39
43
|
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.
|
|
40
|
-
`}function B(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function de(r,e,
|
|
41
|
-
`).some(
|
|
44
|
+
`}function B(r){return r.replace(/[\x00-\x1F\x7F]/g,"")}function de(r,e,i){const a=r.runSync("git",["worktree","list","--porcelain"],e);return a.status!==0?!1:a.stdout.split(`
|
|
45
|
+
`).some(n=>n===`worktree ${i}`)}function ge(r,e){if(e.length===0)return null;const i=r.toLowerCase();let a=null;for(const n of e){const s=he(i,n.toLowerCase());s<=2&&(a===null||s<a.distance)&&(a={name:n,distance:s})}return a?a.name:null}function he(r,e){if(r===e)return 0;if(r.length===0)return e.length;if(e.length===0)return r.length;const i=new Array(e.length+1),a=new Array(e.length+1);for(let n=0;n<=e.length;n++)i[n]=n;for(let n=1;n<=r.length;n++){a[0]=n;for(let s=1;s<=e.length;s++){const $=r[n-1]===e[s-1]?0:1;a[s]=Math.min(a[s-1]+1,i[s]+1,i[s-1]+$)}for(let s=0;s<=e.length;s++)i[s]=a[s]}return i[e.length]}export{Te as runAssimilate,B as safeStderr,ge as suggestRoleName};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const
|
|
2
|
-
`)}export{
|
|
1
|
+
const t="\x1B[32m",r="\x1B[0m";function c(e,o,n){return[`${n?`${t}\u2713${r}`:"\u2713"} Joined as \`${e}\` in cube \`${o}\`.`,"","Next: ask your agent to run the `borg:regen` tool to see your cube.","Add a teammate: run `borg assimilate <role>` in another terminal.","You're set up \u2014 your team can now see you in the cube.",""].join(`
|
|
2
|
+
`)}export{c as renderAssimilationWelcome};
|
package/dist/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{createServer as
|
|
1
|
+
import{createServer as w}from"http";import{URL as _}from"url";import g from"crypto";import P from"open";import{storeIdToken as d,storeRefreshToken as f,getRefreshToken as p}from"./config.js";import{cerr as t}from"./console-prefix.js";import{isNoBrowserEnv as O}from"./auth-env.js";import{requestDeviceCode as I,pollForDeviceToken as x}from"./device-auth.js";class G extends Error{errorCode;errorDescription;constructor(r,s){super(s?`Refresh token invalid (${r}): ${s}`:`Refresh token invalid (${r})`),this.errorCode=r,this.errorDescription=s,this.name="RefreshTokenInvalidError"}}class u extends Error{constructor(r){super(r),this.name="RefreshTransientError"}}const m="675073910799-41pbe12rfhqemidh64h09s4q3e0udpgp.apps.googleusercontent.com",k="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",A="675073910799-6qmi73v5106dj1v0l22j2qnkh5r3e8fq.apps.googleusercontent.com",S="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",L="https://accounts.google.com/o/oauth2/v2/auth",E="https://oauth2.googleapis.com/token",$="https://oauth2.googleapis.com/revoke",T=["openid","email","profile"],Q=8e3,Z=9e3,y=300*1e3,v=y/6e4;function D(){const e=g.randomBytes(32).toString("base64url"),r=g.createHash("sha256").update(e).digest("base64url");return{verifier:e,challenge:r}}async function N(){return new Promise((e,r)=>{const s=w();s.listen(0,()=>{const i=s.address();if(i&&typeof i=="object"){const o=i.port;s.close(()=>e(o))}else s.close(()=>r(new Error("Failed to get assigned port")))}),s.on("error",r)})}async function U(){const e=await N(),r=new Promise((s,i)=>{const o=w((n,c)=>{const a=new _(n.url,`http://localhost:${e}`);if(a.pathname==="/callback"){const h=a.searchParams.get("code"),l=a.searchParams.get("error");if(l){c.writeHead(400,{"Content-Type":"text/html"}),c.end(`
|
|
2
2
|
<html>
|
|
3
3
|
<body>
|
|
4
4
|
<h1>\u25FC Authentication Failed</h1>
|
|
@@ -20,19 +20,19 @@ import{createServer as m}from"http";import{URL as k}from"url";import _ from"cryp
|
|
|
20
20
|
<p>Missing authorization code.</p>
|
|
21
21
|
</body>
|
|
22
22
|
</html>
|
|
23
|
-
`),o.close(),i(new Error("Missing authorization code"))}});o.listen(e,()=>{t(`Callback server listening on http://localhost:${e}`)}),setTimeout(()=>{o.close(),i(new Error(
|
|
23
|
+
`),o.close(),i(new Error("Missing authorization code"))}});o.listen(e,()=>{t(`Callback server listening on http://localhost:${e}`)}),setTimeout(()=>{o.close(),i(new Error(`Authentication timed out after ${v} minutes \u2014 no authorization received from the browser. Re-run \`borg setup\` and complete the Google sign-in in the page that opens.`))},y).unref()});return{port:e,codePromise:r}}async function B(e,r,s){const i=`http://localhost:${s}/callback`,o=await fetch(E,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({client_id:m,client_secret:k,code:e,code_verifier:r,grant_type:"authorization_code",redirect_uri:i})});if(!o.ok){const n=await o.text();throw new Error(`Failed to exchange code for tokens: ${n}`)}return await o.json()}async function C(e){try{await fetch($,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`token=${encodeURIComponent(e)}`})}catch{}}async function M(){t(`
|
|
24
24
|
\u25FC Borg MCP Authentication`),t(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
25
|
-
`);const e=await p();e&&(t("Revoking previous refresh_token to force fresh consent..."),await
|
|
25
|
+
`);const e=await p();e&&(t("Revoking previous refresh_token to force fresh consent..."),await C(e)),t("Generating PKCE challenge...");const r=D();t("Starting local callback server...");const{port:s,codePromise:i}=await U(),o=`http://localhost:${s}/callback`,n=new _(L);n.searchParams.set("client_id",m),n.searchParams.set("redirect_uri",o),n.searchParams.set("response_type","code"),n.searchParams.set("scope",T.join(" ")),n.searchParams.set("code_challenge",r.challenge),n.searchParams.set("code_challenge_method","S256"),n.searchParams.set("access_type","offline"),n.searchParams.set("prompt","consent select_account"),t(`
|
|
26
26
|
\u{1F4F1} Opening browser for authorization...`),t("If browser does not open, visit:"),t(`${n.toString()}
|
|
27
|
-
`);try{await
|
|
27
|
+
`);try{await P(n.toString())}catch(l){t(`Could not open a browser automatically: ${l?.message??"unknown"}`),t("Continue by opening the URL above manually.")}t(`Waiting for you to finish signing in (up to ${v} minutes)... this terminal continues automatically once you approve.`);const c=await i;t("Exchanging authorization code for tokens...");const a=await B(c,r.verifier,s),h=Date.now()+a.expires_in*1e3;await d(a.id_token,h),a.refresh_token?await f(a.refresh_token):(t(`
|
|
28
28
|
\u26A0 No refresh_token returned by Google.`),t(" Your session will expire after ~1 hour and require"),t(" re-running `borg setup`. To enable auto-refresh:"),t(" 1. Visit https://myaccount.google.com/permissions"),t(' 2. Find "Borg MCP" and click "Remove access"'),t(" 3. Re-run `borg setup`"),t(` (Google will then issue a fresh refresh_token.)
|
|
29
29
|
`)),t(`
|
|
30
30
|
\u25FC Authentication successful!
|
|
31
|
-
`)}function
|
|
31
|
+
`)}function H(e){return e?.noBrowser??O()}function b(e=process.env){const r=e.GOOGLE_DEVICE_CLIENT_ID?.trim(),s=e.GOOGLE_DEVICE_CLIENT_SECRET?.trim()||void 0;let i,o;if(r?(i=r,o=s):(i=A,o=S||void 0),!i)throw new Error('No-browser (device-grant) auth needs a Google "TVs & Limited Input devices" OAuth client. Set GOOGLE_DEVICE_CLIENT_ID in the environment, or run `borg setup` on a machine with a browser. See docs/REMOTE_TERMINAL_AUTH.md.');return{clientId:i,clientSecret:o,scopes:T}}function q(e){return new Promise(r=>setTimeout(r,e))}async function F(e={fetch,sleep:q},r=process.env){t(`
|
|
32
32
|
\u25FC Borg MCP Authentication (no-browser mode)`),t(`\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
33
|
-
`);const s=
|
|
34
|
-
`),t("Waiting for authorization (this page can be open on your phone or laptop)...");const n=await
|
|
33
|
+
`);const s=b(r),i=await p();i&&(t("Revoking previous refresh_token to force fresh consent..."),await C(i));const o=await I(s,e);t("To authorize Borg MCP on this machine:"),t(` 1. On any device with a browser, open: ${o.verification_url}`),t(` 2. Enter this code: ${o.user_code}
|
|
34
|
+
`),t("Waiting for authorization (this page can be open on your phone or laptop)...");const n=await x(o,s,e),c=Date.now()+n.expires_in*1e3;await d(n.id_token,c),n.refresh_token?await f(n.refresh_token):(t(`
|
|
35
35
|
\u26A0 No refresh_token returned by Google.`),t(" Your session will expire after ~1 hour and require re-running"),t(" `borg setup`. Re-consent at https://myaccount.google.com/permissions"),t(` (remove "Borg MCP") then re-run setup to restore automatic token refresh.
|
|
36
36
|
`)),t(`
|
|
37
37
|
\u25FC Authentication successful!
|
|
38
|
-
`)}async function
|
|
38
|
+
`)}async function ee(e){return H(e)?F():M()}async function R(e,r,s){const i={client_id:r,refresh_token:e,grant_type:"refresh_token"};s&&(i.client_secret=s);let o;try{o=await fetch(E,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(i)})}catch(a){return{ok:!1,kind:"transient",error:new u(`Network failure during token refresh: ${a?.message??"unknown"}`)}}if(!o.ok){let a=null;try{a=await o.json()}catch{return{ok:!1,kind:"transient",error:new u(`Token refresh failed with HTTP ${o.status} (non-JSON body)`)}}return o.status===400&&a?.error==="invalid_grant"?{ok:!1,kind:"invalid",error:new G("invalid_grant",a.error_description)}:{ok:!1,kind:"transient",error:new u(`Token refresh failed with HTTP ${o.status}${a?.error?` (${a.error})`:""}`)}}let n;try{n=await o.json()}catch(a){return{ok:!1,kind:"transient",error:new u(`Token refresh response unparseable: ${a?.message??"unknown"}`)}}if(!n.id_token||typeof n.expires_in!="number")return{ok:!1,kind:"transient",error:new u("Token refresh response missing id_token or expires_in")};const c=Date.now()+n.expires_in*1e3;if(n.refresh_token){const a=await p();await f(n.refresh_token);try{await d(n.id_token,c)}catch(h){if(a)try{await f(a)}catch{}throw h}return{ok:!0}}return await d(n.id_token,c),{ok:!0}}async function te(e){const r=await R(e,m,k);if(r.ok)return;const s=b(),i=await R(e,s.clientId,s.clientSecret);if(!i.ok)throw r.kind==="invalid"&&i.kind==="invalid"?i.error:r.kind==="transient"?r.error:i.error}export{G as RefreshTokenInvalidError,u as RefreshTransientError,F as authenticateWithDeviceFlow,ee as authenticateWithGoogle,b as buildDeviceAuthConfig,te as refreshIdToken,H as shouldUseDeviceFlow};
|
package/dist/claude.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{spawn as
|
|
3
|
-
`)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(
|
|
2
|
+
import{spawn as S}from"child_process";import{randomUUID as y}from"node:crypto";import{basename as A}from"node:path";import{createInterface as P}from"node:readline/promises";import s from"chalk";import{findProjectRoot as T,getActiveCube as I,inboxPathForDrone as E,setCodexWakeTarget as R}from"./cubes.js";import{handleVersionFlag as D,getPackageVersion as g}from"./version.js";import{isHelpFlag as O,setupHelpText as F,topLevelHelpText as N}from"./cli-help.js";import{runSpawn as H}from"./spawn.js";import{parseSyncArgs as M,runSync as B}from"./sync.js";import{parseAssimilateArgs as G}from"./parse-assimilate-args.js";import{runAssimilate as L}from"./assimilate-cmd.js";import{buildDefaultAssimilateDeps as _}from"./assimilate-deps.js";import{setTerminalTitle as W}from"./terminal-title.js";import{initConsolePrefix as U,consolePrefix as r}from"./console-prefix.js";import{initDebugFromArgv as V}from"./debug.js";import{fetchLatestBorgmcpVersion as j,compareVersionsForStaleness as K}from"./stale-version-check.js";import{defaultCliChoiceDeps as Y,detectCliAvailability as v,installedCliNames as q,parseCliFlag as z,resolveCliChoice as X}from"./cli-platform.js";import{getRefreshToken as J,getIdToken as Q}from"./config.js";import{composeGetStarted as Z,shouldShowGetStarted as ee}from"./get-started.js";import{prepareCodexRemoteLaunch as re,withCodexCwdArg as oe,defaultCodexRemoteDeps as se}from"./codex-remote.js";import{findLoadedCodexThread as te}from"./codex-app-server.js";import{buildAgentKickoffPrompt as ie,recordCodexWakeTarget as ae,socketPathFromRemoteArgs as k}from"./codex-launch.js";import{codexBorgSessionConfigArgs as ne}from"./launch-gate.js";import{addCodexMcpServer as ce,addCodexSessionStartHook as le,addCodexUserPromptSubmitHook as de,addMcpServer as pe,addProjectSessionStartHook as me,addUserPromptSubmitHook as fe,isCodexMcpServerConfigured as ue,isMcpServerConfigured as ge,removeSessionStartHook as he}from"./config-utils.js";async function we(){V(process.argv),D(),await U();const n=(async()=>{if(!process.stderr.isTTY)return;const e=g(),a=await j();if(!a)return;const p=K(e,a);p.stale&&p.message&&process.stderr.write(`${r()}${p.message}
|
|
3
|
+
`)})();if((process.argv[2]==="--help"||process.argv[2]==="-h")&&(process.stdout.write(N(g())),process.exit(0)),process.argv[2]==="setup"){O(process.argv[3])&&(process.stdout.write(F(g())),process.exit(0)),await import("./setup.js");return}if(process.argv[2]==="assimilate"){const e=G(process.argv.slice(3));e.ok||(process.stderr.write(s.red(`${r()}\u25FC borg assimilate: ${e.error}
|
|
4
4
|
`)),process.exit(1));const a=_(),p=await L({role:e.role,flags:e.flags},a);process.exit(p)}if(process.argv[2]==="spawn"){const e=await H();process.exit(e)}if(process.argv[2]==="sync"){const e=M(process.argv.slice(3));e.ok||(process.stderr.write(s.red(`${r()}\u25FC borg sync: ${e.error}
|
|
5
|
-
`)),process.exit(1));const a=await
|
|
6
|
-
`)),process.exit(1));const
|
|
5
|
+
`)),process.exit(1));const a=await B({},e.options);process.exit(a)}if(ee(await J()!==null,await Q()!==null)){const e=q(v()).length>0;process.stdout.write(Z(e)),process.exit(0)}const t=z(process.argv.slice(2));t.error&&(process.stderr.write(s.red(`${r()}\u25FC ${t.error}
|
|
6
|
+
`)),process.exit(1));const $=async e=>{const a=P({input:process.stdin,output:process.stdout});try{return await a.question(e)}finally{a.close()}},o=await X(t.cli,Y($,()=>process.stdin.isTTY===!0));xe();const c=t.rest,i=await I();W(i?{label:i.droneLabel,cubeName:i.name}:null,A(process.cwd()));const b=i&&o==="claude"?`If you haven't yet, arm a persistent Monitor running the command \`borg-inbox-monitor ${E(i.cubeId,i.droneId)}\` so each event's task-notification title summarizes the new cube log entry (drone label, role, and first ~80 chars of the message body) \u2014 letting you triage events without reading the full body. `:"";await Promise.race([n,new Promise(e=>setTimeout(e,2e3))]);const h=o==="codex"?`borg-wake-${y()}`:null;let m,w=[],f={...process.env,BORG_SESSION:"1"},l=null,d=null;if(o==="codex"&&!c.includes("--remote")){console.error(`${r()}${s.gray("\u25FC Starting Codex remote-wake app-server\u2026")}`);const e=await re(se());e.warning?(console.error(`${r()}${s.yellow(`warning: ${e.warning}`)}`),m="\u26A0 Codex wake-path capability check failed: remote-control is unavailable for this session. Run borg:regen manually whenever you return, and expect only fallback wakeups until relaunch."):m="Codex wake-path capability check passed: remote-control socket established for this session.",w=e.args,f={...process.env,...e.env,BORG_SESSION:"1"},l=k(e.args),d=e.server?.cleanup??null}else o==="codex"&&c.includes("--remote")&&(m="Codex wake-path capability check: using caller-provided --remote socket; if no wake arrives, run borg:regen manually when returning to the session.",l=k(c),l&&(f={...process.env,BORG_CODEX_REMOTE_WAKE:"1",BORG_SESSION:"1"}));const x=ie({cli:o,codexWakeNonce:h,monitorClause:b,codexWakePathClause:m});let u=[...c,x];o==="codex"&&(u=[...ne(),...w,...oe(u,process.cwd())]),console.error(`${r()}${s.blue(`\u25FC Launching ${o==="claude"?"Claude Code":"Codex"}\u2026`)}`);const C=S(o,u,{stdio:"inherit",shell:!1,env:f});o==="codex"&&i&&l&&ae({deps:{setCodexWakeTarget:R,findLoadedCodexThread:te},cubeId:i.cubeId,droneId:i.droneId,socketPath:l,passthroughArgs:c,previewNeedle:h??x.slice(0,120),cwd:process.cwd(),launchedAtSeconds:Math.floor(Date.now()/1e3)}),C.on("error",e=>{if(d)try{d()}catch{}e.code==="ENOENT"?(console.error(`${r()}${s.red(`
|
|
7
7
|
\u25FC Failed to launch ${o}`)}`),console.error(`${r()}${s.gray(`Make sure ${o} is installed.
|
|
8
8
|
`)}`)):console.error(`${r()}${s.red(`
|
|
9
9
|
\u25FC Failed to launch ${o}: ${e.message}
|
|
10
|
-
`)}`),process.exit(1)}),
|
|
10
|
+
`)}`),process.exit(1)}),C.on("exit",e=>{if(d)try{d()}catch{}process.exit(e??0)})}function xe(){const n=v();if(n.claude)try{ge()||pe(),me(T(process.cwd())),he(),fe()}catch(t){console.error(`${r()}${s.yellow(`warning: Claude Code integration check failed: ${t?.message??t}`)}`)}if(n.codex)try{ue()||ce(),le(),de()}catch(t){console.error(`${r()}${s.yellow(`warning: Codex integration check failed: ${t?.message??t}`)}`)}}we().catch(n=>{console.error(`${r()}${s.red(`
|
|
11
11
|
\u25FC Error: ${n.message}
|
|
12
12
|
`)}`),process.exit(1)});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fresh-vs-configured rule. "Configured" = the user has completed `borg setup`,
|
|
3
|
+
* signalled by the PRESENCE of a stored credential. Inputs are existence
|
|
4
|
+
* booleans only — the caller null-checks the token accessors and never decodes,
|
|
5
|
+
* logs, or prints the token value (SR gh#817 constraint).
|
|
6
|
+
*
|
|
7
|
+
* Refresh-token presence is the durable signal: it survives id_token expiry, so
|
|
8
|
+
* a configured user whose id_token has lapsed is NOT mistaken for fresh (which
|
|
9
|
+
* would wrongly suppress their normal launch). The id_token presence is the
|
|
10
|
+
* fallback for the rare case where Google returned no refresh_token and the
|
|
11
|
+
* id_token is still valid.
|
|
12
|
+
*/
|
|
13
|
+
export declare function shouldShowGetStarted(hasRefreshToken: boolean, hasIdToken: boolean): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* The user-visible get-started text. Carries ZERO auth material (no tokens, no
|
|
16
|
+
* PII) — it is pure onboarding guidance. When no agent CLI is detected, lead
|
|
17
|
+
* with the install-an-agent-CLI step (mirrors the B1 banner intent).
|
|
18
|
+
*/
|
|
19
|
+
export declare function composeGetStarted(hasAgentCli: boolean): string;
|
|
20
|
+
//# sourceMappingURL=get-started.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function n(o,e){return!o&&!e}function r(o){const e=["","Welcome to Borg MCP \u2014 multi-agent coordination for your AI coding agent.","","You're not set up yet. To get started:",""];let t=1;return o||(e.push(` ${t}. Install an agent CLI first:`," Claude Code: https://claude.ai/download"," Codex: https://developers.openai.com/codex"),t++),e.push(` ${t}. borg setup${" ".repeat(Math.max(1,8))}\u2014 sign in with Google`),t++,e.push(` ${t}. cd into your project, then: borg assimilate \u2014 join/create a cube`,"","Then `borg` launches your agent in that cube. Run `borg --help` for more.",""),e.join(`
|
|
2
|
+
`)}export{r as composeGetStarted,n as shouldShowGetStarted};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function n(o){const e=["","\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557","\u2551 \u25FC Borg MCP Installed \u25FC \u2551","\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",""];return o?e.push("Next step:"," borg setup",""):e.push("\u26A0 No agent CLI detected. Borg runs on top of Claude Code or Codex \u2014"," install one first:"," Claude Code: https://claude.ai/download"," Codex: https://developers.openai.com/codex","","Then run:"," borg setup",""),e.join(`
|
|
2
|
+
`)}export{n as composeInstallBanner};
|
package/dist/postinstall.d.ts
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Post-install script
|
|
4
4
|
*
|
|
5
|
-
* Detects local vs global installation and rejects local installs
|
|
5
|
+
* Detects local vs global installation and rejects local installs.
|
|
6
|
+
* gh#653 B1: also detects whether an agent CLI (Claude Code / Codex) is present
|
|
7
|
+
* and adjusts the "next step" banner so a user with no agent CLI is told to
|
|
8
|
+
* install one FIRST rather than being sent into `borg setup`'s dead-end.
|
|
6
9
|
*/
|
|
7
10
|
export {};
|
|
8
11
|
//# sourceMappingURL=postinstall.d.ts.map
|
package/dist/postinstall.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const
|
|
2
|
+
import{detectCliAvailability as l,installedCliNames as e}from"./cli-platform.js";import{composeInstallBanner as r}from"./postinstall-banner.js";const n=process.env.npm_config_global==="true";n||(console.error(`
|
|
3
3
|
\u25FC Error: borg must be installed globally
|
|
4
4
|
`),console.error("Please install with:"),console.error(` npm install -g borgmcp
|
|
5
5
|
`),console.error(`Local installation is not supported.
|
|
6
|
-
`),process.exit(1))
|
|
7
|
-
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`),console.log("\u2551 \u25FC Borg MCP Installed \u25FC \u2551"),console.log(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
8
|
-
`),console.log("Next step:"),console.log(` borg setup
|
|
9
|
-
`);
|
|
6
|
+
`),process.exit(1));let o=!0;try{o=e(l()).length>0}catch{o=!0}console.log(r(o));
|
package/dist/setup.js
CHANGED
|
@@ -35,7 +35,7 @@ import h from"prompts";import e from"chalk";import g from"open";import u from"wh
|
|
|
35
35
|
\u25FC You're all set on the Free tier: 1 cube, 3 agent sessions, 100 req/hr.
|
|
36
36
|
`));break}}console.log(e.green.bold(`Setup complete!
|
|
37
37
|
`)),console.log(e.yellow(`\u{1F504} Restart Claude Code/Codex (or open a new session) for the changes to take effect.
|
|
38
|
-
`)),console.log(e.gray("\u25FC Next steps:")),console.log(e.gray('1.
|
|
38
|
+
`)),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
|
|
39
39
|
`))}async function p(){for(let n=0;n<24;n++){await new Promise(t=>setTimeout(t,5e3));try{if((await c()).hasAccess){console.log(e.green(`\u25FC Subscription activated!
|
|
40
40
|
`));return}}catch{}}throw new Error("Timeout - Run setup again after subscribing")}P().catch(i=>{console.error(e.red(`
|
|
41
41
|
\u25FC Setup failed: ${i.message}
|
package/dist/token-store.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import
|
|
1
|
+
import w from"path";import d from"crypto";import{AsyncEntry as h}from"@napi-rs/keyring";import{decryptString as k,encryptString as g}from"./token-crypto.js";const S="borg-mcp",E=t=>new h(S,t);function K(t=E){return{name:"keychain",async get(e){return await t(e).getPassword()??null},async set(e,o){await t(e).setPassword(o)},async delete(e){try{await t(e).deletePassword()}catch(o){const a=String(o?.message??"");if(/no entry|not found|no matching/i.test(a))return;throw o}}}}const O=25,_=2e3,F=1e4;function x(t){return new Promise(e=>setTimeout(e,t))}function v(){return`${process.pid}.${d.randomBytes(6).toString("hex")}`}function T(t){const{filePath:e,key:o,fs:a}=t,y=t.sleep??x,s=t.now??Date.now,p=t.uniqueSuffix??v,c=`${e}.lock`;async function l(){let r;try{r=await a.readFile(e)}catch{return{}}try{const n=k(r.trim(),o),i=JSON.parse(n);return i&&typeof i=="object"?i:{}}catch{return{}}}async function u(r){await a.mkdir(w.dirname(e),448);const n=`${e}.${p()}.tmp`;await a.writeFile(n,g(JSON.stringify(r),o),384);try{await a.rename(n,e)}catch(i){try{await a.removeFile(n)}catch{}throw i}}async function f(r){await a.mkdir(w.dirname(c),448);const n=s()+_;let i=!1;for(;!i&&(i=await a.createExclusive(c,`${process.pid}@${s()}`),!i);){const m=await a.fileAgeMs(c);if(m!==null&&m>F){await a.removeFile(c);continue}if(s()>=n){await a.removeFile(c),i=await a.createExclusive(c,`${process.pid}@${s()}`);break}await y(O)}try{return await r()}finally{i&&await a.removeFile(c)}}return{name:"encrypted-file",async get(r){const n=await l();return Object.prototype.hasOwnProperty.call(n,r)?n[r]:null},set(r,n){return f(async()=>{const i=await l();i[r]=n,await u(i)})},delete(r){return f(async()=>{const n=await l();Object.prototype.hasOwnProperty.call(n,r)&&(delete n[r],await u(n))})}}}async function b(t){return t.forced==="keychain"?t.makeKeychain():t.forced==="file"?t.makeFile():await t.keyringAvailable()?t.makeKeychain():t.makeFile()}async function L(t){const e=t.env.BORG_TOKEN?.trim();if(e)return e;const o=t.env.BORG_TOKEN_FILE?.trim();return o?(await t.readFile(o)).trim():null}export{T as makeEncryptedFileBackend,K as makeKeychainBackend,L as readCallerManagedIdToken,b as selectTokenBackend};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
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",
|