borgmcp 1.0.22 → 1.0.23
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/auth.js +17 -17
- package/dist/remote-client.js +1 -1
- package/package.json +1 -1
package/dist/auth.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import{createServer as w}from"http";import{URL as
|
|
1
|
+
import{createServer as w}from"http";import{URL as k}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 r}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(t,s){super(s?`Refresh token invalid (${t}): ${s}`:`Refresh token invalid (${t})`),this.errorCode=t,this.errorDescription=s,this.name="RefreshTokenInvalidError"}}class l extends Error{constructor(t){super(t),this.name="RefreshTransientError"}}const m="675073910799-41pbe12rfhqemidh64h09s4q3e0udpgp.apps.googleusercontent.com",_="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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"),t=g.createHash("sha256").update(e).digest("base64url");return{verifier:e,challenge:t}}async function N(){return new Promise((e,t)=>{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(()=>t(new Error("Failed to get assigned port")))}),s.on("error",t)})}async function U(){const e=await N(),t=new Promise((s,i)=>{const o=w((n,c)=>{const a=new k(n.url,`http://localhost:${e}`);if(a.pathname==="/callback"){const h=a.searchParams.get("code"),u=a.searchParams.get("error");if(u){c.writeHead(400,{"Content-Type":"text/html"}),c.end(`
|
|
2
2
|
<html>
|
|
3
3
|
<body>
|
|
4
4
|
<h1>\u25FC Authentication Failed</h1>
|
|
5
|
-
<p>Error: ${
|
|
5
|
+
<p>Error: ${u}</p>
|
|
6
6
|
<p>You can close this window.</p>
|
|
7
7
|
</body>
|
|
8
8
|
</html>
|
|
9
|
-
`),o.close(),i(new Error(`OAuth error: ${
|
|
9
|
+
`),o.close(),i(new Error(`OAuth error: ${u}`));return}if(h){c.writeHead(200,{"Content-Type":"text/html"}),c.end(`
|
|
10
10
|
<html>
|
|
11
11
|
<body>
|
|
12
12
|
<h1>\u25FC Authentication Successful!</h1>
|
|
@@ -20,19 +20,19 @@ import{createServer as w}from"http";import{URL as _}from"url";import g 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,()=>{
|
|
24
|
-
\u25FC Borg MCP Authentication`),
|
|
25
|
-
`);const e=await p();e&&(
|
|
26
|
-
\u{1F4F1} Opening browser for authorization...`),
|
|
27
|
-
`);try{await P(n.toString())}catch(
|
|
28
|
-
\u26A0 No refresh_token returned by Google.`),
|
|
29
|
-
`)),
|
|
23
|
+
`),o.close(),i(new Error("Missing authorization code"))}});o.listen(e,()=>{r(`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:t}}async function B(e,t,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:_,code:e,code_verifier:t,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 b(e){try{await fetch($,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`token=${encodeURIComponent(e)}`})}catch{}}async function M(){r(`
|
|
24
|
+
\u25FC Borg MCP Authentication`),r(`\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&&(r("Revoking previous refresh_token to force fresh consent..."),await b(e)),r("Generating PKCE challenge...");const t=D();r("Starting local callback server...");const{port:s,codePromise:i}=await U(),o=`http://localhost:${s}/callback`,n=new k(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",t.challenge),n.searchParams.set("code_challenge_method","S256"),n.searchParams.set("access_type","offline"),n.searchParams.set("prompt","consent select_account"),r(`
|
|
26
|
+
\u{1F4F1} Opening browser for authorization...`),r("If browser does not open, visit:"),r(`${n.toString()}
|
|
27
|
+
`);try{await P(n.toString())}catch(u){r(`Could not open a browser automatically: ${u?.message??"unknown"}`),r("Continue by opening the URL above manually.")}r(`Waiting for you to finish signing in (up to ${v} minutes)... this terminal continues automatically once you approve.`);const c=await i;r("Exchanging authorization code for tokens...");const a=await B(c,t.verifier,s),h=Date.now()+a.expires_in*1e3;await d(a.id_token,h),a.refresh_token?await f(a.refresh_token):(r(`
|
|
28
|
+
\u26A0 No refresh_token returned by Google.`),r(" Your session will expire after ~1 hour and require"),r(" re-running `borg setup`. To enable auto-refresh:"),r(" 1. Visit https://myaccount.google.com/permissions"),r(' 2. Find "Borg MCP" and click "Remove access"'),r(" 3. Re-run `borg setup`"),r(` (Google will then issue a fresh refresh_token.)
|
|
29
|
+
`)),r(`
|
|
30
30
|
\u25FC Authentication successful!
|
|
31
|
-
`)}function
|
|
32
|
-
\u25FC Borg MCP Authentication (no-browser mode)`),
|
|
33
|
-
`);const s=
|
|
34
|
-
`),
|
|
35
|
-
\u26A0 No refresh_token returned by Google.`),
|
|
36
|
-
`)),
|
|
31
|
+
`)}function F(e){return e?.noBrowser??O()}function C(e=process.env){const t=e.GOOGLE_DEVICE_CLIENT_ID?.trim(),s=e.GOOGLE_DEVICE_CLIENT_SECRET?.trim()||void 0;let i,o;if(t?(i=t,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 H(e){return new Promise(t=>setTimeout(t,e))}async function q(e={fetch,sleep:H},t=process.env){r(`
|
|
32
|
+
\u25FC Borg MCP Authentication (no-browser mode)`),r(`\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=C(t),i=await p();i&&(r("Revoking previous refresh_token to force fresh consent..."),await b(i));const o=await I(s,e);r("To authorize Borg MCP on this machine:"),r(` 1. On any device with a browser, open: ${o.verification_url}`),r(` 2. Enter this code: ${o.user_code}
|
|
34
|
+
`),r("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):(r(`
|
|
35
|
+
\u26A0 No refresh_token returned by Google.`),r(" Your session will expire after ~1 hour and require re-running"),r(" `borg setup`. Re-consent at https://myaccount.google.com/permissions"),r(` (remove "Borg MCP") then re-run setup to restore automatic token refresh.
|
|
36
|
+
`)),r(`
|
|
37
37
|
\u25FC Authentication successful!
|
|
38
|
-
`)}async function ee(e){return
|
|
38
|
+
`)}async function ee(e){return F(e)?q():M()}async function R(e,t,s){const i={client_id:t,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 l(`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 l(`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 l(`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 l(`Token refresh response unparseable: ${a?.message??"unknown"}`)}}if(!n.id_token||typeof n.expires_in!="number")return{ok:!1,kind:"transient",error:new l("Token refresh response missing id_token or expires_in")};const c=Date.now()+n.expires_in*1e3;try{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}}else await d(n.id_token,c);return{ok:!0}}catch(a){return{ok:!1,kind:"transient",persistFailed:!0,error:new l(`Token refresh succeeded but saving it to the credential store failed (the keychain may be locked, or a background process can't write it): ${a?.message??"unknown"}`)}}}async function te(e){const t=await R(e,m,_);if(t.ok)return;if(t.kind==="transient"&&t.persistFailed)throw t.error;const s=C(),i=await R(e,s.clientId,s.clientSecret);if(!i.ok)throw t.kind==="invalid"&&i.kind==="invalid"?i.error:t.kind==="transient"?t.error:i.error}export{G as RefreshTokenInvalidError,l as RefreshTransientError,q as authenticateWithDeviceFlow,ee as authenticateWithGoogle,C as buildDeviceAuthConfig,te as refreshIdToken,F as shouldUseDeviceFlow};
|
package/dist/remote-client.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getIdToken as f,getRefreshToken as w,clearTokens as m}from"./config.js";import{refreshIdToken as j,RefreshTokenInvalidError as g,RefreshTransientError as
|
|
1
|
+
import{getIdToken as f,getRefreshToken as w,clearTokens as m}from"./config.js";import{refreshIdToken as j,RefreshTokenInvalidError as g,RefreshTransientError as T}from"./auth.js";import{consolePrefix as R}from"./console-prefix.js";import{debugLog as b}from"./debug.js";import{assertUuidShape as $}from"./evict-drone.js";const E=process.env.BORG_API_URL||"https://api.borgmcp.ai",k=3,C=6e4;let l=null;function x(e){return l||(l=j(e).finally(()=>{l=null}),l)}function P(e){if(e==null)return null;const n=e.trim();return/^\d+$/.test(n)?parseInt(n,10)*1e3:null}function O(e,n,t=C,o=()=>Math.random()*500){const s=e??1e3*(n+1);return Math.min(s,t)+o()}function I(e){const n=(t,o)=>`${t}${/[.:!?]$/.test(t)?"":":"} ${o}`;try{const t=JSON.parse(e);if(typeof t?.error=="string")return typeof t.details=="string"?n(t.error,t.details):t.error;if(t?.error&&typeof t.error=="object"){const o=t.error.message,s=t.error.details??t.details;if(typeof o=="string"&&typeof s=="string")return n(o,s);if(typeof o=="string")return o}if(typeof t?.message=="string"&&typeof t?.details=="string")return n(t.message,t.details);if(typeof t?.message=="string")return t.message}catch{}return e}async function A(e,n,t){const o=t.maxRetries??k;let s=e,a=0;for(;s.status===429&&a<o;){const p=O(P(s.headers.get("Retry-After")),a,t.capMs,t.jitter);t.log?.(`rate limited (429); retrying in ${Math.round(p)}ms (attempt ${a+1}/${o})`),await t.sleep(p),a++,s=await n()}return s}function L(e){return new Promise(n=>setTimeout(n,e))}async function J(){let e=await f();if(!e){const n=await w(),t=n!=null;if(n)try{await x(n),e=await f()}catch(o){if(o instanceof g)await m();else throw o instanceof T?o:new T(`Token refresh failed unexpectedly (your saved login was NOT cleared \u2014 retry; if it persists, restart the borg session): ${o?.message??"unknown"}`)}if(!e)throw new Error(t?"Authentication expired \u2014 your saved login has expired. Run: borg setup":"Authentication required \u2014 you are not signed in. Run: borg setup")}return e}async function G(){const e=await w();if(!e)return null;try{return await x(e),await f()}catch(n){if(n instanceof g&&await m(),n instanceof T)throw n;return null}}async function q(){if(await f())return"valid";const n=await w();if(!n)return"dead";try{return await x(n),await f()?"valid":"transient"}catch(t){return t instanceof g?(await m(),"dead"):"transient"}}async function r(e,n={}){let t=await J();const{droneSession:o,apiUrl:s,headers:a,...p}=n,_=s??E,y=(p.method??"GET").toUpperCase(),h=async c=>{const d={Authorization:`Bearer ${c}`,...a};o&&(d["X-Drone-Session"]=o),b(`\u2192 ${y} ${e}`);const u=await fetch(`${_}${e}`,{...p,headers:d});return b(`\u2190 ${u.status} ${y} ${e}`),u};let i=await h(t);if(i.status===401){const c=await G();c&&(t=c,i=await h(t))}if(i.status===401)throw new Error("Authentication required. Run: borg setup");if(i.status===429&&(i=await A(i,()=>h(t),{sleep:L,log:c=>console.error(`${R()}${c}`)})),!i.ok){const c=await i.text();b(`\u2717 ${i.status} ${y} ${e}: ${c}`);const d=I(c);if(i.status===429){const u=i.headers.get("Retry-After"),S=u?` (retry after ${u}s)`:"";throw new Error(`HTTP 429: rate limited${S}: ${d}`)}throw new Error(`HTTP ${i.status}: ${d}`)}return i}async function N(e,n,t,o){const s={hostname:t??null};return(o==="claude"||o==="codex")&&(s.agent_kind=o),typeof e=="string"?s.cube_name=e:(e.cube_id&&(s.cube_id=e.cube_id),e.cube_name&&(s.cube_name=e.cube_name),e.role_id&&(s.role_id=e.role_id),e.role_name&&(s.role_name=e.role_name),e.prior_drone_id&&(s.prior_drone_id=e.prior_drone_id)),await(await r("/api/assimilate",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s),apiUrl:n})).json()}async function B(e,n){return await(await r("/api/drone/cube",{method:"GET",droneSession:e,apiUrl:n})).json()}async function F(e,n){return await(await r("/api/drone/role",{method:"GET",droneSession:e,apiUrl:n})).json()}async function X(e,n){return await(await r("/api/drone/whoami",{method:"GET",droneSession:e,apiUrl:n})).json()}async function W(e,n,t){const o=t?`?since=${encodeURIComponent(t)}`:"";return await(await r(`/api/drone/roster${o}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function z(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since),t.limit!==void 0&&o.set("limit",String(t.limit)),t.unreadOnly&&o.set("unread_only","true");const s=o.toString();return await(await r(`/api/drone/log${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function Q(e,n,t){await r(`/api/drone/log/${t}/ack`,{method:"POST",body:JSON.stringify({kind:"ack"}),droneSession:e,apiUrl:n})}async function V(e,n,t={}){const o=new URLSearchParams;t.since&&o.set("since",t.since);const s=o.toString();return await(await r(`/api/drone/regen${s?`?${s}`:""}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function Y(e,n,t,o){const s=new URLSearchParams({role:t,section:o});return await(await r(`/api/drone/role-rationale?${s.toString()}`,{method:"GET",droneSession:e,apiUrl:n})).json()}async function Z(e,n,t,o={}){const s={message:t,...o.visibility?{visibility:o.visibility}:{},...o.recipientDroneIds?{recipientDroneIds:o.recipientDroneIds}:{},...o.class?{class:o.class}:{},...o.to?{to:o.to}:{}};return await(await r("/api/drone/log",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(s)})).json()}async function K(e,n,t){const o={kind:t.kind??"friction",message:t.message,...t.metadata?{metadata:t.metadata}:{}};return await(await r("/api/drone/report",{method:"POST",headers:{"Content-Type":"application/json"},droneSession:e,apiUrl:n,body:JSON.stringify(o)})).json()}async function ee(){return await(await r("/api/cubes",{method:"GET"})).json()}async function te(){return await(await r("/api/templates",{method:"GET"})).json()}async function ne(e,n,t){const o={cube_directive:n};e&&(o.name=e),t?.template&&(o.template=t.template),t&&Object.prototype.hasOwnProperty.call(t,"message_taxonomy")&&(o.message_taxonomy=t.message_taxonomy??null);const a=await(await r("/api/cubes",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json();return a.cube?{...a.cube,roles:a.roles??[],drones:a.drones??[]}:a}async function oe(e,n){return await(await r(`/api/cubes/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function se(e,n){return await(await r(`/api/cubes/${e}/taxonomy-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function re(e){await r(`/api/cubes/${e}`,{method:"DELETE"})}async function ae(e,n){return await(await r(`/api/cubes/${e}/roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function ie(e,n){return await(await r(`/api/roles/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function ce(e,n){return await(await r(`/api/roles/${e}/section-patch`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(n)})).json()}async function pe(e){await r(`/api/roles/${e}`,{method:"DELETE"})}async function de(e,n){return $(e,"drone_id"),await(await r(`/api/drones/${e}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({role_id:n})})).json()}async function ue(e){$(e,"drone_id"),await r(`/api/drones/${e}`,{method:"DELETE"})}async function fe(e){const t=await(await r(`/api/cubes/${e}`,{method:"GET"})).json();return t.cube?{...t.cube,roles:t.roles??[],drones:t.drones??[]}:t}async function le(e,n){return await(await r(`/api/cubes/${e}/apply-template`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n})})).json()}async function ye(){return await(await r("/api/subscription/status",{method:"GET"})).json()}async function he(e,n="software-dev",t=!1,o){return await(await r(`/api/cubes/${e}/sync-roles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({template_name:n,apply:t,...o?{decisions:o}:{}})})).json()}async function we(){const n=await(await r("/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.checkout_url)throw new Error("No checkout URL in response");return n.checkout_url}async function me(){const n=await(await r("/api/subscription/portal",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(!n.portal_url)throw new Error(n.message||"No portal URL in response");return n.portal_url}export{E as API_URL,Q as ackLogEntry,Z as appendLog,le as applyTemplate,N as assimilate,ye as checkSubscriptionStatus,me as createBillingPortalSession,ne as createCube,ae as createRole,we as createSubscription,re as deleteCube,pe as deleteRole,ue as evictDrone,I as extractHttpErrorMessage,fe as getCube,B as getCubeInfo,F as getRoleInfo,W as getRoster,J as getValidToken,ee as listCubes,te as listTemplates,P as parseRetryAfterMs,ce as patchRoleSection,se as patchTaxonomyClass,q as probeSession,O as rateLimitWaitMs,z as readLog,de as reassignDrone,V as regen,A as retryOn429,Y as roleRationale,K as submitReport,he as syncRoles,oe as updateCube,ie as updateRole,X as whoami};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "borgmcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.23",
|
|
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",
|