@hienlh/ppm 0.18.12 → 0.18.14
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 +27 -0
- package/CLAUDE.md +3 -0
- package/README.md +6 -6
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +4 -4
- package/assets/skills/ppm/references/http-api.md +19 -1
- package/dist/web/assets/{adaptive-context-menu-DGCKKeTy.js → adaptive-context-menu-DCZtgOL7.js} +1 -1
- package/dist/web/assets/{ai-resource-editor-BOHHTTe7.js → ai-resource-editor-D5CPBg06.js} +1 -1
- package/dist/web/assets/{ai-settings-section-BigC_nZR.js → ai-settings-section-DY-t1bK8.js} +1 -1
- package/dist/web/assets/{angular-html-BYPaydvh.js → angular-html-CTvM4ulF.js} +1 -1
- package/dist/web/assets/{angular-ts-Bb4AMKlk.js → angular-ts-B_vSkjco.js} +1 -1
- package/dist/web/assets/api-client-BogqsFna.js +1 -0
- package/dist/web/assets/{api-settings-Bd3M4YpI.js → api-settings-B2iNXQ0c.js} +1 -1
- package/dist/web/assets/api-settings-DRF9oYkn.js +1 -0
- package/dist/web/assets/{api-themes-Cpx419SL.js → api-themes-DrWnJbhs.js} +1 -1
- package/dist/web/assets/{apl-DB2VjFCS.js → apl-DywzTry0.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-nw5-j_I_.js +1 -0
- package/dist/web/assets/{astro-BeoJdVpk.js → astro-1JaRgaSb.js} +1 -1
- package/dist/web/assets/{audio-preview-BgFuTn-0.js → audio-preview-DAuGvOiK.js} +1 -1
- package/dist/web/assets/{blade-Cf2q1sPf.js → blade-CDeeJWBb.js} +1 -1
- package/dist/web/assets/{bsl-W1kOaNpu.js → bsl-BfgoPVYR.js} +1 -1
- package/dist/web/assets/{button-5zTrkEoN.js → button-tNy0ba3y.js} +1 -1
- package/dist/web/assets/{cairo-BoAZFcje.js → cairo-C26gNwWW.js} +1 -1
- package/dist/web/assets/chat-tab-IcnU3nqQ.js +15 -0
- package/dist/web/assets/{cobol-B0Nm7RXj.js → cobol-D92nBnON.js} +1 -1
- package/dist/web/assets/code-editor-Dno5Srkm.js +10 -0
- package/dist/web/assets/{coffee-BakMn3on.js → coffee-CIWH40R1.js} +1 -1
- package/dist/web/assets/{conflict-editor-BUs5k9q_.js → conflict-editor-CDp0I3mD.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-DOrBqXCn.js} +1 -1
- package/dist/web/assets/{crystal-kqqO3zkK.js → crystal-BNKqmHux.js} +1 -1
- package/dist/web/assets/{csv-preview-ByoDgLAZ.js → csv-preview-D4svNDAq.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-Brkf9z41.js → data-grid-overlay-editor-BqV0AxZg.js} +1 -1
- package/dist/web/assets/{database-viewer-Btc3h8Yu.js → database-viewer-BCvv_Bwf.js} +1 -1
- package/dist/web/assets/{dialog-DwtGlowE.js → dialog-DcLGDwaX.js} +1 -1
- package/dist/web/assets/diff-viewer-D41fCFAi.js +4 -0
- package/dist/web/assets/{docx-preview-DAD_vg59.js → docx-preview-DfaQn9v7.js} +1 -1
- package/dist/web/assets/{edge-Cq1ZKZuE.js → edge-Dbjp42Pt.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-B_VE7ppQ.js → edit-diff-preview-BqcUuFCc.js} +1 -1
- package/dist/web/assets/{elixir-BM_JoJRk.js → elixir-na3b1pYy.js} +1 -1
- package/dist/web/assets/{elm-Dw7oO08Y.js → elm-_Nalil1R.js} +1 -1
- package/dist/web/assets/{erb-CfKOGAfk.js → erb-CW_7dubf.js} +1 -1
- package/dist/web/assets/{erlang-DlDmGVeE.js → erlang-DmZ7Zzb0.js} +1 -1
- package/dist/web/assets/{esm-DGAP0GFh.js → esm-CwyBetO1.js} +1 -1
- package/dist/web/assets/explorer-body-DB1L30pa.js +2 -0
- package/dist/web/assets/explorer-window-content-Cwlt3C3I.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-BW_mccb3.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-DB2A8L4-.js} +1 -1
- package/dist/web/assets/{fsharp-CdNvy0PG.js → fsharp-BYHrti7z.js} +1 -1
- package/dist/web/assets/{gdresource-DxuG5i8i.js → gdresource-D5GBin40.js} +1 -1
- package/dist/web/assets/{git-commit-3lMonsur.js → git-commit-CUiOBoIL.js} +1 -1
- package/dist/web/assets/{git-log-panel-Co0PBPHC.js → git-log-panel-C1QDkmgl.js} +1 -1
- package/dist/web/assets/{git-rebase-N8Z9kIMf.js → git-rebase-DOxGnpdw.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-C1xf87gN.js +1 -0
- package/dist/web/assets/glide-data-grid-Dz8lx6n2.js +138 -0
- package/dist/web/assets/{glimmer-js-Dp9OXiib.js → glimmer-js-Begy4UT7.js} +1 -1
- package/dist/web/assets/{glimmer-ts-CDVKBcDj.js → glimmer-ts-rPApRY8N.js} +1 -1
- package/dist/web/assets/{glsl-DPJxI4us.js → glsl-wrbPvrBL.js} +1 -1
- package/dist/web/assets/{graphql-BrFhaQQl.js → graphql-Cu4-NR__.js} +1 -1
- package/dist/web/assets/group-chat-tab-DFwr-pig.js +1 -0
- package/dist/web/assets/{hack-BzXbDMlA.js → hack-Ddo93XKD.js} +1 -1
- package/dist/web/assets/{haml-CPpkX0s5.js → haml-BFX_1i8j.js} +1 -1
- package/dist/web/assets/{handlebars-BqrApy52.js → handlebars-5gFKA4H5.js} +1 -1
- package/dist/web/assets/{html-DmjUl-OA.js → html-C06I-zeO.js} +1 -1
- package/dist/web/assets/{html-derivative-Cnd6XFR5.js → html-derivative-e5JLAFZF.js} +1 -1
- package/dist/web/assets/{http-DMLWYun2.js → http-CrAAhSU7.js} +1 -1
- package/dist/web/assets/{hurl-CKkCVxbj.js → hurl-T1V3SX9T.js} +1 -1
- package/dist/web/assets/{hxml-CAx19TtQ.js → hxml-B056LLKN.js} +1 -1
- package/dist/web/assets/{image-preview-DHUoylQ1.js → image-preview-DWyuCYT3.js} +1 -1
- package/dist/web/assets/index-DWpNR3nB.css +2 -0
- package/dist/web/assets/index-oUKV5nIA.js +52 -0
- package/dist/web/assets/info-3K5VOQVL-C2z7FTDe.js +1 -0
- package/dist/web/assets/{input-DcEk7mA3.js → input-DouJ9Zvt.js} +1 -1
- package/dist/web/assets/{jinja-BmB9YN9l.js → jinja-DGdtRh43.js} +1 -1
- package/dist/web/assets/{jison-BaVsKKpw.js → jison-Ah6QobPS.js} +1 -1
- package/dist/web/assets/{julia-BgVBLveX.js → julia-BJeGOK0K.js} +1 -1
- package/dist/web/assets/{just-C2ENIbl9.js → just-iw9oRs7j.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-DHjj5hyN.js} +1 -1
- package/dist/web/assets/{liquid-NxpapreE.js → liquid-B-FFecDc.js} +1 -1
- package/dist/web/assets/{lua-DVepQvwH.js → lua-DDianJp_.js} +1 -1
- package/dist/web/assets/{markdown-renderer-DlcDhgrF.js → markdown-renderer-BHiUhQN8.js} +3 -3
- package/dist/web/assets/markdown-renderer-CnE36_gd.js +1 -0
- package/dist/web/assets/{marko-CJ2606Xl.js → marko-BW5SBVng.js} +1 -1
- package/dist/web/assets/{mdc-D2bjqclE.js → mdc-B9gL9bjl.js} +1 -1
- package/dist/web/assets/{media-url-H17XJF-I.js → media-url-DUK-k0bB.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-g-r2hCFa.js → mobile-bottom-sheet-CJ7ApOkI.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-BzjlS1Xn.js +1 -0
- package/dist/web/assets/{nextflow-BtJwUbfF.js → nextflow-dTluY6qy.js} +1 -1
- package/dist/web/assets/{nginx-j4gyIhHE.js → nginx-DlwghGI-.js} +1 -1
- package/dist/web/assets/{nim-ClH_gjP5.js → nim-N9jddb5D.js} +1 -1
- package/dist/web/assets/notification-store-C7UBqxYP.js +1 -0
- package/dist/web/assets/{number-overlay-editor-C1EqJnWq.js → number-overlay-editor-BVXM7aAs.js} +1 -1
- package/dist/web/assets/{overview-panel-vTeeJYVZ.js → overview-panel-DCCDmtCQ.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-DQUxN0wU.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-D7yLmAZW.js} +1 -1
- package/dist/web/assets/{perl-C9H7X7CT.js → perl-CkmpYKa_.js} +1 -1
- package/dist/web/assets/{php-Bx2U-cQ_.js → php-BwZ3KDPo.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-BOr9ek4Q.js +1 -0
- package/dist/web/assets/{postgres-viewer-DHwcQucn.js → postgres-viewer--CRN1fvO.js} +3 -3
- package/dist/web/assets/{process-table-BGRJNlpn.js → process-table-CXC10GSy.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-B3rBPqQo.js} +1 -1
- package/dist/web/assets/{qml-BXD94Eqz.js → qml-BfDbWFmg.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-DSErxZFb.js +1 -0
- package/dist/web/assets/{razor-DZ0-Ge0S.js → razor-B1ww1r38.js} +1 -1
- package/dist/web/assets/{rst-Dtdh0QUj.js → rst-C0MSq6m-.js} +1 -1
- package/dist/web/assets/{ruby-BC51BW2Q.js → ruby-ClIZ1wb3.js} +1 -1
- package/dist/web/assets/{run-in-terminal-Sd3KyjN4.js → run-in-terminal-4efbW2Xv.js} +1 -1
- package/dist/web/assets/{sas-BN8i4qOU.js → sas-DR0j3o4_.js} +1 -1
- package/dist/web/assets/{scroll-area-0gljZ6lF.js → scroll-area-ZP8wcwTI.js} +1 -1
- package/dist/web/assets/{scss-H-QtA7Rz.js → scss-Vh3JLayR.js} +1 -1
- package/dist/web/assets/{send-to-chat-u6m8l-rH.js → send-to-chat-BNrEpAyC.js} +1 -1
- package/dist/web/assets/{settings-store-CHY6Kzin.js → settings-store-OLJE7dPH.js} +2 -2
- package/dist/web/assets/{settings-tab-IY6K1MiZ.js → settings-tab-B1rvHFc6.js} +1 -1
- package/dist/web/assets/settings-tab-BUlmPJ2w.js +1 -0
- package/dist/web/assets/{shaderlab-CalNsj_x.js → shaderlab-0rHRnQ18.js} +1 -1
- package/dist/web/assets/{shellsession-BFrcoR8x.js → shellsession-Dxdw6D8c.js} +1 -1
- package/dist/web/assets/{soy-CNbku7vP.js → soy-BHlSNj1y.js} +1 -1
- package/dist/web/assets/{sparql-BN_orcVp.js → sparql-B2QaPpni.js} +1 -1
- package/dist/web/assets/{sql-query-editor-C6pC3I8l.js → sql-query-editor-R_iKjPP9.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-DTr5PzUb.js → sqlite-viewer-b2onA0SP.js} +1 -1
- package/dist/web/assets/{stata-C6a5Ijzq.js → stata-CwIxjsEJ.js} +1 -1
- package/dist/web/assets/{surrealql-ClplI_HY.js → surrealql-C6vo-rp-.js} +1 -1
- package/dist/web/assets/{svelte-DNm-UNXB.js → svelte-LLdJ6rlH.js} +1 -1
- package/dist/web/assets/{switch-DKvKya9y.js → switch-DV-BDjyk.js} +1 -1
- package/dist/web/assets/system-monitor-body-DPYQlBEt.js +2 -0
- package/dist/web/assets/system-monitor-tab-CPQdWv27.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-BUqRsPK2.js → system-monitor-window-content-BPif0azo.js} +1 -1
- package/dist/web/assets/tab-host-window-content-BnAN6m4K.js +1 -0
- package/dist/web/assets/{tab-store-CJuJRapl.js → tab-store-tFnyPmm4.js} +1 -1
- package/dist/web/assets/team-member-sheet-Bu2wG1sm.js +2 -0
- package/dist/web/assets/{team-member-window-content-T4Q5JI-G.js → team-member-window-content-BSyU-5Uc.js} +1 -1
- package/dist/web/assets/{templ-CSdqVobu.js → templ-CJGvhEP5.js} +1 -1
- package/dist/web/assets/{terminal-tab-Bb2rZegQ.js → terminal-tab-Ncor0Rfu.js} +3 -3
- package/dist/web/assets/{tex-DzSJ4kcZ.js → tex-psXWhc4t.js} +1 -1
- package/dist/web/assets/{tool-cards-lhqhrDTT.js → tool-cards-DdVP1wrD.js} +4 -4
- package/dist/web/assets/treemap-KZPCXAKY-C17p0LQw.js +1 -0
- package/dist/web/assets/{ts-tags-C28w3bTg.js → ts-tags-D5Ar_ZCO.js} +1 -1
- package/dist/web/assets/{twig-i5jY3QXB.js → twig-vJgGC-qY.js} +1 -1
- package/dist/web/assets/{use-blob-url-Dsq9OjTh.js → use-blob-url-BOVGWg9F.js} +1 -1
- package/dist/web/assets/use-coarse-long-press-BmI_GVW5.js +1 -0
- package/dist/web/assets/use-monaco-theme-BshnLXoY.js +1 -0
- package/dist/web/assets/{use-open-team-member-DkiEjFYE.js → use-open-team-member-CY3wOdWL.js} +1 -1
- package/dist/web/assets/{use-websocket-B8R50f7f.js → use-websocket-B7xjNsyD.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-By4AVvJY.js → vendor-mermaid-999ksD45.js} +1 -1
- package/dist/web/assets/video-preview-7F72sRwf.js +1 -0
- package/dist/web/assets/{vue-Ceyet_lv.js → vue-BWngFyav.js} +1 -1
- package/dist/web/assets/{vue-html-GuU7g_hg.js → vue-html-BN5ZLu-H.js} +1 -1
- package/dist/web/assets/{vue-vine-CxXoaJr2.js → vue-vine-BZHnO8yV.js} +1 -1
- package/dist/web/assets/window-pip-registry-BnYU83LE.js +1 -0
- package/dist/web/assets/{window-store-CsEW6jiE.js → window-store-CQ1lghvv.js} +1 -1
- package/dist/web/assets/{xml-CbqOVhkP.js → xml-DS2beZHp.js} +1 -1
- package/dist/web/assets/{xsl-KfAmCauy.js → xsl-Ciiku1Bq.js} +1 -1
- package/dist/web/index.html +47 -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 +399 -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 +4 -3
- 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/edge-forwarder.ts +191 -191
- 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/services/windows-process-tree.ts +351 -351
- 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/floating-window/floating-window.tsx +8 -1
- package/src/web/components/floating-window/open-tab-in-pip.ts +78 -0
- package/src/web/components/floating-window/window-pip-registry.ts +46 -0
- 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/layout/tab-pop-out-menu-item.tsx +25 -2
- 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/diff-viewer-CZaDnR4w.js +0 -4
- 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/group-chat-tab-rOd8J8GC.js +0 -1
- 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/tab-host-window-content-BnqGPu8a.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/use-monaco-theme-BZvlsoBv.js +0 -1
- package/dist/web/assets/video-preview-m0-3p7QZ.js +0 -1
- package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
- /package/dist/web/assets/{abap-C6nB04tn.js → abap-CjzrhAS2.js} +0 -0
- /package/dist/web/assets/{actionscript-3-Vx4rw2sC.js → actionscript-3-B59Kbl-9.js} +0 -0
- /package/dist/web/assets/{ada-Ccd8vM20.js → ada-B7gItKAE.js} +0 -0
- /package/dist/web/assets/{andromeeda-DnBX5xMg.js → andromeeda-Dy5zcy0Q.js} +0 -0
- /package/dist/web/assets/{apache-B-9_sx0g.js → apache-B6T9jrc0.js} +0 -0
- /package/dist/web/assets/{apex-_wPVBYoL.js → apex-CTsIxDQw.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-DuycKmmb.js} +0 -0
- /package/dist/web/assets/{apply-theme-BeLRfsSu.js → apply-theme-r8tKDrCR.js} +0 -0
- /package/dist/web/assets/{ara-C40Ztoms.js → ara-CISL3599.js} +0 -0
- /package/dist/web/assets/{asciidoc-CybYkJV4.js → asciidoc-BXyE2Lpr.js} +0 -0
- /package/dist/web/assets/{asm-9eOKwzR0.js → asm-y258phtP.js} +0 -0
- /package/dist/web/assets/{aurora-x-B5ZH4t7t.js → aurora-x-Dq3Jc78M.js} +0 -0
- /package/dist/web/assets/{awk-Dqilob-2.js → awk-DxL6XSJv.js} +0 -0
- /package/dist/web/assets/{ayu-dark-CPgRhf5r.js → ayu-dark-DjoK6Evo.js} +0 -0
- /package/dist/web/assets/{ayu-light-Do06Q52f.js → ayu-light-CKKXBLMI.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-_BhxcrxY.js → ayu-mirage-DGXSrrXB.js} +0 -0
- /package/dist/web/assets/{ballerina-DDwK54QW.js → ballerina-DZSXFmUf.js} +0 -0
- /package/dist/web/assets/{bat-BGyGlzGk.js → bat-BNsZk6nY.js} +0 -0
- /package/dist/web/assets/{beancount-CRWBdapI.js → beancount-cPl4gJct.js} +0 -0
- /package/dist/web/assets/{berry-DbSFn2gO.js → berry-DGAodERk.js} +0 -0
- /package/dist/web/assets/{bibtex-QpsV9gm9.js → bibtex-DftVoRif.js} +0 -0
- /package/dist/web/assets/{bicep-CxS8CUve.js → bicep-Suioyyzh.js} +0 -0
- /package/dist/web/assets/{bird2-CuZhQzYR.js → bird2-DjU24I2P.js} +0 -0
- /package/dist/web/assets/{c-9bnDqE3g.js → c-DFi8Y_nN.js} +0 -0
- /package/dist/web/assets/{c3-JMZTrOhA.js → c3-1eSHnH5V.js} +0 -0
- /package/dist/web/assets/{cadence-DsDNayJS.js → cadence-DLLsFwQP.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-BJfpynya.js → can-open-in-ppm-in727eFQ.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-DYIKXXBe.js → catppuccin-frappe-DDj0JEiY.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-IYYOnUJ8.js → catppuccin-latte-DPyANHyR.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-BrJ_7mfS.js → catppuccin-macchiato-CO-h8VTT.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-gzSu7v7-.js → catppuccin-mocha-BCmbN2IZ.js} +0 -0
- /package/dist/web/assets/{clarity-DLzsC0Hb.js → clarity-C0pYsZjs.js} +0 -0
- /package/dist/web/assets/{clipboard-7fw0Shic.js → clipboard-DBLGKMrH.js} +0 -0
- /package/dist/web/assets/{clojure-DS1BvoTt.js → clojure-JFr6s8iZ.js} +0 -0
- /package/dist/web/assets/{cmake-CDvsJwYp.js → cmake-EEcLEgVy.js} +0 -0
- /package/dist/web/assets/{codeowners-BjsYiflJ.js → codeowners-Deh3Lx0u.js} +0 -0
- /package/dist/web/assets/{codeql-CG-7ARat.js → codeql-C317igT5.js} +0 -0
- /package/dist/web/assets/{common-lisp-DSO1Lbh9.js → common-lisp-DepbATZs.js} +0 -0
- /package/dist/web/assets/{coq-DYdh3oP_.js → coq-MxgxU5ya.js} +0 -0
- /package/dist/web/assets/{csharp-DxEBz5s0.js → csharp-eEFXdsAq.js} +0 -0
- /package/dist/web/assets/{css-dal3Q-8C.js → css-Db-syWW9.js} +0 -0
- /package/dist/web/assets/{csv-CpqjMGiA.js → csv-DHeYcbaq.js} +0 -0
- /package/dist/web/assets/{csv-parser-3umBA4NY.js → csv-parser-_JWMLGl3.js} +0 -0
- /package/dist/web/assets/{cue-DinoJ_5k.js → cue-BmUNowrt.js} +0 -0
- /package/dist/web/assets/{cypher-DzxqmkUW.js → cypher-XjXHgCNS.js} +0 -0
- /package/dist/web/assets/{d-BxNwJuEC.js → d-BWJnZtig.js} +0 -0
- /package/dist/web/assets/{dark-plus-Clmv3V-0.js → dark-plus-Db7umas1.js} +0 -0
- /package/dist/web/assets/{dart-BWVtXD35.js → dart-DSI4HYh1.js} +0 -0
- /package/dist/web/assets/{data-grid-types-B4n51Fck.js → data-grid-types-CfVHh9gv.js} +0 -0
- /package/dist/web/assets/{dax-DsImuWgJ.js → dax-yw1JsAni.js} +0 -0
- /package/dist/web/assets/{desktop-B0jJuG2r.js → desktop-C2QO4RvD.js} +0 -0
- /package/dist/web/assets/{diff-CWqAAxoE.js → diff-Drt8aEyp.js} +0 -0
- /package/dist/web/assets/{dist-B-EXdSYE.js → dist-DNRvhVdz.js} +0 -0
- /package/dist/web/assets/{dist-C0eNNfbt.js → dist-VBxOHLmN.js} +0 -0
- /package/dist/web/assets/{docker-CYFCJdIL.js → docker-ByDKbmLl.js} +0 -0
- /package/dist/web/assets/{dotenv-CKDUjURg.js → dotenv-BkfOLytR.js} +0 -0
- /package/dist/web/assets/{dracula-CSoPspHE.js → dracula-DAmrm-Un.js} +0 -0
- /package/dist/web/assets/{dracula-soft-BuFjw5L1.js → dracula-soft-BUOEnKqR.js} +0 -0
- /package/dist/web/assets/{dream-maker-B6PdDRZu.js → dream-maker-DQBIfSiO.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-CMcXvD0G.js → emacs-lisp-Blh5kBD3.js} +0 -0
- /package/dist/web/assets/{everforest-dark-cU8-wYzi.js → everforest-dark-CEzhDxHq.js} +0 -0
- /package/dist/web/assets/{everforest-light-KhorllqK.js → everforest-light-CPk_5Khm.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-B1MT_EBZ.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-C_HGcewt.js} +0 -0
- /package/dist/web/assets/{fluent-BTINnwF6.js → fluent-Dp1Iclmx.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-53WBtyi2.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BQim9wDz.js → fortran-free-form-B_-UA4XC.js} +0 -0
- /package/dist/web/assets/{gdscript-03w5u8un.js → gdscript-Bm_JpjS_.js} +0 -0
- /package/dist/web/assets/{gdshader-Dp2daNzU.js → gdshader-C_PNmfYU.js} +0 -0
- /package/dist/web/assets/{genie-CoNcjPiD.js → genie-B5rmR2TF.js} +0 -0
- /package/dist/web/assets/{gherkin-NrPOSPQL.js → gherkin-BTJ34TJ9.js} +0 -0
- /package/dist/web/assets/{github-dark-z4RE9ZqA.js → github-dark-DOkNVj9H.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DOKw-1cI.js → github-dark-default-DTsmiyVo.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-DJRNMkEA.js → github-dark-dimmed-CjbxAe9s.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-Bj7jJ_Gs.js → github-dark-high-contrast-r_M8F_k7.js} +0 -0
- /package/dist/web/assets/{github-light-B4k_yMFu.js → github-light-BzfxyMyb.js} +0 -0
- /package/dist/web/assets/{github-light-default-BHc-Uzhs.js → github-light-default-BOqiBIy5.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-CHisIywL.js → github-light-high-contrast-DQzBrDM4.js} +0 -0
- /package/dist/web/assets/{gleam-CfbypZ4q.js → gleam-BWmuAtD8.js} +0 -0
- /package/dist/web/assets/{gn-YoAjyhJa.js → gn-CNQPjiZW.js} +0 -0
- /package/dist/web/assets/{gnuplot-xVkc1f1c.js → gnuplot-BlHjcPMe.js} +0 -0
- /package/dist/web/assets/{go-GPCwP3Gu.js → go-Bn8Ba9uz.js} +0 -0
- /package/dist/web/assets/{groovy-Crh4Yww9.js → groovy-C1Y1_F4V.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-BluUFuDS.js → gruvbox-dark-hard-DueOsHav.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Cxk1FWQp.js → gruvbox-dark-medium-DaQjD538.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-CHXMiFmj.js → gruvbox-dark-soft-CbbX2WOm.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-B6eKPjAD.js → gruvbox-light-hard-E2M70ljZ.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-pDwMm1ut.js → gruvbox-light-medium-B_DDLY6h.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-Cxu6kHqK.js → gruvbox-light-soft-2SZmyC1F.js} +0 -0
- /package/dist/web/assets/{haskell-C6JKHjbK.js → haskell-D8DeoXtG.js} +0 -0
- /package/dist/web/assets/{haxe-BuBNdF0a.js → haxe-BmZm7B0W.js} +0 -0
- /package/dist/web/assets/{hcl-DZpDdGj2.js → hcl-CKnBM3ng.js} +0 -0
- /package/dist/web/assets/{hjson-CBkhYRuo.js → hjson-Ct9t4-ci.js} +0 -0
- /package/dist/web/assets/{hlsl-C5nMxnp9.js → hlsl-BZSASBqS.js} +0 -0
- /package/dist/web/assets/{horizon-VAFvA6R6.js → horizon-CzX1AZ08.js} +0 -0
- /package/dist/web/assets/{horizon-bright-BC0xn7T3.js → horizon-bright-CCVNeRIP.js} +0 -0
- /package/dist/web/assets/{houston-BxrUivmz.js → houston-DBlJN7xc.js} +0 -0
- /package/dist/web/assets/{hy-DLtOXoaH.js → hy-Dyc6h6Xb.js} +0 -0
- /package/dist/web/assets/{image-gallery-Gnf7TdCR.js → image-gallery-DujlrjOC.js} +0 -0
- /package/dist/web/assets/{imba-BdpFXM2W.js → imba-Bauru7s2.js} +0 -0
- /package/dist/web/assets/{ini-BInJO_T0.js → ini-SjdwtaTp.js} +0 -0
- /package/dist/web/assets/{java-Dxuddnkb.js → java-D2r0ocpf.js} +0 -0
- /package/dist/web/assets/{javascript-CVamJfLY.js → javascript-y-lof7iP.js} +0 -0
- /package/dist/web/assets/{json-BLZ0ozYk.js → json-DO4LSx5A.js} +0 -0
- /package/dist/web/assets/{json5-BnXzmMKs.js → json5-BEwWf-W3.js} +0 -0
- /package/dist/web/assets/{jsonc-CSgkSga7.js → jsonc-OBEFv7gP.js} +0 -0
- /package/dist/web/assets/{jsonl-CY5W2nF2.js → jsonl-DOyW4Mg3.js} +0 -0
- /package/dist/web/assets/{jsonnet-CEJYH3sF.js → jsonnet-BL5R2CpP.js} +0 -0
- /package/dist/web/assets/{jssm-CsJc7Uhk.js → jssm-ksPusAyg.js} +0 -0
- /package/dist/web/assets/{jsx-jCiRaHU7.js → jsx-B6XEu6O-.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-DfoA_GxX.js → kanagawa-dragon-JBVe_ji_.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-BOLYJpWd.js → kanagawa-lotus-ChprO68_.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-DfKlbT1G.js → kanagawa-wave-D_uB_R0Q.js} +0 -0
- /package/dist/web/assets/{kdl-CmssIFTi.js → kdl-pf5B8N_m.js} +0 -0
- /package/dist/web/assets/{kotlin-BRsNgp6x.js → kotlin-BdF0METC.js} +0 -0
- /package/dist/web/assets/{kusto-T8X9jUab.js → kusto-BneVP-OU.js} +0 -0
- /package/dist/web/assets/{laserwave-DkJMVJ8Z.js → laserwave-C4-UPIyG.js} +0 -0
- /package/dist/web/assets/{lean-C65KGVPq.js → lean-BN6Jdymq.js} +0 -0
- /package/dist/web/assets/{less-exIPdDlf.js → less-Cx-fdlDC.js} +0 -0
- /package/dist/web/assets/{lib-mC1NZfuw.js → lib-CMuBa85c.js} +0 -0
- /package/dist/web/assets/{libesm-DSutUtl1.js → libesm-BB74PRsH.js} +0 -0
- /package/dist/web/assets/{light-plus-DiHUXAZt.js → light-plus-DFnnn_iL.js} +0 -0
- /package/dist/web/assets/{llvm-CJBnEzFM.js → llvm-BGxtW0Nw.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-kQb4eJnj.js} +0 -0
- /package/dist/web/assets/{logo-CC9NKzca.js → logo-DqAzaq7s.js} +0 -0
- /package/dist/web/assets/{luau-Bw10I6zf.js → luau-Dc2cp-zS.js} +0 -0
- /package/dist/web/assets/{make-C_6M90m6.js → make-DYg9dwaJ.js} +0 -0
- /package/dist/web/assets/{markdown-bR9_R-LF.js → markdown-O4EPpS3K.js} +0 -0
- /package/dist/web/assets/{material-theme-BUx2Qujn.js → material-theme-ClP8yfQ2.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-CRJrPgKm.js → material-theme-darker-LOK55hGu.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-CUAYppPa.js → material-theme-lighter-j72tzZqi.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-DuINF1fg.js → material-theme-ocean-C19LKClA.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-W6_icGCj.js → material-theme-palenight-DJ5tN2ax.js} +0 -0
- /package/dist/web/assets/{matlab-rfqn_b3z.js → matlab-DQNZ_NU8.js} +0 -0
- /package/dist/web/assets/{mdx-Bh1B_YOJ.js → mdx-CJayB7XK.js} +0 -0
- /package/dist/web/assets/{mermaid-Dxf6Ya2k.js → mermaid-Cb0TRX92.js} +0 -0
- /package/dist/web/assets/{min-dark-CivZ2L0h.js → min-dark-CWM8Bo-m.js} +0 -0
- /package/dist/web/assets/{min-light-B1Ykqtma.js → min-light-BBR1sjS2.js} +0 -0
- /package/dist/web/assets/{mipsasm-DynHaM0d.js → mipsasm-Jc7u7YSS.js} +0 -0
- /package/dist/web/assets/{mojo-Clw5PbGH.js → mojo-CTB0cutd.js} +0 -0
- /package/dist/web/assets/{monitor-Dv8vn_6O.js → monitor-4vWfqzuN.js} +0 -0
- /package/dist/web/assets/{monokai-DFIooFZT.js → monokai-CvM_YNKx.js} +0 -0
- /package/dist/web/assets/{moonbit-DnBSZWGM.js → moonbit-CbFEfYYM.js} +0 -0
- /package/dist/web/assets/{move-CCcL2PfL.js → move-BT38p0kT.js} +0 -0
- /package/dist/web/assets/{narrat-BDarg5Lk.js → narrat-55QZ_kOG.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-DrZOeCNW.js → nextflow-groovy-Bg8peyqg.js} +0 -0
- /package/dist/web/assets/{night-owl-BnZZCK7v.js → night-owl-DzS77iPK.js} +0 -0
- /package/dist/web/assets/{night-owl-light-CEwBsVHS.js → night-owl-light-DER7fGnx.js} +0 -0
- /package/dist/web/assets/{nix-amCzOPMR.js → nix-BSI8YesN.js} +0 -0
- /package/dist/web/assets/{nord-BIs2BC6q.js → nord-9bn7iNWo.js} +0 -0
- /package/dist/web/assets/{nushell-Y02fo4yw.js → nushell-qDVZn_lw.js} +0 -0
- /package/dist/web/assets/{objective-c-7xOZtWW1.js → objective-c-Blf-ATos.js} +0 -0
- /package/dist/web/assets/{objective-cpp-eVtjUvVB.js → objective-cpp-Bz8bAq47.js} +0 -0
- /package/dist/web/assets/{ocaml-Bi6ZwqG4.js → ocaml-B9heAsc3.js} +0 -0
- /package/dist/web/assets/{odin-8xf1Y0S9.js → odin-BaFYnOwO.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-hVv-_y_b.js → one-dark-pro-D6B_hSe-.js} +0 -0
- /package/dist/web/assets/{one-light-CJJ3MtHt.js → one-light-Ba-f0wyk.js} +0 -0
- /package/dist/web/assets/{openscad-B2HcywLk.js → openscad-UvYyL2MK.js} +0 -0
- /package/dist/web/assets/{pascal-C7fllV_7.js → pascal-B4-qvEoG.js} +0 -0
- /package/dist/web/assets/{pkl-qvLC1IWP.js → pkl-B146nqGx.js} +0 -0
- /package/dist/web/assets/{plastic-BT1wHZv2.js → plastic-L38t-Id3.js} +0 -0
- /package/dist/web/assets/{play-4MZi1Axp.js → play-CpCAoQRZ.js} +0 -0
- /package/dist/web/assets/{plsql-1MsmdmJD.js → plsql-ItOG5Cca.js} +0 -0
- /package/dist/web/assets/{plus-BhMZqFsN.js → plus-BR0qewJI.js} +0 -0
- /package/dist/web/assets/{po-BigskytD.js → po-imiCTaNX.js} +0 -0
- /package/dist/web/assets/{poimandres-DuklniF7.js → poimandres-Bu6sIba9.js} +0 -0
- /package/dist/web/assets/{polar-e2pjN_sp.js → polar-CTeGoFxV.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-jvFe6GNE.js} +0 -0
- /package/dist/web/assets/{powerquery-Cr2PtehI.js → powerquery-D9Dw1hJp.js} +0 -0
- /package/dist/web/assets/{powershell-BxFTGLJ4.js → powershell-CZqvYQtg.js} +0 -0
- /package/dist/web/assets/{prisma-COWJiwdZ.js → prisma-B4D2nO_7.js} +0 -0
- /package/dist/web/assets/{prolog-DFvGKRx6.js → prolog-CQbyUcCF.js} +0 -0
- /package/dist/web/assets/{proto-CcK80lFt.js → proto-Cwpo0cW1.js} +0 -0
- /package/dist/web/assets/{puppet-BF60bZ4F.js → puppet-CKxCwBod.js} +0 -0
- /package/dist/web/assets/{purescript-zL_69MzU.js → purescript-B-MyXMMp.js} +0 -0
- /package/dist/web/assets/{python-CIwKhfb_.js → python-D6QbSR2S.js} +0 -0
- /package/dist/web/assets/{qmldir-CXNe04Lo.js → qmldir-BpTSVcw_.js} +0 -0
- /package/dist/web/assets/{qss-C8lAWe6G.js → qss-DBc4gbpe.js} +0 -0
- /package/dist/web/assets/{r-CL5ZVL3_.js → r-i9jMMi0s.js} +0 -0
- /package/dist/web/assets/{racket-BBfh0uh0.js → racket-BWNaxU7C.js} +0 -0
- /package/dist/web/assets/{raku-o6E8R5Q3.js → raku-fqTZLn7L.js} +0 -0
- /package/dist/web/assets/{react-CBTnT4sY.js → react-X4jUntEf.js} +0 -0
- /package/dist/web/assets/{red-GomRF2Xp.js → red-D-JEcuzA.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-BbirlU1S.js} +0 -0
- /package/dist/web/assets/{regexp-Bl5sF65W.js → regexp-D8VTFrtg.js} +0 -0
- /package/dist/web/assets/{rel-DgMdqDuj.js → rel-ByQGKiNM.js} +0 -0
- /package/dist/web/assets/{resolve-theme-CRt_7wpd.js → resolve-theme-Cqb9zWYc.js} +0 -0
- /package/dist/web/assets/{riscv-8b3-Ye4X.js → riscv-B5nqCfLE.js} +0 -0
- /package/dist/web/assets/{ron-DrmU1KHP.js → ron-DOVh_iZR.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-DEfhNlFH.js → rose-pine-dawn-B7hkmmWy.js} +0 -0
- /package/dist/web/assets/{rose-pine-C_-E2tF1.js → rose-pine-jp51ZUzi.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-BdvX3gdn.js → rose-pine-moon-Ff063yLj.js} +0 -0
- /package/dist/web/assets/{rosmsg-B6GhGpd5.js → rosmsg-Cyk7Il8z.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-DRNM5epF.js} +0 -0
- /package/dist/web/assets/{sass-B0tJOHLM.js → sass-D_cB0-M1.js} +0 -0
- /package/dist/web/assets/{save-CtIGOjmx.js → save-DxonD4y1.js} +0 -0
- /package/dist/web/assets/{scala-n8zaFOXv.js → scala-Crl-1kKS.js} +0 -0
- /package/dist/web/assets/{scheme-BKD9ztBU.js → scheme-DCaWyEJ0.js} +0 -0
- /package/dist/web/assets/{sdbl-D8gB5-rS.js → sdbl-BbXNqwtX.js} +0 -0
- /package/dist/web/assets/{send-BlyNJAzy.js → send-B-t5Mz26.js} +0 -0
- /package/dist/web/assets/{shallow-DYo6goJ4.js → shallow-4zAfs2KW.js} +0 -0
- /package/dist/web/assets/{shellscript-BfXlf_Ao.js → shellscript-BAgksBhW.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-GB-ElUW_.js} +0 -0
- /package/dist/web/assets/{slack-ochin-w8wg8vuC.js → slack-ochin-tU6CFD3S.js} +0 -0
- /package/dist/web/assets/{smalltalk-hiAXA64u.js → smalltalk-DSVGUbtL.js} +0 -0
- /package/dist/web/assets/{snazzy-light-BwlV3Xty.js → snazzy-light-DZhi3PoS.js} +0 -0
- /package/dist/web/assets/{solarized-dark-vmNGdykz.js → solarized-dark-D6AMCco7.js} +0 -0
- /package/dist/web/assets/{solarized-light-DGv_0JXq.js → solarized-light-DepuTWSE.js} +0 -0
- /package/dist/web/assets/{solidity-Y7QSH0B1.js → solidity-CsxN618l.js} +0 -0
- /package/dist/web/assets/{splunk-BtgpLjHi.js → splunk-CF1m0Kkv.js} +0 -0
- /package/dist/web/assets/{sql-CRHYhOia.js → sql-DvyMDgrB.js} +0 -0
- /package/dist/web/assets/{ssh-config-DOqSs3au.js → ssh-config-a8sDo-Q8.js} +0 -0
- /package/dist/web/assets/{stylus-crCAEaBT.js → stylus-DCJ-6AjT.js} +0 -0
- /package/dist/web/assets/{swift-eE3rT7TC.js → swift-c-ZFxyPP.js} +0 -0
- /package/dist/web/assets/{synthwave-84-BWy14LBX.js → synthwave-84-9NPgBcuv.js} +0 -0
- /package/dist/web/assets/{system-verilog-DC0-DZM4.js → system-verilog-DPD02IOo.js} +0 -0
- /package/dist/web/assets/{systemd-to5p6N8I.js → systemd-BUr5HRtU.js} +0 -0
- /package/dist/web/assets/{table-Bbc783ko.js → table-FLU8ei8b.js} +0 -0
- /package/dist/web/assets/{talonscript-DoTcU8wm.js → talonscript-BNBQV5YJ.js} +0 -0
- /package/dist/web/assets/{tasl-cNZvUs95.js → tasl-BETjpSyu.js} +0 -0
- /package/dist/web/assets/{tcl-mZE8sZH8.js → tcl-Clp_AZiC.js} +0 -0
- /package/dist/web/assets/{terraform-CqHPpIfu.js → terraform-B0kZ0lim.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-oOJ0a7Dk.js} +0 -0
- /package/dist/web/assets/{toml-CU_vscgn.js → toml-DOPCSumy.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-D6qUGf84.js} +0 -0
- /package/dist/web/assets/{tsx-BMvvMKiv.js → tsx-B6qYhT6O.js} +0 -0
- /package/dist/web/assets/{turtle-DBqKCq9o.js → turtle-C0Rjzw73.js} +0 -0
- /package/dist/web/assets/{typescript-DXJDmMZZ.js → typescript-BiydShEu.js} +0 -0
- /package/dist/web/assets/{typespec-CBseCMtZ.js → typespec-DjmF6Aww.js} +0 -0
- /package/dist/web/assets/{typst-4oY25U9y.js → typst-CPuscfcL.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-D1weanbj.js → use-is-mobile-b94q8RvF.js} +0 -0
- /package/dist/web/assets/{utils-BNRH507E.js → utils-C9OjCrou.js} +0 -0
- /package/dist/web/assets/{v-BXaZ_1oy.js → v-DE4vpLLQ.js} +0 -0
- /package/dist/web/assets/{vala-BImWJE7z.js → vala-CmUF2pS5.js} +0 -0
- /package/dist/web/assets/{vb-CLfj2_sy.js → vb-BvcRoX4b.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-_yyu5n32.js → vendor-xterm-K8kpLxdb.js} +0 -0
- /package/dist/web/assets/{verilog-BWa8Ed2b.js → verilog-BGIzyH3R.js} +0 -0
- /package/dist/web/assets/{vesper-BGj1cg6m.js → vesper-CG7IrHQQ.js} +0 -0
- /package/dist/web/assets/{vhdl-mByK7mlx.js → vhdl-DMSumomw.js} +0 -0
- /package/dist/web/assets/{viml-CugmlW5g.js → viml-Drt7AEmU.js} +0 -0
- /package/dist/web/assets/{vitesse-black-vPdpy9TL.js → vitesse-black-DmsjfN0b.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BuQK3dUh.js → vitesse-dark-Dl55_WeJ.js} +0 -0
- /package/dist/web/assets/{vitesse-light-B3nz1XL9.js → vitesse-light-Crbv9aGg.js} +0 -0
- /package/dist/web/assets/{vyper-DH_GTIHG.js → vyper-CB7psEJj.js} +0 -0
- /package/dist/web/assets/{wasm-DfVbVJlW.js → wasm-BY1tuM5m.js} +0 -0
- /package/dist/web/assets/{wasm-C-LF9GjX.js → wasm-Dck8KPgP.js} +0 -0
- /package/dist/web/assets/{wenyan-CzQkHyHx.js → wenyan-DhewQ68b.js} +0 -0
- /package/dist/web/assets/{wgsl-Du_M1DW9.js → wgsl-DlmiP456.js} +0 -0
- /package/dist/web/assets/{wikitext-BWN_w5S7.js → wikitext-cQCnqNgE.js} +0 -0
- /package/dist/web/assets/{wit-BR13v5HD.js → wit-S5MaCngB.js} +0 -0
- /package/dist/web/assets/{wolfram-BoXxp_Xl.js → wolfram-Bh8n_9zr.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-BoK_17l9.js} +0 -0
- /package/dist/web/assets/{zenscript-C-EdUtUd.js → zenscript-DB3ZFwNJ.js} +0 -0
- /package/dist/web/assets/{zig-BD4eA5nU.js → zig-Bv1BWmfT.js} +0 -0
|
@@ -2,17 +2,32 @@ import { WatchTree } from "./file-watcher/watch-tree.ts";
|
|
|
2
2
|
|
|
3
3
|
const DEBOUNCE_MS = 500;
|
|
4
4
|
/**
|
|
5
|
-
* Directory budgets.
|
|
6
|
-
* the
|
|
7
|
-
*
|
|
5
|
+
* Directory budgets. These bound the directories PPM *covers*, which is not the same as
|
|
6
|
+
* the inotify watches it holds: measured on Linux, watching a directory costs one
|
|
7
|
+
* descriptor for it plus one for every file inside — 4 directories holding 600 files
|
|
8
|
+
* come to 604, recursive and non-recursive alike. So a directory cap bounds handles and
|
|
9
|
+
* walk cost, not descriptors; what keeps PPM inside the machine-wide 524288 ceiling is
|
|
10
|
+
* never registering `node_modules` in the first place.
|
|
8
11
|
*
|
|
9
|
-
* The per-project cap is hard. The total is best-effort: a
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
12
|
+
* The per-project cap is hard. The total is best-effort: a tree keeps growing after
|
|
13
|
+
* it starts, through `syncChildDir` -> `cover`, up to its own `maxDirs` — and on
|
|
14
|
+
* Windows/macOS a recursive handle covers directories created later without telling
|
|
15
|
+
* us either. So projects can add up past the total.
|
|
16
|
+
*
|
|
17
|
+
* A pnpm monorepo runs past 8000 directories on its own — the cap then silently stops
|
|
18
|
+
* watching the rest of the tree, which reads as a file change that never arrives.
|
|
19
|
+
* 12000 covers those with room to spare.
|
|
20
|
+
*/
|
|
21
|
+
const MAX_DIRS_PER_PROJECT = 12_000;
|
|
22
|
+
const MAX_DIRS_TOTAL = 30_000;
|
|
23
|
+
/**
|
|
24
|
+
* Floor on what a project is handed. Dividing the total down to nothing is worse than
|
|
25
|
+
* overshooting it: at 12000 per project the budgets ran 12000 -> 8000 -> **0**, and a
|
|
26
|
+
* project watching zero directories reports no changes at all — the same "file change
|
|
27
|
+
* that never arrives", total instead of partial, and indistinguishable from a broken
|
|
28
|
+
* watcher. A late project now gets a small budget and the truncation warning instead.
|
|
13
29
|
*/
|
|
14
|
-
const
|
|
15
|
-
const MAX_DIRS_TOTAL = 20_000;
|
|
30
|
+
const MIN_DIRS_PER_PROJECT = 1_000;
|
|
16
31
|
|
|
17
32
|
type ChangeCallback = (projectName: string, path: string) => void;
|
|
18
33
|
|
|
@@ -64,7 +79,10 @@ export function startWatching(projectName: string, projectPath: string): void {
|
|
|
64
79
|
return;
|
|
65
80
|
}
|
|
66
81
|
|
|
67
|
-
const maxDirs = Math.max(
|
|
82
|
+
const maxDirs = Math.max(
|
|
83
|
+
MIN_DIRS_PER_PROJECT,
|
|
84
|
+
Math.min(MAX_DIRS_PER_PROJECT, MAX_DIRS_TOTAL - totalCoveredDirs()),
|
|
85
|
+
);
|
|
68
86
|
const entry: WatchEntry = {
|
|
69
87
|
tree: new WatchTree({
|
|
70
88
|
root: projectPath,
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { resolve, sep } from "node:path";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { getPpmDir } from "./ppm-dir.ts";
|
|
4
|
+
import { getUploadsDir } from "./chat-upload-storage.service.ts";
|
|
5
|
+
import { realPathOrSelf } from "./fs-ops/fs-real-path.ts";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Every path a generic filesystem route (read, write, copy, move, upload,
|
|
9
|
+
* trash…) must refuse because it holds credential material: the PPM config
|
|
10
|
+
* DB (provider keys, auth token) and `~/.cloudflared` (the Cloudflare login
|
|
11
|
+
* cert). Split out of `fs-path-guard.service.ts` so this one seam — "is this
|
|
12
|
+
* a credential path" — stays a single file instead of growing alongside the
|
|
13
|
+
* platform-allowlist and protected-root logic that lives there.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** Case-insensitive prefix test on Windows/macOS-style paths. */
|
|
17
|
+
function isInside(child: string, parent: string): boolean {
|
|
18
|
+
const norm = (p: string) => (process.platform === "win32" ? p.toLowerCase() : p);
|
|
19
|
+
const c = norm(child);
|
|
20
|
+
const p = norm(parent);
|
|
21
|
+
return c === p || c.startsWith(p.endsWith(sep) ? p : p + sep);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** True when the path is the PPM directory or anything inside it. */
|
|
25
|
+
export function isPpmDirPath(resolved: string): boolean {
|
|
26
|
+
return isInside(resolved, getPpmDir());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Chat attachments the user uploaded through the UI. They sit under the PPM
|
|
31
|
+
* dir so chat history keeps resolving them across reboots, but they are
|
|
32
|
+
* ordinary user content, not credential material: the transcript has to render
|
|
33
|
+
* the very image the assistant just read back from that directory.
|
|
34
|
+
*/
|
|
35
|
+
export function isChatUploadPath(resolved: string): boolean {
|
|
36
|
+
return isInside(resolved, getUploadsDir());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* True when the path is `~/.cloudflared` or anything inside it. Real
|
|
41
|
+
* `homedir()` is a deliberate exception to the getPpmDir()-only rule (see
|
|
42
|
+
* CLAUDE.md "PPM Directory"): `cloudflared`, not PPM, decides this location,
|
|
43
|
+
* and it holds `cert.pem` — an account-level Cloudflare login credential that
|
|
44
|
+
* must never be servable through a generic file route. `isInside` is a
|
|
45
|
+
* prefix match against this exact resolved path, not a substring test, so an
|
|
46
|
+
* unrelated folder that merely contains ".cloudflared" as a path segment
|
|
47
|
+
* elsewhere does not match.
|
|
48
|
+
*/
|
|
49
|
+
export function isCloudflaredDirPath(resolved: string): boolean {
|
|
50
|
+
return isInside(resolved, resolve(homedir(), ".cloudflared"));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** True when the path holds credential material a generic file route must never serve or relocate. */
|
|
54
|
+
export function isCredentialPath(resolved: string): boolean {
|
|
55
|
+
return isPpmDirPath(resolved) || isCloudflaredDirPath(resolved);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Refuse the PPM directory subtree and `~/.cloudflared` on read-style doors.
|
|
60
|
+
* The former stores the config database with provider credentials and auth
|
|
61
|
+
* tokens; the latter stores the Cloudflare login cert. Neither may be
|
|
62
|
+
* downloadable through a generic file route.
|
|
63
|
+
*
|
|
64
|
+
* Chat uploads are the one exception, and only for the PPM-dir branch — chat
|
|
65
|
+
* uploads always live under `getPpmDir()`, never under `~/.cloudflared`, so
|
|
66
|
+
* the exception is a no-op for the cloudflared branch. Every caller applies
|
|
67
|
+
* this to the requested path *and* to its real path, so a symlink parked in
|
|
68
|
+
* the uploads directory still fails on the second call and cannot reach the
|
|
69
|
+
* rest of the PPM dir through the exception, and a symlink pointing at
|
|
70
|
+
* `~/.cloudflared/cert.pem` fails the same way.
|
|
71
|
+
*/
|
|
72
|
+
export function assertNotPpmDir(resolved: string): void {
|
|
73
|
+
if (isCredentialPath(resolved) && !isChatUploadPath(resolved)) {
|
|
74
|
+
throw Object.assign(new Error("Access denied"), { status: 403, code: "EDENIED" });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Refuse operations that would relocate a credential directory's contents
|
|
80
|
+
* anywhere else — copy, move, rename, upload-over, trash. Reading a
|
|
81
|
+
* credential path is already blocked by `assertNotPpmDir`, so without this a
|
|
82
|
+
* copy to a public path followed by an ordinary read would still hand out
|
|
83
|
+
* the PPM config DB or the Cloudflare login cert; every write/transfer door
|
|
84
|
+
* in `fs-ops/` calls this on both the source and the destination.
|
|
85
|
+
*/
|
|
86
|
+
export function assertNotPpmSubtree(candidate: string): void {
|
|
87
|
+
if (isCredentialPath(candidate)) {
|
|
88
|
+
throw Object.assign(new Error(`Refusing to operate on a credential directory: ${candidate}`), {
|
|
89
|
+
status: 403,
|
|
90
|
+
code: "EPROTECTED",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Same refusal, applied to the real path as well. A path that does not exist
|
|
97
|
+
* yet is still resolved through its parents, so a symlinked directory cannot
|
|
98
|
+
* be used to reach — or create something inside — a credential directory.
|
|
99
|
+
*/
|
|
100
|
+
export async function assertNotPpmSubtreeDeep(candidate: string): Promise<void> {
|
|
101
|
+
assertNotPpmSubtree(candidate);
|
|
102
|
+
assertNotPpmSubtree(await realPathOrSelf(candidate));
|
|
103
|
+
}
|
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
import { resolve
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
2
|
import { lstat, realpath } from "node:fs/promises";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { getPpmDir } from "./ppm-dir.ts";
|
|
5
|
-
import { getUploadsDir } from "./chat-upload-storage.service.ts";
|
|
6
5
|
import { realPathOrSelf, realPathOrSelfSync } from "./fs-ops/fs-real-path.ts";
|
|
7
6
|
|
|
8
7
|
// Re-exported so callers reach both the guards and the resolver they build on
|
|
9
8
|
// through one import.
|
|
10
9
|
export { realPathOrSelf, realPathOrSelfSync };
|
|
11
10
|
|
|
11
|
+
// The credential-path predicates (PPM dir + ~/.cloudflared) live in their own
|
|
12
|
+
// module so this file stays focused on the platform allowlist and protected
|
|
13
|
+
// roots; re-exported here so every existing caller keeps a single import.
|
|
14
|
+
export {
|
|
15
|
+
isPpmDirPath,
|
|
16
|
+
isChatUploadPath,
|
|
17
|
+
isCloudflaredDirPath,
|
|
18
|
+
isCredentialPath,
|
|
19
|
+
assertNotPpmDir,
|
|
20
|
+
assertNotPpmSubtree,
|
|
21
|
+
assertNotPpmSubtreeDeep,
|
|
22
|
+
} from "./fs-credential-path-guard.ts";
|
|
23
|
+
|
|
12
24
|
/**
|
|
13
25
|
* Central guard for every filesystem route that works outside project scope.
|
|
14
26
|
* The whole disk is browsable behind PPM auth, so the remaining defences are:
|
|
@@ -38,14 +50,6 @@ export function resolvePath(input: string): string {
|
|
|
38
50
|
return resolve(input);
|
|
39
51
|
}
|
|
40
52
|
|
|
41
|
-
/** Case-insensitive prefix test on Windows/macOS-style paths. */
|
|
42
|
-
function isInside(child: string, parent: string): boolean {
|
|
43
|
-
const norm = (p: string) => (process.platform === "win32" ? p.toLowerCase() : p);
|
|
44
|
-
const c = norm(child);
|
|
45
|
-
const p = norm(parent);
|
|
46
|
-
return c === p || c.startsWith(p.endsWith(sep) ? p : p + sep);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
53
|
/**
|
|
50
54
|
* Whitelist for system-level access. POSIX exposes the whole tree; Windows
|
|
51
55
|
* requires a drive letter, which keeps UNC shares (`\\server\share`) out —
|
|
@@ -80,62 +84,6 @@ export function assertAllowed(resolved: string): void {
|
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
86
|
|
|
83
|
-
/** True when the path is the PPM directory or anything inside it. */
|
|
84
|
-
export function isPpmDirPath(resolved: string): boolean {
|
|
85
|
-
return isInside(resolved, getPpmDir());
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Chat attachments the user uploaded through the UI. They sit under the PPM
|
|
90
|
-
* dir so chat history keeps resolving them across reboots, but they are
|
|
91
|
-
* ordinary user content, not credential material: the transcript has to render
|
|
92
|
-
* the very image the assistant just read back from that directory.
|
|
93
|
-
*/
|
|
94
|
-
export function isChatUploadPath(resolved: string): boolean {
|
|
95
|
-
return isInside(resolved, getUploadsDir());
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Refuse the PPM directory subtree on read-style doors. It stores the config
|
|
100
|
-
* database with provider credentials and auth tokens, which must never be
|
|
101
|
-
* downloadable through a generic file route.
|
|
102
|
-
*
|
|
103
|
-
* Chat uploads are the one exception. Every caller applies this to the
|
|
104
|
-
* requested path *and* to its real path, so a symlink parked in the uploads
|
|
105
|
-
* directory still fails on the second call and cannot reach the rest of the
|
|
106
|
-
* PPM dir through the exception.
|
|
107
|
-
*/
|
|
108
|
-
export function assertNotPpmDir(resolved: string): void {
|
|
109
|
-
if (isPpmDirPath(resolved) && !isChatUploadPath(resolved)) {
|
|
110
|
-
throw Object.assign(new Error("Access denied"), { status: 403, code: "EDENIED" });
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Refuse operations that would take the PPM directory's contents anywhere
|
|
116
|
-
* else. Reading that subtree is already blocked, so without this a copy to a
|
|
117
|
-
* public path followed by an ordinary read would still hand out the
|
|
118
|
-
* credentials database.
|
|
119
|
-
*/
|
|
120
|
-
export function assertNotPpmSubtree(candidate: string): void {
|
|
121
|
-
if (isPpmDirPath(candidate)) {
|
|
122
|
-
throw Object.assign(new Error(`Refusing to operate on the PPM directory: ${candidate}`), {
|
|
123
|
-
status: 403,
|
|
124
|
-
code: "EPROTECTED",
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Same refusal, applied to the real path as well. A path that does not exist
|
|
131
|
-
* yet is still resolved through its parents, so a symlinked directory cannot
|
|
132
|
-
* be used to reach — or create something inside — the PPM directory.
|
|
133
|
-
*/
|
|
134
|
-
export async function assertNotPpmSubtreeDeep(candidate: string): Promise<void> {
|
|
135
|
-
assertNotPpmSubtree(candidate);
|
|
136
|
-
assertNotPpmSubtree(await realPathOrSelf(candidate));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
87
|
/** Paths whose removal or rename would break the host or PPM itself. */
|
|
140
88
|
export function isProtectedRoot(candidate: string): boolean {
|
|
141
89
|
const p = resolve(candidate);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare REST calls the setup flow needs beyond what cloudflared's own
|
|
3
|
+
* CLI exposes: looking up an existing tunnel's UUID by name (so the DNS
|
|
4
|
+
* collision check can tell "already ours" from "someone else's" before
|
|
5
|
+
* `tunnel create` has even run) and reading a zone's DNS records for a
|
|
6
|
+
* candidate hostname.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const TIMEOUT_MS = 10_000;
|
|
10
|
+
|
|
11
|
+
export interface DnsRecord {
|
|
12
|
+
content: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function cfGet<T extends { success?: boolean }>(url: string, apiToken: string): Promise<T> {
|
|
16
|
+
const res = await fetch(url, {
|
|
17
|
+
headers: { Authorization: `Bearer ${apiToken}` },
|
|
18
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
19
|
+
});
|
|
20
|
+
const json = (await res.json().catch(() => null)) as T | null;
|
|
21
|
+
if (!res.ok || !json || json.success !== true) {
|
|
22
|
+
throw new Error(`Cloudflare API call failed (HTTP ${res.status})`);
|
|
23
|
+
}
|
|
24
|
+
return json;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** DNS records at `zoneID` matching `name` exactly (empty when none exist). */
|
|
28
|
+
export async function fetchDnsRecords(zoneID: string, apiToken: string, name: string): Promise<DnsRecord[]> {
|
|
29
|
+
const url = `https://api.cloudflare.com/client/v4/zones/${encodeURIComponent(zoneID)}/dns_records?name=${encodeURIComponent(name)}`;
|
|
30
|
+
const json = await cfGet<{ success: true; result?: DnsRecord[] }>(url, apiToken);
|
|
31
|
+
return Array.isArray(json.result) ? json.result : [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** UUID of an existing, non-deleted tunnel named `name` — null if none exists yet. */
|
|
35
|
+
export async function fetchTunnelIdByName(accountID: string, apiToken: string, name: string): Promise<string | null> {
|
|
36
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${encodeURIComponent(accountID)}/cfd_tunnel?name=${encodeURIComponent(name)}&is_deleted=false`;
|
|
37
|
+
const json = await cfGet<{ success: true; result?: Array<{ id?: string }> }>(url, apiToken);
|
|
38
|
+
const found = (json.result ?? []).find((t) => typeof t.id === "string" && t.id);
|
|
39
|
+
return found?.id ?? null;
|
|
40
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the zone name behind a `zoneID`, using the `apiToken` embedded in the
|
|
3
|
+
* user's origin cert — this avoids asking the user to type a hostname; PPM
|
|
4
|
+
* proposes `ppm.<zone>` from data it already has.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** 10s bound so a hung Cloudflare API call never wedges setup indefinitely. */
|
|
8
|
+
const TIMEOUT_MS = 10_000;
|
|
9
|
+
|
|
10
|
+
export async function fetchZoneName(zoneID: string, apiToken: string): Promise<string> {
|
|
11
|
+
const url = `https://api.cloudflare.com/client/v4/zones/${encodeURIComponent(zoneID)}`;
|
|
12
|
+
const res = await fetch(url, {
|
|
13
|
+
headers: { Authorization: `Bearer ${apiToken}` },
|
|
14
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
let json: { success?: boolean; result?: { name?: string } } | null = null;
|
|
18
|
+
try {
|
|
19
|
+
json = await res.json();
|
|
20
|
+
} catch {
|
|
21
|
+
json = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!res.ok || !json || json.success !== true || typeof json.result?.name !== "string" || !json.result.name) {
|
|
25
|
+
throw new Error(`Cloudflare rejected the certificate token (HTTP ${res.status}) — log in again`);
|
|
26
|
+
}
|
|
27
|
+
return json.result.name;
|
|
28
|
+
}
|
|
@@ -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
|
+
}
|