@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.
- package/.release-run.sh +5 -0
- package/CHANGELOG.md +21 -0
- package/CLAUDE.md +4 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +16 -2
- package/bun.lock +3 -0
- package/dist/web/assets/{ai-resource-editor-DL011EDu.js → ai-resource-editor-BLYgg033.js} +1 -1
- package/dist/web/assets/{angular-html-D4YJUMLP.js → angular-html-jfMqUuRd.js} +1 -1
- package/dist/web/assets/{angular-ts-CUvq2h3X.js → angular-ts-wytn9PMm.js} +1 -1
- package/dist/web/assets/{api-client-COQtP-dB.js → api-client-B9OFv3nY.js} +1 -1
- package/dist/web/assets/api-client-CKo60P6G.js +1 -0
- package/dist/web/assets/{api-settings-Do0Z0IRN.js → api-settings-DGhshDRg.js} +1 -1
- package/dist/web/assets/api-settings-fdkhqLSF.js +1 -0
- package/dist/web/assets/{api-themes-DXi8sz-9.js → api-themes-okiyhhp9.js} +1 -1
- package/dist/web/assets/{apl-DSANFLx0.js → apl-i-ahKx6O.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-B4J2-O8Q.js +1 -0
- package/dist/web/assets/{astro-CXIotiNS.js → astro-DVBCJfG6.js} +1 -1
- package/dist/web/assets/{audio-preview-H16VO99g.js → audio-preview-y8XpCNrh.js} +1 -1
- package/dist/web/assets/{blade-BvrP2gPd.js → blade-BHlL8SqU.js} +1 -1
- package/dist/web/assets/{bsl-CGf6Xuyu.js → bsl-D-UOfR1F.js} +1 -1
- package/dist/web/assets/{cairo-BboZ9qIf.js → cairo-Bn6rucli.js} +1 -1
- package/dist/web/assets/can-open-in-ppm-BzVXoV-d.js +1 -0
- package/dist/web/assets/chat-tab-BcR-3_6R.js +13 -0
- package/dist/web/assets/clock-B1CPBp2g.js +1 -0
- package/dist/web/assets/{cobol-D39V6ukp.js → cobol-Byn9FYKu.js} +1 -1
- package/dist/web/assets/code-editor-CJ8g7dqm.js +10 -0
- package/dist/web/assets/{coffee-DTtK7kKi.js → coffee-DjBl4gm-.js} +1 -1
- package/dist/web/assets/{conflict-editor-B_wAb0Kb.js → conflict-editor-BP7JFZzG.js} +3 -3
- package/dist/web/assets/context-menu-BSE5yuMG.js +1 -0
- package/dist/web/assets/copy-Su9m2utd.js +1 -0
- package/dist/web/assets/{cpp-CnU9hfBq.js → cpp-CKzic-3E.js} +1 -1
- package/dist/web/assets/{crystal-DMjCL--0.js → crystal-Bkg-Ly_g.js} +1 -1
- package/dist/web/assets/csv-preview-xAwUDNat.js +2 -0
- package/dist/web/assets/{data-grid-overlay-editor-Bdc80TJ1.js → data-grid-overlay-editor-DMTCHIG9.js} +1 -1
- package/dist/web/assets/{database-viewer-awmnuDCt.js → database-viewer-BVLWYPa4.js} +1 -1
- package/dist/web/assets/dialog-D__LRQTn.js +1 -0
- package/dist/web/assets/diff-viewer-BheSOHfl.js +4 -0
- package/dist/web/assets/{docx-preview-DDw3duw9.js → docx-preview-CGlMqltP.js} +1 -1
- package/dist/web/assets/{edge-CTom35R_.js → edge-DVbZnd3Y.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-D1TEK7Jz.js → edit-diff-preview-ClJAJwlL.js} +1 -1
- package/dist/web/assets/{elixir-BAX9HSOu.js → elixir-Dwi8Wu7W.js} +1 -1
- package/dist/web/assets/{elm-CpD28xsW.js → elm-DtIv-HXJ.js} +1 -1
- package/dist/web/assets/{erb-DHX2IIEs.js → erb-D3HEDz1x.js} +1 -1
- package/dist/web/assets/{erlang-B_etLcPG.js → erlang-2MjTCzGz.js} +1 -1
- package/dist/web/assets/{esm-PmlN1RTs.js → esm-BfqDE09P.js} +1 -1
- package/dist/web/assets/explorer-body-DE5339g9.js +2 -0
- package/dist/web/assets/explorer-window-content-BrTHAoXM.js +1 -0
- package/dist/web/assets/{extension-webview-DkTI51P3.js → extension-webview-DRq1tWC1.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CpR0cN7O.js → fortran-fixed-form-BLyAtyPM.js} +1 -1
- package/dist/web/assets/{fsharp-DxITxZW5.js → fsharp-Bxbn_gbr.js} +1 -1
- package/dist/web/assets/{gdresource-DJ7IvROZ.js → gdresource-sFMY4D2Q.js} +1 -1
- package/dist/web/assets/{git-commit-sQznNP43.js → git-commit-BzB9Lio5.js} +1 -1
- package/dist/web/assets/git-log-panel-pjyOjchU.js +1 -0
- package/dist/web/assets/{git-rebase-DkHvejAR.js → git-rebase-CKSVd848.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CtQ3yXN-.js +1 -0
- package/dist/web/assets/{glide-data-grid-Bpgp72Hg.js → glide-data-grid-LX3N4SsJ.js} +9 -9
- package/dist/web/assets/{glimmer-js-BwJnDfL-.js → glimmer-js-BY8aExAJ.js} +1 -1
- package/dist/web/assets/{glimmer-ts-Coz155XK.js → glimmer-ts-Bak54dBX.js} +1 -1
- package/dist/web/assets/{glsl-BED7ZmUZ.js → glsl-nR2eaJQe.js} +1 -1
- package/dist/web/assets/{graphql-B_KyE6oz.js → graphql-CGPPFAU6.js} +1 -1
- package/dist/web/assets/group-chat-tab-CVQfptkT.js +1 -0
- package/dist/web/assets/{hack-sxywdIAQ.js → hack-CXIMX-Ol.js} +1 -1
- package/dist/web/assets/{haml-Dts9JlPU.js → haml-tPY-9Pfm.js} +1 -1
- package/dist/web/assets/{handlebars-C9HXIMtj.js → handlebars-BuW1LT7T.js} +1 -1
- package/dist/web/assets/{html-2p0RvXi4.js → html-bgVcwZ-t.js} +1 -1
- package/dist/web/assets/{html-derivative-BiTPuOba.js → html-derivative-D6-IxTgn.js} +1 -1
- package/dist/web/assets/{http-CHd2gYPf.js → http-CJ1zyB-h.js} +1 -1
- package/dist/web/assets/{hurl-boMPgDm9.js → hurl-CmoT7l3T.js} +1 -1
- package/dist/web/assets/{hxml-BQvjiuBt.js → hxml-CztOsL8o.js} +1 -1
- package/dist/web/assets/{image-preview-Dwg0EuJ2.js → image-preview-BDySmiim.js} +1 -1
- package/dist/web/assets/index-XoDY9bc0.js +52 -0
- package/dist/web/assets/index-YCTraslE.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-Cg0mIJn9.js +1 -0
- package/dist/web/assets/{jinja-B-zKiwlp.js → jinja-B1pq3jJq.js} +1 -1
- package/dist/web/assets/{jison-C2vJYjbJ.js → jison-D8B5pCwA.js} +1 -1
- package/dist/web/assets/{julia-DJ8MrQPA.js → julia-D0K3bwpU.js} +1 -1
- package/dist/web/assets/{just-BvQHq7VE.js → just-B9Jlddu-.js} +1 -1
- package/dist/web/assets/keybindings-store-BE1jVUZW.js +1 -0
- package/dist/web/assets/{latex-ChNeshUC.js → latex-BPJSSE6j.js} +1 -1
- package/dist/web/assets/{liquid-KNJEBOwW.js → liquid-lv10fo4U.js} +1 -1
- package/dist/web/assets/{lua-D8NNsLbC.js → lua-PNuaIBEF.js} +1 -1
- package/dist/web/assets/{markdown-renderer-C7dlPs2S.js → markdown-renderer-B8Kz5rvP.js} +3 -3
- package/dist/web/assets/markdown-renderer-Btync7yg.js +1 -0
- package/dist/web/assets/{marko-HGo5Qtz4.js → marko-Du3frD6X.js} +1 -1
- package/dist/web/assets/{mdc-omkodQB5.js → mdc-BeDVcFRl.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet-CzRXE6mY.js +1 -0
- package/dist/web/assets/mobile-explorer-sheet-CDU8xriw.js +1 -0
- package/dist/web/assets/{nextflow-Adq02pv-.js → nextflow-DkcXP71s.js} +1 -1
- package/dist/web/assets/{nginx-ByvWruGD.js → nginx-C5n8N_bt.js} +1 -1
- package/dist/web/assets/{nim-BrMojX4O.js → nim-CIhyfs-A.js} +1 -1
- package/dist/web/assets/notification-store-Bi7VHAy4.js +1 -0
- package/dist/web/assets/{number-overlay-editor-CunvWJnN.js → number-overlay-editor-B8P26qGC.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-D79vAFR_.js +1 -0
- package/dist/web/assets/{panel-store-CpKvXNnm.js → panel-store-gbNJvZB_.js} +1 -1
- package/dist/web/assets/{pdf-preview-CEWYMxdp.js → pdf-preview-CtZWvujY.js} +1 -1
- package/dist/web/assets/{perl-Bd_0l-b4.js → perl-Cf31sZYE.js} +1 -1
- package/dist/web/assets/{php-D-DLTnWJ.js → php-Dzl_MDIW.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-DYUOH68U.js +1 -0
- package/dist/web/assets/{postgres-viewer-D7PggslU.js → postgres-viewer-fqMUi0xS.js} +3 -3
- package/dist/web/assets/{project-store-D0ALup9Z.js → project-store-D1D1MN03.js} +1 -1
- package/dist/web/assets/{pug-D2aO-l2T.js → pug-CxSYHrB3.js} +1 -1
- package/dist/web/assets/{qml-CyQVzNXS.js → qml-DlEjGhwN.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-B1eznNtd.js +1 -0
- package/dist/web/assets/{razor-8tvJXsO3.js → razor-KVt5uW14.js} +1 -1
- package/dist/web/assets/{rst-C4AZyQQl.js → rst-D5BwNTnw.js} +1 -1
- package/dist/web/assets/{ruby-O_z2NTLY.js → ruby-D5fejQwd.js} +1 -1
- package/dist/web/assets/{sas-BtnF4zaV.js → sas-Bj7B1CB2.js} +1 -1
- package/dist/web/assets/{scss-GAyp5euH.js → scss-BB0CUoCL.js} +1 -1
- package/dist/web/assets/settings-store-Bm7uL8VA.js +2 -0
- package/dist/web/assets/settings-tab-DxtBG0DW.js +1 -0
- package/dist/web/assets/{shaderlab-nM5YZwa5.js → shaderlab-BdjEZUMc.js} +1 -1
- package/dist/web/assets/{shellsession-D2OCI4Gv.js → shellsession-Bp1OA1rR.js} +1 -1
- package/dist/web/assets/{soy-DrLNhqfp.js → soy-DlYz3wsC.js} +1 -1
- package/dist/web/assets/sparkles-BvJ1EGWd.js +1 -0
- package/dist/web/assets/{sparql-CsM6F2Yv.js → sparql-CdfII5Jd.js} +1 -1
- package/dist/web/assets/{sql-query-editor-ZnKcxCyj.js → sql-query-editor-D2UxRvG7.js} +1 -1
- package/dist/web/assets/sqlite-viewer-CTBwzT-r.js +1 -0
- package/dist/web/assets/{stata-Bk3cgfXJ.js → stata-BvUA6GUW.js} +1 -1
- package/dist/web/assets/{surrealql-Dkz0DPK8.js → surrealql--DLNbTyI.js} +1 -1
- package/dist/web/assets/{svelte-23si1KV5.js → svelte-CBfSsG4n.js} +1 -1
- package/dist/web/assets/{system-monitor-tab-Ck3REHUg.js → system-monitor-tab-Bscf6tzK.js} +1 -1
- package/dist/web/assets/{tab-store-BQmDVh9c.js → tab-store-DnNnaAcE.js} +1 -1
- package/dist/web/assets/{templ-CPxlP4vF.js → templ-Dv8zpKZW.js} +1 -1
- package/dist/web/assets/{terminal-tab-D6tYDFSF.js → terminal-tab-CTKU5gaF.js} +3 -3
- package/dist/web/assets/{tex-ugJpgYPc.js → tex-CsrOiqh1.js} +1 -1
- package/dist/web/assets/tool-cards-xstpayaW.js +7 -0
- package/dist/web/assets/treemap-KZPCXAKY-SeI2TxLs.js +1 -0
- package/dist/web/assets/{ts-tags-BovP9TMu.js → ts-tags-3T98k4EJ.js} +1 -1
- package/dist/web/assets/{twig-B7VUm2Ca.js → twig-u6QZ45FH.js} +1 -1
- package/dist/web/assets/{use-blob-url-B3YovXF6.js → use-blob-url-BLRmhlZI.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-wKbyNwIn.js → use-monaco-theme-DxYstgYa.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-CYOBtZtD.js → vendor-mermaid-C977xNeE.js} +1 -1
- package/dist/web/assets/{vendor-ui-DMRGWRuc.js → vendor-ui-CXJtBULr.js} +1 -1
- package/dist/web/assets/{video-preview-DwrfenNA.js → video-preview-cfaoRxG3.js} +1 -1
- package/dist/web/assets/{vue-m75pWowk.js → vue-CxIsxtwn.js} +1 -1
- package/dist/web/assets/{vue-html-odHs7IMn.js → vue-html-BGL5qnuH.js} +1 -1
- package/dist/web/assets/{vue-vine-BbBB3V_G.js → vue-vine-DHPh_gMr.js} +1 -1
- package/dist/web/assets/{xml-BHzIcDWI.js → xml-C-vM2m7J.js} +1 -1
- package/dist/web/assets/{xsl-DTLhN7uu.js → xsl-DvTXNwuO.js} +1 -1
- package/dist/web/index.html +26 -23
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +10 -1
- package/docs/design-guidelines.md +31 -0
- package/docs/project-roadmap.md +10 -0
- package/docs/system-architecture.md +59 -0
- package/package.json +2 -1
- package/src/providers/claude-agent-sdk.ts +6 -0
- package/src/server/index.ts +8 -0
- package/src/server/routes/files.ts +2 -2
- package/src/server/routes/fs-browse.ts +99 -74
- package/src/server/routes/fs-ops.ts +118 -0
- package/src/server/routes/fs-sqlite.ts +129 -0
- package/src/server/routes/host-info.ts +17 -0
- package/src/server/routes/settings.ts +2 -0
- package/src/services/download-token.service.ts +21 -5
- package/src/services/file.service.ts +8 -12
- package/src/services/fs-browse.service.ts +104 -161
- package/src/services/fs-ops/fs-content-disposition.ts +17 -0
- package/src/services/fs-ops/fs-core-ops.ts +149 -0
- package/src/services/fs-ops/fs-error-response.ts +24 -0
- package/src/services/fs-ops/fs-git-init.service.ts +23 -0
- package/src/services/fs-ops/fs-hidden-names.ts +17 -0
- package/src/services/fs-ops/fs-list-files.service.ts +68 -0
- package/src/services/fs-ops/fs-ops-copy-move.service.ts +60 -0
- package/src/services/fs-ops/fs-ops-mutate.service.ts +59 -0
- package/src/services/fs-ops/fs-ops-read-write.service.ts +70 -0
- package/src/services/fs-ops/fs-ops-stat.service.ts +109 -0
- package/src/services/fs-ops/fs-ops-trash.service.ts +111 -0
- package/src/services/fs-ops/fs-real-path.ts +44 -0
- package/src/services/fs-ops/sql-row-reader.ts +26 -0
- package/src/services/fs-ops/sql-statement-guard.ts +63 -0
- package/src/services/fs-path-guard.service.ts +196 -0
- package/src/services/host-info/apple-bookmark-decoder.ts +97 -0
- package/src/services/host-info/apple-plist-xml-parser.ts +107 -0
- package/src/services/host-info/concurrency-limit.ts +20 -0
- package/src/services/host-info/host-drives.ts +190 -0
- package/src/services/host-info/host-info.service.ts +171 -0
- package/src/services/host-info/host-known-folders.ts +180 -0
- package/src/services/host-info/pinned-linux-bookmarks.ts +89 -0
- package/src/services/host-info/pinned-macos-finder-favorites.ts +175 -0
- package/src/services/host-info/pinned-windows-quick-access.ts +87 -0
- package/src/services/host-info/spawn-runner.ts +41 -0
- package/src/services/jsonl-transcript-parser.ts +6 -0
- package/src/services/sqlite-types.ts +26 -0
- package/src/services/sqlite.service.ts +38 -29
- package/src/services/subagent-transcript-merger.ts +144 -0
- package/src/types/system.ts +50 -0
- package/src/web/app.tsx +19 -3
- package/src/web/components/editor/code-editor.tsx +3 -9
- package/src/web/components/explorer/file-tree.tsx +103 -21
- package/src/web/components/explorer/tree-node-context-menu.tsx +6 -1
- package/src/web/components/explorer/tree-node.tsx +34 -93
- package/src/web/components/explorer/use-file-upload-drag.ts +14 -9
- package/src/web/components/explorer/use-tree-row-dnd.ts +206 -0
- package/src/web/components/floating-window/default-window-chrome.tsx +71 -0
- package/src/web/components/floating-window/floating-window.tsx +167 -0
- package/src/web/components/floating-window/use-window-drag.ts +28 -0
- package/src/web/components/floating-window/use-window-gesture-context.ts +44 -0
- package/src/web/components/floating-window/use-window-keyboard.ts +55 -0
- package/src/web/components/floating-window/use-window-resize.ts +30 -0
- package/src/web/components/floating-window/window-chrome-contract.ts +38 -0
- package/src/web/components/floating-window/window-content-registry.ts +51 -0
- package/src/web/components/floating-window/window-geometry.ts +121 -0
- package/src/web/components/floating-window/window-layer.tsx +92 -0
- package/src/web/components/floating-window/window-persistence.ts +104 -0
- package/src/web/components/floating-window/window-resize-handles.tsx +81 -0
- package/src/web/components/floating-window/window-store-types.ts +20 -0
- package/src/web/components/floating-window/window-store.ts +192 -0
- package/src/web/components/layout/command-palette.tsx +10 -0
- package/src/web/components/layout/mobile-drawer.tsx +11 -2
- package/src/web/components/layout/nav-section-rail.tsx +7 -1
- package/src/web/components/os-explorer/actions/explorer-actions-clipboard.ts +186 -0
- package/src/web/components/os-explorer/actions/explorer-actions-mutate.ts +109 -0
- package/src/web/components/os-explorer/actions/explorer-actions-open.ts +67 -0
- package/src/web/components/os-explorer/actions/explorer-actions-transfer-helpers.ts +35 -0
- package/src/web/components/os-explorer/actions/use-explorer-actions.ts +205 -0
- package/src/web/components/os-explorer/can-open-in-ppm.ts +89 -0
- package/src/web/components/os-explorer/dnd/drag-depth-counter.ts +36 -0
- package/src/web/components/os-explorer/dnd/drag-ghost.ts +44 -0
- package/src/web/components/os-explorer/dnd/drop-target-decision.ts +46 -0
- package/src/web/components/os-explorer/dnd/drop-target-style.ts +7 -0
- package/src/web/components/os-explorer/dnd/entry-drag-payload.ts +115 -0
- package/src/web/components/os-explorer/dnd/entry-drag-state.ts +35 -0
- package/src/web/components/os-explorer/dnd/entry-drop-executor.ts +43 -0
- package/src/web/components/os-explorer/dnd/use-drag-auto-scroll.ts +70 -0
- package/src/web/components/os-explorer/dnd/use-drop-transfer.tsx +82 -0
- package/src/web/components/os-explorer/dnd/use-entry-drag-source.ts +73 -0
- package/src/web/components/os-explorer/dnd/use-entry-drop-target.ts +150 -0
- package/src/web/components/os-explorer/dnd/use-entry-row-dnd.ts +66 -0
- package/src/web/components/os-explorer/dnd/use-path-drop-target.ts +30 -0
- package/src/web/components/os-explorer/explorer-body.tsx +153 -0
- package/src/web/components/os-explorer/explorer-breadcrumb.tsx +108 -0
- package/src/web/components/os-explorer/explorer-context-menu.tsx +131 -0
- package/src/web/components/os-explorer/explorer-dialogs.tsx +103 -0
- package/src/web/components/os-explorer/explorer-modal-shell.tsx +60 -0
- package/src/web/components/os-explorer/explorer-pins-store.ts +63 -0
- package/src/web/components/os-explorer/explorer-sidebar.tsx +198 -0
- package/src/web/components/os-explorer/explorer-sort-menu.tsx +68 -0
- package/src/web/components/os-explorer/explorer-status-bar.tsx +35 -0
- package/src/web/components/os-explorer/explorer-store.ts +179 -0
- package/src/web/components/os-explorer/explorer-toolbar.tsx +139 -0
- package/src/web/components/os-explorer/explorer-window-content.tsx +36 -0
- package/src/web/components/os-explorer/format-file-meta.ts +74 -0
- package/src/web/components/os-explorer/icons/file-type-icon-map.ts +106 -0
- package/src/web/components/os-explorer/icons/file-type-icon.tsx +56 -0
- package/src/web/components/os-explorer/icons/thumbnail-fetch-semaphore.ts +44 -0
- package/src/web/components/os-explorer/icons/thumbnail-image.tsx +124 -0
- package/src/web/components/os-explorer/mobile/mobile-bottom-toolbar.tsx +102 -0
- package/src/web/components/os-explorer/mobile/mobile-explorer-sheet.tsx +61 -0
- package/src/web/components/os-explorer/mobile/mobile-explorer-top-bar.tsx +41 -0
- package/src/web/components/os-explorer/mobile/mobile-more-sheet.tsx +117 -0
- package/src/web/components/os-explorer/mobile/mobile-places-sheet.tsx +120 -0
- package/src/web/components/os-explorer/mobile/mobile-places-strip.tsx +109 -0
- package/src/web/components/os-explorer/mobile/mobile-tap-action.ts +19 -0
- package/src/web/components/os-explorer/mobile/use-mobile-row-tap.ts +50 -0
- package/src/web/components/os-explorer/open-explorer.ts +35 -0
- package/src/web/components/os-explorer/properties-dialog.tsx +99 -0
- package/src/web/components/os-explorer/skins/explorer-skin-chrome.tsx +17 -0
- package/src/web/components/os-explorer/skins/folder-icon-macos.tsx +42 -0
- package/src/web/components/os-explorer/skins/folder-icon-windows.tsx +25 -0
- package/src/web/components/os-explorer/skins/macos-skin.ts +14 -0
- package/src/web/components/os-explorer/skins/macos-window-chrome.tsx +69 -0
- package/src/web/components/os-explorer/skins/skin-types.ts +40 -0
- package/src/web/components/os-explorer/skins/skins.css +25 -0
- package/src/web/components/os-explorer/skins/use-explorer-skin.ts +21 -0
- package/src/web/components/os-explorer/skins/windows-skin.ts +11 -0
- package/src/web/components/os-explorer/skins/windows-window-chrome.tsx +56 -0
- package/src/web/components/os-explorer/sort-and-filter-entries.ts +52 -0
- package/src/web/components/os-explorer/sort-options.ts +29 -0
- package/src/web/components/os-explorer/toolbar-icon-button.ts +18 -0
- package/src/web/components/os-explorer/use-coarse-long-press.ts +97 -0
- package/src/web/components/os-explorer/use-explorer-keyboard.ts +124 -0
- package/src/web/components/os-explorer/use-explorer-navigation.ts +155 -0
- package/src/web/components/os-explorer/use-explorer-open-state.ts +67 -0
- package/src/web/components/os-explorer/use-host-info.ts +85 -0
- package/src/web/components/os-explorer/views/column-row.tsx +126 -0
- package/src/web/components/os-explorer/views/column-view-column.tsx +112 -0
- package/src/web/components/os-explorer/views/column-view-mobile.tsx +98 -0
- package/src/web/components/os-explorer/views/column-view-preview.tsx +70 -0
- package/src/web/components/os-explorer/views/column-view.tsx +204 -0
- package/src/web/components/os-explorer/views/compose-interactive-row-props.ts +77 -0
- package/src/web/components/os-explorer/views/explorer-view-registry.tsx +55 -0
- package/src/web/components/os-explorer/views/icons-view-tile.tsx +158 -0
- package/src/web/components/os-explorer/views/icons-view.tsx +188 -0
- package/src/web/components/os-explorer/views/inline-name-input.tsx +60 -0
- package/src/web/components/os-explorer/views/list-row.tsx +162 -0
- package/src/web/components/os-explorer/views/list-view.tsx +177 -0
- package/src/web/components/os-explorer/views/use-column-view-state.ts +158 -0
- package/src/web/components/os-explorer/views/use-entry-selection.ts +178 -0
- package/src/web/components/settings/settings-tab.tsx +29 -2
- package/src/web/components/sqlite/sqlite-viewer.tsx +5 -2
- package/src/web/components/sqlite/use-sqlite.ts +7 -2
- package/src/web/components/ui/adaptive-context-menu.tsx +57 -1
- package/src/web/components/ui/file-browser-picker.tsx +1 -18
- package/src/web/hooks/use-chat.ts +22 -4
- package/src/web/lib/file-download.ts +6 -4
- package/src/web/lib/fs-api.ts +185 -0
- package/src/web/stores/file-store.ts +38 -1
- package/src/web/stores/settings-store.ts +14 -0
- package/src/web/styles/globals.css +1 -0
- package/dist/web/assets/api-client-RtNXvS0P.js +0 -1
- package/dist/web/assets/api-settings-De--HNHp.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-DVRYSsQn.js +0 -1
- package/dist/web/assets/chat-tab-D2nZCpAo.js +0 -13
- package/dist/web/assets/code-editor-DqQy2Qm8.js +0 -10
- package/dist/web/assets/context-menu-DIiZdfbE.js +0 -1
- package/dist/web/assets/csv-preview-BL0I3CKz.js +0 -2
- package/dist/web/assets/dialog-HHdNInEH.js +0 -1
- package/dist/web/assets/diff-viewer-_yQtutHR.js +0 -4
- package/dist/web/assets/git-log-panel-NkghB_rO.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CutkUNY_.js +0 -1
- package/dist/web/assets/group-chat-tab-CQOVBmVp.js +0 -1
- package/dist/web/assets/index-CJ0Wp_rW.js +0 -50
- package/dist/web/assets/index-CVJmlKhp.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-BpNjgbj9.js +0 -1
- package/dist/web/assets/keybindings-store-ClNYe9vD.js +0 -1
- package/dist/web/assets/markdown-renderer-CwpP5Ola.js +0 -1
- package/dist/web/assets/notification-store-DwAhNzTy.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-D5IUZ66-.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-CM29eKDR.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-QtJGaSGt.js +0 -1
- package/dist/web/assets/settings-store-B3LfpCPa.js +0 -2
- package/dist/web/assets/settings-tab-CeSJQIfF.js +0 -1
- package/dist/web/assets/sparkles-B3DmG8PJ.js +0 -1
- package/dist/web/assets/sqlite-viewer-8fk_wut8.js +0 -1
- package/dist/web/assets/tool-cards-C3ka1Y7I.js +0 -7
- package/dist/web/assets/treemap-KZPCXAKY-CjUqpVnS.js +0 -1
- /package/dist/web/assets/{abap-BM5PMK0-.js → abap-C1K0-wzc.js} +0 -0
- /package/dist/web/assets/{actionscript-3-CML-MTNy.js → actionscript-3-CJhQVjLA.js} +0 -0
- /package/dist/web/assets/{ada-DzO1tMqV.js → ada-Bt8r9fME.js} +0 -0
- /package/dist/web/assets/{andromeeda-C3nkvRhj.js → andromeeda-DF1I8qi3.js} +0 -0
- /package/dist/web/assets/{apache-Dg7oWQbs.js → apache-CnlleE6P.js} +0 -0
- /package/dist/web/assets/{apex-3O2170Ju.js → apex-j0C6Xmk6.js} +0 -0
- /package/dist/web/assets/{applescript-5AlzBqQO.js → applescript-DO7AnO8U.js} +0 -0
- /package/dist/web/assets/{apply-theme-BSJDABU3.js → apply-theme-CMQXnUAa.js} +0 -0
- /package/dist/web/assets/{ara-BGtOX5Hn.js → ara-i83AydZj.js} +0 -0
- /package/dist/web/assets/{asciidoc-CIsC4kkq.js → asciidoc-DbBA6-sF.js} +0 -0
- /package/dist/web/assets/{asm-p1-PUkh3.js → asm-B5S47Rgr.js} +0 -0
- /package/dist/web/assets/{aurora-x-BPgGL50S.js → aurora-x-De_IaSKw.js} +0 -0
- /package/dist/web/assets/{awk-Cj93pRL8.js → awk-CfyLHXqS.js} +0 -0
- /package/dist/web/assets/{ayu-dark-C50MYKhw.js → ayu-dark-DTLdZ1Si.js} +0 -0
- /package/dist/web/assets/{ayu-light-DyIp0Idy.js → ayu-light-DTOfkzhu.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-BAdm8pKU.js → ayu-mirage-DsAECCMv.js} +0 -0
- /package/dist/web/assets/{ballerina-DUO9mwyV.js → ballerina-CumGp9A7.js} +0 -0
- /package/dist/web/assets/{bat-Cg8hlSct.js → bat-Ca2SBbYN.js} +0 -0
- /package/dist/web/assets/{beancount-f8f3BBqB.js → beancount-CqyXBwwF.js} +0 -0
- /package/dist/web/assets/{berry-BqVQYZZr.js → berry-yl-kJr_H.js} +0 -0
- /package/dist/web/assets/{bibtex-C_q8ygAM.js → bibtex-DoFFszjV.js} +0 -0
- /package/dist/web/assets/{bicep-DWS-2uWR.js → bicep-CWPi4nfK.js} +0 -0
- /package/dist/web/assets/{bird2-C0pvipi_.js → bird2-DXew-ut0.js} +0 -0
- /package/dist/web/assets/{bot-E9mcVZnr.js → bot-BHXjpQ7x.js} +0 -0
- /package/dist/web/assets/{c-BQFvJ6Ux.js → c-clbySDvd.js} +0 -0
- /package/dist/web/assets/{c3-Da53KeG6.js → c3-B65jvSM1.js} +0 -0
- /package/dist/web/assets/{cadence-B5X2vWm3.js → cadence-Cm8tQTHN.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-Dm3rVY3b.js → catppuccin-frappe-DLeGslh_.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-ZxS-ga_K.js → catppuccin-latte-CR5GeHws.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-B0bAzXh0.js → catppuccin-macchiato-p-XgjxhP.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-BeMC5hqi.js → catppuccin-mocha-B8QOAZLl.js} +0 -0
- /package/dist/web/assets/{chevron-right-DeUue2aK.js → chevron-right-CtcHmZg9.js} +0 -0
- /package/dist/web/assets/{circle-question-mark-PaJgx-3d.js → circle-question-mark-EVgisosn.js} +0 -0
- /package/dist/web/assets/{clarity-BRdirlsw.js → clarity-DPulbvKH.js} +0 -0
- /package/dist/web/assets/{clojure-pSLH-KQp.js → clojure-DFuWS20u.js} +0 -0
- /package/dist/web/assets/{cmake-DsZu4U4F.js → cmake-DejCqcu4.js} +0 -0
- /package/dist/web/assets/{code-TekEnDFM.js → code-Dupqw5Fr.js} +0 -0
- /package/dist/web/assets/{codeowners-CtkKTnsH.js → codeowners-BwAyqWIH.js} +0 -0
- /package/dist/web/assets/{codeql-BpP1PDrj.js → codeql-DgSwFjqI.js} +0 -0
- /package/dist/web/assets/{common-lisp-BX1XgHTt.js → common-lisp-CxIVJCtf.js} +0 -0
- /package/dist/web/assets/{coq-D2bX6-8g.js → coq-DLSjSnmf.js} +0 -0
- /package/dist/web/assets/{csharp-DcR97P0D.js → csharp-C1HHzXBp.js} +0 -0
- /package/dist/web/assets/{css-CIjciIIk.js → css-VXsMY--y.js} +0 -0
- /package/dist/web/assets/{csv-DW48Z5d-.js → csv-BEbcJdHz.js} +0 -0
- /package/dist/web/assets/{csv-parser-DVHBcvuE.js → csv-parser-DHvoTjMF.js} +0 -0
- /package/dist/web/assets/{cue-Byh1xHko.js → cue-BkDnFqUD.js} +0 -0
- /package/dist/web/assets/{cypher-TVlMMWma.js → cypher-1cMqojSQ.js} +0 -0
- /package/dist/web/assets/{d-CxaScvxf.js → d-BnYjRXtH.js} +0 -0
- /package/dist/web/assets/{dark-plus-sn-FFTT1.js → dark-plus-XfbaH7kZ.js} +0 -0
- /package/dist/web/assets/{dart-BmpakMe-.js → dart-Ca7s-8hK.js} +0 -0
- /package/dist/web/assets/{data-grid-types-BuMHLKXV.js → data-grid-types-DP-ZexEN.js} +0 -0
- /package/dist/web/assets/{database-COYAMZaw.js → database-Dj0DxgtX.js} +0 -0
- /package/dist/web/assets/{dax-B1aRUMXT.js → dax-CmuOw-mY.js} +0 -0
- /package/dist/web/assets/{desktop-C6lD62ar.js → desktop-C2gJ6Ovb.js} +0 -0
- /package/dist/web/assets/{diff-CupGmT1a.js → diff-BCEo1Ej7.js} +0 -0
- /package/dist/web/assets/{dist-Bi4gRyhr.js → dist-CipvJy9w.js} +0 -0
- /package/dist/web/assets/{dist-m_oJqIDk.js → dist-D__U4ZJz.js} +0 -0
- /package/dist/web/assets/{docker-DjDr3TJF.js → docker-DN2jlyAh.js} +0 -0
- /package/dist/web/assets/{dotenv-QeT1SLTv.js → dotenv-NExMY2kS.js} +0 -0
- /package/dist/web/assets/{dracula-yI5kpKp5.js → dracula-B9lbE01y.js} +0 -0
- /package/dist/web/assets/{dracula-soft-CYF6cJce.js → dracula-soft-CMBBfKF_.js} +0 -0
- /package/dist/web/assets/{dream-maker-TBDKiHrV.js → dream-maker-DEyDwFsd.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-B7aZTnJ_.js → emacs-lisp-D_qP2zS6.js} +0 -0
- /package/dist/web/assets/{everforest-dark-CN77B1NJ.js → everforest-dark-DbHa2U-p.js} +0 -0
- /package/dist/web/assets/{everforest-light-CyFxGMIs.js → everforest-light-BISkJchP.js} +0 -0
- /package/dist/web/assets/{fennel-C6ktGUzp.js → fennel-6B22SfJR.js} +0 -0
- /package/dist/web/assets/{file-exclamation-point-DBEC-Kst.js → file-exclamation-point-D-ZORzYB.js} +0 -0
- /package/dist/web/assets/{fish-Vr62C2vP.js → fish-CubIIvJD.js} +0 -0
- /package/dist/web/assets/{fluent-CH3IJUki.js → fluent-DF55zQ09.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BN7VmmxZ.js → fortran-free-form-DvOu8jAt.js} +0 -0
- /package/dist/web/assets/{gdscript-BYdRNu_P.js → gdscript-DCPxdLpq.js} +0 -0
- /package/dist/web/assets/{gdshader-C2XOq90k.js → gdshader-CTZ381FO.js} +0 -0
- /package/dist/web/assets/{genie-B4hkIXNU.js → genie-C_QDtvgU.js} +0 -0
- /package/dist/web/assets/{gherkin-CW9NH2DW.js → gherkin-BcvK42tF.js} +0 -0
- /package/dist/web/assets/{github-dark-BWCf99kM.js → github-dark-C9afs_4w.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DZ1m_6OP.js → github-dark-default-CKGVrl3Z.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-CUXSQ8bS.js → github-dark-dimmed-C3dQZ_LC.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-B6BvU8H-.js → github-dark-high-contrast-DW78QukY.js} +0 -0
- /package/dist/web/assets/{github-light-Ch-E92cy.js → github-light-BnP2dznS.js} +0 -0
- /package/dist/web/assets/{github-light-default-CKFslV7i.js → github-light-default-C2fPZzPG.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-DiJVeRlZ.js → github-light-high-contrast-BzIuUFR2.js} +0 -0
- /package/dist/web/assets/{gleam-D4dP7V5M.js → gleam-BpeKwEIO.js} +0 -0
- /package/dist/web/assets/{gn-DuO2D2eW.js → gn-nUb_Mit0.js} +0 -0
- /package/dist/web/assets/{gnuplot-Df_8pFbI.js → gnuplot-D7I0v20p.js} +0 -0
- /package/dist/web/assets/{go-BL16cMJO.js → go-D7Qqska7.js} +0 -0
- /package/dist/web/assets/{groovy-rfHeCRqy.js → groovy-DS-pne5-.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-CLbDN-E3.js → gruvbox-dark-hard-C0W01fsq.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Df0L0qje.js → gruvbox-dark-medium-Bqo6fQbg.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-DV6TAcen.js → gruvbox-dark-soft-D-XSM_fu.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-CWXUzp9b.js → gruvbox-light-hard-IVAwMJRl.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-BOHMe1-7.js → gruvbox-light-medium-38ATiG7i.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-DrrS2sdV.js → gruvbox-light-soft-DnZr3c_0.js} +0 -0
- /package/dist/web/assets/{haskell-DybKb9uW.js → haskell-uQcywkDH.js} +0 -0
- /package/dist/web/assets/{haxe-DLRwZxmt.js → haxe-c6bI4_GT.js} +0 -0
- /package/dist/web/assets/{hcl-CusCzwOk.js → hcl-CQXTiEXq.js} +0 -0
- /package/dist/web/assets/{hjson-BygWIJ-3.js → hjson-ChETf2bz.js} +0 -0
- /package/dist/web/assets/{hlsl-DCKvuEyZ.js → hlsl-BMC4JGoE.js} +0 -0
- /package/dist/web/assets/{horizon-DSOVAzyH.js → horizon-BusFlhLW.js} +0 -0
- /package/dist/web/assets/{horizon-bright-DKfVjGj_.js → horizon-bright-2oD0Mz7W.js} +0 -0
- /package/dist/web/assets/{houston-Ddm8AgpV.js → houston-DdbbjqFg.js} +0 -0
- /package/dist/web/assets/{hy-DzZa6alE.js → hy-D_SOuqw9.js} +0 -0
- /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-BWFpZAAP.js} +0 -0
- /package/dist/web/assets/{imba-aiXalPnK.js → imba-BxLOHO5I.js} +0 -0
- /package/dist/web/assets/{ini-CAvrVa5R.js → ini-ox-CFcg0.js} +0 -0
- /package/dist/web/assets/{java-PRMSQp24.js → java-Bzo8yIal.js} +0 -0
- /package/dist/web/assets/{javascript-DQR6GQKs.js → javascript-DHuz9VXn.js} +0 -0
- /package/dist/web/assets/{json-uWUKqOxq.js → json-0_y80FD5.js} +0 -0
- /package/dist/web/assets/{json5-R_LXJXL0.js → json5-CEQ50pGM.js} +0 -0
- /package/dist/web/assets/{jsonc-tC5zvrPO.js → jsonc-CfrLEAKA.js} +0 -0
- /package/dist/web/assets/{jsonl-BAwnWh7U.js → jsonl-fKbrANC2.js} +0 -0
- /package/dist/web/assets/{jsonnet-CPj4HCRm.js → jsonnet-DKbkPaQ2.js} +0 -0
- /package/dist/web/assets/{jssm-B7HswxOM.js → jssm-Dxxx1Hqu.js} +0 -0
- /package/dist/web/assets/{jsx-z4TGELE1.js → jsx-DlJL-Oj3.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BpeLlraV.js → kanagawa-dragon-CwN4SV1c.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-CLnIPAGs.js → kanagawa-lotus-BHltayiE.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-BglPniBb.js → kanagawa-wave-D-KXPjw6.js} +0 -0
- /package/dist/web/assets/{kdl-B6blIqLq.js → kdl-DvtoJdFo.js} +0 -0
- /package/dist/web/assets/{kotlin-DVAEYiwY.js → kotlin-CTS4MLcx.js} +0 -0
- /package/dist/web/assets/{kusto-Ba87977j.js → kusto-HtSE_sKm.js} +0 -0
- /package/dist/web/assets/{laserwave-C-NuzoMp.js → laserwave-DaXZG9ge.js} +0 -0
- /package/dist/web/assets/{lean-CJ_KLkrJ.js → lean-BYtGUfWx.js} +0 -0
- /package/dist/web/assets/{less-Dfn9YC_2.js → less-BoxmU_Mk.js} +0 -0
- /package/dist/web/assets/{lib-DTaN9riD.js → lib-Bpbn00xM.js} +0 -0
- /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-tR4XlhMj.js} +0 -0
- /package/dist/web/assets/{light-plus-B3BLlguG.js → light-plus-Bt5CyUuo.js} +0 -0
- /package/dist/web/assets/{llvm-6UDP3NQb.js → llvm-CSdk9q9V.js} +0 -0
- /package/dist/web/assets/{log-QW3dqr1D.js → log-B-kp_3CL.js} +0 -0
- /package/dist/web/assets/{logo-DUinJheI.js → logo-DdIsXFDO.js} +0 -0
- /package/dist/web/assets/{luau-B1vcBzl8.js → luau-DUBpLlzQ.js} +0 -0
- /package/dist/web/assets/{make-CqNL10gS.js → make-B3JVjoW_.js} +0 -0
- /package/dist/web/assets/{markdown-BQNMNiYS.js → markdown-Seon0vHs.js} +0 -0
- /package/dist/web/assets/{material-theme-rbV8EefR.js → material-theme-Nq-ChX1P.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-BZrskvoo.js → material-theme-darker-CPV4ayXJ.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-B0CBBjBw.js → material-theme-lighter-0kyjYqEd.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-CX_CHnQq.js → material-theme-ocean-DGpAp5co.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-ztiOCPbw.js → material-theme-palenight-B0AUn0NG.js} +0 -0
- /package/dist/web/assets/{matlab-tHQDNC9G.js → matlab-C2waytxs.js} +0 -0
- /package/dist/web/assets/{mdx-D4kZ6c6G.js → mdx-DAis6R6I.js} +0 -0
- /package/dist/web/assets/{mermaid-DlR4U4Pe.js → mermaid-_rnT2YGD.js} +0 -0
- /package/dist/web/assets/{min-dark-BX-xHAD2.js → min-dark-BSSjchUB.js} +0 -0
- /package/dist/web/assets/{min-light-Bxw0_Gqq.js → min-light-DomAFQsa.js} +0 -0
- /package/dist/web/assets/{mipsasm-DYyZAum-.js → mipsasm-bBgtvPyG.js} +0 -0
- /package/dist/web/assets/{mojo-q9wuEYKR.js → mojo-Cp9BayW0.js} +0 -0
- /package/dist/web/assets/{monokai-D9vrJ8JM.js → monokai-CL_9gbmn.js} +0 -0
- /package/dist/web/assets/{moonbit-C9roWxMb.js → moonbit-BS_fcr-P.js} +0 -0
- /package/dist/web/assets/{move-BvhjG2ic.js → move-C79AH45U.js} +0 -0
- /package/dist/web/assets/{narrat-BXGbPwG3.js → narrat-CJ5s3BDf.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-CMy5-Z4k.js → nextflow-groovy-ZHa4JrPJ.js} +0 -0
- /package/dist/web/assets/{night-owl-CBQBcbmw.js → night-owl-Jkf9zFQK.js} +0 -0
- /package/dist/web/assets/{night-owl-light-CC-brflp.js → night-owl-light-DRPeaG5J.js} +0 -0
- /package/dist/web/assets/{nix-B9EAdptu.js → nix-D2Jet9DN.js} +0 -0
- /package/dist/web/assets/{nord-DxASd4qG.js → nord-CREVs22j.js} +0 -0
- /package/dist/web/assets/{nushell-BsY59i7N.js → nushell-CAiDi_mR.js} +0 -0
- /package/dist/web/assets/{objective-c-CoeQAcnP.js → objective-c-CVkmJkC1.js} +0 -0
- /package/dist/web/assets/{objective-cpp-BysdFvND.js → objective-cpp-DIKDO1Ly.js} +0 -0
- /package/dist/web/assets/{ocaml-CrtQYMMK.js → ocaml-R9qtGQhs.js} +0 -0
- /package/dist/web/assets/{odin-B8pz4gIL.js → odin-DTYNQqVr.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-3sSHqoqQ.js → one-dark-pro-BSLWUJn5.js} +0 -0
- /package/dist/web/assets/{one-light-LUZAvT8g.js → one-light-DbkRXG3t.js} +0 -0
- /package/dist/web/assets/{openscad-DcKia-sV.js → openscad-BlPcSnmT.js} +0 -0
- /package/dist/web/assets/{pascal-zM5SlmgF.js → pascal-B1Y46ZZB.js} +0 -0
- /package/dist/web/assets/{pkl-C_r_zLLt.js → pkl-BhJSJbFv.js} +0 -0
- /package/dist/web/assets/{plastic-B3cRtgKD.js → plastic-BzxsCTpi.js} +0 -0
- /package/dist/web/assets/{plsql-BzyjheXU.js → plsql-DBba7dyP.js} +0 -0
- /package/dist/web/assets/{plus-DcuJuDC3.js → plus-Dm36mgLh.js} +0 -0
- /package/dist/web/assets/{po-Dz4OaDNi.js → po-BvgLlnNo.js} +0 -0
- /package/dist/web/assets/{poimandres-BBbtYwmR.js → poimandres-DUaI_1JP.js} +0 -0
- /package/dist/web/assets/{polar-Che1XC2Z.js → polar-C7uILwJn.js} +0 -0
- /package/dist/web/assets/{postcss-DouzwG4F.js → postcss-BgJlpFdw.js} +0 -0
- /package/dist/web/assets/{powerquery-YOdcTZmd.js → powerquery-Dn1MIpZu.js} +0 -0
- /package/dist/web/assets/{powershell-8ojzYH34.js → powershell-Bo-DYzOS.js} +0 -0
- /package/dist/web/assets/{prisma-DCmjceML.js → prisma-CTLs5IdT.js} +0 -0
- /package/dist/web/assets/{prolog-ByNNz2pB.js → prolog-QRGun3eG.js} +0 -0
- /package/dist/web/assets/{proto-DWMt6Qu1.js → proto-B7PI7apn.js} +0 -0
- /package/dist/web/assets/{puppet-osfKyZp4.js → puppet-CBq0tLrP.js} +0 -0
- /package/dist/web/assets/{purescript-ClIblkvh.js → purescript-DFewOQb2.js} +0 -0
- /package/dist/web/assets/{python-Ctyr4faP.js → python-iWuxKXvT.js} +0 -0
- /package/dist/web/assets/{qmldir-CbNgyhnI.js → qmldir-cXiW3AsF.js} +0 -0
- /package/dist/web/assets/{qss-LbRHzb21.js → qss-DASwAbBL.js} +0 -0
- /package/dist/web/assets/{r-Dvfjp0wc.js → r-TU2eaIPw.js} +0 -0
- /package/dist/web/assets/{racket-DI8v8nzD.js → racket-SkRKqrLY.js} +0 -0
- /package/dist/web/assets/{raku-CX1q9GW0.js → raku-D31lthkZ.js} +0 -0
- /package/dist/web/assets/{react-8hrD3-kv.js → react-DjdYfcEe.js} +0 -0
- /package/dist/web/assets/{red-CSz3QEOw.js → red-CTcQZevb.js} +0 -0
- /package/dist/web/assets/{reg-FMjhUQE-.js → reg-D8YAsTGf.js} +0 -0
- /package/dist/web/assets/{regexp-CCSe1Fc4.js → regexp-CD2uvAWx.js} +0 -0
- /package/dist/web/assets/{rel-Ce0UO-OB.js → rel-CAaI-1fg.js} +0 -0
- /package/dist/web/assets/{resolve-theme-B9Mgd5GG.js → resolve-theme-B0eotaPq.js} +0 -0
- /package/dist/web/assets/{riscv-BFZrdZtB.js → riscv-DR-hJVIj.js} +0 -0
- /package/dist/web/assets/{ron-D0t4kvJG.js → ron-CuUCJ0n4.js} +0 -0
- /package/dist/web/assets/{rose-pine-HzwmOzHM.js → rose-pine-aFHxW48i.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-CSp9xAF9.js → rose-pine-dawn-Bqz2sBRY.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-Cnzr2Cw0.js → rose-pine-moon-wqyh0an4.js} +0 -0
- /package/dist/web/assets/{rosmsg-DEJ8Snbv.js → rosmsg-CI1ywimO.js} +0 -0
- /package/dist/web/assets/{rust-2jBzrC0l.js → rust-p7Imk4G_.js} +0 -0
- /package/dist/web/assets/{sass-DD75Izu_.js → sass-C8JfaqCV.js} +0 -0
- /package/dist/web/assets/{save-BM4p9_9n.js → save-ignW_jk2.js} +0 -0
- /package/dist/web/assets/{scala-DpBKROIu.js → scala-DNxCW1By.js} +0 -0
- /package/dist/web/assets/{scheme-DM2jzwUK.js → scheme-BYuUPJoL.js} +0 -0
- /package/dist/web/assets/{sdbl-C34Cg6e7.js → sdbl-DbPeJPUr.js} +0 -0
- /package/dist/web/assets/{shellscript-DxfzKSgU.js → shellscript-DMiHlR3q.js} +0 -0
- /package/dist/web/assets/{shield-check-C3qYSU8a.js → shield-check-BSA9P3nI.js} +0 -0
- /package/dist/web/assets/{shield-off-D5tU7gQT.js → shield-off-Cb9J9x_n.js} +0 -0
- /package/dist/web/assets/{slack-dark-DIIV3bpD.js → slack-dark-C4xGcQXI.js} +0 -0
- /package/dist/web/assets/{slack-ochin-BTkxWjEP.js → slack-ochin-BzgRt_wm.js} +0 -0
- /package/dist/web/assets/{smalltalk-Bm0qg6SA.js → smalltalk-LDeL9PYH.js} +0 -0
- /package/dist/web/assets/{snazzy-light-ClAuuhn4.js → snazzy-light-CXE7a5j_.js} +0 -0
- /package/dist/web/assets/{solarized-dark-93sEWmYT.js → solarized-dark-DgUhY74x.js} +0 -0
- /package/dist/web/assets/{solarized-light-C74FvgcO.js → solarized-light-DD9C-H-9.js} +0 -0
- /package/dist/web/assets/{solidity-H9fSjkQP.js → solidity-CAbDB4D4.js} +0 -0
- /package/dist/web/assets/{splunk-CDTN2iAG.js → splunk-kQREPfIc.js} +0 -0
- /package/dist/web/assets/{sql-CoqObhgo.js → sql-CBzeNAD8.js} +0 -0
- /package/dist/web/assets/{ssh-config-C3Wv6qRp.js → ssh-config-L1euxrsM.js} +0 -0
- /package/dist/web/assets/{stylus-D0uTFXCy.js → stylus-DvBnYniP.js} +0 -0
- /package/dist/web/assets/{swift-DtMMgvRI.js → swift-DF5NDQp6.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DAroyZBu.js → synthwave-84-DbBhB_Hq.js} +0 -0
- /package/dist/web/assets/{system-verilog-Bj7X_lq4.js → system-verilog-D8s2ZW_k.js} +0 -0
- /package/dist/web/assets/{systemd-CH8ApCUs.js → systemd-a5-XZJsG.js} +0 -0
- /package/dist/web/assets/{table-Di8pv2Y_.js → table-4P8kS7sg.js} +0 -0
- /package/dist/web/assets/{talonscript-70lVC3cw.js → talonscript-BLa0URTW.js} +0 -0
- /package/dist/web/assets/{tasl-WQR8VslV.js → tasl-DIoV4XcC.js} +0 -0
- /package/dist/web/assets/{tcl-caTR_frn.js → tcl-ztYAdXfQ.js} +0 -0
- /package/dist/web/assets/{terraform-CVRZ4SZ4.js → terraform-DvpCCiOZ.js} +0 -0
- /package/dist/web/assets/{tokyo-night-CDyT1jCI.js → tokyo-night-BkHlYUv0.js} +0 -0
- /package/dist/web/assets/{toml-B7D2mIDU.js → toml-acIrxos6.js} +0 -0
- /package/dist/web/assets/{tsv-2kdn5dqL.js → tsv-BJB5EVCT.js} +0 -0
- /package/dist/web/assets/{tsx-DJfG3fzg.js → tsx-B1MmXGT3.js} +0 -0
- /package/dist/web/assets/{turtle-DaQac_UT.js → turtle-DFceTiqd.js} +0 -0
- /package/dist/web/assets/{typescript-BLfAIchl.js → typescript-Dz9wU5Y1.js} +0 -0
- /package/dist/web/assets/{typespec-ymBaOi_c.js → typespec-CPuYZijj.js} +0 -0
- /package/dist/web/assets/{typst-DkvUYMT-.js → typst-CfsCIYv8.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-BcuCjH19.js → use-is-mobile-K1a38BwI.js} +0 -0
- /package/dist/web/assets/{utils-E0yyGxXt.js → utils-B9H0d3Fh.js} +0 -0
- /package/dist/web/assets/{v-D4997FGs.js → v-CGo0jcXA.js} +0 -0
- /package/dist/web/assets/{vala-AFg9DmKE.js → vala-AsSR0hrb.js} +0 -0
- /package/dist/web/assets/{vb-DNUJ9fOP.js → vb-C7tvbXv1.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-QDr0jvpJ.js → vendor-xterm-ClRVIJ-k.js} +0 -0
- /package/dist/web/assets/{verilog-BrnAQ3tF.js → verilog--a33ND-p.js} +0 -0
- /package/dist/web/assets/{vesper-DCZnFmY0.js → vesper-B_Z0h4pn.js} +0 -0
- /package/dist/web/assets/{vhdl-DoK4PjhE.js → vhdl-CWL5dZPN.js} +0 -0
- /package/dist/web/assets/{viml-D6EKG66q.js → viml-BglmT14Y.js} +0 -0
- /package/dist/web/assets/{vitesse-black-C6KILWdF.js → vitesse-black-RKFsSqQI.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BuSr6OT5.js → vitesse-dark-D7zDWNmc.js} +0 -0
- /package/dist/web/assets/{vitesse-light-M-eYyOoe.js → vitesse-light-MAp87W5b.js} +0 -0
- /package/dist/web/assets/{vyper-CwSN64Jn.js → vyper-CgCBYg1G.js} +0 -0
- /package/dist/web/assets/{wasm-BhQEkzil.js → wasm-BOG7ZeTT.js} +0 -0
- /package/dist/web/assets/{wasm-CVlf9Jwb.js → wasm-Bd258nS1.js} +0 -0
- /package/dist/web/assets/{wenyan-BC7irgM3.js → wenyan-DpShPKXZ.js} +0 -0
- /package/dist/web/assets/{wgsl-W-3UHMDO.js → wgsl-BakcCblK.js} +0 -0
- /package/dist/web/assets/{wikitext-T1DoFcm8.js → wikitext-NroRWVAO.js} +0 -0
- /package/dist/web/assets/{wit-DIrx_eCI.js → wit-B5CM2pg6.js} +0 -0
- /package/dist/web/assets/{wolfram-Cza4b443.js → wolfram-7MTFBJpR.js} +0 -0
- /package/dist/web/assets/{x-BsFLg-T8.js → x-BtzqXv1z.js} +0 -0
- /package/dist/web/assets/{yaml-DOZ10aZe.js → yaml-Bwr01G2K.js} +0 -0
- /package/dist/web/assets/{zenscript-DDAXkseY.js → zenscript-Dj8GATvi.js} +0 -0
- /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 {
|
|
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) ?
|
|
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 {
|
|
3
|
-
import {
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
readSystemFile,
|
|
10
|
-
writeSystemFile,
|
|
8
|
+
assertAllowed,
|
|
9
|
+
assertNotPpmDir,
|
|
11
10
|
isAllowedPath,
|
|
12
11
|
resolvePath,
|
|
13
|
-
} from "../../services/fs-
|
|
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 {
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
33
|
-
return c.json(ok(result));
|
|
40
|
+
return c.json(ok(await browse(path, { showHidden })));
|
|
34
41
|
} catch (e) {
|
|
35
|
-
|
|
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
|
-
|
|
45
|
-
return c.json(ok(files));
|
|
52
|
+
return c.json(ok(await list(dir)));
|
|
46
53
|
} catch (e) {
|
|
47
|
-
|
|
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
|
-
|
|
57
|
-
return c.json(ok(result));
|
|
64
|
+
return c.json(ok(await readSystemFile(filePath)));
|
|
58
65
|
} catch (e) {
|
|
59
|
-
|
|
66
|
+
const { body, status } = fail(e);
|
|
67
|
+
return c.json(body, status);
|
|
60
68
|
}
|
|
61
69
|
});
|
|
62
70
|
|
|
63
|
-
/**
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 ?
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
155
|
+
const { body, status } = fail(e);
|
|
156
|
+
return c.json(body, status);
|
|
116
157
|
}
|
|
117
158
|
});
|
|
118
159
|
|
|
119
|
-
/**
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
|
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
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
/**
|
|
12
|
-
|
|
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
|
-
/**
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
266
|
+
copyEntrySync(absSrc, absDest);
|
|
271
267
|
}
|
|
272
268
|
|
|
273
269
|
/**
|