@hienlh/ppm 0.8.61 → 0.8.63
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 +12 -0
- package/dist/web/assets/{_basePickBy-COwDPZl_.js → _basePickBy-CZovQgWd.js} +1 -1
- package/dist/web/assets/{_baseUniq-DCb0mkTp.js → _baseUniq-ClnvscgW.js} +1 -1
- package/dist/web/assets/{api-settings-CuUkz5gb.js → api-settings--eVrUeZM.js} +1 -1
- package/dist/web/assets/{arc-D0bJaFyD.js → arc-C2Qaz-ch.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-ChOahOB7.js +1 -0
- package/dist/web/assets/{architectureDiagram-2XIMDMQ5-BVEUkQYB.js → architectureDiagram-2XIMDMQ5-Jq91S_rs.js} +1 -1
- package/dist/web/assets/{blockDiagram-WCTKOSBZ-CU2t4NHJ.js → blockDiagram-WCTKOSBZ-CKGufRTy.js} +1 -1
- package/dist/web/assets/browser-tab-CjjWgPDL.js +1 -0
- package/dist/web/assets/{c4Diagram-IC4MRINW-DzjR91sM.js → c4Diagram-IC4MRINW-BNP2L9r_.js} +1 -1
- package/dist/web/assets/channel-w7yboq56.js +1 -0
- package/dist/web/assets/chat-tab--hD0r5RS.js +7 -0
- package/dist/web/assets/{chunk-4BX2VUAB-0YMkpW2S.js → chunk-4BX2VUAB-BptTlTyl.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-Dp0pTM5r.js → chunk-55IACEB6-C4mUdyio.js} +1 -1
- package/dist/web/assets/{chunk-7E7YKBS2-CuYKSUgJ.js → chunk-7E7YKBS2-6xAQfBwa.js} +1 -1
- package/dist/web/assets/{chunk-7R4GIKGN-DvbvLUIN.js → chunk-7R4GIKGN-DXaGAn_K.js} +2 -2
- package/dist/web/assets/{chunk-C72U2L5F-CcEW1AMZ.js → chunk-C72U2L5F-DOtEiN5f.js} +1 -1
- package/dist/web/assets/{chunk-EGIJ26TM-Cgt-qg75.js → chunk-EGIJ26TM-D0KJTa_T.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-JCLgVcaC.js → chunk-FMBD7UC4-C_1aG0eb.js} +1 -1
- package/dist/web/assets/{chunk-GEFDOKGD-B82RP9ow.js → chunk-GEFDOKGD-DwVPiYfW.js} +1 -1
- package/dist/web/assets/chunk-GLR3WWYH-D9pZakqr.js +2 -0
- package/dist/web/assets/chunk-HHEYEP7N-Dld5BpGB.js +1 -0
- package/dist/web/assets/{chunk-JSJVCQXG-Pb-JMOgO.js → chunk-JSJVCQXG-BSrqCL_3.js} +1 -1
- package/dist/web/assets/{chunk-KX2RTZJC-BRj-ZEvL.js → chunk-KX2RTZJC-BCxGmbzy.js} +1 -1
- package/dist/web/assets/{chunk-KYZI473N-CBRPKraG.js → chunk-KYZI473N-BKO5gMeU.js} +1 -1
- package/dist/web/assets/{chunk-L3YUKLVL-DNFj84V6.js → chunk-L3YUKLVL-3wBgkSvL.js} +1 -1
- package/dist/web/assets/{chunk-MX3YWQON-BnPzQK-O.js → chunk-MX3YWQON-BgjSEzus.js} +1 -1
- package/dist/web/assets/{chunk-NQ4KR5QH-BRj25yO7.js → chunk-NQ4KR5QH-DLrZwBEm.js} +1 -1
- package/dist/web/assets/{chunk-O4XLMI2P-BdXwVXjJ.js → chunk-O4XLMI2P-BurQy8tt.js} +1 -1
- package/dist/web/assets/{chunk-OZEHJAEY-LfXT4p8B.js → chunk-OZEHJAEY-YTn24bGg.js} +1 -1
- package/dist/web/assets/{chunk-PQ6SQG4A-EdgQyTqa.js → chunk-PQ6SQG4A-BxtUGYhW.js} +1 -1
- package/dist/web/assets/{chunk-PU5JKC2W-D3thuSok.js → chunk-PU5JKC2W-B66ELkQm.js} +1 -1
- package/dist/web/assets/chunk-QZHKN3VN-DwSXwtjH.js +1 -0
- package/dist/web/assets/{chunk-R5LLSJPH-LdG7RqsM.js → chunk-R5LLSJPH-euR2RxLN.js} +1 -1
- package/dist/web/assets/{chunk-WL4C6EOR-BHFnnXOt.js → chunk-WL4C6EOR-_2CBOJdI.js} +1 -1
- package/dist/web/assets/{chunk-XIRO2GV7-DUmQrLsF.js → chunk-XIRO2GV7-kqQ0g6wW.js} +1 -1
- package/dist/web/assets/{chunk-XPW4576I-CsGTseUr.js → chunk-XPW4576I-CtcaMb09.js} +1 -1
- package/dist/web/assets/{chunk-XZSTWKYB-5W2emiq4.js → chunk-XZSTWKYB-BYxFzZwS.js} +1 -1
- package/dist/web/assets/{chunk-YBOYWFTD-COdZIaX4.js → chunk-YBOYWFTD-Dx_fX35n.js} +1 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-BpJ6Oog2.js +1 -0
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-Bj8gIhkP.js +1 -0
- package/dist/web/assets/clone-BSi6cgDh.js +1 -0
- package/dist/web/assets/{code-editor-C6umJOvn.js → code-editor-EG9sb3gL.js} +1 -1
- package/dist/web/assets/{cose-bilkent-S5V4N54A-C1QJ6GPW.js → cose-bilkent-S5V4N54A-CHHjH2dV.js} +1 -1
- package/dist/web/assets/{dagre-CWo8w9wK.js → dagre-CNtSxiE_.js} +1 -1
- package/dist/web/assets/{dagre-KLK3FWXG-Br4t5TRV.js → dagre-KLK3FWXG-ChenfPp1.js} +1 -1
- package/dist/web/assets/database-viewer-h1Zb9cFF.js +1 -0
- package/dist/web/assets/{diagram-E7M64L7V-CkDC2uAj.js → diagram-E7M64L7V-CzKYZM0Y.js} +1 -1
- package/dist/web/assets/{diagram-IFDJBPK2-NvhckwcA.js → diagram-IFDJBPK2-ChB_paPo.js} +1 -1
- package/dist/web/assets/{diagram-P4PSJMXO--nUaNiyB.js → diagram-P4PSJMXO-D1eW1dkL.js} +1 -1
- package/dist/web/assets/{diff-viewer-DApETeeX.js → diff-viewer-DrTqG6RM.js} +1 -1
- package/dist/web/assets/{erDiagram-INFDFZHY-DK4QEZYh.js → erDiagram-INFDFZHY-mCvUFSn6.js} +1 -1
- package/dist/web/assets/{flowDiagram-PKNHOUZH-B9h_Ba-v.js → flowDiagram-PKNHOUZH-14ohZ1M1.js} +1 -1
- package/dist/web/assets/{ganttDiagram-A5KZAMGK-BVlftqyZ.js → ganttDiagram-A5KZAMGK-DIX0pLbk.js} +1 -1
- package/dist/web/assets/git-graph-Bx3h7BK1.js +1 -0
- package/dist/web/assets/gitGraph-HDMCJU4V-CEee2FCA.js +1 -0
- package/dist/web/assets/{gitGraphDiagram-K3NZZRJ6-L7sj3Bs-.js → gitGraphDiagram-K3NZZRJ6-yEWZbdf_.js} +1 -1
- package/dist/web/assets/{graphlib-BbbiUImY.js → graphlib-DhOZxqsh.js} +1 -1
- package/dist/web/assets/index-Beb248lR.css +2 -0
- package/dist/web/assets/index-DmfRyMpE.js +37 -0
- package/dist/web/assets/info-3K5VOQVL-ce_pi3En.js +1 -0
- package/dist/web/assets/infoDiagram-LFFYTUFH-BzqyoqXw.js +2 -0
- package/dist/web/assets/input-Brjz2Vv-.js +41 -0
- package/dist/web/assets/{isEmpty-DXomfd7J.js → isEmpty-C0YYdhYj.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-PHBUUO56-cW7SMLa_.js → ishikawaDiagram-PHBUUO56-olazD6dZ.js} +1 -1
- package/dist/web/assets/{journeyDiagram-4ABVD52K-DFQXUZsc.js → journeyDiagram-4ABVD52K-CttDH9bb.js} +1 -1
- package/dist/web/assets/{kanban-definition-K7BYSVSG-BMUhjxqj.js → kanban-definition-K7BYSVSG-BBXbI37U.js} +1 -1
- package/dist/web/assets/keybindings-store-BScuugqK.js +1 -0
- package/dist/web/assets/{line--xyfYP3x.js → line-DBLLF7lH.js} +1 -1
- package/dist/web/assets/{linear-BdqW7iQu.js → linear-BLFWatDe.js} +1 -1
- package/dist/web/assets/{markdown-renderer-oHkpw_nC.js → markdown-renderer-DwmzGpNI.js} +5 -5
- package/dist/web/assets/{mermaid-parser.core-BY8JfkE_.js → mermaid-parser.core-BKiGOTjR.js} +2 -2
- package/dist/web/assets/{mindmap-definition-YRQLILUH-DIv-LMXG.js → mindmap-definition-YRQLILUH-DoT7m4Sz.js} +1 -1
- package/dist/web/assets/{ordinal-CIoJK3nc.js → ordinal-CCj7PWgZ.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-CdYSLjRL.js +1 -0
- package/dist/web/assets/pie-UPGHQEXC-Bm5LiD-6.js +1 -0
- package/dist/web/assets/{pieDiagram-SKSYHLDU-seSK40d1.js → pieDiagram-SKSYHLDU-Bkh2E4zE.js} +1 -1
- package/dist/web/assets/postgres-viewer-Bp6mOne8.js +1 -0
- package/dist/web/assets/{quadrantDiagram-337W2JSQ-BaRFqlsA.js → quadrantDiagram-337W2JSQ-B7zgALOL.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-C4PnyG7_.js +1 -0
- package/dist/web/assets/{requirementDiagram-Z7DCOOCP-1WWjMQB_.js → requirementDiagram-Z7DCOOCP-D_5GXNRo.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-WA2Y5GQK-DEGGYsk7.js → sankeyDiagram-WA2Y5GQK-BA9EFAAe.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-2WXFIKYE-BtRvoUTC.js → sequenceDiagram-2WXFIKYE-fyWIrHiG.js} +1 -1
- package/dist/web/assets/{settings-store-D3dJqGhB.js → settings-store-Bbhg_ptG.js} +2 -2
- package/dist/web/assets/settings-tab-8lfbaK4W.js +1 -0
- package/dist/web/assets/sqlite-viewer-Cenucoym.js +1 -0
- package/dist/web/assets/{stateDiagram-RAJIS63D-C16aO8tn.js → stateDiagram-RAJIS63D-DfRBcaBu.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-CMN4M2Em.js +1 -0
- package/dist/web/assets/{tab-store-DSz5PQI0.js → tab-store-DcIBZTD4.js} +1 -1
- package/dist/web/assets/{terminal-tab-CxJ3m9tD.js → terminal-tab-B0TAHXjw.js} +1 -1
- package/dist/web/assets/{timeline-definition-YZTLITO2-DrjxCpEM.js → timeline-definition-YZTLITO2-DYfwJ1jM.js} +1 -1
- package/dist/web/assets/treemap-KZPCXAKY-2_y-mhkz.js +1 -0
- package/dist/web/assets/{use-monaco-theme-BQzvItNE.js → use-monaco-theme-vwto-Vlf.js} +1 -1
- package/dist/web/assets/{vennDiagram-LZ73GAT5-DfYFnniI.js → vennDiagram-LZ73GAT5-DqbKNRD9.js} +1 -1
- package/dist/web/assets/{xychartDiagram-JWTSCODW-BRvXOVlG.js → xychartDiagram-JWTSCODW-DhUL86qT.js} +1 -1
- package/dist/web/index.html +10 -12
- package/dist/web/sw.js +1 -1
- package/docs/project-roadmap.md +41 -19
- package/package.json +1 -1
- package/src/server/index.ts +4 -1
- package/src/server/routes/browser-preview.ts +135 -65
- package/src/web/components/browser/browser-tab.tsx +105 -224
- package/src/web/components/chat/message-input.tsx +69 -2
- package/src/web/components/layout/command-palette.tsx +2 -0
- package/src/web/hooks/use-global-keybindings.ts +7 -0
- package/src/web/hooks/use-voice-input.ts +111 -0
- package/src/web/stores/keybindings-store.ts +1 -0
- package/dist/web/assets/architecture-PBZL5I3N-281eTKQ3.js +0 -1
- package/dist/web/assets/arrow-left-C_j9Ki73.js +0 -1
- package/dist/web/assets/browser-tab-CsZFFI1C.js +0 -1
- package/dist/web/assets/channel-CKNZAqoN.js +0 -1
- package/dist/web/assets/chat-tab-WYQKXiDW.js +0 -7
- package/dist/web/assets/chunk-GLR3WWYH-Bx2UL5jF.js +0 -2
- package/dist/web/assets/chunk-HHEYEP7N-BnRVfNc5.js +0 -1
- package/dist/web/assets/chunk-QZHKN3VN-gaBt0Rbd.js +0 -1
- package/dist/web/assets/classDiagram-VBA2DB6C-CqaIqYPn.js +0 -1
- package/dist/web/assets/classDiagram-v2-RAHNMMFH-Bo5WN2ok.js +0 -1
- package/dist/web/assets/clone-DNDy9Sms.js +0 -1
- package/dist/web/assets/database-viewer-TYwvlW4u.js +0 -1
- package/dist/web/assets/git-graph-mtdNxBZs.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-D5qEPjgs.js +0 -1
- package/dist/web/assets/index-CYhfwlmi.js +0 -37
- package/dist/web/assets/index-n0Ww6i6b.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-CbpovIYU.js +0 -1
- package/dist/web/assets/infoDiagram-LFFYTUFH-DFh9c-S2.js +0 -2
- package/dist/web/assets/input-DGlv6gt_.js +0 -41
- package/dist/web/assets/keybindings-store-DA8at4_B.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-BbzPU9BK.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-B0h6hM1j.js +0 -1
- package/dist/web/assets/postgres-viewer-XnXGFIcT.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-CHptMqVT.js +0 -1
- package/dist/web/assets/settings-tab-t--MmXOo.js +0 -1
- package/dist/web/assets/sqlite-viewer-Zm20Z3Ys.js +0 -1
- package/dist/web/assets/stateDiagram-v2-FVOUBMTO-D7qSAjnK.js +0 -1
- package/dist/web/assets/switch-goUjvGec.js +0 -1
- package/dist/web/assets/treemap-KZPCXAKY-BL9OJq3X.js +0 -1
- /package/dist/web/assets/{api-client-icCZ-07C.js → api-client-DpGMOZNf.js} +0 -0
- /package/dist/web/assets/{array-CLwNaqU1.js → array-BGFCBI0e.js} +0 -0
- /package/dist/web/assets/{columns-2-Bcg3QJBg.js → columns-2-ChOTgl3e.js} +0 -0
- /package/dist/web/assets/{cytoscape.esm-B-QQuWwK.js → cytoscape.esm-Ccan6xou.js} +0 -0
- /package/dist/web/assets/{defaultLocale-D_VMtRaY.js → defaultLocale-CRZydyG6.js} +0 -0
- /package/dist/web/assets/{dist-Ckxnw5rl.js → dist-Cce3efmT.js} +0 -0
- /package/dist/web/assets/{dist-CMmNEgEP.js → dist-T0Vhi0Mh.js} +0 -0
- /package/dist/web/assets/{init-vVpfz1D6.js → init-B8gtcn7T.js} +0 -0
- /package/dist/web/assets/{isArrayLikeObject-DvHDmeBe.js → isArrayLikeObject-B4pdpV8V.js} +0 -0
- /package/dist/web/assets/{katex-C3cZrCvP.js → katex-Bbu770d9.js} +0 -0
- /package/dist/web/assets/{math-a44lmFDa.js → math-DwgHI-Cu.js} +0 -0
- /package/dist/web/assets/{path-CuyvWNAH.js → path-DZF-JdEe.js} +0 -0
- /package/dist/web/assets/{preload-helper-CsoeaaUJ.js → preload-helper-qlgyTAkD.js} +0 -0
- /package/dist/web/assets/{react-BPIfZRKM.js → react-BGf7KNLk.js} +0 -0
- /package/dist/web/assets/{rough.esm-c4PR5shF.js → rough.esm-VLpapkIG.js} +0 -0
- /package/dist/web/assets/{src-CLWraeNW.js → src-BoSBNdA_.js} +0 -0
- /package/dist/web/assets/{table-C9jDaRl2.js → table-Yo02WRH-.js} +0 -0
- /package/dist/web/assets/{tag-CENGyt_L.js → tag-CaC1ng2E.js} +0 -0
- /package/dist/web/assets/{utils-Bslrbb-G.js → utils-btZ8C8-R.js} +0 -0
package/dist/web/index.html
CHANGED
|
@@ -39,21 +39,19 @@
|
|
|
39
39
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
40
40
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
41
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" />
|
|
42
|
-
<script type="module" crossorigin src="/assets/index-
|
|
42
|
+
<script type="module" crossorigin src="/assets/index-DmfRyMpE.js"></script>
|
|
43
43
|
<link rel="modulepreload" crossorigin href="/assets/chunk-CFjPhJqf.js">
|
|
44
|
-
<link rel="modulepreload" crossorigin href="/assets/preload-helper-
|
|
45
|
-
<link rel="modulepreload" crossorigin href="/assets/utils-
|
|
44
|
+
<link rel="modulepreload" crossorigin href="/assets/preload-helper-qlgyTAkD.js">
|
|
45
|
+
<link rel="modulepreload" crossorigin href="/assets/utils-btZ8C8-R.js">
|
|
46
46
|
<link rel="modulepreload" crossorigin href="/assets/react-nm2Ru1Pt.js">
|
|
47
47
|
<link rel="modulepreload" crossorigin href="/assets/jsx-runtime-BRW_vwa9.js">
|
|
48
|
-
<link rel="modulepreload" crossorigin href="/assets/input-
|
|
49
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
50
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
51
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
52
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
53
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
54
|
-
<link rel="
|
|
55
|
-
<link rel="modulepreload" crossorigin href="/assets/settings-store-D3dJqGhB.js">
|
|
56
|
-
<link rel="stylesheet" crossorigin href="/assets/index-n0Ww6i6b.css">
|
|
48
|
+
<link rel="modulepreload" crossorigin href="/assets/input-Brjz2Vv-.js">
|
|
49
|
+
<link rel="modulepreload" crossorigin href="/assets/react-BGf7KNLk.js">
|
|
50
|
+
<link rel="modulepreload" crossorigin href="/assets/tab-store-DcIBZTD4.js">
|
|
51
|
+
<link rel="modulepreload" crossorigin href="/assets/api-client-DpGMOZNf.js">
|
|
52
|
+
<link rel="modulepreload" crossorigin href="/assets/api-settings--eVrUeZM.js">
|
|
53
|
+
<link rel="modulepreload" crossorigin href="/assets/settings-store-Bbhg_ptG.js">
|
|
54
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Beb248lR.css">
|
|
57
55
|
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
|
58
56
|
<body class="bg-[#0f1419] text-[#e5e7eb] font-sans antialiased">
|
|
59
57
|
<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":"2b981b018b202252559a4f14445b82bd","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/xychartDiagram-JWTSCODW-BRvXOVlG.js"},{"revision":null,"url":"assets/vennDiagram-LZ73GAT5-DfYFnniI.js"},{"revision":null,"url":"assets/utils-Bslrbb-G.js"},{"revision":null,"url":"assets/use-monaco-theme-BQzvItNE.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-BL9OJq3X.js"},{"revision":null,"url":"assets/timeline-definition-YZTLITO2-DrjxCpEM.js"},{"revision":null,"url":"assets/terminal-tab-CxJ3m9tD.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/tag-CENGyt_L.js"},{"revision":null,"url":"assets/table-C9jDaRl2.js"},{"revision":null,"url":"assets/tab-store-DSz5PQI0.js"},{"revision":null,"url":"assets/switch-goUjvGec.js"},{"revision":null,"url":"assets/stateDiagram-v2-FVOUBMTO-D7qSAjnK.js"},{"revision":null,"url":"assets/stateDiagram-RAJIS63D-C16aO8tn.js"},{"revision":null,"url":"assets/src-CLWraeNW.js"},{"revision":null,"url":"assets/sqlite-viewer-Zm20Z3Ys.js"},{"revision":null,"url":"assets/settings-tab-t--MmXOo.js"},{"revision":null,"url":"assets/settings-store-D3dJqGhB.js"},{"revision":null,"url":"assets/sequenceDiagram-2WXFIKYE-BtRvoUTC.js"},{"revision":null,"url":"assets/sankeyDiagram-WA2Y5GQK-DEGGYsk7.js"},{"revision":null,"url":"assets/rough.esm-c4PR5shF.js"},{"revision":null,"url":"assets/requirementDiagram-Z7DCOOCP-1WWjMQB_.js"},{"revision":null,"url":"assets/react-nm2Ru1Pt.js"},{"revision":null,"url":"assets/react-BPIfZRKM.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-CHptMqVT.js"},{"revision":null,"url":"assets/quadrantDiagram-337W2JSQ-BaRFqlsA.js"},{"revision":null,"url":"assets/preload-helper-CsoeaaUJ.js"},{"revision":null,"url":"assets/postgres-viewer-XnXGFIcT.js"},{"revision":null,"url":"assets/pieDiagram-SKSYHLDU-seSK40d1.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-B0h6hM1j.js"},{"revision":null,"url":"assets/path-CuyvWNAH.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-BbzPU9BK.js"},{"revision":null,"url":"assets/ordinal-CIoJK3nc.js"},{"revision":null,"url":"assets/mindmap-definition-YRQLILUH-DIv-LMXG.js"},{"revision":null,"url":"assets/mermaid-parser.core-BY8JfkE_.js"},{"revision":null,"url":"assets/math-a44lmFDa.js"},{"revision":null,"url":"assets/markdown-renderer-oHkpw_nC.js"},{"revision":null,"url":"assets/linear-BdqW7iQu.js"},{"revision":null,"url":"assets/line--xyfYP3x.js"},{"revision":null,"url":"assets/keybindings-store-DA8at4_B.js"},{"revision":null,"url":"assets/katex-C3cZrCvP.js"},{"revision":null,"url":"assets/kanban-definition-K7BYSVSG-BMUhjxqj.js"},{"revision":null,"url":"assets/jsx-runtime-BRW_vwa9.js"},{"revision":null,"url":"assets/journeyDiagram-4ABVD52K-DFQXUZsc.js"},{"revision":null,"url":"assets/ishikawaDiagram-PHBUUO56-cW7SMLa_.js"},{"revision":null,"url":"assets/isEmpty-DXomfd7J.js"},{"revision":null,"url":"assets/isArrayLikeObject-DvHDmeBe.js"},{"revision":null,"url":"assets/input-DGlv6gt_.js"},{"revision":null,"url":"assets/init-vVpfz1D6.js"},{"revision":null,"url":"assets/infoDiagram-LFFYTUFH-DFh9c-S2.js"},{"revision":null,"url":"assets/info-3K5VOQVL-CbpovIYU.js"},{"revision":null,"url":"assets/index-n0Ww6i6b.css"},{"revision":null,"url":"assets/index-CYhfwlmi.js"},{"revision":null,"url":"assets/graphlib-BbbiUImY.js"},{"revision":null,"url":"assets/gitGraphDiagram-K3NZZRJ6-L7sj3Bs-.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-D5qEPjgs.js"},{"revision":null,"url":"assets/git-graph-mtdNxBZs.js"},{"revision":null,"url":"assets/ganttDiagram-A5KZAMGK-BVlftqyZ.js"},{"revision":null,"url":"assets/flowDiagram-PKNHOUZH-B9h_Ba-v.js"},{"revision":null,"url":"assets/erDiagram-INFDFZHY-DK4QEZYh.js"},{"revision":null,"url":"assets/dist-Ckxnw5rl.js"},{"revision":null,"url":"assets/dist-CMmNEgEP.js"},{"revision":null,"url":"assets/diff-viewer-DApETeeX.js"},{"revision":null,"url":"assets/diagram-P4PSJMXO--nUaNiyB.js"},{"revision":null,"url":"assets/diagram-IFDJBPK2-NvhckwcA.js"},{"revision":null,"url":"assets/diagram-E7M64L7V-CkDC2uAj.js"},{"revision":null,"url":"assets/defaultLocale-D_VMtRaY.js"},{"revision":null,"url":"assets/database-viewer-TYwvlW4u.js"},{"revision":null,"url":"assets/dagre-KLK3FWXG-Br4t5TRV.js"},{"revision":null,"url":"assets/dagre-CWo8w9wK.js"},{"revision":null,"url":"assets/cytoscape.esm-B-QQuWwK.js"},{"revision":null,"url":"assets/cose-bilkent-S5V4N54A-C1QJ6GPW.js"},{"revision":null,"url":"assets/columns-2-Bcg3QJBg.js"},{"revision":null,"url":"assets/code-editor-C6umJOvn.js"},{"revision":null,"url":"assets/clone-DNDy9Sms.js"},{"revision":null,"url":"assets/classDiagram-v2-RAHNMMFH-Bo5WN2ok.js"},{"revision":null,"url":"assets/classDiagram-VBA2DB6C-CqaIqYPn.js"},{"revision":null,"url":"assets/chunk-YBOYWFTD-COdZIaX4.js"},{"revision":null,"url":"assets/chunk-XZSTWKYB-5W2emiq4.js"},{"revision":null,"url":"assets/chunk-XPW4576I-CsGTseUr.js"},{"revision":null,"url":"assets/chunk-XIRO2GV7-DUmQrLsF.js"},{"revision":null,"url":"assets/chunk-WL4C6EOR-BHFnnXOt.js"},{"revision":null,"url":"assets/chunk-R5LLSJPH-LdG7RqsM.js"},{"revision":null,"url":"assets/chunk-QZHKN3VN-gaBt0Rbd.js"},{"revision":null,"url":"assets/chunk-PU5JKC2W-D3thuSok.js"},{"revision":null,"url":"assets/chunk-PQ6SQG4A-EdgQyTqa.js"},{"revision":null,"url":"assets/chunk-OZEHJAEY-LfXT4p8B.js"},{"revision":null,"url":"assets/chunk-O4XLMI2P-BdXwVXjJ.js"},{"revision":null,"url":"assets/chunk-NQ4KR5QH-BRj25yO7.js"},{"revision":null,"url":"assets/chunk-MX3YWQON-BnPzQK-O.js"},{"revision":null,"url":"assets/chunk-L3YUKLVL-DNFj84V6.js"},{"revision":null,"url":"assets/chunk-KYZI473N-CBRPKraG.js"},{"revision":null,"url":"assets/chunk-KX2RTZJC-BRj-ZEvL.js"},{"revision":null,"url":"assets/chunk-JSJVCQXG-Pb-JMOgO.js"},{"revision":null,"url":"assets/chunk-HHEYEP7N-BnRVfNc5.js"},{"revision":null,"url":"assets/chunk-GLR3WWYH-Bx2UL5jF.js"},{"revision":null,"url":"assets/chunk-GEFDOKGD-B82RP9ow.js"},{"revision":null,"url":"assets/chunk-FMBD7UC4-JCLgVcaC.js"},{"revision":null,"url":"assets/chunk-EGIJ26TM-Cgt-qg75.js"},{"revision":null,"url":"assets/chunk-CFjPhJqf.js"},{"revision":null,"url":"assets/chunk-C72U2L5F-CcEW1AMZ.js"},{"revision":null,"url":"assets/chunk-7R4GIKGN-DvbvLUIN.js"},{"revision":null,"url":"assets/chunk-7E7YKBS2-CuYKSUgJ.js"},{"revision":null,"url":"assets/chunk-55IACEB6-Dp0pTM5r.js"},{"revision":null,"url":"assets/chunk-4BX2VUAB-0YMkpW2S.js"},{"revision":null,"url":"assets/chat-tab-WYQKXiDW.js"},{"revision":null,"url":"assets/channel-CKNZAqoN.js"},{"revision":null,"url":"assets/c4Diagram-IC4MRINW-DzjR91sM.js"},{"revision":null,"url":"assets/browser-tab-CsZFFI1C.js"},{"revision":null,"url":"assets/blockDiagram-WCTKOSBZ-CU2t4NHJ.js"},{"revision":null,"url":"assets/arrow-left-C_j9Ki73.js"},{"revision":null,"url":"assets/array-CLwNaqU1.js"},{"revision":null,"url":"assets/architectureDiagram-2XIMDMQ5-BVEUkQYB.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-281eTKQ3.js"},{"revision":null,"url":"assets/arc-D0bJaFyD.js"},{"revision":null,"url":"assets/api-settings-CuUkz5gb.js"},{"revision":null,"url":"assets/api-client-icCZ-07C.js"},{"revision":null,"url":"assets/_baseUniq-DCb0mkTp.js"},{"revision":null,"url":"assets/_basePickBy-COwDPZl_.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":"4c21669b41e8898795a580d086427e56","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/xychartDiagram-JWTSCODW-DhUL86qT.js"},{"revision":null,"url":"assets/vennDiagram-LZ73GAT5-DqbKNRD9.js"},{"revision":null,"url":"assets/utils-btZ8C8-R.js"},{"revision":null,"url":"assets/use-monaco-theme-vwto-Vlf.js"},{"revision":null,"url":"assets/treemap-KZPCXAKY-2_y-mhkz.js"},{"revision":null,"url":"assets/timeline-definition-YZTLITO2-DYfwJ1jM.js"},{"revision":null,"url":"assets/terminal-tab-BrP-ENHg.css"},{"revision":null,"url":"assets/terminal-tab-B0TAHXjw.js"},{"revision":null,"url":"assets/tag-CaC1ng2E.js"},{"revision":null,"url":"assets/table-Yo02WRH-.js"},{"revision":null,"url":"assets/tab-store-DcIBZTD4.js"},{"revision":null,"url":"assets/stateDiagram-v2-FVOUBMTO-CMN4M2Em.js"},{"revision":null,"url":"assets/stateDiagram-RAJIS63D-DfRBcaBu.js"},{"revision":null,"url":"assets/src-BoSBNdA_.js"},{"revision":null,"url":"assets/sqlite-viewer-Cenucoym.js"},{"revision":null,"url":"assets/settings-tab-8lfbaK4W.js"},{"revision":null,"url":"assets/settings-store-Bbhg_ptG.js"},{"revision":null,"url":"assets/sequenceDiagram-2WXFIKYE-fyWIrHiG.js"},{"revision":null,"url":"assets/sankeyDiagram-WA2Y5GQK-BA9EFAAe.js"},{"revision":null,"url":"assets/rough.esm-VLpapkIG.js"},{"revision":null,"url":"assets/requirementDiagram-Z7DCOOCP-D_5GXNRo.js"},{"revision":null,"url":"assets/react-nm2Ru1Pt.js"},{"revision":null,"url":"assets/react-BGf7KNLk.js"},{"revision":null,"url":"assets/radar-KQ55EAFF-C4PnyG7_.js"},{"revision":null,"url":"assets/quadrantDiagram-337W2JSQ-B7zgALOL.js"},{"revision":null,"url":"assets/preload-helper-qlgyTAkD.js"},{"revision":null,"url":"assets/postgres-viewer-Bp6mOne8.js"},{"revision":null,"url":"assets/pieDiagram-SKSYHLDU-Bkh2E4zE.js"},{"revision":null,"url":"assets/pie-UPGHQEXC-Bm5LiD-6.js"},{"revision":null,"url":"assets/path-DZF-JdEe.js"},{"revision":null,"url":"assets/packet-RMMSAZCW-CdYSLjRL.js"},{"revision":null,"url":"assets/ordinal-CCj7PWgZ.js"},{"revision":null,"url":"assets/mindmap-definition-YRQLILUH-DoT7m4Sz.js"},{"revision":null,"url":"assets/mermaid-parser.core-BKiGOTjR.js"},{"revision":null,"url":"assets/math-DwgHI-Cu.js"},{"revision":null,"url":"assets/markdown-renderer-DwmzGpNI.js"},{"revision":null,"url":"assets/linear-BLFWatDe.js"},{"revision":null,"url":"assets/line-DBLLF7lH.js"},{"revision":null,"url":"assets/keybindings-store-BScuugqK.js"},{"revision":null,"url":"assets/katex-Bbu770d9.js"},{"revision":null,"url":"assets/kanban-definition-K7BYSVSG-BBXbI37U.js"},{"revision":null,"url":"assets/jsx-runtime-BRW_vwa9.js"},{"revision":null,"url":"assets/journeyDiagram-4ABVD52K-CttDH9bb.js"},{"revision":null,"url":"assets/ishikawaDiagram-PHBUUO56-olazD6dZ.js"},{"revision":null,"url":"assets/isEmpty-C0YYdhYj.js"},{"revision":null,"url":"assets/isArrayLikeObject-B4pdpV8V.js"},{"revision":null,"url":"assets/input-Brjz2Vv-.js"},{"revision":null,"url":"assets/init-B8gtcn7T.js"},{"revision":null,"url":"assets/infoDiagram-LFFYTUFH-BzqyoqXw.js"},{"revision":null,"url":"assets/info-3K5VOQVL-ce_pi3En.js"},{"revision":null,"url":"assets/index-DmfRyMpE.js"},{"revision":null,"url":"assets/index-Beb248lR.css"},{"revision":null,"url":"assets/graphlib-DhOZxqsh.js"},{"revision":null,"url":"assets/gitGraphDiagram-K3NZZRJ6-yEWZbdf_.js"},{"revision":null,"url":"assets/gitGraph-HDMCJU4V-CEee2FCA.js"},{"revision":null,"url":"assets/git-graph-Bx3h7BK1.js"},{"revision":null,"url":"assets/ganttDiagram-A5KZAMGK-DIX0pLbk.js"},{"revision":null,"url":"assets/flowDiagram-PKNHOUZH-14ohZ1M1.js"},{"revision":null,"url":"assets/erDiagram-INFDFZHY-mCvUFSn6.js"},{"revision":null,"url":"assets/dist-T0Vhi0Mh.js"},{"revision":null,"url":"assets/dist-Cce3efmT.js"},{"revision":null,"url":"assets/diff-viewer-DrTqG6RM.js"},{"revision":null,"url":"assets/diagram-P4PSJMXO-D1eW1dkL.js"},{"revision":null,"url":"assets/diagram-IFDJBPK2-ChB_paPo.js"},{"revision":null,"url":"assets/diagram-E7M64L7V-CzKYZM0Y.js"},{"revision":null,"url":"assets/defaultLocale-CRZydyG6.js"},{"revision":null,"url":"assets/database-viewer-h1Zb9cFF.js"},{"revision":null,"url":"assets/dagre-KLK3FWXG-ChenfPp1.js"},{"revision":null,"url":"assets/dagre-CNtSxiE_.js"},{"revision":null,"url":"assets/cytoscape.esm-Ccan6xou.js"},{"revision":null,"url":"assets/cose-bilkent-S5V4N54A-CHHjH2dV.js"},{"revision":null,"url":"assets/columns-2-ChOTgl3e.js"},{"revision":null,"url":"assets/code-editor-EG9sb3gL.js"},{"revision":null,"url":"assets/clone-BSi6cgDh.js"},{"revision":null,"url":"assets/classDiagram-v2-RAHNMMFH-Bj8gIhkP.js"},{"revision":null,"url":"assets/classDiagram-VBA2DB6C-BpJ6Oog2.js"},{"revision":null,"url":"assets/chunk-YBOYWFTD-Dx_fX35n.js"},{"revision":null,"url":"assets/chunk-XZSTWKYB-BYxFzZwS.js"},{"revision":null,"url":"assets/chunk-XPW4576I-CtcaMb09.js"},{"revision":null,"url":"assets/chunk-XIRO2GV7-kqQ0g6wW.js"},{"revision":null,"url":"assets/chunk-WL4C6EOR-_2CBOJdI.js"},{"revision":null,"url":"assets/chunk-R5LLSJPH-euR2RxLN.js"},{"revision":null,"url":"assets/chunk-QZHKN3VN-DwSXwtjH.js"},{"revision":null,"url":"assets/chunk-PU5JKC2W-B66ELkQm.js"},{"revision":null,"url":"assets/chunk-PQ6SQG4A-BxtUGYhW.js"},{"revision":null,"url":"assets/chunk-OZEHJAEY-YTn24bGg.js"},{"revision":null,"url":"assets/chunk-O4XLMI2P-BurQy8tt.js"},{"revision":null,"url":"assets/chunk-NQ4KR5QH-DLrZwBEm.js"},{"revision":null,"url":"assets/chunk-MX3YWQON-BgjSEzus.js"},{"revision":null,"url":"assets/chunk-L3YUKLVL-3wBgkSvL.js"},{"revision":null,"url":"assets/chunk-KYZI473N-BKO5gMeU.js"},{"revision":null,"url":"assets/chunk-KX2RTZJC-BCxGmbzy.js"},{"revision":null,"url":"assets/chunk-JSJVCQXG-BSrqCL_3.js"},{"revision":null,"url":"assets/chunk-HHEYEP7N-Dld5BpGB.js"},{"revision":null,"url":"assets/chunk-GLR3WWYH-D9pZakqr.js"},{"revision":null,"url":"assets/chunk-GEFDOKGD-DwVPiYfW.js"},{"revision":null,"url":"assets/chunk-FMBD7UC4-C_1aG0eb.js"},{"revision":null,"url":"assets/chunk-EGIJ26TM-D0KJTa_T.js"},{"revision":null,"url":"assets/chunk-CFjPhJqf.js"},{"revision":null,"url":"assets/chunk-C72U2L5F-DOtEiN5f.js"},{"revision":null,"url":"assets/chunk-7R4GIKGN-DXaGAn_K.js"},{"revision":null,"url":"assets/chunk-7E7YKBS2-6xAQfBwa.js"},{"revision":null,"url":"assets/chunk-55IACEB6-C4mUdyio.js"},{"revision":null,"url":"assets/chunk-4BX2VUAB-BptTlTyl.js"},{"revision":null,"url":"assets/chat-tab--hD0r5RS.js"},{"revision":null,"url":"assets/channel-w7yboq56.js"},{"revision":null,"url":"assets/c4Diagram-IC4MRINW-BNP2L9r_.js"},{"revision":null,"url":"assets/browser-tab-CjjWgPDL.js"},{"revision":null,"url":"assets/blockDiagram-WCTKOSBZ-CKGufRTy.js"},{"revision":null,"url":"assets/array-BGFCBI0e.js"},{"revision":null,"url":"assets/architectureDiagram-2XIMDMQ5-Jq91S_rs.js"},{"revision":null,"url":"assets/architecture-PBZL5I3N-ChOahOB7.js"},{"revision":null,"url":"assets/arc-C2Qaz-ch.js"},{"revision":null,"url":"assets/api-settings--eVrUeZM.js"},{"revision":null,"url":"assets/api-client-DpGMOZNf.js"},{"revision":null,"url":"assets/_baseUniq-ClnvscgW.js"},{"revision":null,"url":"assets/_basePickBy-CZovQgWd.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/docs/project-roadmap.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# PPM Project Roadmap
|
|
2
2
|
|
|
3
|
-
**Last Updated:** March
|
|
3
|
+
**Last Updated:** March 27, 2026
|
|
4
4
|
|
|
5
5
|
## Vision
|
|
6
6
|
|
|
@@ -56,19 +56,26 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
|
|
|
56
56
|
|
|
57
57
|
### v0.9.0 — "Open Platform" (Q2–Q3 2026)
|
|
58
58
|
|
|
59
|
-
**Theme:** Multi-provider AI + extension system.
|
|
59
|
+
**Theme:** Multi-provider AI (Claude + Cursor) + extension system. Ship a focused release, expand providers later.
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|---------|----------|-------------|
|
|
63
|
-
| **Multi-provider AI** | Critical | Refactor `ProviderInterface` for clean provider abstraction. Tiered support: Tier 1 (full agentic) = Claude Agent SDK; Tier 2 (chat + tools) = Gemini CLI, OpenAI Codex; Tier 3 (chat-only) = any OpenAI-compatible API. Clean base code for future Chinese providers (DeepSeek, Qwen). |
|
|
64
|
-
| **Extension architecture** | High | Dynamic extension loading system. Extensions = npm packages exporting skills + optional UI panels. First extension: extract DB viewer from core. Extension API: register routes, UI panels, sidebar tabs, skills. Config: `"extensions": ["@ppm/ext-database", "@ppm/ext-docker"]`. |
|
|
65
|
-
| **MCP Management** | Medium | UI to add/remove/configure MCP servers. Test connection. Per-project MCP overrides. Store in SQLite. Pass to Agent SDK via `mcpServers`. |
|
|
61
|
+
**Overall progress: ~40%** (1/3 features complete, merge all at 100%)
|
|
66
62
|
|
|
67
|
-
|
|
63
|
+
| Feature | Priority | Status | Description |
|
|
64
|
+
|---------|----------|--------|-------------|
|
|
65
|
+
| **Multi-provider AI** | Critical | ✅ Done | ProviderInterface, registry, Cursor CLI, CLI provider base, UI provider/model selector, permission mode selector, system prompt customization, comprehensive tests — all on beta branch. |
|
|
66
|
+
| **Extension architecture** | High | 🔴 0% | Dynamic extension loading. Extensions = npm packages. First extension: extract DB viewer from core. Extension API: register routes, UI panels, sidebar tabs, skills. Config: `"extensions": ["@ppm/ext-database", "@ppm/ext-docker"]`. |
|
|
67
|
+
| **MCP Management** | Medium | 🔴 0% | UI to add/remove/configure MCP servers. Test connection. Per-project MCP overrides. Store in SQLite. Pass to Agent SDK via `mcpServers`. |
|
|
68
|
+
|
|
69
|
+
**Multi-provider — v0.9 scope (reduced):**
|
|
68
70
|
- Tier 1 (full agentic): Claude Agent SDK — file edit, terminal, git, full autonomy
|
|
69
|
-
- Tier 2 (
|
|
70
|
-
-
|
|
71
|
-
|
|
71
|
+
- Tier 2 (agentic CLI): Cursor — agentic via its own tool system
|
|
72
|
+
- Provider interface is clean enough to add more providers later without refactor
|
|
73
|
+
|
|
74
|
+
**Deferred to v0.9.5+:**
|
|
75
|
+
- Gemini CLI (Tier 2)
|
|
76
|
+
- OpenAI Codex (Tier 2)
|
|
77
|
+
- Tier 3 (chat-only): Any OpenAI-compatible API
|
|
78
|
+
- Chinese providers (DeepSeek, Qwen) — v1.0+
|
|
72
79
|
|
|
73
80
|
**Extension architecture — design principles:**
|
|
74
81
|
- Extensions are npm packages: `ppm ext install @ppm/ext-database`
|
|
@@ -79,15 +86,27 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
|
|
|
79
86
|
|
|
80
87
|
---
|
|
81
88
|
|
|
82
|
-
### v0.10.0 — "
|
|
89
|
+
### v0.10.0 — "Enhanced Workflow" (Q3 2026)
|
|
83
90
|
|
|
84
|
-
**Theme:**
|
|
91
|
+
**Theme:** Chat UX upgrade + git workflow. High-impact, independent features that ship fast.
|
|
85
92
|
|
|
86
93
|
| Feature | Priority | Description |
|
|
87
94
|
|---------|----------|-------------|
|
|
95
|
+
| **Chat history graph** | High | Visual branching tree of chat sessions. Fork conversations, navigate history graph. Game-changer for AI chat UX. |
|
|
96
|
+
| **Worktree management** | Medium | UI to create/switch/delete git worktrees. Use different providers on different branches. Integrated with project switcher. |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### v0.11.0 — "Intelligence" (Q3–Q4 2026)
|
|
101
|
+
|
|
102
|
+
**Theme:** Event system + PPM's own AI layer. Hooks → Skills API → Clawbot dependency chain.
|
|
103
|
+
|
|
104
|
+
| Feature | Priority | Description |
|
|
105
|
+
|---------|----------|-------------|
|
|
106
|
+
| **Hooks system** | High | Event hooks for PPM lifecycle (file save, git commit, chat message, etc.). Foundation for Skills API and deeper extension integration. |
|
|
88
107
|
| **PPM Skills API** | High | Stable internal API for AI to control PPM: file.read/write/search, terminal.run, git.status/commit/diff, db.query, editor.open/goto, project.switch. Skills are the bridge between AI and PPM features. |
|
|
89
108
|
| **Built-in Clawbot** | High | Lightweight AI agent built into PPM using Anthropic Messages API (not Agent SDK). Uses Skills API + MCP tools. Instant response, no external CLI deps. For quick tasks: file search, code explanation, simple refactors. |
|
|
90
|
-
| **
|
|
109
|
+
| **More providers** | Medium | Gemini CLI (Tier 2), OpenAI Codex (Tier 2), Tier 3 chat-only (any OpenAI-compatible API). Provider interface already clean from v0.9. |
|
|
91
110
|
|
|
92
111
|
**Built-in Clawbot — why it matters:**
|
|
93
112
|
- Claude Agent SDK spawns subprocess — heavy, slow startup, requires CLI installed
|
|
@@ -106,6 +125,7 @@ PPM is the **lightest path from phone to code** — a self-hosted, BYOK, multi-d
|
|
|
106
125
|
| **Self-hosted PPM Cloud** | High | Docker image of PPM Cloud for enterprise/team. Same codebase, self-hosted config flag. `docker-compose up` and it works. LDAP/SSO integration. |
|
|
107
126
|
| **PPM Marketplace** | High | Publish/install/update extensions. Browse community extensions. Revenue sharing for paid extensions. Clawbot can create extension → test → publish in minutes. |
|
|
108
127
|
| **Stability & hardening** | Critical | Security audit, performance optimization, comprehensive test coverage (>80%), documentation for contributors, CI/CD pipeline. |
|
|
128
|
+
| **Inline SQL** | Medium | Select text in Monaco → run as SQL. Connection picker in editor context menu. Results panel below editor. Leverages existing DB service. |
|
|
109
129
|
|
|
110
130
|
---
|
|
111
131
|
|
|
@@ -130,6 +150,7 @@ Features to pick from after v1.0. Will be reviewed and scheduled based on user f
|
|
|
130
150
|
| **Cross-platform binaries** | Distribution | Compile macOS/Linux/Windows binaries via `bun build --compile`. `npx ppm` without Bun. |
|
|
131
151
|
| **OLED dark mode** | UX | True black background for OLED screens. |
|
|
132
152
|
| **Collaborative editing** | Social | Real-time multi-user file editing with CRDT (yjs/automerge). |
|
|
153
|
+
| **Custom domain** | Cloud | Map custom domain to PPM Cloud tunnel URL. DNS CNAME + SSL via Let's Encrypt or Cloudflare. Access PPM at `code.yourdomain.com`. |
|
|
133
154
|
|
|
134
155
|
---
|
|
135
156
|
|
|
@@ -139,9 +160,10 @@ Features to pick from after v1.0. Will be reviewed and scheduled based on user f
|
|
|
139
160
|
|---------|-------|-------------|--------|
|
|
140
161
|
| **v0.7** | Multi-Account & Mobile | Account management, usage tracking, mobile UX | ✅ Current |
|
|
141
162
|
| **v0.8** | Always On | PPM Cloud, auto-start, AI chat enhancements | Q2 2026 |
|
|
142
|
-
| **v0.9** | Open Platform | Multi-provider
|
|
143
|
-
| **v0.10** |
|
|
144
|
-
| **
|
|
163
|
+
| **v0.9** | Open Platform | Multi-provider (Claude + Cursor), extension architecture, MCP | Q2–Q3 2026 |
|
|
164
|
+
| **v0.10** | Enhanced Workflow | Chat history graph, worktree management | Q3 2026 |
|
|
165
|
+
| **v0.11** | Intelligence | Hooks, Skills API, Clawbot, more providers (Gemini/Codex/Tier 3) | Q3–Q4 2026 |
|
|
166
|
+
| **v1.0** | Production Ready | Self-hosted Cloud, Marketplace, stability, inline SQL | Q4 2026 |
|
|
145
167
|
|
|
146
168
|
---
|
|
147
169
|
|
|
@@ -149,7 +171,7 @@ Features to pick from after v1.0. Will be reviewed and scheduled based on user f
|
|
|
149
171
|
|
|
150
172
|
1. **Own "phone to code"** — PPM wins on multi-device access. Don't chase Cursor/Windsurf feature parity.
|
|
151
173
|
2. **PPM Cloud stays razor-thin** — Device registry + tunnel URLs only. No code storage. No cloud execution.
|
|
152
|
-
3. **Multi-provider is tiered** —
|
|
174
|
+
3. **Multi-provider is tiered** — v0.9: Claude SDK (Tier 1) + Cursor (Tier 2). v0.11: Gemini, Codex, Tier 3. Clean interface for future providers.
|
|
153
175
|
4. **Extensions keep core lightweight** — Features are opt-in. DB viewer, future tools = extensions. Core stays fast.
|
|
154
176
|
5. **Clawbot enables the ecosystem** — Users create extensions with AI, publish to Marketplace. Zero-friction.
|
|
155
177
|
6. **Self-hosted first, always** — Cloud is optional convenience. PPM works 100% offline/local.
|
|
@@ -160,7 +182,7 @@ Features to pick from after v1.0. Will be reviewed and scheduled based on user f
|
|
|
160
182
|
|
|
161
183
|
| Item | Priority | Notes |
|
|
162
184
|
|------|----------|-------|
|
|
163
|
-
| Refactor ProviderInterface for multi-provider | High |
|
|
185
|
+
| ~~Refactor ProviderInterface for multi-provider~~ | ~~High~~ | ✅ Done on beta branch (v0.9.0-beta.5) |
|
|
164
186
|
| Simplify ChatService streaming | Medium | Reduce async generator complexity |
|
|
165
187
|
| Extract WebSocket common logic | Low | DRY for chat/terminal WS |
|
|
166
188
|
| Round-robin cursor bug in AccountSelector | Medium | Positional cursor not advancing correctly |
|
package/package.json
CHANGED
package/src/server/index.ts
CHANGED
|
@@ -451,12 +451,15 @@ export async function startServer(options: {
|
|
|
451
451
|
}
|
|
452
452
|
console.log();
|
|
453
453
|
|
|
454
|
-
// Graceful shutdown — stop server + tunnel + DB on exit
|
|
454
|
+
// Graceful shutdown — stop server + tunnel + preview tunnels + DB on exit
|
|
455
455
|
const shutdown = () => {
|
|
456
456
|
try { server.stop(true); } catch {}
|
|
457
457
|
try {
|
|
458
458
|
import("../services/tunnel.service.ts").then(({ tunnelService }) => tunnelService.stopTunnel()).catch(() => {});
|
|
459
459
|
} catch {}
|
|
460
|
+
try {
|
|
461
|
+
import("./routes/browser-preview.ts").then(({ stopAllPreviewTunnels }) => stopAllPreviewTunnels()).catch(() => {});
|
|
462
|
+
} catch {}
|
|
460
463
|
try {
|
|
461
464
|
import("../services/db.service.ts").then(({ closeDb }) => closeDb()).catch(() => {});
|
|
462
465
|
} catch {}
|
|
@@ -1,89 +1,159 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
+
import { ok, err } from "../../types/api.ts";
|
|
3
|
+
import { ensureCloudflared } from "../../services/cloudflared.service.ts";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
|
-
* Browser preview
|
|
5
|
-
*
|
|
6
|
-
*
|
|
6
|
+
* Browser preview API — starts per-port Cloudflare Quick Tunnels so the
|
|
7
|
+
* frontend can iframe any localhost dev server without CORS/path issues.
|
|
8
|
+
*
|
|
9
|
+
* POST /api/preview/tunnel { port: 3000 } → { url: "https://xxx.trycloudflare.com" }
|
|
10
|
+
* DELETE /api/preview/tunnel/:port → stops tunnel for that port
|
|
11
|
+
* GET /api/preview/tunnels → list active tunnels
|
|
7
12
|
*/
|
|
8
13
|
export const browserPreviewRoutes = new Hono();
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
const TUNNEL_URL_REGEX = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/;
|
|
16
|
+
|
|
17
|
+
interface ActiveTunnel {
|
|
18
|
+
port: number;
|
|
19
|
+
url: string;
|
|
20
|
+
process: import("bun").Subprocess;
|
|
21
|
+
startedAt: number;
|
|
14
22
|
}
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
/** Active tunnels keyed by port */
|
|
25
|
+
const activeTunnels = new Map<number, ActiveTunnel>();
|
|
26
|
+
|
|
27
|
+
/** Start a tunnel for a localhost port */
|
|
28
|
+
browserPreviewRoutes.post("/tunnel", async (c) => {
|
|
29
|
+
const body = await c.req.json<{ port: number }>().catch(() => null);
|
|
30
|
+
const port = body?.port;
|
|
31
|
+
if (!port || port < 1 || port > 65535) {
|
|
32
|
+
return c.json(err("Invalid port (1-65535)"), 400);
|
|
20
33
|
}
|
|
21
34
|
|
|
22
|
-
//
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
// Return existing tunnel if already running
|
|
36
|
+
const existing = activeTunnels.get(port);
|
|
37
|
+
if (existing) {
|
|
38
|
+
return c.json(ok({ port, url: existing.url }));
|
|
39
|
+
}
|
|
27
40
|
|
|
28
41
|
try {
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const resp = await fetch(targetUrl, {
|
|
35
|
-
method: c.req.method,
|
|
36
|
-
headers,
|
|
37
|
-
body: ["GET", "HEAD"].includes(c.req.method) ? undefined : c.req.raw.body,
|
|
38
|
-
redirect: "manual",
|
|
39
|
-
});
|
|
42
|
+
const bin = await ensureCloudflared();
|
|
43
|
+
const proc = Bun.spawn(
|
|
44
|
+
[bin, "tunnel", "--url", `http://127.0.0.1:${port}`],
|
|
45
|
+
{ stderr: "pipe", stdout: "ignore", stdin: "ignore" },
|
|
46
|
+
);
|
|
40
47
|
|
|
41
|
-
//
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
// Read stderr to find tunnel URL
|
|
49
|
+
const reader = proc.stderr.getReader();
|
|
50
|
+
const decoder = new TextDecoder();
|
|
51
|
+
const url = await new Promise<string>((resolve, reject) => {
|
|
52
|
+
const timeout = setTimeout(() => {
|
|
53
|
+
try { proc.kill(); } catch {}
|
|
54
|
+
reject(new Error("Tunnel timed out after 30s"));
|
|
55
|
+
}, 30_000);
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
let buffer = "";
|
|
58
|
+
let found = false;
|
|
59
|
+
const read = async () => {
|
|
60
|
+
try {
|
|
61
|
+
while (true) {
|
|
62
|
+
const { done, value } = await reader.read();
|
|
63
|
+
if (done) break;
|
|
64
|
+
if (found) continue;
|
|
65
|
+
buffer += decoder.decode(value, { stream: true });
|
|
66
|
+
const match = buffer.match(TUNNEL_URL_REGEX);
|
|
67
|
+
if (match) {
|
|
68
|
+
found = true;
|
|
69
|
+
buffer = "";
|
|
70
|
+
clearTimeout(timeout);
|
|
71
|
+
resolve(match[0]);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (!found) {
|
|
75
|
+
clearTimeout(timeout);
|
|
76
|
+
reject(new Error("cloudflared exited without tunnel URL"));
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
if (!found) { clearTimeout(timeout); reject(e); }
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
read();
|
|
50
83
|
});
|
|
51
|
-
|
|
52
|
-
|
|
84
|
+
|
|
85
|
+
activeTunnels.set(port, { port, url, process: proc, startedAt: Date.now() });
|
|
86
|
+
|
|
87
|
+
// Auto-cleanup when process exits
|
|
88
|
+
proc.exited.then(() => activeTunnels.delete(port)).catch(() => activeTunnels.delete(port));
|
|
89
|
+
|
|
90
|
+
console.log(`[preview] tunnel started for port ${port} → ${url}`);
|
|
91
|
+
return c.json(ok({ port, url }));
|
|
92
|
+
} catch (e: any) {
|
|
93
|
+
return c.json(err(e.message || "Failed to start tunnel"), 500);
|
|
53
94
|
}
|
|
54
95
|
});
|
|
55
96
|
|
|
56
|
-
|
|
57
|
-
browserPreviewRoutes.
|
|
58
|
-
const port = c.req.param("port");
|
|
59
|
-
|
|
60
|
-
|
|
97
|
+
/** Stop a tunnel */
|
|
98
|
+
browserPreviewRoutes.delete("/tunnel/:port{[0-9]+}", (c) => {
|
|
99
|
+
const port = parseInt(c.req.param("port"), 10);
|
|
100
|
+
const tunnel = activeTunnels.get(port);
|
|
101
|
+
if (!tunnel) {
|
|
102
|
+
return c.json(err("No tunnel running for this port"), 404);
|
|
61
103
|
}
|
|
62
104
|
|
|
63
|
-
|
|
64
|
-
|
|
105
|
+
try { tunnel.process.kill(); } catch {}
|
|
106
|
+
activeTunnels.delete(port);
|
|
107
|
+
console.log(`[preview] tunnel stopped for port ${port}`);
|
|
108
|
+
return c.json(ok({ port }));
|
|
109
|
+
});
|
|
65
110
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
});
|
|
111
|
+
/** List active tunnels */
|
|
112
|
+
browserPreviewRoutes.get("/tunnels", (c) => {
|
|
113
|
+
const list = Array.from(activeTunnels.values()).map((t) => ({
|
|
114
|
+
port: t.port,
|
|
115
|
+
url: t.url,
|
|
116
|
+
startedAt: t.startedAt,
|
|
117
|
+
}));
|
|
118
|
+
return c.json(ok(list));
|
|
119
|
+
});
|
|
76
120
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
121
|
+
/** Check if a cloudflared process is still alive */
|
|
122
|
+
function isProcessAlive(proc: import("bun").Subprocess): boolean {
|
|
123
|
+
try { process.kill(proc.pid, 0); return true; } catch { return false; }
|
|
124
|
+
}
|
|
80
125
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
126
|
+
/** Remove ghost tunnels (process died or target port no longer listening) */
|
|
127
|
+
async function cleanupGhostTunnels() {
|
|
128
|
+
for (const [port, tunnel] of activeTunnels) {
|
|
129
|
+
// Check if cloudflared process is still running
|
|
130
|
+
if (!isProcessAlive(tunnel.process)) {
|
|
131
|
+
console.log(`[preview] ghost cleanup: tunnel for port ${port} — process dead`);
|
|
132
|
+
activeTunnels.delete(port);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
// Check if target port is still listening
|
|
136
|
+
try {
|
|
137
|
+
const conn = await Bun.connect({ hostname: "127.0.0.1", port, socket: {
|
|
138
|
+
data() {}, open(s) { s.end(); }, error() {}, close() {},
|
|
139
|
+
}});
|
|
140
|
+
conn.end();
|
|
141
|
+
} catch {
|
|
142
|
+
// Port not listening — kill tunnel
|
|
143
|
+
console.log(`[preview] ghost cleanup: tunnel for port ${port} — port not listening`);
|
|
144
|
+
try { tunnel.process.kill(); } catch {}
|
|
145
|
+
activeTunnels.delete(port);
|
|
146
|
+
}
|
|
88
147
|
}
|
|
89
|
-
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Run ghost cleanup every 30s
|
|
151
|
+
setInterval(cleanupGhostTunnels, 30_000);
|
|
152
|
+
|
|
153
|
+
/** Cleanup all tunnels on server shutdown */
|
|
154
|
+
export function stopAllPreviewTunnels() {
|
|
155
|
+
for (const [port, tunnel] of activeTunnels) {
|
|
156
|
+
try { tunnel.process.kill(); } catch {}
|
|
157
|
+
activeTunnels.delete(port);
|
|
158
|
+
}
|
|
159
|
+
}
|