@hienlh/ppm 0.17.57 → 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 +16 -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-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-D7jNc8mi.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-CpGcSopK.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-DTMmic31.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-CR2ZtvNP.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-BTCFWY2A.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-LinTYl0_.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-0SGb7_tH.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-DSxSupDq.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-DEn8amrO.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-CjrM15gz.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-CT5M9PKQ.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-De3oz9zD.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-CciNqtMc.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-k-17kQqf.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-D-S_YnG6.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/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/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/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-C5U7a_rP.js +0 -13
- 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/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-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/{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
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/** Orchestrator + 60s cache for `GET /api/system/host`. Picks providers by
|
|
2
|
+
* platform, runs them concurrently (each guarded by its own 5s timeout so
|
|
3
|
+
* one dead network share or TCC-denied read never blocks the response),
|
|
4
|
+
* then applies the two response-wide rules the individual providers can't
|
|
5
|
+
* enforce themselves: only directories that still exist on disk survive
|
|
6
|
+
* (drops Shell COM's archive-as-folder quirk and stale bookmarks), and a
|
|
7
|
+
* known-folder path wins over a pinned duplicate at the same path.
|
|
8
|
+
*
|
|
9
|
+
* Existence checks use `fs.promises.stat`, not `statSync` — the whole-disk
|
|
10
|
+
* scope means a stalled UNC/mapped path must never block the event loop
|
|
11
|
+
* (same rule `host-drives.ts` follows for the Windows letter probe). */
|
|
12
|
+
import { stat as fsStat } from "node:fs/promises";
|
|
13
|
+
import { hostname as osHostname, homedir as osHomedir } from "node:os";
|
|
14
|
+
import type { HostInfo, PinnedFolder } from "../../types/system.ts";
|
|
15
|
+
import { getDrives } from "./host-drives.ts";
|
|
16
|
+
import { getKnownFolders } from "./host-known-folders.ts";
|
|
17
|
+
import { getWindowsQuickAccessPinned } from "./pinned-windows-quick-access.ts";
|
|
18
|
+
import { getLinuxPinned } from "./pinned-linux-bookmarks.ts";
|
|
19
|
+
import { getMacosFinderFavoritesPinned } from "./pinned-macos-finder-favorites.ts";
|
|
20
|
+
|
|
21
|
+
const CACHE_TTL_MS = 60_000;
|
|
22
|
+
const PROVIDER_TIMEOUT_MS = 5_000;
|
|
23
|
+
/** Floors how often an explicit `?refresh=true` can actually trigger a rebuild — without
|
|
24
|
+
* this, N concurrent refresh requests (or a trigger-happy client) each spawn their own
|
|
25
|
+
* round of PowerShell/plutil/findmnt processes. */
|
|
26
|
+
const REFRESH_FLOOR_MS = 5_000;
|
|
27
|
+
|
|
28
|
+
let cached: { info: HostInfo; expiresAt: number } | null = null;
|
|
29
|
+
let lastBuildAt = 0;
|
|
30
|
+
/** Shared by all callers while a build is running — concurrent calls (refresh or not)
|
|
31
|
+
* await the same build instead of each starting their own. */
|
|
32
|
+
let inFlight: Promise<HostInfo> | null = null;
|
|
33
|
+
|
|
34
|
+
async function getPinnedByPlatform(
|
|
35
|
+
platform: NodeJS.Platform,
|
|
36
|
+
homedir: string,
|
|
37
|
+
warnings: string[],
|
|
38
|
+
): Promise<PinnedFolder[]> {
|
|
39
|
+
if (platform === "win32") return getWindowsQuickAccessPinned(warnings);
|
|
40
|
+
if (platform === "linux") return getLinuxPinned(homedir, warnings);
|
|
41
|
+
if (platform === "darwin") return getMacosFinderFavoritesPinned(homedir, warnings);
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface HostInfoDeps {
|
|
46
|
+
getDrives: typeof getDrives;
|
|
47
|
+
getKnownFolders: typeof getKnownFolders;
|
|
48
|
+
getPinned: typeof getPinnedByPlatform;
|
|
49
|
+
/** Directory-existence probe — swappable so orchestrator tests never touch the real filesystem. */
|
|
50
|
+
isDirectory: (path: string) => Promise<boolean>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const defaultDeps: HostInfoDeps = {
|
|
54
|
+
getDrives,
|
|
55
|
+
getKnownFolders,
|
|
56
|
+
getPinned: getPinnedByPlatform,
|
|
57
|
+
isDirectory: async (p) => {
|
|
58
|
+
try {
|
|
59
|
+
return (await fsStat(p)).isDirectory();
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Race a provider call against its own timeout; a rejection or a timeout both
|
|
67
|
+
* degrade to `fallback` plus a warning — the caller never sees an exception. */
|
|
68
|
+
async function withTimeout<T>(label: string, promise: Promise<T>, fallback: T, warnings: string[]): Promise<T> {
|
|
69
|
+
const guarded = promise.catch((e) => {
|
|
70
|
+
warnings.push(`${label}: provider threw (${(e as Error)?.message ?? e})`);
|
|
71
|
+
return fallback;
|
|
72
|
+
});
|
|
73
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
74
|
+
const timeoutPromise = new Promise<T>((resolve) => {
|
|
75
|
+
timer = setTimeout(() => {
|
|
76
|
+
warnings.push(`${label}: provider timed out after ${PROVIDER_TIMEOUT_MS}ms`);
|
|
77
|
+
resolve(fallback);
|
|
78
|
+
}, PROVIDER_TIMEOUT_MS);
|
|
79
|
+
});
|
|
80
|
+
try {
|
|
81
|
+
return await Promise.race([guarded, timeoutPromise]);
|
|
82
|
+
} finally {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function existingDirectories<T extends { path: string }>(
|
|
88
|
+
items: T[],
|
|
89
|
+
isDirectory: HostInfoDeps["isDirectory"],
|
|
90
|
+
): Promise<T[]> {
|
|
91
|
+
const results = await Promise.allSettled(items.map(async (item) => ((await isDirectory(item.path)) ? item : null)));
|
|
92
|
+
const out: T[] = [];
|
|
93
|
+
for (const r of results) {
|
|
94
|
+
if (r.status === "fulfilled" && r.value) out.push(r.value);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function toHostPlatform(p: NodeJS.Platform): HostInfo["platform"] {
|
|
100
|
+
if (p === "win32" || p === "darwin" || p === "linux") return p;
|
|
101
|
+
return "linux"; // unsupported host (e.g. freebsd) — closest POSIX-shaped fallback
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Pure orchestration for an explicit platform/homedir/hostname (no `process.*` reads) —
|
|
105
|
+
* lets tests exercise all three OS branches, and the cache/dedupe/existence-filter
|
|
106
|
+
* logic in isolation, from any single host. Never throws. */
|
|
107
|
+
export async function buildHostInfo(
|
|
108
|
+
platform: NodeJS.Platform,
|
|
109
|
+
homedir: string,
|
|
110
|
+
hostname: string,
|
|
111
|
+
overrides: Partial<HostInfoDeps> = {},
|
|
112
|
+
): Promise<HostInfo> {
|
|
113
|
+
const deps: HostInfoDeps = { ...defaultDeps, ...overrides };
|
|
114
|
+
const warnings: string[] = [];
|
|
115
|
+
|
|
116
|
+
const [drives, rawKnownFolders, rawPinned] = await Promise.all([
|
|
117
|
+
withTimeout("drives", deps.getDrives(platform, warnings), [], warnings),
|
|
118
|
+
withTimeout("knownFolders", deps.getKnownFolders(platform, homedir, warnings), [], warnings),
|
|
119
|
+
withTimeout("pinned", deps.getPinned(platform, homedir, warnings), [], warnings),
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
const knownFolders = await existingDirectories(rawKnownFolders, deps.isDirectory);
|
|
123
|
+
const knownPaths = new Set(knownFolders.map((f) => f.path));
|
|
124
|
+
const pinned = await existingDirectories(
|
|
125
|
+
rawPinned.filter((p) => !knownPaths.has(p.path)),
|
|
126
|
+
deps.isDirectory,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
platform: toHostPlatform(platform),
|
|
131
|
+
sep: platform === "win32" ? "\\" : "/",
|
|
132
|
+
homedir,
|
|
133
|
+
hostname,
|
|
134
|
+
drives,
|
|
135
|
+
knownFolders,
|
|
136
|
+
pinned,
|
|
137
|
+
warnings,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Cached singleton entrypoint for the route. Pass `refresh: true` to bypass the 60s
|
|
142
|
+
* cache — floored to one rebuild per `REFRESH_FLOOR_MS`, and de-duped against any
|
|
143
|
+
* build already in flight, so concurrent/rapid-fire refreshes share one process spawn. */
|
|
144
|
+
export async function getHostInfo(
|
|
145
|
+
opts: { refresh?: boolean } = {},
|
|
146
|
+
overrides: Partial<HostInfoDeps> = {},
|
|
147
|
+
): Promise<HostInfo> {
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
if (!opts.refresh && cached && cached.expiresAt > now) return cached.info;
|
|
150
|
+
if (opts.refresh && cached && now - lastBuildAt < REFRESH_FLOOR_MS) return cached.info;
|
|
151
|
+
if (inFlight) return inFlight;
|
|
152
|
+
|
|
153
|
+
inFlight = (async () => {
|
|
154
|
+
try {
|
|
155
|
+
const info = await buildHostInfo(process.platform, osHomedir(), osHostname(), overrides);
|
|
156
|
+
lastBuildAt = Date.now();
|
|
157
|
+
cached = { info, expiresAt: lastBuildAt + CACHE_TTL_MS };
|
|
158
|
+
return info;
|
|
159
|
+
} finally {
|
|
160
|
+
inFlight = null;
|
|
161
|
+
}
|
|
162
|
+
})();
|
|
163
|
+
return inFlight;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Test-only: force the next `getHostInfo()` call to rebuild instead of serving the cache. */
|
|
167
|
+
export function _resetHostInfoCache(): void {
|
|
168
|
+
cached = null;
|
|
169
|
+
lastBuildAt = 0;
|
|
170
|
+
inFlight = null;
|
|
171
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/** Resolved known-folder locations per OS — not the naive `~/Desktop` guess.
|
|
2
|
+
* Windows asks `[Environment]::GetFolderPath` (handles OneDrive-redirected
|
|
3
|
+
* folders) plus the registry for Downloads (no `SHGetKnownFolderPath` shim
|
|
4
|
+
* in `[Environment]`). macOS prefers the iCloud "Desktop & Documents" sync
|
|
5
|
+
* location when present (Finder silently moves the real content there).
|
|
6
|
+
* Linux asks `xdg-user-dir` (merges locale + `/etc/xdg` defaults), falling
|
|
7
|
+
* back to a tolerant regex parse of `user-dirs.dirs`. */
|
|
8
|
+
import * as fsp from "node:fs/promises";
|
|
9
|
+
import type { KnownFolder, KnownFolderKey } from "../../types/system.ts";
|
|
10
|
+
import { defaultRunner, type Runner } from "./spawn-runner.ts";
|
|
11
|
+
|
|
12
|
+
export interface KnownFolderDeps {
|
|
13
|
+
run: Runner;
|
|
14
|
+
pathExists: (path: string) => Promise<boolean>;
|
|
15
|
+
readFile: (path: string) => Promise<string>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const defaultDeps: KnownFolderDeps = {
|
|
19
|
+
run: defaultRunner,
|
|
20
|
+
pathExists: async (p) => {
|
|
21
|
+
try {
|
|
22
|
+
return (await fsp.stat(p)).isDirectory();
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
readFile: (p) => fsp.readFile(p, "utf-8"),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// ── Windows ──────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
const WIN_NAMES: Record<string, string> = {
|
|
33
|
+
desktop: "Desktop",
|
|
34
|
+
documents: "Documents",
|
|
35
|
+
pictures: "Pictures",
|
|
36
|
+
music: "Music",
|
|
37
|
+
videos: "Videos",
|
|
38
|
+
downloads: "Downloads",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const WIN_SCRIPT = `
|
|
42
|
+
$r = [ordered]@{}
|
|
43
|
+
$r.desktop = [Environment]::GetFolderPath('Desktop')
|
|
44
|
+
$r.documents = [Environment]::GetFolderPath('MyDocuments')
|
|
45
|
+
$r.pictures = [Environment]::GetFolderPath('MyPictures')
|
|
46
|
+
$r.music = [Environment]::GetFolderPath('MyMusic')
|
|
47
|
+
$r.videos = [Environment]::GetFolderPath('MyVideos')
|
|
48
|
+
$reg = Get-ItemProperty 'HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders' -ErrorAction SilentlyContinue
|
|
49
|
+
$dl = $reg.'{374DE290-123F-4565-9164-39C4925E467B}'
|
|
50
|
+
if ($dl) { $r.downloads = [Environment]::ExpandEnvironmentVariables($dl) }
|
|
51
|
+
$r | ConvertTo-Json -Compress
|
|
52
|
+
`.trim();
|
|
53
|
+
|
|
54
|
+
async function getWindowsKnownFolders(deps: KnownFolderDeps, warnings: string[]): Promise<KnownFolder[]> {
|
|
55
|
+
try {
|
|
56
|
+
const res = await deps.run(
|
|
57
|
+
["powershell.exe", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", WIN_SCRIPT],
|
|
58
|
+
5000,
|
|
59
|
+
);
|
|
60
|
+
if (res.timedOut || res.code !== 0) {
|
|
61
|
+
warnings.push(`knownFolders: PowerShell ${res.timedOut ? "timed out" : `exited ${res.code}`}`);
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
const parsed = JSON.parse(res.stdout.trim() || "{}") as Record<string, string | undefined>;
|
|
65
|
+
const folders: KnownFolder[] = [];
|
|
66
|
+
for (const key of Object.keys(WIN_NAMES)) {
|
|
67
|
+
const path = parsed[key];
|
|
68
|
+
if (path) folders.push({ key: key as KnownFolderKey, name: WIN_NAMES[key]!, path });
|
|
69
|
+
}
|
|
70
|
+
return folders;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
warnings.push(`knownFolders: PowerShell failed (${(e as Error)?.message ?? e})`);
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── macOS ────────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
const DARWIN_FOLDERS: { key: KnownFolderKey; name: string; dir: string; cloudSynced: boolean }[] = [
|
|
80
|
+
{ key: "desktop", name: "Desktop", dir: "Desktop", cloudSynced: true },
|
|
81
|
+
{ key: "documents", name: "Documents", dir: "Documents", cloudSynced: true },
|
|
82
|
+
{ key: "downloads", name: "Downloads", dir: "Downloads", cloudSynced: false },
|
|
83
|
+
{ key: "pictures", name: "Pictures", dir: "Pictures", cloudSynced: false },
|
|
84
|
+
{ key: "music", name: "Music", dir: "Music", cloudSynced: false },
|
|
85
|
+
{ key: "videos", name: "Movies", dir: "Movies", cloudSynced: false },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
async function getDarwinKnownFolders(deps: KnownFolderDeps, homedir: string): Promise<KnownFolder[]> {
|
|
89
|
+
const cloudRoot = `${homedir}/Library/Mobile Documents/com~apple~CloudDocs`;
|
|
90
|
+
const cloudExists = await deps.pathExists(cloudRoot);
|
|
91
|
+
const folders: KnownFolder[] = [];
|
|
92
|
+
for (const entry of DARWIN_FOLDERS) {
|
|
93
|
+
let path = `${homedir}/${entry.dir}`;
|
|
94
|
+
if (entry.cloudSynced && cloudExists) {
|
|
95
|
+
const cloudPath = `${cloudRoot}/${entry.dir}`;
|
|
96
|
+
if (await deps.pathExists(cloudPath)) path = cloudPath;
|
|
97
|
+
}
|
|
98
|
+
folders.push({ key: entry.key, name: entry.name, path });
|
|
99
|
+
}
|
|
100
|
+
if (cloudExists) folders.push({ key: "icloud", name: "iCloud Drive", path: cloudRoot });
|
|
101
|
+
return folders;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Linux ────────────────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
const LINUX_FOLDERS: { key: KnownFolderKey; name: string; xdg: string }[] = [
|
|
107
|
+
{ key: "desktop", name: "Desktop", xdg: "DESKTOP" },
|
|
108
|
+
{ key: "documents", name: "Documents", xdg: "DOCUMENTS" },
|
|
109
|
+
{ key: "downloads", name: "Downloads", xdg: "DOWNLOAD" },
|
|
110
|
+
{ key: "pictures", name: "Pictures", xdg: "PICTURES" },
|
|
111
|
+
{ key: "music", name: "Music", xdg: "MUSIC" },
|
|
112
|
+
{ key: "videos", name: "Videos", xdg: "VIDEOS" },
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
async function readUserDirsFallback(
|
|
116
|
+
deps: KnownFolderDeps,
|
|
117
|
+
homedir: string,
|
|
118
|
+
warnings: string[],
|
|
119
|
+
): Promise<Record<string, string>> {
|
|
120
|
+
try {
|
|
121
|
+
const raw = await deps.readFile(`${homedir}/.config/user-dirs.dirs`);
|
|
122
|
+
const map: Record<string, string> = {};
|
|
123
|
+
for (const line of raw.split("\n")) {
|
|
124
|
+
const m = line.match(/^XDG_(\w+)_DIR="(.+)"$/);
|
|
125
|
+
if (!m) continue;
|
|
126
|
+
map[m[1]!] = m[2]!.replace("$HOME", homedir);
|
|
127
|
+
}
|
|
128
|
+
return map;
|
|
129
|
+
} catch (e) {
|
|
130
|
+
warnings.push(`knownFolders: user-dirs.dirs read failed (${(e as Error)?.message ?? e})`);
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function getLinuxKnownFolders(
|
|
136
|
+
deps: KnownFolderDeps,
|
|
137
|
+
homedir: string,
|
|
138
|
+
warnings: string[],
|
|
139
|
+
): Promise<KnownFolder[]> {
|
|
140
|
+
const folders: KnownFolder[] = [];
|
|
141
|
+
let fallback: Record<string, string> | null = null;
|
|
142
|
+
for (const entry of LINUX_FOLDERS) {
|
|
143
|
+
let path: string | undefined;
|
|
144
|
+
try {
|
|
145
|
+
const res = await deps.run(["xdg-user-dir", entry.xdg], 3000);
|
|
146
|
+
if (!res.timedOut && res.code === 0) {
|
|
147
|
+
const out = res.stdout.trim();
|
|
148
|
+
if (out && out !== homedir) path = out;
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// spawn itself threw (e.g. xdg-user-dir not installed) — fall through to file parse
|
|
152
|
+
}
|
|
153
|
+
if (!path) {
|
|
154
|
+
fallback ??= await readUserDirsFallback(deps, homedir, warnings);
|
|
155
|
+
path = fallback[entry.xdg];
|
|
156
|
+
}
|
|
157
|
+
if (path) folders.push({ key: entry.key, name: entry.name, path });
|
|
158
|
+
}
|
|
159
|
+
return folders;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Resolve known folders for `platform`, appending any failure to the shared `warnings` array (never throws). */
|
|
163
|
+
export async function getKnownFolders(
|
|
164
|
+
platform: NodeJS.Platform,
|
|
165
|
+
homedir: string,
|
|
166
|
+
warnings: string[],
|
|
167
|
+
overrides: Partial<KnownFolderDeps> = {},
|
|
168
|
+
): Promise<KnownFolder[]> {
|
|
169
|
+
const deps: KnownFolderDeps = { ...defaultDeps, ...overrides };
|
|
170
|
+
try {
|
|
171
|
+
if (platform === "win32") return await getWindowsKnownFolders(deps, warnings);
|
|
172
|
+
if (platform === "darwin") return await getDarwinKnownFolders(deps, homedir);
|
|
173
|
+
if (platform === "linux") return await getLinuxKnownFolders(deps, homedir, warnings);
|
|
174
|
+
warnings.push(`knownFolders: unsupported platform "${platform}"`);
|
|
175
|
+
return [];
|
|
176
|
+
} catch (e) {
|
|
177
|
+
warnings.push(`knownFolders: unexpected failure (${(e as Error)?.message ?? e})`);
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/** Linux pinned folders: GTK3/GTK4 bookmarks (Nautilus, `~/.config/gtk-3.0/bookmarks`,
|
|
2
|
+
* falling back to `gtk-4.0` since some sources report GTK4-only apps write
|
|
3
|
+
* there) and KDE Places (Dolphin, `~/.local/share/user-places.xbel`). Both
|
|
4
|
+
* formats are plain text/XML, stable for 15+ years — no reverse-engineering
|
|
5
|
+
* needed, so a tolerant regex parse is used instead of pulling in an XML lib. */
|
|
6
|
+
import * as fsp from "node:fs/promises";
|
|
7
|
+
import type { PinnedFolder } from "../../types/system.ts";
|
|
8
|
+
|
|
9
|
+
export interface LinuxBookmarkDeps {
|
|
10
|
+
readFile: (path: string) => Promise<string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const defaultDeps: LinuxBookmarkDeps = { readFile: (p) => fsp.readFile(p, "utf-8") };
|
|
14
|
+
|
|
15
|
+
interface RawBookmark {
|
|
16
|
+
name: string;
|
|
17
|
+
path: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseGtkBookmarks(raw: string): RawBookmark[] {
|
|
21
|
+
const out: RawBookmark[] = [];
|
|
22
|
+
for (const line of raw.split("\n")) {
|
|
23
|
+
const trimmed = line.trim();
|
|
24
|
+
if (!trimmed) continue;
|
|
25
|
+
const m = trimmed.match(/^file:\/\/(\S+)(?: (.*))?$/);
|
|
26
|
+
if (!m) continue; // malformed line or a non-file:// bookmark (e.g. network location) — skip, not fatal
|
|
27
|
+
const path = decodeURIComponent(m[1]!);
|
|
28
|
+
const name = m[2] ? decodeURIComponent(m[2]) : (path.split("/").filter(Boolean).pop() ?? path);
|
|
29
|
+
out.push({ name, path });
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function getGtkPinned(deps: LinuxBookmarkDeps, homedir: string, warnings: string[]): Promise<PinnedFolder[]> {
|
|
35
|
+
const candidates = [`${homedir}/.config/gtk-3.0/bookmarks`, `${homedir}/.config/gtk-4.0/bookmarks`];
|
|
36
|
+
for (const file of candidates) {
|
|
37
|
+
try {
|
|
38
|
+
const raw = await deps.readFile(file);
|
|
39
|
+
return parseGtkBookmarks(raw).map((b) => ({ ...b, source: "gtk" as const }));
|
|
40
|
+
} catch {
|
|
41
|
+
// try the next candidate path
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
warnings.push("pinned: no GTK bookmarks file found (gtk-3.0/gtk-4.0)");
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseKdePlaces(raw: string): RawBookmark[] {
|
|
49
|
+
const out: RawBookmark[] = [];
|
|
50
|
+
const re = /<bookmark\s+href="file:\/\/(.*?)"[^>]*>[\s\S]*?<title>(.*?)<\/title>/g;
|
|
51
|
+
let m: RegExpExecArray | null;
|
|
52
|
+
while ((m = re.exec(raw))) {
|
|
53
|
+
out.push({ path: decodeURIComponent(m[1]!), name: decodeURIComponent(m[2]!) });
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function getKdePinned(deps: LinuxBookmarkDeps, homedir: string, warnings: string[]): Promise<PinnedFolder[]> {
|
|
59
|
+
try {
|
|
60
|
+
const raw = await deps.readFile(`${homedir}/.local/share/user-places.xbel`);
|
|
61
|
+
return parseKdePlaces(raw).map((b) => ({ ...b, source: "kde" as const }));
|
|
62
|
+
} catch (e) {
|
|
63
|
+
warnings.push(`pinned: KDE user-places.xbel not found (${(e as Error)?.message ?? e})`);
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** GTK (Nautilus) + KDE (Dolphin) pinned folders, deduped by path (GTK wins on conflict).
|
|
69
|
+
* Appends any failure to the shared `warnings` array (never throws). */
|
|
70
|
+
export async function getLinuxPinned(
|
|
71
|
+
homedir: string,
|
|
72
|
+
warnings: string[],
|
|
73
|
+
overrides: Partial<LinuxBookmarkDeps> = {},
|
|
74
|
+
): Promise<PinnedFolder[]> {
|
|
75
|
+
const deps: LinuxBookmarkDeps = { ...defaultDeps, ...overrides };
|
|
76
|
+
const [gtk, kde] = await Promise.all([
|
|
77
|
+
getGtkPinned(deps, homedir, warnings),
|
|
78
|
+
getKdePinned(deps, homedir, warnings),
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
const seen = new Set<string>();
|
|
82
|
+
const result: PinnedFolder[] = [];
|
|
83
|
+
for (const item of [...gtk, ...kde]) {
|
|
84
|
+
if (seen.has(item.path)) continue;
|
|
85
|
+
seen.add(item.path);
|
|
86
|
+
result.push(item);
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/** macOS Finder sidebar Favorites. The container file
|
|
2
|
+
* (`~/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.FavoriteItems.sfl{2,3,4}`)
|
|
3
|
+
* is an NSKeyedArchiver binary plist; `plutil -convert xml1` turns it into
|
|
4
|
+
* readable XML without extra permissions, but the payload is still an
|
|
5
|
+
* archived object graph (`$objects` array + `CF$UID` references), not plain
|
|
6
|
+
* key/value pairs. Each favorite item is a dict carrying `Name` plus either
|
|
7
|
+
* a direct `URL` (fast path) or a `Bookmark` binary blob that must be
|
|
8
|
+
* decoded by `apple-bookmark-decoder.ts` (slow path, used when the item has
|
|
9
|
+
* moved since it was pinned).
|
|
10
|
+
*
|
|
11
|
+
* UNVERIFIED against a real macOS sample — field names/nesting come from
|
|
12
|
+
* public reverse-engineering (see plans research), not Apple docs. Kept
|
|
13
|
+
* enabled; any parse failure degrades to a `warnings[]` entry instead of
|
|
14
|
+
* blocking the whole `/api/system/host` response. Also gated by TCC: the
|
|
15
|
+
* sharedfilelist directory needs Full Disk Access for an unsandboxed
|
|
16
|
+
* process, so a denied read must surface as an actionable warning, not a
|
|
17
|
+
* silent empty list. */
|
|
18
|
+
import * as fsp from "node:fs/promises";
|
|
19
|
+
import type { PinnedFolder } from "../../types/system.ts";
|
|
20
|
+
import { defaultRunner, type Runner } from "./spawn-runner.ts";
|
|
21
|
+
import { parsePlistXml, type PlistValue } from "./apple-plist-xml-parser.ts";
|
|
22
|
+
import { decodeBookmarkPath } from "./apple-bookmark-decoder.ts";
|
|
23
|
+
|
|
24
|
+
export interface FinderFavoritesDeps {
|
|
25
|
+
run: Runner;
|
|
26
|
+
fileExists: (path: string) => Promise<boolean>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const defaultDeps: FinderFavoritesDeps = {
|
|
30
|
+
run: defaultRunner,
|
|
31
|
+
fileExists: async (p) => {
|
|
32
|
+
try {
|
|
33
|
+
await fsp.access(p);
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const SFL_EXTENSIONS = ["sfl4", "sfl3", "sfl2"]; // newest naming first (macOS 26, 13-25, <=12)
|
|
42
|
+
|
|
43
|
+
interface RawFavorite {
|
|
44
|
+
name: string;
|
|
45
|
+
path: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isUidMarker(v: PlistValue): v is { "CF$UID": number } {
|
|
49
|
+
return (
|
|
50
|
+
typeof v === "object" &&
|
|
51
|
+
v !== null &&
|
|
52
|
+
!Array.isArray(v) &&
|
|
53
|
+
!Buffer.isBuffer(v) &&
|
|
54
|
+
Object.keys(v).length === 1 &&
|
|
55
|
+
typeof (v as Record<string, PlistValue>)["CF$UID"] === "number"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function resolveUid(objects: PlistValue[], v: PlistValue): PlistValue | null {
|
|
60
|
+
return isUidMarker(v) ? (objects[v["CF$UID"]] ?? null) : v;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function asRecord(v: PlistValue | null): Record<string, PlistValue> | null {
|
|
64
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) && !Buffer.isBuffer(v)
|
|
65
|
+
? (v as Record<string, PlistValue>)
|
|
66
|
+
: null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** NSDictionary is archived as parallel `NS.keys`/`NS.objects` UID-ref arrays, not plain key/value pairs. */
|
|
70
|
+
function resolveNsKeyedDict(raw: PlistValue | null, objects: PlistValue[]): Record<string, PlistValue> | null {
|
|
71
|
+
const rec = asRecord(raw);
|
|
72
|
+
if (!rec) return null;
|
|
73
|
+
const nsKeys = rec["NS.keys"];
|
|
74
|
+
const nsObjects = rec["NS.objects"];
|
|
75
|
+
if (!Array.isArray(nsKeys) || !Array.isArray(nsObjects) || nsKeys.length !== nsObjects.length) return rec;
|
|
76
|
+
const out: Record<string, PlistValue> = {};
|
|
77
|
+
for (let i = 0; i < nsKeys.length; i++) {
|
|
78
|
+
const key = resolveUid(objects, nsKeys[i]!);
|
|
79
|
+
if (typeof key === "string") out[key] = nsObjects[i]!;
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function extractFavorites(objects: PlistValue[], warnings: string[]): RawFavorite[] {
|
|
85
|
+
const out: RawFavorite[] = [];
|
|
86
|
+
for (const raw of objects) {
|
|
87
|
+
const item = resolveNsKeyedDict(raw, objects);
|
|
88
|
+
if (!item || item["Name"] === undefined) continue;
|
|
89
|
+
const name = resolveUid(objects, item["Name"]);
|
|
90
|
+
if (typeof name !== "string") continue;
|
|
91
|
+
|
|
92
|
+
let path: string | null = null;
|
|
93
|
+
|
|
94
|
+
// Fast path: NSURL already carries a file:// string.
|
|
95
|
+
if (item["URL"] !== undefined) {
|
|
96
|
+
const urlDict = resolveNsKeyedDict(resolveUid(objects, item["URL"]), objects);
|
|
97
|
+
const relative = urlDict?.["NS.relative"];
|
|
98
|
+
const relativeStr = relative !== undefined ? resolveUid(objects, relative) : undefined;
|
|
99
|
+
if (typeof relativeStr === "string" && relativeStr.startsWith("file://")) {
|
|
100
|
+
try {
|
|
101
|
+
path = decodeURIComponent(relativeStr.replace(/^file:\/\//, ""));
|
|
102
|
+
} catch {
|
|
103
|
+
// Malformed percent-encoding — fall through to the Bookmark blob below.
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Slow path: decode the raw Bookmark blob (used once the item has moved).
|
|
109
|
+
if (!path && item["Bookmark"] !== undefined) {
|
|
110
|
+
const bookmark = resolveUid(objects, item["Bookmark"]);
|
|
111
|
+
if (Buffer.isBuffer(bookmark)) {
|
|
112
|
+
try {
|
|
113
|
+
path = decodeBookmarkPath(bookmark);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
warnings.push(`pinned: Finder Favorites bookmark decode failed for "${name}" (${(e as Error)?.message ?? e})`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (path) out.push({ name, path });
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Read Finder sidebar Favorites, appending any failure to the shared `warnings` array (never throws). */
|
|
126
|
+
export async function getMacosFinderFavoritesPinned(
|
|
127
|
+
homedir: string,
|
|
128
|
+
warnings: string[],
|
|
129
|
+
overrides: Partial<FinderFavoritesDeps> = {},
|
|
130
|
+
): Promise<PinnedFolder[]> {
|
|
131
|
+
const deps: FinderFavoritesDeps = { ...defaultDeps, ...overrides };
|
|
132
|
+
const dir = `${homedir}/Library/Application Support/com.apple.sharedfilelist`;
|
|
133
|
+
|
|
134
|
+
let file: string | null = null;
|
|
135
|
+
for (const ext of SFL_EXTENSIONS) {
|
|
136
|
+
const candidate = `${dir}/com.apple.LSSharedFileList.FavoriteItems.${ext}`;
|
|
137
|
+
if (await deps.fileExists(candidate)) {
|
|
138
|
+
file = candidate;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (!file) {
|
|
143
|
+
warnings.push(
|
|
144
|
+
"pinned: Finder Favorites file not found (.sfl2/.sfl3/.sfl4) — grant Full Disk Access to the terminal/ppm binary if it should exist",
|
|
145
|
+
);
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let xml: string;
|
|
150
|
+
try {
|
|
151
|
+
const res = await deps.run(["plutil", "-convert", "xml1", "-o", "-", file], 5000);
|
|
152
|
+
if (res.timedOut || res.code !== 0) {
|
|
153
|
+
warnings.push(
|
|
154
|
+
`pinned: Finder Favorites: plutil ${res.timedOut ? "timed out" : `exited ${res.code}`} — grant Full Disk Access to the terminal/ppm binary`,
|
|
155
|
+
);
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
xml = res.stdout;
|
|
159
|
+
} catch (e) {
|
|
160
|
+
warnings.push(`pinned: Finder Favorites plutil failed (${(e as Error)?.message ?? e})`);
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const plist = parsePlistXml(xml);
|
|
166
|
+
const objects = asRecord(plist)?.["$objects"];
|
|
167
|
+
if (!Array.isArray(objects)) throw new Error("no $objects array in NSKeyedArchiver container");
|
|
168
|
+
return extractFavorites(objects, warnings).map((f) => ({ ...f, source: "finder-favorites" as const }));
|
|
169
|
+
} catch (e) {
|
|
170
|
+
warnings.push(
|
|
171
|
+
`pinned: Finder Favorites parse failed (${(e as Error)?.message ?? e}) — reverse-engineered format, verify against a real macOS sample`,
|
|
172
|
+
);
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
}
|