@hellcoder/companion 0.119.1 → 0.120.0
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/assets/{AgentsPage-E73zA-6k.js → AgentsPage-V7NBPNSh.js} +5 -5
- package/dist/assets/CronManager-CGfXlVZS.js +1 -0
- package/dist/assets/{DashboardPage-Dk4z826H.js → DashboardPage-N7MWZiJQ.js} +1 -1
- package/dist/assets/{IntegrationsPage-C7cQyPBt.js → IntegrationsPage-DRP477b0.js} +1 -1
- package/dist/assets/{LinearOAuthSettingsPage-BG4-JYjD.js → LinearOAuthSettingsPage-CPW4TQuD.js} +1 -1
- package/dist/assets/{LinearSettingsPage-CBbKNZVH.js → LinearSettingsPage-BwVTEKqs.js} +1 -1
- package/dist/assets/{MagicUIDashboard-DpxmzOIx.js → MagicUIDashboard-exuRY5LQ.js} +1 -1
- package/dist/assets/MagicUIView-AKoy6jOb.js +1 -0
- package/dist/assets/{Playground-BdB18EpN.js → Playground-BiEa7THU.js} +1 -1
- package/dist/assets/{PromptsPage-C7vU_XHW.js → PromptsPage-BYtZdliB.js} +1 -1
- package/dist/assets/{RunsPage-BsIYybEZ.js → RunsPage-DD6KPNKm.js} +1 -1
- package/dist/assets/{SandboxManager-DKcCgC-r.js → SandboxManager-C8tBjBXY.js} +1 -1
- package/dist/assets/SettingsPage-77tDzDuD.js +1 -0
- package/dist/assets/{TailscalePage-BhGXhs0h.js → TailscalePage-B0TbcOPL.js} +1 -1
- package/dist/assets/{index-C0oFg9YU.js → index-BM8QkiV8.js} +58 -58
- package/dist/assets/{sw-register-CWqt9RnE.js → sw-register-Cr16zatT.js} +1 -1
- package/dist/index.html +1 -1
- package/dist/sw.js +1 -1
- package/package.json +1 -1
- package/server/codex-app-server.ts +61 -0
- package/server/codex-auth.test.ts +254 -0
- package/server/codex-auth.ts +315 -0
- package/server/codex-models.ts +2 -43
- package/server/protocol/codex-upstream/ClientRequest.ts.txt +62 -17
- package/server/protocol/codex-upstream/README.md +1 -1
- package/server/protocol/codex-upstream/ServerNotification.ts.txt +52 -4
- package/server/protocol/codex-upstream/ServerRequest.ts.txt +4 -1
- package/server/protocol/codex-upstream/v2/DynamicToolCallParams.ts.txt +1 -1
- package/server/routes/codex-auth-routes.test.ts +115 -0
- package/server/routes/codex-auth-routes.ts +49 -0
- package/server/routes.ts +2 -0
- package/dist/assets/CronManager-Dbz9rad2.js +0 -1
- package/dist/assets/MagicUIView-wrboravS.js +0 -1
- package/dist/assets/SettingsPage-BIvJyPOd.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{_ as u}from"./index-
|
|
1
|
+
import{_ as u}from"./index-BM8QkiV8.js";function w(r={}){const{immediate:i=!1,onNeedReload:d,onNeedRefresh:_,onOfflineReady:s,onRegistered:n,onRegisteredSW:o,onRegisterError:t}=r;let a,c;const l=async(e=!0)=>{await c};async function f(){if("serviceWorker"in navigator){if(a=await u(async()=>{const{Workbox:e}=await import("./workbox-window.prod.es5-BBnX5xw4.js");return{Workbox:e}},[]).then(({Workbox:e})=>new e("/sw.js",{scope:"/",type:"classic"})).catch(e=>{t==null||t(e)}),!a)return;a.addEventListener("activated",e=>{(e.isUpdate||e.isExternal)&&(d?d():window.location.reload())}),a.addEventListener("installed",e=>{e.isUpdate||s==null||s()}),a.register({immediate:i}).then(e=>{o?o("/sw.js",e):n==null||n(e)}).catch(e=>{t==null||t(e)})}}return c=f(),l}w({onRegisteredSW(r,i){i&&setInterval(()=>{i.update()},3600*1e3)},onOfflineReady(){console.log("[SW] Offline-ready: all assets precached")}});
|
package/dist/index.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<meta name="apple-mobile-web-app-title" content="Companion ME" />
|
|
12
12
|
<meta name="theme-color" content="#d97757" />
|
|
13
13
|
<title>The Companion — Moritz Edition</title>
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-BM8QkiV8.js"></script>
|
|
15
15
|
<link rel="stylesheet" crossorigin href="/assets/index-CUfPwiv5.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body class="bg-cc-bg text-cc-fg">
|
package/dist/sw.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
try{self["workbox:core:7.4.0"]&&_()}catch{}const x=(a,...e)=>{let t=a;return e.length>0&&(t+=` :: ${JSON.stringify(e)}`),t},N=x;class l extends Error{constructor(e,t){const s=N(e,t);super(s),this.name=e,this.details=t}}const E=new Set,f={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:typeof registration<"u"?registration.scope:""},b=a=>[f.prefix,a,f.suffix].filter(e=>e&&e.length>0).join("-"),O=a=>{for(const e of Object.keys(f))a(e)},C={updateDetails:a=>{O(e=>{typeof a[e]=="string"&&(f[e]=a[e])})},getGoogleAnalyticsName:a=>a||b(f.googleAnalytics),getPrecacheName:a=>a||b(f.precache),getPrefix:()=>f.prefix,getRuntimeName:a=>a||b(f.runtime),getSuffix:()=>f.suffix};function P(a,e){const t=new URL(a);for(const s of e)t.searchParams.delete(s);return t.href}async function I(a,e,t,s){const n=P(e.url,t);if(e.url===n)return a.match(e,s);const r=Object.assign(Object.assign({},s),{ignoreSearch:!0}),c=await a.keys(e,r);for(const i of c){const o=P(i.url,t);if(n===o)return a.match(i,s)}}let y;function M(){if(y===void 0){const a=new Response("");if("body"in a)try{new Response(a.body),y=!0}catch{y=!1}y=!1}return y}class D{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}}async function S(){for(const a of E)await a()}const W=a=>new URL(String(a),location.href).href.replace(new RegExp(`^${location.origin}`),"");function A(a){return new Promise(e=>setTimeout(e,a))}function K(a,e){const t=e();return a.waitUntil(t),t}async function q(a,e){let t=null;if(a.url&&(t=new URL(a.url).origin),t!==self.location.origin)throw new l("cross-origin-copy-response",{origin:t});const s=a.clone(),r={headers:new Headers(s.headers),status:s.status,statusText:s.statusText},c=M()?s.body:await s.blob();return new Response(c,r)}function j(){self.addEventListener("activate",()=>self.clients.claim())}try{self["workbox:precaching:7.4.0"]&&_()}catch{}const H="__WB_REVISION__";function F(a){if(!a)throw new l("add-to-cache-list-unexpected-type",{entry:a});if(typeof a=="string"){const r=new URL(a,location.href);return{cacheKey:r.href,url:r.href}}const{revision:e,url:t}=a;if(!t)throw new l("add-to-cache-list-unexpected-type",{entry:a});if(!e){const r=new URL(t,location.href);return{cacheKey:r.href,url:r.href}}const s=new URL(t,location.href),n=new URL(t,location.href);return s.searchParams.set(H,e),{cacheKey:s.href,url:n.href}}class B{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:s})=>{if(e.type==="install"&&t&&t.originalRequest&&t.originalRequest instanceof Request){const n=t.originalRequest.url;s?this.notUpdatedURLs.push(n):this.updatedURLs.push(n)}return s}}}class ${constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:t,params:s})=>{const n=(s==null?void 0:s.cacheKey)||this._precacheController.getCacheKeyForURL(t.url);return n?new Request(n,{headers:t.headers}):t},this._precacheController=e}}try{self["workbox:strategies:7.4.0"]&&_()}catch{}function m(a){return typeof a=="string"?new Request(a):a}class G{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new D,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(const s of this._plugins)this._pluginStateMap.set(s,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){const{event:t}=this;let s=m(e);if(s.mode==="navigate"&&t instanceof FetchEvent&&t.preloadResponse){const c=await t.preloadResponse;if(c)return c}const n=this.hasCallback("fetchDidFail")?s.clone():null;try{for(const c of this.iterateCallbacks("requestWillFetch"))s=await c({request:s.clone(),event:t})}catch(c){if(c instanceof Error)throw new l("plugin-error-request-will-fetch",{thrownErrorMessage:c.message})}const r=s.clone();try{let c;c=await fetch(s,s.mode==="navigate"?void 0:this._strategy.fetchOptions);for(const i of this.iterateCallbacks("fetchDidSucceed"))c=await i({event:t,request:r,response:c});return c}catch(c){throw n&&await this.runCallbacks("fetchDidFail",{error:c,event:t,originalRequest:n.clone(),request:r.clone()}),c}}async fetchAndCachePut(e){const t=await this.fetch(e),s=t.clone();return this.waitUntil(this.cachePut(e,s)),t}async cacheMatch(e){const t=m(e);let s;const{cacheName:n,matchOptions:r}=this._strategy,c=await this.getCacheKey(t,"read"),i=Object.assign(Object.assign({},r),{cacheName:n});s=await caches.match(c,i);for(const o of this.iterateCallbacks("cachedResponseWillBeUsed"))s=await o({cacheName:n,matchOptions:r,cachedResponse:s,request:c,event:this.event})||void 0;return s}async cachePut(e,t){const s=m(e);await A(0);const n=await this.getCacheKey(s,"write");if(!t)throw new l("cache-put-with-no-response",{url:W(n.url)});const r=await this._ensureResponseSafeToCache(t);if(!r)return!1;const{cacheName:c,matchOptions:i}=this._strategy,o=await self.caches.open(c),h=this.hasCallback("cacheDidUpdate"),p=h?await I(o,n.clone(),["__WB_REVISION__"],i):null;try{await o.put(n,h?r.clone():r)}catch(u){if(u instanceof Error)throw u.name==="QuotaExceededError"&&await S(),u}for(const u of this.iterateCallbacks("cacheDidUpdate"))await u({cacheName:c,oldResponse:p,newResponse:r.clone(),request:n,event:this.event});return!0}async getCacheKey(e,t){const s=`${e.url} | ${t}`;if(!this._cacheKeys[s]){let n=e;for(const r of this.iterateCallbacks("cacheKeyWillBeUsed"))n=m(await r({mode:t,request:n,event:this.event,params:this.params}));this._cacheKeys[s]=n}return this._cacheKeys[s]}hasCallback(e){for(const t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(const s of this.iterateCallbacks(e))await s(t)}*iterateCallbacks(e){for(const t of this._strategy.plugins)if(typeof t[e]=="function"){const s=this._pluginStateMap.get(t);yield r=>{const c=Object.assign(Object.assign({},r),{state:s});return t[e](c)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){const e=this._extendLifetimePromises.splice(0),s=(await Promise.allSettled(e)).find(n=>n.status==="rejected");if(s)throw s.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,s=!1;for(const n of this.iterateCallbacks("cacheWillUpdate"))if(t=await n({request:this.request,response:t,event:this.event})||void 0,s=!0,!t)break;return s||t&&t.status!==200&&(t=void 0),t}}class V{constructor(e={}){this.cacheName=C.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){const[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});const t=e.event,s=typeof e.request=="string"?new Request(e.request):e.request,n="params"in e?e.params:void 0,r=new G(this,{event:t,request:s,params:n}),c=this._getResponse(r,s,t),i=this._awaitComplete(c,r,s,t);return[c,i]}async _getResponse(e,t,s){await e.runCallbacks("handlerWillStart",{event:s,request:t});let n;try{if(n=await this._handle(t,e),!n||n.type==="error")throw new l("no-response",{url:t.url})}catch(r){if(r instanceof Error){for(const c of e.iterateCallbacks("handlerDidError"))if(n=await c({error:r,event:s,request:t}),n)break}if(!n)throw r}for(const r of e.iterateCallbacks("handlerWillRespond"))n=await r({event:s,request:t,response:n});return n}async _awaitComplete(e,t,s,n){let r,c;try{r=await e}catch{}try{await t.runCallbacks("handlerDidRespond",{event:n,request:s,response:r}),await t.doneWaiting()}catch(i){i instanceof Error&&(c=i)}if(await t.runCallbacks("handlerDidComplete",{event:n,request:s,response:r,error:c}),t.destroy(),c)throw c}}class d extends V{constructor(e={}){e.cacheName=C.getPrecacheName(e.cacheName),super(e),this._fallbackToNetwork=e.fallbackToNetwork!==!1,this.plugins.push(d.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){const s=await t.cacheMatch(e);return s||(t.event&&t.event.type==="install"?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,t){let s;const n=t.params||{};if(this._fallbackToNetwork){const r=n.integrity,c=e.integrity,i=!c||c===r;s=await t.fetch(new Request(e,{integrity:e.mode!=="no-cors"?c||r:void 0})),r&&i&&e.mode!=="no-cors"&&(this._useDefaultCacheabilityPluginIfNeeded(),await t.cachePut(e,s.clone()))}else throw new l("missing-precache-entry",{cacheName:this.cacheName,url:e.url});return s}async _handleInstall(e,t){this._useDefaultCacheabilityPluginIfNeeded();const s=await t.fetch(e);if(!await t.cachePut(e,s.clone()))throw new l("bad-precaching-response",{url:e.url,status:s.status});return s}_useDefaultCacheabilityPluginIfNeeded(){let e=null,t=0;for(const[s,n]of this.plugins.entries())n!==d.copyRedirectedCacheableResponsesPlugin&&(n===d.defaultPrecacheCacheabilityPlugin&&(e=s),n.cacheWillUpdate&&t++);t===0?this.plugins.push(d.defaultPrecacheCacheabilityPlugin):t>1&&e!==null&&this.plugins.splice(e,1)}}d.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:a}){return!a||a.status>=400?null:a}};d.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:a}){return a.redirected?await q(a):a}};class Q{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:s=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new d({cacheName:C.getPrecacheName(e),plugins:[...t,new $({precacheController:this})],fallbackToNetwork:s}),this.install=this.install.bind(this),this.activate=this.activate.bind(this)}get strategy(){return this._strategy}precache(e){this.addToCacheList(e),this._installAndActiveListenersAdded||(self.addEventListener("install",this.install),self.addEventListener("activate",this.activate),this._installAndActiveListenersAdded=!0)}addToCacheList(e){const t=[];for(const s of e){typeof s=="string"?t.push(s):s&&s.revision===void 0&&t.push(s.url);const{cacheKey:n,url:r}=F(s),c=typeof s!="string"&&s.revision?"reload":"default";if(this._urlsToCacheKeys.has(r)&&this._urlsToCacheKeys.get(r)!==n)throw new l("add-to-cache-list-conflicting-entries",{firstEntry:this._urlsToCacheKeys.get(r),secondEntry:n});if(typeof s!="string"&&s.integrity){if(this._cacheKeysToIntegrities.has(n)&&this._cacheKeysToIntegrities.get(n)!==s.integrity)throw new l("add-to-cache-list-conflicting-integrities",{url:r});this._cacheKeysToIntegrities.set(n,s.integrity)}if(this._urlsToCacheKeys.set(r,n),this._urlsToCacheModes.set(r,c),t.length>0){const i=`Workbox is precaching URLs without revision info: ${t.join(", ")}
|
|
2
|
-
This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(i)}}}install(e){return K(e,async()=>{const t=new B;this.strategy.plugins.push(t);for(const[r,c]of this._urlsToCacheKeys){const i=this._cacheKeysToIntegrities.get(c),o=this._urlsToCacheModes.get(r),h=new Request(r,{integrity:i,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:c},request:h,event:e}))}const{updatedURLs:s,notUpdatedURLs:n}=t;return{updatedURLs:s,notUpdatedURLs:n}})}activate(e){return K(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),s=await t.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(const c of s)n.has(c.url)||(await t.delete(c),r.push(c.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const t=this.getCacheKeyForURL(e);if(!t)throw new l("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let U;const L=()=>(U||(U=new Q),U);try{self["workbox:routing:7.4.0"]&&_()}catch{}const v="GET",R=a=>a&&typeof a=="object"?a:{handle:a};class g{constructor(e,t,s=v){this.handler=R(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=R(e)}}class z extends g{constructor(e,t,s){const n=({url:r})=>{const c=e.exec(r.href);if(c&&!(r.origin!==location.origin&&c.index!==0))return c.slice(1)};super(n,t,s)}}class J{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&e.data.type==="CACHE_URLS"){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(n=>{typeof n=="string"&&(n=[n]);const r=new Request(...n);return this.handleRequest({request:r,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:r,route:c}=this.findMatchingRoute({event:t,request:e,sameOrigin:n,url:s});let i=c&&c.handler;const o=e.method;if(!i&&this._defaultHandlerMap.has(o)&&(i=this._defaultHandlerMap.get(o)),!i)return;let h;try{h=i.handle({url:s,request:e,event:t,params:r})}catch(u){h=Promise.reject(u)}const p=c&&c.catchHandler;return h instanceof Promise&&(this._catchHandler||p)&&(h=h.catch(async u=>{if(p)try{return await p.handle({url:s,request:e,event:t,params:r})}catch(k){k instanceof Error&&(u=k)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw u})),h}findMatchingRoute({url:e,sameOrigin:t,request:s,event:n}){const r=this._routes.get(s.method)||[];for(const c of r){let i;const o=c.match({url:e,sameOrigin:t,request:s,event:n});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o=="boolean")&&(i=void 0),{route:c,params:i}}return{}}setDefaultHandler(e,t=v){this._defaultHandlerMap.set(t,R(e))}setCatchHandler(e){this._catchHandler=R(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new l("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new l("unregister-route-route-not-registered")}}let w;const X=()=>(w||(w=new J,w.addFetchListener(),w.addCacheListener()),w);function T(a,e,t){let s;if(typeof a=="string"){const r=new URL(a,location.href),c=({url:i})=>i.href===r.href;s=new g(c,e,t)}else if(a instanceof RegExp)s=new z(a,e,t);else if(typeof a=="function")s=new g(a,e,t);else if(a instanceof g)s=a;else throw new l("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return X().registerRoute(s),s}function Y(a,e=[]){for(const t of[...a.searchParams.keys()])e.some(s=>s.test(t))&&a.searchParams.delete(t);return a}function*Z(a,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:n}={}){const r=new URL(a,location.href);r.hash="",yield r.href;const c=Y(r,e);if(yield c.href,t&&c.pathname.endsWith("/")){const i=new URL(c.href);i.pathname+=t,yield i.href}if(s){const i=new URL(c.href);i.pathname+=".html",yield i.href}if(n){const i=n({url:r});for(const o of i)yield o.href}}class ee extends g{constructor(e,t){const s=({request:n})=>{const r=e.getURLsToCacheKeys();for(const c of Z(n.url,t)){const i=r.get(c);if(i){const o=e.getIntegrityForCacheKey(i);return{cacheKey:i,integrity:o}}}};super(s,e.strategy)}}function te(a){const e=L(),t=new ee(e,a);T(t)}const se="-precache-",ae=async(a,e=se)=>{const s=(await self.caches.keys()).filter(n=>n.includes(e)&&n.includes(self.registration.scope)&&n!==a);return await Promise.all(s.map(n=>self.caches.delete(n))),s};function ne(){self.addEventListener("activate",(a=>{const e=C.getPrecacheName();a.waitUntil(ae(e).then(t=>{}))}))}function re(a){return L().createHandlerBoundToURL(a)}function ce(a){L().precache(a)}function ie(a,e){ce(a),te(e)}class oe extends g{constructor(e,{allowlist:t=[/./],denylist:s=[]}={}){super(n=>this._match(n),e),this._allowlist=t,this._denylist=s}_match({url:e,request:t}){if(t&&t.mode!=="navigate")return!1;const s=e.pathname+e.search;for(const n of this._denylist)if(n.test(s))return!1;return!!this._allowlist.some(n=>n.test(s))}}self.skipWaiting();j();ie([{"revision":"fab346dacb0b9b9af12554e9a59f004d","url":"logo.svg"},{"revision":"623840baec64a3438663496bb1050d30","url":"logo-docker.svg"},{"revision":"c5c8611b432190ec28522f1a2dfcbb7f","url":"logo-codex.svg"},{"revision":"
|
|
2
|
+
This is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(i)}}}install(e){return K(e,async()=>{const t=new B;this.strategy.plugins.push(t);for(const[r,c]of this._urlsToCacheKeys){const i=this._cacheKeysToIntegrities.get(c),o=this._urlsToCacheModes.get(r),h=new Request(r,{integrity:i,cache:o,credentials:"same-origin"});await Promise.all(this.strategy.handleAll({params:{cacheKey:c},request:h,event:e}))}const{updatedURLs:s,notUpdatedURLs:n}=t;return{updatedURLs:s,notUpdatedURLs:n}})}activate(e){return K(e,async()=>{const t=await self.caches.open(this.strategy.cacheName),s=await t.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(const c of s)n.has(c.url)||(await t.delete(c),r.push(c.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){const t=e instanceof Request?e.url:e,s=this.getCacheKeyForURL(t);if(s)return(await self.caches.open(this.strategy.cacheName)).match(s)}createHandlerBoundToURL(e){const t=this.getCacheKeyForURL(e);if(!t)throw new l("non-precached-url",{url:e});return s=>(s.request=new Request(e),s.params=Object.assign({cacheKey:t},s.params),this.strategy.handle(s))}}let U;const L=()=>(U||(U=new Q),U);try{self["workbox:routing:7.4.0"]&&_()}catch{}const v="GET",R=a=>a&&typeof a=="object"?a:{handle:a};class g{constructor(e,t,s=v){this.handler=R(t),this.match=e,this.method=s}setCatchHandler(e){this.catchHandler=R(e)}}class z extends g{constructor(e,t,s){const n=({url:r})=>{const c=e.exec(r.href);if(c&&!(r.origin!==location.origin&&c.index!==0))return c.slice(1)};super(n,t,s)}}class J{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener("fetch",(e=>{const{request:t}=e,s=this.handleRequest({request:t,event:e});s&&e.respondWith(s)}))}addCacheListener(){self.addEventListener("message",(e=>{if(e.data&&e.data.type==="CACHE_URLS"){const{payload:t}=e.data,s=Promise.all(t.urlsToCache.map(n=>{typeof n=="string"&&(n=[n]);const r=new Request(...n);return this.handleRequest({request:r,event:e})}));e.waitUntil(s),e.ports&&e.ports[0]&&s.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){const s=new URL(e.url,location.href);if(!s.protocol.startsWith("http"))return;const n=s.origin===location.origin,{params:r,route:c}=this.findMatchingRoute({event:t,request:e,sameOrigin:n,url:s});let i=c&&c.handler;const o=e.method;if(!i&&this._defaultHandlerMap.has(o)&&(i=this._defaultHandlerMap.get(o)),!i)return;let h;try{h=i.handle({url:s,request:e,event:t,params:r})}catch(u){h=Promise.reject(u)}const p=c&&c.catchHandler;return h instanceof Promise&&(this._catchHandler||p)&&(h=h.catch(async u=>{if(p)try{return await p.handle({url:s,request:e,event:t,params:r})}catch(k){k instanceof Error&&(u=k)}if(this._catchHandler)return this._catchHandler.handle({url:s,request:e,event:t});throw u})),h}findMatchingRoute({url:e,sameOrigin:t,request:s,event:n}){const r=this._routes.get(s.method)||[];for(const c of r){let i;const o=c.match({url:e,sameOrigin:t,request:s,event:n});if(o)return i=o,(Array.isArray(i)&&i.length===0||o.constructor===Object&&Object.keys(o).length===0||typeof o=="boolean")&&(i=void 0),{route:c,params:i}}return{}}setDefaultHandler(e,t=v){this._defaultHandlerMap.set(t,R(e))}setCatchHandler(e){this._catchHandler=R(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new l("unregister-route-but-not-found-with-method",{method:e.method});const t=this._routes.get(e.method).indexOf(e);if(t>-1)this._routes.get(e.method).splice(t,1);else throw new l("unregister-route-route-not-registered")}}let w;const X=()=>(w||(w=new J,w.addFetchListener(),w.addCacheListener()),w);function T(a,e,t){let s;if(typeof a=="string"){const r=new URL(a,location.href),c=({url:i})=>i.href===r.href;s=new g(c,e,t)}else if(a instanceof RegExp)s=new z(a,e,t);else if(typeof a=="function")s=new g(a,e,t);else if(a instanceof g)s=a;else throw new l("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});return X().registerRoute(s),s}function Y(a,e=[]){for(const t of[...a.searchParams.keys()])e.some(s=>s.test(t))&&a.searchParams.delete(t);return a}function*Z(a,{ignoreURLParametersMatching:e=[/^utm_/,/^fbclid$/],directoryIndex:t="index.html",cleanURLs:s=!0,urlManipulation:n}={}){const r=new URL(a,location.href);r.hash="",yield r.href;const c=Y(r,e);if(yield c.href,t&&c.pathname.endsWith("/")){const i=new URL(c.href);i.pathname+=t,yield i.href}if(s){const i=new URL(c.href);i.pathname+=".html",yield i.href}if(n){const i=n({url:r});for(const o of i)yield o.href}}class ee extends g{constructor(e,t){const s=({request:n})=>{const r=e.getURLsToCacheKeys();for(const c of Z(n.url,t)){const i=r.get(c);if(i){const o=e.getIntegrityForCacheKey(i);return{cacheKey:i,integrity:o}}}};super(s,e.strategy)}}function te(a){const e=L(),t=new ee(e,a);T(t)}const se="-precache-",ae=async(a,e=se)=>{const s=(await self.caches.keys()).filter(n=>n.includes(e)&&n.includes(self.registration.scope)&&n!==a);return await Promise.all(s.map(n=>self.caches.delete(n))),s};function ne(){self.addEventListener("activate",(a=>{const e=C.getPrecacheName();a.waitUntil(ae(e).then(t=>{}))}))}function re(a){return L().createHandlerBoundToURL(a)}function ce(a){L().precache(a)}function ie(a,e){ce(a),te(e)}class oe extends g{constructor(e,{allowlist:t=[/./],denylist:s=[]}={}){super(n=>this._match(n),e),this._allowlist=t,this._denylist=s}_match({url:e,request:t}){if(t&&t.mode!=="navigate")return!1;const s=e.pathname+e.search;for(const n of this._denylist)if(n.test(s))return!1;return!!this._allowlist.some(n=>n.test(s))}}self.skipWaiting();j();ie([{"revision":"fab346dacb0b9b9af12554e9a59f004d","url":"logo.svg"},{"revision":"623840baec64a3438663496bb1050d30","url":"logo-docker.svg"},{"revision":"c5c8611b432190ec28522f1a2dfcbb7f","url":"logo-codex.svg"},{"revision":"24362bb58b4c98ab977d21940ecd4fa0","url":"index.html"},{"revision":"1421e9c6cad8218ef5689b85034d3a95","url":"icon-512.png"},{"revision":"b96da5a0b6636012517f54b0e69c304d","url":"icon-192.png"},{"revision":"c64ca8ae8596d4ffe13d0f591beb2274","url":"favicon.svg"},{"revision":"6481face04bd08e6f0bfc2a633840b57","url":"apple-touch-icon.png"},{"revision":"83304194a3c8be2b5b61242eeb1c1046","url":"fonts/MesloLGSNerdFontMono-Regular.woff2"},{"revision":"60fafe18cbcb717c51cdabf87f9490f0","url":"fonts/MesloLGSNerdFontMono-Bold.woff2"},{"revision":null,"url":"assets/workbox-window.prod.es5-BBnX5xw4.js"},{"revision":null,"url":"assets/sw-register-Cr16zatT.js"},{"revision":null,"url":"assets/index-CUfPwiv5.css"},{"revision":null,"url":"assets/index-BM8QkiV8.js"},{"revision":null,"url":"assets/TailscalePage-B0TbcOPL.js"},{"revision":null,"url":"assets/SettingsPage-77tDzDuD.js"},{"revision":null,"url":"assets/SandboxManager-C8tBjBXY.js"},{"revision":null,"url":"assets/RunsPage-DD6KPNKm.js"},{"revision":null,"url":"assets/PromptsPage-BYtZdliB.js"},{"revision":null,"url":"assets/Playground-BiEa7THU.js"},{"revision":null,"url":"assets/MagicUIView-AKoy6jOb.js"},{"revision":null,"url":"assets/MagicUIDashboard-exuRY5LQ.js"},{"revision":null,"url":"assets/LinearSettingsPage-BwVTEKqs.js"},{"revision":null,"url":"assets/LinearOAuthSettingsPage-CPW4TQuD.js"},{"revision":null,"url":"assets/IntegrationsPage-DRP477b0.js"},{"revision":null,"url":"assets/DashboardPage-N7MWZiJQ.js"},{"revision":null,"url":"assets/CronManager-CGfXlVZS.js"},{"revision":null,"url":"assets/AgentsPage-V7NBPNSh.js"}]);ne();T(new oe(re("index.html"),{denylist:[/^\/api/,/^\/ws/]}));
|
package/package.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { getEnrichedPath, resolveBinary } from "./path-resolver.js";
|
|
4
|
+
|
|
5
|
+
/** A resolved plan for spawning `codex app-server`. */
|
|
6
|
+
export interface CodexAppServerSpawnPlan {
|
|
7
|
+
/** argv[0] plus arguments, ready for `spawn(cmd[0], cmd.slice(1))`. */
|
|
8
|
+
cmd: string[];
|
|
9
|
+
env: NodeJS.ProcessEnv;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build the spawn command for `codex app-server`, resolving the binary and
|
|
14
|
+
* applying the sibling-node shim that cli-launcher uses so a CLI shipped with a
|
|
15
|
+
* bundled node still launches correctly.
|
|
16
|
+
*
|
|
17
|
+
* Extracted from codex-models.ts so the auth flow (codex-auth.ts) can reuse the
|
|
18
|
+
* exact same binary resolution instead of duplicating it. `extraEnv` lets a
|
|
19
|
+
* caller pin CODEX_HOME (the auth flow must write to the real ~/.codex rather
|
|
20
|
+
* than a per-session Companion home).
|
|
21
|
+
*
|
|
22
|
+
* Returns null when the binary cannot be resolved.
|
|
23
|
+
*/
|
|
24
|
+
export function buildCodexAppServerSpawn(
|
|
25
|
+
binaryName: string,
|
|
26
|
+
extraEnv?: Record<string, string | undefined>,
|
|
27
|
+
extraArgs: string[] = [],
|
|
28
|
+
): CodexAppServerSpawnPlan | null {
|
|
29
|
+
const resolved = resolveBinary(binaryName);
|
|
30
|
+
if (!resolved) return null;
|
|
31
|
+
|
|
32
|
+
const binaryDir = resolve(resolved, "..");
|
|
33
|
+
const siblingNode = join(binaryDir, "node");
|
|
34
|
+
const enrichedPath = getEnrichedPath();
|
|
35
|
+
const pathSep = process.platform === "win32" ? ";" : ":";
|
|
36
|
+
const spawnPath = [binaryDir, ...enrichedPath.split(pathSep)].filter(Boolean).join(pathSep);
|
|
37
|
+
|
|
38
|
+
const args = ["app-server", ...extraArgs];
|
|
39
|
+
|
|
40
|
+
let cmd: string[];
|
|
41
|
+
if (existsSync(siblingNode)) {
|
|
42
|
+
let codexScript: string;
|
|
43
|
+
try {
|
|
44
|
+
codexScript = realpathSync(resolved);
|
|
45
|
+
} catch {
|
|
46
|
+
codexScript = resolved;
|
|
47
|
+
}
|
|
48
|
+
cmd = [siblingNode, codexScript, ...args];
|
|
49
|
+
} else {
|
|
50
|
+
const isCmdScript = process.platform === "win32"
|
|
51
|
+
&& (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
52
|
+
cmd = isCmdScript ? ["cmd.exe", "/c", resolved, ...args] : [resolved, ...args];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
cmd,
|
|
57
|
+
// CLAUDECODE is stripped so Codex does not think it is running nested
|
|
58
|
+
// inside a Claude Code session.
|
|
59
|
+
env: { ...process.env, CLAUDECODE: undefined, PATH: spawnPath, ...extraEnv },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
3
|
+
import { PassThrough } from "node:stream";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tests for the Codex ChatGPT device-code login manager.
|
|
7
|
+
*
|
|
8
|
+
* The manager drives a real `codex app-server` child process over newline
|
|
9
|
+
* delimited JSON-RPC, so these tests stub the spawn boundary with a fake child
|
|
10
|
+
* that speaks the same framing. That lets us assert the state machine
|
|
11
|
+
* (pending → success/error/canceled), idempotency, and the auth propagation
|
|
12
|
+
* behaviour without needing an authenticated Codex install.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const spawnMock = vi.fn();
|
|
16
|
+
const buildSpawnMock = vi.fn(() => ({ cmd: ["codex", "app-server"], env: {} }));
|
|
17
|
+
|
|
18
|
+
vi.mock("node:child_process", () => ({ spawn: spawnMock }));
|
|
19
|
+
vi.mock("./codex-app-server.js", () => ({ buildCodexAppServerSpawn: buildSpawnMock }));
|
|
20
|
+
|
|
21
|
+
/** A fake `codex app-server` child speaking NDJSON JSON-RPC. */
|
|
22
|
+
class FakeChild extends EventEmitter {
|
|
23
|
+
stdout = new PassThrough();
|
|
24
|
+
stdin: { write: (chunk: string) => boolean };
|
|
25
|
+
killed = false;
|
|
26
|
+
/** Every request the manager sent, parsed. */
|
|
27
|
+
sent: Array<{ method: string; id?: number; params?: unknown }> = [];
|
|
28
|
+
/** Response factories keyed by method; return undefined to stay silent. */
|
|
29
|
+
responders: Record<string, (params: unknown) => unknown> = {};
|
|
30
|
+
|
|
31
|
+
constructor() {
|
|
32
|
+
super();
|
|
33
|
+
this.stdin = {
|
|
34
|
+
write: (chunk: string) => {
|
|
35
|
+
for (const line of chunk.split("\n")) {
|
|
36
|
+
if (!line.trim()) continue;
|
|
37
|
+
const msg = JSON.parse(line);
|
|
38
|
+
this.sent.push(msg);
|
|
39
|
+
if (msg.method === "initialize") {
|
|
40
|
+
this.reply(msg.id, { userAgent: "test" });
|
|
41
|
+
} else if (this.responders[msg.method]) {
|
|
42
|
+
const result = this.responders[msg.method](msg.params);
|
|
43
|
+
if (result !== undefined && typeof msg.id === "number") this.reply(msg.id, result);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
reply(id: number, result: unknown): void {
|
|
52
|
+
// Async so the manager's promise handlers are already attached.
|
|
53
|
+
setImmediate(() => this.stdout.write(JSON.stringify({ id, result }) + "\n"));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
notify(method: string, params: unknown): void {
|
|
57
|
+
this.stdout.write(JSON.stringify({ method, params }) + "\n");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
kill(): void {
|
|
61
|
+
this.killed = true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let child: FakeChild;
|
|
66
|
+
let auth: typeof import("./codex-auth.js");
|
|
67
|
+
|
|
68
|
+
/** Poll until `predicate` holds, so we don't rely on fixed sleeps. */
|
|
69
|
+
async function until(predicate: () => boolean, timeoutMs = 1000): Promise<void> {
|
|
70
|
+
const deadline = Date.now() + timeoutMs;
|
|
71
|
+
while (Date.now() < deadline) {
|
|
72
|
+
if (predicate()) return;
|
|
73
|
+
await new Promise((r) => setTimeout(r, 5));
|
|
74
|
+
}
|
|
75
|
+
throw new Error("Condition not met in time");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const DEVICE_CODE_RESULT = {
|
|
79
|
+
type: "chatgptDeviceCode",
|
|
80
|
+
loginId: "login-1",
|
|
81
|
+
userCode: "ABCD-1234",
|
|
82
|
+
verificationUrl: "https://auth.openai.com/codex/device",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
beforeEach(async () => {
|
|
86
|
+
vi.resetModules();
|
|
87
|
+
child = new FakeChild();
|
|
88
|
+
spawnMock.mockReset();
|
|
89
|
+
spawnMock.mockImplementation(() => child);
|
|
90
|
+
buildSpawnMock.mockReturnValue({ cmd: ["codex", "app-server"], env: {} });
|
|
91
|
+
auth = await import("./codex-auth.js");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
afterEach(() => {
|
|
95
|
+
auth._resetCodexAuthState();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("startDeviceLogin", () => {
|
|
99
|
+
it("returns the device code and enters the pending state", async () => {
|
|
100
|
+
child.responders["account/login/start"] = () => DEVICE_CODE_RESULT;
|
|
101
|
+
|
|
102
|
+
const status = await auth.startDeviceLogin();
|
|
103
|
+
|
|
104
|
+
expect(status.state).toBe("pending");
|
|
105
|
+
expect(status.userCode).toBe("ABCD-1234");
|
|
106
|
+
expect(status.verificationUrl).toBe("https://auth.openai.com/codex/device");
|
|
107
|
+
// Device code must be requested explicitly — the plain `chatgpt` type would
|
|
108
|
+
// use a localhost OAuth callback, which breaks for a remote server.
|
|
109
|
+
const start = child.sent.find((m) => m.method === "account/login/start");
|
|
110
|
+
expect(start?.params).toEqual({ type: "chatgptDeviceCode" });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("is idempotent while a login is in flight so the user's code stays valid", async () => {
|
|
114
|
+
child.responders["account/login/start"] = () => DEVICE_CODE_RESULT;
|
|
115
|
+
|
|
116
|
+
const first = await auth.startDeviceLogin();
|
|
117
|
+
const second = await auth.startDeviceLogin();
|
|
118
|
+
|
|
119
|
+
expect(second).toEqual(first);
|
|
120
|
+
// Only one app-server spawned: a second spawn would orphan the first child
|
|
121
|
+
// and invalidate a code the user may already be typing.
|
|
122
|
+
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("transitions to success when Codex reports login completed", async () => {
|
|
126
|
+
child.responders["account/login/start"] = () => DEVICE_CODE_RESULT;
|
|
127
|
+
await auth.startDeviceLogin();
|
|
128
|
+
|
|
129
|
+
child.notify("account/login/completed", { loginId: "login-1", success: true });
|
|
130
|
+
|
|
131
|
+
await until(() => auth.getLoginStatus().state === "success");
|
|
132
|
+
expect(child.killed).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("surfaces the upstream error message when login fails", async () => {
|
|
136
|
+
child.responders["account/login/start"] = () => DEVICE_CODE_RESULT;
|
|
137
|
+
await auth.startDeviceLogin();
|
|
138
|
+
|
|
139
|
+
child.notify("account/login/completed", {
|
|
140
|
+
loginId: "login-1", success: false, error: "Login was not completed",
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await until(() => auth.getLoginStatus().state === "error");
|
|
144
|
+
expect(auth.getLoginStatus().error).toBe("Login was not completed");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("ignores a completion notification for a superseded login id", async () => {
|
|
148
|
+
child.responders["account/login/start"] = () => DEVICE_CODE_RESULT;
|
|
149
|
+
await auth.startDeviceLogin();
|
|
150
|
+
|
|
151
|
+
child.notify("account/login/completed", { loginId: "some-older-login", success: true });
|
|
152
|
+
|
|
153
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
154
|
+
expect(auth.getLoginStatus().state).toBe("pending");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("fails clearly when the CLI is too old to return a device code", async () => {
|
|
158
|
+
// An older Codex answers the RPC but without device-code fields.
|
|
159
|
+
child.responders["account/login/start"] = () => ({ type: "chatgpt", authUrl: "https://x" });
|
|
160
|
+
|
|
161
|
+
await expect(auth.startDeviceLogin()).rejects.toThrow(/device code/i);
|
|
162
|
+
expect(child.killed).toBe(true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("reports an error when the codex binary cannot be resolved", async () => {
|
|
166
|
+
buildSpawnMock.mockReturnValue(null as never);
|
|
167
|
+
await expect(auth.startDeviceLogin()).rejects.toThrow(/not found/i);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("marks the login failed if the app-server dies before completing", async () => {
|
|
171
|
+
child.responders["account/login/start"] = () => DEVICE_CODE_RESULT;
|
|
172
|
+
await auth.startDeviceLogin();
|
|
173
|
+
|
|
174
|
+
child.emit("exit", 1);
|
|
175
|
+
|
|
176
|
+
await until(() => auth.getLoginStatus().state === "error");
|
|
177
|
+
expect(auth.getLoginStatus().error).toMatch(/exited/i);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("cancelLogin", () => {
|
|
182
|
+
it("cancels an in-flight login and kills the child", async () => {
|
|
183
|
+
child.responders["account/login/start"] = () => DEVICE_CODE_RESULT;
|
|
184
|
+
await auth.startDeviceLogin();
|
|
185
|
+
|
|
186
|
+
const status = await auth.cancelLogin();
|
|
187
|
+
|
|
188
|
+
expect(status.state).toBe("canceled");
|
|
189
|
+
expect(child.killed).toBe(true);
|
|
190
|
+
// The terminal result is retained so a poll arriving after cancel sees it.
|
|
191
|
+
expect(auth.getLoginStatus().state).toBe("canceled");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("is a no-op when nothing is in flight", async () => {
|
|
195
|
+
expect((await auth.cancelLogin()).state).toBe("idle");
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("getAccountStatus", () => {
|
|
200
|
+
it("reports a signed-in ChatGPT account with email and plan", async () => {
|
|
201
|
+
child.responders["account/read"] = () => ({
|
|
202
|
+
account: { type: "chatgpt", email: "user@example.com", planType: "pro" },
|
|
203
|
+
requiresOpenaiAuth: false,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const status = await auth.getAccountStatus();
|
|
207
|
+
|
|
208
|
+
expect(status).toMatchObject({
|
|
209
|
+
cliAvailable: true, authenticated: true, method: "chatgpt",
|
|
210
|
+
email: "user@example.com", planType: "pro",
|
|
211
|
+
});
|
|
212
|
+
expect(child.killed).toBe(true); // short-lived probe must not leak a process
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it("distinguishes API-key auth from a ChatGPT subscription", async () => {
|
|
216
|
+
child.responders["account/read"] = () => ({ account: { type: "apiKey" }, requiresOpenaiAuth: false });
|
|
217
|
+
|
|
218
|
+
const status = await auth.getAccountStatus();
|
|
219
|
+
|
|
220
|
+
expect(status.method).toBe("apiKey");
|
|
221
|
+
expect(status.authenticated).toBe(true);
|
|
222
|
+
expect(status.email).toBeNull();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("reports unauthenticated when Codex has no account", async () => {
|
|
226
|
+
child.responders["account/read"] = () => ({ account: null, requiresOpenaiAuth: true });
|
|
227
|
+
|
|
228
|
+
const status = await auth.getAccountStatus();
|
|
229
|
+
|
|
230
|
+
expect(status.authenticated).toBe(false);
|
|
231
|
+
expect(status.method).toBeNull();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("reports the CLI as unavailable rather than throwing", async () => {
|
|
235
|
+
buildSpawnMock.mockReturnValue(null as never);
|
|
236
|
+
|
|
237
|
+
const status = await auth.getAccountStatus();
|
|
238
|
+
|
|
239
|
+
expect(status.cliAvailable).toBe(false);
|
|
240
|
+
expect(status.error).toMatch(/not found/i);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("logout", () => {
|
|
245
|
+
it("calls account/logout and reports success", async () => {
|
|
246
|
+
child.responders["account/logout"] = () => ({});
|
|
247
|
+
|
|
248
|
+
const res = await auth.logout();
|
|
249
|
+
|
|
250
|
+
expect(res.ok).toBe(true);
|
|
251
|
+
expect(child.sent.some((m) => m.method === "account/logout")).toBe(true);
|
|
252
|
+
expect(child.killed).toBe(true);
|
|
253
|
+
});
|
|
254
|
+
});
|