@hienlh/ppm 0.18.12 → 0.18.13
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/AGENTS.md +17 -5
- package/CHANGELOG.md +22 -0
- package/CLAUDE.md +3 -0
- package/README.md +6 -6
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +19 -1
- package/dist/web/assets/{adaptive-context-menu-DGCKKeTy.js → adaptive-context-menu-CbgPYQmj.js} +1 -1
- package/dist/web/assets/{ai-resource-editor-BOHHTTe7.js → ai-resource-editor-unM2HNmg.js} +1 -1
- package/dist/web/assets/{ai-settings-section-BigC_nZR.js → ai-settings-section-CdktdrwF.js} +1 -1
- package/dist/web/assets/{angular-html-BYPaydvh.js → angular-html-DcpbdcNG.js} +1 -1
- package/dist/web/assets/{angular-ts-Bb4AMKlk.js → angular-ts-Bv_WIk34.js} +1 -1
- package/dist/web/assets/api-client-BogqsFna.js +1 -0
- package/dist/web/assets/{api-settings-Bd3M4YpI.js → api-settings-BdCtex9i.js} +1 -1
- package/dist/web/assets/api-settings-DfXViPKz.js +1 -0
- package/dist/web/assets/{api-themes-Cpx419SL.js → api-themes-pRRoLvgD.js} +1 -1
- package/dist/web/assets/{apl-DB2VjFCS.js → apl-D5_lLszu.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-BzFm9ELu.js +1 -0
- package/dist/web/assets/{astro-BeoJdVpk.js → astro-CBHwddYL.js} +1 -1
- package/dist/web/assets/{audio-preview-BgFuTn-0.js → audio-preview-BD1iDovf.js} +1 -1
- package/dist/web/assets/{blade-Cf2q1sPf.js → blade-DGe1d2Pq.js} +1 -1
- package/dist/web/assets/{bsl-W1kOaNpu.js → bsl-2lyAzRJA.js} +1 -1
- package/dist/web/assets/{button-5zTrkEoN.js → button-2WCAS8dI.js} +1 -1
- package/dist/web/assets/{cairo-BoAZFcje.js → cairo-DjzZeJrq.js} +1 -1
- package/dist/web/assets/chat-tab-DzK8LGti.js +15 -0
- package/dist/web/assets/{cobol-B0Nm7RXj.js → cobol-cC3XIuAJ.js} +1 -1
- package/dist/web/assets/code-editor-CuMfPN9X.js +10 -0
- package/dist/web/assets/{coffee-BakMn3on.js → coffee-4eGboVoU.js} +1 -1
- package/dist/web/assets/{conflict-editor-BUs5k9q_.js → conflict-editor-DdJag4QK.js} +1 -1
- package/dist/web/assets/{context-menu-Bq92km4s.js → context-menu-BO1c9jKy.js} +1 -1
- package/dist/web/assets/{cpp-BRAJQHK2.js → cpp-DF9_G-OU.js} +1 -1
- package/dist/web/assets/{crystal-kqqO3zkK.js → crystal-OavZlzcm.js} +1 -1
- package/dist/web/assets/{csv-preview-ByoDgLAZ.js → csv-preview-DnXwouF3.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-Brkf9z41.js → data-grid-overlay-editor-B9Gpjm3l.js} +1 -1
- package/dist/web/assets/{database-viewer-Btc3h8Yu.js → database-viewer-Dk8oGyZx.js} +1 -1
- package/dist/web/assets/{dialog-DwtGlowE.js → dialog-B-l2ZxVm.js} +1 -1
- package/dist/web/assets/{diff-viewer-CZaDnR4w.js → diff-viewer-eaUBjLCH.js} +1 -1
- package/dist/web/assets/{docx-preview-DAD_vg59.js → docx-preview-DAdHPlhZ.js} +1 -1
- package/dist/web/assets/{edge-Cq1ZKZuE.js → edge-TzYh8OlN.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-B_VE7ppQ.js → edit-diff-preview-DlwP3UIU.js} +1 -1
- package/dist/web/assets/{elixir-BM_JoJRk.js → elixir-Cy3RhKKH.js} +1 -1
- package/dist/web/assets/{elm-Dw7oO08Y.js → elm-BTBZdweK.js} +1 -1
- package/dist/web/assets/{erb-CfKOGAfk.js → erb-dv1mI0y4.js} +1 -1
- package/dist/web/assets/{erlang-DlDmGVeE.js → erlang-DjeiDrkl.js} +1 -1
- package/dist/web/assets/{esm-DGAP0GFh.js → esm-FRwvcesu.js} +1 -1
- package/dist/web/assets/explorer-body-DkCkSVrN.js +2 -0
- package/dist/web/assets/explorer-window-content-MsnTVngu.js +1 -0
- package/dist/web/assets/{extension-store-Cpfh2vMm.js → extension-store-DUaZINyC.js} +1 -1
- package/dist/web/assets/{extension-webview-B2OnjuCu.js → extension-webview-W0qRfaAU.js} +1 -1
- package/dist/web/assets/{file-store-aOERIEo-.js → file-store-yReVbR8q.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-XnAhQWxS.js → fortran-fixed-form-BCH9DAIJ.js} +1 -1
- package/dist/web/assets/{fsharp-CdNvy0PG.js → fsharp-C93NYKYD.js} +1 -1
- package/dist/web/assets/{gdresource-DxuG5i8i.js → gdresource-BcIEE2eX.js} +1 -1
- package/dist/web/assets/{git-commit-3lMonsur.js → git-commit-BVjlXEy8.js} +1 -1
- package/dist/web/assets/{git-log-panel-Co0PBPHC.js → git-log-panel-2_4HuP47.js} +1 -1
- package/dist/web/assets/{git-rebase-N8Z9kIMf.js → git-rebase-Bj_21B1V.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-B72vcb2r.js +1 -0
- package/dist/web/assets/glide-data-grid-B0IY6n_Q.js +138 -0
- package/dist/web/assets/{glimmer-js-Dp9OXiib.js → glimmer-js-C20yj4Zv.js} +1 -1
- package/dist/web/assets/{glimmer-ts-CDVKBcDj.js → glimmer-ts-ipbYC7kh.js} +1 -1
- package/dist/web/assets/{glsl-DPJxI4us.js → glsl-Dovazu3t.js} +1 -1
- package/dist/web/assets/{graphql-BrFhaQQl.js → graphql-DpMlM7hC.js} +1 -1
- package/dist/web/assets/{group-chat-tab-rOd8J8GC.js → group-chat-tab-DRLDHOp6.js} +1 -1
- package/dist/web/assets/{hack-BzXbDMlA.js → hack-gc7LCBuV.js} +1 -1
- package/dist/web/assets/{haml-CPpkX0s5.js → haml-vtlFm9gm.js} +1 -1
- package/dist/web/assets/{handlebars-BqrApy52.js → handlebars-DGKhpCji.js} +1 -1
- package/dist/web/assets/{html-DmjUl-OA.js → html-DvfhQbXb.js} +1 -1
- package/dist/web/assets/{html-derivative-Cnd6XFR5.js → html-derivative-C3oTSSCN.js} +1 -1
- package/dist/web/assets/{http-DMLWYun2.js → http-SG6zLTo7.js} +1 -1
- package/dist/web/assets/{hurl-CKkCVxbj.js → hurl-BWXsYlqW.js} +1 -1
- package/dist/web/assets/{hxml-CAx19TtQ.js → hxml-DUdMzaoh.js} +1 -1
- package/dist/web/assets/{image-preview-DHUoylQ1.js → image-preview-DxgFOV1T.js} +1 -1
- package/dist/web/assets/index-CBn4SOoT.js +52 -0
- package/dist/web/assets/index-DWpNR3nB.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-3irPxRK_.js +1 -0
- package/dist/web/assets/{input-DcEk7mA3.js → input-DuAF-Stj.js} +1 -1
- package/dist/web/assets/{jinja-BmB9YN9l.js → jinja-955w9RKM.js} +1 -1
- package/dist/web/assets/{jison-BaVsKKpw.js → jison-WWfqPo50.js} +1 -1
- package/dist/web/assets/{julia-BgVBLveX.js → julia-BFrfnaNl.js} +1 -1
- package/dist/web/assets/{just-C2ENIbl9.js → just-BrZdyR_I.js} +1 -1
- package/dist/web/assets/{keybindings-store-BGIyqXHI.js → keybindings-store-0Wqfa7Qp.js} +1 -1
- package/dist/web/assets/keybindings-store-d54pv2NK.js +1 -0
- package/dist/web/assets/{latex-BiDIWxKL.js → latex-CwtPPASD.js} +1 -1
- package/dist/web/assets/{liquid-NxpapreE.js → liquid-CYZuvDDd.js} +1 -1
- package/dist/web/assets/{lua-DVepQvwH.js → lua-BV095ZyY.js} +1 -1
- package/dist/web/assets/markdown-renderer-BTS8ifdu.js +1 -0
- package/dist/web/assets/{markdown-renderer-DlcDhgrF.js → markdown-renderer-CnRcKK3d.js} +3 -3
- package/dist/web/assets/{marko-CJ2606Xl.js → marko-CNeljZ1C.js} +1 -1
- package/dist/web/assets/{mdc-D2bjqclE.js → mdc-JfGapShy.js} +1 -1
- package/dist/web/assets/{media-url-H17XJF-I.js → media-url-Bm4PsduE.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-g-r2hCFa.js → mobile-bottom-sheet-M5iZDX7y.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-BqK2-WuN.js +1 -0
- package/dist/web/assets/{nextflow-BtJwUbfF.js → nextflow-DD5C8nqX.js} +1 -1
- package/dist/web/assets/{nginx-j4gyIhHE.js → nginx-Ca7B__x_.js} +1 -1
- package/dist/web/assets/{nim-ClH_gjP5.js → nim-B8FogsDw.js} +1 -1
- package/dist/web/assets/notification-store-Yvy1I8gd.js +1 -0
- package/dist/web/assets/{number-overlay-editor-C1EqJnWq.js → number-overlay-editor-DG9xJwEG.js} +1 -1
- package/dist/web/assets/{overview-panel-vTeeJYVZ.js → overview-panel-s53bnl0q.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-Z5jWjFCV.js +1 -0
- package/dist/web/assets/{panel-store-BSnkjgHt.js → panel-store-B4rijFKN.js} +1 -1
- package/dist/web/assets/{pdf-preview-CqFGRY0Z.js → pdf-preview-DYt3aBzM.js} +1 -1
- package/dist/web/assets/{perl-C9H7X7CT.js → perl-BR78hS8R.js} +1 -1
- package/dist/web/assets/{php-Bx2U-cQ_.js → php-DXBOMC9z.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-Bq7UMi8P.js +1 -0
- package/dist/web/assets/{postgres-viewer-DHwcQucn.js → postgres-viewer-BvoWSuoZ.js} +3 -3
- package/dist/web/assets/{process-table-BGRJNlpn.js → process-table-BYwKigiD.js} +1 -1
- package/dist/web/assets/{project-store--nvQiy75.js → project-store-BuY7rxn_.js} +1 -1
- package/dist/web/assets/{pug-Cw74sKsE.js → pug-CvTwVVKV.js} +1 -1
- package/dist/web/assets/{qml-BXD94Eqz.js → qml-CPSoZ1-B.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-BLo4BOTD.js +1 -0
- package/dist/web/assets/{razor-DZ0-Ge0S.js → razor-BUA1YKtF.js} +1 -1
- package/dist/web/assets/{rst-Dtdh0QUj.js → rst-BJsq7ISz.js} +1 -1
- package/dist/web/assets/{ruby-BC51BW2Q.js → ruby-DSDMeYf5.js} +1 -1
- package/dist/web/assets/{run-in-terminal-Sd3KyjN4.js → run-in-terminal-C9xpskFn.js} +1 -1
- package/dist/web/assets/{sas-BN8i4qOU.js → sas-B1nJOjbN.js} +1 -1
- package/dist/web/assets/{scroll-area-0gljZ6lF.js → scroll-area-DBcG1fQo.js} +1 -1
- package/dist/web/assets/{scss-H-QtA7Rz.js → scss-BZFyRiQn.js} +1 -1
- package/dist/web/assets/{send-to-chat-u6m8l-rH.js → send-to-chat-BIg70MUZ.js} +1 -1
- package/dist/web/assets/{settings-store-CHY6Kzin.js → settings-store-GV5pkkke.js} +2 -2
- package/dist/web/assets/{settings-tab-IY6K1MiZ.js → settings-tab-BePvIcjd.js} +1 -1
- package/dist/web/assets/settings-tab-Br-lJSPa.js +1 -0
- package/dist/web/assets/{shaderlab-CalNsj_x.js → shaderlab-laINRXBo.js} +1 -1
- package/dist/web/assets/{shellsession-BFrcoR8x.js → shellsession-BpOOvWVR.js} +1 -1
- package/dist/web/assets/{soy-CNbku7vP.js → soy-BW4d91Sa.js} +1 -1
- package/dist/web/assets/{sparql-BN_orcVp.js → sparql-BsvvyoHn.js} +1 -1
- package/dist/web/assets/{sql-query-editor-C6pC3I8l.js → sql-query-editor-qQce5QJ6.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-DTr5PzUb.js → sqlite-viewer-yhc0YNvu.js} +1 -1
- package/dist/web/assets/{stata-C6a5Ijzq.js → stata-K-dZInUt.js} +1 -1
- package/dist/web/assets/{surrealql-ClplI_HY.js → surrealql-CpNp2sgf.js} +1 -1
- package/dist/web/assets/{svelte-DNm-UNXB.js → svelte-BG6iBA8R.js} +1 -1
- package/dist/web/assets/{switch-DKvKya9y.js → switch-_NcrktLe.js} +1 -1
- package/dist/web/assets/system-monitor-body-D8Z5QXBB.js +2 -0
- package/dist/web/assets/system-monitor-tab-BclYXFTe.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-BUqRsPK2.js → system-monitor-window-content-CaOhD0AU.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-BnqGPu8a.js → tab-host-window-content-f_Rc5rJK.js} +1 -1
- package/dist/web/assets/{tab-store-CJuJRapl.js → tab-store-tFnyPmm4.js} +1 -1
- package/dist/web/assets/team-member-sheet-QDHoYCs0.js +2 -0
- package/dist/web/assets/{team-member-window-content-T4Q5JI-G.js → team-member-window-content-B1jkKkDi.js} +1 -1
- package/dist/web/assets/{templ-CSdqVobu.js → templ-gxNKXmt3.js} +1 -1
- package/dist/web/assets/{terminal-tab-Bb2rZegQ.js → terminal-tab-UvwM0sty.js} +3 -3
- package/dist/web/assets/{tex-DzSJ4kcZ.js → tex-C0ON27g5.js} +1 -1
- package/dist/web/assets/{tool-cards-lhqhrDTT.js → tool-cards-jB7GT-wj.js} +4 -4
- package/dist/web/assets/treemap-KZPCXAKY-dyrE6SW2.js +1 -0
- package/dist/web/assets/{ts-tags-C28w3bTg.js → ts-tags-BX_CTVgQ.js} +1 -1
- package/dist/web/assets/{twig-i5jY3QXB.js → twig-CwL7XnU0.js} +1 -1
- package/dist/web/assets/{use-blob-url-Dsq9OjTh.js → use-blob-url-DcRcjNTE.js} +1 -1
- package/dist/web/assets/use-coarse-long-press-BmI_GVW5.js +1 -0
- package/dist/web/assets/{use-monaco-theme-BZvlsoBv.js → use-monaco-theme--9tOOkDA.js} +1 -1
- package/dist/web/assets/{use-open-team-member-DkiEjFYE.js → use-open-team-member-1hRsHLKg.js} +1 -1
- package/dist/web/assets/{use-websocket-B8R50f7f.js → use-websocket-rxJLbFqy.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-By4AVvJY.js → vendor-mermaid-CHTa6D69.js} +1 -1
- package/dist/web/assets/video-preview-DMhnf2Gi.js +1 -0
- package/dist/web/assets/{vue-Ceyet_lv.js → vue-Bykm6wSS.js} +1 -1
- package/dist/web/assets/{vue-html-GuU7g_hg.js → vue-html-CETdK_iV.js} +1 -1
- package/dist/web/assets/{vue-vine-CxXoaJr2.js → vue-vine-DiJb6fO5.js} +1 -1
- package/dist/web/assets/{window-store-CsEW6jiE.js → window-store-CQ1lghvv.js} +1 -1
- package/dist/web/assets/{xml-CbqOVhkP.js → xml-BNw8J5YW.js} +1 -1
- package/dist/web/assets/{xsl-KfAmCauy.js → xsl-CsG9L31b.js} +1 -1
- package/dist/web/index.html +46 -45
- package/dist/web/sw.js +1 -1
- package/docs/architecture/ai-chat-and-providers.md +309 -0
- package/docs/architecture/data-and-storage.md +356 -0
- package/docs/architecture/extensions.md +496 -0
- package/docs/architecture/integrations.md +214 -0
- package/docs/architecture/workspace-and-ui.md +391 -0
- package/docs/claude-agent-sdk-reference.md +1 -1
- package/docs/code-patterns.md +361 -0
- package/docs/code-standards.md +8 -358
- package/docs/codebase-subsystems.md +364 -0
- package/docs/codebase-summary.md +34 -379
- package/docs/deployment-guide.md +280 -490
- package/docs/design-guidelines.md +1 -1
- package/docs/lessons-learned.md +27 -10
- package/docs/project-changelog.md +18 -806
- package/docs/project-overview-pdr.md +117 -111
- package/docs/project-roadmap.md +89 -133
- package/docs/system-architecture.md +77 -1826
- package/package.json +2 -2
- package/src/cli/commands/autostart.ts +2 -2
- package/src/cli/commands/config-cmd.ts +12 -3
- package/src/cli/commands/status.ts +3 -2
- package/src/cli/commands/stop.ts +32 -4
- package/src/server/index.ts +17 -3
- package/src/server/routes/chat-unread.ts +36 -0
- package/src/server/routes/named-tunnel.ts +132 -0
- package/src/server/routes/tunnel.ts +13 -0
- package/src/server/ws/chat.ts +5 -2
- package/src/services/autostart-register.ts +47 -29
- package/src/services/cloud.service.ts +26 -1
- package/src/services/config-secret-keys.ts +48 -0
- package/src/services/config.service.ts +1 -1
- package/src/services/db.service.ts +62 -12
- package/src/services/extension-rpc-handlers.ts +9 -1
- package/src/services/file-watcher/watch-tree.ts +67 -33
- package/src/services/file-watcher.service.ts +28 -10
- package/src/services/fs-credential-path-guard.ts +103 -0
- package/src/services/fs-path-guard.service.ts +14 -66
- package/src/services/named-tunnel/cloudflare-dns-api.ts +40 -0
- package/src/services/named-tunnel/cloudflare-zone-api.ts +28 -0
- package/src/services/named-tunnel/cloudflared-cert.ts +95 -0
- package/src/services/named-tunnel/cloudflared-exec.ts +50 -0
- package/src/services/named-tunnel/cloudflared-login-helpers.ts +98 -0
- package/src/services/named-tunnel/cloudflared-login.service.ts +182 -0
- package/src/services/named-tunnel/hostname-rules.ts +58 -0
- package/src/services/named-tunnel/login-output-parser.ts +36 -0
- package/src/services/named-tunnel/named-tunnel-args.ts +110 -0
- package/src/services/named-tunnel/named-tunnel-config.ts +67 -0
- package/src/services/named-tunnel/named-tunnel-probe-state.ts +95 -0
- package/src/services/named-tunnel/named-tunnel-readiness.ts +59 -0
- package/src/services/named-tunnel/named-tunnel-runtime.ts +42 -0
- package/src/services/named-tunnel/named-tunnel-setup-confirm.ts +98 -0
- package/src/services/named-tunnel/named-tunnel-setup.service.ts +191 -0
- package/src/services/supervisor-state.ts +67 -2
- package/src/services/supervisor.ts +454 -157
- package/src/services/tunnel-registry-parse.ts +27 -0
- package/src/services/tunnel-registry.service.ts +2 -15
- package/src/services/tunnel.service.ts +12 -3
- package/src/types/config.ts +31 -0
- package/src/web/app.tsx +5 -0
- package/src/web/components/chat/chat-history-bar.tsx +95 -98
- package/src/web/components/chat/chat-toolbar-overflow-menu.tsx +57 -0
- package/src/web/components/chat/session-debug-dialog.tsx +35 -27
- package/src/web/components/layout/cloud-share-named-tunnel-row.tsx +111 -0
- package/src/web/components/layout/cloud-share-popover.tsx +56 -14
- package/src/web/components/shared/connection-lost-overlay.tsx +44 -5
- package/src/web/components/tunnels/named-tunnel/hostname-validation.ts +14 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-copy.ts +96 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-hostname-field.tsx +51 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-login-step.tsx +105 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-section.tsx +159 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-setup-content.tsx +197 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-setup-popup.tsx +57 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-step-reducer.ts +191 -0
- package/src/web/components/tunnels/named-tunnel/use-named-tunnel-setup.ts +212 -0
- package/src/web/components/tunnels/tunnel-manager-tab.tsx +4 -0
- package/src/web/hooks/use-global-events.ts +3 -1
- package/src/web/lib/api-named-tunnel.ts +54 -0
- package/src/web/lib/last-known-local-url.ts +23 -0
- package/src/web/stores/notification-store.ts +20 -1
- package/dist/web/assets/api-client-DTZEgjBQ.js +0 -1
- package/dist/web/assets/api-settings-kd1j4iR8.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-8dCMT9r7.js +0 -1
- package/dist/web/assets/chat-tab-E3bf_V8X.js +0 -15
- package/dist/web/assets/code-editor-BUlM2gXk.js +0 -10
- package/dist/web/assets/explorer-body-9aBmyVpZ.js +0 -2
- package/dist/web/assets/explorer-window-content-DRtqoSPA.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-2e_8dfbs.js +0 -1
- package/dist/web/assets/glide-data-grid-C4_zIneN.js +0 -138
- package/dist/web/assets/index-BImUaigO.js +0 -52
- package/dist/web/assets/index-CSfi6DSU.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-BZi1Gha1.js +0 -1
- package/dist/web/assets/keybindings-store-CzV2sPls.js +0 -1
- package/dist/web/assets/markdown-renderer-CcbR33VG.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-SIwQXlwA.js +0 -1
- package/dist/web/assets/notification-store-DV5QtUTg.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-o5htEcin.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-BXAIFSAD.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-Dbt2Aebl.js +0 -1
- package/dist/web/assets/settings-tab-Dic6TKkU.js +0 -1
- package/dist/web/assets/system-monitor-body-iwGiCns9.js +0 -2
- package/dist/web/assets/system-monitor-tab-DdXQhEel.js +0 -1
- package/dist/web/assets/team-member-sheet-LkAFSCvb.js +0 -2
- package/dist/web/assets/treemap-KZPCXAKY-DUbnl9dz.js +0 -1
- package/dist/web/assets/video-preview-m0-3p7QZ.js +0 -1
- /package/dist/web/assets/{abap-C6nB04tn.js → abap-DGlrX_8G.js} +0 -0
- /package/dist/web/assets/{actionscript-3-Vx4rw2sC.js → actionscript-3-CUqRtUJE.js} +0 -0
- /package/dist/web/assets/{ada-Ccd8vM20.js → ada-CY3Spa33.js} +0 -0
- /package/dist/web/assets/{andromeeda-DnBX5xMg.js → andromeeda-Lxs2TJn7.js} +0 -0
- /package/dist/web/assets/{apache-B-9_sx0g.js → apache-DfxgGdgM.js} +0 -0
- /package/dist/web/assets/{apex-_wPVBYoL.js → apex-QVDF1dnI.js} +0 -0
- /package/dist/web/assets/{api-client-aQb5hHE5.js → api-client-oY1tC3tM.js} +0 -0
- /package/dist/web/assets/{applescript-CcvD-qr0.js → applescript-pwJ8QXh-.js} +0 -0
- /package/dist/web/assets/{apply-theme-BeLRfsSu.js → apply-theme-B_bMVL6a.js} +0 -0
- /package/dist/web/assets/{ara-C40Ztoms.js → ara-mPjeX5YX.js} +0 -0
- /package/dist/web/assets/{asciidoc-CybYkJV4.js → asciidoc-Bf7LsDkI.js} +0 -0
- /package/dist/web/assets/{asm-9eOKwzR0.js → asm-rRKC1OUx.js} +0 -0
- /package/dist/web/assets/{aurora-x-B5ZH4t7t.js → aurora-x-DwQr0DkG.js} +0 -0
- /package/dist/web/assets/{awk-Dqilob-2.js → awk-Bpt768fN.js} +0 -0
- /package/dist/web/assets/{ayu-dark-CPgRhf5r.js → ayu-dark-D9lwQHMs.js} +0 -0
- /package/dist/web/assets/{ayu-light-Do06Q52f.js → ayu-light-B4-ptrU5.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-_BhxcrxY.js → ayu-mirage-DAyRzH5Q.js} +0 -0
- /package/dist/web/assets/{ballerina-DDwK54QW.js → ballerina-Cgdpj1HC.js} +0 -0
- /package/dist/web/assets/{bat-BGyGlzGk.js → bat-4FrN36S2.js} +0 -0
- /package/dist/web/assets/{beancount-CRWBdapI.js → beancount-DkcxQER3.js} +0 -0
- /package/dist/web/assets/{berry-DbSFn2gO.js → berry-Dnuut-Ml.js} +0 -0
- /package/dist/web/assets/{bibtex-QpsV9gm9.js → bibtex-CCDwmbuA.js} +0 -0
- /package/dist/web/assets/{bicep-CxS8CUve.js → bicep-BC0ryoGM.js} +0 -0
- /package/dist/web/assets/{bird2-CuZhQzYR.js → bird2-C50V_A-0.js} +0 -0
- /package/dist/web/assets/{c-9bnDqE3g.js → c-Ddorhq0U.js} +0 -0
- /package/dist/web/assets/{c3-JMZTrOhA.js → c3-DdIf5r9E.js} +0 -0
- /package/dist/web/assets/{cadence-DsDNayJS.js → cadence-s9pd1Wng.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-BJfpynya.js → can-open-in-ppm-C6hZV1OP.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-DYIKXXBe.js → catppuccin-frappe-CzJNTyu-.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-IYYOnUJ8.js → catppuccin-latte-BNpt-m7s.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-BrJ_7mfS.js → catppuccin-macchiato-FYGkfsJ5.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-gzSu7v7-.js → catppuccin-mocha-it2g5D1q.js} +0 -0
- /package/dist/web/assets/{clarity-DLzsC0Hb.js → clarity-BVFBTjMe.js} +0 -0
- /package/dist/web/assets/{clipboard-7fw0Shic.js → clipboard-BWznymz7.js} +0 -0
- /package/dist/web/assets/{clojure-DS1BvoTt.js → clojure-BSYvXMI8.js} +0 -0
- /package/dist/web/assets/{cmake-CDvsJwYp.js → cmake-DGEqH-Np.js} +0 -0
- /package/dist/web/assets/{codeowners-BjsYiflJ.js → codeowners-D8FCOxWg.js} +0 -0
- /package/dist/web/assets/{codeql-CG-7ARat.js → codeql-COV0eluG.js} +0 -0
- /package/dist/web/assets/{common-lisp-DSO1Lbh9.js → common-lisp-xHnZUlgt.js} +0 -0
- /package/dist/web/assets/{coq-DYdh3oP_.js → coq-Cs_2i3Xg.js} +0 -0
- /package/dist/web/assets/{csharp-DxEBz5s0.js → csharp-B1ONB4PT.js} +0 -0
- /package/dist/web/assets/{css-dal3Q-8C.js → css-BgGQbvrZ.js} +0 -0
- /package/dist/web/assets/{csv-CpqjMGiA.js → csv-CNHmQBzs.js} +0 -0
- /package/dist/web/assets/{csv-parser-3umBA4NY.js → csv-parser-DWR24J17.js} +0 -0
- /package/dist/web/assets/{cue-DinoJ_5k.js → cue-ZXAKzwIy.js} +0 -0
- /package/dist/web/assets/{cypher-DzxqmkUW.js → cypher-4PL5bmT5.js} +0 -0
- /package/dist/web/assets/{d-BxNwJuEC.js → d-CEsV3RT2.js} +0 -0
- /package/dist/web/assets/{dark-plus-Clmv3V-0.js → dark-plus-NtFnYSDz.js} +0 -0
- /package/dist/web/assets/{dart-BWVtXD35.js → dart-CKHn4mDf.js} +0 -0
- /package/dist/web/assets/{data-grid-types-B4n51Fck.js → data-grid-types-CxU8oo-j.js} +0 -0
- /package/dist/web/assets/{dax-DsImuWgJ.js → dax-Cmkj1I_M.js} +0 -0
- /package/dist/web/assets/{desktop-B0jJuG2r.js → desktop-DJLalvPQ.js} +0 -0
- /package/dist/web/assets/{diff-CWqAAxoE.js → diff-BnS2wpj6.js} +0 -0
- /package/dist/web/assets/{dist-C0eNNfbt.js → dist-CuIdRyUB.js} +0 -0
- /package/dist/web/assets/{dist-B-EXdSYE.js → dist-uYjs3Wv6.js} +0 -0
- /package/dist/web/assets/{docker-CYFCJdIL.js → docker-CovNy7ml.js} +0 -0
- /package/dist/web/assets/{dotenv-CKDUjURg.js → dotenv-Gh4w3I6B.js} +0 -0
- /package/dist/web/assets/{dracula-CSoPspHE.js → dracula-NF0tAHXl.js} +0 -0
- /package/dist/web/assets/{dracula-soft-BuFjw5L1.js → dracula-soft-Cj_qBurK.js} +0 -0
- /package/dist/web/assets/{dream-maker-B6PdDRZu.js → dream-maker-CRuZPcl7.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-CMcXvD0G.js → emacs-lisp-BACmC9qj.js} +0 -0
- /package/dist/web/assets/{everforest-dark-cU8-wYzi.js → everforest-dark-Dd9av4QG.js} +0 -0
- /package/dist/web/assets/{everforest-light-KhorllqK.js → everforest-light-C-jsss-B.js} +0 -0
- /package/dist/web/assets/{external-link-B8BynhvY.js → external-link-B6Hl0U4X.js} +0 -0
- /package/dist/web/assets/{fennel-BCELZK87.js → fennel-BJA9XnL8.js} +0 -0
- /package/dist/web/assets/{file-exclamation-point-CZ4DiR75.js → file-exclamation-point-JzhTU2TX.js} +0 -0
- /package/dist/web/assets/{fish-BQc0y_jy.js → fish-CKlhNSc2.js} +0 -0
- /package/dist/web/assets/{fluent-BTINnwF6.js → fluent-wfglRk5D.js} +0 -0
- /package/dist/web/assets/{folder-symlink-B8XrtvQK.js → folder-symlink-Dx1hOk8X.js} +0 -0
- /package/dist/web/assets/{format-bytes-WVcFxJuT.js → format-bytes-utwHMTry.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BQim9wDz.js → fortran-free-form-BlXCdid3.js} +0 -0
- /package/dist/web/assets/{gdscript-03w5u8un.js → gdscript-BwAVG6Et.js} +0 -0
- /package/dist/web/assets/{gdshader-Dp2daNzU.js → gdshader-DPc_T7ib.js} +0 -0
- /package/dist/web/assets/{genie-CoNcjPiD.js → genie-B-zZSLCX.js} +0 -0
- /package/dist/web/assets/{gherkin-NrPOSPQL.js → gherkin-CJ0ajpGs.js} +0 -0
- /package/dist/web/assets/{github-dark-z4RE9ZqA.js → github-dark-Dz8ZkzSP.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DOKw-1cI.js → github-dark-default-CNUYo_kp.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-DJRNMkEA.js → github-dark-dimmed-241z_Ycx.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-Bj7jJ_Gs.js → github-dark-high-contrast-7r6yZZYH.js} +0 -0
- /package/dist/web/assets/{github-light-B4k_yMFu.js → github-light-C8gmZn-D.js} +0 -0
- /package/dist/web/assets/{github-light-default-BHc-Uzhs.js → github-light-default-GAEgq1eA.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-CHisIywL.js → github-light-high-contrast-3z91352E.js} +0 -0
- /package/dist/web/assets/{gleam-CfbypZ4q.js → gleam-C7vxW5zd.js} +0 -0
- /package/dist/web/assets/{gn-YoAjyhJa.js → gn-CyJIXwHP.js} +0 -0
- /package/dist/web/assets/{gnuplot-xVkc1f1c.js → gnuplot-CqLbGgPd.js} +0 -0
- /package/dist/web/assets/{go-GPCwP3Gu.js → go-DdDcK9aX.js} +0 -0
- /package/dist/web/assets/{groovy-Crh4Yww9.js → groovy-UYHggEqH.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-BluUFuDS.js → gruvbox-dark-hard-B4ierkJn.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Cxk1FWQp.js → gruvbox-dark-medium-DYwkDSZ6.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-CHXMiFmj.js → gruvbox-dark-soft-CVL4P-Lz.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-B6eKPjAD.js → gruvbox-light-hard-BJ0LewUZ.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-pDwMm1ut.js → gruvbox-light-medium-CaqqjeGJ.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-Cxu6kHqK.js → gruvbox-light-soft-ByKMoVNr.js} +0 -0
- /package/dist/web/assets/{haskell-C6JKHjbK.js → haskell-BilYRVPp.js} +0 -0
- /package/dist/web/assets/{haxe-BuBNdF0a.js → haxe-Brs9Bj1s.js} +0 -0
- /package/dist/web/assets/{hcl-DZpDdGj2.js → hcl-ClrZpol-.js} +0 -0
- /package/dist/web/assets/{hjson-CBkhYRuo.js → hjson-9PvHnYMX.js} +0 -0
- /package/dist/web/assets/{hlsl-C5nMxnp9.js → hlsl-BbBk4vqV.js} +0 -0
- /package/dist/web/assets/{horizon-VAFvA6R6.js → horizon-E5Py0cny.js} +0 -0
- /package/dist/web/assets/{horizon-bright-BC0xn7T3.js → horizon-bright-DVCsNGEk.js} +0 -0
- /package/dist/web/assets/{houston-BxrUivmz.js → houston-JdFEax9Y.js} +0 -0
- /package/dist/web/assets/{hy-DLtOXoaH.js → hy-W_W3tLKT.js} +0 -0
- /package/dist/web/assets/{image-gallery-Gnf7TdCR.js → image-gallery-5IURd6db.js} +0 -0
- /package/dist/web/assets/{imba-BdpFXM2W.js → imba-CfAqJz7T.js} +0 -0
- /package/dist/web/assets/{ini-BInJO_T0.js → ini-C_3gvKWs.js} +0 -0
- /package/dist/web/assets/{java-Dxuddnkb.js → java-CKqtXZ-j.js} +0 -0
- /package/dist/web/assets/{javascript-CVamJfLY.js → javascript-C7gSEfae.js} +0 -0
- /package/dist/web/assets/{json-BLZ0ozYk.js → json-BoN6e11T.js} +0 -0
- /package/dist/web/assets/{json5-BnXzmMKs.js → json5-CgE7C8HW.js} +0 -0
- /package/dist/web/assets/{jsonc-CSgkSga7.js → jsonc-DjyoattY.js} +0 -0
- /package/dist/web/assets/{jsonl-CY5W2nF2.js → jsonl-lI82wGSw.js} +0 -0
- /package/dist/web/assets/{jsonnet-CEJYH3sF.js → jsonnet-CszEI8va.js} +0 -0
- /package/dist/web/assets/{jssm-CsJc7Uhk.js → jssm-CZXDlszb.js} +0 -0
- /package/dist/web/assets/{jsx-jCiRaHU7.js → jsx-BeRz0eEz.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-DfoA_GxX.js → kanagawa-dragon-BOmnUiyd.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-BOLYJpWd.js → kanagawa-lotus-ffU_P5w-.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-DfKlbT1G.js → kanagawa-wave-DKTJZrMu.js} +0 -0
- /package/dist/web/assets/{kdl-CmssIFTi.js → kdl-BWECJveH.js} +0 -0
- /package/dist/web/assets/{kotlin-BRsNgp6x.js → kotlin-DpVwBCSt.js} +0 -0
- /package/dist/web/assets/{kusto-T8X9jUab.js → kusto-BgVqjUZA.js} +0 -0
- /package/dist/web/assets/{laserwave-DkJMVJ8Z.js → laserwave-CEVCi8em.js} +0 -0
- /package/dist/web/assets/{lean-C65KGVPq.js → lean-CGyfRhLJ.js} +0 -0
- /package/dist/web/assets/{less-exIPdDlf.js → less-B0_H10Ku.js} +0 -0
- /package/dist/web/assets/{lib-mC1NZfuw.js → lib-hrC3bruJ.js} +0 -0
- /package/dist/web/assets/{libesm-DSutUtl1.js → libesm-DVpFmwJu.js} +0 -0
- /package/dist/web/assets/{light-plus-DiHUXAZt.js → light-plus-DqzMqPID.js} +0 -0
- /package/dist/web/assets/{llvm-CJBnEzFM.js → llvm-9ucnfNiF.js} +0 -0
- /package/dist/web/assets/{loader-circle-gSPVxfvH.js → loader-circle-Dpqcv0eS.js} +0 -0
- /package/dist/web/assets/{lock-Did-87UV.js → lock-DldbT31x.js} +0 -0
- /package/dist/web/assets/{log-DxMyUMuP.js → log-m6A2dOSj.js} +0 -0
- /package/dist/web/assets/{logo-CC9NKzca.js → logo-ByPcsC9P.js} +0 -0
- /package/dist/web/assets/{luau-Bw10I6zf.js → luau-DFCo8nBn.js} +0 -0
- /package/dist/web/assets/{make-C_6M90m6.js → make-C90w3MeA.js} +0 -0
- /package/dist/web/assets/{markdown-bR9_R-LF.js → markdown-DT0uywZG.js} +0 -0
- /package/dist/web/assets/{material-theme-BUx2Qujn.js → material-theme-BVUO93tJ.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-CRJrPgKm.js → material-theme-darker-xXgGDwnF.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-CUAYppPa.js → material-theme-lighter-BjStPzWB.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-DuINF1fg.js → material-theme-ocean-wFmSiKjy.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-W6_icGCj.js → material-theme-palenight-e-0CtRoe.js} +0 -0
- /package/dist/web/assets/{matlab-rfqn_b3z.js → matlab-BNaNQAUH.js} +0 -0
- /package/dist/web/assets/{mdx-Bh1B_YOJ.js → mdx-DfpVWAqO.js} +0 -0
- /package/dist/web/assets/{mermaid-Dxf6Ya2k.js → mermaid-Bpq7wFCP.js} +0 -0
- /package/dist/web/assets/{min-dark-CivZ2L0h.js → min-dark-CL2Y0WH6.js} +0 -0
- /package/dist/web/assets/{min-light-B1Ykqtma.js → min-light-DOgr_IdP.js} +0 -0
- /package/dist/web/assets/{mipsasm-DynHaM0d.js → mipsasm-C9ie2zlB.js} +0 -0
- /package/dist/web/assets/{mojo-Clw5PbGH.js → mojo-Md2A2sJy.js} +0 -0
- /package/dist/web/assets/{monitor-Dv8vn_6O.js → monitor-4vWfqzuN.js} +0 -0
- /package/dist/web/assets/{monokai-DFIooFZT.js → monokai-nbXIEySB.js} +0 -0
- /package/dist/web/assets/{moonbit-DnBSZWGM.js → moonbit-CJ-T-s4A.js} +0 -0
- /package/dist/web/assets/{move-CCcL2PfL.js → move-DwEehLIK.js} +0 -0
- /package/dist/web/assets/{narrat-BDarg5Lk.js → narrat-CuF4sIKr.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-DrZOeCNW.js → nextflow-groovy-1dYhfPfq.js} +0 -0
- /package/dist/web/assets/{night-owl-BnZZCK7v.js → night-owl-gZS5mfzY.js} +0 -0
- /package/dist/web/assets/{night-owl-light-CEwBsVHS.js → night-owl-light-DDAster5.js} +0 -0
- /package/dist/web/assets/{nix-amCzOPMR.js → nix-BlbEYs2c.js} +0 -0
- /package/dist/web/assets/{nord-BIs2BC6q.js → nord-BRiPb8Vy.js} +0 -0
- /package/dist/web/assets/{nushell-Y02fo4yw.js → nushell-DgWicB1i.js} +0 -0
- /package/dist/web/assets/{objective-c-7xOZtWW1.js → objective-c-D8mPZ42Y.js} +0 -0
- /package/dist/web/assets/{objective-cpp-eVtjUvVB.js → objective-cpp-B0pNXzuj.js} +0 -0
- /package/dist/web/assets/{ocaml-Bi6ZwqG4.js → ocaml-lQd7aK5d.js} +0 -0
- /package/dist/web/assets/{odin-8xf1Y0S9.js → odin-CxtIOBjm.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-hVv-_y_b.js → one-dark-pro-T_UFnWfe.js} +0 -0
- /package/dist/web/assets/{one-light-CJJ3MtHt.js → one-light-LBVqwyV-.js} +0 -0
- /package/dist/web/assets/{openscad-B2HcywLk.js → openscad-C5dnS6J7.js} +0 -0
- /package/dist/web/assets/{pascal-C7fllV_7.js → pascal-CmWCWQML.js} +0 -0
- /package/dist/web/assets/{pkl-qvLC1IWP.js → pkl-BWWOtDQk.js} +0 -0
- /package/dist/web/assets/{plastic-BT1wHZv2.js → plastic-ekhupckF.js} +0 -0
- /package/dist/web/assets/{play-4MZi1Axp.js → play-CpCAoQRZ.js} +0 -0
- /package/dist/web/assets/{plsql-1MsmdmJD.js → plsql-BPsDHXx4.js} +0 -0
- /package/dist/web/assets/{plus-BhMZqFsN.js → plus-BR0qewJI.js} +0 -0
- /package/dist/web/assets/{po-BigskytD.js → po-BX2FiuW2.js} +0 -0
- /package/dist/web/assets/{poimandres-DuklniF7.js → poimandres-DzXOdZcN.js} +0 -0
- /package/dist/web/assets/{polar-e2pjN_sp.js → polar-DDRXgJzG.js} +0 -0
- /package/dist/web/assets/{portal-container-context-Cm5HipFr.js → portal-container-context-a399Hd9F.js} +0 -0
- /package/dist/web/assets/{postcss-DtAeYjWw.js → postcss-BHW6XBhx.js} +0 -0
- /package/dist/web/assets/{powerquery-Cr2PtehI.js → powerquery-B89c2NBb.js} +0 -0
- /package/dist/web/assets/{powershell-BxFTGLJ4.js → powershell-Dvxrd3Tu.js} +0 -0
- /package/dist/web/assets/{prisma-COWJiwdZ.js → prisma-lMK8EFLU.js} +0 -0
- /package/dist/web/assets/{prolog-DFvGKRx6.js → prolog-D0YrtMG4.js} +0 -0
- /package/dist/web/assets/{proto-CcK80lFt.js → proto-J0mM25aw.js} +0 -0
- /package/dist/web/assets/{puppet-BF60bZ4F.js → puppet-CUT0xkhh.js} +0 -0
- /package/dist/web/assets/{purescript-zL_69MzU.js → purescript-CoJWSGRp.js} +0 -0
- /package/dist/web/assets/{python-CIwKhfb_.js → python-BBrmuz6P.js} +0 -0
- /package/dist/web/assets/{qmldir-CXNe04Lo.js → qmldir-CUtzs4Q7.js} +0 -0
- /package/dist/web/assets/{qss-C8lAWe6G.js → qss-Dm1RZ8aL.js} +0 -0
- /package/dist/web/assets/{r-CL5ZVL3_.js → r-BGP7qZTe.js} +0 -0
- /package/dist/web/assets/{racket-BBfh0uh0.js → racket-K_6U1lqJ.js} +0 -0
- /package/dist/web/assets/{raku-o6E8R5Q3.js → raku-bNSiZeW_.js} +0 -0
- /package/dist/web/assets/{react-CBTnT4sY.js → react-X4jUntEf.js} +0 -0
- /package/dist/web/assets/{red-GomRF2Xp.js → red-uVeIEF0f.js} +0 -0
- /package/dist/web/assets/{refresh-cw-B06yaBrW.js → refresh-cw-B9iv7csb.js} +0 -0
- /package/dist/web/assets/{reg-_ffDIvx9.js → reg-DSm9GWjh.js} +0 -0
- /package/dist/web/assets/{regexp-Bl5sF65W.js → regexp-CljRm_j_.js} +0 -0
- /package/dist/web/assets/{rel-DgMdqDuj.js → rel-d3JcopZg.js} +0 -0
- /package/dist/web/assets/{resolve-theme-CRt_7wpd.js → resolve-theme-DLKmm-lE.js} +0 -0
- /package/dist/web/assets/{riscv-8b3-Ye4X.js → riscv-CJmaELjT.js} +0 -0
- /package/dist/web/assets/{ron-DrmU1KHP.js → ron-D0g9B1rW.js} +0 -0
- /package/dist/web/assets/{rose-pine-C_-E2tF1.js → rose-pine-DOrD2BSH.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-DEfhNlFH.js → rose-pine-dawn-CzZQ-LCF.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-BdvX3gdn.js → rose-pine-moon-CdmsYp7C.js} +0 -0
- /package/dist/web/assets/{rosmsg-B6GhGpd5.js → rosmsg-CmDth6pO.js} +0 -0
- /package/dist/web/assets/{rotate-ccw-COXu_9oj.js → rotate-ccw-DnuPzJRE.js} +0 -0
- /package/dist/web/assets/{rust-DmC97M1-.js → rust-DGdP6jiR.js} +0 -0
- /package/dist/web/assets/{sass-B0tJOHLM.js → sass-Dkgt2FKZ.js} +0 -0
- /package/dist/web/assets/{save-CtIGOjmx.js → save-DxonD4y1.js} +0 -0
- /package/dist/web/assets/{scala-n8zaFOXv.js → scala-BQLTON6C.js} +0 -0
- /package/dist/web/assets/{scheme-BKD9ztBU.js → scheme-DKlxUGCi.js} +0 -0
- /package/dist/web/assets/{sdbl-D8gB5-rS.js → sdbl-C3iZJQeZ.js} +0 -0
- /package/dist/web/assets/{send-BlyNJAzy.js → send-B-t5Mz26.js} +0 -0
- /package/dist/web/assets/{shallow-DYo6goJ4.js → shallow-jwPzEunk.js} +0 -0
- /package/dist/web/assets/{shellscript-BfXlf_Ao.js → shellscript-BRu-ZQAx.js} +0 -0
- /package/dist/web/assets/{shield-check-B0iGUiOK.js → shield-check-CdTiK1j2.js} +0 -0
- /package/dist/web/assets/{shield-off-BNLWBhcq.js → shield-off-DuetkXKM.js} +0 -0
- /package/dist/web/assets/{slack-dark-CylEddse.js → slack-dark-qkm_u2zz.js} +0 -0
- /package/dist/web/assets/{slack-ochin-w8wg8vuC.js → slack-ochin-BP8_8e4m.js} +0 -0
- /package/dist/web/assets/{smalltalk-hiAXA64u.js → smalltalk-Bnz4gt07.js} +0 -0
- /package/dist/web/assets/{snazzy-light-BwlV3Xty.js → snazzy-light-fbPZg5io.js} +0 -0
- /package/dist/web/assets/{solarized-dark-vmNGdykz.js → solarized-dark-CM0vTRoi.js} +0 -0
- /package/dist/web/assets/{solarized-light-DGv_0JXq.js → solarized-light-C4_W9mvs.js} +0 -0
- /package/dist/web/assets/{solidity-Y7QSH0B1.js → solidity-Bz6v1PoD.js} +0 -0
- /package/dist/web/assets/{splunk-BtgpLjHi.js → splunk-DyNxu-0i.js} +0 -0
- /package/dist/web/assets/{sql-CRHYhOia.js → sql-DXRL1gw1.js} +0 -0
- /package/dist/web/assets/{ssh-config-DOqSs3au.js → ssh-config-2z1QHuOG.js} +0 -0
- /package/dist/web/assets/{stylus-crCAEaBT.js → stylus-D4I-VaeY.js} +0 -0
- /package/dist/web/assets/{swift-eE3rT7TC.js → swift-C_2G-tIc.js} +0 -0
- /package/dist/web/assets/{synthwave-84-BWy14LBX.js → synthwave-84-osBjChRP.js} +0 -0
- /package/dist/web/assets/{system-verilog-DC0-DZM4.js → system-verilog-zQP2qPNU.js} +0 -0
- /package/dist/web/assets/{systemd-to5p6N8I.js → systemd-UGfjRSBM.js} +0 -0
- /package/dist/web/assets/{table-Bbc783ko.js → table-FLU8ei8b.js} +0 -0
- /package/dist/web/assets/{talonscript-DoTcU8wm.js → talonscript-C1XrFPwo.js} +0 -0
- /package/dist/web/assets/{tasl-cNZvUs95.js → tasl-B5raVn43.js} +0 -0
- /package/dist/web/assets/{tcl-mZE8sZH8.js → tcl-3-vbeMIc.js} +0 -0
- /package/dist/web/assets/{terraform-CqHPpIfu.js → terraform-a8VXZDm9.js} +0 -0
- /package/dist/web/assets/{text-wrap-CBT4Gxtg.js → text-wrap-B05VGRa-.js} +0 -0
- /package/dist/web/assets/{tokyo-night-Tp2rTIZd.js → tokyo-night-4SRhf-Hz.js} +0 -0
- /package/dist/web/assets/{toml-CU_vscgn.js → toml-B4N0i8wT.js} +0 -0
- /package/dist/web/assets/{trash-2-Bk4yYlck.js → trash-2-CTXlTflF.js} +0 -0
- /package/dist/web/assets/{triangle-alert-B8EYecpe.js → triangle-alert-BRq0VAfd.js} +0 -0
- /package/dist/web/assets/{tsv-ik7iFB9D.js → tsv-DA5k9Lvw.js} +0 -0
- /package/dist/web/assets/{tsx-BMvvMKiv.js → tsx-D9SZ8owC.js} +0 -0
- /package/dist/web/assets/{turtle-DBqKCq9o.js → turtle-BTHTvyY_.js} +0 -0
- /package/dist/web/assets/{typescript-DXJDmMZZ.js → typescript-DYzV9p5L.js} +0 -0
- /package/dist/web/assets/{typespec-CBseCMtZ.js → typespec-BC15eh7F.js} +0 -0
- /package/dist/web/assets/{typst-4oY25U9y.js → typst-BJUqzA6D.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-D1weanbj.js → use-is-mobile-DFWViA4S.js} +0 -0
- /package/dist/web/assets/{utils-BNRH507E.js → utils-C9OjCrou.js} +0 -0
- /package/dist/web/assets/{v-BXaZ_1oy.js → v-50Lps6oA.js} +0 -0
- /package/dist/web/assets/{vala-BImWJE7z.js → vala-8wiUvvrD.js} +0 -0
- /package/dist/web/assets/{vb-CLfj2_sy.js → vb-BlgPLt6B.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-_yyu5n32.js → vendor-xterm-BZOrwF_F.js} +0 -0
- /package/dist/web/assets/{verilog-BWa8Ed2b.js → verilog-CD9pVW5p.js} +0 -0
- /package/dist/web/assets/{vesper-BGj1cg6m.js → vesper-BrivW4PT.js} +0 -0
- /package/dist/web/assets/{vhdl-mByK7mlx.js → vhdl-Bunxfa7N.js} +0 -0
- /package/dist/web/assets/{viml-CugmlW5g.js → viml-DndUb2qM.js} +0 -0
- /package/dist/web/assets/{vitesse-black-vPdpy9TL.js → vitesse-black-BurZSkxW.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BuQK3dUh.js → vitesse-dark-8UShspzC.js} +0 -0
- /package/dist/web/assets/{vitesse-light-B3nz1XL9.js → vitesse-light-DghCAZ90.js} +0 -0
- /package/dist/web/assets/{vyper-DH_GTIHG.js → vyper-b-Clr-8u.js} +0 -0
- /package/dist/web/assets/{wasm-DfVbVJlW.js → wasm-C5v2T06Y.js} +0 -0
- /package/dist/web/assets/{wasm-C-LF9GjX.js → wasm-CpcKStkw.js} +0 -0
- /package/dist/web/assets/{wenyan-CzQkHyHx.js → wenyan-Dz2srLZR.js} +0 -0
- /package/dist/web/assets/{wgsl-Du_M1DW9.js → wgsl-CeD1-dRb.js} +0 -0
- /package/dist/web/assets/{wikitext-BWN_w5S7.js → wikitext-CyeS-9FH.js} +0 -0
- /package/dist/web/assets/{wit-BR13v5HD.js → wit-Kr-q-qBQ.js} +0 -0
- /package/dist/web/assets/{wolfram-BoXxp_Xl.js → wolfram-BKm8bmln.js} +0 -0
- /package/dist/web/assets/{x-nhriOX9o.js → x-YDu4m2Iq.js} +0 -0
- /package/dist/web/assets/{yaml-DpR_EF-d.js → yaml-2XSxomjM.js} +0 -0
- /package/dist/web/assets/{zenscript-C-EdUtUd.js → zenscript-CKIS34vf.js} +0 -0
- /package/dist/web/assets/{zig-BD4eA5nU.js → zig-DdHGNllX.js} +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hienlh/ppm",
|
|
3
|
-
"version": "0.18.
|
|
4
|
-
"description": "Personal Project Manager
|
|
3
|
+
"version": "0.18.13",
|
|
4
|
+
"description": "Personal Project Manager — mobile-first web IDE with AI assistance",
|
|
5
5
|
"author": "hienlh",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"module": "src/index.ts",
|
|
@@ -58,8 +58,8 @@ export function registerAutoStartCommands(program: Command): void {
|
|
|
58
58
|
const { disableAutoStart } = await import("../../services/autostart-register.ts");
|
|
59
59
|
|
|
60
60
|
try {
|
|
61
|
-
await disableAutoStart();
|
|
62
|
-
console.log(
|
|
61
|
+
const msg = await disableAutoStart();
|
|
62
|
+
console.log(` ✓ ${msg}\n`);
|
|
63
63
|
} catch (err: unknown) {
|
|
64
64
|
const msg = err instanceof Error ? err.message : String(err);
|
|
65
65
|
console.error(` ✗ Failed to disable auto-start: ${msg}\n`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import type { PpmConfig } from "../../types/config.ts";
|
|
3
|
+
import { isSecretConfigKey, redactSecretConfigValue } from "../../services/config-secret-keys.ts";
|
|
3
4
|
|
|
4
5
|
const C = {
|
|
5
6
|
reset: "\x1b[0m",
|
|
@@ -65,6 +66,10 @@ export function registerConfigCommands(program: Command): void {
|
|
|
65
66
|
.description("Get a config value (e.g. port, auth.enabled)")
|
|
66
67
|
.action(async (key: string) => {
|
|
67
68
|
try {
|
|
69
|
+
if (isSecretConfigKey(key)) {
|
|
70
|
+
console.log("(secret — use the API's masked status endpoint)");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
68
73
|
const { configService } = await import("../../services/config.service.ts");
|
|
69
74
|
configService.load();
|
|
70
75
|
const all = configService.getAll();
|
|
@@ -78,10 +83,14 @@ export function registerConfigCommands(program: Command): void {
|
|
|
78
83
|
}
|
|
79
84
|
process.exit(1);
|
|
80
85
|
}
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
// A leaf secret key is caught above; an ANCESTOR of one (e.g. "tunnel",
|
|
87
|
+
// "auth") reaches here with the raw object still intact, so every
|
|
88
|
+
// result — object or scalar — is deep-redacted before printing.
|
|
89
|
+
const display = redactSecretConfigValue(key, value);
|
|
90
|
+
if (typeof display === "object") {
|
|
91
|
+
console.log(JSON.stringify(display, null, 2));
|
|
83
92
|
} else {
|
|
84
|
-
console.log(`${C.bold}${key}${C.reset} = ${C.green}${String(
|
|
93
|
+
console.log(`${C.bold}${key}${C.reset} = ${C.green}${String(display)}${C.reset}`);
|
|
85
94
|
}
|
|
86
95
|
} catch (err) {
|
|
87
96
|
console.error(`${C.red}Error:${C.reset}`, (err as Error).message);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import { readFileSync, existsSync } from "node:fs";
|
|
3
3
|
import { getPpmDir } from "../../services/ppm-dir.ts";
|
|
4
|
+
import { redactSecrets } from "../../services/redact-secrets.ts";
|
|
4
5
|
|
|
5
6
|
interface DaemonStatus {
|
|
6
7
|
running: boolean;
|
|
@@ -149,13 +150,13 @@ export async function showStatus(options: { json?: boolean; all?: boolean }) {
|
|
|
149
150
|
const serverProcs = ppmProcs.filter((p) => p.args.includes("ppm") && !p.args.includes("pgrep"));
|
|
150
151
|
if (serverProcs.length > 0) {
|
|
151
152
|
console.log(`\n PPM-related processes (${serverProcs.length}):`);
|
|
152
|
-
for (const p of serverProcs) console.log(` PID ${p.pid} ${p.args}`);
|
|
153
|
+
for (const p of serverProcs) console.log(` PID ${p.pid} ${redactSecrets(p.args)}`);
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
// All cloudflared processes
|
|
156
157
|
if (cfProcs.length > 0) {
|
|
157
158
|
console.log(`\n Cloudflared processes (${cfProcs.length}):`);
|
|
158
|
-
for (const p of cfProcs) console.log(` PID ${p.pid} ${p.args}`);
|
|
159
|
+
for (const p of cfProcs) console.log(` PID ${p.pid} ${redactSecrets(p.args)}`);
|
|
159
160
|
}
|
|
160
161
|
|
|
161
162
|
if (serverProcs.length === 0 && cfProcs.length === 0 && !status.running) {
|
package/src/cli/commands/stop.ts
CHANGED
|
@@ -19,6 +19,31 @@ function killPid(pid: number, label: string): boolean {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* PIDs of cloudflared processes spawned for THIS PPM_HOME: their command line
|
|
24
|
+
* carries `--config <ppmDir>/cloudflared-*.yml` (and usually the binary under
|
|
25
|
+
* `<ppmDir>/bin`). Anything else on the machine is not ours to kill.
|
|
26
|
+
*/
|
|
27
|
+
function findOwnCloudflaredPids(): number[] {
|
|
28
|
+
if (process.platform !== "win32") return [];
|
|
29
|
+
try {
|
|
30
|
+
const script =
|
|
31
|
+
`Get-CimInstance Win32_Process -Filter "Name='cloudflared.exe'" | ForEach-Object { "$($_.ProcessId)\`t$($_.CommandLine)" }`;
|
|
32
|
+
const result = Bun.spawnSync(
|
|
33
|
+
["powershell.exe", "-NoProfile", "-NonInteractive", "-Command", script],
|
|
34
|
+
{ stdout: "pipe", stderr: "ignore" },
|
|
35
|
+
);
|
|
36
|
+
const ppmDir = getPpmDir().replace(/\//g, "\\").toLowerCase();
|
|
37
|
+
return result.stdout.toString().split("\n")
|
|
38
|
+
.map((line) => line.trim().split("\t"))
|
|
39
|
+
.filter((parts) => parts.length >= 2 && (parts[1] ?? "").replace(/\//g, "\\").toLowerCase().includes(ppmDir))
|
|
40
|
+
.map((parts) => parseInt(parts[0]!, 10))
|
|
41
|
+
.filter((pid) => !isNaN(pid));
|
|
42
|
+
} catch {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
22
47
|
function findPidsByName(name: string): number[] {
|
|
23
48
|
try {
|
|
24
49
|
if (process.platform === "win32") {
|
|
@@ -222,11 +247,14 @@ async function hardStop() {
|
|
|
222
247
|
try { process.kill(tunnelPid, 0); killPid(tunnelPid, "tunnel"); } catch {}
|
|
223
248
|
}
|
|
224
249
|
|
|
225
|
-
// Windows fallback: kill orphan cloudflared processes
|
|
250
|
+
// Windows fallback: kill orphan cloudflared processes — but only OURS.
|
|
251
|
+
// `taskkill /IM cloudflared.exe` would also take down every other
|
|
252
|
+
// cloudflared on the box: a second PPM_HOME instance, the user's own named
|
|
253
|
+
// tunnels, or another PPM's production tunnel (which then rotates its URL).
|
|
254
|
+
// Our processes are recognisable by the config/binary path under this
|
|
255
|
+
// PPM_HOME, so match on that and kill by exact PID.
|
|
226
256
|
if (process.platform === "win32") {
|
|
227
|
-
|
|
228
|
-
Bun.spawnSync(["taskkill", "/F", "/IM", "cloudflared.exe"], { stdout: "ignore", stderr: "ignore" });
|
|
229
|
-
} catch {}
|
|
257
|
+
for (const pid of findOwnCloudflaredPids()) killPid(pid, "orphan tunnel");
|
|
230
258
|
// process.kill on Windows is TerminateProcess — the supervisor's shutdown
|
|
231
259
|
// handler never runs, so its server grandchildren survive and keep the
|
|
232
260
|
// inherited listening-socket handle open (zombie port). Reap them here.
|
package/src/server/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { tunnelRoutes } from "./routes/tunnel.ts";
|
|
|
13
13
|
import { tunnelService } from "../services/tunnel.service.ts";
|
|
14
14
|
import { staticRoutes } from "./routes/static.ts";
|
|
15
15
|
import { projectScopedRouter } from "./routes/project-scoped.ts";
|
|
16
|
+
import { chatUnreadRoutes } from "./routes/chat-unread.ts";
|
|
16
17
|
import { postgresRoutes } from "./routes/postgres.ts";
|
|
17
18
|
import { databaseRoutes } from "./routes/database.ts";
|
|
18
19
|
import { fsBrowseRoutes } from "./routes/fs-browse.ts";
|
|
@@ -114,7 +115,10 @@ export const app = new Hono();
|
|
|
114
115
|
app.use("*", cors());
|
|
115
116
|
|
|
116
117
|
// Public endpoints (before auth)
|
|
117
|
-
|
|
118
|
+
// Per-boot id so a named-tunnel health probe can tell "my connector is dark"
|
|
119
|
+
// from "someone else's connector answers my hostname" across a restart.
|
|
120
|
+
const instanceId = crypto.randomUUID();
|
|
121
|
+
app.get("/api/health", (c) => c.json(ok({ status: "running", instanceId })));
|
|
118
122
|
app.get("/api/info", (c) => c.json(ok({
|
|
119
123
|
version: VERSION,
|
|
120
124
|
device_name: configService.get("device_name") || null,
|
|
@@ -180,7 +184,10 @@ app.route("/api/settings", settingsRoutes);
|
|
|
180
184
|
app.route("/api/settings/mcp", mcpRoutes);
|
|
181
185
|
app.route("/api/settings/themes", settingsThemesRoutes);
|
|
182
186
|
app.route("/api/tunnel", tunnelRoutes);
|
|
187
|
+
import { namedTunnelRoutes } from "./routes/named-tunnel.ts";
|
|
188
|
+
app.route("/api/tunnel/named", namedTunnelRoutes);
|
|
183
189
|
app.route("/api/projects", projectRoutes);
|
|
190
|
+
app.route("/api/chat", chatUnreadRoutes);
|
|
184
191
|
app.route("/api/project/:projectName", projectScopedRouter);
|
|
185
192
|
app.route("/api/postgres", postgresRoutes);
|
|
186
193
|
app.route("/api/db", databaseRoutes);
|
|
@@ -345,7 +352,12 @@ export async function startServer(options: {
|
|
|
345
352
|
signalSupervisor("SIGUSR1");
|
|
346
353
|
await waitForNewSupervisor(statusFile, supervisorPid);
|
|
347
354
|
} else {
|
|
348
|
-
|
|
355
|
+
// resume is a lifecycle action and outranks a pending retunnel
|
|
356
|
+
// (writeCmd overwrites it), so `false` here means a genuinely
|
|
357
|
+
// different lifecycle command is already queued.
|
|
358
|
+
if (!writeCmd("resume")) {
|
|
359
|
+
console.log(" Warning: another supervisor command is already pending; resume may be delayed.");
|
|
360
|
+
}
|
|
349
361
|
signalSupervisor("SIGUSR2", "resume");
|
|
350
362
|
await waitForServerReady(statusFile, port);
|
|
351
363
|
}
|
|
@@ -367,7 +379,9 @@ export async function startServer(options: {
|
|
|
367
379
|
|
|
368
380
|
if (state === "paused") {
|
|
369
381
|
console.log(" Supervisor is paused (max restarts). Sending resume...");
|
|
370
|
-
writeCmd("resume")
|
|
382
|
+
if (!writeCmd("resume")) {
|
|
383
|
+
console.log(" Warning: another supervisor command is already pending; resume may be delayed.");
|
|
384
|
+
}
|
|
371
385
|
signalSupervisor("SIGUSR2", "resume");
|
|
372
386
|
await waitForServerReady(statusFile, port);
|
|
373
387
|
return;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { clearSessionUnreadMany } from "../../services/db.service.ts";
|
|
3
|
+
import { ok, err } from "../../types/api.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Unread routes that are deliberately *not* project-scoped.
|
|
7
|
+
*
|
|
8
|
+
* The per-session read route lives under `/api/project/:projectName/chat`, but it never
|
|
9
|
+
* read the project segment — and being mounted there means the project middleware 404s
|
|
10
|
+
* first. That makes the entries most in need of clearing the ones that cannot be:
|
|
11
|
+
* a session whose project was renamed or deleted, or one PPM never recorded a project
|
|
12
|
+
* for. Unread state is keyed by session, so clearing it does not need a project at all.
|
|
13
|
+
*/
|
|
14
|
+
export const chatUnreadRoutes = new Hono();
|
|
15
|
+
|
|
16
|
+
/** POST /chat/sessions/read — mark many sessions as read, in one request */
|
|
17
|
+
chatUnreadRoutes.post("/sessions/read", async (c) => {
|
|
18
|
+
try {
|
|
19
|
+
const body = await c.req.json<{ sessionIds?: unknown }>();
|
|
20
|
+
const ids = Array.isArray(body.sessionIds)
|
|
21
|
+
? body.sessionIds.filter((v): v is string => typeof v === "string" && v.length > 0)
|
|
22
|
+
: null;
|
|
23
|
+
if (!ids) return c.json(err("sessionIds must be an array of session ids"), 400);
|
|
24
|
+
|
|
25
|
+
clearSessionUnreadMany(ids);
|
|
26
|
+
// One broadcast per session, but one request and one DB transaction: "Clear all" used
|
|
27
|
+
// to be N requests fanned out to every connected device.
|
|
28
|
+
const { broadcastGlobalEvent } = await import("../ws/chat.ts");
|
|
29
|
+
for (const id of ids) {
|
|
30
|
+
broadcastGlobalEvent({ type: "session:unread_changed", sessionId: id, unreadCount: 0, unreadType: null, projectName: "" });
|
|
31
|
+
}
|
|
32
|
+
return c.json(ok({ cleared: ids.length }));
|
|
33
|
+
} catch (e) {
|
|
34
|
+
return c.json(err((e as Error).message), 500);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named-tunnel setup API (`/api/tunnel/named`). Thin wrappers over
|
|
3
|
+
* `cloudflared-login.service` and `named-tunnel-setup.service` — all business
|
|
4
|
+
* logic lives there; this file is guard + (de)serialization only.
|
|
5
|
+
*/
|
|
6
|
+
import { Hono, type Context } from "hono";
|
|
7
|
+
import { ok, err } from "../../types/api.ts";
|
|
8
|
+
import { configService } from "../../services/config.service.ts";
|
|
9
|
+
import { resolveTunnelConfig, maskToken } from "../../services/named-tunnel/named-tunnel-config.ts";
|
|
10
|
+
import { cloudflaredLoginService } from "../../services/named-tunnel/cloudflared-login.service.ts";
|
|
11
|
+
import { namedTunnelSetupService, SetupError } from "../../services/named-tunnel/named-tunnel-setup.service.ts";
|
|
12
|
+
import { broadcastGlobalEvent } from "../ws/global.ts";
|
|
13
|
+
import { readStatus } from "../../services/supervisor-state.ts";
|
|
14
|
+
|
|
15
|
+
export const namedTunnelRoutes = new Hono();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A stable, guessable public hostname must never be one unauthenticated
|
|
19
|
+
* request away. `authMiddleware` already lets everything through when PPM
|
|
20
|
+
* auth is disabled (auth.ts:10-13 explicitly passes through), so the four
|
|
21
|
+
* mutating routes below enforce their own independent check, plus a
|
|
22
|
+
* same-origin check so a foreign page cannot drive them via ambient
|
|
23
|
+
* browser credentials.
|
|
24
|
+
*/
|
|
25
|
+
function assertMutationAllowed(c: Context): Response | null {
|
|
26
|
+
if (!configService.get("auth").enabled) {
|
|
27
|
+
return c.json(err("named-tunnel setup requires PPM authentication to be enabled"), 403);
|
|
28
|
+
}
|
|
29
|
+
const origin = c.req.header("origin");
|
|
30
|
+
if (origin) {
|
|
31
|
+
let originHost: string | null = null;
|
|
32
|
+
try { originHost = new URL(origin).host; } catch { originHost = null; }
|
|
33
|
+
const requestHost = new URL(c.req.url).host;
|
|
34
|
+
if (!originHost || originHost !== requestHost) {
|
|
35
|
+
return c.json(err("cross-origin request rejected"), 403);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
namedTunnelRoutes.get("/status", (c) => {
|
|
42
|
+
const resolved = resolveTunnelConfig(configService.get("tunnel"));
|
|
43
|
+
// Config says what the user *asked for*; the supervisor's status.json says
|
|
44
|
+
// what is *actually running*. When a named tunnel failed and the supervisor
|
|
45
|
+
// fell back to quick, the warning it left behind is the only signal the UI
|
|
46
|
+
// has that the two disagree — surface it instead of a silently wrong URL.
|
|
47
|
+
const live = readStatus();
|
|
48
|
+
const liveMode = live.tunnelMode === "named" || live.tunnelMode === "quick" ? live.tunnelMode : null;
|
|
49
|
+
const tunnelWarning = typeof live.tunnelWarning === "string" ? live.tunnelWarning : null;
|
|
50
|
+
return c.json(ok({
|
|
51
|
+
mode: resolved.mode,
|
|
52
|
+
liveMode,
|
|
53
|
+
hostname: resolved.hostname,
|
|
54
|
+
tunnelName: resolved.tunnelName,
|
|
55
|
+
tokenMasked: maskToken(resolved.token),
|
|
56
|
+
certState: namedTunnelSetupService.currentCertState(),
|
|
57
|
+
dismissed: resolved.dismissed,
|
|
58
|
+
login: cloudflaredLoginService.getLoginSnapshot(),
|
|
59
|
+
tunnelWarning,
|
|
60
|
+
// A stable public hostname needs PPM auth to gate it — the client uses
|
|
61
|
+
// this to hide the first-run popup entirely on an auth-disabled install
|
|
62
|
+
// rather than walking the user into a 403 on `POST /login`.
|
|
63
|
+
authEnabled: configService.get("auth").enabled,
|
|
64
|
+
}));
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// One-way, low-value target — must work even before auth is configured so
|
|
68
|
+
// the popup can be silenced regardless of the mutation guard above.
|
|
69
|
+
namedTunnelRoutes.post("/dismiss", (c) => {
|
|
70
|
+
const current = configService.get("tunnel");
|
|
71
|
+
configService.set("tunnel", { ...current, dismissed: true });
|
|
72
|
+
return c.json(ok({ dismissed: true }));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
namedTunnelRoutes.get("/zone", async (c) => {
|
|
76
|
+
try {
|
|
77
|
+
const info = await namedTunnelSetupService.readZoneInfo();
|
|
78
|
+
return c.json(ok({ zone: info.zone, proposedHostname: info.proposedHostname }));
|
|
79
|
+
} catch (e) {
|
|
80
|
+
const status = e instanceof SetupError ? e.status : 500;
|
|
81
|
+
return c.json(err((e as Error).message || "failed to read zone info"), status as 400 | 500);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
namedTunnelRoutes.post("/login", async (c) => {
|
|
86
|
+
const guard = assertMutationAllowed(c);
|
|
87
|
+
if (guard) return guard;
|
|
88
|
+
const relogin = c.req.query("relogin") === "1";
|
|
89
|
+
const snapshot = await cloudflaredLoginService.startLogin({ relogin });
|
|
90
|
+
return c.json(ok(snapshot));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
namedTunnelRoutes.post("/login/cancel", (c) => {
|
|
94
|
+
const guard = assertMutationAllowed(c);
|
|
95
|
+
if (guard) return guard;
|
|
96
|
+
cloudflaredLoginService.cancelLogin();
|
|
97
|
+
return c.json(ok({ state: "cancelled" as const }));
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
namedTunnelRoutes.post("/setup", async (c) => {
|
|
101
|
+
const guard = assertMutationAllowed(c);
|
|
102
|
+
if (guard) return guard;
|
|
103
|
+
|
|
104
|
+
let body: { hostname?: unknown };
|
|
105
|
+
try {
|
|
106
|
+
body = await c.req.json();
|
|
107
|
+
} catch {
|
|
108
|
+
return c.json(err("invalid JSON body"), 400);
|
|
109
|
+
}
|
|
110
|
+
const hostname = typeof body.hostname === "string" ? body.hostname.trim().toLowerCase() : "";
|
|
111
|
+
if (!hostname) return c.json(err("hostname is required"), 400);
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const result = await namedTunnelSetupService.runSetup(hostname);
|
|
115
|
+
if (result.ok === "pending") {
|
|
116
|
+
return c.json(ok({ hostname: result.hostname, tunnelName: result.tunnelName, pending: true, message: result.message }));
|
|
117
|
+
}
|
|
118
|
+
return c.json(ok({ hostname: result.hostname, tunnelName: result.tunnelName }));
|
|
119
|
+
} catch (e) {
|
|
120
|
+
const status = e instanceof SetupError ? e.status : 500;
|
|
121
|
+
const message = (e as Error).message || "setup failed";
|
|
122
|
+
broadcastGlobalEvent({ type: "tunnel:setup_error", message });
|
|
123
|
+
return c.json(err(message), status as 400 | 409 | 500);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
namedTunnelRoutes.post("/disable", async (c) => {
|
|
128
|
+
const guard = assertMutationAllowed(c);
|
|
129
|
+
if (guard) return guard;
|
|
130
|
+
await namedTunnelSetupService.disableNamedTunnel();
|
|
131
|
+
return c.json(ok({ mode: "quick" as const }));
|
|
132
|
+
});
|
|
@@ -3,6 +3,8 @@ import { tunnelService } from "../../services/tunnel.service.ts";
|
|
|
3
3
|
import { configService } from "../../services/config.service.ts";
|
|
4
4
|
import { getLocalIp } from "../../lib/network-utils.ts";
|
|
5
5
|
import { ok, err } from "../../types/api.ts";
|
|
6
|
+
import { getConfigValue } from "../../services/db.service.ts";
|
|
7
|
+
import { resolveTunnelConfig } from "../../services/named-tunnel/named-tunnel-config.ts";
|
|
6
8
|
|
|
7
9
|
export const tunnelRoutes = new Hono();
|
|
8
10
|
|
|
@@ -17,6 +19,17 @@ tunnelRoutes.get("/", (c) => {
|
|
|
17
19
|
|
|
18
20
|
/** POST /api/tunnel/start — start tunnel if not already running */
|
|
19
21
|
tunnelRoutes.post("/start", async (c) => {
|
|
22
|
+
// Named mode is exclusively supervisor-owned (spawnTunnel). This manual-start
|
|
23
|
+
// surface would otherwise spawn a SECOND cloudflared connector for the same
|
|
24
|
+
// hostname whenever `existing` is momentarily null — e.g. the restart/downgrade
|
|
25
|
+
// window right after a named tunnel is configured or disabled — and Cloudflare
|
|
26
|
+
// round-robins between the two, so ~half of requests hit whichever origin the
|
|
27
|
+
// stale connector still points at.
|
|
28
|
+
const cfg = resolveTunnelConfig(getConfigValue("tunnel"));
|
|
29
|
+
if (cfg.mode === "named") {
|
|
30
|
+
return c.json(err("named tunnel is supervisor-managed"), 409);
|
|
31
|
+
}
|
|
32
|
+
|
|
20
33
|
const existing = tunnelService.getTunnelUrl();
|
|
21
34
|
if (existing) {
|
|
22
35
|
return c.json(ok({ url: existing }));
|
package/src/server/ws/chat.ts
CHANGED
|
@@ -644,7 +644,10 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
|
|
|
644
644
|
}).catch(() => {});
|
|
645
645
|
// Persist unread to DB + broadcast to all tabs/devices
|
|
646
646
|
const doneSession = chatService.getSession(sessionId);
|
|
647
|
-
|
|
647
|
+
// The project comes from the connection, not from a separate write on open: a
|
|
648
|
+
// session PPM did not create has no metadata row until this runs, and this is the
|
|
649
|
+
// one place that knows both the session and its project.
|
|
650
|
+
incrementSessionUnread(sessionId, "done", doneSession?.title, entry.projectName || null);
|
|
648
651
|
broadcastGlobalEvent({ type: "session:unread_changed", sessionId, unreadCount: -1, unreadType: "done", projectName: entry.projectName || "", sessionTitle: doneSession?.title || null });
|
|
649
652
|
|
|
650
653
|
import("../../services/notification.service.ts").then(({ notificationService }) => {
|
|
@@ -666,7 +669,7 @@ async function startSessionConsumer(sessionId: string, providerId: string, conte
|
|
|
666
669
|
const nType = isQuestion ? "question" : "approval_request";
|
|
667
670
|
// Persist unread to DB + broadcast to all tabs/devices
|
|
668
671
|
const approvalSession = chatService.getSession(sessionId);
|
|
669
|
-
incrementSessionUnread(sessionId, nType, approvalSession?.title);
|
|
672
|
+
incrementSessionUnread(sessionId, nType, approvalSession?.title, entry.projectName || null);
|
|
670
673
|
broadcastGlobalEvent({ type: "session:unread_changed", sessionId, unreadCount: -1, unreadType: nType, projectName: entry.projectName || "", sessionTitle: approvalSession?.title || null });
|
|
671
674
|
|
|
672
675
|
import("../../services/notification.service.ts").then(({ notificationService }) => {
|
|
@@ -35,6 +35,23 @@ const METADATA_FILE = resolve(homedir(), ".ppm", "autostart.json");
|
|
|
35
35
|
* machine-global, so PPM_HOME does not isolate them. Registering or removing
|
|
36
36
|
* one from an isolated run would hijack the user's live service.
|
|
37
37
|
*/
|
|
38
|
+
/**
|
|
39
|
+
* Run a read-only status probe, treating a missing executable as "not registered".
|
|
40
|
+
*
|
|
41
|
+
* `Bun.spawnSync` *throws* when the binary is not on PATH — it does not return exit 127 —
|
|
42
|
+
* and `getAutoStartStatus()` sits on the `ppm start` (`server/index.ts`) and `ppm stop`
|
|
43
|
+
* paths. So a Linux box without systemd, or any container that has no `systemctl`, died
|
|
44
|
+
* there instead of falling back to spawning the supervisor directly.
|
|
45
|
+
*/
|
|
46
|
+
function probe(cmd: string[]): { stdout: string; exitCode: number } | null {
|
|
47
|
+
try {
|
|
48
|
+
const r = Bun.spawnSync({ cmd, stdout: "pipe", stderr: "ignore" });
|
|
49
|
+
return { stdout: r.stdout.toString(), exitCode: r.exitCode };
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
function skipIsolated(action: "enable" | "disable"): string {
|
|
39
56
|
const msg = `Auto-start ${action} skipped: PPM_HOME is isolated (${process.env.PPM_HOME}) — refusing to modify the real service manager`;
|
|
40
57
|
console.warn(` ${msg}`);
|
|
@@ -147,17 +164,19 @@ function statusMacOS(): AutoStartStatus {
|
|
|
147
164
|
const fileExists = existsSync(plistPath);
|
|
148
165
|
|
|
149
166
|
// Check if loaded
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
});
|
|
154
|
-
const output = result.stdout.toString();
|
|
155
|
-
const isLoaded = output.includes(PLIST_LABEL);
|
|
167
|
+
const isLoaded = probe(["launchctl", "list"])?.stdout.includes(PLIST_LABEL) ?? false;
|
|
168
|
+
return mapMacStatus(fileExists, isLoaded, plistPath);
|
|
169
|
+
}
|
|
156
170
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
171
|
+
/**
|
|
172
|
+
* The enabled/running split for launchd, kept pure so it can be tested anywhere.
|
|
173
|
+
*
|
|
174
|
+
* Mirrors systemd's is-enabled / is-active split: the plist on disk is the registration,
|
|
175
|
+
* the loaded job is the running state. Tying `enabled` to `isLoaded` made
|
|
176
|
+
* "enabled && !running" unsatisfiable, so `ppm start` could never take the launchd path
|
|
177
|
+
* and always spawned the supervisor directly — the regression this guards.
|
|
178
|
+
*/
|
|
179
|
+
export function mapMacStatus(fileExists: boolean, isLoaded: boolean, plistPath: string): AutoStartStatus {
|
|
161
180
|
return {
|
|
162
181
|
enabled: fileExists,
|
|
163
182
|
running: isLoaded,
|
|
@@ -272,18 +291,12 @@ function statusLinux(): AutoStartStatus {
|
|
|
272
291
|
const fileExists = existsSync(servicePath);
|
|
273
292
|
|
|
274
293
|
// Check enabled
|
|
275
|
-
const enabled =
|
|
276
|
-
|
|
277
|
-
stdout: "pipe", stderr: "ignore",
|
|
278
|
-
});
|
|
279
|
-
const isEnabled = enabled.stdout.toString().trim() === "enabled";
|
|
294
|
+
const enabled = probe(["systemctl", "--user", "is-enabled", "ppm.service"]);
|
|
295
|
+
const isEnabled = enabled?.stdout.trim() === "enabled";
|
|
280
296
|
|
|
281
297
|
// Check active
|
|
282
|
-
const active =
|
|
283
|
-
|
|
284
|
-
stdout: "pipe", stderr: "ignore",
|
|
285
|
-
});
|
|
286
|
-
const isActive = active.stdout.toString().trim() === "active";
|
|
298
|
+
const active = probe(["systemctl", "--user", "is-active", "ppm.service"]);
|
|
299
|
+
const isActive = active?.stdout.trim() === "active";
|
|
287
300
|
|
|
288
301
|
return {
|
|
289
302
|
enabled: isEnabled,
|
|
@@ -361,8 +374,8 @@ function statusWindows(): AutoStartStatus {
|
|
|
361
374
|
const fileExists = existsSync(vbsPath);
|
|
362
375
|
|
|
363
376
|
// Check if the scheduled task exists
|
|
364
|
-
const result =
|
|
365
|
-
const taskExists = result
|
|
377
|
+
const result = probe(buildSchtasksQueryCommand());
|
|
378
|
+
const taskExists = result?.exitCode === 0 && result.stdout.includes(TASK_NAME);
|
|
366
379
|
|
|
367
380
|
return {
|
|
368
381
|
enabled: taskExists,
|
|
@@ -387,14 +400,19 @@ export async function enableAutoStart(config: AutoStartConfig, opts?: { skipStar
|
|
|
387
400
|
throw new Error(`Auto-start not supported on ${platform}`);
|
|
388
401
|
}
|
|
389
402
|
|
|
390
|
-
/**
|
|
391
|
-
|
|
392
|
-
|
|
403
|
+
/**
|
|
404
|
+
* Disable auto-start for the current platform. Returns what happened, so a refusal is
|
|
405
|
+
* not reported as a success — `ppm autostart disable` printed "Auto-start disabled"
|
|
406
|
+
* even when the isolation guard had made it do nothing.
|
|
407
|
+
*/
|
|
408
|
+
export async function disableAutoStart(): Promise<string> {
|
|
409
|
+
if (isIsolatedPpmHome()) return skipIsolated("disable");
|
|
393
410
|
const platform = process.platform;
|
|
394
|
-
if (platform === "darwin")
|
|
395
|
-
if (platform === "linux")
|
|
396
|
-
if (platform === "win32")
|
|
397
|
-
throw new Error(`Auto-start not supported on ${platform}`);
|
|
411
|
+
if (platform === "darwin") await disableMacOS();
|
|
412
|
+
else if (platform === "linux") await disableLinux();
|
|
413
|
+
else if (platform === "win32") await disableWindows();
|
|
414
|
+
else throw new Error(`Auto-start not supported on ${platform}`);
|
|
415
|
+
return "Auto-start disabled. PPM will no longer start on boot.";
|
|
398
416
|
}
|
|
399
417
|
|
|
400
418
|
/** Get auto-start status for the current platform */
|
|
@@ -5,6 +5,7 @@ import { randomBytes } from "node:crypto";
|
|
|
5
5
|
import { VERSION } from "../version.ts";
|
|
6
6
|
import { configService } from "./config.service.ts";
|
|
7
7
|
import { getPpmDir } from "./ppm-dir.ts";
|
|
8
|
+
import { readStatus } from "./supervisor-state.ts";
|
|
8
9
|
|
|
9
10
|
const authFile = () => resolve(getPpmDir(), "cloud-auth.json");
|
|
10
11
|
const deviceFile = () => resolve(getPpmDir(), "cloud-device.json");
|
|
@@ -374,6 +375,27 @@ export async function listDevices(): Promise<DeviceInfo[]> {
|
|
|
374
375
|
// ─── Heartbeat ──────────────────────────────────────────────────────────
|
|
375
376
|
|
|
376
377
|
/** Send a single heartbeat to cloud (non-blocking, logs errors) */
|
|
378
|
+
/**
|
|
379
|
+
* Tunnel health as last observed by the supervisor, in the shape the cloud
|
|
380
|
+
* heartbeat sends. `degraded` means the tunnel process is up but its public
|
|
381
|
+
* hostname did not answer as us — the device is reachable locally, the link
|
|
382
|
+
* may not be.
|
|
383
|
+
*/
|
|
384
|
+
function describeTunnelHealth(): { status: "online" | "degraded"; tunnel_mode?: string; tunnel_warning?: string } {
|
|
385
|
+
try {
|
|
386
|
+
const s = readStatus();
|
|
387
|
+
const warning = typeof s.tunnelWarning === "string" && s.tunnelWarning ? s.tunnelWarning : null;
|
|
388
|
+
const mode = s.tunnelMode === "named" || s.tunnelMode === "quick" ? s.tunnelMode : undefined;
|
|
389
|
+
return {
|
|
390
|
+
status: warning ? "degraded" : "online",
|
|
391
|
+
...(mode ? { tunnel_mode: mode } : {}),
|
|
392
|
+
...(warning ? { tunnel_warning: warning } : {}),
|
|
393
|
+
};
|
|
394
|
+
} catch {
|
|
395
|
+
return { status: "online" }; // never let a status-file hiccup stop the heartbeat
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
377
399
|
export async function sendHeartbeat(tunnelUrl: string): Promise<boolean> {
|
|
378
400
|
const device = getCloudDevice();
|
|
379
401
|
if (!device) return false;
|
|
@@ -385,7 +407,10 @@ export async function sendHeartbeat(tunnelUrl: string): Promise<boolean> {
|
|
|
385
407
|
body: JSON.stringify({
|
|
386
408
|
secret_key: device.secret_key,
|
|
387
409
|
tunnel_url: tunnelUrl,
|
|
388
|
-
|
|
410
|
+
// A heartbeat that always says "online" makes the cloud link redirect
|
|
411
|
+
// into a hostname that stopped answering minutes ago. Report what the
|
|
412
|
+
// supervisor actually observed so the permanent link can say why.
|
|
413
|
+
...describeTunnelHealth(),
|
|
389
414
|
name: device.name,
|
|
390
415
|
}),
|
|
391
416
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared denylist for config keys that must never be echoed back by a
|
|
3
|
+
* config-dump surface (`ppm config get`, the extension RPC `workspace:config:get`,
|
|
4
|
+
* and any future status/masking endpoint). Both consumers keep their own local
|
|
5
|
+
* `getNestedValue` implementation — verified not shared — so this list is the
|
|
6
|
+
* one place the two surfaces stay in sync.
|
|
7
|
+
*
|
|
8
|
+
* Exact-key based, not a blanket group hide: sibling fields like
|
|
9
|
+
* `tunnel.mode` or `tunnel.hostname` stay visible so the UI and
|
|
10
|
+
* `ppm config get tunnel.mode` keep working.
|
|
11
|
+
*/
|
|
12
|
+
export const SECRET_CONFIG_KEYS: readonly string[] = [
|
|
13
|
+
"tunnel.namedTunnelToken",
|
|
14
|
+
"auth.token",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
/** Marker shown in place of a redacted secret value. Never the empty string, so a caller can tell "masked" apart from "absent". */
|
|
18
|
+
const REDACTED = "[REDACTED]";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* True when `key` is a secret key or a dotted descendant of one (e.g. a
|
|
22
|
+
* caller requesting `tunnel.namedTunnelToken.raw` would still be blocked).
|
|
23
|
+
*/
|
|
24
|
+
export function isSecretConfigKey(key: string): boolean {
|
|
25
|
+
return SECRET_CONFIG_KEYS.some((entry) => key === entry || key.startsWith(`${entry}.`));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Deep-redacts secret leaves inside a config-dump result. `isSecretConfigKey`
|
|
30
|
+
* alone only catches a request for the secret key itself or a descendant of
|
|
31
|
+
* it — a request for an ANCESTOR (`ppm config get tunnel`, or `auth`) walks
|
|
32
|
+
* right past that check and returns the whole object, `namedTunnelToken`/
|
|
33
|
+
* `token` included. This walks `value`'s own keys, rebuilding the dotted path
|
|
34
|
+
* as it goes, and swaps in the masked marker wherever that path matches the
|
|
35
|
+
* denylist — every other key is copied through unchanged, so the shape of
|
|
36
|
+
* the object a caller gets back never changes, only the secret's value does.
|
|
37
|
+
*/
|
|
38
|
+
export function redactSecretConfigValue(key: string, value: unknown): unknown {
|
|
39
|
+
if (isSecretConfigKey(key)) return REDACTED;
|
|
40
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
41
|
+
const out: Record<string, unknown> = {};
|
|
42
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
|
43
|
+
out[k] = redactSecretConfigValue(key ? `${key}.${k}` : k, v);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
|
|
17
17
|
/** Top-level config keys stored in the config table (not projects) */
|
|
18
18
|
const CONFIG_TABLE_KEYS: (keyof PpmConfig)[] = [
|
|
19
|
-
"device_name", "port", "host", "theme", "auth", "ai", "telegram", "clawbot", "query_audit",
|
|
19
|
+
"device_name", "port", "host", "theme", "auth", "ai", "telegram", "clawbot", "query_audit", "tunnel",
|
|
20
20
|
];
|
|
21
21
|
|
|
22
22
|
/** File filter config keys stored in the config table */
|