@hienlh/ppm 0.17.57 → 0.18.1
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 +24 -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-CXWQcx3_.js → ai-resource-editor-Bmk4P0YS.js} +1 -1
- package/dist/web/assets/{angular-html-D4YJUMLP.js → angular-html-C-Kjr43S.js} +1 -1
- package/dist/web/assets/{angular-ts-CUvq2h3X.js → angular-ts-CaDDbZqm.js} +1 -1
- package/dist/web/assets/api-client-CaRE4HYh.js +1 -0
- package/dist/web/assets/{api-client-COQtP-dB.js → api-client-rTAGdEKo.js} +1 -1
- package/dist/web/assets/{api-settings-Do0Z0IRN.js → api-settings-CAhcNpj5.js} +1 -1
- package/dist/web/assets/api-settings-DBcXfeJ8.js +1 -0
- package/dist/web/assets/{api-themes-DXi8sz-9.js → api-themes-CAjg_Bt9.js} +1 -1
- package/dist/web/assets/{apl-DSANFLx0.js → apl-B1WsQFAb.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-B4J2-O8Q.js +1 -0
- package/dist/web/assets/{astro-CXIotiNS.js → astro-Bdnz7nVp.js} +1 -1
- package/dist/web/assets/{audio-preview-D7jNc8mi.js → audio-preview-CikvnAOI.js} +1 -1
- package/dist/web/assets/{blade-BvrP2gPd.js → blade-B2euh8sS.js} +1 -1
- package/dist/web/assets/{bot-E9mcVZnr.js → bot-nk1k3ELu.js} +1 -1
- package/dist/web/assets/{bsl-CGf6Xuyu.js → bsl-GNeMaxd0.js} +1 -1
- package/dist/web/assets/{cairo-BboZ9qIf.js → cairo-D7G0W7-s.js} +1 -1
- package/dist/web/assets/can-open-in-ppm-BzVXoV-d.js +1 -0
- package/dist/web/assets/chat-tab-B3-KcCn2.js +13 -0
- package/dist/web/assets/chevron-right-BLOtX8Vy.js +1 -0
- package/dist/web/assets/{circle-question-mark-PaJgx-3d.js → circle-question-mark-CENpx4oA.js} +1 -1
- package/dist/web/assets/clock-D493FU_D.js +1 -0
- package/dist/web/assets/{cobol-D39V6ukp.js → cobol-Q6YgysnI.js} +1 -1
- package/dist/web/assets/code-CLF5x7KK.js +1 -0
- package/dist/web/assets/code-editor-BpbZw13T.js +10 -0
- package/dist/web/assets/{coffee-DTtK7kKi.js → coffee-BjufWxVc.js} +1 -1
- package/dist/web/assets/{conflict-editor-CpGcSopK.js → conflict-editor-DC3liBm_.js} +3 -3
- package/dist/web/assets/context-menu-q-RHHAWz.js +1 -0
- package/dist/web/assets/copy-BApI9v7Z.js +1 -0
- package/dist/web/assets/{cpp-CnU9hfBq.js → cpp-CqWU0gCR.js} +1 -1
- package/dist/web/assets/{createLucideIcon-ConhwJnb.js → createLucideIcon-D8pyde1A.js} +1 -1
- package/dist/web/assets/{crystal-DMjCL--0.js → crystal-ylayVajF.js} +1 -1
- package/dist/web/assets/csv-preview-BhCOjgq1.js +2 -0
- package/dist/web/assets/{data-grid-overlay-editor-Bdc80TJ1.js → data-grid-overlay-editor-BzPfsCfd.js} +1 -1
- package/dist/web/assets/{data-grid-types-BuMHLKXV.js → data-grid-types-CUx8cusN.js} +1 -1
- package/dist/web/assets/{database-COYAMZaw.js → database-B9okYLx6.js} +1 -1
- package/dist/web/assets/{database-viewer-DTMmic31.js → database-viewer-Z6uLyZVO.js} +1 -1
- package/dist/web/assets/dialog-CkI7rwrh.js +1 -0
- package/dist/web/assets/diff-viewer-CT1ALWt2.js +4 -0
- package/dist/web/assets/{dist-m_oJqIDk.js → dist-DduBIVkD.js} +1 -1
- package/dist/web/assets/{docx-preview-CR2ZtvNP.js → docx-preview-C_fSjfy_.js} +1 -1
- package/dist/web/assets/{edge-CTom35R_.js → edge-DX4NxQfh.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-D1TEK7Jz.js → edit-diff-preview-DfX-9om9.js} +1 -1
- package/dist/web/assets/{elixir-BAX9HSOu.js → elixir-CSh3NjbV.js} +1 -1
- package/dist/web/assets/{elm-CpD28xsW.js → elm-DBzwFYq9.js} +1 -1
- package/dist/web/assets/{erb-DHX2IIEs.js → erb-yxVOb8aB.js} +1 -1
- package/dist/web/assets/{erlang-B_etLcPG.js → erlang-5XIDkYrM.js} +1 -1
- package/dist/web/assets/{esm-PmlN1RTs.js → esm-DO4el2OJ.js} +1 -1
- package/dist/web/assets/explorer-body-CQCyGEet.js +2 -0
- package/dist/web/assets/explorer-window-content-EtyTAZSh.js +1 -0
- package/dist/web/assets/{extension-webview-BTCFWY2A.js → extension-webview-ePIM2fwM.js} +1 -1
- package/dist/web/assets/{eye-off-Rq1n2-oe.js → eye-off-CUdzgmX8.js} +1 -1
- package/dist/web/assets/{file-exclamation-point-DBEC-Kst.js → file-exclamation-point-COAwgNGw.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CpR0cN7O.js → fortran-fixed-form-D4tE5VWY.js} +1 -1
- package/dist/web/assets/{fsharp-DxITxZW5.js → fsharp-By8soj6-.js} +1 -1
- package/dist/web/assets/{gdresource-DJ7IvROZ.js → gdresource-DoSwM7Hv.js} +1 -1
- package/dist/web/assets/{git-commit-sQznNP43.js → git-commit-_ZiciHIx.js} +1 -1
- package/dist/web/assets/git-log-panel-BRWmTzX1.js +1 -0
- package/dist/web/assets/{git-rebase-DkHvejAR.js → git-rebase-C3Y5N9-s.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CtQ3yXN-.js +1 -0
- package/dist/web/assets/glide-data-grid-DCJCz4XB.js +138 -0
- package/dist/web/assets/{glimmer-js-BwJnDfL-.js → glimmer-js-BPBTXVlC.js} +1 -1
- package/dist/web/assets/{glimmer-ts-Coz155XK.js → glimmer-ts-BQSZdy_D.js} +1 -1
- package/dist/web/assets/{glsl-BED7ZmUZ.js → glsl-CbQdxpU-.js} +1 -1
- package/dist/web/assets/{graphql-B_KyE6oz.js → graphql-GMlKikpY.js} +1 -1
- package/dist/web/assets/group-chat-tab-C3UD2bcm.js +1 -0
- package/dist/web/assets/{hack-sxywdIAQ.js → hack-DyshcLec.js} +1 -1
- package/dist/web/assets/{haml-Dts9JlPU.js → haml-rqCqWzsq.js} +1 -1
- package/dist/web/assets/{handlebars-C9HXIMtj.js → handlebars-BCKwSWjR.js} +1 -1
- package/dist/web/assets/{html-2p0RvXi4.js → html-Cg29xPR1.js} +1 -1
- package/dist/web/assets/{html-derivative-BiTPuOba.js → html-derivative-geltIVg8.js} +1 -1
- package/dist/web/assets/{http-CHd2gYPf.js → http-pG7Vp237.js} +1 -1
- package/dist/web/assets/{hurl-boMPgDm9.js → hurl-Di3Uu_Mc.js} +1 -1
- package/dist/web/assets/{hxml-BQvjiuBt.js → hxml-CRy4lbv8.js} +1 -1
- package/dist/web/assets/{image-preview-0SGb7_tH.js → image-preview-CP82lor8.js} +1 -1
- package/dist/web/assets/index-BYetJoXQ.js +52 -0
- package/dist/web/assets/index-D-xwjNyX.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-Cg0mIJn9.js +1 -0
- package/dist/web/assets/{jinja-B-zKiwlp.js → jinja-C8GyVnvd.js} +1 -1
- package/dist/web/assets/{jison-C2vJYjbJ.js → jison-BywnqPn6.js} +1 -1
- package/dist/web/assets/{julia-DJ8MrQPA.js → julia-DTrO3T3B.js} +1 -1
- package/dist/web/assets/{just-BvQHq7VE.js → just-CA3KSHPi.js} +1 -1
- package/dist/web/assets/keybindings-store-yBWU8ZYT.js +1 -0
- package/dist/web/assets/{latex-ChNeshUC.js → latex-BC5xUMpc.js} +1 -1
- package/dist/web/assets/{lib-DTaN9riD.js → lib-BFetOULu.js} +1 -1
- package/dist/web/assets/{liquid-KNJEBOwW.js → liquid-Chd8tSyQ.js} +1 -1
- package/dist/web/assets/{lua-D8NNsLbC.js → lua-DHaEtwLv.js} +1 -1
- package/dist/web/assets/markdown-renderer-B2D4gdok.js +1 -0
- package/dist/web/assets/markdown-renderer-DDgl5zDc.js +3 -0
- package/dist/web/assets/{marko-HGo5Qtz4.js → marko-Desx_bC-.js} +1 -1
- package/dist/web/assets/{mdc-omkodQB5.js → mdc-DahWHc1t.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet-BQt45q9T.js +1 -0
- package/dist/web/assets/mobile-explorer-sheet-BtxyynR3.js +1 -0
- package/dist/web/assets/{nextflow-Adq02pv-.js → nextflow-3A7lutCA.js} +1 -1
- package/dist/web/assets/{nginx-ByvWruGD.js → nginx-XQda6Hdy.js} +1 -1
- package/dist/web/assets/{nim-BrMojX4O.js → nim-DHqlpwP1.js} +1 -1
- package/dist/web/assets/notification-store-DKw_mGED.js +1 -0
- package/dist/web/assets/{number-overlay-editor-CunvWJnN.js → number-overlay-editor-DgZtViPO.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-D79vAFR_.js +1 -0
- package/dist/web/assets/{panel-store-CpKvXNnm.js → panel-store-s6Qx49RK.js} +1 -1
- package/dist/web/assets/{pdf-preview-DEn8amrO.js → pdf-preview-BXDOe7bG.js} +1 -1
- package/dist/web/assets/{perl-Bd_0l-b4.js → perl-M-ncjACb.js} +1 -1
- package/dist/web/assets/{php-D-DLTnWJ.js → php-CKIajrZi.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-DYUOH68U.js +1 -0
- package/dist/web/assets/{plus-DcuJuDC3.js → plus-9LatwJMx.js} +1 -1
- package/dist/web/assets/{postgres-viewer-CjrM15gz.js → postgres-viewer-DkHYV33h.js} +3 -3
- package/dist/web/assets/{project-store-D0ALup9Z.js → project-store-DyaEpHVG.js} +1 -1
- package/dist/web/assets/{pug-D2aO-l2T.js → pug-DO0u_gO4.js} +1 -1
- package/dist/web/assets/{qml-CyQVzNXS.js → qml-UEje1PAA.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-B1eznNtd.js +1 -0
- package/dist/web/assets/{razor-8tvJXsO3.js → razor-lCs5T1Ib.js} +1 -1
- package/dist/web/assets/{react-8hrD3-kv.js → react-w6wINGnd.js} +1 -1
- package/dist/web/assets/{rst-C4AZyQQl.js → rst-CZX_O0z4.js} +1 -1
- package/dist/web/assets/{ruby-O_z2NTLY.js → ruby-idycVAyE.js} +1 -1
- package/dist/web/assets/{sas-BtnF4zaV.js → sas-o5gTfUwp.js} +1 -1
- package/dist/web/assets/{save-BM4p9_9n.js → save-C4z1p51e.js} +1 -1
- package/dist/web/assets/{scss-GAyp5euH.js → scss-CRTk3iBQ.js} +1 -1
- package/dist/web/assets/settings-store-ChqHdT2V.js +2 -0
- package/dist/web/assets/settings-tab-ESL-6MAA.js +1 -0
- package/dist/web/assets/{shaderlab-nM5YZwa5.js → shaderlab-3e5ln8ci.js} +1 -1
- package/dist/web/assets/{shellsession-D2OCI4Gv.js → shellsession-CGQjQ75I.js} +1 -1
- package/dist/web/assets/{shield-check-C3qYSU8a.js → shield-check-C5laObJv.js} +1 -1
- package/dist/web/assets/{shield-off-D5tU7gQT.js → shield-off-DULFtyLS.js} +1 -1
- package/dist/web/assets/{soy-DrLNhqfp.js → soy-CxY0VwX2.js} +1 -1
- package/dist/web/assets/sparkles-n1hzh65o.js +1 -0
- package/dist/web/assets/{sparql-CsM6F2Yv.js → sparql-CBAGAFuq.js} +1 -1
- package/dist/web/assets/{sql-query-editor-CT5M9PKQ.js → sql-query-editor-CZdZuawP.js} +1 -1
- package/dist/web/assets/sqlite-viewer-Cpq06-nz.js +1 -0
- package/dist/web/assets/{stata-Bk3cgfXJ.js → stata-BoezhOvk.js} +1 -1
- package/dist/web/assets/{surrealql-Dkz0DPK8.js → surrealql-CHEzAhu7.js} +1 -1
- package/dist/web/assets/{svelte-23si1KV5.js → svelte-y6xtu2zX.js} +1 -1
- package/dist/web/assets/{system-monitor-tab-De3oz9zD.js → system-monitor-tab-XR8NXHpv.js} +1 -1
- package/dist/web/assets/{tab-store-BQmDVh9c.js → tab-store-C42sktPo.js} +1 -1
- package/dist/web/assets/{table-Di8pv2Y_.js → table-CHmxuh6M.js} +1 -1
- package/dist/web/assets/{templ-CPxlP4vF.js → templ-DbG2y5yD.js} +1 -1
- package/dist/web/assets/{terminal-tab-CciNqtMc.js → terminal-tab-CCCB-_50.js} +3 -3
- package/dist/web/assets/{tex-ugJpgYPc.js → tex-CeKQhrQG.js} +1 -1
- package/dist/web/assets/tool-cards-CsTs5go2.js +7 -0
- package/dist/web/assets/treemap-KZPCXAKY-SeI2TxLs.js +1 -0
- package/dist/web/assets/{ts-tags-BovP9TMu.js → ts-tags-iKKOXJjG.js} +1 -1
- package/dist/web/assets/{twig-B7VUm2Ca.js → twig-DK8Vrp-G.js} +1 -1
- package/dist/web/assets/{use-blob-url-B3YovXF6.js → use-blob-url-DYtOHK7u.js} +1 -1
- package/dist/web/assets/{use-is-mobile-BcuCjH19.js → use-is-mobile-DnFSkUG2.js} +1 -1
- package/dist/web/assets/use-monaco-theme-CZh7xTJZ.js +1 -0
- package/dist/web/assets/vendor-markdown-Ls5AcU91.js +291 -0
- package/dist/web/assets/{vendor-mermaid-CYOBtZtD.js → vendor-mermaid-C977xNeE.js} +1 -1
- package/dist/web/assets/{vendor-ui-DMRGWRuc.js → vendor-ui-CaeeFO0B.js} +2 -2
- package/dist/web/assets/{video-preview-D-S_YnG6.js → video-preview-KbRcyuAA.js} +1 -1
- package/dist/web/assets/{vue-m75pWowk.js → vue-JkIEObMV.js} +1 -1
- package/dist/web/assets/{vue-html-odHs7IMn.js → vue-html-D8SqnU86.js} +1 -1
- package/dist/web/assets/{vue-vine-BbBB3V_G.js → vue-vine-DJj7N4WD.js} +1 -1
- package/dist/web/assets/x-B1qHmLdR.js +1 -0
- package/dist/web/assets/{xml-BHzIcDWI.js → xml-C-00w9Rv.js} +1 -1
- package/dist/web/assets/{xsl-DTLhN7uu.js → xsl-CVdCwPHV.js} +1 -1
- package/dist/web/index.html +29 -26
- package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
- package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
- package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
- package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
- package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
- 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/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/sqlite-types.ts +26 -0
- package/src/services/sqlite.service.ts +38 -29
- package/src/services/subagent-transcript-merger.ts +1 -1
- 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/group-chat/group-list.tsx +2 -3
- package/src/web/components/layout/command-palette.tsx +10 -0
- package/src/web/components/layout/mobile-drawer-tab-bar.tsx +3 -10
- package/src/web/components/layout/mobile-drawer.tsx +13 -2
- package/src/web/components/layout/nav-section-rail.tsx +16 -11
- 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/feature-badge.tsx +40 -0
- package/src/web/components/ui/file-browser-picker.tsx +1 -18
- package/src/web/lib/feature-badges.ts +22 -0
- package/src/web/lib/file-download.ts +6 -4
- package/src/web/lib/fs-api.ts +185 -0
- package/src/web/lib/sidebar-tabs/tab-registry.ts +4 -3
- 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-C5U7a_rP.js +0 -13
- package/dist/web/assets/chevron-right-DeUue2aK.js +0 -1
- package/dist/web/assets/code-TekEnDFM.js +0 -1
- package/dist/web/assets/code-editor-CqlzLQ7T.js +0 -10
- package/dist/web/assets/context-menu-DIiZdfbE.js +0 -1
- package/dist/web/assets/csv-preview-DD94T9ff.js +0 -2
- package/dist/web/assets/dialog-HHdNInEH.js +0 -1
- package/dist/web/assets/diff-viewer-DyQLYP7J.js +0 -4
- package/dist/web/assets/git-log-panel-CrUgsLY5.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CutkUNY_.js +0 -1
- package/dist/web/assets/glide-data-grid-LinTYl0_.js +0 -138
- package/dist/web/assets/group-chat-tab-BqNJW7y2.js +0 -1
- package/dist/web/assets/index-CVJmlKhp.css +0 -2
- package/dist/web/assets/index-I2ik0ZF7.js +0 -50
- package/dist/web/assets/info-3K5VOQVL-BpNjgbj9.js +0 -1
- package/dist/web/assets/keybindings-store-C46tNxJE.js +0 -1
- package/dist/web/assets/markdown-renderer-DSxSupDq.js +0 -3
- package/dist/web/assets/markdown-renderer-xzyhJklS.js +0 -1
- package/dist/web/assets/notification-store-Dek7ClI2.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-DIuR1VIc.js +0 -1
- package/dist/web/assets/sparkles-B3DmG8PJ.js +0 -1
- package/dist/web/assets/sqlite-viewer-CEObRQ7n.js +0 -1
- package/dist/web/assets/tool-cards-Bdx04yYD.js +0 -7
- package/dist/web/assets/treemap-KZPCXAKY-CjUqpVnS.js +0 -1
- package/dist/web/assets/use-monaco-theme-k-17kQqf.js +0 -1
- package/dist/web/assets/vendor-markdown-CKTlmXbz.js +0 -291
- package/dist/web/assets/x-BsFLg-T8.js +0 -1
- /package/dist/web/assets/{abap-BM5PMK0-.js → abap-DrMyiWmK.js} +0 -0
- /package/dist/web/assets/{actionscript-3-CML-MTNy.js → actionscript-3-BlSOyi0y.js} +0 -0
- /package/dist/web/assets/{ada-DzO1tMqV.js → ada-B_m96icB.js} +0 -0
- /package/dist/web/assets/{andromeeda-C3nkvRhj.js → andromeeda-Ch3vOiZB.js} +0 -0
- /package/dist/web/assets/{apache-Dg7oWQbs.js → apache-CdsaXc1n.js} +0 -0
- /package/dist/web/assets/{apex-3O2170Ju.js → apex-0AnsBevk.js} +0 -0
- /package/dist/web/assets/{applescript-5AlzBqQO.js → applescript-CSxLolzy.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-DXZS0jA5.js} +0 -0
- /package/dist/web/assets/{asciidoc-CIsC4kkq.js → asciidoc-DDy3pH2A.js} +0 -0
- /package/dist/web/assets/{asm-p1-PUkh3.js → asm-Bd17hhqg.js} +0 -0
- /package/dist/web/assets/{aurora-x-BPgGL50S.js → aurora-x-C62HJRs-.js} +0 -0
- /package/dist/web/assets/{awk-Cj93pRL8.js → awk-Cc-nazni.js} +0 -0
- /package/dist/web/assets/{ayu-dark-C50MYKhw.js → ayu-dark-BsfmNWCU.js} +0 -0
- /package/dist/web/assets/{ayu-light-DyIp0Idy.js → ayu-light-BZ4HLa4X.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-BAdm8pKU.js → ayu-mirage-Bhyjyiqr.js} +0 -0
- /package/dist/web/assets/{ballerina-DUO9mwyV.js → ballerina-QiWgir3v.js} +0 -0
- /package/dist/web/assets/{bat-Cg8hlSct.js → bat-DxtlkwN3.js} +0 -0
- /package/dist/web/assets/{beancount-f8f3BBqB.js → beancount-Bt21X7M_.js} +0 -0
- /package/dist/web/assets/{berry-BqVQYZZr.js → berry-Cksre7lO.js} +0 -0
- /package/dist/web/assets/{bibtex-C_q8ygAM.js → bibtex-DkakgDlR.js} +0 -0
- /package/dist/web/assets/{bicep-DWS-2uWR.js → bicep-8crmcyXU.js} +0 -0
- /package/dist/web/assets/{bird2-C0pvipi_.js → bird2-CewQ2I_Z.js} +0 -0
- /package/dist/web/assets/{c-BQFvJ6Ux.js → c-C_6zE8Ha.js} +0 -0
- /package/dist/web/assets/{c3-Da53KeG6.js → c3-DLWB9-dp.js} +0 -0
- /package/dist/web/assets/{cadence-B5X2vWm3.js → cadence-D8qYS6pR.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-Dm3rVY3b.js → catppuccin-frappe-Bh95lNRy.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-ZxS-ga_K.js → catppuccin-latte-C1PlN3G0.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-B0bAzXh0.js → catppuccin-macchiato-CldtgdK4.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-BeMC5hqi.js → catppuccin-mocha-BiwqxX9A.js} +0 -0
- /package/dist/web/assets/{clarity-BRdirlsw.js → clarity-CGiU22jY.js} +0 -0
- /package/dist/web/assets/{clojure-pSLH-KQp.js → clojure-COIC3doh.js} +0 -0
- /package/dist/web/assets/{cmake-DsZu4U4F.js → cmake-DWUEQfHv.js} +0 -0
- /package/dist/web/assets/{codeowners-CtkKTnsH.js → codeowners-BNHiAQea.js} +0 -0
- /package/dist/web/assets/{codeql-BpP1PDrj.js → codeql-BuKaoS0j.js} +0 -0
- /package/dist/web/assets/{common-lisp-BX1XgHTt.js → common-lisp-D5IKbxsd.js} +0 -0
- /package/dist/web/assets/{coq-D2bX6-8g.js → coq-BMVS-p94.js} +0 -0
- /package/dist/web/assets/{csharp-DcR97P0D.js → csharp-DMlbXbM4.js} +0 -0
- /package/dist/web/assets/{css-CIjciIIk.js → css-CjkJC89X.js} +0 -0
- /package/dist/web/assets/{csv-DW48Z5d-.js → csv-DoNVBBf0.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-DoTRbBg-.js} +0 -0
- /package/dist/web/assets/{cypher-TVlMMWma.js → cypher-DVsNdTT3.js} +0 -0
- /package/dist/web/assets/{d-CxaScvxf.js → d-EX7BBSrv.js} +0 -0
- /package/dist/web/assets/{dark-plus-sn-FFTT1.js → dark-plus-P_YTHwlh.js} +0 -0
- /package/dist/web/assets/{dart-BmpakMe-.js → dart-Do-rgU4S.js} +0 -0
- /package/dist/web/assets/{dax-B1aRUMXT.js → dax-DxOieacu.js} +0 -0
- /package/dist/web/assets/{desktop-C6lD62ar.js → desktop-CvdV7SBf.js} +0 -0
- /package/dist/web/assets/{diff-CupGmT1a.js → diff-CNOetGen.js} +0 -0
- /package/dist/web/assets/{dist-Bi4gRyhr.js → dist-CMfLpPW_.js} +0 -0
- /package/dist/web/assets/{docker-DjDr3TJF.js → docker-DAlG4_q-.js} +0 -0
- /package/dist/web/assets/{dotenv-QeT1SLTv.js → dotenv-Dh3qf6yx.js} +0 -0
- /package/dist/web/assets/{dracula-yI5kpKp5.js → dracula-baAGmJYG.js} +0 -0
- /package/dist/web/assets/{dracula-soft-CYF6cJce.js → dracula-soft-l7ZIyums.js} +0 -0
- /package/dist/web/assets/{dream-maker-TBDKiHrV.js → dream-maker-DeRjdBn8.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-B7aZTnJ_.js → emacs-lisp-CB05-jQA.js} +0 -0
- /package/dist/web/assets/{everforest-dark-CN77B1NJ.js → everforest-dark-CT-llQBT.js} +0 -0
- /package/dist/web/assets/{everforest-light-CyFxGMIs.js → everforest-light-ByPJkTT7.js} +0 -0
- /package/dist/web/assets/{fennel-C6ktGUzp.js → fennel-CeMarbvn.js} +0 -0
- /package/dist/web/assets/{fish-Vr62C2vP.js → fish-BYrt6Xtb.js} +0 -0
- /package/dist/web/assets/{fluent-CH3IJUki.js → fluent-Cr5VcAwh.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BN7VmmxZ.js → fortran-free-form-DQmKtrLa.js} +0 -0
- /package/dist/web/assets/{gdscript-BYdRNu_P.js → gdscript-QTDKjzhY.js} +0 -0
- /package/dist/web/assets/{gdshader-C2XOq90k.js → gdshader-Chr-5UYt.js} +0 -0
- /package/dist/web/assets/{genie-B4hkIXNU.js → genie-kWHMWnt0.js} +0 -0
- /package/dist/web/assets/{gherkin-CW9NH2DW.js → gherkin-DtEBQFUo.js} +0 -0
- /package/dist/web/assets/{github-dark-BWCf99kM.js → github-dark-B5aiUta2.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DZ1m_6OP.js → github-dark-default-BkxtsTqR.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-CUXSQ8bS.js → github-dark-dimmed-B9QjR_0K.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-B6BvU8H-.js → github-dark-high-contrast-D6bO-5c0.js} +0 -0
- /package/dist/web/assets/{github-light-Ch-E92cy.js → github-light-Cg2ZW_CJ.js} +0 -0
- /package/dist/web/assets/{github-light-default-CKFslV7i.js → github-light-default-eD7qUVlQ.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-DiJVeRlZ.js → github-light-high-contrast-CFnlSVjd.js} +0 -0
- /package/dist/web/assets/{gleam-D4dP7V5M.js → gleam-BpYoOqIT.js} +0 -0
- /package/dist/web/assets/{gn-DuO2D2eW.js → gn-BxwtZPau.js} +0 -0
- /package/dist/web/assets/{gnuplot-Df_8pFbI.js → gnuplot-CRyiZDfl.js} +0 -0
- /package/dist/web/assets/{go-BL16cMJO.js → go-Dw1Qx94W.js} +0 -0
- /package/dist/web/assets/{groovy-rfHeCRqy.js → groovy-zq_SbWk-.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-CLbDN-E3.js → gruvbox-dark-hard-DbbNy8-Q.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Df0L0qje.js → gruvbox-dark-medium-BQZ7Kj6C.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-DV6TAcen.js → gruvbox-dark-soft-vNavErXi.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-CWXUzp9b.js → gruvbox-light-hard-AOFbCd7i.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-BOHMe1-7.js → gruvbox-light-medium-DqtxND6H.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-DrrS2sdV.js → gruvbox-light-soft-Cg0basu6.js} +0 -0
- /package/dist/web/assets/{haskell-DybKb9uW.js → haskell-CcsmcOez.js} +0 -0
- /package/dist/web/assets/{haxe-DLRwZxmt.js → haxe-BimYY9X4.js} +0 -0
- /package/dist/web/assets/{hcl-CusCzwOk.js → hcl-CTrXCK40.js} +0 -0
- /package/dist/web/assets/{hjson-BygWIJ-3.js → hjson-CmqqU6Qg.js} +0 -0
- /package/dist/web/assets/{hlsl-DCKvuEyZ.js → hlsl-CuJtX_uf.js} +0 -0
- /package/dist/web/assets/{horizon-DSOVAzyH.js → horizon-BGeSjDYJ.js} +0 -0
- /package/dist/web/assets/{horizon-bright-DKfVjGj_.js → horizon-bright-GYMgd3R0.js} +0 -0
- /package/dist/web/assets/{houston-Ddm8AgpV.js → houston--JD_5r2q.js} +0 -0
- /package/dist/web/assets/{hy-DzZa6alE.js → hy-C9ILOiqk.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-B6F3yus5.js} +0 -0
- /package/dist/web/assets/{ini-CAvrVa5R.js → ini-BfkQswMZ.js} +0 -0
- /package/dist/web/assets/{java-PRMSQp24.js → java-DEH5avQ1.js} +0 -0
- /package/dist/web/assets/{javascript-DQR6GQKs.js → javascript-Dr4yHuV2.js} +0 -0
- /package/dist/web/assets/{json-uWUKqOxq.js → json-BuR35ewf.js} +0 -0
- /package/dist/web/assets/{json5-R_LXJXL0.js → json5-Ny9ZiJAC.js} +0 -0
- /package/dist/web/assets/{jsonc-tC5zvrPO.js → jsonc-zYVuoZkR.js} +0 -0
- /package/dist/web/assets/{jsonl-BAwnWh7U.js → jsonl-B9xthb_H.js} +0 -0
- /package/dist/web/assets/{jsonnet-CPj4HCRm.js → jsonnet-CCoLJbMP.js} +0 -0
- /package/dist/web/assets/{jssm-B7HswxOM.js → jssm-W0GDlQXi.js} +0 -0
- /package/dist/web/assets/{jsx-z4TGELE1.js → jsx-BrLJpWC-.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BpeLlraV.js → kanagawa-dragon-CuVwt_MO.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-CLnIPAGs.js → kanagawa-lotus-B3W_617P.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-BglPniBb.js → kanagawa-wave-CEx4MP5W.js} +0 -0
- /package/dist/web/assets/{kdl-B6blIqLq.js → kdl-DilRejnB.js} +0 -0
- /package/dist/web/assets/{kotlin-DVAEYiwY.js → kotlin-Bq5JtKaa.js} +0 -0
- /package/dist/web/assets/{kusto-Ba87977j.js → kusto-Bhp2KYog.js} +0 -0
- /package/dist/web/assets/{laserwave-C-NuzoMp.js → laserwave-WA_7c5hu.js} +0 -0
- /package/dist/web/assets/{lean-CJ_KLkrJ.js → lean-AEGcXwS-.js} +0 -0
- /package/dist/web/assets/{less-Dfn9YC_2.js → less-NPqH-26u.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-B4xW7cgY.js} +0 -0
- /package/dist/web/assets/{llvm-6UDP3NQb.js → llvm-CPyWNcDE.js} +0 -0
- /package/dist/web/assets/{log-QW3dqr1D.js → log-CeQmiQSz.js} +0 -0
- /package/dist/web/assets/{logo-DUinJheI.js → logo-BO0VrjBm.js} +0 -0
- /package/dist/web/assets/{luau-B1vcBzl8.js → luau-B6GT9kA1.js} +0 -0
- /package/dist/web/assets/{make-CqNL10gS.js → make-CwfmN7e_.js} +0 -0
- /package/dist/web/assets/{markdown-BQNMNiYS.js → markdown-Bbe4FlLp.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-BZrskvoo.js → material-theme-darker-u7M3XGe7.js} +0 -0
- /package/dist/web/assets/{material-theme-rbV8EefR.js → material-theme-iD9XbfsB.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-B0CBBjBw.js → material-theme-lighter-BGdI48zn.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-CX_CHnQq.js → material-theme-ocean-_mk62F5q.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-ztiOCPbw.js → material-theme-palenight-CxFbDyAu.js} +0 -0
- /package/dist/web/assets/{matlab-tHQDNC9G.js → matlab-Z0RUE3V_.js} +0 -0
- /package/dist/web/assets/{mdx-D4kZ6c6G.js → mdx-BZ5wPYzV.js} +0 -0
- /package/dist/web/assets/{mermaid-DlR4U4Pe.js → mermaid-CY1_aP-h.js} +0 -0
- /package/dist/web/assets/{min-dark-BX-xHAD2.js → min-dark-DKWtxOre.js} +0 -0
- /package/dist/web/assets/{min-light-Bxw0_Gqq.js → min-light-e1iwHDfP.js} +0 -0
- /package/dist/web/assets/{mipsasm-DYyZAum-.js → mipsasm-BiDfQ4ir.js} +0 -0
- /package/dist/web/assets/{mojo-q9wuEYKR.js → mojo-DteFEHsj.js} +0 -0
- /package/dist/web/assets/{monokai-D9vrJ8JM.js → monokai-BgPbE9xZ.js} +0 -0
- /package/dist/web/assets/{moonbit-C9roWxMb.js → moonbit-mEbRp-mA.js} +0 -0
- /package/dist/web/assets/{move-BvhjG2ic.js → move-CogqZw7A.js} +0 -0
- /package/dist/web/assets/{narrat-BXGbPwG3.js → narrat-BD2qjmy1.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-CMy5-Z4k.js → nextflow-groovy-BNX8GOD_.js} +0 -0
- /package/dist/web/assets/{night-owl-CBQBcbmw.js → night-owl-ed3GbziD.js} +0 -0
- /package/dist/web/assets/{night-owl-light-CC-brflp.js → night-owl-light-B0j7O0Wn.js} +0 -0
- /package/dist/web/assets/{nix-B9EAdptu.js → nix-DFDbcQQm.js} +0 -0
- /package/dist/web/assets/{nord-DxASd4qG.js → nord-D93TxEQi.js} +0 -0
- /package/dist/web/assets/{nushell-BsY59i7N.js → nushell-3_H_XKja.js} +0 -0
- /package/dist/web/assets/{objective-c-CoeQAcnP.js → objective-c-BM3u6nlm.js} +0 -0
- /package/dist/web/assets/{objective-cpp-BysdFvND.js → objective-cpp-DZ73h42g.js} +0 -0
- /package/dist/web/assets/{ocaml-CrtQYMMK.js → ocaml-B1PU5gmh.js} +0 -0
- /package/dist/web/assets/{odin-B8pz4gIL.js → odin-D14L6aRL.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-3sSHqoqQ.js → one-dark-pro-Bj-94Z8v.js} +0 -0
- /package/dist/web/assets/{one-light-LUZAvT8g.js → one-light-DTdGWyOZ.js} +0 -0
- /package/dist/web/assets/{openscad-DcKia-sV.js → openscad-Ciq554Sj.js} +0 -0
- /package/dist/web/assets/{pascal-zM5SlmgF.js → pascal-Bfd4qdGi.js} +0 -0
- /package/dist/web/assets/{pkl-C_r_zLLt.js → pkl-CTurDUfj.js} +0 -0
- /package/dist/web/assets/{plastic-B3cRtgKD.js → plastic-BLQfZXQp.js} +0 -0
- /package/dist/web/assets/{plsql-BzyjheXU.js → plsql-DyYfP9kQ.js} +0 -0
- /package/dist/web/assets/{po-Dz4OaDNi.js → po-CgXdT8vQ.js} +0 -0
- /package/dist/web/assets/{poimandres-BBbtYwmR.js → poimandres-DOg5fK6s.js} +0 -0
- /package/dist/web/assets/{polar-Che1XC2Z.js → polar-Dv4aoL6X.js} +0 -0
- /package/dist/web/assets/{postcss-DouzwG4F.js → postcss-DxugHnJD.js} +0 -0
- /package/dist/web/assets/{powerquery-YOdcTZmd.js → powerquery-CR3kzRyX.js} +0 -0
- /package/dist/web/assets/{powershell-8ojzYH34.js → powershell-BE91xJQB.js} +0 -0
- /package/dist/web/assets/{prisma-DCmjceML.js → prisma-B_3yUlRQ.js} +0 -0
- /package/dist/web/assets/{prolog-ByNNz2pB.js → prolog-DcT781Ht.js} +0 -0
- /package/dist/web/assets/{proto-DWMt6Qu1.js → proto-CSf-w5k2.js} +0 -0
- /package/dist/web/assets/{puppet-osfKyZp4.js → puppet-CLGO6QWl.js} +0 -0
- /package/dist/web/assets/{purescript-ClIblkvh.js → purescript-Bah2McKd.js} +0 -0
- /package/dist/web/assets/{python-Ctyr4faP.js → python-DUH8qy7N.js} +0 -0
- /package/dist/web/assets/{qmldir-CbNgyhnI.js → qmldir-YIsHnjKq.js} +0 -0
- /package/dist/web/assets/{qss-LbRHzb21.js → qss-CLmoVNA4.js} +0 -0
- /package/dist/web/assets/{r-Dvfjp0wc.js → r-Pon-b-vz.js} +0 -0
- /package/dist/web/assets/{racket-DI8v8nzD.js → racket-BA09P5Kg.js} +0 -0
- /package/dist/web/assets/{raku-CX1q9GW0.js → raku-DlpzU7Fe.js} +0 -0
- /package/dist/web/assets/{red-CSz3QEOw.js → red-l7VuCR6d.js} +0 -0
- /package/dist/web/assets/{reg-FMjhUQE-.js → reg-g_k7gQEr.js} +0 -0
- /package/dist/web/assets/{regexp-CCSe1Fc4.js → regexp-CT17B7I7.js} +0 -0
- /package/dist/web/assets/{rel-Ce0UO-OB.js → rel-DkPCS7Bp.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-CYuFTw30.js} +0 -0
- /package/dist/web/assets/{ron-D0t4kvJG.js → ron-cY7u_yEu.js} +0 -0
- /package/dist/web/assets/{rose-pine-HzwmOzHM.js → rose-pine-Cr_h_Ubo.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-CSp9xAF9.js → rose-pine-dawn-rRewaSZg.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-Cnzr2Cw0.js → rose-pine-moon-3VX5Xwca.js} +0 -0
- /package/dist/web/assets/{rosmsg-DEJ8Snbv.js → rosmsg-Du4ie5PU.js} +0 -0
- /package/dist/web/assets/{rust-2jBzrC0l.js → rust-B7xWL-Eh.js} +0 -0
- /package/dist/web/assets/{sass-DD75Izu_.js → sass-oXbOG8de.js} +0 -0
- /package/dist/web/assets/{scala-DpBKROIu.js → scala-nlZJhKHf.js} +0 -0
- /package/dist/web/assets/{scheme-DM2jzwUK.js → scheme-FZ9_Zy8D.js} +0 -0
- /package/dist/web/assets/{sdbl-C34Cg6e7.js → sdbl-D_lyvXiu.js} +0 -0
- /package/dist/web/assets/{shellscript-DxfzKSgU.js → shellscript-DRWRboAd.js} +0 -0
- /package/dist/web/assets/{slack-dark-DIIV3bpD.js → slack-dark-BGk2uVHu.js} +0 -0
- /package/dist/web/assets/{slack-ochin-BTkxWjEP.js → slack-ochin-PAoSX041.js} +0 -0
- /package/dist/web/assets/{smalltalk-Bm0qg6SA.js → smalltalk-qP0zvICo.js} +0 -0
- /package/dist/web/assets/{snazzy-light-ClAuuhn4.js → snazzy-light-B7uccpH6.js} +0 -0
- /package/dist/web/assets/{solarized-dark-93sEWmYT.js → solarized-dark-CbICLZs3.js} +0 -0
- /package/dist/web/assets/{solarized-light-C74FvgcO.js → solarized-light-CprWhyjj.js} +0 -0
- /package/dist/web/assets/{solidity-H9fSjkQP.js → solidity-B7pcm2K6.js} +0 -0
- /package/dist/web/assets/{splunk-CDTN2iAG.js → splunk-Co2LL4J_.js} +0 -0
- /package/dist/web/assets/{sql-CoqObhgo.js → sql-cETIdGnT.js} +0 -0
- /package/dist/web/assets/{ssh-config-C3Wv6qRp.js → ssh-config-DETG6JCD.js} +0 -0
- /package/dist/web/assets/{stylus-D0uTFXCy.js → stylus-tmTre2-O.js} +0 -0
- /package/dist/web/assets/{swift-DtMMgvRI.js → swift-BeKQbULV.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DAroyZBu.js → synthwave-84-eCL4-Xdn.js} +0 -0
- /package/dist/web/assets/{system-verilog-Bj7X_lq4.js → system-verilog-CviwWvEx.js} +0 -0
- /package/dist/web/assets/{systemd-CH8ApCUs.js → systemd-WuoFIbBS.js} +0 -0
- /package/dist/web/assets/{talonscript-70lVC3cw.js → talonscript-B9VBu7VI.js} +0 -0
- /package/dist/web/assets/{tasl-WQR8VslV.js → tasl-CN7r11ky.js} +0 -0
- /package/dist/web/assets/{tcl-caTR_frn.js → tcl-e1cdhe3h.js} +0 -0
- /package/dist/web/assets/{terraform-CVRZ4SZ4.js → terraform-BEP0mfa_.js} +0 -0
- /package/dist/web/assets/{tokyo-night-CDyT1jCI.js → tokyo-night-BfA_dA37.js} +0 -0
- /package/dist/web/assets/{toml-B7D2mIDU.js → toml-BrDIfNnt.js} +0 -0
- /package/dist/web/assets/{tsv-2kdn5dqL.js → tsv-CyfGyE4i.js} +0 -0
- /package/dist/web/assets/{tsx-DJfG3fzg.js → tsx-BrFA8Iwf.js} +0 -0
- /package/dist/web/assets/{turtle-DaQac_UT.js → turtle-9GBtR0mP.js} +0 -0
- /package/dist/web/assets/{typescript-BLfAIchl.js → typescript-BuTRZ65V.js} +0 -0
- /package/dist/web/assets/{typespec-ymBaOi_c.js → typespec-CRiNNRXF.js} +0 -0
- /package/dist/web/assets/{typst-DkvUYMT-.js → typst-CmpTnnkl.js} +0 -0
- /package/dist/web/assets/{utils-E0yyGxXt.js → utils-B9H0d3Fh.js} +0 -0
- /package/dist/web/assets/{v-D4997FGs.js → v-DnIi2aab.js} +0 -0
- /package/dist/web/assets/{vala-AFg9DmKE.js → vala-B7VxCNZP.js} +0 -0
- /package/dist/web/assets/{vb-DNUJ9fOP.js → vb-DGoQ3pBT.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-zGOC4qwV.js} +0 -0
- /package/dist/web/assets/{vesper-DCZnFmY0.js → vesper-DXR9-06q.js} +0 -0
- /package/dist/web/assets/{vhdl-DoK4PjhE.js → vhdl-CCO0oz0l.js} +0 -0
- /package/dist/web/assets/{viml-D6EKG66q.js → viml-Dxwr-QNN.js} +0 -0
- /package/dist/web/assets/{vitesse-black-C6KILWdF.js → vitesse-black-Bhah7RxE.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BuSr6OT5.js → vitesse-dark-CGK6XVwc.js} +0 -0
- /package/dist/web/assets/{vitesse-light-M-eYyOoe.js → vitesse-light-ne5kfw8h.js} +0 -0
- /package/dist/web/assets/{vyper-CwSN64Jn.js → vyper-ErDFqVoP.js} +0 -0
- /package/dist/web/assets/{wasm-CVlf9Jwb.js → wasm-Bd258nS1.js} +0 -0
- /package/dist/web/assets/{wasm-BhQEkzil.js → wasm-DtWvUWGr.js} +0 -0
- /package/dist/web/assets/{wenyan-BC7irgM3.js → wenyan-EZe1IEYT.js} +0 -0
- /package/dist/web/assets/{wgsl-W-3UHMDO.js → wgsl-B1o5_I_9.js} +0 -0
- /package/dist/web/assets/{wikitext-T1DoFcm8.js → wikitext-C_rwp8An.js} +0 -0
- /package/dist/web/assets/{wit-DIrx_eCI.js → wit-gbYtF3rY.js} +0 -0
- /package/dist/web/assets/{wolfram-Cza4b443.js → wolfram-DrcQFe2n.js} +0 -0
- /package/dist/web/assets/{yaml-DOZ10aZe.js → yaml-CzDAObLX.js} +0 -0
- /package/dist/web/assets/{zenscript-DDAXkseY.js → zenscript-B-J1phtg.js} +0 -0
- /package/dist/web/assets/{zig-CrzwMpQE.js → zig-BlzNia0O.js} +0 -0
|
@@ -1,20 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
readdirSync,
|
|
4
|
-
statSync,
|
|
5
|
-
lstatSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
writeFileSync,
|
|
8
|
-
} from "node:fs";
|
|
9
|
-
import { resolve, basename, dirname, normalize } from "node:path";
|
|
1
|
+
import { lstat, readdir, stat } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, resolve, sep } from "node:path";
|
|
10
3
|
import { homedir } from "node:os";
|
|
4
|
+
import {
|
|
5
|
+
assertAllowed,
|
|
6
|
+
isAllowedPath,
|
|
7
|
+
resolvePath,
|
|
8
|
+
} from "./fs-path-guard.service.ts";
|
|
9
|
+
import { isHiddenName } from "./fs-ops/fs-hidden-names.ts";
|
|
10
|
+
import { kindOfStats, type EntryKind } from "./fs-ops/fs-ops-stat.service.ts";
|
|
11
|
+
|
|
12
|
+
// Re-exported so existing importers keep a single filesystem entry point.
|
|
13
|
+
export { isAllowedPath, resolvePath };
|
|
14
|
+
export {
|
|
15
|
+
readSystemFileSync,
|
|
16
|
+
writeSystemFile,
|
|
17
|
+
} from "./fs-ops/fs-ops-read-write.service.ts";
|
|
18
|
+
export { list } from "./fs-ops/fs-list-files.service.ts";
|
|
11
19
|
|
|
12
20
|
// ── Types ──────────────────────────────────────────────────────────
|
|
13
21
|
|
|
14
22
|
export interface BrowseEntry {
|
|
15
23
|
name: string;
|
|
16
24
|
path: string;
|
|
25
|
+
/** Coarse type kept for existing consumers; `kind` carries the detail. */
|
|
17
26
|
type: "file" | "directory";
|
|
27
|
+
kind: EntryKind;
|
|
18
28
|
size?: number;
|
|
19
29
|
modified: string;
|
|
20
30
|
}
|
|
@@ -24,6 +34,10 @@ export interface BrowseResult {
|
|
|
24
34
|
current: string;
|
|
25
35
|
parent: string | null;
|
|
26
36
|
breadcrumbs: { name: string; path: string }[];
|
|
37
|
+
/** Platform path separator, so the client can render native paths. */
|
|
38
|
+
sep: string;
|
|
39
|
+
/** True when the directory held more entries than the listing cap. */
|
|
40
|
+
truncated?: boolean;
|
|
27
41
|
}
|
|
28
42
|
|
|
29
43
|
export interface BrowseOptions {
|
|
@@ -32,86 +46,84 @@ export interface BrowseOptions {
|
|
|
32
46
|
|
|
33
47
|
// ── Constants ──────────────────────────────────────────────────────
|
|
34
48
|
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return /^[A-Z]:\\/i.test(resolved);
|
|
49
|
+
/** Listing cap — beyond this the UI is unusable and the scan gets expensive. */
|
|
50
|
+
const BROWSE_MAX_ENTRIES = 5_000;
|
|
51
|
+
/** Parallel stat calls; enough to hide latency, few enough to stay polite. */
|
|
52
|
+
const STAT_CONCURRENCY = 16;
|
|
53
|
+
/**
|
|
54
|
+
* A dead SMB share, sleeping USB disk or OneDrive placeholder can make a
|
|
55
|
+
* single stat hang for minutes. The browse answer must still arrive, so a
|
|
56
|
+
* slow entry is reported with an unknown kind instead of holding the request.
|
|
57
|
+
*/
|
|
58
|
+
const STAT_TIMEOUT_MS = 1_500;
|
|
59
|
+
|
|
60
|
+
// ── Browse ─────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
async function statEntry(fullPath: string, name: string): Promise<BrowseEntry | null> {
|
|
63
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
64
|
+
const timeout = new Promise<"timeout">((r) => {
|
|
65
|
+
timer = setTimeout(() => r("timeout"), STAT_TIMEOUT_MS);
|
|
66
|
+
});
|
|
67
|
+
const stats = await Promise.race([lstat(fullPath).catch(() => "error" as const), timeout]);
|
|
68
|
+
// Clearing matters: a listing of thousands of entries would otherwise hold
|
|
69
|
+
// thousands of live timers until they each fire.
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
|
|
72
|
+
if (stats === "error") return null; // unreadable — skipped, never fatal
|
|
73
|
+
if (stats === "timeout") {
|
|
74
|
+
return {
|
|
75
|
+
name,
|
|
76
|
+
path: fullPath,
|
|
77
|
+
type: "file",
|
|
78
|
+
kind: "unknown",
|
|
79
|
+
modified: new Date(0).toISOString(),
|
|
80
|
+
};
|
|
68
81
|
}
|
|
69
|
-
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
const kind = kindOfStats(stats);
|
|
83
|
+
return {
|
|
84
|
+
name,
|
|
85
|
+
path: fullPath,
|
|
86
|
+
type: kind === "directory" ? "directory" : "file",
|
|
87
|
+
kind,
|
|
88
|
+
size: kind === "file" ? stats.size : undefined,
|
|
89
|
+
modified: stats.mtime.toISOString(),
|
|
90
|
+
};
|
|
73
91
|
}
|
|
74
92
|
|
|
75
|
-
|
|
93
|
+
/** Run `worker` over `items` with a fixed number of workers in flight. */
|
|
94
|
+
async function mapBounded<T, R>(items: T[], limit: number, worker: (item: T) => Promise<R>): Promise<R[]> {
|
|
95
|
+
const results: R[] = new Array(items.length);
|
|
96
|
+
let next = 0;
|
|
97
|
+
const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
98
|
+
while (next < items.length) {
|
|
99
|
+
const index = next++;
|
|
100
|
+
results[index] = await worker(items[index]!);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
await Promise.all(runners);
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
76
106
|
|
|
77
|
-
/** List entries of a single directory (1
|
|
78
|
-
export function browse(
|
|
79
|
-
dirPath?: string,
|
|
80
|
-
options?: BrowseOptions,
|
|
81
|
-
): BrowseResult {
|
|
107
|
+
/** List entries of a single directory (1 level, no symlink descent). */
|
|
108
|
+
export async function browse(dirPath?: string, options?: BrowseOptions): Promise<BrowseResult> {
|
|
82
109
|
const resolved = dirPath ? resolvePath(dirPath) : homedir();
|
|
110
|
+
assertAllowed(resolved);
|
|
83
111
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (!existsSync(resolved)) {
|
|
88
|
-
throw Object.assign(new Error("Directory not found"), { status: 404 });
|
|
89
|
-
}
|
|
90
|
-
if (!statSync(resolved).isDirectory()) {
|
|
91
|
-
throw Object.assign(new Error("Not a directory"), { status: 400 });
|
|
112
|
+
const st = await stat(resolved);
|
|
113
|
+
if (!st.isDirectory()) {
|
|
114
|
+
throw Object.assign(new Error("Not a directory"), { status: 400, code: "ENOTDIR" });
|
|
92
115
|
}
|
|
93
116
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
entries.push({
|
|
105
|
-
name: entry.name,
|
|
106
|
-
path: fullPath,
|
|
107
|
-
type: st.isDirectory() ? "directory" : "file",
|
|
108
|
-
size: st.isFile() ? st.size : undefined,
|
|
109
|
-
modified: st.mtime.toISOString(),
|
|
110
|
-
});
|
|
111
|
-
} catch {
|
|
112
|
-
/* permission denied — skip */
|
|
113
|
-
}
|
|
114
|
-
}
|
|
117
|
+
const names = (await readdir(resolved)).filter(
|
|
118
|
+
(name) => options?.showHidden || !isHiddenName(name),
|
|
119
|
+
);
|
|
120
|
+
const truncated = names.length > BROWSE_MAX_ENTRIES;
|
|
121
|
+
const visible = truncated ? names.slice(0, BROWSE_MAX_ENTRIES) : names;
|
|
122
|
+
|
|
123
|
+
const statted = await mapBounded(visible, STAT_CONCURRENCY, (name) =>
|
|
124
|
+
statEntry(resolve(resolved, name), name),
|
|
125
|
+
);
|
|
126
|
+
const entries = statted.filter((e): e is BrowseEntry => e !== null);
|
|
115
127
|
|
|
116
128
|
entries.sort((a, b) => {
|
|
117
129
|
if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
|
|
@@ -124,12 +136,18 @@ export function browse(
|
|
|
124
136
|
current: resolved,
|
|
125
137
|
parent: parentDir !== resolved ? parentDir : null,
|
|
126
138
|
breadcrumbs: buildBreadcrumbs(resolved),
|
|
139
|
+
sep,
|
|
140
|
+
...(truncated ? { truncated: true } : {}),
|
|
127
141
|
};
|
|
128
142
|
}
|
|
129
143
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
144
|
+
/** Label of a filesystem root: `C:` on Windows, `/` on POSIX. */
|
|
145
|
+
function rootLabel(rootPath: string): string {
|
|
146
|
+
const trimmed = rootPath.replace(/[\\/]+$/, "");
|
|
147
|
+
return trimmed || "/";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildBreadcrumbs(absPath: string): { name: string; path: string }[] {
|
|
133
151
|
const home = homedir();
|
|
134
152
|
const parts: { name: string; path: string }[] = [];
|
|
135
153
|
let current = absPath;
|
|
@@ -143,84 +161,9 @@ function buildBreadcrumbs(
|
|
|
143
161
|
current = dirname(current);
|
|
144
162
|
}
|
|
145
163
|
|
|
146
|
-
// Reached filesystem root
|
|
164
|
+
// Reached a filesystem root (drive root on Windows, `/` on POSIX)
|
|
147
165
|
if (!parts.length || parts[0]!.path !== current) {
|
|
148
|
-
parts.unshift({ name:
|
|
166
|
+
parts.unshift({ name: rootLabel(current), path: current });
|
|
149
167
|
}
|
|
150
168
|
return parts;
|
|
151
169
|
}
|
|
152
|
-
|
|
153
|
-
// ── List (moved from index.ts inline) ──────────────────────────────
|
|
154
|
-
|
|
155
|
-
/** Breadth-first file listing for command palette.
|
|
156
|
-
* Lists all files at each level before descending into subdirectories,
|
|
157
|
-
* so root-level files (e.g. ~/.npmrc) are always found before the limit. */
|
|
158
|
-
export function list(dir: string): string[] {
|
|
159
|
-
const resolved = resolvePath(dir);
|
|
160
|
-
if (!isAllowedPath(resolved)) {
|
|
161
|
-
throw Object.assign(new Error("Access denied"), { status: 403 });
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const files: string[] = [];
|
|
165
|
-
const queue: { path: string; depth: number }[] = [{ path: resolved, depth: 0 }];
|
|
166
|
-
|
|
167
|
-
while (queue.length > 0 && files.length < LIST_MAX_FILES) {
|
|
168
|
-
const { path: dirPath, depth } = queue.shift()!;
|
|
169
|
-
if (depth > LIST_MAX_DEPTH) continue;
|
|
170
|
-
let entries: import("node:fs").Dirent[];
|
|
171
|
-
try {
|
|
172
|
-
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
173
|
-
} catch {
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
for (const entry of entries) {
|
|
177
|
-
if (SKIP_NAMES.has(entry.name)) continue;
|
|
178
|
-
const full = resolve(dirPath, entry.name);
|
|
179
|
-
if (entry.isFile()) {
|
|
180
|
-
files.push(full);
|
|
181
|
-
if (files.length >= LIST_MAX_FILES) return files;
|
|
182
|
-
} else if (entry.isDirectory()) {
|
|
183
|
-
queue.push({ path: full, depth: depth + 1 });
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return files;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ── Read (moved from index.ts inline) ──────────────────────────────
|
|
192
|
-
|
|
193
|
-
/** Read a file outside project scope. */
|
|
194
|
-
export function readSystemFile(
|
|
195
|
-
filePath: string,
|
|
196
|
-
): { content: string; path: string } {
|
|
197
|
-
const resolved = resolvePath(filePath);
|
|
198
|
-
if (!isAllowedPath(resolved)) {
|
|
199
|
-
throw Object.assign(new Error("Access denied"), { status: 403 });
|
|
200
|
-
}
|
|
201
|
-
if (!existsSync(resolved)) {
|
|
202
|
-
throw Object.assign(new Error("File not found"), { status: 404 });
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const st = statSync(resolved);
|
|
206
|
-
if (!st.isFile()) {
|
|
207
|
-
throw Object.assign(new Error("Not a file"), { status: 400 });
|
|
208
|
-
}
|
|
209
|
-
if (st.size > READ_MAX_SIZE) {
|
|
210
|
-
throw Object.assign(new Error("File too large (>5MB)"), { status: 400 });
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const content = readFileSync(resolved, "utf-8");
|
|
214
|
-
return { content, path: resolved };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// ── Write (moved from index.ts inline) ─────────────────────────────
|
|
218
|
-
|
|
219
|
-
/** Write a file outside project scope. */
|
|
220
|
-
export function writeSystemFile(filePath: string, content: string): void {
|
|
221
|
-
const resolved = resolvePath(filePath);
|
|
222
|
-
if (!isAllowedPath(resolved)) {
|
|
223
|
-
throw Object.assign(new Error("Access denied"), { status: 403 });
|
|
224
|
-
}
|
|
225
|
-
writeFileSync(resolved, content, "utf-8");
|
|
226
|
-
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Percent-encode a filename for the RFC 5987 `filename*` form.
|
|
3
|
+
* `encodeURIComponent` leaves `'`, `(`, `)` and `*`, which are not attr-chars
|
|
4
|
+
* there, so a name containing them would produce a header value a strict
|
|
5
|
+
* parser rejects.
|
|
6
|
+
*/
|
|
7
|
+
export function encodeRfc5987(name: string): string {
|
|
8
|
+
return encodeURIComponent(name).replace(
|
|
9
|
+
/['()*]/g,
|
|
10
|
+
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** `Content-Disposition` for a download, with a UTF-8 safe filename. */
|
|
15
|
+
export function attachmentDisposition(filename: string): string {
|
|
16
|
+
return `attachment; filename*=UTF-8''${encodeRfc5987(filename)}`;
|
|
17
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { cpSync, renameSync, rmSync, lstatSync } from "node:fs";
|
|
2
|
+
import { cp, lstat, rename, rm } from "node:fs/promises";
|
|
3
|
+
import { resolve, sep } from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shared copy/move/rename/remove core used by both the project-scoped file
|
|
7
|
+
* service and the out-of-project explorer routes. The interesting rules —
|
|
8
|
+
* collision detection, self-nesting refusal, case-only rename, cross-device
|
|
9
|
+
* fallback — live here once so the two doors cannot drift apart. Everything
|
|
10
|
+
* inspects the link itself (`lstat`), never the symlink target, so operating
|
|
11
|
+
* on a link never touches the file it points at.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
function fsError(message: string, code: string, status: number): Error {
|
|
15
|
+
return Object.assign(new Error(message), { code, status });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function eexist(path: string): Error {
|
|
19
|
+
return fsError(`Already exists: ${path}`, "EEXIST", 409);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Path-level equality on a case-insensitive filesystem. */
|
|
23
|
+
function sameNameIgnoringCase(a: string, b: string): boolean {
|
|
24
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A rename that only changes letter case (`Foo` → `foo`) hits a false
|
|
29
|
+
* collision on Windows/macOS, where the destination "exists" because it is
|
|
30
|
+
* the source. Allowed when the platform is case-insensitive or the two paths
|
|
31
|
+
* resolve to the same inode.
|
|
32
|
+
*/
|
|
33
|
+
export function isCaseOnlyRename(src: string, dst: string): boolean {
|
|
34
|
+
const a = resolve(src);
|
|
35
|
+
const b = resolve(dst);
|
|
36
|
+
if (a === b || !sameNameIgnoringCase(a, b)) return false;
|
|
37
|
+
if (process.platform === "win32" || process.platform === "darwin") return true;
|
|
38
|
+
try {
|
|
39
|
+
return lstatSync(a).ino === lstatSync(b).ino;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Copying or moving a directory into its own subtree would recurse forever. */
|
|
46
|
+
export function assertNotNested(src: string, dst: string): void {
|
|
47
|
+
const a = resolve(src);
|
|
48
|
+
const b = resolve(dst);
|
|
49
|
+
if (b === a || b.startsWith(a.endsWith(sep) ? a : a + sep)) {
|
|
50
|
+
throw fsError(`Cannot place ${a} inside itself`, "EINVAL", 400);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function existsAsLink(path: string): Promise<boolean> {
|
|
55
|
+
try {
|
|
56
|
+
await lstat(path);
|
|
57
|
+
return true;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function existsAsLinkSync(path: string): boolean {
|
|
64
|
+
try {
|
|
65
|
+
lstatSync(path);
|
|
66
|
+
return true;
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Reject an occupied destination before any bytes move. `allowCaseOnly` is
|
|
74
|
+
* set for rename/move, where the "existing" destination is the source itself
|
|
75
|
+
* under different casing; a copy in that situation would target one file with
|
|
76
|
+
* itself and must still fail.
|
|
77
|
+
*/
|
|
78
|
+
async function assertFreeDestination(src: string, dst: string, allowCaseOnly: boolean): Promise<void> {
|
|
79
|
+
if (!(await existsAsLink(dst))) return;
|
|
80
|
+
if (allowCaseOnly && isCaseOnlyRename(src, dst)) return;
|
|
81
|
+
throw eexist(dst);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Async ops (explorer routes; never block the event loop) ─────────
|
|
85
|
+
|
|
86
|
+
export async function copyEntry(src: string, dst: string): Promise<void> {
|
|
87
|
+
assertNotNested(src, dst);
|
|
88
|
+
await assertFreeDestination(src, dst, false);
|
|
89
|
+
await cp(src, dst, { recursive: true, force: false, errorOnExist: true, verbatimSymlinks: true });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function renameEntry(src: string, dst: string): Promise<void> {
|
|
93
|
+
assertNotNested(src, dst);
|
|
94
|
+
await assertFreeDestination(src, dst, true);
|
|
95
|
+
await rename(src, dst);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function removeEntry(path: string): Promise<void> {
|
|
99
|
+
await rm(path, { recursive: true, force: false });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Move with a cross-device fallback: `rename` fails with EXDEV between two
|
|
104
|
+
* volumes (C: → D:, /home → /mnt), where a copy followed by a delete is the
|
|
105
|
+
* only way to keep the operation looking atomic to the caller.
|
|
106
|
+
*/
|
|
107
|
+
export async function moveEntry(
|
|
108
|
+
src: string,
|
|
109
|
+
dst: string,
|
|
110
|
+
/** Injectable so the cross-device branch is testable on a single volume. */
|
|
111
|
+
renameImpl: (from: string, to: string) => Promise<void> = rename,
|
|
112
|
+
): Promise<{ crossDevice: boolean; sourceRemoved: boolean }> {
|
|
113
|
+
assertNotNested(src, dst);
|
|
114
|
+
await assertFreeDestination(src, dst, true);
|
|
115
|
+
try {
|
|
116
|
+
await renameImpl(src, dst);
|
|
117
|
+
return { crossDevice: false, sourceRemoved: true };
|
|
118
|
+
} catch (e) {
|
|
119
|
+
if ((e as { code?: string }).code !== "EXDEV") throw e;
|
|
120
|
+
await cp(src, dst, { recursive: true, force: false, errorOnExist: true, verbatimSymlinks: true });
|
|
121
|
+
// The copy succeeded, so the data is safe either way. If the source cannot
|
|
122
|
+
// be removed (locked file, read-only mount) the caller must learn that two
|
|
123
|
+
// copies now exist rather than be told the move completed.
|
|
124
|
+
try {
|
|
125
|
+
await rm(src, { recursive: true, force: false });
|
|
126
|
+
} catch {
|
|
127
|
+
return { crossDevice: true, sourceRemoved: false };
|
|
128
|
+
}
|
|
129
|
+
return { crossDevice: true, sourceRemoved: true };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Sync ops (project-scoped file service keeps its sync contract) ──
|
|
134
|
+
|
|
135
|
+
export function copyEntrySync(src: string, dst: string): void {
|
|
136
|
+
assertNotNested(src, dst);
|
|
137
|
+
if (existsAsLinkSync(dst)) throw eexist(dst);
|
|
138
|
+
cpSync(src, dst, { recursive: true, force: false, errorOnExist: true, verbatimSymlinks: true });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function renameEntrySync(src: string, dst: string): void {
|
|
142
|
+
assertNotNested(src, dst);
|
|
143
|
+
if (existsAsLinkSync(dst) && !isCaseOnlyRename(src, dst)) throw eexist(dst);
|
|
144
|
+
renameSync(src, dst);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function removeEntrySync(path: string): void {
|
|
148
|
+
rmSync(path, { recursive: true, force: false });
|
|
149
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
2
|
+
import { errorStatus } from "../../server/helpers/error-status.ts";
|
|
3
|
+
import { mapFsError } from "../fs-path-guard.service.ts";
|
|
4
|
+
import { err } from "../../types/api.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Single translation from a thrown filesystem error to the JSON body the
|
|
8
|
+
* explorer expects. `code` drives the client prompts (collision → rename,
|
|
9
|
+
* NO_TRASH → offer permanent delete), `hint` explains a refusal a user can
|
|
10
|
+
* actually fix (macOS Full Disk Access).
|
|
11
|
+
*/
|
|
12
|
+
export function fsErrorBody(e: unknown): {
|
|
13
|
+
body: ReturnType<typeof err> & { code: string; hint?: string };
|
|
14
|
+
status: ContentfulStatusCode;
|
|
15
|
+
} {
|
|
16
|
+
const info = mapFsError(e);
|
|
17
|
+
// Domain errors from the project-scoped services carry no `code`; fall back
|
|
18
|
+
// to the shared class-based mapping so both doors answer alike.
|
|
19
|
+
const status = (info.status === 500 ? errorStatus(e) : info.status) as ContentfulStatusCode;
|
|
20
|
+
return {
|
|
21
|
+
body: { ...err(info.message), code: info.code, hint: info.hint },
|
|
22
|
+
status,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `git init` for a freshly created project folder. It gets a kill timer like
|
|
3
|
+
* every other spawn here: git can hang on a broken hooks template or a wedged
|
|
4
|
+
* filesystem, and the directory already exists by then, so a stuck init must
|
|
5
|
+
* not hold the HTTP request open.
|
|
6
|
+
*/
|
|
7
|
+
const GIT_INIT_TIMEOUT_MS = 5_000;
|
|
8
|
+
|
|
9
|
+
export async function runGitInit(path: string): Promise<void> {
|
|
10
|
+
const proc = Bun.spawn(["git", "init", path], { stdout: "ignore", stderr: "ignore" });
|
|
11
|
+
const timer = setTimeout(() => {
|
|
12
|
+
try {
|
|
13
|
+
proc.kill();
|
|
14
|
+
} catch {
|
|
15
|
+
/* already exited */
|
|
16
|
+
}
|
|
17
|
+
}, GIT_INIT_TIMEOUT_MS);
|
|
18
|
+
try {
|
|
19
|
+
await proc.exited;
|
|
20
|
+
} finally {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hidden-entry rules shared by browse and stat. POSIX hides dot-names; on
|
|
3
|
+
* Windows the dot convention barely exists, so the shell-managed system
|
|
4
|
+
* entries a user never wants to see are named explicitly.
|
|
5
|
+
*/
|
|
6
|
+
const WINDOWS_HIDDEN_NAMES = new Set([
|
|
7
|
+
"desktop.ini",
|
|
8
|
+
"$recycle.bin",
|
|
9
|
+
"system volume information",
|
|
10
|
+
"thumbs.db",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export function isHiddenName(name: string): boolean {
|
|
14
|
+
if (name.startsWith(".")) return true;
|
|
15
|
+
if (process.platform !== "win32") return false;
|
|
16
|
+
return WINDOWS_HIDDEN_NAMES.has(name.toLowerCase());
|
|
17
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Dirent } from "node:fs";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { assertAllowed, resolvePath } from "../fs-path-guard.service.ts";
|
|
5
|
+
|
|
6
|
+
/** Noise that would swamp a palette listing. */
|
|
7
|
+
const SKIP_NAMES = new Set([".git", "node_modules", ".DS_Store"]);
|
|
8
|
+
const LIST_MAX_FILES = 200;
|
|
9
|
+
const LIST_MAX_DEPTH = 4;
|
|
10
|
+
/** Directories read in parallel per level — same bound the browse listing uses. */
|
|
11
|
+
const READ_CONCURRENCY = 16;
|
|
12
|
+
/**
|
|
13
|
+
* A dead network mount or sleeping disk can make one `readdir` hang for
|
|
14
|
+
* minutes. The palette would rather return a short list than stall every
|
|
15
|
+
* websocket on the server, so a slow directory is simply dropped.
|
|
16
|
+
*/
|
|
17
|
+
const READ_TIMEOUT_MS = 1_500;
|
|
18
|
+
|
|
19
|
+
async function readDirSafe(dirPath: string): Promise<Dirent[]> {
|
|
20
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
21
|
+
const timeout = new Promise<Dirent[]>((r) => {
|
|
22
|
+
timer = setTimeout(() => r([]), READ_TIMEOUT_MS);
|
|
23
|
+
});
|
|
24
|
+
try {
|
|
25
|
+
return await Promise.race([
|
|
26
|
+
readdir(dirPath, { withFileTypes: true }).catch(() => [] as Dirent[]),
|
|
27
|
+
timeout,
|
|
28
|
+
]);
|
|
29
|
+
} finally {
|
|
30
|
+
clearTimeout(timer);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Breadth-first file listing for the command palette. Lists all files at each
|
|
36
|
+
* level before descending, so root-level files (e.g. ~/.npmrc) are always
|
|
37
|
+
* found before the limit is reached.
|
|
38
|
+
*/
|
|
39
|
+
export async function list(dir: string): Promise<string[]> {
|
|
40
|
+
const resolved = resolvePath(dir);
|
|
41
|
+
assertAllowed(resolved);
|
|
42
|
+
|
|
43
|
+
const files: string[] = [];
|
|
44
|
+
let level = [resolved];
|
|
45
|
+
|
|
46
|
+
for (let depth = 0; depth <= LIST_MAX_DEPTH && level.length && files.length < LIST_MAX_FILES; depth++) {
|
|
47
|
+
const next: string[] = [];
|
|
48
|
+
for (let i = 0; i < level.length; i += READ_CONCURRENCY) {
|
|
49
|
+
const batch = await Promise.all(level.slice(i, i + READ_CONCURRENCY).map(readDirSafe));
|
|
50
|
+
batch.forEach((entries, index) => {
|
|
51
|
+
const dirPath = level[i + index]!;
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
if (SKIP_NAMES.has(entry.name)) continue;
|
|
54
|
+
const full = resolve(dirPath, entry.name);
|
|
55
|
+
if (entry.isFile()) {
|
|
56
|
+
if (files.length < LIST_MAX_FILES) files.push(full);
|
|
57
|
+
} else if (entry.isDirectory()) {
|
|
58
|
+
next.push(full);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
if (files.length >= LIST_MAX_FILES) return files.slice(0, LIST_MAX_FILES);
|
|
63
|
+
}
|
|
64
|
+
level = next;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return files;
|
|
68
|
+
}
|