@hienlh/ppm 0.7.15 → 0.7.17
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/CHANGELOG.md +20 -0
- package/bunfig.toml +2 -0
- package/dist/web/assets/chat-tab-C0AcTU9S.js +7 -0
- package/dist/web/assets/{code-editor-SU4Nfh2K.js → code-editor-CYt4hqge.js} +1 -1
- package/dist/web/assets/{database-viewer-Dg4ohCfA.js → database-viewer-CDto4TrW.js} +1 -1
- package/dist/web/assets/{diff-viewer-DnlJFC01.js → diff-viewer-2zSPeCzX.js} +1 -1
- package/dist/web/assets/git-graph-HfH98qwn.js +1 -0
- package/dist/web/assets/index-CTOMzCnZ.js +28 -0
- package/dist/web/assets/index-D6GLlwUx.css +2 -0
- package/dist/web/assets/keybindings-store-DrjDQzVs.js +1 -0
- package/dist/web/assets/{markdown-renderer-BKBMsxI-.js → markdown-renderer-dqkYhU3y.js} +1 -1
- package/dist/web/assets/{postgres-viewer-CqG795AK.js → postgres-viewer-kqZBNVYW.js} +1 -1
- package/dist/web/assets/settings-tab-jhRBFgf_.js +1 -0
- package/dist/web/assets/{sqlite-viewer-C9lMdBbX.js → sqlite-viewer-C2744fw1.js} +1 -1
- package/dist/web/assets/switch-PAf5UhcN.js +1 -0
- package/dist/web/assets/{terminal-tab-DZaF62V1.js → terminal-tab-BDqc6Dl5.js} +1 -1
- package/dist/web/index.html +3 -3
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/providers/claude-agent-sdk.ts +1 -0
- package/src/server/routes/accounts.ts +60 -6
- package/src/services/account.service.ts +180 -17
- package/src/services/claude-usage.service.ts +9 -2
- package/src/services/db.service.ts +25 -3
- package/src/types/api.ts +2 -1
- package/src/types/chat.ts +5 -1
- package/src/web/app.tsx +4 -0
- package/src/web/components/chat/chat-history-bar.tsx +2 -1
- package/src/web/components/chat/message-list.tsx +5 -0
- package/src/web/components/chat/usage-badge.tsx +118 -22
- package/src/web/components/settings/accounts-settings-section.tsx +268 -33
- package/src/web/hooks/use-chat.ts +10 -1
- package/src/web/hooks/use-server-reload.ts +39 -0
- package/src/web/lib/api-settings.ts +49 -0
- package/src/web/styles/globals.css +7 -0
- package/test-claude-oauth-v2.mjs +165 -0
- package/test-claude-oauth.mjs +175 -0
- package/test-verify-oat.mjs +106 -0
- package/dist/web/assets/ai-settings-section-BxCMGg-I.js +0 -1
- package/dist/web/assets/chat-tab-CMBHi9uq.js +0 -7
- package/dist/web/assets/git-graph-BS7u6nZh.js +0 -1
- package/dist/web/assets/index-BUWCv9tx.js +0 -28
- package/dist/web/assets/index-sMxUHxFZ.css +0 -2
- package/dist/web/assets/keybindings-store-BU4VqInW.js +0 -1
- package/dist/web/assets/settings-tab-Beh31IHk.js +0 -1
package/dist/web/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),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),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});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:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(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 t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"dc26f937cb7c3bf676c6cb6ef436c7f0","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"eb9818b9094675c0c5d303168f273345","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"9af0be92dcefdc1f1290441cb5ff5d9b","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"a261b429c39dbb75ae97972d7d005e6d","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"79953d804e1bbacecfd79b85fd679016","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"fdcba0d09aac31df7a0bc652f6e739bd","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/utils-DC-bdPS3.js"},{"revision":null,"url":"assets/use-monaco-theme-Bt1Lr3jH.js"},{"revision":null,"url":"assets/terminal-tab-DZaF62V1.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/table-C0oSLUYn.js"},{"revision":null,"url":"assets/tab-store-0CKk8cSr.js"},{"revision":null,"url":"assets/sqlite-viewer-C9lMdBbX.js"},{"revision":null,"url":"assets/settings-tab-Beh31IHk.js"},{"revision":null,"url":"assets/settings-store-2NQzaOVJ.js"},{"revision":null,"url":"assets/react-rgzL83kk.js"},{"revision":null,"url":"assets/react-CYzKIDNi.js"},{"revision":null,"url":"assets/postgres-viewer-CqG795AK.js"},{"revision":null,"url":"assets/markdown-renderer-BKBMsxI-.js"},{"revision":null,"url":"assets/keybindings-store-BU4VqInW.js"},{"revision":null,"url":"assets/jsx-runtime-wQxeESYQ.js"},{"revision":null,"url":"assets/input-CVIzrYsH.js"},{"revision":null,"url":"assets/index-sMxUHxFZ.css"},{"revision":null,"url":"assets/index-BUWCv9tx.js"},{"revision":null,"url":"assets/git-graph-BS7u6nZh.js"},{"revision":null,"url":"assets/dist-D9RHR8A4.js"},{"revision":null,"url":"assets/diff-viewer-DnlJFC01.js"},{"revision":null,"url":"assets/database-viewer-Dg4ohCfA.js"},{"revision":null,"url":"assets/columns-2-fz8yNaAo.js"},{"revision":null,"url":"assets/code-editor-SU4Nfh2K.js"},{"revision":null,"url":"assets/chat-tab-CMBHi9uq.js"},{"revision":null,"url":"assets/api-client-TUmacMRS.js"},{"revision":null,"url":"assets/ai-settings-section-BxCMGg-I.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
|
1
|
+
try{self[`workbox:core:7.3.0`]&&_()}catch{}var e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n},t=class extends Error{constructor(t,n){let r=e(t,n);super(r),this.name=t,this.details=n}},n={googleAnalytics:`googleAnalytics`,precache:`precache-v2`,prefix:`workbox`,runtime:`runtime`,suffix:typeof registration<`u`?registration.scope:``},r=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(`-`),i=e=>{for(let t of Object.keys(n))e(t)},a={updateDetails:e=>{i(t=>{typeof e[t]==`string`&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||r(n.googleAnalytics),getPrecacheName:e=>e||r(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||r(n.runtime),getSuffix:()=>n.suffix};function o(e,t){let n=t();return e.waitUntil(n),n}try{self[`workbox:precaching:7.3.0`]&&_()}catch{}var s=`__WB_REVISION__`;function c(e){if(!e)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(typeof e==`string`){let t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}let{revision:n,url:r}=e;if(!r)throw new t(`add-to-cache-list-unexpected-type`,{entry:e});if(!n){let e=new URL(r,location.href);return{cacheKey:e.href,url:e.href}}let i=new URL(r,location.href),a=new URL(r,location.href);return i.searchParams.set(s,n),{cacheKey:i.href,url:a.href}}var l=class{constructor(){this.updatedURLs=[],this.notUpdatedURLs=[],this.handlerWillStart=async({request:e,state:t})=>{t&&(t.originalRequest=e)},this.cachedResponseWillBeUsed=async({event:e,state:t,cachedResponse:n})=>{if(e.type===`install`&&t&&t.originalRequest&&t.originalRequest instanceof Request){let e=t.originalRequest.url;n?this.notUpdatedURLs.push(e):this.updatedURLs.push(e)}return n}}},u=class{constructor({precacheController:e}){this.cacheKeyWillBeUsed=async({request:e,params:t})=>{let n=t?.cacheKey||this._precacheController.getCacheKeyForURL(e.url);return n?new Request(n,{headers:e.headers}):e},this._precacheController=e}},d;function f(){if(d===void 0){let e=new Response(``);if(`body`in e)try{new Response(e.body),d=!0}catch{d=!1}d=!1}return d}async function p(e,n){let r=null;if(e.url&&(r=new URL(e.url).origin),r!==self.location.origin)throw new t(`cross-origin-copy-response`,{origin:r});let i=e.clone(),a={headers:new Headers(i.headers),status:i.status,statusText:i.statusText},o=n?n(a):a,s=f()?i.body:await i.blob();return new Response(s,o)}var m=e=>new URL(String(e),location.href).href.replace(RegExp(`^${location.origin}`),``);function h(e,t){let n=new URL(e);for(let e of t)n.searchParams.delete(e);return n.href}async function g(e,t,n,r){let i=h(t.url,n);if(t.url===i)return e.match(t,r);let a=Object.assign(Object.assign({},r),{ignoreSearch:!0}),o=await e.keys(t,a);for(let t of o)if(i===h(t.url,n))return e.match(t,r)}var v=class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},y=new Set;async function b(){for(let e of y)await e()}function x(e){return new Promise(t=>setTimeout(t,e))}try{self[`workbox:strategies:7.3.0`]&&_()}catch{}function S(e){return typeof e==`string`?new Request(e):e}var C=class{constructor(e,t){this._cacheKeys={},Object.assign(this,t),this.event=t.event,this._strategy=e,this._handlerDeferred=new v,this._extendLifetimePromises=[],this._plugins=[...e.plugins],this._pluginStateMap=new Map;for(let e of this._plugins)this._pluginStateMap.set(e,{});this.event.waitUntil(this._handlerDeferred.promise)}async fetch(e){let{event:n}=this,r=S(e);if(r.mode===`navigate`&&n instanceof FetchEvent&&n.preloadResponse){let e=await n.preloadResponse;if(e)return e}let i=this.hasCallback(`fetchDidFail`)?r.clone():null;try{for(let e of this.iterateCallbacks(`requestWillFetch`))r=await e({request:r.clone(),event:n})}catch(e){if(e instanceof Error)throw new t(`plugin-error-request-will-fetch`,{thrownErrorMessage:e.message})}let a=r.clone();try{let e;e=await fetch(r,r.mode===`navigate`?void 0:this._strategy.fetchOptions);for(let t of this.iterateCallbacks(`fetchDidSucceed`))e=await t({event:n,request:a,response:e});return e}catch(e){throw i&&await this.runCallbacks(`fetchDidFail`,{error:e,event:n,originalRequest:i.clone(),request:a.clone()}),e}}async fetchAndCachePut(e){let t=await this.fetch(e),n=t.clone();return this.waitUntil(this.cachePut(e,n)),t}async cacheMatch(e){let t=S(e),n,{cacheName:r,matchOptions:i}=this._strategy,a=await this.getCacheKey(t,`read`),o=Object.assign(Object.assign({},i),{cacheName:r});n=await caches.match(a,o);for(let e of this.iterateCallbacks(`cachedResponseWillBeUsed`))n=await e({cacheName:r,matchOptions:i,cachedResponse:n,request:a,event:this.event})||void 0;return n}async cachePut(e,n){let r=S(e);await x(0);let i=await this.getCacheKey(r,`write`);if(!n)throw new t(`cache-put-with-no-response`,{url:m(i.url)});let a=await this._ensureResponseSafeToCache(n);if(!a)return!1;let{cacheName:o,matchOptions:s}=this._strategy,c=await self.caches.open(o),l=this.hasCallback(`cacheDidUpdate`),u=l?await g(c,i.clone(),[`__WB_REVISION__`],s):null;try{await c.put(i,l?a.clone():a)}catch(e){if(e instanceof Error)throw e.name===`QuotaExceededError`&&await b(),e}for(let e of this.iterateCallbacks(`cacheDidUpdate`))await e({cacheName:o,oldResponse:u,newResponse:a.clone(),request:i,event:this.event});return!0}async getCacheKey(e,t){let n=`${e.url} | ${t}`;if(!this._cacheKeys[n]){let r=e;for(let e of this.iterateCallbacks(`cacheKeyWillBeUsed`))r=S(await e({mode:t,request:r,event:this.event,params:this.params}));this._cacheKeys[n]=r}return this._cacheKeys[n]}hasCallback(e){for(let t of this._strategy.plugins)if(e in t)return!0;return!1}async runCallbacks(e,t){for(let n of this.iterateCallbacks(e))await n(t)}*iterateCallbacks(e){for(let t of this._strategy.plugins)if(typeof t[e]==`function`){let n=this._pluginStateMap.get(t);yield r=>{let i=Object.assign(Object.assign({},r),{state:n});return t[e](i)}}}waitUntil(e){return this._extendLifetimePromises.push(e),e}async doneWaiting(){for(;this._extendLifetimePromises.length;){let e=this._extendLifetimePromises.splice(0),t=(await Promise.allSettled(e)).find(e=>e.status===`rejected`);if(t)throw t.reason}}destroy(){this._handlerDeferred.resolve(null)}async _ensureResponseSafeToCache(e){let t=e,n=!1;for(let e of this.iterateCallbacks(`cacheWillUpdate`))if(t=await e({request:this.request,response:t,event:this.event})||void 0,n=!0,!t)break;return n||t&&t.status!==200&&(t=void 0),t}},w=class{constructor(e={}){this.cacheName=a.getRuntimeName(e.cacheName),this.plugins=e.plugins||[],this.fetchOptions=e.fetchOptions,this.matchOptions=e.matchOptions}handle(e){let[t]=this.handleAll(e);return t}handleAll(e){e instanceof FetchEvent&&(e={event:e,request:e.request});let t=e.event,n=typeof e.request==`string`?new Request(e.request):e.request,r=`params`in e?e.params:void 0,i=new C(this,{event:t,request:n,params:r}),a=this._getResponse(i,n,t);return[a,this._awaitComplete(a,i,n,t)]}async _getResponse(e,n,r){await e.runCallbacks(`handlerWillStart`,{event:r,request:n});let i;try{if(i=await this._handle(n,e),!i||i.type===`error`)throw new t(`no-response`,{url:n.url})}catch(t){if(t instanceof Error){for(let a of e.iterateCallbacks(`handlerDidError`))if(i=await a({error:t,event:r,request:n}),i)break}if(!i)throw t}for(let t of e.iterateCallbacks(`handlerWillRespond`))i=await t({event:r,request:n,response:i});return i}async _awaitComplete(e,t,n,r){let i,a;try{i=await e}catch{}try{await t.runCallbacks(`handlerDidRespond`,{event:r,request:n,response:i}),await t.doneWaiting()}catch(e){e instanceof Error&&(a=e)}if(await t.runCallbacks(`handlerDidComplete`,{event:r,request:n,response:i,error:a}),t.destroy(),a)throw a}},T=class e extends w{constructor(t={}){t.cacheName=a.getPrecacheName(t.cacheName),super(t),this._fallbackToNetwork=t.fallbackToNetwork!==!1,this.plugins.push(e.copyRedirectedCacheableResponsesPlugin)}async _handle(e,t){return await t.cacheMatch(e)||(t.event&&t.event.type===`install`?await this._handleInstall(e,t):await this._handleFetch(e,t))}async _handleFetch(e,n){let r,i=n.params||{};if(this._fallbackToNetwork){let t=i.integrity,a=e.integrity,o=!a||a===t;r=await n.fetch(new Request(e,{integrity:e.mode===`no-cors`?void 0:a||t})),t&&o&&e.mode!==`no-cors`&&(this._useDefaultCacheabilityPluginIfNeeded(),await n.cachePut(e,r.clone()))}else throw new t(`missing-precache-entry`,{cacheName:this.cacheName,url:e.url});return r}async _handleInstall(e,n){this._useDefaultCacheabilityPluginIfNeeded();let r=await n.fetch(e);if(!await n.cachePut(e,r.clone()))throw new t(`bad-precaching-response`,{url:e.url,status:r.status});return r}_useDefaultCacheabilityPluginIfNeeded(){let t=null,n=0;for(let[r,i]of this.plugins.entries())i!==e.copyRedirectedCacheableResponsesPlugin&&(i===e.defaultPrecacheCacheabilityPlugin&&(t=r),i.cacheWillUpdate&&n++);n===0?this.plugins.push(e.defaultPrecacheCacheabilityPlugin):n>1&&t!==null&&this.plugins.splice(t,1)}};T.defaultPrecacheCacheabilityPlugin={async cacheWillUpdate({response:e}){return!e||e.status>=400?null:e}},T.copyRedirectedCacheableResponsesPlugin={async cacheWillUpdate({response:e}){return e.redirected?await p(e):e}};var E=class{constructor({cacheName:e,plugins:t=[],fallbackToNetwork:n=!0}={}){this._urlsToCacheKeys=new Map,this._urlsToCacheModes=new Map,this._cacheKeysToIntegrities=new Map,this._strategy=new T({cacheName:a.getPrecacheName(e),plugins:[...t,new u({precacheController:this})],fallbackToNetwork:n}),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),!0)}addToCacheList(e){let n=[];for(let r of e){typeof r==`string`?n.push(r):r&&r.revision===void 0&&n.push(r.url);let{cacheKey:e,url:i}=c(r),a=typeof r!=`string`&&r.revision?`reload`:`default`;if(this._urlsToCacheKeys.has(i)&&this._urlsToCacheKeys.get(i)!==e)throw new t(`add-to-cache-list-conflicting-entries`,{firstEntry:this._urlsToCacheKeys.get(i),secondEntry:e});if(typeof r!=`string`&&r.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==r.integrity)throw new t(`add-to-cache-list-conflicting-integrities`,{url:i});this._cacheKeysToIntegrities.set(e,r.integrity)}if(this._urlsToCacheKeys.set(i,e),this._urlsToCacheModes.set(i,a),n.length>0){let e=`Workbox is precaching URLs without revision info: ${n.join(`, `)}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;console.warn(e)}}}install(e){return o(e,async()=>{let t=new l;this.strategy.plugins.push(t);for(let[t,n]of this._urlsToCacheKeys){let r=this._cacheKeysToIntegrities.get(n),i=this._urlsToCacheModes.get(t),a=new Request(t,{integrity:r,cache:i,credentials:`same-origin`});await Promise.all(this.strategy.handleAll({params:{cacheKey:n},request:a,event:e}))}let{updatedURLs:n,notUpdatedURLs:r}=t;return{updatedURLs:n,notUpdatedURLs:r}})}activate(e){return o(e,async()=>{let e=await self.caches.open(this.strategy.cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),r=[];for(let i of t)n.has(i.url)||(await e.delete(i),r.push(i.url));return{deletedURLs:r}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){let t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}getIntegrityForCacheKey(e){return this._cacheKeysToIntegrities.get(e)}async matchPrecache(e){let t=e instanceof Request?e.url:e,n=this.getCacheKeyForURL(t);if(n)return(await self.caches.open(this.strategy.cacheName)).match(n)}createHandlerBoundToURL(e){let n=this.getCacheKeyForURL(e);if(!n)throw new t(`non-precached-url`,{url:e});return t=>(t.request=new Request(e),t.params=Object.assign({cacheKey:n},t.params),this.strategy.handle(t))}},D,O=()=>(D||=new E,D);try{self[`workbox:routing:7.3.0`]&&_()}catch{}var k=e=>e&&typeof e==`object`?e:{handle:e},A=class{constructor(e,t,n=`GET`){this.handler=k(t),this.match=e,this.method=n}setCatchHandler(e){this.catchHandler=k(e)}},j=class extends A{constructor(e,t,n){super(({url:t})=>{let n=e.exec(t.href);if(n&&!(t.origin!==location.origin&&n.index!==0))return n.slice(1)},t,n)}},M=class{constructor(){this._routes=new Map,this._defaultHandlerMap=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(`fetch`,(e=>{let{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)}))}addCacheListener(){self.addEventListener(`message`,(e=>{if(e.data&&e.data.type===`CACHE_URLS`){let{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(t=>{typeof t==`string`&&(t=[t]);let n=new Request(...t);return this.handleRequest({request:n,event:e})}));e.waitUntil(n),e.ports&&e.ports[0]&&n.then(()=>e.ports[0].postMessage(!0))}}))}handleRequest({request:e,event:t}){let n=new URL(e.url,location.href);if(!n.protocol.startsWith(`http`))return;let r=n.origin===location.origin,{params:i,route:a}=this.findMatchingRoute({event:t,request:e,sameOrigin:r,url:n}),o=a&&a.handler,s=e.method;if(!o&&this._defaultHandlerMap.has(s)&&(o=this._defaultHandlerMap.get(s)),!o)return;let c;try{c=o.handle({url:n,request:e,event:t,params:i})}catch(e){c=Promise.reject(e)}let l=a&&a.catchHandler;return c instanceof Promise&&(this._catchHandler||l)&&(c=c.catch(async r=>{if(l)try{return await l.handle({url:n,request:e,event:t,params:i})}catch(e){e instanceof Error&&(r=e)}if(this._catchHandler)return this._catchHandler.handle({url:n,request:e,event:t});throw r})),c}findMatchingRoute({url:e,sameOrigin:t,request:n,event:r}){let i=this._routes.get(n.method)||[];for(let a of i){let i,o=a.match({url:e,sameOrigin:t,request:n,event:r});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:a,params:i}}return{}}setDefaultHandler(e,t=`GET`){this._defaultHandlerMap.set(t,k(e))}setCatchHandler(e){this._catchHandler=k(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 t(`unregister-route-but-not-found-with-method`,{method:e.method});let n=this._routes.get(e.method).indexOf(e);if(n>-1)this._routes.get(e.method).splice(n,1);else throw new t(`unregister-route-route-not-registered`)}},N,P=()=>(N||(N=new M,N.addFetchListener(),N.addCacheListener()),N);function F(e,n,r){let i;if(typeof e==`string`){let t=new URL(e,location.href);i=new A(({url:e})=>e.href===t.href,n,r)}else if(e instanceof RegExp)i=new j(e,n,r);else if(typeof e==`function`)i=new A(e,n,r);else if(e instanceof A)i=e;else throw new t(`unsupported-route-type`,{moduleName:`workbox-routing`,funcName:`registerRoute`,paramName:`capture`});return P().registerRoute(i),i}function I(e,t=[]){for(let n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}function*L(e,{ignoreURLParametersMatching:t=[/^utm_/,/^fbclid$/],directoryIndex:n=`index.html`,cleanURLs:r=!0,urlManipulation:i}={}){let a=new URL(e,location.href);a.hash=``,yield a.href;let o=I(a,t);if(yield o.href,n&&o.pathname.endsWith(`/`)){let e=new URL(o.href);e.pathname+=n,yield e.href}if(r){let e=new URL(o.href);e.pathname+=`.html`,yield e.href}if(i){let e=i({url:a});for(let t of e)yield t.href}}var R=class extends A{constructor(e,t){super(({request:n})=>{let r=e.getURLsToCacheKeys();for(let i of L(n.url,t)){let t=r.get(i);if(t)return{cacheKey:t,integrity:e.getIntegrityForCacheKey(t)}}},e.strategy)}};function z(e){F(new R(O(),e))}function B(e){O().precache(e)}function V(e,t){B(e),z(t)}V([{"revision":"1872c500de691dce40960bb85481de07","url":"registerSW.js"},{"revision":"aca130ca572c3647be08097ed9115d15","url":"index.html"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-512.svg"},{"revision":"a0fb34fc84eb148d51812cd62669f20d","url":"icon-192.svg"},{"revision":"eb9818b9094675c0c5d303168f273345","url":"monacoeditorwork/ts.worker.bundle.js"},{"revision":"9af0be92dcefdc1f1290441cb5ff5d9b","url":"monacoeditorwork/json.worker.bundle.js"},{"revision":"a261b429c39dbb75ae97972d7d005e6d","url":"monacoeditorwork/html.worker.bundle.js"},{"revision":"79953d804e1bbacecfd79b85fd679016","url":"monacoeditorwork/editor.worker.bundle.js"},{"revision":"fdcba0d09aac31df7a0bc652f6e739bd","url":"monacoeditorwork/css.worker.bundle.js"},{"revision":null,"url":"assets/utils-DC-bdPS3.js"},{"revision":null,"url":"assets/use-monaco-theme-Bt1Lr3jH.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/terminal-tab-BDqc6Dl5.js"},{"revision":null,"url":"assets/table-C0oSLUYn.js"},{"revision":null,"url":"assets/tab-store-0CKk8cSr.js"},{"revision":null,"url":"assets/switch-PAf5UhcN.js"},{"revision":null,"url":"assets/sqlite-viewer-C2744fw1.js"},{"revision":null,"url":"assets/settings-tab-jhRBFgf_.js"},{"revision":null,"url":"assets/settings-store-2NQzaOVJ.js"},{"revision":null,"url":"assets/react-rgzL83kk.js"},{"revision":null,"url":"assets/react-CYzKIDNi.js"},{"revision":null,"url":"assets/postgres-viewer-kqZBNVYW.js"},{"revision":null,"url":"assets/markdown-renderer-dqkYhU3y.js"},{"revision":null,"url":"assets/keybindings-store-DrjDQzVs.js"},{"revision":null,"url":"assets/jsx-runtime-wQxeESYQ.js"},{"revision":null,"url":"assets/input-CVIzrYsH.js"},{"revision":null,"url":"assets/index-D6GLlwUx.css"},{"revision":null,"url":"assets/index-CTOMzCnZ.js"},{"revision":null,"url":"assets/git-graph-HfH98qwn.js"},{"revision":null,"url":"assets/dist-D9RHR8A4.js"},{"revision":null,"url":"assets/diff-viewer-2zSPeCzX.js"},{"revision":null,"url":"assets/database-viewer-CDto4TrW.js"},{"revision":null,"url":"assets/columns-2-fz8yNaAo.js"},{"revision":null,"url":"assets/code-editor-CYt4hqge.js"},{"revision":null,"url":"assets/chat-tab-C0AcTU9S.js"},{"revision":null,"url":"assets/api-client-TUmacMRS.js"},{"revision":"79c8870653c8f419f2e3323085e1f4be","url":"manifest.webmanifest"}]),self.addEventListener(`push`,e=>{e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{if(t.some(e=>e.visibilityState===`visible`))return;let n=e.data?.json()??{title:`PPM`,body:`Chat completed`};return self.registration.showNotification(n.title,{body:n.body,icon:`/icon-192.png`,badge:`/icon-192.png`,tag:`ppm-chat-done`,silent:!1,data:{url:self.location.origin}})}))}),self.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(self.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(t=>{for(let e of t)if(e.url.includes(self.location.origin)&&`focus`in e)return e.focus();return self.clients.openWindow(e.notification.data?.url||`/`)}))});
|
package/package.json
CHANGED
|
@@ -483,6 +483,7 @@ export class ClaudeAgentSdkProvider implements AIProvider {
|
|
|
483
483
|
const account = accountSelector.isEnabled() ? accountSelector.next() : null;
|
|
484
484
|
if (account) {
|
|
485
485
|
console.log(`[sdk] Using account ${account.id} (${account.email ?? "no-email"})`);
|
|
486
|
+
yield { type: "account_info" as const, accountId: account.id, accountLabel: account.label ?? account.email ?? "Unknown" };
|
|
486
487
|
}
|
|
487
488
|
const queryEnv = this.buildQueryEnv(meta.projectPath, account);
|
|
488
489
|
console.log(`[sdk] query: session=${sessionId} sdkId=${sdkId} isFirst=${isFirstMessage} fork=${shouldFork} cwd=${effectiveCwd} platform=${process.platform} accountMode=${!!account}`);
|
|
@@ -2,19 +2,29 @@ import { Hono } from "hono";
|
|
|
2
2
|
import type { Context } from "hono";
|
|
3
3
|
import { accountService } from "../../services/account.service.ts";
|
|
4
4
|
import { accountSelector } from "../../services/account-selector.service.ts";
|
|
5
|
+
import { updateAccount } from "../../services/db.service.ts";
|
|
5
6
|
import { getAllAccountUsages, getUsageForAccount } from "../../services/claude-usage.service.ts";
|
|
6
7
|
import { ok, err } from "../../types/api.ts";
|
|
7
8
|
|
|
8
9
|
export const accountsRoutes = new Hono();
|
|
9
10
|
|
|
10
|
-
function
|
|
11
|
+
function getBaseUrl(c: Context): string {
|
|
12
|
+
// Respect X-Forwarded-Host/Origin for dev proxy (Vite → backend)
|
|
13
|
+
const fwdHost = c.req.header("x-forwarded-host");
|
|
14
|
+
const fwdProto = c.req.header("x-forwarded-proto") ?? "http";
|
|
15
|
+
if (fwdHost) return `${fwdProto}://${fwdHost}`;
|
|
16
|
+
const origin = c.req.header("origin");
|
|
17
|
+
if (origin) return origin;
|
|
11
18
|
const url = new URL(c.req.url);
|
|
12
|
-
return `${url.protocol}//${url.host}
|
|
19
|
+
return `${url.protocol}//${url.host}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getCallbackUrl(c: Context): string {
|
|
23
|
+
return `${getBaseUrl(c)}/api/accounts/oauth/callback`;
|
|
13
24
|
}
|
|
14
25
|
|
|
15
26
|
function getUiBase(c: Context): string {
|
|
16
|
-
|
|
17
|
-
return `${url.protocol}//${url.host}`;
|
|
27
|
+
return getBaseUrl(c);
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
/** GET /api/accounts */
|
|
@@ -83,12 +93,39 @@ accountsRoutes.post("/", async (c) => {
|
|
|
83
93
|
}
|
|
84
94
|
});
|
|
85
95
|
|
|
86
|
-
/** GET /api/accounts/oauth/start → redirect to Claude OAuth */
|
|
96
|
+
/** GET /api/accounts/oauth/start → redirect to Claude OAuth (legacy, localhost callback) */
|
|
87
97
|
accountsRoutes.get("/oauth/start", (c) => {
|
|
88
|
-
const
|
|
98
|
+
const referer = c.req.header("referer");
|
|
99
|
+
let callbackBase: string;
|
|
100
|
+
if (referer) {
|
|
101
|
+
const refUrl = new URL(referer);
|
|
102
|
+
callbackBase = `${refUrl.protocol}//${refUrl.host}`;
|
|
103
|
+
} else {
|
|
104
|
+
callbackBase = getBaseUrl(c);
|
|
105
|
+
}
|
|
106
|
+
const callbackUrl = `${callbackBase}/api/accounts/oauth/callback`;
|
|
107
|
+
const url = accountService.startOAuthFlow(callbackUrl);
|
|
89
108
|
return c.redirect(url);
|
|
90
109
|
});
|
|
91
110
|
|
|
111
|
+
/** GET /api/accounts/oauth/url → return OAuth URL for manual code flow */
|
|
112
|
+
accountsRoutes.get("/oauth/url", (c) => {
|
|
113
|
+
const { url, state } = accountService.startOAuthCodeFlow();
|
|
114
|
+
return c.json(ok({ url, state }));
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/** POST /api/accounts/oauth/exchange → exchange code from platform callback */
|
|
118
|
+
accountsRoutes.post("/oauth/exchange", async (c) => {
|
|
119
|
+
const body = await c.req.json<{ code: string; state: string }>();
|
|
120
|
+
if (!body.code || !body.state) return c.json(err("code and state are required"), 400);
|
|
121
|
+
try {
|
|
122
|
+
const account = await accountService.completeOAuthCodeFlow(body.code.trim(), body.state);
|
|
123
|
+
return c.json(ok(account));
|
|
124
|
+
} catch (e) {
|
|
125
|
+
return c.json(err((e as Error).message), 400);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
92
129
|
/** GET /api/accounts/oauth/callback — exchange code for tokens */
|
|
93
130
|
accountsRoutes.get("/oauth/callback", async (c) => {
|
|
94
131
|
const { code, state, error } = c.req.query();
|
|
@@ -146,6 +183,23 @@ accountsRoutes.get("/:id/usage", (c) => {
|
|
|
146
183
|
return c.json(ok(getUsageForAccount(id)));
|
|
147
184
|
});
|
|
148
185
|
|
|
186
|
+
/** POST /api/accounts/:id/verify — re-verify token & refresh profile */
|
|
187
|
+
accountsRoutes.post("/:id/verify", async (c) => {
|
|
188
|
+
const { id } = c.req.param();
|
|
189
|
+
const account = accountService.getWithTokens(id);
|
|
190
|
+
if (!account) return c.json(err("Account not found"), 404);
|
|
191
|
+
try {
|
|
192
|
+
const result = await accountService.verifyToken(account.accessToken);
|
|
193
|
+
if (result.valid && result.profileData) {
|
|
194
|
+
updateAccount(id, { profile_json: JSON.stringify(result.profileData) });
|
|
195
|
+
if (result.email) updateAccount(id, { email: result.email });
|
|
196
|
+
}
|
|
197
|
+
return c.json(ok(result));
|
|
198
|
+
} catch (e) {
|
|
199
|
+
return c.json(err((e as Error).message), 500);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
149
203
|
/** DELETE /api/accounts/:id */
|
|
150
204
|
accountsRoutes.delete("/:id", (c) => {
|
|
151
205
|
const { id } = c.req.param();
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
insertAccount,
|
|
7
7
|
updateAccount,
|
|
8
8
|
deleteAccount,
|
|
9
|
+
deleteSnapshotsForAccount,
|
|
9
10
|
incrementAccountRequests,
|
|
10
11
|
type AccountRow,
|
|
11
12
|
} from "./db.service.ts";
|
|
@@ -20,6 +21,7 @@ export interface Account {
|
|
|
20
21
|
priority: number;
|
|
21
22
|
totalRequests: number;
|
|
22
23
|
lastUsedAt: number | null;
|
|
24
|
+
profileData: OAuthProfileData | null;
|
|
23
25
|
createdAt: number;
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -28,16 +30,48 @@ export interface AccountWithTokens extends Account {
|
|
|
28
30
|
refreshToken: string;
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
export interface OAuthProfileData {
|
|
34
|
+
account?: {
|
|
35
|
+
uuid?: string;
|
|
36
|
+
full_name?: string;
|
|
37
|
+
display_name?: string;
|
|
38
|
+
email?: string;
|
|
39
|
+
has_claude_max?: boolean;
|
|
40
|
+
has_claude_pro?: boolean;
|
|
41
|
+
created_at?: string;
|
|
42
|
+
};
|
|
43
|
+
organization?: {
|
|
44
|
+
uuid?: string;
|
|
45
|
+
name?: string;
|
|
46
|
+
organization_type?: string;
|
|
47
|
+
billing_type?: string;
|
|
48
|
+
rate_limit_tier?: string;
|
|
49
|
+
has_extra_usage_enabled?: boolean;
|
|
50
|
+
subscription_status?: string;
|
|
51
|
+
subscription_created_at?: string;
|
|
52
|
+
};
|
|
53
|
+
application?: {
|
|
54
|
+
uuid?: string;
|
|
55
|
+
name?: string;
|
|
56
|
+
slug?: string;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
31
60
|
const OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
32
61
|
const OAUTH_AUTH_URL = "https://claude.ai/oauth/authorize";
|
|
33
62
|
const OAUTH_TOKEN_URL = "https://api.anthropic.com/v1/oauth/token";
|
|
34
63
|
const OAUTH_SCOPE = "org:create_api_key user:profile user:inference";
|
|
64
|
+
const OAUTH_PLATFORM_REDIRECT = "https://platform.claude.com/oauth/code/callback";
|
|
35
65
|
|
|
36
66
|
class AccountService {
|
|
37
67
|
private pendingStates = new Map<string, { verifier: string; createdAt: number }>();
|
|
38
68
|
private refreshTimer: ReturnType<typeof setInterval> | null = null;
|
|
39
69
|
|
|
40
70
|
private toAccount(row: AccountRow): Account {
|
|
71
|
+
let profileData: OAuthProfileData | null = null;
|
|
72
|
+
if (row.profile_json) {
|
|
73
|
+
try { profileData = JSON.parse(row.profile_json); } catch { /* ignore */ }
|
|
74
|
+
}
|
|
41
75
|
return {
|
|
42
76
|
id: row.id,
|
|
43
77
|
label: row.label,
|
|
@@ -48,6 +82,7 @@ class AccountService {
|
|
|
48
82
|
priority: row.priority,
|
|
49
83
|
totalRequests: row.total_requests,
|
|
50
84
|
lastUsedAt: row.last_used_at,
|
|
85
|
+
profileData,
|
|
51
86
|
createdAt: row.created_at,
|
|
52
87
|
};
|
|
53
88
|
}
|
|
@@ -75,13 +110,43 @@ class AccountService {
|
|
|
75
110
|
}
|
|
76
111
|
}
|
|
77
112
|
|
|
113
|
+
/** Find existing account by email or profile UUID */
|
|
114
|
+
private findDuplicate(email?: string | null, profileData?: OAuthProfileData | null): Account | null {
|
|
115
|
+
if (!email && !profileData?.account?.uuid) return null;
|
|
116
|
+
const existing = this.list();
|
|
117
|
+
for (const acc of existing) {
|
|
118
|
+
// Match by account UUID (most reliable)
|
|
119
|
+
if (profileData?.account?.uuid && acc.profileData?.account?.uuid === profileData.account.uuid) {
|
|
120
|
+
return acc;
|
|
121
|
+
}
|
|
122
|
+
// Match by email
|
|
123
|
+
if (email && acc.email && acc.email === email) {
|
|
124
|
+
return acc;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
78
130
|
add(params: {
|
|
79
131
|
email: string;
|
|
80
132
|
accessToken: string;
|
|
81
133
|
refreshToken: string;
|
|
82
134
|
expiresAt: number;
|
|
83
135
|
label?: string;
|
|
136
|
+
profileData?: OAuthProfileData;
|
|
84
137
|
}): Account {
|
|
138
|
+
// Check for duplicate — update existing account tokens instead of creating new
|
|
139
|
+
const dup = this.findDuplicate(params.email, params.profileData);
|
|
140
|
+
if (dup) {
|
|
141
|
+
this.updateTokens(dup.id, params.accessToken, params.refreshToken, params.expiresAt);
|
|
142
|
+
if (params.profileData) {
|
|
143
|
+
updateAccount(dup.id, { profile_json: JSON.stringify(params.profileData) });
|
|
144
|
+
}
|
|
145
|
+
if (params.label) updateAccount(dup.id, { label: params.label });
|
|
146
|
+
if (params.email) updateAccount(dup.id, { email: params.email });
|
|
147
|
+
return this.toAccount(getAccountById(dup.id)!);
|
|
148
|
+
}
|
|
149
|
+
|
|
85
150
|
const id = randomUUID();
|
|
86
151
|
insertAccount({
|
|
87
152
|
id,
|
|
@@ -95,6 +160,7 @@ class AccountService {
|
|
|
95
160
|
priority: 0,
|
|
96
161
|
total_requests: 0,
|
|
97
162
|
last_used_at: null,
|
|
163
|
+
profile_json: params.profileData ? JSON.stringify(params.profileData) : null,
|
|
98
164
|
});
|
|
99
165
|
return this.toAccount(getAccountById(id)!);
|
|
100
166
|
}
|
|
@@ -105,13 +171,14 @@ class AccountService {
|
|
|
105
171
|
orgName?: string;
|
|
106
172
|
subscriptionType?: string;
|
|
107
173
|
authMethod?: string;
|
|
174
|
+
profileData?: OAuthProfileData;
|
|
108
175
|
}> {
|
|
109
176
|
const isOAuth = token.startsWith("sk-ant-oat");
|
|
110
177
|
|
|
111
178
|
if (isOAuth) {
|
|
112
|
-
// Verify via
|
|
179
|
+
// Verify via profile API — returns email, org, subscription info
|
|
113
180
|
try {
|
|
114
|
-
const res = await fetch("https://api.anthropic.com/api/oauth/
|
|
181
|
+
const res = await fetch("https://api.anthropic.com/api/oauth/profile", {
|
|
115
182
|
headers: {
|
|
116
183
|
Accept: "application/json",
|
|
117
184
|
Authorization: `Bearer ${token}`,
|
|
@@ -120,8 +187,19 @@ class AccountService {
|
|
|
120
187
|
},
|
|
121
188
|
signal: AbortSignal.timeout(10_000),
|
|
122
189
|
});
|
|
123
|
-
|
|
124
|
-
|
|
190
|
+
if (res.status === 200) {
|
|
191
|
+
const data = await res.json() as OAuthProfileData;
|
|
192
|
+
return {
|
|
193
|
+
valid: true,
|
|
194
|
+
authMethod: "oauth_token",
|
|
195
|
+
email: data.account?.email,
|
|
196
|
+
orgName: data.organization?.name,
|
|
197
|
+
subscriptionType: data.organization?.organization_type,
|
|
198
|
+
profileData: data,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
// 429 = rate limited but valid token (no profile data available)
|
|
202
|
+
if (res.status === 429) {
|
|
125
203
|
return { valid: true, authMethod: "oauth_token" };
|
|
126
204
|
}
|
|
127
205
|
return { valid: false };
|
|
@@ -162,12 +240,26 @@ class AccountService {
|
|
|
162
240
|
async addManual(params: { apiKey: string; label: string | null }): Promise<Account> {
|
|
163
241
|
const info = await this.verifyToken(params.apiKey);
|
|
164
242
|
if (!info.valid) throw new Error("Invalid token — could not authenticate");
|
|
165
|
-
|
|
243
|
+
|
|
166
244
|
const email = info.email ?? null;
|
|
167
|
-
//
|
|
245
|
+
// Check for duplicate — update tokens on existing account
|
|
246
|
+
const dup = this.findDuplicate(email, info.profileData);
|
|
247
|
+
if (dup) {
|
|
248
|
+
updateAccount(dup.id, { access_token: encrypt(params.apiKey), status: "active", cooldown_until: null });
|
|
249
|
+
if (info.profileData) updateAccount(dup.id, { profile_json: JSON.stringify(info.profileData) });
|
|
250
|
+
if (email) updateAccount(dup.id, { email });
|
|
251
|
+
return this.toAccount(getAccountById(dup.id)!);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const id = randomUUID();
|
|
255
|
+
// Auto-generate label: display_name > orgName (subscription) > authMethod-based > user-provided > fallback
|
|
168
256
|
let label = params.label;
|
|
169
257
|
if (!label) {
|
|
170
|
-
|
|
258
|
+
const displayName = info.profileData?.account?.display_name || info.profileData?.account?.full_name;
|
|
259
|
+
if (displayName) {
|
|
260
|
+
const orgName = info.profileData?.organization?.name;
|
|
261
|
+
label = orgName ? `${displayName} (${orgName})` : displayName;
|
|
262
|
+
} else if (info.orgName) {
|
|
171
263
|
label = `${info.orgName}${info.subscriptionType ? ` (${info.subscriptionType})` : ""}`;
|
|
172
264
|
} else if (info.authMethod === "oauth_token") {
|
|
173
265
|
label = `Claude Pro/Max`;
|
|
@@ -189,6 +281,7 @@ class AccountService {
|
|
|
189
281
|
priority: 0,
|
|
190
282
|
total_requests: 0,
|
|
191
283
|
last_used_at: null,
|
|
284
|
+
profile_json: info.profileData ? JSON.stringify(info.profileData) : null,
|
|
192
285
|
});
|
|
193
286
|
return this.toAccount(getAccountById(id)!);
|
|
194
287
|
}
|
|
@@ -219,6 +312,7 @@ class AccountService {
|
|
|
219
312
|
}
|
|
220
313
|
|
|
221
314
|
remove(id: string): void {
|
|
315
|
+
deleteSnapshotsForAccount(id);
|
|
222
316
|
deleteAccount(id);
|
|
223
317
|
}
|
|
224
318
|
|
|
@@ -227,12 +321,34 @@ class AccountService {
|
|
|
227
321
|
updateAccount(id, { last_used_at: Math.floor(Date.now() / 1000) });
|
|
228
322
|
}
|
|
229
323
|
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
// OAuth profile
|
|
326
|
+
// ---------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
async fetchOAuthProfile(token: string): Promise<OAuthProfileData | undefined> {
|
|
329
|
+
try {
|
|
330
|
+
const res = await fetch("https://api.anthropic.com/api/oauth/profile", {
|
|
331
|
+
headers: {
|
|
332
|
+
Accept: "application/json",
|
|
333
|
+
Authorization: `Bearer ${token}`,
|
|
334
|
+
"anthropic-beta": "oauth-2025-04-20",
|
|
335
|
+
"User-Agent": "ppm/1.0",
|
|
336
|
+
},
|
|
337
|
+
signal: AbortSignal.timeout(10_000),
|
|
338
|
+
});
|
|
339
|
+
if (res.status === 200) return await res.json() as OAuthProfileData;
|
|
340
|
+
} catch {
|
|
341
|
+
// Profile fetch is best-effort
|
|
342
|
+
}
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
|
|
230
346
|
// ---------------------------------------------------------------------------
|
|
231
347
|
// OAuth PKCE helpers
|
|
232
348
|
// ---------------------------------------------------------------------------
|
|
233
349
|
|
|
234
350
|
private generatePkce(): { verifier: string; challenge: string } {
|
|
235
|
-
const verifier = randomBytes(
|
|
351
|
+
const verifier = randomBytes(96).toString("base64url");
|
|
236
352
|
const challenge = createHash("sha256").update(verifier).digest("base64url");
|
|
237
353
|
return { verifier, challenge };
|
|
238
354
|
}
|
|
@@ -262,36 +378,82 @@ class AccountService {
|
|
|
262
378
|
return `${OAUTH_AUTH_URL}?${params}`;
|
|
263
379
|
}
|
|
264
380
|
|
|
381
|
+
/** Generate OAuth URL using platform.claude.com callback (user copies code manually) */
|
|
382
|
+
startOAuthCodeFlow(): { url: string; state: string } {
|
|
383
|
+
this.cleanExpiredStates();
|
|
384
|
+
const { verifier, challenge } = this.generatePkce();
|
|
385
|
+
const state = randomBytes(16).toString("hex");
|
|
386
|
+
this.pendingStates.set(state, { verifier, createdAt: Date.now() });
|
|
387
|
+
|
|
388
|
+
const params = new URLSearchParams({
|
|
389
|
+
response_type: "code",
|
|
390
|
+
client_id: OAUTH_CLIENT_ID,
|
|
391
|
+
redirect_uri: OAUTH_PLATFORM_REDIRECT,
|
|
392
|
+
scope: OAUTH_SCOPE,
|
|
393
|
+
state,
|
|
394
|
+
code_challenge: challenge,
|
|
395
|
+
code_challenge_method: "S256",
|
|
396
|
+
code: "true",
|
|
397
|
+
});
|
|
398
|
+
return { url: `${OAUTH_AUTH_URL}?${params}`, state };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Exchange code from platform.claude.com callback */
|
|
402
|
+
async completeOAuthCodeFlow(code: string, state: string): Promise<Account> {
|
|
403
|
+
const pending = this.pendingStates.get(state);
|
|
404
|
+
if (!pending) throw new Error("Invalid or expired OAuth state");
|
|
405
|
+
this.pendingStates.delete(state);
|
|
406
|
+
|
|
407
|
+
const tokens = await this.exchangeCode(code, pending.verifier, OAUTH_PLATFORM_REDIRECT, state);
|
|
408
|
+
const profileData = await this.fetchOAuthProfile(tokens.accessToken);
|
|
409
|
+
const displayName = profileData?.account?.display_name || profileData?.account?.full_name;
|
|
410
|
+
const orgName = profileData?.organization?.name;
|
|
411
|
+
const label = displayName ? (orgName ? `${displayName} (${orgName})` : displayName) : undefined;
|
|
412
|
+
return this.add({
|
|
413
|
+
email: profileData?.account?.email ?? tokens.email,
|
|
414
|
+
accessToken: tokens.accessToken,
|
|
415
|
+
refreshToken: tokens.refreshToken,
|
|
416
|
+
expiresAt: tokens.expiresAt,
|
|
417
|
+
label,
|
|
418
|
+
profileData,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
265
422
|
async completeOAuthFlow(code: string, state: string, redirectUri: string): Promise<Account> {
|
|
266
423
|
const pending = this.pendingStates.get(state);
|
|
267
424
|
if (!pending) throw new Error("Invalid or expired OAuth state");
|
|
268
425
|
this.pendingStates.delete(state);
|
|
269
426
|
|
|
270
427
|
const tokens = await this.exchangeCode(code, pending.verifier, redirectUri);
|
|
428
|
+
// Fetch profile data with the new token
|
|
429
|
+
const profileData = await this.fetchOAuthProfile(tokens.accessToken);
|
|
271
430
|
return this.add({
|
|
272
|
-
email: tokens.email,
|
|
431
|
+
email: profileData?.account?.email ?? tokens.email,
|
|
273
432
|
accessToken: tokens.accessToken,
|
|
274
433
|
refreshToken: tokens.refreshToken,
|
|
275
434
|
expiresAt: tokens.expiresAt,
|
|
435
|
+
profileData,
|
|
276
436
|
});
|
|
277
437
|
}
|
|
278
438
|
|
|
279
|
-
async exchangeCode(code: string, verifier: string, redirectUri: string): Promise<{
|
|
439
|
+
async exchangeCode(code: string, verifier: string, redirectUri: string, state?: string): Promise<{
|
|
280
440
|
accessToken: string;
|
|
281
441
|
refreshToken: string;
|
|
282
442
|
expiresAt: number;
|
|
283
443
|
email: string;
|
|
284
444
|
}> {
|
|
445
|
+
const body: Record<string, string> = {
|
|
446
|
+
grant_type: "authorization_code",
|
|
447
|
+
client_id: OAUTH_CLIENT_ID,
|
|
448
|
+
code,
|
|
449
|
+
redirect_uri: redirectUri,
|
|
450
|
+
code_verifier: verifier,
|
|
451
|
+
};
|
|
452
|
+
if (state) body.state = state;
|
|
285
453
|
const res = await fetch(OAUTH_TOKEN_URL, {
|
|
286
454
|
method: "POST",
|
|
287
455
|
headers: { "Content-Type": "application/json" },
|
|
288
|
-
body: JSON.stringify(
|
|
289
|
-
grant_type: "authorization_code",
|
|
290
|
-
client_id: OAUTH_CLIENT_ID,
|
|
291
|
-
code,
|
|
292
|
-
redirect_uri: redirectUri,
|
|
293
|
-
code_verifier: verifier,
|
|
294
|
-
}),
|
|
456
|
+
body: JSON.stringify(body),
|
|
295
457
|
});
|
|
296
458
|
if (!res.ok) {
|
|
297
459
|
const text = await res.text();
|
|
@@ -370,6 +532,7 @@ class AccountService {
|
|
|
370
532
|
priority: row.priority ?? 0,
|
|
371
533
|
total_requests: row.total_requests ?? 0,
|
|
372
534
|
last_used_at: row.last_used_at,
|
|
535
|
+
profile_json: row.profile_json ?? null,
|
|
373
536
|
});
|
|
374
537
|
count++;
|
|
375
538
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getLatestSnapshotForAccount,
|
|
8
8
|
getAllLatestSnapshots,
|
|
9
9
|
cleanupOldLimitSnapshots,
|
|
10
|
+
touchSnapshotTimestamp,
|
|
10
11
|
type LimitSnapshotRow,
|
|
11
12
|
} from "./db.service.ts";
|
|
12
13
|
import { accountService } from "./account.service.ts";
|
|
@@ -86,7 +87,9 @@ function dbBucketToLimitBucket(util: number, resetsAt: string, windowHours: numb
|
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
function snapshotToUsage(row: LimitSnapshotRow): ClaudeUsage {
|
|
89
|
-
|
|
90
|
+
// SQLite datetime('now') returns UTC without Z suffix — JS would parse as local time
|
|
91
|
+
const utcTimestamp = row.recorded_at.endsWith("Z") ? row.recorded_at : row.recorded_at.replace(" ", "T") + "Z";
|
|
92
|
+
const result: ClaudeUsage = { lastFetchedAt: utcTimestamp };
|
|
90
93
|
if (row.five_hour_util != null) result.session = dbBucketToLimitBucket(row.five_hour_util, row.five_hour_resets_at ?? "", 5);
|
|
91
94
|
if (row.weekly_util != null) result.weekly = dbBucketToLimitBucket(row.weekly_util, row.weekly_resets_at ?? "", 168);
|
|
92
95
|
if (row.weekly_opus_util != null) result.weeklyOpus = dbBucketToLimitBucket(row.weekly_opus_util, row.weekly_opus_resets_at ?? "", 168);
|
|
@@ -141,7 +144,11 @@ function hasChanged(data: ClaudeUsage, last: LimitSnapshotRow | null): boolean {
|
|
|
141
144
|
|
|
142
145
|
function persistIfChanged(data: ClaudeUsage, accountId: string | null): void {
|
|
143
146
|
const last = accountId ? getLatestSnapshotForAccount(accountId) : getLatestLimitSnapshot();
|
|
144
|
-
if (!hasChanged(data, last))
|
|
147
|
+
if (!hasChanged(data, last)) {
|
|
148
|
+
// Data unchanged but still update timestamp so "last fetched" is accurate
|
|
149
|
+
if (accountId) touchSnapshotTimestamp(accountId);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
145
152
|
insertLimitSnapshot({
|
|
146
153
|
account_id: accountId,
|
|
147
154
|
five_hour_util: data.session?.utilization ?? null,
|
|
@@ -219,6 +219,15 @@ function runMigrations(database: Database): void {
|
|
|
219
219
|
PRAGMA user_version = 6;
|
|
220
220
|
`);
|
|
221
221
|
}
|
|
222
|
+
|
|
223
|
+
if (current < 7) {
|
|
224
|
+
try {
|
|
225
|
+
database.exec(`ALTER TABLE accounts ADD COLUMN profile_json TEXT`);
|
|
226
|
+
} catch {
|
|
227
|
+
// Column may already exist
|
|
228
|
+
}
|
|
229
|
+
database.exec(`PRAGMA user_version = 7`);
|
|
230
|
+
}
|
|
222
231
|
}
|
|
223
232
|
|
|
224
233
|
// ---------------------------------------------------------------------------
|
|
@@ -453,6 +462,17 @@ export function getAllLatestSnapshots(): LimitSnapshotRow[] {
|
|
|
453
462
|
).all() as LimitSnapshotRow[];
|
|
454
463
|
}
|
|
455
464
|
|
|
465
|
+
export function touchSnapshotTimestamp(accountId: string): void {
|
|
466
|
+
getDb().query(
|
|
467
|
+
`UPDATE claude_limit_snapshots SET recorded_at = datetime('now')
|
|
468
|
+
WHERE id = (SELECT id FROM claude_limit_snapshots WHERE account_id = ? ORDER BY recorded_at DESC LIMIT 1)`,
|
|
469
|
+
).run(accountId);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export function deleteSnapshotsForAccount(accountId: string): void {
|
|
473
|
+
getDb().query("DELETE FROM claude_limit_snapshots WHERE account_id = ?").run(accountId);
|
|
474
|
+
}
|
|
475
|
+
|
|
456
476
|
export function cleanupOldLimitSnapshots(): void {
|
|
457
477
|
getDb().query(
|
|
458
478
|
"DELETE FROM claude_limit_snapshots WHERE recorded_at < datetime('now', '-7 days')",
|
|
@@ -600,6 +620,7 @@ export interface AccountRow {
|
|
|
600
620
|
priority: number;
|
|
601
621
|
total_requests: number;
|
|
602
622
|
last_used_at: number | null;
|
|
623
|
+
profile_json: string | null;
|
|
603
624
|
created_at: number;
|
|
604
625
|
}
|
|
605
626
|
|
|
@@ -613,12 +634,12 @@ export function getAccountById(id: string): AccountRow | null {
|
|
|
613
634
|
|
|
614
635
|
export function insertAccount(row: Omit<AccountRow, "created_at">): void {
|
|
615
636
|
getDb().query(
|
|
616
|
-
`INSERT INTO accounts (id, label, email, access_token, refresh_token, expires_at, status, cooldown_until, priority, total_requests, last_used_at)
|
|
617
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
637
|
+
`INSERT INTO accounts (id, label, email, access_token, refresh_token, expires_at, status, cooldown_until, priority, total_requests, last_used_at, profile_json)
|
|
638
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
618
639
|
).run(
|
|
619
640
|
row.id, row.label, row.email, row.access_token, row.refresh_token,
|
|
620
641
|
row.expires_at, row.status, row.cooldown_until, row.priority,
|
|
621
|
-
row.total_requests, row.last_used_at,
|
|
642
|
+
row.total_requests, row.last_used_at, row.profile_json,
|
|
622
643
|
);
|
|
623
644
|
}
|
|
624
645
|
|
|
@@ -635,6 +656,7 @@ export function updateAccount(id: string, updates: Partial<Omit<AccountRow, "id"
|
|
|
635
656
|
if (updates.priority !== undefined) { sets.push("priority = ?"); vals.push(updates.priority); }
|
|
636
657
|
if (updates.total_requests !== undefined) { sets.push("total_requests = ?"); vals.push(updates.total_requests); }
|
|
637
658
|
if (updates.last_used_at !== undefined) { sets.push("last_used_at = ?"); vals.push(updates.last_used_at); }
|
|
659
|
+
if (updates.profile_json !== undefined) { sets.push("profile_json = ?"); vals.push(updates.profile_json); }
|
|
638
660
|
if (sets.length === 0) return;
|
|
639
661
|
vals.push(id);
|
|
640
662
|
getDb().query(`UPDATE accounts SET ${sets.join(", ")} WHERE id = ?`).run(...(vals as SQLQueryBindings[]));
|
package/src/types/api.ts
CHANGED
|
@@ -34,4 +34,5 @@ export type ChatWsServerMessage =
|
|
|
34
34
|
| { type: "tool_result"; output: string; isError?: boolean; toolUseId?: string; parentToolUseId?: string }
|
|
35
35
|
| { type: "approval_request"; requestId: string; tool: string; input: unknown }
|
|
36
36
|
| { type: "done"; sessionId: string; contextWindowPct?: number }
|
|
37
|
-
| { type: "error"; message: string }
|
|
37
|
+
| { type: "error"; message: string }
|
|
38
|
+
| { type: "account_info"; accountId: string; accountLabel: string };
|