@hienlh/ppm 0.6.6 → 0.6.7
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 +11 -0
- package/dist/web/assets/chat-tab-CWhxhPKH.js +7 -0
- package/dist/web/assets/{code-editor-ZFl5kZ4-.js → code-editor-BUg7alP6.js} +1 -1
- package/dist/web/assets/{database-viewer-DPpOsMqa.js → database-viewer-CAgZOkZc.js} +1 -1
- package/dist/web/assets/{diff-viewer-CX74l6lV.js → diff-viewer-DVvY1aFb.js} +1 -1
- package/dist/web/assets/{git-graph-Dju1rygf.js → git-graph-xD6TLRVv.js} +1 -1
- package/dist/web/assets/index-CigdXBuQ.css +2 -0
- package/dist/web/assets/index-DBdw8tN_.js +22 -0
- package/dist/web/assets/keybindings-store-kHLASnRb.js +1 -0
- package/dist/web/assets/{markdown-renderer-Bke6DHFh.js → markdown-renderer-z99RjIxZ.js} +1 -1
- package/dist/web/assets/{postgres-viewer-DaNYnInA.js → postgres-viewer-CaMySHpD.js} +1 -1
- package/dist/web/assets/{settings-tab-DD05d8rM.js → settings-tab-BnDkeQWk.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-Cx7tLyT-.js → sqlite-viewer-EwHWc37J.js} +1 -1
- package/dist/web/assets/terminal-tab-CTN18lb6.js +36 -0
- package/dist/web/index.html +2 -2
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- 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/services/fs-browse.service.ts +216 -0
- package/src/web/app.tsx +23 -19
- 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/projects/dir-suggest.tsx +22 -12
- package/src/web/components/ui/browse-button.tsx +42 -0
- package/src/web/components/ui/file-browser-picker.tsx +374 -0
- package/src/web/stores/project-store.ts +0 -14
- package/dist/web/assets/chat-tab-dwpaSkQD.js +0 -7
- 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/terminal-tab-_farMLMO.js +0 -36
package/dist/web/index.html
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
39
39
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
40
40
|
<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-
|
|
41
|
+
<script type="module" crossorigin src="/assets/index-DBdw8tN_.js"></script>
|
|
42
42
|
<link rel="modulepreload" crossorigin href="/assets/react-CYzKIDNi.js">
|
|
43
43
|
<link rel="modulepreload" crossorigin href="/assets/utils-siJJ3uG0.js">
|
|
44
44
|
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-wQxeESYQ.js">
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
<link rel="modulepreload" crossorigin href="/assets/tab-store-DIyJSjtr.js">
|
|
48
48
|
<link rel="modulepreload" crossorigin href="/assets/api-client-4Ni0i4Hl.js">
|
|
49
49
|
<link rel="modulepreload" crossorigin href="/assets/settings-store-CfB0vCtQ.js">
|
|
50
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
50
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CigdXBuQ.css">
|
|
51
51
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
|
52
52
|
<body class="bg-[#0f1419] text-[#e5e7eb] font-sans antialiased">
|
|
53
53
|
<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":"ba24b0a24eb3a419f20bb0032733de86","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-CTN18lb6.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-EwHWc37J.js"},{"revision":null,"url":"assets/settings-tab-BnDkeQWk.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-CaMySHpD.js"},{"revision":null,"url":"assets/markdown-renderer-z99RjIxZ.js"},{"revision":null,"url":"assets/keybindings-store-kHLASnRb.js"},{"revision":null,"url":"assets/jsx-runtime-wQxeESYQ.js"},{"revision":null,"url":"assets/input-nI4xe1Y9.js"},{"revision":null,"url":"assets/index-DBdw8tN_.js"},{"revision":null,"url":"assets/index-CigdXBuQ.css"},{"revision":null,"url":"assets/git-graph-xD6TLRVv.js"},{"revision":null,"url":"assets/dist-Jb3Tnkpc.js"},{"revision":null,"url":"assets/diff-viewer-DVvY1aFb.js"},{"revision":null,"url":"assets/database-viewer-CAgZOkZc.js"},{"revision":null,"url":"assets/code-editor-BUg7alP6.js"},{"revision":null,"url":"assets/chat-tab-CWhxhPKH.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
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
|
+
});
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
readdirSync,
|
|
4
|
+
statSync,
|
|
5
|
+
lstatSync,
|
|
6
|
+
readFileSync,
|
|
7
|
+
writeFileSync,
|
|
8
|
+
} from "node:fs";
|
|
9
|
+
import { resolve, basename, dirname, normalize } from "node:path";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
|
|
12
|
+
// ── Types ──────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export interface BrowseEntry {
|
|
15
|
+
name: string;
|
|
16
|
+
path: string;
|
|
17
|
+
type: "file" | "directory";
|
|
18
|
+
size?: number;
|
|
19
|
+
modified: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface BrowseResult {
|
|
23
|
+
entries: BrowseEntry[];
|
|
24
|
+
current: string;
|
|
25
|
+
parent: string | null;
|
|
26
|
+
breadcrumbs: { name: string; path: string }[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface BrowseOptions {
|
|
30
|
+
showHidden?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── Constants ──────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
const SKIP_NAMES = new Set([".git", "node_modules", ".DS_Store"]);
|
|
36
|
+
const LIST_MAX_FILES = 200;
|
|
37
|
+
const LIST_MAX_DEPTH = 4;
|
|
38
|
+
const READ_MAX_SIZE = 5 * 1024 * 1024; // 5 MB
|
|
39
|
+
|
|
40
|
+
/** Roots allowed for system-level browsing (outside project scope). */
|
|
41
|
+
const ALLOWED_ROOTS_POSIX = ["/Volumes", "/mnt", "/media", "/tmp", "/home"];
|
|
42
|
+
|
|
43
|
+
// ── Shared helpers ─────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/** Resolve a path, expanding leading `~` to home directory. */
|
|
46
|
+
export function resolvePath(input: string): string {
|
|
47
|
+
const home = homedir();
|
|
48
|
+
return input.startsWith("~")
|
|
49
|
+
? resolve(home, input.slice(2))
|
|
50
|
+
: resolve(input);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Check if an absolute path is within the allowed whitelist. */
|
|
54
|
+
export function isAllowedPath(resolved: string): boolean {
|
|
55
|
+
const home = homedir();
|
|
56
|
+
if (resolved === home || resolved.startsWith(home + "/")) return true;
|
|
57
|
+
|
|
58
|
+
if (process.platform === "win32") {
|
|
59
|
+
return /^[A-Z]:\\/i.test(resolved);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return ALLOWED_ROOTS_POSIX.some(
|
|
63
|
+
(r) => resolved === r || resolved.startsWith(r + "/"),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Browse (new) ───────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/** List entries of a single directory (1-level, structured). */
|
|
70
|
+
export function browse(
|
|
71
|
+
dirPath?: string,
|
|
72
|
+
options?: BrowseOptions,
|
|
73
|
+
): BrowseResult {
|
|
74
|
+
const resolved = dirPath ? resolvePath(dirPath) : homedir();
|
|
75
|
+
|
|
76
|
+
if (!isAllowedPath(resolved)) {
|
|
77
|
+
throw Object.assign(new Error("Access denied"), { status: 403 });
|
|
78
|
+
}
|
|
79
|
+
if (!existsSync(resolved)) {
|
|
80
|
+
throw Object.assign(new Error("Directory not found"), { status: 404 });
|
|
81
|
+
}
|
|
82
|
+
if (!statSync(resolved).isDirectory()) {
|
|
83
|
+
throw Object.assign(new Error("Not a directory"), { status: 400 });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const raw = readdirSync(resolved, { withFileTypes: true });
|
|
87
|
+
const entries: BrowseEntry[] = [];
|
|
88
|
+
|
|
89
|
+
for (const entry of raw) {
|
|
90
|
+
if (!options?.showHidden && entry.name.startsWith(".")) continue;
|
|
91
|
+
if (entry.name.startsWith(".env")) continue; // always hide .env*
|
|
92
|
+
|
|
93
|
+
const fullPath = resolve(resolved, entry.name);
|
|
94
|
+
try {
|
|
95
|
+
if (lstatSync(fullPath).isSymbolicLink()) continue;
|
|
96
|
+
const st = statSync(fullPath);
|
|
97
|
+
entries.push({
|
|
98
|
+
name: entry.name,
|
|
99
|
+
path: fullPath,
|
|
100
|
+
type: st.isDirectory() ? "directory" : "file",
|
|
101
|
+
size: st.isFile() ? st.size : undefined,
|
|
102
|
+
modified: st.mtime.toISOString(),
|
|
103
|
+
});
|
|
104
|
+
} catch {
|
|
105
|
+
/* permission denied — skip */
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
entries.sort((a, b) => {
|
|
110
|
+
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
111
|
+
return a.name.localeCompare(b.name);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const parentDir = dirname(resolved);
|
|
115
|
+
return {
|
|
116
|
+
entries,
|
|
117
|
+
current: resolved,
|
|
118
|
+
parent: parentDir !== resolved ? parentDir : null,
|
|
119
|
+
breadcrumbs: buildBreadcrumbs(resolved),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function buildBreadcrumbs(
|
|
124
|
+
absPath: string,
|
|
125
|
+
): { name: string; path: string }[] {
|
|
126
|
+
const home = homedir();
|
|
127
|
+
const parts: { name: string; path: string }[] = [];
|
|
128
|
+
let current = absPath;
|
|
129
|
+
|
|
130
|
+
while (current !== dirname(current)) {
|
|
131
|
+
if (current === home) {
|
|
132
|
+
parts.unshift({ name: "~", path: current });
|
|
133
|
+
return parts;
|
|
134
|
+
}
|
|
135
|
+
parts.unshift({ name: basename(current), path: current });
|
|
136
|
+
current = dirname(current);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Reached filesystem root
|
|
140
|
+
if (!parts.length || parts[0]!.path !== current) {
|
|
141
|
+
parts.unshift({ name: basename(current) || "/", path: current });
|
|
142
|
+
}
|
|
143
|
+
return parts;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── List (moved from index.ts inline) ──────────────────────────────
|
|
147
|
+
|
|
148
|
+
/** Recursive file listing for command palette. */
|
|
149
|
+
export function list(dir: string): string[] {
|
|
150
|
+
const resolved = resolvePath(dir);
|
|
151
|
+
if (!isAllowedPath(resolved)) {
|
|
152
|
+
throw Object.assign(new Error("Access denied"), { status: 403 });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const files: string[] = [];
|
|
156
|
+
|
|
157
|
+
function walk(dirPath: string, depth: number) {
|
|
158
|
+
if (depth > LIST_MAX_DEPTH || files.length >= LIST_MAX_FILES) return;
|
|
159
|
+
let entries: import("node:fs").Dirent[];
|
|
160
|
+
try {
|
|
161
|
+
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
162
|
+
} catch {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
for (const entry of entries) {
|
|
166
|
+
if (SKIP_NAMES.has(entry.name)) continue;
|
|
167
|
+
const full = resolve(dirPath, entry.name);
|
|
168
|
+
if (entry.isFile()) {
|
|
169
|
+
files.push(full);
|
|
170
|
+
if (files.length >= LIST_MAX_FILES) return;
|
|
171
|
+
} else if (entry.isDirectory()) {
|
|
172
|
+
walk(full, depth + 1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
walk(resolved, 0);
|
|
178
|
+
return files;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ── Read (moved from index.ts inline) ──────────────────────────────
|
|
182
|
+
|
|
183
|
+
/** Read a file outside project scope. */
|
|
184
|
+
export function readSystemFile(
|
|
185
|
+
filePath: string,
|
|
186
|
+
): { content: string; path: string } {
|
|
187
|
+
const resolved = resolvePath(filePath);
|
|
188
|
+
if (!isAllowedPath(resolved)) {
|
|
189
|
+
throw Object.assign(new Error("Access denied"), { status: 403 });
|
|
190
|
+
}
|
|
191
|
+
if (!existsSync(resolved)) {
|
|
192
|
+
throw Object.assign(new Error("File not found"), { status: 404 });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const st = statSync(resolved);
|
|
196
|
+
if (!st.isFile()) {
|
|
197
|
+
throw Object.assign(new Error("Not a file"), { status: 400 });
|
|
198
|
+
}
|
|
199
|
+
if (st.size > READ_MAX_SIZE) {
|
|
200
|
+
throw Object.assign(new Error("File too large (>5MB)"), { status: 400 });
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const content = readFileSync(resolved, "utf-8");
|
|
204
|
+
return { content, path: resolved };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ── Write (moved from index.ts inline) ─────────────────────────────
|
|
208
|
+
|
|
209
|
+
/** Write a file outside project scope. */
|
|
210
|
+
export function writeSystemFile(filePath: string, content: string): void {
|
|
211
|
+
const resolved = resolvePath(filePath);
|
|
212
|
+
if (!isAllowedPath(resolved)) {
|
|
213
|
+
throw Object.assign(new Error("Access denied"), { status: 403 });
|
|
214
|
+
}
|
|
215
|
+
writeFileSync(resolved, content, "utf-8");
|
|
216
|
+
}
|
package/src/web/app.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState, useCallback } from "react";
|
|
1
|
+
import { useEffect, useState, useCallback, useRef } from "react";
|
|
2
2
|
import { Toaster } from "@/components/ui/sonner";
|
|
3
3
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
4
4
|
import { PanelLayout } from "@/components/layout/panel-layout";
|
|
@@ -8,7 +8,7 @@ import { MobileNav } from "@/components/layout/mobile-nav";
|
|
|
8
8
|
import { MobileDrawer } from "@/components/layout/mobile-drawer";
|
|
9
9
|
import { ProjectBottomSheet } from "@/components/layout/project-bottom-sheet";
|
|
10
10
|
import { LoginScreen } from "@/components/auth/login-screen";
|
|
11
|
-
import { useProjectStore } from "@/stores/project-store";
|
|
11
|
+
import { useProjectStore, resolveOrder } from "@/stores/project-store";
|
|
12
12
|
import { useTabStore } from "@/stores/tab-store";
|
|
13
13
|
import {
|
|
14
14
|
useSettingsStore,
|
|
@@ -45,6 +45,9 @@ export function App() {
|
|
|
45
45
|
const fetchServerInfo = useSettingsStore((s) => s.fetchServerInfo);
|
|
46
46
|
const activeProject = useProjectStore((s) => s.activeProject);
|
|
47
47
|
|
|
48
|
+
// Capture URL state on mount — before any effect can overwrite it
|
|
49
|
+
const initialUrlRef = useRef(parseUrlState());
|
|
50
|
+
|
|
48
51
|
// Apply theme on mount and when it changes
|
|
49
52
|
useEffect(() => {
|
|
50
53
|
applyThemeClass(theme);
|
|
@@ -112,23 +115,24 @@ export function App() {
|
|
|
112
115
|
if (authState !== "authenticated") return;
|
|
113
116
|
|
|
114
117
|
fetchProjects().then(() => {
|
|
115
|
-
const { projectName: urlProject, tabId: urlTab } =
|
|
116
|
-
const projects = useProjectStore.getState()
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
118
|
+
const { projectName: urlProject, tabId: urlTab } = initialUrlRef.current;
|
|
119
|
+
const { projects, customOrder } = useProjectStore.getState();
|
|
120
|
+
if (projects.length === 0) return;
|
|
121
|
+
|
|
122
|
+
// URL project takes priority, then fall back to first sorted project
|
|
123
|
+
let target = urlProject ? projects.find((p) => p.name === urlProject) : undefined;
|
|
124
|
+
if (!target) {
|
|
125
|
+
target = resolveOrder(projects, customOrder)[0];
|
|
126
|
+
}
|
|
127
|
+
if (target) {
|
|
128
|
+
useProjectStore.getState().setActiveProject(target);
|
|
129
|
+
if (urlProject && urlTab) {
|
|
130
|
+
queueMicrotask(() => {
|
|
131
|
+
const { tabs } = useTabStore.getState();
|
|
132
|
+
if (tabs.some((t) => t.id === urlTab)) {
|
|
133
|
+
useTabStore.getState().setActiveTab(urlTab);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
132
136
|
}
|
|
133
137
|
}
|
|
134
138
|
});
|