@hienlh/ppm 0.17.56 → 0.18.0

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 (583) hide show
  1. package/.release-run.sh +5 -0
  2. package/CHANGELOG.md +21 -0
  3. package/CLAUDE.md +4 -0
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/http-api.md +16 -2
  6. package/bun.lock +3 -0
  7. package/dist/web/assets/{ai-resource-editor-DL011EDu.js → ai-resource-editor-BLYgg033.js} +1 -1
  8. package/dist/web/assets/{angular-html-D4YJUMLP.js → angular-html-jfMqUuRd.js} +1 -1
  9. package/dist/web/assets/{angular-ts-CUvq2h3X.js → angular-ts-wytn9PMm.js} +1 -1
  10. package/dist/web/assets/{api-client-COQtP-dB.js → api-client-B9OFv3nY.js} +1 -1
  11. package/dist/web/assets/api-client-CKo60P6G.js +1 -0
  12. package/dist/web/assets/{api-settings-Do0Z0IRN.js → api-settings-DGhshDRg.js} +1 -1
  13. package/dist/web/assets/api-settings-fdkhqLSF.js +1 -0
  14. package/dist/web/assets/{api-themes-DXi8sz-9.js → api-themes-okiyhhp9.js} +1 -1
  15. package/dist/web/assets/{apl-DSANFLx0.js → apl-i-ahKx6O.js} +1 -1
  16. package/dist/web/assets/architecture-PBZL5I3N-B4J2-O8Q.js +1 -0
  17. package/dist/web/assets/{astro-CXIotiNS.js → astro-DVBCJfG6.js} +1 -1
  18. package/dist/web/assets/{audio-preview-H16VO99g.js → audio-preview-y8XpCNrh.js} +1 -1
  19. package/dist/web/assets/{blade-BvrP2gPd.js → blade-BHlL8SqU.js} +1 -1
  20. package/dist/web/assets/{bsl-CGf6Xuyu.js → bsl-D-UOfR1F.js} +1 -1
  21. package/dist/web/assets/{cairo-BboZ9qIf.js → cairo-Bn6rucli.js} +1 -1
  22. package/dist/web/assets/can-open-in-ppm-BzVXoV-d.js +1 -0
  23. package/dist/web/assets/chat-tab-BcR-3_6R.js +13 -0
  24. package/dist/web/assets/clock-B1CPBp2g.js +1 -0
  25. package/dist/web/assets/{cobol-D39V6ukp.js → cobol-Byn9FYKu.js} +1 -1
  26. package/dist/web/assets/code-editor-CJ8g7dqm.js +10 -0
  27. package/dist/web/assets/{coffee-DTtK7kKi.js → coffee-DjBl4gm-.js} +1 -1
  28. package/dist/web/assets/{conflict-editor-B_wAb0Kb.js → conflict-editor-BP7JFZzG.js} +3 -3
  29. package/dist/web/assets/context-menu-BSE5yuMG.js +1 -0
  30. package/dist/web/assets/copy-Su9m2utd.js +1 -0
  31. package/dist/web/assets/{cpp-CnU9hfBq.js → cpp-CKzic-3E.js} +1 -1
  32. package/dist/web/assets/{crystal-DMjCL--0.js → crystal-Bkg-Ly_g.js} +1 -1
  33. package/dist/web/assets/csv-preview-xAwUDNat.js +2 -0
  34. package/dist/web/assets/{data-grid-overlay-editor-Bdc80TJ1.js → data-grid-overlay-editor-DMTCHIG9.js} +1 -1
  35. package/dist/web/assets/{database-viewer-awmnuDCt.js → database-viewer-BVLWYPa4.js} +1 -1
  36. package/dist/web/assets/dialog-D__LRQTn.js +1 -0
  37. package/dist/web/assets/diff-viewer-BheSOHfl.js +4 -0
  38. package/dist/web/assets/{docx-preview-DDw3duw9.js → docx-preview-CGlMqltP.js} +1 -1
  39. package/dist/web/assets/{edge-CTom35R_.js → edge-DVbZnd3Y.js} +1 -1
  40. package/dist/web/assets/{edit-diff-preview-D1TEK7Jz.js → edit-diff-preview-ClJAJwlL.js} +1 -1
  41. package/dist/web/assets/{elixir-BAX9HSOu.js → elixir-Dwi8Wu7W.js} +1 -1
  42. package/dist/web/assets/{elm-CpD28xsW.js → elm-DtIv-HXJ.js} +1 -1
  43. package/dist/web/assets/{erb-DHX2IIEs.js → erb-D3HEDz1x.js} +1 -1
  44. package/dist/web/assets/{erlang-B_etLcPG.js → erlang-2MjTCzGz.js} +1 -1
  45. package/dist/web/assets/{esm-PmlN1RTs.js → esm-BfqDE09P.js} +1 -1
  46. package/dist/web/assets/explorer-body-DE5339g9.js +2 -0
  47. package/dist/web/assets/explorer-window-content-BrTHAoXM.js +1 -0
  48. package/dist/web/assets/{extension-webview-DkTI51P3.js → extension-webview-DRq1tWC1.js} +1 -1
  49. package/dist/web/assets/{fortran-fixed-form-CpR0cN7O.js → fortran-fixed-form-BLyAtyPM.js} +1 -1
  50. package/dist/web/assets/{fsharp-DxITxZW5.js → fsharp-Bxbn_gbr.js} +1 -1
  51. package/dist/web/assets/{gdresource-DJ7IvROZ.js → gdresource-sFMY4D2Q.js} +1 -1
  52. package/dist/web/assets/{git-commit-sQznNP43.js → git-commit-BzB9Lio5.js} +1 -1
  53. package/dist/web/assets/git-log-panel-pjyOjchU.js +1 -0
  54. package/dist/web/assets/{git-rebase-DkHvejAR.js → git-rebase-CKSVd848.js} +1 -1
  55. package/dist/web/assets/gitGraph-HDMCJU4V-CtQ3yXN-.js +1 -0
  56. package/dist/web/assets/{glide-data-grid-Bpgp72Hg.js → glide-data-grid-LX3N4SsJ.js} +9 -9
  57. package/dist/web/assets/{glimmer-js-BwJnDfL-.js → glimmer-js-BY8aExAJ.js} +1 -1
  58. package/dist/web/assets/{glimmer-ts-Coz155XK.js → glimmer-ts-Bak54dBX.js} +1 -1
  59. package/dist/web/assets/{glsl-BED7ZmUZ.js → glsl-nR2eaJQe.js} +1 -1
  60. package/dist/web/assets/{graphql-B_KyE6oz.js → graphql-CGPPFAU6.js} +1 -1
  61. package/dist/web/assets/group-chat-tab-CVQfptkT.js +1 -0
  62. package/dist/web/assets/{hack-sxywdIAQ.js → hack-CXIMX-Ol.js} +1 -1
  63. package/dist/web/assets/{haml-Dts9JlPU.js → haml-tPY-9Pfm.js} +1 -1
  64. package/dist/web/assets/{handlebars-C9HXIMtj.js → handlebars-BuW1LT7T.js} +1 -1
  65. package/dist/web/assets/{html-2p0RvXi4.js → html-bgVcwZ-t.js} +1 -1
  66. package/dist/web/assets/{html-derivative-BiTPuOba.js → html-derivative-D6-IxTgn.js} +1 -1
  67. package/dist/web/assets/{http-CHd2gYPf.js → http-CJ1zyB-h.js} +1 -1
  68. package/dist/web/assets/{hurl-boMPgDm9.js → hurl-CmoT7l3T.js} +1 -1
  69. package/dist/web/assets/{hxml-BQvjiuBt.js → hxml-CztOsL8o.js} +1 -1
  70. package/dist/web/assets/{image-preview-Dwg0EuJ2.js → image-preview-BDySmiim.js} +1 -1
  71. package/dist/web/assets/index-XoDY9bc0.js +52 -0
  72. package/dist/web/assets/index-YCTraslE.css +2 -0
  73. package/dist/web/assets/info-3K5VOQVL-Cg0mIJn9.js +1 -0
  74. package/dist/web/assets/{jinja-B-zKiwlp.js → jinja-B1pq3jJq.js} +1 -1
  75. package/dist/web/assets/{jison-C2vJYjbJ.js → jison-D8B5pCwA.js} +1 -1
  76. package/dist/web/assets/{julia-DJ8MrQPA.js → julia-D0K3bwpU.js} +1 -1
  77. package/dist/web/assets/{just-BvQHq7VE.js → just-B9Jlddu-.js} +1 -1
  78. package/dist/web/assets/keybindings-store-BE1jVUZW.js +1 -0
  79. package/dist/web/assets/{latex-ChNeshUC.js → latex-BPJSSE6j.js} +1 -1
  80. package/dist/web/assets/{liquid-KNJEBOwW.js → liquid-lv10fo4U.js} +1 -1
  81. package/dist/web/assets/{lua-D8NNsLbC.js → lua-PNuaIBEF.js} +1 -1
  82. package/dist/web/assets/{markdown-renderer-C7dlPs2S.js → markdown-renderer-B8Kz5rvP.js} +3 -3
  83. package/dist/web/assets/markdown-renderer-Btync7yg.js +1 -0
  84. package/dist/web/assets/{marko-HGo5Qtz4.js → marko-Du3frD6X.js} +1 -1
  85. package/dist/web/assets/{mdc-omkodQB5.js → mdc-BeDVcFRl.js} +1 -1
  86. package/dist/web/assets/mobile-bottom-sheet-CzRXE6mY.js +1 -0
  87. package/dist/web/assets/mobile-explorer-sheet-CDU8xriw.js +1 -0
  88. package/dist/web/assets/{nextflow-Adq02pv-.js → nextflow-DkcXP71s.js} +1 -1
  89. package/dist/web/assets/{nginx-ByvWruGD.js → nginx-C5n8N_bt.js} +1 -1
  90. package/dist/web/assets/{nim-BrMojX4O.js → nim-CIhyfs-A.js} +1 -1
  91. package/dist/web/assets/notification-store-Bi7VHAy4.js +1 -0
  92. package/dist/web/assets/{number-overlay-editor-CunvWJnN.js → number-overlay-editor-B8P26qGC.js} +1 -1
  93. package/dist/web/assets/packet-RMMSAZCW-D79vAFR_.js +1 -0
  94. package/dist/web/assets/{panel-store-CpKvXNnm.js → panel-store-gbNJvZB_.js} +1 -1
  95. package/dist/web/assets/{pdf-preview-CEWYMxdp.js → pdf-preview-CtZWvujY.js} +1 -1
  96. package/dist/web/assets/{perl-Bd_0l-b4.js → perl-Cf31sZYE.js} +1 -1
  97. package/dist/web/assets/{php-D-DLTnWJ.js → php-Dzl_MDIW.js} +1 -1
  98. package/dist/web/assets/pie-UPGHQEXC-DYUOH68U.js +1 -0
  99. package/dist/web/assets/{postgres-viewer-D7PggslU.js → postgres-viewer-fqMUi0xS.js} +3 -3
  100. package/dist/web/assets/{project-store-D0ALup9Z.js → project-store-D1D1MN03.js} +1 -1
  101. package/dist/web/assets/{pug-D2aO-l2T.js → pug-CxSYHrB3.js} +1 -1
  102. package/dist/web/assets/{qml-CyQVzNXS.js → qml-DlEjGhwN.js} +1 -1
  103. package/dist/web/assets/radar-KQ55EAFF-B1eznNtd.js +1 -0
  104. package/dist/web/assets/{razor-8tvJXsO3.js → razor-KVt5uW14.js} +1 -1
  105. package/dist/web/assets/{rst-C4AZyQQl.js → rst-D5BwNTnw.js} +1 -1
  106. package/dist/web/assets/{ruby-O_z2NTLY.js → ruby-D5fejQwd.js} +1 -1
  107. package/dist/web/assets/{sas-BtnF4zaV.js → sas-Bj7B1CB2.js} +1 -1
  108. package/dist/web/assets/{scss-GAyp5euH.js → scss-BB0CUoCL.js} +1 -1
  109. package/dist/web/assets/settings-store-Bm7uL8VA.js +2 -0
  110. package/dist/web/assets/settings-tab-DxtBG0DW.js +1 -0
  111. package/dist/web/assets/{shaderlab-nM5YZwa5.js → shaderlab-BdjEZUMc.js} +1 -1
  112. package/dist/web/assets/{shellsession-D2OCI4Gv.js → shellsession-Bp1OA1rR.js} +1 -1
  113. package/dist/web/assets/{soy-DrLNhqfp.js → soy-DlYz3wsC.js} +1 -1
  114. package/dist/web/assets/sparkles-BvJ1EGWd.js +1 -0
  115. package/dist/web/assets/{sparql-CsM6F2Yv.js → sparql-CdfII5Jd.js} +1 -1
  116. package/dist/web/assets/{sql-query-editor-ZnKcxCyj.js → sql-query-editor-D2UxRvG7.js} +1 -1
  117. package/dist/web/assets/sqlite-viewer-CTBwzT-r.js +1 -0
  118. package/dist/web/assets/{stata-Bk3cgfXJ.js → stata-BvUA6GUW.js} +1 -1
  119. package/dist/web/assets/{surrealql-Dkz0DPK8.js → surrealql--DLNbTyI.js} +1 -1
  120. package/dist/web/assets/{svelte-23si1KV5.js → svelte-CBfSsG4n.js} +1 -1
  121. package/dist/web/assets/{system-monitor-tab-Ck3REHUg.js → system-monitor-tab-Bscf6tzK.js} +1 -1
  122. package/dist/web/assets/{tab-store-BQmDVh9c.js → tab-store-DnNnaAcE.js} +1 -1
  123. package/dist/web/assets/{templ-CPxlP4vF.js → templ-Dv8zpKZW.js} +1 -1
  124. package/dist/web/assets/{terminal-tab-D6tYDFSF.js → terminal-tab-CTKU5gaF.js} +3 -3
  125. package/dist/web/assets/{tex-ugJpgYPc.js → tex-CsrOiqh1.js} +1 -1
  126. package/dist/web/assets/tool-cards-xstpayaW.js +7 -0
  127. package/dist/web/assets/treemap-KZPCXAKY-SeI2TxLs.js +1 -0
  128. package/dist/web/assets/{ts-tags-BovP9TMu.js → ts-tags-3T98k4EJ.js} +1 -1
  129. package/dist/web/assets/{twig-B7VUm2Ca.js → twig-u6QZ45FH.js} +1 -1
  130. package/dist/web/assets/{use-blob-url-B3YovXF6.js → use-blob-url-BLRmhlZI.js} +1 -1
  131. package/dist/web/assets/{use-monaco-theme-wKbyNwIn.js → use-monaco-theme-DxYstgYa.js} +1 -1
  132. package/dist/web/assets/{vendor-mermaid-CYOBtZtD.js → vendor-mermaid-C977xNeE.js} +1 -1
  133. package/dist/web/assets/{vendor-ui-DMRGWRuc.js → vendor-ui-CXJtBULr.js} +1 -1
  134. package/dist/web/assets/{video-preview-DwrfenNA.js → video-preview-cfaoRxG3.js} +1 -1
  135. package/dist/web/assets/{vue-m75pWowk.js → vue-CxIsxtwn.js} +1 -1
  136. package/dist/web/assets/{vue-html-odHs7IMn.js → vue-html-BGL5qnuH.js} +1 -1
  137. package/dist/web/assets/{vue-vine-BbBB3V_G.js → vue-vine-DHPh_gMr.js} +1 -1
  138. package/dist/web/assets/{xml-BHzIcDWI.js → xml-C-vM2m7J.js} +1 -1
  139. package/dist/web/assets/{xsl-DTLhN7uu.js → xsl-DvTXNwuO.js} +1 -1
  140. package/dist/web/index.html +26 -23
  141. package/dist/web/sw.js +1 -1
  142. package/docs/codebase-summary.md +10 -1
  143. package/docs/design-guidelines.md +31 -0
  144. package/docs/project-roadmap.md +10 -0
  145. package/docs/system-architecture.md +59 -0
  146. package/package.json +2 -1
  147. package/src/providers/claude-agent-sdk.ts +6 -0
  148. package/src/server/index.ts +8 -0
  149. package/src/server/routes/files.ts +2 -2
  150. package/src/server/routes/fs-browse.ts +99 -74
  151. package/src/server/routes/fs-ops.ts +118 -0
  152. package/src/server/routes/fs-sqlite.ts +129 -0
  153. package/src/server/routes/host-info.ts +17 -0
  154. package/src/server/routes/settings.ts +2 -0
  155. package/src/services/download-token.service.ts +21 -5
  156. package/src/services/file.service.ts +8 -12
  157. package/src/services/fs-browse.service.ts +104 -161
  158. package/src/services/fs-ops/fs-content-disposition.ts +17 -0
  159. package/src/services/fs-ops/fs-core-ops.ts +149 -0
  160. package/src/services/fs-ops/fs-error-response.ts +24 -0
  161. package/src/services/fs-ops/fs-git-init.service.ts +23 -0
  162. package/src/services/fs-ops/fs-hidden-names.ts +17 -0
  163. package/src/services/fs-ops/fs-list-files.service.ts +68 -0
  164. package/src/services/fs-ops/fs-ops-copy-move.service.ts +60 -0
  165. package/src/services/fs-ops/fs-ops-mutate.service.ts +59 -0
  166. package/src/services/fs-ops/fs-ops-read-write.service.ts +70 -0
  167. package/src/services/fs-ops/fs-ops-stat.service.ts +109 -0
  168. package/src/services/fs-ops/fs-ops-trash.service.ts +111 -0
  169. package/src/services/fs-ops/fs-real-path.ts +44 -0
  170. package/src/services/fs-ops/sql-row-reader.ts +26 -0
  171. package/src/services/fs-ops/sql-statement-guard.ts +63 -0
  172. package/src/services/fs-path-guard.service.ts +196 -0
  173. package/src/services/host-info/apple-bookmark-decoder.ts +97 -0
  174. package/src/services/host-info/apple-plist-xml-parser.ts +107 -0
  175. package/src/services/host-info/concurrency-limit.ts +20 -0
  176. package/src/services/host-info/host-drives.ts +190 -0
  177. package/src/services/host-info/host-info.service.ts +171 -0
  178. package/src/services/host-info/host-known-folders.ts +180 -0
  179. package/src/services/host-info/pinned-linux-bookmarks.ts +89 -0
  180. package/src/services/host-info/pinned-macos-finder-favorites.ts +175 -0
  181. package/src/services/host-info/pinned-windows-quick-access.ts +87 -0
  182. package/src/services/host-info/spawn-runner.ts +41 -0
  183. package/src/services/jsonl-transcript-parser.ts +6 -0
  184. package/src/services/sqlite-types.ts +26 -0
  185. package/src/services/sqlite.service.ts +38 -29
  186. package/src/services/subagent-transcript-merger.ts +144 -0
  187. package/src/types/system.ts +50 -0
  188. package/src/web/app.tsx +19 -3
  189. package/src/web/components/editor/code-editor.tsx +3 -9
  190. package/src/web/components/explorer/file-tree.tsx +103 -21
  191. package/src/web/components/explorer/tree-node-context-menu.tsx +6 -1
  192. package/src/web/components/explorer/tree-node.tsx +34 -93
  193. package/src/web/components/explorer/use-file-upload-drag.ts +14 -9
  194. package/src/web/components/explorer/use-tree-row-dnd.ts +206 -0
  195. package/src/web/components/floating-window/default-window-chrome.tsx +71 -0
  196. package/src/web/components/floating-window/floating-window.tsx +167 -0
  197. package/src/web/components/floating-window/use-window-drag.ts +28 -0
  198. package/src/web/components/floating-window/use-window-gesture-context.ts +44 -0
  199. package/src/web/components/floating-window/use-window-keyboard.ts +55 -0
  200. package/src/web/components/floating-window/use-window-resize.ts +30 -0
  201. package/src/web/components/floating-window/window-chrome-contract.ts +38 -0
  202. package/src/web/components/floating-window/window-content-registry.ts +51 -0
  203. package/src/web/components/floating-window/window-geometry.ts +121 -0
  204. package/src/web/components/floating-window/window-layer.tsx +92 -0
  205. package/src/web/components/floating-window/window-persistence.ts +104 -0
  206. package/src/web/components/floating-window/window-resize-handles.tsx +81 -0
  207. package/src/web/components/floating-window/window-store-types.ts +20 -0
  208. package/src/web/components/floating-window/window-store.ts +192 -0
  209. package/src/web/components/layout/command-palette.tsx +10 -0
  210. package/src/web/components/layout/mobile-drawer.tsx +11 -2
  211. package/src/web/components/layout/nav-section-rail.tsx +7 -1
  212. package/src/web/components/os-explorer/actions/explorer-actions-clipboard.ts +186 -0
  213. package/src/web/components/os-explorer/actions/explorer-actions-mutate.ts +109 -0
  214. package/src/web/components/os-explorer/actions/explorer-actions-open.ts +67 -0
  215. package/src/web/components/os-explorer/actions/explorer-actions-transfer-helpers.ts +35 -0
  216. package/src/web/components/os-explorer/actions/use-explorer-actions.ts +205 -0
  217. package/src/web/components/os-explorer/can-open-in-ppm.ts +89 -0
  218. package/src/web/components/os-explorer/dnd/drag-depth-counter.ts +36 -0
  219. package/src/web/components/os-explorer/dnd/drag-ghost.ts +44 -0
  220. package/src/web/components/os-explorer/dnd/drop-target-decision.ts +46 -0
  221. package/src/web/components/os-explorer/dnd/drop-target-style.ts +7 -0
  222. package/src/web/components/os-explorer/dnd/entry-drag-payload.ts +115 -0
  223. package/src/web/components/os-explorer/dnd/entry-drag-state.ts +35 -0
  224. package/src/web/components/os-explorer/dnd/entry-drop-executor.ts +43 -0
  225. package/src/web/components/os-explorer/dnd/use-drag-auto-scroll.ts +70 -0
  226. package/src/web/components/os-explorer/dnd/use-drop-transfer.tsx +82 -0
  227. package/src/web/components/os-explorer/dnd/use-entry-drag-source.ts +73 -0
  228. package/src/web/components/os-explorer/dnd/use-entry-drop-target.ts +150 -0
  229. package/src/web/components/os-explorer/dnd/use-entry-row-dnd.ts +66 -0
  230. package/src/web/components/os-explorer/dnd/use-path-drop-target.ts +30 -0
  231. package/src/web/components/os-explorer/explorer-body.tsx +153 -0
  232. package/src/web/components/os-explorer/explorer-breadcrumb.tsx +108 -0
  233. package/src/web/components/os-explorer/explorer-context-menu.tsx +131 -0
  234. package/src/web/components/os-explorer/explorer-dialogs.tsx +103 -0
  235. package/src/web/components/os-explorer/explorer-modal-shell.tsx +60 -0
  236. package/src/web/components/os-explorer/explorer-pins-store.ts +63 -0
  237. package/src/web/components/os-explorer/explorer-sidebar.tsx +198 -0
  238. package/src/web/components/os-explorer/explorer-sort-menu.tsx +68 -0
  239. package/src/web/components/os-explorer/explorer-status-bar.tsx +35 -0
  240. package/src/web/components/os-explorer/explorer-store.ts +179 -0
  241. package/src/web/components/os-explorer/explorer-toolbar.tsx +139 -0
  242. package/src/web/components/os-explorer/explorer-window-content.tsx +36 -0
  243. package/src/web/components/os-explorer/format-file-meta.ts +74 -0
  244. package/src/web/components/os-explorer/icons/file-type-icon-map.ts +106 -0
  245. package/src/web/components/os-explorer/icons/file-type-icon.tsx +56 -0
  246. package/src/web/components/os-explorer/icons/thumbnail-fetch-semaphore.ts +44 -0
  247. package/src/web/components/os-explorer/icons/thumbnail-image.tsx +124 -0
  248. package/src/web/components/os-explorer/mobile/mobile-bottom-toolbar.tsx +102 -0
  249. package/src/web/components/os-explorer/mobile/mobile-explorer-sheet.tsx +61 -0
  250. package/src/web/components/os-explorer/mobile/mobile-explorer-top-bar.tsx +41 -0
  251. package/src/web/components/os-explorer/mobile/mobile-more-sheet.tsx +117 -0
  252. package/src/web/components/os-explorer/mobile/mobile-places-sheet.tsx +120 -0
  253. package/src/web/components/os-explorer/mobile/mobile-places-strip.tsx +109 -0
  254. package/src/web/components/os-explorer/mobile/mobile-tap-action.ts +19 -0
  255. package/src/web/components/os-explorer/mobile/use-mobile-row-tap.ts +50 -0
  256. package/src/web/components/os-explorer/open-explorer.ts +35 -0
  257. package/src/web/components/os-explorer/properties-dialog.tsx +99 -0
  258. package/src/web/components/os-explorer/skins/explorer-skin-chrome.tsx +17 -0
  259. package/src/web/components/os-explorer/skins/folder-icon-macos.tsx +42 -0
  260. package/src/web/components/os-explorer/skins/folder-icon-windows.tsx +25 -0
  261. package/src/web/components/os-explorer/skins/macos-skin.ts +14 -0
  262. package/src/web/components/os-explorer/skins/macos-window-chrome.tsx +69 -0
  263. package/src/web/components/os-explorer/skins/skin-types.ts +40 -0
  264. package/src/web/components/os-explorer/skins/skins.css +25 -0
  265. package/src/web/components/os-explorer/skins/use-explorer-skin.ts +21 -0
  266. package/src/web/components/os-explorer/skins/windows-skin.ts +11 -0
  267. package/src/web/components/os-explorer/skins/windows-window-chrome.tsx +56 -0
  268. package/src/web/components/os-explorer/sort-and-filter-entries.ts +52 -0
  269. package/src/web/components/os-explorer/sort-options.ts +29 -0
  270. package/src/web/components/os-explorer/toolbar-icon-button.ts +18 -0
  271. package/src/web/components/os-explorer/use-coarse-long-press.ts +97 -0
  272. package/src/web/components/os-explorer/use-explorer-keyboard.ts +124 -0
  273. package/src/web/components/os-explorer/use-explorer-navigation.ts +155 -0
  274. package/src/web/components/os-explorer/use-explorer-open-state.ts +67 -0
  275. package/src/web/components/os-explorer/use-host-info.ts +85 -0
  276. package/src/web/components/os-explorer/views/column-row.tsx +126 -0
  277. package/src/web/components/os-explorer/views/column-view-column.tsx +112 -0
  278. package/src/web/components/os-explorer/views/column-view-mobile.tsx +98 -0
  279. package/src/web/components/os-explorer/views/column-view-preview.tsx +70 -0
  280. package/src/web/components/os-explorer/views/column-view.tsx +204 -0
  281. package/src/web/components/os-explorer/views/compose-interactive-row-props.ts +77 -0
  282. package/src/web/components/os-explorer/views/explorer-view-registry.tsx +55 -0
  283. package/src/web/components/os-explorer/views/icons-view-tile.tsx +158 -0
  284. package/src/web/components/os-explorer/views/icons-view.tsx +188 -0
  285. package/src/web/components/os-explorer/views/inline-name-input.tsx +60 -0
  286. package/src/web/components/os-explorer/views/list-row.tsx +162 -0
  287. package/src/web/components/os-explorer/views/list-view.tsx +177 -0
  288. package/src/web/components/os-explorer/views/use-column-view-state.ts +158 -0
  289. package/src/web/components/os-explorer/views/use-entry-selection.ts +178 -0
  290. package/src/web/components/settings/settings-tab.tsx +29 -2
  291. package/src/web/components/sqlite/sqlite-viewer.tsx +5 -2
  292. package/src/web/components/sqlite/use-sqlite.ts +7 -2
  293. package/src/web/components/ui/adaptive-context-menu.tsx +57 -1
  294. package/src/web/components/ui/file-browser-picker.tsx +1 -18
  295. package/src/web/hooks/use-chat.ts +22 -4
  296. package/src/web/lib/file-download.ts +6 -4
  297. package/src/web/lib/fs-api.ts +185 -0
  298. package/src/web/stores/file-store.ts +38 -1
  299. package/src/web/stores/settings-store.ts +14 -0
  300. package/src/web/styles/globals.css +1 -0
  301. package/dist/web/assets/api-client-RtNXvS0P.js +0 -1
  302. package/dist/web/assets/api-settings-De--HNHp.js +0 -1
  303. package/dist/web/assets/architecture-PBZL5I3N-DVRYSsQn.js +0 -1
  304. package/dist/web/assets/chat-tab-D2nZCpAo.js +0 -13
  305. package/dist/web/assets/code-editor-DqQy2Qm8.js +0 -10
  306. package/dist/web/assets/context-menu-DIiZdfbE.js +0 -1
  307. package/dist/web/assets/csv-preview-BL0I3CKz.js +0 -2
  308. package/dist/web/assets/dialog-HHdNInEH.js +0 -1
  309. package/dist/web/assets/diff-viewer-_yQtutHR.js +0 -4
  310. package/dist/web/assets/git-log-panel-NkghB_rO.js +0 -1
  311. package/dist/web/assets/gitGraph-HDMCJU4V-CutkUNY_.js +0 -1
  312. package/dist/web/assets/group-chat-tab-CQOVBmVp.js +0 -1
  313. package/dist/web/assets/index-CJ0Wp_rW.js +0 -50
  314. package/dist/web/assets/index-CVJmlKhp.css +0 -2
  315. package/dist/web/assets/info-3K5VOQVL-BpNjgbj9.js +0 -1
  316. package/dist/web/assets/keybindings-store-ClNYe9vD.js +0 -1
  317. package/dist/web/assets/markdown-renderer-CwpP5Ola.js +0 -1
  318. package/dist/web/assets/notification-store-DwAhNzTy.js +0 -1
  319. package/dist/web/assets/packet-RMMSAZCW-D5IUZ66-.js +0 -1
  320. package/dist/web/assets/pie-UPGHQEXC-CM29eKDR.js +0 -1
  321. package/dist/web/assets/radar-KQ55EAFF-QtJGaSGt.js +0 -1
  322. package/dist/web/assets/settings-store-B3LfpCPa.js +0 -2
  323. package/dist/web/assets/settings-tab-CeSJQIfF.js +0 -1
  324. package/dist/web/assets/sparkles-B3DmG8PJ.js +0 -1
  325. package/dist/web/assets/sqlite-viewer-8fk_wut8.js +0 -1
  326. package/dist/web/assets/tool-cards-C3ka1Y7I.js +0 -7
  327. package/dist/web/assets/treemap-KZPCXAKY-CjUqpVnS.js +0 -1
  328. /package/dist/web/assets/{abap-BM5PMK0-.js → abap-C1K0-wzc.js} +0 -0
  329. /package/dist/web/assets/{actionscript-3-CML-MTNy.js → actionscript-3-CJhQVjLA.js} +0 -0
  330. /package/dist/web/assets/{ada-DzO1tMqV.js → ada-Bt8r9fME.js} +0 -0
  331. /package/dist/web/assets/{andromeeda-C3nkvRhj.js → andromeeda-DF1I8qi3.js} +0 -0
  332. /package/dist/web/assets/{apache-Dg7oWQbs.js → apache-CnlleE6P.js} +0 -0
  333. /package/dist/web/assets/{apex-3O2170Ju.js → apex-j0C6Xmk6.js} +0 -0
  334. /package/dist/web/assets/{applescript-5AlzBqQO.js → applescript-DO7AnO8U.js} +0 -0
  335. /package/dist/web/assets/{apply-theme-BSJDABU3.js → apply-theme-CMQXnUAa.js} +0 -0
  336. /package/dist/web/assets/{ara-BGtOX5Hn.js → ara-i83AydZj.js} +0 -0
  337. /package/dist/web/assets/{asciidoc-CIsC4kkq.js → asciidoc-DbBA6-sF.js} +0 -0
  338. /package/dist/web/assets/{asm-p1-PUkh3.js → asm-B5S47Rgr.js} +0 -0
  339. /package/dist/web/assets/{aurora-x-BPgGL50S.js → aurora-x-De_IaSKw.js} +0 -0
  340. /package/dist/web/assets/{awk-Cj93pRL8.js → awk-CfyLHXqS.js} +0 -0
  341. /package/dist/web/assets/{ayu-dark-C50MYKhw.js → ayu-dark-DTLdZ1Si.js} +0 -0
  342. /package/dist/web/assets/{ayu-light-DyIp0Idy.js → ayu-light-DTOfkzhu.js} +0 -0
  343. /package/dist/web/assets/{ayu-mirage-BAdm8pKU.js → ayu-mirage-DsAECCMv.js} +0 -0
  344. /package/dist/web/assets/{ballerina-DUO9mwyV.js → ballerina-CumGp9A7.js} +0 -0
  345. /package/dist/web/assets/{bat-Cg8hlSct.js → bat-Ca2SBbYN.js} +0 -0
  346. /package/dist/web/assets/{beancount-f8f3BBqB.js → beancount-CqyXBwwF.js} +0 -0
  347. /package/dist/web/assets/{berry-BqVQYZZr.js → berry-yl-kJr_H.js} +0 -0
  348. /package/dist/web/assets/{bibtex-C_q8ygAM.js → bibtex-DoFFszjV.js} +0 -0
  349. /package/dist/web/assets/{bicep-DWS-2uWR.js → bicep-CWPi4nfK.js} +0 -0
  350. /package/dist/web/assets/{bird2-C0pvipi_.js → bird2-DXew-ut0.js} +0 -0
  351. /package/dist/web/assets/{bot-E9mcVZnr.js → bot-BHXjpQ7x.js} +0 -0
  352. /package/dist/web/assets/{c-BQFvJ6Ux.js → c-clbySDvd.js} +0 -0
  353. /package/dist/web/assets/{c3-Da53KeG6.js → c3-B65jvSM1.js} +0 -0
  354. /package/dist/web/assets/{cadence-B5X2vWm3.js → cadence-Cm8tQTHN.js} +0 -0
  355. /package/dist/web/assets/{catppuccin-frappe-Dm3rVY3b.js → catppuccin-frappe-DLeGslh_.js} +0 -0
  356. /package/dist/web/assets/{catppuccin-latte-ZxS-ga_K.js → catppuccin-latte-CR5GeHws.js} +0 -0
  357. /package/dist/web/assets/{catppuccin-macchiato-B0bAzXh0.js → catppuccin-macchiato-p-XgjxhP.js} +0 -0
  358. /package/dist/web/assets/{catppuccin-mocha-BeMC5hqi.js → catppuccin-mocha-B8QOAZLl.js} +0 -0
  359. /package/dist/web/assets/{chevron-right-DeUue2aK.js → chevron-right-CtcHmZg9.js} +0 -0
  360. /package/dist/web/assets/{circle-question-mark-PaJgx-3d.js → circle-question-mark-EVgisosn.js} +0 -0
  361. /package/dist/web/assets/{clarity-BRdirlsw.js → clarity-DPulbvKH.js} +0 -0
  362. /package/dist/web/assets/{clojure-pSLH-KQp.js → clojure-DFuWS20u.js} +0 -0
  363. /package/dist/web/assets/{cmake-DsZu4U4F.js → cmake-DejCqcu4.js} +0 -0
  364. /package/dist/web/assets/{code-TekEnDFM.js → code-Dupqw5Fr.js} +0 -0
  365. /package/dist/web/assets/{codeowners-CtkKTnsH.js → codeowners-BwAyqWIH.js} +0 -0
  366. /package/dist/web/assets/{codeql-BpP1PDrj.js → codeql-DgSwFjqI.js} +0 -0
  367. /package/dist/web/assets/{common-lisp-BX1XgHTt.js → common-lisp-CxIVJCtf.js} +0 -0
  368. /package/dist/web/assets/{coq-D2bX6-8g.js → coq-DLSjSnmf.js} +0 -0
  369. /package/dist/web/assets/{csharp-DcR97P0D.js → csharp-C1HHzXBp.js} +0 -0
  370. /package/dist/web/assets/{css-CIjciIIk.js → css-VXsMY--y.js} +0 -0
  371. /package/dist/web/assets/{csv-DW48Z5d-.js → csv-BEbcJdHz.js} +0 -0
  372. /package/dist/web/assets/{csv-parser-DVHBcvuE.js → csv-parser-DHvoTjMF.js} +0 -0
  373. /package/dist/web/assets/{cue-Byh1xHko.js → cue-BkDnFqUD.js} +0 -0
  374. /package/dist/web/assets/{cypher-TVlMMWma.js → cypher-1cMqojSQ.js} +0 -0
  375. /package/dist/web/assets/{d-CxaScvxf.js → d-BnYjRXtH.js} +0 -0
  376. /package/dist/web/assets/{dark-plus-sn-FFTT1.js → dark-plus-XfbaH7kZ.js} +0 -0
  377. /package/dist/web/assets/{dart-BmpakMe-.js → dart-Ca7s-8hK.js} +0 -0
  378. /package/dist/web/assets/{data-grid-types-BuMHLKXV.js → data-grid-types-DP-ZexEN.js} +0 -0
  379. /package/dist/web/assets/{database-COYAMZaw.js → database-Dj0DxgtX.js} +0 -0
  380. /package/dist/web/assets/{dax-B1aRUMXT.js → dax-CmuOw-mY.js} +0 -0
  381. /package/dist/web/assets/{desktop-C6lD62ar.js → desktop-C2gJ6Ovb.js} +0 -0
  382. /package/dist/web/assets/{diff-CupGmT1a.js → diff-BCEo1Ej7.js} +0 -0
  383. /package/dist/web/assets/{dist-Bi4gRyhr.js → dist-CipvJy9w.js} +0 -0
  384. /package/dist/web/assets/{dist-m_oJqIDk.js → dist-D__U4ZJz.js} +0 -0
  385. /package/dist/web/assets/{docker-DjDr3TJF.js → docker-DN2jlyAh.js} +0 -0
  386. /package/dist/web/assets/{dotenv-QeT1SLTv.js → dotenv-NExMY2kS.js} +0 -0
  387. /package/dist/web/assets/{dracula-yI5kpKp5.js → dracula-B9lbE01y.js} +0 -0
  388. /package/dist/web/assets/{dracula-soft-CYF6cJce.js → dracula-soft-CMBBfKF_.js} +0 -0
  389. /package/dist/web/assets/{dream-maker-TBDKiHrV.js → dream-maker-DEyDwFsd.js} +0 -0
  390. /package/dist/web/assets/{emacs-lisp-B7aZTnJ_.js → emacs-lisp-D_qP2zS6.js} +0 -0
  391. /package/dist/web/assets/{everforest-dark-CN77B1NJ.js → everforest-dark-DbHa2U-p.js} +0 -0
  392. /package/dist/web/assets/{everforest-light-CyFxGMIs.js → everforest-light-BISkJchP.js} +0 -0
  393. /package/dist/web/assets/{fennel-C6ktGUzp.js → fennel-6B22SfJR.js} +0 -0
  394. /package/dist/web/assets/{file-exclamation-point-DBEC-Kst.js → file-exclamation-point-D-ZORzYB.js} +0 -0
  395. /package/dist/web/assets/{fish-Vr62C2vP.js → fish-CubIIvJD.js} +0 -0
  396. /package/dist/web/assets/{fluent-CH3IJUki.js → fluent-DF55zQ09.js} +0 -0
  397. /package/dist/web/assets/{fortran-free-form-BN7VmmxZ.js → fortran-free-form-DvOu8jAt.js} +0 -0
  398. /package/dist/web/assets/{gdscript-BYdRNu_P.js → gdscript-DCPxdLpq.js} +0 -0
  399. /package/dist/web/assets/{gdshader-C2XOq90k.js → gdshader-CTZ381FO.js} +0 -0
  400. /package/dist/web/assets/{genie-B4hkIXNU.js → genie-C_QDtvgU.js} +0 -0
  401. /package/dist/web/assets/{gherkin-CW9NH2DW.js → gherkin-BcvK42tF.js} +0 -0
  402. /package/dist/web/assets/{github-dark-BWCf99kM.js → github-dark-C9afs_4w.js} +0 -0
  403. /package/dist/web/assets/{github-dark-default-DZ1m_6OP.js → github-dark-default-CKGVrl3Z.js} +0 -0
  404. /package/dist/web/assets/{github-dark-dimmed-CUXSQ8bS.js → github-dark-dimmed-C3dQZ_LC.js} +0 -0
  405. /package/dist/web/assets/{github-dark-high-contrast-B6BvU8H-.js → github-dark-high-contrast-DW78QukY.js} +0 -0
  406. /package/dist/web/assets/{github-light-Ch-E92cy.js → github-light-BnP2dznS.js} +0 -0
  407. /package/dist/web/assets/{github-light-default-CKFslV7i.js → github-light-default-C2fPZzPG.js} +0 -0
  408. /package/dist/web/assets/{github-light-high-contrast-DiJVeRlZ.js → github-light-high-contrast-BzIuUFR2.js} +0 -0
  409. /package/dist/web/assets/{gleam-D4dP7V5M.js → gleam-BpeKwEIO.js} +0 -0
  410. /package/dist/web/assets/{gn-DuO2D2eW.js → gn-nUb_Mit0.js} +0 -0
  411. /package/dist/web/assets/{gnuplot-Df_8pFbI.js → gnuplot-D7I0v20p.js} +0 -0
  412. /package/dist/web/assets/{go-BL16cMJO.js → go-D7Qqska7.js} +0 -0
  413. /package/dist/web/assets/{groovy-rfHeCRqy.js → groovy-DS-pne5-.js} +0 -0
  414. /package/dist/web/assets/{gruvbox-dark-hard-CLbDN-E3.js → gruvbox-dark-hard-C0W01fsq.js} +0 -0
  415. /package/dist/web/assets/{gruvbox-dark-medium-Df0L0qje.js → gruvbox-dark-medium-Bqo6fQbg.js} +0 -0
  416. /package/dist/web/assets/{gruvbox-dark-soft-DV6TAcen.js → gruvbox-dark-soft-D-XSM_fu.js} +0 -0
  417. /package/dist/web/assets/{gruvbox-light-hard-CWXUzp9b.js → gruvbox-light-hard-IVAwMJRl.js} +0 -0
  418. /package/dist/web/assets/{gruvbox-light-medium-BOHMe1-7.js → gruvbox-light-medium-38ATiG7i.js} +0 -0
  419. /package/dist/web/assets/{gruvbox-light-soft-DrrS2sdV.js → gruvbox-light-soft-DnZr3c_0.js} +0 -0
  420. /package/dist/web/assets/{haskell-DybKb9uW.js → haskell-uQcywkDH.js} +0 -0
  421. /package/dist/web/assets/{haxe-DLRwZxmt.js → haxe-c6bI4_GT.js} +0 -0
  422. /package/dist/web/assets/{hcl-CusCzwOk.js → hcl-CQXTiEXq.js} +0 -0
  423. /package/dist/web/assets/{hjson-BygWIJ-3.js → hjson-ChETf2bz.js} +0 -0
  424. /package/dist/web/assets/{hlsl-DCKvuEyZ.js → hlsl-BMC4JGoE.js} +0 -0
  425. /package/dist/web/assets/{horizon-DSOVAzyH.js → horizon-BusFlhLW.js} +0 -0
  426. /package/dist/web/assets/{horizon-bright-DKfVjGj_.js → horizon-bright-2oD0Mz7W.js} +0 -0
  427. /package/dist/web/assets/{houston-Ddm8AgpV.js → houston-DdbbjqFg.js} +0 -0
  428. /package/dist/web/assets/{hy-DzZa6alE.js → hy-D_SOuqw9.js} +0 -0
  429. /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-BWFpZAAP.js} +0 -0
  430. /package/dist/web/assets/{imba-aiXalPnK.js → imba-BxLOHO5I.js} +0 -0
  431. /package/dist/web/assets/{ini-CAvrVa5R.js → ini-ox-CFcg0.js} +0 -0
  432. /package/dist/web/assets/{java-PRMSQp24.js → java-Bzo8yIal.js} +0 -0
  433. /package/dist/web/assets/{javascript-DQR6GQKs.js → javascript-DHuz9VXn.js} +0 -0
  434. /package/dist/web/assets/{json-uWUKqOxq.js → json-0_y80FD5.js} +0 -0
  435. /package/dist/web/assets/{json5-R_LXJXL0.js → json5-CEQ50pGM.js} +0 -0
  436. /package/dist/web/assets/{jsonc-tC5zvrPO.js → jsonc-CfrLEAKA.js} +0 -0
  437. /package/dist/web/assets/{jsonl-BAwnWh7U.js → jsonl-fKbrANC2.js} +0 -0
  438. /package/dist/web/assets/{jsonnet-CPj4HCRm.js → jsonnet-DKbkPaQ2.js} +0 -0
  439. /package/dist/web/assets/{jssm-B7HswxOM.js → jssm-Dxxx1Hqu.js} +0 -0
  440. /package/dist/web/assets/{jsx-z4TGELE1.js → jsx-DlJL-Oj3.js} +0 -0
  441. /package/dist/web/assets/{kanagawa-dragon-BpeLlraV.js → kanagawa-dragon-CwN4SV1c.js} +0 -0
  442. /package/dist/web/assets/{kanagawa-lotus-CLnIPAGs.js → kanagawa-lotus-BHltayiE.js} +0 -0
  443. /package/dist/web/assets/{kanagawa-wave-BglPniBb.js → kanagawa-wave-D-KXPjw6.js} +0 -0
  444. /package/dist/web/assets/{kdl-B6blIqLq.js → kdl-DvtoJdFo.js} +0 -0
  445. /package/dist/web/assets/{kotlin-DVAEYiwY.js → kotlin-CTS4MLcx.js} +0 -0
  446. /package/dist/web/assets/{kusto-Ba87977j.js → kusto-HtSE_sKm.js} +0 -0
  447. /package/dist/web/assets/{laserwave-C-NuzoMp.js → laserwave-DaXZG9ge.js} +0 -0
  448. /package/dist/web/assets/{lean-CJ_KLkrJ.js → lean-BYtGUfWx.js} +0 -0
  449. /package/dist/web/assets/{less-Dfn9YC_2.js → less-BoxmU_Mk.js} +0 -0
  450. /package/dist/web/assets/{lib-DTaN9riD.js → lib-Bpbn00xM.js} +0 -0
  451. /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-tR4XlhMj.js} +0 -0
  452. /package/dist/web/assets/{light-plus-B3BLlguG.js → light-plus-Bt5CyUuo.js} +0 -0
  453. /package/dist/web/assets/{llvm-6UDP3NQb.js → llvm-CSdk9q9V.js} +0 -0
  454. /package/dist/web/assets/{log-QW3dqr1D.js → log-B-kp_3CL.js} +0 -0
  455. /package/dist/web/assets/{logo-DUinJheI.js → logo-DdIsXFDO.js} +0 -0
  456. /package/dist/web/assets/{luau-B1vcBzl8.js → luau-DUBpLlzQ.js} +0 -0
  457. /package/dist/web/assets/{make-CqNL10gS.js → make-B3JVjoW_.js} +0 -0
  458. /package/dist/web/assets/{markdown-BQNMNiYS.js → markdown-Seon0vHs.js} +0 -0
  459. /package/dist/web/assets/{material-theme-rbV8EefR.js → material-theme-Nq-ChX1P.js} +0 -0
  460. /package/dist/web/assets/{material-theme-darker-BZrskvoo.js → material-theme-darker-CPV4ayXJ.js} +0 -0
  461. /package/dist/web/assets/{material-theme-lighter-B0CBBjBw.js → material-theme-lighter-0kyjYqEd.js} +0 -0
  462. /package/dist/web/assets/{material-theme-ocean-CX_CHnQq.js → material-theme-ocean-DGpAp5co.js} +0 -0
  463. /package/dist/web/assets/{material-theme-palenight-ztiOCPbw.js → material-theme-palenight-B0AUn0NG.js} +0 -0
  464. /package/dist/web/assets/{matlab-tHQDNC9G.js → matlab-C2waytxs.js} +0 -0
  465. /package/dist/web/assets/{mdx-D4kZ6c6G.js → mdx-DAis6R6I.js} +0 -0
  466. /package/dist/web/assets/{mermaid-DlR4U4Pe.js → mermaid-_rnT2YGD.js} +0 -0
  467. /package/dist/web/assets/{min-dark-BX-xHAD2.js → min-dark-BSSjchUB.js} +0 -0
  468. /package/dist/web/assets/{min-light-Bxw0_Gqq.js → min-light-DomAFQsa.js} +0 -0
  469. /package/dist/web/assets/{mipsasm-DYyZAum-.js → mipsasm-bBgtvPyG.js} +0 -0
  470. /package/dist/web/assets/{mojo-q9wuEYKR.js → mojo-Cp9BayW0.js} +0 -0
  471. /package/dist/web/assets/{monokai-D9vrJ8JM.js → monokai-CL_9gbmn.js} +0 -0
  472. /package/dist/web/assets/{moonbit-C9roWxMb.js → moonbit-BS_fcr-P.js} +0 -0
  473. /package/dist/web/assets/{move-BvhjG2ic.js → move-C79AH45U.js} +0 -0
  474. /package/dist/web/assets/{narrat-BXGbPwG3.js → narrat-CJ5s3BDf.js} +0 -0
  475. /package/dist/web/assets/{nextflow-groovy-CMy5-Z4k.js → nextflow-groovy-ZHa4JrPJ.js} +0 -0
  476. /package/dist/web/assets/{night-owl-CBQBcbmw.js → night-owl-Jkf9zFQK.js} +0 -0
  477. /package/dist/web/assets/{night-owl-light-CC-brflp.js → night-owl-light-DRPeaG5J.js} +0 -0
  478. /package/dist/web/assets/{nix-B9EAdptu.js → nix-D2Jet9DN.js} +0 -0
  479. /package/dist/web/assets/{nord-DxASd4qG.js → nord-CREVs22j.js} +0 -0
  480. /package/dist/web/assets/{nushell-BsY59i7N.js → nushell-CAiDi_mR.js} +0 -0
  481. /package/dist/web/assets/{objective-c-CoeQAcnP.js → objective-c-CVkmJkC1.js} +0 -0
  482. /package/dist/web/assets/{objective-cpp-BysdFvND.js → objective-cpp-DIKDO1Ly.js} +0 -0
  483. /package/dist/web/assets/{ocaml-CrtQYMMK.js → ocaml-R9qtGQhs.js} +0 -0
  484. /package/dist/web/assets/{odin-B8pz4gIL.js → odin-DTYNQqVr.js} +0 -0
  485. /package/dist/web/assets/{one-dark-pro-3sSHqoqQ.js → one-dark-pro-BSLWUJn5.js} +0 -0
  486. /package/dist/web/assets/{one-light-LUZAvT8g.js → one-light-DbkRXG3t.js} +0 -0
  487. /package/dist/web/assets/{openscad-DcKia-sV.js → openscad-BlPcSnmT.js} +0 -0
  488. /package/dist/web/assets/{pascal-zM5SlmgF.js → pascal-B1Y46ZZB.js} +0 -0
  489. /package/dist/web/assets/{pkl-C_r_zLLt.js → pkl-BhJSJbFv.js} +0 -0
  490. /package/dist/web/assets/{plastic-B3cRtgKD.js → plastic-BzxsCTpi.js} +0 -0
  491. /package/dist/web/assets/{plsql-BzyjheXU.js → plsql-DBba7dyP.js} +0 -0
  492. /package/dist/web/assets/{plus-DcuJuDC3.js → plus-Dm36mgLh.js} +0 -0
  493. /package/dist/web/assets/{po-Dz4OaDNi.js → po-BvgLlnNo.js} +0 -0
  494. /package/dist/web/assets/{poimandres-BBbtYwmR.js → poimandres-DUaI_1JP.js} +0 -0
  495. /package/dist/web/assets/{polar-Che1XC2Z.js → polar-C7uILwJn.js} +0 -0
  496. /package/dist/web/assets/{postcss-DouzwG4F.js → postcss-BgJlpFdw.js} +0 -0
  497. /package/dist/web/assets/{powerquery-YOdcTZmd.js → powerquery-Dn1MIpZu.js} +0 -0
  498. /package/dist/web/assets/{powershell-8ojzYH34.js → powershell-Bo-DYzOS.js} +0 -0
  499. /package/dist/web/assets/{prisma-DCmjceML.js → prisma-CTLs5IdT.js} +0 -0
  500. /package/dist/web/assets/{prolog-ByNNz2pB.js → prolog-QRGun3eG.js} +0 -0
  501. /package/dist/web/assets/{proto-DWMt6Qu1.js → proto-B7PI7apn.js} +0 -0
  502. /package/dist/web/assets/{puppet-osfKyZp4.js → puppet-CBq0tLrP.js} +0 -0
  503. /package/dist/web/assets/{purescript-ClIblkvh.js → purescript-DFewOQb2.js} +0 -0
  504. /package/dist/web/assets/{python-Ctyr4faP.js → python-iWuxKXvT.js} +0 -0
  505. /package/dist/web/assets/{qmldir-CbNgyhnI.js → qmldir-cXiW3AsF.js} +0 -0
  506. /package/dist/web/assets/{qss-LbRHzb21.js → qss-DASwAbBL.js} +0 -0
  507. /package/dist/web/assets/{r-Dvfjp0wc.js → r-TU2eaIPw.js} +0 -0
  508. /package/dist/web/assets/{racket-DI8v8nzD.js → racket-SkRKqrLY.js} +0 -0
  509. /package/dist/web/assets/{raku-CX1q9GW0.js → raku-D31lthkZ.js} +0 -0
  510. /package/dist/web/assets/{react-8hrD3-kv.js → react-DjdYfcEe.js} +0 -0
  511. /package/dist/web/assets/{red-CSz3QEOw.js → red-CTcQZevb.js} +0 -0
  512. /package/dist/web/assets/{reg-FMjhUQE-.js → reg-D8YAsTGf.js} +0 -0
  513. /package/dist/web/assets/{regexp-CCSe1Fc4.js → regexp-CD2uvAWx.js} +0 -0
  514. /package/dist/web/assets/{rel-Ce0UO-OB.js → rel-CAaI-1fg.js} +0 -0
  515. /package/dist/web/assets/{resolve-theme-B9Mgd5GG.js → resolve-theme-B0eotaPq.js} +0 -0
  516. /package/dist/web/assets/{riscv-BFZrdZtB.js → riscv-DR-hJVIj.js} +0 -0
  517. /package/dist/web/assets/{ron-D0t4kvJG.js → ron-CuUCJ0n4.js} +0 -0
  518. /package/dist/web/assets/{rose-pine-HzwmOzHM.js → rose-pine-aFHxW48i.js} +0 -0
  519. /package/dist/web/assets/{rose-pine-dawn-CSp9xAF9.js → rose-pine-dawn-Bqz2sBRY.js} +0 -0
  520. /package/dist/web/assets/{rose-pine-moon-Cnzr2Cw0.js → rose-pine-moon-wqyh0an4.js} +0 -0
  521. /package/dist/web/assets/{rosmsg-DEJ8Snbv.js → rosmsg-CI1ywimO.js} +0 -0
  522. /package/dist/web/assets/{rust-2jBzrC0l.js → rust-p7Imk4G_.js} +0 -0
  523. /package/dist/web/assets/{sass-DD75Izu_.js → sass-C8JfaqCV.js} +0 -0
  524. /package/dist/web/assets/{save-BM4p9_9n.js → save-ignW_jk2.js} +0 -0
  525. /package/dist/web/assets/{scala-DpBKROIu.js → scala-DNxCW1By.js} +0 -0
  526. /package/dist/web/assets/{scheme-DM2jzwUK.js → scheme-BYuUPJoL.js} +0 -0
  527. /package/dist/web/assets/{sdbl-C34Cg6e7.js → sdbl-DbPeJPUr.js} +0 -0
  528. /package/dist/web/assets/{shellscript-DxfzKSgU.js → shellscript-DMiHlR3q.js} +0 -0
  529. /package/dist/web/assets/{shield-check-C3qYSU8a.js → shield-check-BSA9P3nI.js} +0 -0
  530. /package/dist/web/assets/{shield-off-D5tU7gQT.js → shield-off-Cb9J9x_n.js} +0 -0
  531. /package/dist/web/assets/{slack-dark-DIIV3bpD.js → slack-dark-C4xGcQXI.js} +0 -0
  532. /package/dist/web/assets/{slack-ochin-BTkxWjEP.js → slack-ochin-BzgRt_wm.js} +0 -0
  533. /package/dist/web/assets/{smalltalk-Bm0qg6SA.js → smalltalk-LDeL9PYH.js} +0 -0
  534. /package/dist/web/assets/{snazzy-light-ClAuuhn4.js → snazzy-light-CXE7a5j_.js} +0 -0
  535. /package/dist/web/assets/{solarized-dark-93sEWmYT.js → solarized-dark-DgUhY74x.js} +0 -0
  536. /package/dist/web/assets/{solarized-light-C74FvgcO.js → solarized-light-DD9C-H-9.js} +0 -0
  537. /package/dist/web/assets/{solidity-H9fSjkQP.js → solidity-CAbDB4D4.js} +0 -0
  538. /package/dist/web/assets/{splunk-CDTN2iAG.js → splunk-kQREPfIc.js} +0 -0
  539. /package/dist/web/assets/{sql-CoqObhgo.js → sql-CBzeNAD8.js} +0 -0
  540. /package/dist/web/assets/{ssh-config-C3Wv6qRp.js → ssh-config-L1euxrsM.js} +0 -0
  541. /package/dist/web/assets/{stylus-D0uTFXCy.js → stylus-DvBnYniP.js} +0 -0
  542. /package/dist/web/assets/{swift-DtMMgvRI.js → swift-DF5NDQp6.js} +0 -0
  543. /package/dist/web/assets/{synthwave-84-DAroyZBu.js → synthwave-84-DbBhB_Hq.js} +0 -0
  544. /package/dist/web/assets/{system-verilog-Bj7X_lq4.js → system-verilog-D8s2ZW_k.js} +0 -0
  545. /package/dist/web/assets/{systemd-CH8ApCUs.js → systemd-a5-XZJsG.js} +0 -0
  546. /package/dist/web/assets/{table-Di8pv2Y_.js → table-4P8kS7sg.js} +0 -0
  547. /package/dist/web/assets/{talonscript-70lVC3cw.js → talonscript-BLa0URTW.js} +0 -0
  548. /package/dist/web/assets/{tasl-WQR8VslV.js → tasl-DIoV4XcC.js} +0 -0
  549. /package/dist/web/assets/{tcl-caTR_frn.js → tcl-ztYAdXfQ.js} +0 -0
  550. /package/dist/web/assets/{terraform-CVRZ4SZ4.js → terraform-DvpCCiOZ.js} +0 -0
  551. /package/dist/web/assets/{tokyo-night-CDyT1jCI.js → tokyo-night-BkHlYUv0.js} +0 -0
  552. /package/dist/web/assets/{toml-B7D2mIDU.js → toml-acIrxos6.js} +0 -0
  553. /package/dist/web/assets/{tsv-2kdn5dqL.js → tsv-BJB5EVCT.js} +0 -0
  554. /package/dist/web/assets/{tsx-DJfG3fzg.js → tsx-B1MmXGT3.js} +0 -0
  555. /package/dist/web/assets/{turtle-DaQac_UT.js → turtle-DFceTiqd.js} +0 -0
  556. /package/dist/web/assets/{typescript-BLfAIchl.js → typescript-Dz9wU5Y1.js} +0 -0
  557. /package/dist/web/assets/{typespec-ymBaOi_c.js → typespec-CPuYZijj.js} +0 -0
  558. /package/dist/web/assets/{typst-DkvUYMT-.js → typst-CfsCIYv8.js} +0 -0
  559. /package/dist/web/assets/{use-is-mobile-BcuCjH19.js → use-is-mobile-K1a38BwI.js} +0 -0
  560. /package/dist/web/assets/{utils-E0yyGxXt.js → utils-B9H0d3Fh.js} +0 -0
  561. /package/dist/web/assets/{v-D4997FGs.js → v-CGo0jcXA.js} +0 -0
  562. /package/dist/web/assets/{vala-AFg9DmKE.js → vala-AsSR0hrb.js} +0 -0
  563. /package/dist/web/assets/{vb-DNUJ9fOP.js → vb-C7tvbXv1.js} +0 -0
  564. /package/dist/web/assets/{vendor-xterm-QDr0jvpJ.js → vendor-xterm-ClRVIJ-k.js} +0 -0
  565. /package/dist/web/assets/{verilog-BrnAQ3tF.js → verilog--a33ND-p.js} +0 -0
  566. /package/dist/web/assets/{vesper-DCZnFmY0.js → vesper-B_Z0h4pn.js} +0 -0
  567. /package/dist/web/assets/{vhdl-DoK4PjhE.js → vhdl-CWL5dZPN.js} +0 -0
  568. /package/dist/web/assets/{viml-D6EKG66q.js → viml-BglmT14Y.js} +0 -0
  569. /package/dist/web/assets/{vitesse-black-C6KILWdF.js → vitesse-black-RKFsSqQI.js} +0 -0
  570. /package/dist/web/assets/{vitesse-dark-BuSr6OT5.js → vitesse-dark-D7zDWNmc.js} +0 -0
  571. /package/dist/web/assets/{vitesse-light-M-eYyOoe.js → vitesse-light-MAp87W5b.js} +0 -0
  572. /package/dist/web/assets/{vyper-CwSN64Jn.js → vyper-CgCBYg1G.js} +0 -0
  573. /package/dist/web/assets/{wasm-BhQEkzil.js → wasm-BOG7ZeTT.js} +0 -0
  574. /package/dist/web/assets/{wasm-CVlf9Jwb.js → wasm-Bd258nS1.js} +0 -0
  575. /package/dist/web/assets/{wenyan-BC7irgM3.js → wenyan-DpShPKXZ.js} +0 -0
  576. /package/dist/web/assets/{wgsl-W-3UHMDO.js → wgsl-BakcCblK.js} +0 -0
  577. /package/dist/web/assets/{wikitext-T1DoFcm8.js → wikitext-NroRWVAO.js} +0 -0
  578. /package/dist/web/assets/{wit-DIrx_eCI.js → wit-B5CM2pg6.js} +0 -0
  579. /package/dist/web/assets/{wolfram-Cza4b443.js → wolfram-7MTFBJpR.js} +0 -0
  580. /package/dist/web/assets/{x-BsFLg-T8.js → x-BtzqXv1z.js} +0 -0
  581. /package/dist/web/assets/{yaml-DOZ10aZe.js → yaml-Bwr01G2K.js} +0 -0
  582. /package/dist/web/assets/{zenscript-DDAXkseY.js → zenscript-Dj8GATvi.js} +0 -0
  583. /package/dist/web/assets/{zig-CrzwMpQE.js → zig-LAE-fbS-.js} +0 -0
