@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
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse `~/.cloudflared/cert.pem` — the credential `cloudflared tunnel login` writes.
|
|
3
|
+
* State is always derived by parsing the file, never `existsSync`: a truncated,
|
|
4
|
+
* foreign, or revoked cert must never look "authenticated" just because a file
|
|
5
|
+
* happens to sit at the expected path.
|
|
6
|
+
*/
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
|
|
11
|
+
export interface ArgoCert {
|
|
12
|
+
zoneID: string;
|
|
13
|
+
accountID: string;
|
|
14
|
+
apiToken: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type CertState =
|
|
18
|
+
| { kind: "absent" }
|
|
19
|
+
| { kind: "unparseable"; reason: string }
|
|
20
|
+
| { kind: "parsed"; cert: ArgoCert };
|
|
21
|
+
|
|
22
|
+
const HEX32 = /^[0-9a-f]{32}$/;
|
|
23
|
+
// The real-world label cloudflared ships today.
|
|
24
|
+
const STRICT_BLOCK = /-----BEGIN ARGO TUNNEL TOKEN-----([\s\S]*?)-----END ARGO TUNNEL TOKEN-----/;
|
|
25
|
+
// Fallback for a future cloudflared build that renames the label — accept any
|
|
26
|
+
// "...TOKEN..." PEM block rather than hard-failing every login on a cosmetic
|
|
27
|
+
// rename, while still preferring the known-good label as the first match.
|
|
28
|
+
const GENERIC_TOKEN_BLOCK = /-----BEGIN ([A-Z0-9 ]*TOKEN[A-Z0-9 ]*)-----([\s\S]*?)-----END \1-----/;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* `cloudflared` decides this location, not PPM — it is a deliberate real-`homedir()`
|
|
32
|
+
* exception (see CLAUDE.md's "PPM Directory" section): `getPpmDir()` would point
|
|
33
|
+
* PPM's own data elsewhere and never find the user's actual login credential.
|
|
34
|
+
*/
|
|
35
|
+
export function getOriginCertPath(): string {
|
|
36
|
+
return process.env.TUNNEL_ORIGIN_CERT || resolve(homedir(), ".cloudflared", "cert.pem");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Extract the token PEM block's base64 payload, trying the strict label first. */
|
|
40
|
+
function extractPayload(pem: string): string | null {
|
|
41
|
+
const strict = pem.match(STRICT_BLOCK);
|
|
42
|
+
if (strict) return strict[1]!;
|
|
43
|
+
const generic = pem.match(GENERIC_TOKEN_BLOCK);
|
|
44
|
+
if (generic) return generic[2]!;
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Read and parse the origin cert. Never throws — every failure mode collapses
|
|
50
|
+
* to `unparseable` with a reason string that is safe to log (it never contains
|
|
51
|
+
* the base64 payload or any field value, so a leaked apiToken can't hide in it).
|
|
52
|
+
*/
|
|
53
|
+
export function readOriginCertState(path?: string): CertState {
|
|
54
|
+
const certPath = path ?? getOriginCertPath();
|
|
55
|
+
if (!existsSync(certPath)) return { kind: "absent" };
|
|
56
|
+
|
|
57
|
+
let raw: string;
|
|
58
|
+
try {
|
|
59
|
+
raw = readFileSync(certPath, "utf-8");
|
|
60
|
+
} catch {
|
|
61
|
+
return { kind: "unparseable", reason: "cert.pem could not be read" };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const payload = extractPayload(raw);
|
|
65
|
+
if (payload == null) return { kind: "unparseable", reason: "no TOKEN PEM block found" };
|
|
66
|
+
|
|
67
|
+
let decoded: string;
|
|
68
|
+
try {
|
|
69
|
+
decoded = Buffer.from(payload.replace(/\s+/g, ""), "base64").toString("utf-8");
|
|
70
|
+
} catch {
|
|
71
|
+
return { kind: "unparseable", reason: "PEM block is not valid base64" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let json: unknown;
|
|
75
|
+
try {
|
|
76
|
+
json = JSON.parse(decoded);
|
|
77
|
+
} catch {
|
|
78
|
+
return { kind: "unparseable", reason: "PEM payload is not valid JSON" };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (typeof json !== "object" || json === null) {
|
|
82
|
+
return { kind: "unparseable", reason: "PEM payload is not a JSON object" };
|
|
83
|
+
}
|
|
84
|
+
const obj = json as Record<string, unknown>;
|
|
85
|
+
const { zoneID, accountID, apiToken } = obj;
|
|
86
|
+
if (typeof zoneID !== "string" || !zoneID || typeof accountID !== "string" || !accountID ||
|
|
87
|
+
typeof apiToken !== "string" || !apiToken) {
|
|
88
|
+
return { kind: "unparseable", reason: "cert is missing zoneID/accountID/apiToken" };
|
|
89
|
+
}
|
|
90
|
+
if (!HEX32.test(zoneID) || !HEX32.test(accountID)) {
|
|
91
|
+
return { kind: "unparseable", reason: "zoneID/accountID are not 32-char hex ids" };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { kind: "parsed", cert: { zoneID, accountID, apiToken } };
|
|
95
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-shot cloudflared invocations (`tunnel create`, `route dns`, `tunnel
|
|
3
|
+
* token`) — distinct from the long-running login process in
|
|
4
|
+
* `cloudflared-login.service.ts`, which needs incremental stdout/stderr reads
|
|
5
|
+
* instead of a single awaited result.
|
|
6
|
+
*/
|
|
7
|
+
import { ensureCloudflared } from "../cloudflared.service.ts";
|
|
8
|
+
import { killProcessTree } from "../windows-process-tree.ts";
|
|
9
|
+
|
|
10
|
+
export interface RunCloudflaredResult {
|
|
11
|
+
code: number;
|
|
12
|
+
stdout: string;
|
|
13
|
+
stderr: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Short one-shot calls must never hang setup indefinitely on a network stall. */
|
|
17
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Spawn cloudflared with `args`, capture both streams, and enforce a timeout.
|
|
21
|
+
* Never logs `args` — phase 1b's argv builders already keep them secret-free
|
|
22
|
+
* (the run token travels via `--token-file`, never argv), so this stays that
|
|
23
|
+
* way rather than re-introducing a leak at the logging layer.
|
|
24
|
+
*/
|
|
25
|
+
export async function runCloudflared(
|
|
26
|
+
args: string[],
|
|
27
|
+
opts: { timeoutMs?: number } = {},
|
|
28
|
+
): Promise<RunCloudflaredResult> {
|
|
29
|
+
const bin = await ensureCloudflared();
|
|
30
|
+
const proc = Bun.spawn([bin, ...args], { stdout: "pipe", stderr: "pipe" });
|
|
31
|
+
|
|
32
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
33
|
+
let timedOut = false;
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
timedOut = true;
|
|
36
|
+
killProcessTree(proc.pid);
|
|
37
|
+
}, timeoutMs);
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const [stdout, stderr, code] = await Promise.all([
|
|
41
|
+
new Response(proc.stdout).text(),
|
|
42
|
+
new Response(proc.stderr).text(),
|
|
43
|
+
proc.exited,
|
|
44
|
+
]);
|
|
45
|
+
if (timedOut) throw new Error(`cloudflared timed out after ${timeoutMs}ms`);
|
|
46
|
+
return { code, stdout, stderr };
|
|
47
|
+
} finally {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure/self-contained helpers for the login session state machine, split out
|
|
3
|
+
* of `cloudflared-login.service.ts` purely to keep that file under the
|
|
4
|
+
* project's line-count guideline — none of these hold their own module state.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, renameSync } from "node:fs";
|
|
7
|
+
import { getOriginCertPath } from "./cloudflared-cert.ts";
|
|
8
|
+
import { resolveTunnelConfig } from "./named-tunnel-config.ts";
|
|
9
|
+
import { configService } from "../config.service.ts";
|
|
10
|
+
import { extractLoginUrl, isLoginSuccess } from "./login-output-parser.ts";
|
|
11
|
+
|
|
12
|
+
const VERIFY_TIMEOUT_MS = 8_000;
|
|
13
|
+
|
|
14
|
+
export type CertLiveCheck = "valid" | "invalid" | "unreachable";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Live-checks an apiToken against Cloudflare, distinguishing an authoritative
|
|
18
|
+
* rejection from simply failing to ask. This distinction is the whole point:
|
|
19
|
+
* the caller renames a stale cert.pem aside on "invalid" (Cloudflare itself
|
|
20
|
+
* said the token is dead) but must NOT do that on "unreachable" (a network
|
|
21
|
+
* blip, timeout, rate limit, or Cloudflare-side outage) — a user on a flaky
|
|
22
|
+
* connection who presses "Set up" must never have a perfectly working
|
|
23
|
+
* credential discarded just because the verify call itself didn't land.
|
|
24
|
+
*/
|
|
25
|
+
export async function verifyCertLive(apiToken: string): Promise<CertLiveCheck> {
|
|
26
|
+
let res: Response;
|
|
27
|
+
try {
|
|
28
|
+
res = await fetch("https://api.cloudflare.com/client/v4/user/tokens/verify", {
|
|
29
|
+
headers: { Authorization: `Bearer ${apiToken}` },
|
|
30
|
+
signal: AbortSignal.timeout(VERIFY_TIMEOUT_MS),
|
|
31
|
+
});
|
|
32
|
+
} catch {
|
|
33
|
+
return "unreachable"; // network error, DNS failure, or the bounded timeout firing
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 401/403: Cloudflare rejected the token outright. 429/5xx: rate-limited or
|
|
37
|
+
// Cloudflare's own outage — neither says anything about the token itself.
|
|
38
|
+
if (res.status === 401 || res.status === 403) return "invalid";
|
|
39
|
+
if (res.status === 429 || res.status >= 500) return "unreachable";
|
|
40
|
+
|
|
41
|
+
const json = (await res.json().catch(() => null)) as { success?: boolean } | null;
|
|
42
|
+
if (json?.success === true) return "valid";
|
|
43
|
+
if (json?.success === false) return "invalid";
|
|
44
|
+
return "unreachable"; // an unexpected response shape is not an authoritative rejection
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** True when no `tunnel` identity is pinned yet, or the cert matches the pinned one. */
|
|
48
|
+
export function pinsMatch(cert: { zoneID: string; accountID: string }): boolean {
|
|
49
|
+
const resolved = resolveTunnelConfig(configService.get("tunnel"));
|
|
50
|
+
if (resolved.mode !== "named") return true;
|
|
51
|
+
return resolved.zoneID === cert.zoneID && resolved.accountID === cert.accountID;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Rename an existing cert.pem aside so a stale/foreign cert can never block a fresh login. */
|
|
55
|
+
export function renameCertAside(): void {
|
|
56
|
+
const path = getOriginCertPath();
|
|
57
|
+
if (!existsSync(path)) return;
|
|
58
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
59
|
+
renameSync(path, `${path}.bak-${stamp}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Read a login process's stdout/stderr stream incrementally: report the URL
|
|
64
|
+
* as soon as it appears, and signal success the moment the success line
|
|
65
|
+
* shows up (without waiting for the process to exit). `isSuperseded` lets the
|
|
66
|
+
* caller bail out the instant this stream stops being the "current" one
|
|
67
|
+
* (cancel/retry/timeout raced ahead of us).
|
|
68
|
+
*/
|
|
69
|
+
export async function pumpLoginStream(
|
|
70
|
+
stream: ReadableStream<Uint8Array>,
|
|
71
|
+
hooks: { isSuperseded: () => boolean; hasUrl: () => boolean; onUrl: (url: string) => void; onSuccess: () => void },
|
|
72
|
+
): Promise<void> {
|
|
73
|
+
const reader = stream.getReader();
|
|
74
|
+
const decoder = new TextDecoder();
|
|
75
|
+
let buffered = "";
|
|
76
|
+
try {
|
|
77
|
+
while (true) {
|
|
78
|
+
const { done, value } = await reader.read();
|
|
79
|
+
if (done) return;
|
|
80
|
+
if (hooks.isSuperseded()) return;
|
|
81
|
+
|
|
82
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
83
|
+
buffered += chunk;
|
|
84
|
+
|
|
85
|
+
if (!hooks.hasUrl()) {
|
|
86
|
+
const url = extractLoginUrl(chunk) ?? extractLoginUrl(buffered);
|
|
87
|
+
if (url) hooks.onUrl(url);
|
|
88
|
+
}
|
|
89
|
+
if (isLoginSuccess(chunk) || isLoginSuccess(buffered)) {
|
|
90
|
+
hooks.onSuccess();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch {
|
|
95
|
+
// Stream closed underneath us (process killed) — a terminal state has
|
|
96
|
+
// already been (or is about to be) set by whichever timer/cancel did it.
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login session singleton behind the "log in to Cloudflare" popup.
|
|
3
|
+
*
|
|
4
|
+
* One process-per-machine at a time: a second `startLogin` call while one is
|
|
5
|
+
* live (`waiting`/`slow`) just returns the current snapshot instead of
|
|
6
|
+
* spawning a competing `cloudflared tunnel login`. Every terminal state
|
|
7
|
+
* (`success`/`timeout`/`cancelled`/`error`) drops the process ref, so the
|
|
8
|
+
* *next* call after a terminal state always starts a genuinely fresh process
|
|
9
|
+
* with a fresh URL — a killed session's URL is gone for good, there is no
|
|
10
|
+
* "cert appears later" path once the process is dead.
|
|
11
|
+
*/
|
|
12
|
+
import { readOriginCertState } from "./cloudflared-cert.ts";
|
|
13
|
+
import { loginArgs } from "./named-tunnel-args.ts";
|
|
14
|
+
import { ensureCloudflared } from "../cloudflared.service.ts";
|
|
15
|
+
import { killProcessTree } from "../windows-process-tree.ts";
|
|
16
|
+
import { broadcastGlobalEvent } from "../../server/ws/global.ts";
|
|
17
|
+
import { verifyCertLive, pinsMatch, renameCertAside, pumpLoginStream } from "./cloudflared-login-helpers.ts";
|
|
18
|
+
|
|
19
|
+
export type LoginState = "idle" | "waiting" | "slow" | "success" | "timeout" | "cancelled" | "error";
|
|
20
|
+
export interface LoginSnapshot { state: LoginState; url: string | null; message: string | null }
|
|
21
|
+
|
|
22
|
+
export const LOGIN_SLOW_MS = 60_000;
|
|
23
|
+
export const LOGIN_TIMEOUT_MS = 300_000;
|
|
24
|
+
const CERT_POLL_MS = 2_000;
|
|
25
|
+
|
|
26
|
+
interface Session {
|
|
27
|
+
state: LoginState;
|
|
28
|
+
url: string | null;
|
|
29
|
+
message: string | null;
|
|
30
|
+
proc: ReturnType<typeof Bun.spawn> | null;
|
|
31
|
+
slowTimer: ReturnType<typeof setTimeout> | null;
|
|
32
|
+
killTimer: ReturnType<typeof setTimeout> | null;
|
|
33
|
+
certPollTimer: ReturnType<typeof setInterval> | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function freshSession(): Session {
|
|
37
|
+
return { state: "idle", url: null, message: null, proc: null, slowTimer: null, killTimer: null, certPollTimer: null };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let session: Session = freshSession();
|
|
41
|
+
|
|
42
|
+
export function getLoginSnapshot(): LoginSnapshot {
|
|
43
|
+
return { state: session.state, url: session.url, message: session.message };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function clearTimers(): void {
|
|
47
|
+
if (session.slowTimer) clearTimeout(session.slowTimer);
|
|
48
|
+
if (session.killTimer) clearTimeout(session.killTimer);
|
|
49
|
+
if (session.certPollTimer) clearInterval(session.certPollTimer);
|
|
50
|
+
session.slowTimer = null;
|
|
51
|
+
session.killTimer = null;
|
|
52
|
+
session.certPollTimer = null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Move to a terminal state: clear timers, drop the process ref, broadcast. */
|
|
56
|
+
function setTerminal(state: LoginState, message: string | null): void {
|
|
57
|
+
clearTimers();
|
|
58
|
+
session.proc = null;
|
|
59
|
+
session.state = state;
|
|
60
|
+
session.message = message;
|
|
61
|
+
broadcastGlobalEvent({ type: "tunnel:login_state", state, message: message ?? undefined });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Wraps `pumpLoginStream` with this module's session-singleton hooks. */
|
|
65
|
+
function pumpStream(stream: ReadableStream<Uint8Array>, proc: ReturnType<typeof Bun.spawn>): Promise<void> {
|
|
66
|
+
return pumpLoginStream(stream, {
|
|
67
|
+
isSuperseded: () => session.proc !== proc,
|
|
68
|
+
hasUrl: () => session.url != null,
|
|
69
|
+
onUrl: (url) => {
|
|
70
|
+
session.url = url;
|
|
71
|
+
broadcastGlobalEvent({ type: "tunnel:login_url", url });
|
|
72
|
+
},
|
|
73
|
+
onSuccess: () => setTerminal("success", "logged in"),
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Start (or return the live) login session.
|
|
79
|
+
*
|
|
80
|
+
* The cert-present shortcut is intentionally re-verified on every call — not
|
|
81
|
+
* a bare `existsSync` — because a replaced or revoked cert must never
|
|
82
|
+
* silently pass as "already logged in".
|
|
83
|
+
*/
|
|
84
|
+
export async function startLogin(opts: { relogin?: boolean } = {}): Promise<LoginSnapshot> {
|
|
85
|
+
if (session.state === "waiting" || session.state === "slow") {
|
|
86
|
+
return getLoginSnapshot();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const relogin = !!opts.relogin;
|
|
90
|
+
if (!relogin) {
|
|
91
|
+
const certState = readOriginCertState();
|
|
92
|
+
if (certState.kind === "parsed") {
|
|
93
|
+
const liveCheck = await verifyCertLive(certState.cert.apiToken);
|
|
94
|
+
|
|
95
|
+
// WHY: a network blip must never discard a working credential. Only an
|
|
96
|
+
// authoritative "Cloudflare said no" (invalid) or a confirmed pin
|
|
97
|
+
// mismatch earns a rename-aside below; "unreachable" means the check
|
|
98
|
+
// itself failed to run, so cert.pem is left untouched and the user
|
|
99
|
+
// gets a clear, retryable error instead of an unwanted fresh login.
|
|
100
|
+
if (liveCheck === "unreachable") {
|
|
101
|
+
setTerminal("error", "không kết nối được Cloudflare để kiểm tra đăng nhập — thử lại");
|
|
102
|
+
return getLoginSnapshot();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (liveCheck === "valid" && pinsMatch(certState.cert)) {
|
|
106
|
+
session = freshSession();
|
|
107
|
+
session.state = "success";
|
|
108
|
+
session.message = "already logged in";
|
|
109
|
+
return getLoginSnapshot();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// liveCheck === "invalid" (Cloudflare authoritatively rejected the
|
|
113
|
+
// token), or valid but pinned to a different account/zone: this cert
|
|
114
|
+
// can never pass the shortcut again as-is — move it aside now instead
|
|
115
|
+
// of leaving a bad file to keep silently triggering the same
|
|
116
|
+
// fallthrough on every future login attempt.
|
|
117
|
+
renameCertAside();
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
// Explicit ?relogin=1 — the user deliberately pressed "Đăng nhập lại",
|
|
121
|
+
// so this renames unconditionally regardless of network reachability.
|
|
122
|
+
renameCertAside();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const bin = await ensureCloudflared();
|
|
126
|
+
const proc = Bun.spawn([bin, ...loginArgs()], { stdout: "pipe", stderr: "pipe" });
|
|
127
|
+
session = { state: "waiting", url: null, message: null, proc, slowTimer: null, killTimer: null, certPollTimer: null };
|
|
128
|
+
|
|
129
|
+
session.slowTimer = setTimeout(() => {
|
|
130
|
+
if (session.proc !== proc) return;
|
|
131
|
+
session.state = "slow";
|
|
132
|
+
broadcastGlobalEvent({ type: "tunnel:login_state", state: "slow" });
|
|
133
|
+
}, LOGIN_SLOW_MS);
|
|
134
|
+
|
|
135
|
+
session.killTimer = setTimeout(() => {
|
|
136
|
+
if (session.proc !== proc) return;
|
|
137
|
+
killProcessTree(proc.pid);
|
|
138
|
+
setTerminal("timeout", "login timed out after 5 minutes");
|
|
139
|
+
}, LOGIN_TIMEOUT_MS);
|
|
140
|
+
|
|
141
|
+
session.certPollTimer = setInterval(() => {
|
|
142
|
+
if (session.proc !== proc) return;
|
|
143
|
+
if (readOriginCertState().kind === "parsed") setTerminal("success", "logged in");
|
|
144
|
+
}, CERT_POLL_MS);
|
|
145
|
+
|
|
146
|
+
Promise.all([pumpStream(proc.stdout, proc), pumpStream(proc.stderr, proc)])
|
|
147
|
+
.then(async () => {
|
|
148
|
+
if (session.proc !== proc) return; // already terminal
|
|
149
|
+
const code = await proc.exited;
|
|
150
|
+
if (session.proc !== proc) return;
|
|
151
|
+
if (code === 0) {
|
|
152
|
+
setTerminal("success", "logged in");
|
|
153
|
+
} else {
|
|
154
|
+
setTerminal("error", `cloudflared exited with code ${code}`);
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
.catch(() => {});
|
|
158
|
+
|
|
159
|
+
return getLoginSnapshot();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Kill the live process immediately (if any) and mark the session cancelled. */
|
|
163
|
+
export function cancelLogin(): void {
|
|
164
|
+
if (session.proc) killProcessTree(session.proc.pid);
|
|
165
|
+
setTerminal("cancelled", "cancelled");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Orphaned login process on server crash/restart would otherwise wait forever
|
|
169
|
+
// for a browser callback that lands nowhere.
|
|
170
|
+
process.on("exit", () => {
|
|
171
|
+
if (session.proc) {
|
|
172
|
+
try { killProcessTree(session.proc.pid); } catch {}
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Aggregate for callers that need a single patchable seam (the HTTP routes) —
|
|
178
|
+
* route tests monkey-patch these properties directly rather than reaching for
|
|
179
|
+
* `mock.module`, which replaces the module for every importer process-wide and
|
|
180
|
+
* would poison the direct-function unit tests in this same file's test suite.
|
|
181
|
+
*/
|
|
182
|
+
export const cloudflaredLoginService = { getLoginSnapshot, startLogin, cancelLogin };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hostname shape rules for the named tunnel.
|
|
3
|
+
*
|
|
4
|
+
* Exactly one label above the zone, never the apex, never `www`. Two reasons,
|
|
5
|
+
* not just a rule to copy: `cloudflared route dns` never errors on a hostname
|
|
6
|
+
* outside the zone — it silently appends the zone instead (e.g. asking for
|
|
7
|
+
* `ppm-probe.ppm.sh` under zone `hienle.tech` creates `ppm-probe.ppm.sh.hienle.tech`),
|
|
8
|
+
* so PPM must refuse before calling it; and Cloudflare's free Universal SSL
|
|
9
|
+
* certificate only covers one level of subdomain, so a deeper label parses fine
|
|
10
|
+
* here but serves a TLS error to every visitor.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type HostnameCheck = { ok: true } | { ok: false; reason: string };
|
|
14
|
+
|
|
15
|
+
const LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
16
|
+
const MAX_HOSTNAME_LENGTH = 253;
|
|
17
|
+
|
|
18
|
+
/** Default proposal shown to the user: `ppm.<zone>`. */
|
|
19
|
+
export function proposeHostname(zone: string): string {
|
|
20
|
+
return `ppm.${zone.trim().toLowerCase()}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Combine a user-typed prefix with the pinned zone. */
|
|
24
|
+
export function buildHostname(prefix: string, zone: string): string {
|
|
25
|
+
return `${prefix.trim().toLowerCase()}.${zone.trim().toLowerCase()}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function validateHostname(hostname: string, zone: string): HostnameCheck {
|
|
29
|
+
const host = hostname.trim().toLowerCase();
|
|
30
|
+
const z = zone.trim().toLowerCase();
|
|
31
|
+
if (!host || !z) return { ok: false, reason: "hostname and zone are required" };
|
|
32
|
+
if (host.length > MAX_HOSTNAME_LENGTH) return { ok: false, reason: "hostname exceeds 253 characters" };
|
|
33
|
+
|
|
34
|
+
if (host === z) {
|
|
35
|
+
return { ok: false, reason: "hostname cannot be the zone apex — a PPM token must never rewrite the zone's own record" };
|
|
36
|
+
}
|
|
37
|
+
if (host === `www.${z}`) {
|
|
38
|
+
return { ok: false, reason: "www is reserved" };
|
|
39
|
+
}
|
|
40
|
+
if (!host.endsWith(`.${z}`)) {
|
|
41
|
+
return { ok: false, reason: `hostname must be a subdomain of ${z} — cloudflared silently mis-routes anything else` };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Everything above the zone must be exactly one label — Cloudflare's free
|
|
45
|
+
// certificate only covers one level, and a deeper label serves a TLS error.
|
|
46
|
+
const prefix = host.slice(0, host.length - z.length - 1);
|
|
47
|
+
if (prefix.includes(".")) {
|
|
48
|
+
return { ok: false, reason: "hostname must be exactly one label above the zone" };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const label of host.split(".")) {
|
|
52
|
+
if (!LABEL.test(label)) {
|
|
53
|
+
return { ok: false, reason: `invalid DNS label: "${label}"` };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { ok: true };
|
|
58
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure parsers for `cloudflared tunnel login` stdout/stderr chunks.
|
|
3
|
+
*
|
|
4
|
+
* cloudflared prints the browser URL and progress lines on stderr, not
|
|
5
|
+
* stdout (verified 2026-09-05, cloudflared 2026.3.0) — callers must feed both
|
|
6
|
+
* streams through these functions, never stdout alone.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Primary shape: the real dash.cloudflare.com/argotunnel URL cloudflared prints today. */
|
|
10
|
+
const PRIMARY_URL = /https:\/\/dash\.cloudflare\.com\/argotunnel\?[^\s]+/;
|
|
11
|
+
|
|
12
|
+
/** Exact success line cloudflared prints on a successful login, exit code 0. */
|
|
13
|
+
const SUCCESS_LINE = /You have successfully logged in\./;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extract the login URL from a stdout/stderr chunk (or accumulated buffer).
|
|
17
|
+
* Falls back to the first `https://` token on any line mentioning
|
|
18
|
+
* "argotunnel" so a cosmetic rename of the URL shape never leaves the flow
|
|
19
|
+
* stuck at `waiting` with no way for the user to open the browser page.
|
|
20
|
+
*/
|
|
21
|
+
export function extractLoginUrl(text: string): string | null {
|
|
22
|
+
const primary = text.match(PRIMARY_URL);
|
|
23
|
+
if (primary) return primary[0];
|
|
24
|
+
|
|
25
|
+
for (const line of text.split(/\r?\n/)) {
|
|
26
|
+
if (!/argotunnel/i.test(line)) continue;
|
|
27
|
+
const fallback = line.match(/https:\/\/[^\s]+/);
|
|
28
|
+
if (fallback) return fallback[0];
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** True once cloudflared has printed its success line. */
|
|
34
|
+
export function isLoginSuccess(text: string): boolean {
|
|
35
|
+
return SUCCESS_LINE.test(text);
|
|
36
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cloudflared argv builders for the named-tunnel flow, plus the two artifact
|
|
3
|
+
* files they depend on. Every builder here guarantees its own artifacts (mirrors
|
|
4
|
+
* `cloudflared.service.ts`'s `getQuickTunnelArgs` → `ensureQuickTunnelConfig`
|
|
5
|
+
* pattern) rather than assuming an earlier phase already created them.
|
|
6
|
+
*
|
|
7
|
+
* The run token never appears in argv, ever — it is written to a 0600 file and
|
|
8
|
+
* passed via `--token-file`, so it can't leak through `ppm status`, a process
|
|
9
|
+
* list, or a crash dump that captures cmdlines.
|
|
10
|
+
*/
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
import { hostname as osHostname } from "node:os";
|
|
13
|
+
import { existsSync, mkdirSync, writeFileSync, chmodSync } from "node:fs";
|
|
14
|
+
import { getPpmDir } from "../ppm-dir.ts";
|
|
15
|
+
import { getOriginCertPath } from "./cloudflared-cert.ts";
|
|
16
|
+
|
|
17
|
+
const namedTunnelConfigPath = () => resolve(getPpmDir(), "cloudflared-named.yml");
|
|
18
|
+
const namedTunnelTokenPath = () => resolve(getPpmDir(), "named-tunnel.token");
|
|
19
|
+
const isWindows = process.platform === "win32";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Ensure `getPpmDir()` exists without letting a pre-existing directory throw
|
|
23
|
+
* EEXIST (Windows read-only-attribute folders — Desktop/Documents/Downloads —
|
|
24
|
+
* have thrown on a recursive mkdir here before even though the dir was already
|
|
25
|
+
* present and usable).
|
|
26
|
+
*/
|
|
27
|
+
function ensurePpmDir(): void {
|
|
28
|
+
if (existsSync(getPpmDir())) return;
|
|
29
|
+
try {
|
|
30
|
+
mkdirSync(getPpmDir(), { recursive: true, mode: 0o700 });
|
|
31
|
+
} catch (e: any) {
|
|
32
|
+
if (e?.code !== "EEXIST") throw e;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Best-effort chmod — POSIX enforces the mode; Windows has no equivalent bit. */
|
|
37
|
+
function chmodBestEffort(path: string, mode: number): void {
|
|
38
|
+
if (isWindows) return;
|
|
39
|
+
try { chmodSync(path, mode); } catch {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create `~/.ppm/cloudflared-named.yml` if absent; returns its path.
|
|
44
|
+
* A comment-only file still parses but logs `ERR Configuration file ... was
|
|
45
|
+
* empty` on every command (harmless noise, verified live); a *missing* file
|
|
46
|
+
* exits 1. `no-autoupdate: true` is a real, valid key so the file is never
|
|
47
|
+
* comment-only.
|
|
48
|
+
*/
|
|
49
|
+
export function ensureNamedTunnelConfig(): string {
|
|
50
|
+
const path = namedTunnelConfigPath();
|
|
51
|
+
if (!existsSync(path)) {
|
|
52
|
+
ensurePpmDir();
|
|
53
|
+
writeFileSync(path, "no-autoupdate: true\n", { mode: 0o600 });
|
|
54
|
+
}
|
|
55
|
+
chmodBestEffort(path, 0o600);
|
|
56
|
+
return path;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Write the run token to a 0600 file; returns its path. Overwritten on every call. */
|
|
60
|
+
export function writeNamedTunnelToken(token: string): string {
|
|
61
|
+
ensurePpmDir();
|
|
62
|
+
const path = namedTunnelTokenPath();
|
|
63
|
+
writeFileSync(path, token, { mode: 0o600 });
|
|
64
|
+
chmodBestEffort(path, 0o600);
|
|
65
|
+
return path;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** `tunnel login` has no cert yet, so no `--origincert` — that flag would point nowhere. */
|
|
69
|
+
export function loginArgs(): string[] {
|
|
70
|
+
return ["tunnel", "login"];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Global flags (`--origincert`/`--config`) always precede the subcommand. */
|
|
74
|
+
function managementPrefix(): string[] {
|
|
75
|
+
return ["--origincert", getOriginCertPath(), "--config", ensureNamedTunnelConfig()];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function createTunnelArgs(name: string): string[] {
|
|
79
|
+
return [...managementPrefix(), "tunnel", "create", name];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function tunnelTokenArgs(name: string): string[] {
|
|
83
|
+
return [...managementPrefix(), "tunnel", "token", name];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* `--overwrite-dns` only when the caller has confirmed this is our own tunnel's
|
|
88
|
+
* UUID being re-pointed (e.g. after a reset) — never passed blindly, since it
|
|
89
|
+
* silently steals a CNAME that might belong to something else.
|
|
90
|
+
*/
|
|
91
|
+
export function routeDnsArgs(name: string, hostname: string, overwrite: boolean): string[] {
|
|
92
|
+
const flags = overwrite ? ["--overwrite-dns"] : [];
|
|
93
|
+
return [...managementPrefix(), "tunnel", "route", "dns", ...flags, name, hostname];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Argv for the long-running named-tunnel process. No `--token` literal ever
|
|
98
|
+
* appears here — only a path to a 0600 file cloudflared reads itself.
|
|
99
|
+
*/
|
|
100
|
+
export function namedRunArgs(token: string, port: number): string[] {
|
|
101
|
+
const cfg = ensureNamedTunnelConfig();
|
|
102
|
+
const tokenPath = writeNamedTunnelToken(token);
|
|
103
|
+
return ["--config", cfg, "tunnel", "run", "--token-file", tokenPath, "--url", `http://127.0.0.1:${port}`];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Stable, DNS-safe tunnel name derived from this machine's hostname. */
|
|
107
|
+
export function tunnelNameForHost(): string {
|
|
108
|
+
const sanitized = osHostname().toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
109
|
+
return `ppm-${sanitized}`.slice(0, 32);
|
|
110
|
+
}
|