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