@@ -2,7 +2,7 @@ import { Hono } from "hono";
2
2
  import { resolve, isAbsolute } from "node:path";
3
3
  import { existsSync, mkdirSync } from "node:fs";
4
4
  import { fileService, SecurityError, NotFoundError, ValidationError } from "../../services/file.service.ts";
5
- import { readSystemFile } from "../../services/fs-browse.service.ts";
5
+ import { readSystemFileSync } from "../../services/fs-browse.service.ts";
6
6
  import { ok, err } from "../../types/api.ts";
7
7
  import { errorStatus } from "../helpers/error-status.ts";
8
8
  import mammoth from "mammoth";
@@ -243,7 +243,7 @@ fileRoutes.get("/compare", (c) => {
243
243
  }
244
244
  // Support absolute paths (files outside project, e.g. /tmp/)
245
245
  const readSide = (p: string) =>
246
- isAbsolute(p) ? readSystemFile(p).content : fileService.readFile(projectPath, p).content;
246
+ isAbsolute(p) ? readSystemFileSync(p).content : fileService.readFile(projectPath, p).content;
247
247
  const original = readSide(file1);
248
248
  const modified = readSide(file2);
249
249
  return c.json(ok({ original, modified }));
@@ -1,103 +1,139 @@
1
1
  import { Hono } from "hono";
2
- import { existsSync, mkdirSync, realpathSync, rmSync, statSync } from "fs";
3
- import { resolve } from "path";
4
- import { $ } from "bun";
2
+ import { stat } from "node:fs/promises";
3
+ import { basename } from "node:path";
5
4
  import mammoth from "mammoth";
5
+ import { browse } from "../../services/fs-browse.service.ts";
6
+ import { list } from "../../services/fs-ops/fs-list-files.service.ts";
6
7
  import {
7
- browse,
8
- list,
9
- readSystemFile,
10
- writeSystemFile,
8
+ assertAllowed,
9
+ assertNotPpmDir,
11
10
  isAllowedPath,
12
11
  resolvePath,
13
- } from "../../services/fs-browse.service.ts";
12
+ } from "../../services/fs-path-guard.service.ts";
13
+ import {
14
+ readSystemFile,
15
+ realPathOrSelf,
16
+ writeSystemFile,
17
+ } from "../../services/fs-ops/fs-ops-read-write.service.ts";
18
+ import { makeDir } from "../../services/fs-ops/fs-ops-mutate.service.ts";
19
+ import { runGitInit } from "../../services/fs-ops/fs-git-init.service.ts";
20
+ import { attachmentDisposition } from "../../services/fs-ops/fs-content-disposition.ts";
21
+ import { fsErrorBody } from "../../services/fs-ops/fs-error-response.ts";
14
22
  import { isImageExtension } from "../../shared/image-extensions.ts";
