@hienlh/ppm 0.7.16 → 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.
Files changed (39) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/bunfig.toml +2 -0
  3. package/dist/web/assets/chat-tab-C0AcTU9S.js +7 -0
  4. package/dist/web/assets/{code-editor-DXqocnye.js → code-editor-CYt4hqge.js} +1 -1
  5. package/dist/web/assets/{database-viewer-ChX5vA56.js → database-viewer-CDto4TrW.js} +1 -1
  6. package/dist/web/assets/{diff-viewer-8RNfSVOl.js → diff-viewer-2zSPeCzX.js} +1 -1
  7. package/dist/web/assets/git-graph-HfH98qwn.js +1 -0
  8. package/dist/web/assets/index-CTOMzCnZ.js +28 -0
  9. package/dist/web/assets/index-D6GLlwUx.css +2 -0
  10. package/dist/web/assets/keybindings-store-DrjDQzVs.js +1 -0
  11. package/dist/web/assets/{markdown-renderer-BOHSi1fK.js → markdown-renderer-dqkYhU3y.js} +1 -1
  12. package/dist/web/assets/{postgres-viewer-DRo3924t.js → postgres-viewer-kqZBNVYW.js} +1 -1
  13. package/dist/web/assets/settings-tab-jhRBFgf_.js +1 -0
  14. package/dist/web/assets/{sqlite-viewer-0iVQjCmF.js → sqlite-viewer-C2744fw1.js} +1 -1
  15. package/dist/web/assets/switch-PAf5UhcN.js +1 -0
  16. package/dist/web/assets/{terminal-tab-Cuznr8Lg.js → terminal-tab-BDqc6Dl5.js} +1 -1
  17. package/dist/web/index.html +3 -3
  18. package/dist/web/sw.js +1 -1
  19. package/package.json +1 -1
  20. package/src/server/routes/accounts.ts +60 -6
  21. package/src/services/account.service.ts +180 -17
  22. package/src/services/claude-usage.service.ts +9 -2
  23. package/src/services/db.service.ts +25 -3
  24. package/src/web/components/chat/chat-history-bar.tsx +2 -1
  25. package/src/web/components/chat/message-list.tsx +4 -2
  26. package/src/web/components/chat/usage-badge.tsx +118 -22
  27. package/src/web/components/settings/accounts-settings-section.tsx +268 -33
  28. package/src/web/lib/api-settings.ts +49 -0
  29. package/src/web/styles/globals.css +7 -0
  30. package/test-claude-oauth-v2.mjs +165 -0
  31. package/test-claude-oauth.mjs +175 -0
  32. package/test-verify-oat.mjs +106 -0
  33. package/dist/web/assets/ai-settings-section-BxCMGg-I.js +0 -1
  34. package/dist/web/assets/chat-tab-DtIaMWNT.js +0 -7
  35. package/dist/web/assets/git-graph-DYbWcg6M.js +0 -1
  36. package/dist/web/assets/index-BzhcIgja.js +0 -28
  37. package/dist/web/assets/index-sMxUHxFZ.css +0 -2
  38. package/dist/web/assets/keybindings-store-DBQQ_pTh.js +0 -1
  39. package/dist/web/assets/settings-tab-6ytjTMb9.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":"d0d027accf1e8ee526a37eaa72d3af33","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-Cuznr8Lg.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-0iVQjCmF.js"},{"revision":null,"url":"assets/settings-tab-6ytjTMb9.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-DRo3924t.js"},{"revision":null,"url":"assets/markdown-renderer-BOHSi1fK.js"},{"revision":null,"url":"assets/keybindings-store-DBQQ_pTh.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-BzhcIgja.js"},{"revision":null,"url":"assets/git-graph-DYbWcg6M.js"},{"revision":null,"url":"assets/dist-D9RHR8A4.js"},{"revision":null,"url":"assets/diff-viewer-8RNfSVOl.js"},{"revision":null,"url":"assets/database-viewer-ChX5vA56.js"},{"revision":null,"url":"assets/columns-2-fz8yNaAo.js"},{"revision":null,"url":"assets/code-editor-DXqocnye.js"},{"revision":null,"url":"assets/chat-tab-DtIaMWNT.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hienlh/ppm",
3
- "version": "0.7.16",
3
+ "version": "0.7.17",
4
4
  "description": "Personal Project Manager — mobile-first web IDE with AI assistance",
5
5
  "author": "hienlh",
6
6
  "license": "MIT",
@@ -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 getCallbackUrl(c: Context): string {
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}/api/accounts/oauth/callback`;
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
- const url = new URL(c.req.url);
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 url = accountService.startOAuthFlow(getCallbackUrl(c));
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 usage API — 200/429 = valid, 401/403 = invalid
179
+ // Verify via profile API — returns email, org, subscription info
113
180
  try {
114
- const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
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
- // 200 = valid, 429 = rate limited but valid token
124
- if (res.status === 200 || res.status === 429) {
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
- const id = randomUUID();
243
+
166
244
  const email = info.email ?? null;
167
- // Auto-generate label: orgName (subscription) > authMethod-based > user-provided > fallback
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
- if (info.orgName) {
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(32).toString("base64url");
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
- const result: ClaudeUsage = { lastFetchedAt: row.recorded_at };
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)) return;
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[]));
@@ -158,7 +158,7 @@ export function ChatHistoryBar({
158
158
  <Settings2 className="size-3" />
159
159
  </button>
160
160
 
161
- {/* Usage badge */}
161
+ {/* Usage & Accounts */}
162
162
  <button
163
163
  onClick={() => togglePanel("usage")}
164
164
  className={`flex items-center gap-1 px-1.5 py-0.5 rounded text-[11px] font-medium tabular-nums transition-colors hover:bg-surface-elevated ${
@@ -314,6 +314,7 @@ export function ChatHistoryBar({
314
314
  lastFetchedAt={lastFetchedAt}
315
315
  />
316
316
  )}
317
+
317
318
  </div>
318
319
  );
319
320
  }