@hienlh/ppm 0.6.6 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +86 -313
- package/dist/web/assets/chat-tab-CbNbBMGw.js +7 -0
- package/dist/web/assets/{code-editor-ZFl5kZ4-.js → code-editor-D6OuzcC-.js} +1 -1
- package/dist/web/assets/{database-viewer-DPpOsMqa.js → database-viewer-BxUpM_uA.js} +1 -1
- package/dist/web/assets/{diff-viewer-CX74l6lV.js → diff-viewer-DAhrHpNM.js} +1 -1
- package/dist/web/assets/{dist-Jb3Tnkpc.js → dist-CNRrBoQi.js} +14 -14
- package/dist/web/assets/git-graph-BpTt5iOd.js +1 -0
- package/dist/web/assets/index-BU_07_oW.js +29 -0
- package/dist/web/assets/index-CBQhXXeV.css +2 -0
- package/dist/web/assets/keybindings-store-C0m8_V9X.js +1 -0
- package/dist/web/assets/{markdown-renderer-Bke6DHFh.js → markdown-renderer-CvGYO9sH.js} +2 -2
- package/dist/web/assets/postgres-viewer-BL99auSm.js +1 -0
- package/dist/web/assets/{settings-tab-DD05d8rM.js → settings-tab-Bwsxb41F.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-Cx7tLyT-.js → sqlite-viewer-DfgaCbWT.js} +1 -1
- package/dist/web/assets/terminal-tab-D27e4ZTD.js +36 -0
- package/dist/web/index.html +4 -3
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/lib/network-utils.ts +12 -0
- package/src/server/index.ts +3 -79
- package/src/server/routes/database.ts +57 -0
- package/src/server/routes/fs-browse.ts +67 -0
- package/src/server/routes/settings.ts +52 -0
- package/src/server/routes/tunnel.ts +1 -12
- package/src/server/ws/chat.ts +30 -3
- package/src/services/config.service.ts +1 -1
- package/src/services/fs-browse.service.ts +216 -0
- package/src/services/notification.service.ts +42 -0
- package/src/services/telegram-notification.service.ts +106 -0
- package/src/types/config.ts +6 -0
- package/src/web/app.tsx +61 -18
- package/src/web/components/chat/message-list.tsx +8 -105
- package/src/web/components/chat/question-card.tsx +334 -0
- package/src/web/components/database/connection-form-dialog.tsx +15 -6
- package/src/web/components/database/connection-import-export.tsx +116 -0
- package/src/web/components/database/database-sidebar.tsx +12 -8
- package/src/web/components/database/use-connections.ts +13 -1
- package/src/web/components/layout/add-project-form.tsx +23 -12
- package/src/web/components/layout/command-palette.tsx +1 -1
- package/src/web/components/layout/draggable-tab.tsx +10 -2
- package/src/web/components/layout/mobile-nav.tsx +42 -3
- package/src/web/components/layout/project-bar.tsx +16 -8
- package/src/web/components/layout/tab-bar.tsx +55 -4
- package/src/web/components/projects/dir-suggest.tsx +22 -12
- package/src/web/components/settings/settings-tab.tsx +135 -94
- package/src/web/components/settings/telegram-settings-section.tsx +113 -0
- package/src/web/components/ui/accordion.tsx +64 -0
- package/src/web/components/ui/browse-button.tsx +42 -0
- package/src/web/components/ui/file-browser-picker.tsx +374 -0
- package/src/web/hooks/use-chat.ts +29 -0
- package/src/web/hooks/use-notification-badge.ts +20 -0
- package/src/web/hooks/use-tab-overflow.ts +91 -0
- package/src/web/hooks/use-url-sync.ts +5 -2
- package/src/web/index.html +1 -0
- package/src/web/lib/favicon.ts +21 -0
- package/src/web/lib/notification-sounds.ts +61 -0
- package/src/web/stores/notification-store.ts +83 -0
- package/src/web/stores/project-store.ts +0 -14
- package/dist/web/assets/chat-tab-dwpaSkQD.js +0 -7
- package/dist/web/assets/git-graph-Dju1rygf.js +0 -1
- package/dist/web/assets/index-DSg2VjxL.css +0 -2
- package/dist/web/assets/index-DXOEmhRm.js +0 -21
- package/dist/web/assets/keybindings-store-VhiJwp77.js +0 -1
- package/dist/web/assets/postgres-viewer-DaNYnInA.js +0 -1
- package/dist/web/assets/terminal-tab-_farMLMO.js +0 -36
- /package/dist/web/assets/{tab-store-DIyJSjtr.js → tab-store-Bm1Hw8OR.js} +0 -0
package/dist/web/index.html
CHANGED
|
@@ -35,19 +35,20 @@
|
|
|
35
35
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
|
|
36
36
|
<meta name="theme-color" content="#0f1419" />
|
|
37
37
|
<title>PPM — Personal Project Manager</title>
|
|
38
|
+
<link rel="icon" id="ppm-favicon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2032%2032%22%3E%0A%20%20%3Crect%20width%3D%2232%22%20height%3D%2232%22%20rx%3D%226%22%20fill%3D%22%233b82f6%22%2F%3E%0A%20%20%3Ctext%20x%3D%2216%22%20y%3D%2222%22%20text-anchor%3D%22middle%22%20font-family%3D%22system-ui%2Csans-serif%22%20font-weight%3D%22700%22%20font-size%3D%2211%22%20fill%3D%22white%22%3EPPM%3C%2Ftext%3E%0A%3C%2Fsvg%3E" />
|
|
38
39
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
39
40
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
40
41
|
<link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;600;700&family=Geist:wght@400;500;600;700&display=swap" rel="stylesheet" />
|
|
41
|
-
<script type="module" crossorigin src="/assets/index-
|
|
42
|
+
<script type="module" crossorigin src="/assets/index-BU_07_oW.js"></script>
|
|
42
43
|
<link rel="modulepreload" crossorigin href="/assets/react-CYzKIDNi.js">
|
|
43
44
|
<link rel="modulepreload" crossorigin href="/assets/utils-siJJ3uG0.js">
|
|
44
45
|
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-wQxeESYQ.js">
|
|
45
46
|
<link rel="modulepreload" crossorigin href="/assets/input-nI4xe1Y9.js">
|
|
46
47
|
<link rel="modulepreload" crossorigin href="/assets/react-DHSo28we.js">
|
|
47
|
-
<link rel="modulepreload" crossorigin href="/assets/tab-store-
|
|
48
|
+
<link rel="modulepreload" crossorigin href="/assets/tab-store-Bm1Hw8OR.js">
|
|
48
49
|
<link rel="modulepreload" crossorigin href="/assets/api-client-4Ni0i4Hl.js">
|
|
49
50
|
<link rel="modulepreload" crossorigin href="/assets/settings-store-CfB0vCtQ.js">
|
|
50
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
51
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CBQhXXeV.css">
|
|
51
52
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
|
52
53
|
<body class="bg-[#0f1419] text-[#e5e7eb] font-sans antialiased">
|
|
53
54
|
<div id="root"></div>
|
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":"d6a5223443425f06ed7277f61fdaa009","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-siJJ3uG0.js"},{"revision":null,"url":"assets/use-monaco-theme-Dexl3s3E.js"},{"revision":null,"url":"assets/terminal-tab-_farMLMO.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/table-DCVKGOr2.js"},{"revision":null,"url":"assets/tab-store-DIyJSjtr.js"},{"revision":null,"url":"assets/sqlite-viewer-Cx7tLyT-.js"},{"revision":null,"url":"assets/settings-tab-DD05d8rM.js"},{"revision":null,"url":"assets/settings-store-CfB0vCtQ.js"},{"revision":null,"url":"assets/react-DHSo28we.js"},{"revision":null,"url":"assets/react-CYzKIDNi.js"},{"revision":null,"url":"assets/postgres-viewer-DaNYnInA.js"},{"revision":null,"url":"assets/markdown-renderer-Bke6DHFh.js"},{"revision":null,"url":"assets/keybindings-store-VhiJwp77.js"},{"revision":null,"url":"assets/jsx-runtime-wQxeESYQ.js"},{"revision":null,"url":"assets/input-nI4xe1Y9.js"},{"revision":null,"url":"assets/index-DXOEmhRm.js"},{"revision":null,"url":"assets/index-DSg2VjxL.css"},{"revision":null,"url":"assets/git-graph-Dju1rygf.js"},{"revision":null,"url":"assets/dist-Jb3Tnkpc.js"},{"revision":null,"url":"assets/diff-viewer-CX74l6lV.js"},{"revision":null,"url":"assets/database-viewer-DPpOsMqa.js"},{"revision":null,"url":"assets/code-editor-ZFl5kZ4-.js"},{"revision":null,"url":"assets/chat-tab-dwpaSkQD.js"},{"revision":null,"url":"assets/api-client-4Ni0i4Hl.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":"4276cff85869e97ffbe37bc4b5426eda","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-siJJ3uG0.js"},{"revision":null,"url":"assets/use-monaco-theme-Dexl3s3E.js"},{"revision":null,"url":"assets/terminal-tab-D27e4ZTD.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/table-DCVKGOr2.js"},{"revision":null,"url":"assets/tab-store-Bm1Hw8OR.js"},{"revision":null,"url":"assets/sqlite-viewer-DfgaCbWT.js"},{"revision":null,"url":"assets/settings-tab-Bwsxb41F.js"},{"revision":null,"url":"assets/settings-store-CfB0vCtQ.js"},{"revision":null,"url":"assets/react-DHSo28we.js"},{"revision":null,"url":"assets/react-CYzKIDNi.js"},{"revision":null,"url":"assets/postgres-viewer-BL99auSm.js"},{"revision":null,"url":"assets/markdown-renderer-CvGYO9sH.js"},{"revision":null,"url":"assets/keybindings-store-C0m8_V9X.js"},{"revision":null,"url":"assets/jsx-runtime-wQxeESYQ.js"},{"revision":null,"url":"assets/input-nI4xe1Y9.js"},{"revision":null,"url":"assets/index-CBQhXXeV.css"},{"revision":null,"url":"assets/index-BU_07_oW.js"},{"revision":null,"url":"assets/git-graph-BpTt5iOd.js"},{"revision":null,"url":"assets/dist-CNRrBoQi.js"},{"revision":null,"url":"assets/diff-viewer-DAhrHpNM.js"},{"revision":null,"url":"assets/database-viewer-BxUpM_uA.js"},{"revision":null,"url":"assets/code-editor-D6OuzcC-.js"},{"revision":null,"url":"assets/chat-tab-CbNbBMGw.js"},{"revision":null,"url":"assets/api-client-4Ni0i4Hl.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
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { networkInterfaces } from "node:os";
|
|
2
|
+
|
|
3
|
+
/** Return first non-internal IPv4 address, or null if none found */
|
|
4
|
+
export function getLocalIp(): string | null {
|
|
5
|
+
const nets = networkInterfaces();
|
|
6
|
+
for (const name of Object.keys(nets)) {
|
|
7
|
+
for (const net of nets[name] ?? []) {
|
|
8
|
+
if (net.family === "IPv4" && !net.internal) return net.address;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
}
|
package/src/server/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { staticRoutes } from "./routes/static.ts";
|
|
|
11
11
|
import { projectScopedRouter } from "./routes/project-scoped.ts";
|
|
12
12
|
import { postgresRoutes } from "./routes/postgres.ts";
|
|
13
13
|
import { databaseRoutes } from "./routes/database.ts";
|
|
14
|
+
import { fsBrowseRoutes } from "./routes/fs-browse.ts";
|
|
14
15
|
import { initAdapters } from "../services/database/init-adapters.ts";
|
|
15
16
|
import { terminalWebSocket } from "./ws/terminal.ts";
|
|
16
17
|
import { chatWebSocket } from "./ws/chat.ts";
|
|
@@ -103,85 +104,8 @@ if (process.env.NODE_ENV !== "production") {
|
|
|
103
104
|
app.use("/api/*", authMiddleware);
|
|
104
105
|
app.get("/api/auth/check", (c) => c.json(ok(true)));
|
|
105
106
|
|
|
106
|
-
// Filesystem
|
|
107
|
-
app.
|
|
108
|
-
const dir = c.req.query("dir");
|
|
109
|
-
if (!dir) return c.json(err("dir is required"), 400);
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
const path = await import("node:path");
|
|
113
|
-
const fs = await import("node:fs");
|
|
114
|
-
const { homedir } = await import("node:os");
|
|
115
|
-
const resolved = dir.startsWith("~") ? path.resolve(homedir(), dir.slice(2)) : path.resolve(dir);
|
|
116
|
-
|
|
117
|
-
const SKIP = new Set([".git", "node_modules", ".DS_Store"]);
|
|
118
|
-
const MAX_FILES = 200;
|
|
119
|
-
const MAX_DEPTH = 4;
|
|
120
|
-
const files: string[] = [];
|
|
121
|
-
|
|
122
|
-
function walk(dirPath: string, depth: number) {
|
|
123
|
-
if (depth > MAX_DEPTH || files.length >= MAX_FILES) return;
|
|
124
|
-
let entries: import("node:fs").Dirent[];
|
|
125
|
-
try { entries = fs.readdirSync(dirPath, { withFileTypes: true }); } catch { return; }
|
|
126
|
-
for (const entry of entries) {
|
|
127
|
-
if (SKIP.has(entry.name)) continue;
|
|
128
|
-
const full = path.join(dirPath, entry.name);
|
|
129
|
-
if (entry.isFile()) {
|
|
130
|
-
files.push(full);
|
|
131
|
-
if (files.length >= MAX_FILES) return;
|
|
132
|
-
} else if (entry.isDirectory()) {
|
|
133
|
-
walk(full, depth + 1);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
walk(resolved, 0);
|
|
139
|
-
return c.json(ok(files));
|
|
140
|
-
} catch (e) {
|
|
141
|
-
return c.json(err((e as Error).message), 500);
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// Filesystem file read (for opening files outside project) — cross-platform
|
|
146
|
-
app.get("/api/fs/read", async (c) => {
|
|
147
|
-
const filePath = c.req.query("path");
|
|
148
|
-
if (!filePath) return c.json(err("path is required"), 400);
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const path = await import("node:path");
|
|
152
|
-
const fs = await import("node:fs");
|
|
153
|
-
const { homedir } = await import("node:os");
|
|
154
|
-
const resolved = filePath.startsWith("~") ? path.resolve(homedir(), filePath.slice(2)) : path.resolve(filePath);
|
|
155
|
-
|
|
156
|
-
if (!fs.existsSync(resolved)) return c.json(err("File not found"), 404);
|
|
157
|
-
const stat = fs.statSync(resolved);
|
|
158
|
-
if (!stat.isFile()) return c.json(err("Not a file"), 400);
|
|
159
|
-
if (stat.size > 5 * 1024 * 1024) return c.json(err("File too large (>5MB)"), 400);
|
|
160
|
-
|
|
161
|
-
const content = fs.readFileSync(resolved, "utf-8");
|
|
162
|
-
return c.json(ok({ content, path: resolved }));
|
|
163
|
-
} catch (e) {
|
|
164
|
-
return c.json(err((e as Error).message), 500);
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// Filesystem file write (for saving files outside project) — cross-platform
|
|
169
|
-
app.put("/api/fs/write", async (c) => {
|
|
170
|
-
const body = await c.req.json<{ path: string; content: string }>();
|
|
171
|
-
if (!body.path || body.content == null) return c.json(err("path and content required"), 400);
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
const pathMod = await import("node:path");
|
|
175
|
-
const fs = await import("node:fs");
|
|
176
|
-
const { homedir } = await import("node:os");
|
|
177
|
-
const resolved = body.path.startsWith("~") ? pathMod.resolve(homedir(), body.path.slice(2)) : pathMod.resolve(body.path);
|
|
178
|
-
|
|
179
|
-
fs.writeFileSync(resolved, body.content, "utf-8");
|
|
180
|
-
return c.json(ok(true));
|
|
181
|
-
} catch (e) {
|
|
182
|
-
return c.json(err((e as Error).message), 500);
|
|
183
|
-
}
|
|
184
|
-
});
|
|
107
|
+
// Filesystem operations (browse, list, read, write) — consolidated in fs-browse route
|
|
108
|
+
app.route("/api/fs", fsBrowseRoutes);
|
|
185
109
|
|
|
186
110
|
// API routes
|
|
187
111
|
app.route("/api/settings", settingsRoutes);
|
|
@@ -43,6 +43,63 @@ databaseRoutes.get("/connections", (c) => {
|
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
+
/** GET /api/db/connections/export — full connection data including credentials */
|
|
47
|
+
databaseRoutes.get("/connections/export", (c) => {
|
|
48
|
+
try {
|
|
49
|
+
const conns = getConnections();
|
|
50
|
+
const exported = conns.map(({ id, sort_order, created_at, updated_at, ...rest }) => rest);
|
|
51
|
+
return c.json(ok({ version: 1, exported_at: new Date().toISOString(), connections: exported }));
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return c.json(err((e as Error).message), 500);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
/** POST /api/db/connections/import — bulk create from exported JSON */
|
|
58
|
+
databaseRoutes.post("/connections/import", async (c) => {
|
|
59
|
+
try {
|
|
60
|
+
const body = await c.req.json<{ connections: Array<{ type: string; name: string; connection_config: string; group_name?: string | null; color?: string | null; readonly?: number }> }>();
|
|
61
|
+
if (!Array.isArray(body.connections)) return c.json(err("connections array is required"), 400);
|
|
62
|
+
|
|
63
|
+
const existingNames = new Set(getConnections().map((c) => c.name));
|
|
64
|
+
const created: ReturnType<typeof sanitizeConn>[] = [];
|
|
65
|
+
const errors: string[] = [];
|
|
66
|
+
|
|
67
|
+
for (const entry of body.connections) {
|
|
68
|
+
try {
|
|
69
|
+
if (!entry.type || !entry.name || !entry.connection_config) {
|
|
70
|
+
errors.push(`Skipped: missing type/name/connection_config`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!["sqlite", "postgres"].includes(entry.type)) {
|
|
74
|
+
errors.push(`Skipped "${entry.name}": invalid type "${entry.type}"`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
let config: ConnectionConfig;
|
|
78
|
+
try { config = JSON.parse(entry.connection_config); } catch {
|
|
79
|
+
errors.push(`Skipped "${entry.name}": invalid connection_config JSON`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Deduplicate name
|
|
84
|
+
let name = entry.name;
|
|
85
|
+
let suffix = 2;
|
|
86
|
+
while (existingNames.has(name)) { name = `${entry.name} (${suffix++})`; }
|
|
87
|
+
existingNames.add(name);
|
|
88
|
+
|
|
89
|
+
const conn = insertConnection(entry.type as "sqlite" | "postgres", name, config, entry.group_name, entry.color);
|
|
90
|
+
if (entry.readonly === 0) updateConnection(conn.id, { readonly: 0 });
|
|
91
|
+
created.push(sanitizeConn(getConnectionById(conn.id)!));
|
|
92
|
+
} catch (e) {
|
|
93
|
+
errors.push(`Failed "${entry.name}": ${(e as Error).message}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return c.json(ok({ imported: created.length, skipped: errors.length, errors, connections: created }), 201);
|
|
98
|
+
} catch (e) {
|
|
99
|
+
return c.json(err((e as Error).message), 500);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
46
103
|
/** GET /api/db/connections/:id */
|
|
47
104
|
databaseRoutes.get("/connections/:id", (c) => {
|
|
48
105
|
const conn = resolveConn(c.req.param("id"));
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import {
|
|
3
|
+
browse,
|
|
4
|
+
list,
|
|
5
|
+
readSystemFile,
|
|
6
|
+
writeSystemFile,
|
|
7
|
+
} from "../../services/fs-browse.service.ts";
|
|
8
|
+
import { ok, err } from "../../types/api.ts";
|
|
9
|
+
|
|
10
|
+
export const fsBrowseRoutes = new Hono();
|
|
11
|
+
|
|
12
|
+
/** Map error to HTTP status (errors carry .status from service). */
|
|
13
|
+
function errorStatus(e: unknown): 400 | 403 | 404 | 500 {
|
|
14
|
+
const status = (e as { status?: number }).status;
|
|
15
|
+
if (status === 403 || status === 404 || status === 400) return status;
|
|
16
|
+
return 500;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** GET /api/fs/browse?path=/some/dir&showHidden=false */
|
|
20
|
+
fsBrowseRoutes.get("/browse", (c) => {
|
|
21
|
+
try {
|
|
22
|
+
const path = c.req.query("path") || undefined;
|
|
23
|
+
const showHidden = c.req.query("showHidden") === "true";
|
|
24
|
+
const result = browse(path, { showHidden });
|
|
25
|
+
return c.json(ok(result));
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return c.json(err((e as Error).message), errorStatus(e));
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/** GET /api/fs/list?dir=/some/dir — recursive file listing (command palette) */
|
|
32
|
+
fsBrowseRoutes.get("/list", (c) => {
|
|
33
|
+
try {
|
|
34
|
+
const dir = c.req.query("dir");
|
|
35
|
+
if (!dir) return c.json(err("dir is required"), 400);
|
|
36
|
+
const files = list(dir);
|
|
37
|
+
return c.json(ok(files));
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return c.json(err((e as Error).message), errorStatus(e));
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/** GET /api/fs/read?path=/some/file — read file outside project */
|
|
44
|
+
fsBrowseRoutes.get("/read", (c) => {
|
|
45
|
+
try {
|
|
46
|
+
const filePath = c.req.query("path");
|
|
47
|
+
if (!filePath) return c.json(err("path is required"), 400);
|
|
48
|
+
const result = readSystemFile(filePath);
|
|
49
|
+
return c.json(ok(result));
|
|
50
|
+
} catch (e) {
|
|
51
|
+
return c.json(err((e as Error).message), errorStatus(e));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/** PUT /api/fs/write — write file outside project { path, content } */
|
|
56
|
+
fsBrowseRoutes.put("/write", async (c) => {
|
|
57
|
+
try {
|
|
58
|
+
const body = await c.req.json<{ path: string; content: string }>();
|
|
59
|
+
if (!body.path || body.content == null) {
|
|
60
|
+
return c.json(err("path and content required"), 400);
|
|
61
|
+
}
|
|
62
|
+
writeSystemFile(body.path, body.content);
|
|
63
|
+
return c.json(ok(true));
|
|
64
|
+
} catch (e) {
|
|
65
|
+
return c.json(err((e as Error).message), errorStatus(e));
|
|
66
|
+
}
|
|
67
|
+
});
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
validateDefaultProvider,
|
|
7
7
|
VALID_PROVIDERS,
|
|
8
8
|
type AIProviderConfig,
|
|
9
|
+
type TelegramConfig,
|
|
9
10
|
type ThemeConfig,
|
|
10
11
|
} from "../../types/config.ts";
|
|
11
12
|
import { ok, err } from "../../types/api.ts";
|
|
@@ -135,3 +136,54 @@ settingsRoutes.put("/keybindings", async (c) => {
|
|
|
135
136
|
return c.json(err((e as Error).message), 400);
|
|
136
137
|
}
|
|
137
138
|
});
|
|
139
|
+
|
|
140
|
+
// ── Telegram ──────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
/** GET /settings/telegram — return current telegram config (masks bot_token) */
|
|
143
|
+
settingsRoutes.get("/telegram", (c) => {
|
|
144
|
+
const tg = configService.get("telegram") as TelegramConfig | undefined;
|
|
145
|
+
if (!tg) return c.json(ok({ bot_token: "", chat_id: "" }));
|
|
146
|
+
return c.json(ok({
|
|
147
|
+
bot_token: tg.bot_token ? `${tg.bot_token.slice(0, 6)}...` : "",
|
|
148
|
+
chat_id: tg.chat_id,
|
|
149
|
+
}));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
/** PUT /settings/telegram — save telegram bot_token + chat_id */
|
|
153
|
+
settingsRoutes.put("/telegram", async (c) => {
|
|
154
|
+
try {
|
|
155
|
+
const body = await c.req.json<{ bot_token?: string; chat_id?: string }>();
|
|
156
|
+
const current = (configService.get("telegram") as TelegramConfig | undefined) ?? { bot_token: "", chat_id: "" };
|
|
157
|
+
const updated: TelegramConfig = {
|
|
158
|
+
bot_token: body.bot_token ?? current.bot_token,
|
|
159
|
+
chat_id: body.chat_id ?? current.chat_id,
|
|
160
|
+
};
|
|
161
|
+
configService.set("telegram", updated);
|
|
162
|
+
configService.save();
|
|
163
|
+
return c.json(ok({
|
|
164
|
+
bot_token: updated.bot_token ? `${updated.bot_token.slice(0, 6)}...` : "",
|
|
165
|
+
chat_id: updated.chat_id,
|
|
166
|
+
}));
|
|
167
|
+
} catch (e) {
|
|
168
|
+
return c.json(err((e as Error).message), 400);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
/** POST /settings/telegram/test — send a test message */
|
|
173
|
+
settingsRoutes.post("/telegram/test", async (c) => {
|
|
174
|
+
try {
|
|
175
|
+
const body = await c.req.json<{ bot_token?: string; chat_id?: string }>();
|
|
176
|
+
const current = (configService.get("telegram") as TelegramConfig | undefined) ?? { bot_token: "", chat_id: "" };
|
|
177
|
+
const token = body.bot_token || current.bot_token;
|
|
178
|
+
const chatId = body.chat_id || current.chat_id;
|
|
179
|
+
if (!token || !chatId) {
|
|
180
|
+
return c.json(err("bot_token and chat_id are required"), 400);
|
|
181
|
+
}
|
|
182
|
+
const { telegramService } = await import("../../services/telegram-notification.service.ts");
|
|
183
|
+
const result = await telegramService.sendTest(token, chatId);
|
|
184
|
+
if (!result.ok) return c.json(err(result.error ?? "Failed"), 500);
|
|
185
|
+
return c.json(ok({ sent: true }));
|
|
186
|
+
} catch (e) {
|
|
187
|
+
return c.json(err((e as Error).message), 500);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
@@ -1,20 +1,9 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import { networkInterfaces } from "node:os";
|
|
3
2
|
import { tunnelService } from "../../services/tunnel.service.ts";
|
|
4
3
|
import { configService } from "../../services/config.service.ts";
|
|
4
|
+
import { getLocalIp } from "../../lib/network-utils.ts";
|
|
5
5
|
import { ok, err } from "../../types/api.ts";
|
|
6
6
|
|
|
7
|
-
/** Return first non-internal IPv4 address */
|
|
8
|
-
function getLocalIp(): string | null {
|
|
9
|
-
const nets = networkInterfaces();
|
|
10
|
-
for (const name of Object.keys(nets)) {
|
|
11
|
-
for (const net of nets[name] ?? []) {
|
|
12
|
-
if (net.family === "IPv4" && !net.internal) return net.address;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
7
|
export const tunnelRoutes = new Hono();
|
|
19
8
|
|
|
20
9
|
/** GET /api/tunnel — current tunnel status + local URL */
|
package/src/server/ws/chat.ts
CHANGED
|
@@ -35,6 +35,14 @@ interface SessionEntry {
|
|
|
35
35
|
/** Tracks active sessions — persists even when FE disconnects */
|
|
36
36
|
const activeSessions = new Map<string, SessionEntry>();
|
|
37
37
|
|
|
38
|
+
/** Check if any frontend client is currently connected via WebSocket */
|
|
39
|
+
export function hasActiveClient(): boolean {
|
|
40
|
+
for (const entry of activeSessions.values()) {
|
|
41
|
+
if (entry.ws) return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
/** Send event to FE if connected, silently drop otherwise */
|
|
39
47
|
function safeSend(sessionId: string, event: unknown): void {
|
|
40
48
|
const entry = activeSessions.get(sessionId);
|
|
@@ -159,15 +167,34 @@ async function runStreamLoop(sessionId: string, providerId: string, content: str
|
|
|
159
167
|
if (session) session.title = found.summary;
|
|
160
168
|
}
|
|
161
169
|
}).catch(() => {});
|
|
162
|
-
// Fire-and-forget push
|
|
163
|
-
import("../../services/
|
|
170
|
+
// Fire-and-forget notification broadcast (push + telegram)
|
|
171
|
+
import("../../services/notification.service.ts").then(({ notificationService }) => {
|
|
164
172
|
const project = entry.projectName || "Project";
|
|
165
173
|
const session = chatService.getSession(sessionId);
|
|
166
174
|
const sessionTitle = session?.title || `Session ${sessionId.slice(0, 8)}`;
|
|
167
|
-
|
|
175
|
+
notificationService.broadcast("done", {
|
|
176
|
+
title: "Chat completed",
|
|
177
|
+
body: `${project} — ${sessionTitle}`,
|
|
178
|
+
project,
|
|
179
|
+
sessionId,
|
|
180
|
+
sessionTitle,
|
|
181
|
+
});
|
|
168
182
|
}).catch(() => {});
|
|
169
183
|
} else if (evType === "approval_request") {
|
|
170
184
|
entry.pendingApprovalEvent = ev;
|
|
185
|
+
// Fire-and-forget notification for approval/question
|
|
186
|
+
import("../../services/notification.service.ts").then(({ notificationService }) => {
|
|
187
|
+
const project = entry.projectName || "Project";
|
|
188
|
+
const session = chatService.getSession(sessionId);
|
|
189
|
+
const sTitle = session?.title || `Session ${sessionId.slice(0, 8)}`;
|
|
190
|
+
const isQuestion = ev.tool === "AskUserQuestion";
|
|
191
|
+
const nType = isQuestion ? "question" : "approval_request";
|
|
192
|
+
const title = isQuestion ? "AI has a question" : "Waiting for approval";
|
|
193
|
+
const body = isQuestion
|
|
194
|
+
? `${project} — ${sTitle}`
|
|
195
|
+
: `${project} — ${ev.tool} needs permission`;
|
|
196
|
+
notificationService.broadcast(nType as any, { title, body, project, sessionId, sessionTitle: sTitle, tool: ev.tool });
|
|
197
|
+
}).catch(() => {});
|
|
171
198
|
} else {
|
|
172
199
|
logSessionEvent(sessionId, evType.toUpperCase(), JSON.stringify(ev).slice(0, 200));
|
|
173
200
|
}
|
|
@@ -19,7 +19,7 @@ const PPM_DIR = resolve(homedir(), ".ppm");
|
|
|
19
19
|
|
|
20
20
|
/** Top-level config keys stored in the config table (not projects) */
|
|
21
21
|
const CONFIG_TABLE_KEYS: (keyof PpmConfig)[] = [
|
|
22
|
-
"device_name", "port", "host", "theme", "auth", "ai", "push",
|
|
22
|
+
"device_name", "port", "host", "theme", "auth", "ai", "push", "telegram",
|
|
23
23
|
];
|
|
24
24
|
|
|
25
25
|
class ConfigService {
|