15
23
  import { ok, err } from "../../types/api.ts";
16
- import { createDownloadToken } from "../../services/download-token.service.ts";
24
+ import {
25
+ createDownloadToken,
26
+ consumeDownloadToken,
27
+ } from "../../services/download-token.service.ts";
17
28
 
18
29
  export const fsBrowseRoutes = new Hono();
19
30
 
20
- /** Map error to HTTP status (errors carry .status from service). */
21
- function errorStatus(e: unknown): 400 | 403 | 404 | 500 {
22
- const status = (e as { status?: number }).status;
23
- if (status === 403 || status === 404 || status === 400) return status;
24
- return 500;
31
+ function fail(e: unknown) {
32
+ return fsErrorBody(e);
25
33
  }
26
34
 
27
35
  /** GET /api/fs/browse?path=/some/dir&showHidden=false */
28
- fsBrowseRoutes.get("/browse", (c) => {
36
+ fsBrowseRoutes.get("/browse", async (c) => {
29
37
  try {
30
38
  const path = c.req.query("path") || undefined;
31
39
  const showHidden = c.req.query("showHidden") === "true";
32
- const result = browse(path, { showHidden });
33
- return c.json(ok(result));
40
+ return c.json(ok(await browse(path, { showHidden })));
34
41
  } catch (e) {
35
- return c.json(err((e as Error).message), errorStatus(e));
42
+ const { body, status } = fail(e);
43
+ return c.json(body, status);
36
44
  }
37
45
  });
38
46
 
39
47
  /** GET /api/fs/list?dir=/some/dir — recursive file listing (command palette) */
40
- fsBrowseRoutes.get("/list", (c) => {
48
+ fsBrowseRoutes.get("/list", async (c) => {
41
49
  try {
42
50
  const dir = c.req.query("dir");
43
51
  if (!dir) return c.json(err("dir is required"), 400);
44
- const files = list(dir);
45
- return c.json(ok(files));
52
+ return c.json(ok(await list(dir)));
46
53
  } catch (e) {
47
- return c.json(err((e as Error).message), errorStatus(e));
54
+ const { body, status } = fail(e);
55
+ return c.json(body, status);
48
56
  }
49
57
  });
50
58
 
51
59
  /** GET /api/fs/read?path=/some/file — read file outside project */
52
- fsBrowseRoutes.get("/read", (c) => {
60
+ fsBrowseRoutes.get("/read", async (c) => {
53
61
  try {
54
62
  const filePath = c.req.query("path");
55
63
  if (!filePath) return c.json(err("path is required"), 400);
56
- const result = readSystemFile(filePath);
57
- return c.json(ok(result));
64
+ return c.json(ok(await readSystemFile(filePath)));
58
65
  } catch (e) {
59
- return c.json(err((e as Error).message), errorStatus(e));
66
+ const { body, status } = fail(e);
67
+ return c.json(body, status);
60
68
  }
61
69
  });
62
70
 
63
- /** POST /api/fs/download/token — generate a short-lived token for external file downloads */
64
- fsBrowseRoutes.post("/download/token", (c) => {
65
- const token = createDownloadToken();
66
- return c.json(ok({ token }));
71
+ /**
72
+ * POST /api/fs/download/token body: { path }
73
+ * The token is bound to that one file and spent on first use, so a URL that
74
+ * leaks through history or a proxy log cannot fetch anything else.
75
+ */
76
+ fsBrowseRoutes.post("/download/token", async (c) => {
77
+ try {
78
+ const body = await c.req.json<{ path?: string }>().catch(() => ({}) as { path?: string });
79
+ if (!body.path) return c.json(err("path is required"), 400);
80
+ const resolved = resolvePath(body.path);
81
+ assertAllowed(resolved);
82
+ assertNotPpmDir(resolved);
83
+ assertNotPpmDir(await realPathOrSelf(resolved));
84
+ return c.json(ok({ token: createDownloadToken(resolved) }));
85
+ } catch (e) {
86
+ const { body, status } = fail(e);
87
+ return c.json(body, status);
88
+ }
67
89
  });
68
90
 
69
- /** GET /api/fs/raw?path=/some/file — serve file as binary (for images in markdown, etc.) */
70
- fsBrowseRoutes.get("/raw", (c) => {
91
+ /** GET /api/fs/raw?path=/some/file — serve file as binary (images, downloads) */
92
+ fsBrowseRoutes.get("/raw", async (c) => {
71
93
  try {
72
94
  const filePath = c.req.query("path");
73
95
  if (!filePath) return c.json(err("path is required"), 400);
74
96
 
75
97
  const resolved = resolvePath(filePath);
98
+ const real = await realPathOrSelf(resolved);
76
99
  // Images are streamed even from outside the browse whitelist: the chat UI renders images
77
100
  // the assistant just read, and those live at arbitrary paths. Limiting the exception to
78
101
  // image extensions stops it from becoming an arbitrary-file read. The extension is
79
102
  // re-checked after resolving symlinks, so a link named *.png cannot hand out a
80
103
  // non-image target.
81
104
  if (!isAllowedPath(resolved)) {
82
- const target = existsSync(resolved) ? realpathSync(resolved) : resolved;
83
- if (!isImageExtension(resolved) || !isImageExtension(target)) {
105
+ if (!isImageExtension(resolved) || !isImageExtension(real)) {
84
106
  return c.json(err("Access denied"), 403);
85
107
  }
86
108
  }
87
- if (!existsSync(resolved)) return c.json(err("File not found"), 404);
109
+ assertNotPpmDir(resolved);
110
+ assertNotPpmDir(real);
111
+
112
+ // A download token authenticated this request in place of the session, so
113
+ // it must match the file being served and is spent here.
114
+ const dlToken = c.req.query("dl_token");
115
+ if (dlToken && !consumeDownloadToken(dlToken, resolved)) {
116
+ return c.json(err("Download token invalid for this path"), 403);
117
+ }
118
+
119
+ const info = await stat(resolved).catch(() => null);
120
+ if (!info) return c.json(err("File not found"), 404);
88
121
 
89
122
  const download = c.req.query("download") === "true";
90
- const filename = resolved.split("/").pop() ?? "download";
123
+ const filename = basename(resolved) || "download";
91
124
  const file = Bun.file(resolved);
92
125
  return new Response(file.stream(), {
93
126
  headers: {
94
127
  "Content-Type": download ? "application/octet-stream" : (file.type || "application/octet-stream"),
95
- "Content-Disposition": download ? `attachment; filename="${filename}"` : "inline",
96
- "Cache-Control": "private, max-age=3600",
128
+ "Content-Disposition": download ? attachmentDisposition(filename) : "inline",
129
+ // A download is a one-shot transfer of a file the user may edit right
130
+ // after; caching it would hand out stale bytes on the next download.
131
+ "Cache-Control": download ? "no-store" : "private, max-age=3600",
97
132
  },
98
133
  });
99
134
  } catch (e) {
100
- return c.json(err((e as Error).message), errorStatus(e));
135
+ const { body, status } = fail(e);
136
+ return c.json(body, status);
101
137
  }
102
138
  });
103
139
 
@@ -106,63 +142,52 @@ fsBrowseRoutes.get("/docx-html", async (c) => {
106
142
  try {
107
143
  const filePath = c.req.query("path");
108
144
  if (!filePath) return c.json(err("path is required"), 400);
109
- if (!existsSync(filePath)) return c.json(err("File not found"), 404);
145
+ const resolved = resolvePath(filePath);
146
+ assertAllowed(resolved);
147
+ assertNotPpmDir(resolved);
148
+ assertNotPpmDir(await realPathOrSelf(resolved));
149
+ if (!(await stat(resolved).catch(() => null))) return c.json(err("File not found"), 404);
110
150
 
111
- const arrayBuf = await Bun.file(filePath).arrayBuffer();
151
+ const arrayBuf = await Bun.file(resolved).arrayBuffer();
112
152
  const result = await mammoth.convertToHtml({ buffer: Buffer.from(arrayBuf) });
113
153
  return c.json(ok({ html: result.value, warnings: result.messages }));
114
154
  } catch (e) {
115
- return c.json(err((e as Error).message), errorStatus(e));
155
+ const { body, status } = fail(e);
156
+ return c.json(body, status);
116
157
  }
117
158
  });
118
159
 
119
- /** DELETE /api/fs/rmdir — delete a directory { path } */
120
- fsBrowseRoutes.delete("/rmdir", async (c) => {
121
- try {
122
- const body = await c.req.json<{ path: string }>();
123
- if (!body.path) return c.json(err("path is required"), 400);
124
-
125
- const abs = resolve(body.path);
126
- if (!existsSync(abs)) return c.json(err("Directory not found"), 404);
127
- if (!statSync(abs).isDirectory()) return c.json(err("Path is not a directory"), 400);
128
-
129
- rmSync(abs, { recursive: true });
130
- return c.json(ok({ removed: abs }));
131
- } catch (e) {
132
- return c.json(err((e as Error).message), errorStatus(e));
133
- }
134
- });
135
-
136
- /** POST /api/fs/mkdir — create directory and git init { path } */
160
+ /**
161
+ * POST /api/fs/mkdir create directory { path, gitInit? }
162
+ * `gitInit` defaults to true because the project picker uses this route to
163
+ * create new project folders; the explorer passes false.
164
+ */
137
165
  fsBrowseRoutes.post("/mkdir", async (c) => {
138
166
  try {
139
- const body = await c.req.json<{ path: string }>();
167
+ const body = await c.req.json<{ path?: string; gitInit?: boolean }>();
140
168
  if (!body.path) return c.json(err("path is required"), 400);
141
169
 
142
- const abs = resolve(body.path);
143
- if (existsSync(abs)) {
144
- return c.json(err(`Directory already exists: ${abs}`), 400);
145
- }
146
-
147
- mkdirSync(abs, { recursive: true });
148
- // git init in the new directory
149
- await $`git init ${abs}`.quiet();
150
- return c.json(ok({ path: abs, gitInitialized: true }), 201);
170
+ const { path } = await makeDir(body.path);
171
+ const gitInit = body.gitInit !== false;
172
+ if (gitInit) await runGitInit(path);
173
+ return c.json(ok({ path, gitInitialized: gitInit }), 201);
151
174
  } catch (e) {
152
- return c.json(err((e as Error).message), errorStatus(e));
175
+ const { body, status } = fail(e);
176
+ return c.json(body, status);
153
177
  }
154
178
  });
155
179
 
156
180
  /** PUT /api/fs/write — write file outside project { path, content } */
157
181
  fsBrowseRoutes.put("/write", async (c) => {
158
182
  try {
159
- const body = await c.req.json<{ path: string; content: string }>();
183
+ const body = await c.req.json<{ path?: string; content?: string }>();
160
184
  if (!body.path || body.content == null) {
161
185
  return c.json(err("path and content required"), 400);
162
186
  }
163
- writeSystemFile(body.path, body.content);
187
+ await writeSystemFile(body.path, body.content);
164
188
  return c.json(ok(true));
165
189
  } catch (e) {
166
- return c.json(err((e as Error).message), errorStatus(e));
190
+ const { body, status } = fail(e);
191
+ return c.json(body, status);
167
192
  }
168
193
  });
@@ -0,0 +1,118 @@
1
+ import { Hono, type Context } from "hono";
2
+ import { ok, err } from "../../types/api.ts";
3
+ import { fsErrorBody } from "../../services/fs-ops/fs-error-response.ts";
4
+ import { statPath } from "../../services/fs-ops/fs-ops-stat.service.ts";
5
+ import { copyPath, movePath } from "../../services/fs-ops/fs-ops-copy-move.service.ts";
6
+ import {
7
+ deletePath,
8
+ makeDir,
9
+ renamePath,
10
+ touchFile,
11
+ } from "../../services/fs-ops/fs-ops-mutate.service.ts";
12
+ import { trashPath } from "../../services/fs-ops/fs-ops-trash.service.ts";
13
+
14
+ /**
15
+ * Out-of-project filesystem mutations for the explorer. Mounted on the same
16
+ * `/api/fs` prefix as the browse routes; every handler resolves and whitelists
17
+ * its paths inside the service layer and reports failures through one mapper
18
+ * so the client always gets `{ error, code }`.
19
+ *
20
+ * Path contract for `/copy` and `/move`: `destination` is the FULL target
21
+ * path. Dropping an entry onto a folder is a client-side compose —
22
+ * `join(targetDir, basename(source))` — because a destination that already
23
+ * exists (a folder included) answers 409 EEXIST rather than merging.
24
+ */
25
+ export const fsOpsRoutes = new Hono();
26
+
27
+ function fail(c: Context, e: unknown) {
28
+ const { body, status } = fsErrorBody(e);
29
+ return c.json(body, status);
30
+ }
31
+
32
+ /** GET /api/fs/stat?path=... */
33
+ fsOpsRoutes.get("/stat", async (c) => {
34
+ try {
35
+ const path = c.req.query("path");
36
+ if (!path) return c.json(err("path is required"), 400);
37
+ return c.json(ok(await statPath(path)));
38
+ } catch (e) {
39
+ return fail(c, e);
40
+ }
41
+ });
42
+
43
+ /** POST /api/fs/copy — body: { source, destination } (destination = full target path) */
44
+ fsOpsRoutes.post("/copy", async (c) => {
45
+ try {
46
+ const body = await c.req.json<{ source?: string; destination?: string }>();
47
+ if (!body.source || !body.destination) {
48
+ return c.json(err("source and destination are required"), 400);
49
+ }
50
+ return c.json(ok(await copyPath(body.source, body.destination)));
51
+ } catch (e) {
52
+ return fail(c, e);
53
+ }
54
+ });
55
+
56
+ /** POST /api/fs/move — body: { source, destination } (destination = full target path) */
57
+ fsOpsRoutes.post("/move", async (c) => {
58
+ try {
59
+ const body = await c.req.json<{ source?: string; destination?: string }>();
60
+ if (!body.source || !body.destination) {
61
+ return c.json(err("source and destination are required"), 400);
62
+ }
63
+ return c.json(ok(await movePath(body.source, body.destination)));
64
+ } catch (e) {
65
+ return fail(c, e);
66
+ }
67
+ });
68
+
69
+ /** POST /api/fs/rename — body: { path, newName } */
70
+ fsOpsRoutes.post("/rename", async (c) => {
71
+ try {
72
+ const body = await c.req.json<{ path?: string; newName?: string }>();
73
+ if (!body.path || !body.newName) {
74
+ return c.json(err("path and newName are required"), 400);
75
+ }
76
+ return c.json(ok(await renamePath(body.path, body.newName)));
77
+ } catch (e) {
78
+ return fail(c, e);
79
+ }
80
+ });
81
+
82
+ /** POST /api/fs/touch — body: { path } */
83
+ fsOpsRoutes.post("/touch", async (c) => {
84
+ try {
85
+ const body = await c.req.json<{ path?: string }>();
86
+ if (!body.path) return c.json(err("path is required"), 400);
87
+ return c.json(ok(await touchFile(body.path)), 201);
88
+ } catch (e) {
89
+ return fail(c, e);
90
+ }
91
+ });
92
+
93
+ /**
94
+ * DELETE /api/fs/delete — body: { path, permanent?: boolean }
95
+ * Default is the OS trash so the action stays undoable; when no trash backend
96
+ * exists the answer is 409 NO_TRASH and the client asks about a hard delete.
97
+ */
98
+ async function handleDelete(c: Context) {
99
+ try {
100
+ type DeleteBody = { path?: string; permanent?: boolean };
101
+ // A DELETE without a body is a client mistake, not a server error.
102
+ const body = await c.req.json<DeleteBody>().catch((): DeleteBody => ({}));
103
+ if (!body.path) return c.json(err("path is required"), 400);
104
+ if (body.permanent) {
105
+ const result = await deletePath(body.path);
106
+ return c.json(ok({ ...result, trashed: false, permanent: true }));
107
+ }
108
+ const result = await trashPath(body.path);
109
+ return c.json(ok({ removed: result.path, trashed: true, permanent: false }));
110
+ } catch (e) {
111
+ return fail(c, e);
112
+ }
113
+ }
114
+
115
+ fsOpsRoutes.delete("/delete", handleDelete);
116
+
117
+ /** DELETE /api/fs/rmdir — legacy name kept as an alias of /delete. */
118
+ fsOpsRoutes.delete("/rmdir", handleDelete);
@@ -0,0 +1,129 @@
1
+ import { Hono } from "hono";
2
+ import { sqliteService } from "../../services/sqlite.service.ts";
3
+ import { detectOperation } from "../../services/query-audit/query-audit.service.ts";
4
+ import { logQuery, type AuditFields } from "./query-audit-hook.ts";
5
+ import { stat } from "node:fs/promises";
6
+ import {
7
+ assertAllowed,
8
+ assertNotPpmSubtreeDeep,
9
+ resolvePath,
10
+ } from "../../services/fs-path-guard.service.ts";
11
+ import { fsErrorBody } from "../../services/fs-ops/fs-error-response.ts";
12
+ import { ok, err } from "../../types/api.ts";
13
+
14
+ /**
15
+ * SQLite viewer for database files outside any project, so a `.db` opened from
16
+ * the explorer behaves like one inside a project. Paths are absolute and pass
17
+ * the same guard as every other filesystem route; the PPM directory stays out
18
+ * of reach because it holds the credentials database.
19
+ */
20
+ export const fsSqliteRoutes = new Hono();
21
+
22
+ /**
23
+ * Guard an absolute db path; the service is addressed with no project root.
24
+ * The PPM refusal follows symlinks like every other read door, and existence
25
+ * is probed here — asynchronously — so the service never runs a blocking
26
+ * `existsSync` against a path the caller chose.
27
+ */
28
+ async function guardDbPath(input: string): Promise<string> {
29
+ const resolved = resolvePath(input);
30
+ assertAllowed(resolved);
31
+ await assertNotPpmSubtreeDeep(resolved);
32
+ const info = await stat(resolved);
33
+ if (!info.isFile()) {
34
+ throw Object.assign(new Error("Not a database file"), { status: 400, code: "EINVAL" });
35
+ }
36
+ return resolved;
37
+ }
38
+
39
+ /** Row cap for a foreign database of unknown size. */
40
+ const MAX_QUERY_ROWS = 1_000;
41
+
42
+ /** These routes address a database by file path, so there is no connection id. */
43
+ function fileAudit(dbPath: string): Pick<AuditFields, "connectionId" | "connectionName" | "dbType"> {
44
+ return { connectionId: null, connectionName: dbPath, dbType: "sqlite" };
45
+ }
46
+
47
+ function fail(e: unknown) {
48
+ return fsErrorBody(e);
49
+ }
50
+
51
+ /** GET /api/fs/sqlite/tables?path=/abs/file.db */
52
+ fsSqliteRoutes.get("/tables", async (c) => {
53
+ try {
54
+ const path = c.req.query("path");
55
+ if (!path) return c.json(err("Missing query parameter: path"), 400);
56
+ return c.json(ok(sqliteService.getTables("", await guardDbPath(path))));
57
+ } catch (e) {
58
+ const { body, status } = fail(e);
59
+ return c.json(body, status);
60
+ }
61
+ });
62
+
63
+ /** GET /api/fs/sqlite/schema?path=...&table=... */
64
+ fsSqliteRoutes.get("/schema", async (c) => {
65
+ try {
66
+ const path = c.req.query("path");
67
+ const table = c.req.query("table");
68
+ if (!path || !table) return c.json(err("Missing query parameters: path, table"), 400);
69
+ return c.json(ok(sqliteService.getTableSchema("", await guardDbPath(path), table)));
70
+ } catch (e) {
71
+ const { body, status } = fail(e);
72
+ return c.json(body, status);
73
+ }
74
+ });
75
+
76
+ /** GET /api/fs/sqlite/data?path=...&table=...&page=1&limit=100&orderBy=...&orderDir=ASC */
77
+ fsSqliteRoutes.get("/data", async (c) => {
78
+ try {
79
+ const path = c.req.query("path");
80
+ const table = c.req.query("table");
81
+ if (!path || !table) return c.json(err("Missing query parameters: path, table"), 400);
82
+ const page = parseInt(c.req.query("page") ?? "1", 10);
83
+ const limit = Math.min(parseInt(c.req.query("limit") ?? "100", 10), 1000);
84
+ const orderBy = c.req.query("orderBy");
85
+ const orderDir = c.req.query("orderDir") === "DESC" ? "DESC" : "ASC";
86
+ const data = sqliteService.getTableData(
87
+ "", await guardDbPath(path), table, page, limit, orderBy, orderDir as "ASC" | "DESC",
88
+ );
89
+ return c.json(ok(data));
90
+ } catch (e) {
91
+ const { body, status } = fail(e);
92
+ return c.json(body, status);
93
+ }
94
+ });
95
+
96
+ /** POST /api/fs/sqlite/query — body: { path, sql } */
97
+ fsSqliteRoutes.post("/query", async (c) => {
98
+ const startedAt = Date.now();
99
+ try {
100
+ const body = await c.req.json<{ path?: string; sql?: string }>();
101
+ if (!body.path || !body.sql) return c.json(err("Missing required fields: path, sql"), 400);
102
+ const dbPath = await guardDbPath(body.path);
103
+
104
+ const audit = {
105
+ ...fileAudit(dbPath),
106
+ source: "editor" as const,
107
+ operation: detectOperation(body.sql),
108
+ sql: body.sql,
109
+ };
110
+
111
+ try {
112
+ const result = sqliteService.executeQuery("", dbPath, body.sql, MAX_QUERY_ROWS);
113
+ logQuery(c, {
114
+ ...audit,
115
+ status: "ok",
116
+ rows: result.rows,
117
+ rowCount: result.changeType === "select" ? result.rows.length : result.rowsAffected,
118
+ durationMs: Date.now() - startedAt,
119
+ });
120
+ return c.json(ok(result));
121
+ } catch (e) {
122
+ logQuery(c, { ...audit, status: "error", error: (e as Error).message, durationMs: Date.now() - startedAt });
123
+ throw e;
124
+ }
125
+ } catch (e) {
126
+ const { body, status } = fail(e);
127
+ return c.json(body, status);
128
+ }
129
+ });
@@ -0,0 +1,17 @@
1
+ import { Hono } from "hono";
2
+ import { getHostInfo } from "../../services/host-info/host-info.service.ts";
3
+ import { ok, err } from "../../types/api.ts";
4
+
5
+ export const hostInfoRoutes = new Hono();
6
+
7
+ /** GET /host — cached (60s) server facts: platform, drives, known folders, OS-pinned folders.
8
+ * `?refresh=true` bypasses the cache. Providers never throw, so this route never 500s on their behalf. */
9
+ hostInfoRoutes.get("/host", async (c) => {
10
+ const refresh = c.req.query("refresh") === "true";
11
+ try {
12
+ const info = await getHostInfo({ refresh });
13
+ return c.json(ok(info));
14
+ } catch (e: any) {
15
+ return c.json(err(`Failed to read host info: ${e?.message ?? e}`), 500);
16
+ }
17
+ });
@@ -131,6 +131,8 @@ const UI_PREF_VALIDATORS: Record<string, (v: unknown) => boolean> = {
131
131
  strList(groups, 200) && strList(tables, 500)
132
132
  );
133
133
  },
134
+ // OS Explorer window chrome override — "auto" follows the host platform
135
+ explorerSkin: (v) => v === "auto" || v === "windows" || v === "macos",
134
136
  // Project switcher prefs
135
137
  projectSortMode: (v) => v === "recent" || v === "priority" || v === "name",
136
138
  recentOpen: (v) =>
@@ -3,27 +3,43 @@ import { randomUUIDv7 } from "bun";
3
3
  interface DownloadToken {
4
4
  token: string;
5
5
  createdAt: number;
6
+ /** Absolute path this token may fetch; null = legacy project-scoped token. */
7
+ path: string | null;
6
8
  }
7
9
 
8
10
  const TTL_MS = 30_000;
9
11
  const tokens = new Map<string, DownloadToken>();
10
12
 
11
- /** Generate a one-time download token (30s TTL) */
12
- export function createDownloadToken(): string {
13
+ /**
14
+ * Generate a short-lived download token. Passing `path` binds the token to one
15
+ * file and makes it single-use — required now that the browse whitelist covers
16
+ * the whole disk, so a leaked token cannot be replayed against another file.
17
+ */
18
+ export function createDownloadToken(path?: string): string {
13
19
  const token = randomUUIDv7();
14
- tokens.set(token, { token, createdAt: Date.now() });
20
+ tokens.set(token, { token, createdAt: Date.now(), path: path ?? null });
15
21
  cleanup();
16
22
  return token;
17
23
  }
18
24
 
19
- /** Validate a download token (TTL-based, allows multiple uses within window) */
20
- export function consumeDownloadToken(token: string): boolean {
25
+ /**
26
+ * Validate a download token.
27
+ *
28
+ * Without `path` the token is only checked, not spent: the auth middleware
29
+ * runs before any handler knows which file is being requested, so it may only
30
+ * gate the request. The handler then calls this again *with* the resolved
31
+ * path, which enforces the binding and consumes the token.
32
+ */
33
+ export function consumeDownloadToken(token: string, path?: string): boolean {
21
34
  const entry = tokens.get(token);
22
35
  if (!entry) return false;
23
36
  if (Date.now() - entry.createdAt > TTL_MS) {
24
37
  tokens.delete(token);
25
38
  return false;
26
39
  }
40
+ if (path === undefined) return true;
41
+ if (entry.path === null || entry.path !== path) return false;
42
+ tokens.delete(token);
27
43
  return true;
28
44
  }
29
45
 
@@ -5,13 +5,14 @@ import {
5
5
  readFileSync,
6
6
  writeFileSync,
7
7
  mkdirSync,
8
- unlinkSync,
9
- rmSync,
10
- renameSync,
11
- cpSync,
12
8
  } from "node:fs";
13
9
  import { resolve, relative, dirname, join, normalize, sep } from "node:path";
14
10
  import ignore, { type Ignore } from "ignore";
11
+ import {
12
+ copyEntrySync,
13
+ removeEntrySync,
14
+ renameEntrySync,
15
+ } from "./fs-ops/fs-core-ops.ts";
15
16
  import type { FileNode, FileEntry, FileDirEntry } from "../types/project.ts";
16
17
  import {
17
18
  listDir as listDirImpl,
@@ -227,12 +228,7 @@ class FileService {
227
228
 
228
229
  if (!existsSync(absPath)) throw new NotFoundError(`Not found: ${filePath}`);
229
230
 
230
- const stat = statSync(absPath);
231
- if (stat.isDirectory()) {
232
- rmSync(absPath, { recursive: true, force: true });
233
- } else {
234
- unlinkSync(absPath);
235
- }
231
+ removeEntrySync(absPath);
236
232
  }
237
233
 
238
234
  /** Rename a file or directory */
@@ -248,7 +244,7 @@ class FileService {
248
244
  const dir = dirname(absNew);
249
245
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
250
246
 
251
- renameSync(absOld, absNew);
247
+ renameEntrySync(absOld, absNew);
252
248
  }
253
249
 
254
250
  /** Move a file or directory to a new location */
@@ -267,7 +263,7 @@ class FileService {
267
263
 
268
264
  const dir = dirname(absDest);
269
265
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
270
- cpSync(absSrc, absDest, { recursive: true });
266
+ copyEntrySync(absSrc, absDest);
271
267
  }
272
268
 
273
269
  /**