@hienlh/ppm 0.18.14 → 0.18.16
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/.rdtest2-cf.out +0 -0
- package/CHANGELOG.md +26 -0
- package/CLAUDE.md +3 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +22 -4
- package/assets/skills/ppm/references/http-api.md +8 -1
- package/dist/web/assets/{adaptive-context-menu-DCZtgOL7.js → adaptive-context-menu-BbuRVTLn.js} +1 -1
- package/dist/web/assets/{ai-resource-editor-D5CPBg06.js → ai-resource-editor-Cm4n3VXt.js} +1 -1
- package/dist/web/assets/ai-settings-section-BEM1z7kp.js +1 -0
- package/dist/web/assets/{angular-html-CTvM4ulF.js → angular-html-BlcrLjUx.js} +1 -1
- package/dist/web/assets/{angular-ts-B_vSkjco.js → angular-ts-BUjtceAJ.js} +1 -1
- package/dist/web/assets/api-client-D1CePgQ4.js +1 -0
- package/dist/web/assets/api-settings-CNtKkHqW.js +1 -0
- package/dist/web/assets/{api-settings-B2iNXQ0c.js → api-settings-Cubuqjsu.js} +1 -1
- package/dist/web/assets/{api-themes-DrWnJbhs.js → api-themes-YsZgf6Xj.js} +1 -1
- package/dist/web/assets/{apl-DywzTry0.js → apl-B3g0v1pk.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-6konx-J1.js +1 -0
- package/dist/web/assets/{astro-1JaRgaSb.js → astro-FRnyn7cH.js} +1 -1
- package/dist/web/assets/{audio-preview-DAuGvOiK.js → audio-preview-DWTRm4HC.js} +1 -1
- package/dist/web/assets/{blade-CDeeJWBb.js → blade-NLdOqUKw.js} +1 -1
- package/dist/web/assets/{bsl-BfgoPVYR.js → bsl-CenHZfaU.js} +1 -1
- package/dist/web/assets/{button-tNy0ba3y.js → button-Cq8yixjf.js} +1 -1
- package/dist/web/assets/{cairo-C26gNwWW.js → cairo-B_WR0aiQ.js} +1 -1
- package/dist/web/assets/chat-tab-C2e6Pwc2.js +15 -0
- package/dist/web/assets/{cobol-D92nBnON.js → cobol-DOynhWYj.js} +1 -1
- package/dist/web/assets/code-editor-DWtv_VDo.js +10 -0
- package/dist/web/assets/{coffee-CIWH40R1.js → coffee-Bn6dbw4P.js} +1 -1
- package/dist/web/assets/{conflict-editor-CDp0I3mD.js → conflict-editor-Bi2RWfKQ.js} +1 -1
- package/dist/web/assets/{context-menu-BO1c9jKy.js → context-menu-DfWZAxQy.js} +1 -1
- package/dist/web/assets/{cpp-DOrBqXCn.js → cpp-CoZR9t8r.js} +1 -1
- package/dist/web/assets/{crystal-BNKqmHux.js → crystal-BSheONSg.js} +1 -1
- package/dist/web/assets/{csv-preview-D4svNDAq.js → csv-preview-DxGZ1qg5.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-BqV0AxZg.js → data-grid-overlay-editor-gwPoVSCU.js} +1 -1
- package/dist/web/assets/{database-viewer-BCvv_Bwf.js → database-viewer-DSPRzZny.js} +1 -1
- package/dist/web/assets/{dialog-DcLGDwaX.js → dialog-BSfuiTkJ.js} +1 -1
- package/dist/web/assets/{diff-viewer-D41fCFAi.js → diff-viewer-YbpnNv5Y.js} +1 -1
- package/dist/web/assets/{docx-preview-DfaQn9v7.js → docx-preview-DqVDOMmv.js} +1 -1
- package/dist/web/assets/{edge-Dbjp42Pt.js → edge-DTtoslQu.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-BqcUuFCc.js → edit-diff-preview-CXSbSUBX.js} +1 -1
- package/dist/web/assets/{elixir-na3b1pYy.js → elixir-CIBCZ3Zi.js} +1 -1
- package/dist/web/assets/{elm-_Nalil1R.js → elm-BLmb7K1H.js} +1 -1
- package/dist/web/assets/{erb-CW_7dubf.js → erb-BBY8ApRd.js} +1 -1
- package/dist/web/assets/{erlang-DmZ7Zzb0.js → erlang-D5Zb5_P0.js} +1 -1
- package/dist/web/assets/{esm-CwyBetO1.js → esm-6F8v1jLL.js} +1 -1
- package/dist/web/assets/explorer-body-DpQndBVw.js +2 -0
- package/dist/web/assets/explorer-window-content-m0K6Mtcg.js +1 -0
- package/dist/web/assets/{extension-store-DUaZINyC.js → extension-store-Bhu--fPX.js} +1 -1
- package/dist/web/assets/{extension-webview-BW_mccb3.js → extension-webview-BSh6KGd7.js} +1 -1
- package/dist/web/assets/{file-store-yReVbR8q.js → file-store-6HW2Lfl4.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-DB2A8L4-.js → fortran-fixed-form-CBdwkEUJ.js} +1 -1
- package/dist/web/assets/{fsharp-BYHrti7z.js → fsharp-AT4XdVYH.js} +1 -1
- package/dist/web/assets/{gdresource-D5GBin40.js → gdresource-D852fWBR.js} +1 -1
- package/dist/web/assets/{git-commit-CUiOBoIL.js → git-commit-DWfWAyxy.js} +1 -1
- package/dist/web/assets/{git-log-panel-C1QDkmgl.js → git-log-panel-CkTiR70w.js} +1 -1
- package/dist/web/assets/{git-rebase-DOxGnpdw.js → git-rebase-C6opk8gr.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-ikUwsekd.js +1 -0
- package/dist/web/assets/glide-data-grid-BB5mCaCD.js +138 -0
- package/dist/web/assets/{glimmer-js-Begy4UT7.js → glimmer-js-DLi5mWYO.js} +1 -1
- package/dist/web/assets/{glimmer-ts-rPApRY8N.js → glimmer-ts-D-scFX30.js} +1 -1
- package/dist/web/assets/{glsl-wrbPvrBL.js → glsl-dbQzF4iA.js} +1 -1
- package/dist/web/assets/{graphql-Cu4-NR__.js → graphql-D1m5Wen2.js} +1 -1
- package/dist/web/assets/group-chat-tab-BPIOdFJw.js +1 -0
- package/dist/web/assets/{hack-Ddo93XKD.js → hack-CqYmpO7u.js} +1 -1
- package/dist/web/assets/{haml-BFX_1i8j.js → haml-BXzKWEag.js} +1 -1
- package/dist/web/assets/hand-x0ejq3fS.js +1 -0
- package/dist/web/assets/{handlebars-5gFKA4H5.js → handlebars-DXnrtHtq.js} +1 -1
- package/dist/web/assets/{html-C06I-zeO.js → html-DhgQJ-xL.js} +1 -1
- package/dist/web/assets/{html-derivative-e5JLAFZF.js → html-derivative-xZRft-YE.js} +1 -1
- package/dist/web/assets/{http-CrAAhSU7.js → http-B53mSj14.js} +1 -1
- package/dist/web/assets/{hurl-T1V3SX9T.js → hurl-CsomVy04.js} +1 -1
- package/dist/web/assets/{hxml-B056LLKN.js → hxml-LffjWwKA.js} +1 -1
- package/dist/web/assets/{image-preview-DWyuCYT3.js → image-preview-CNA6XMNS.js} +1 -1
- package/dist/web/assets/index-BHdtSKzb.js +52 -0
- package/dist/web/assets/index-DN9XUQ8D.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-BAgmboBp.js +1 -0
- package/dist/web/assets/{input-DouJ9Zvt.js → input-D0ol5jW0.js} +1 -1
- package/dist/web/assets/{jinja-DGdtRh43.js → jinja-CCHURl7V.js} +1 -1
- package/dist/web/assets/{jison-Ah6QobPS.js → jison-7qBd8dnR.js} +1 -1
- package/dist/web/assets/{julia-BJeGOK0K.js → julia-BY9pnTcy.js} +1 -1
- package/dist/web/assets/{just-iw9oRs7j.js → just-D7wRSNRr.js} +1 -1
- package/dist/web/assets/{keybindings-store-0Wqfa7Qp.js → keybindings-store-BGkB72gM.js} +1 -1
- package/dist/web/assets/keybindings-store-Bq6VWl5X.js +1 -0
- package/dist/web/assets/keyboard-DBBaoQSu.js +1 -0
- package/dist/web/assets/{latex-DHjj5hyN.js → latex-RgsCae55.js} +1 -1
- package/dist/web/assets/{liquid-B-FFecDc.js → liquid-CnDFzvOZ.js} +1 -1
- package/dist/web/assets/{lua-DDianJp_.js → lua-0lj5W8sm.js} +1 -1
- package/dist/web/assets/{markdown-renderer-BHiUhQN8.js → markdown-renderer-B9wzBWm9.js} +3 -3
- package/dist/web/assets/markdown-renderer-DeSV1CCQ.js +1 -0
- package/dist/web/assets/{marko-BW5SBVng.js → marko-CQgqAhbV.js} +1 -1
- package/dist/web/assets/{mdc-B9gL9bjl.js → mdc-BkIcTiIy.js} +1 -1
- package/dist/web/assets/{media-url-DUK-k0bB.js → media-url-DDciwvLU.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet-pjcHKorA.js +1 -0
- package/dist/web/assets/mobile-explorer-sheet-DPdxpnKh.js +1 -0
- package/dist/web/assets/monitor-smartphone-DLZ6Q05s.js +1 -0
- package/dist/web/assets/{nextflow-dTluY6qy.js → nextflow-DvIW68DS.js} +1 -1
- package/dist/web/assets/{nginx-DlwghGI-.js → nginx-O-hft0gG.js} +1 -1
- package/dist/web/assets/{nim-N9jddb5D.js → nim-BxrR4HwQ.js} +1 -1
- package/dist/web/assets/notification-store-BtijuGez.js +1 -0
- package/dist/web/assets/{number-overlay-editor-BVXM7aAs.js → number-overlay-editor-BoLRYuZT.js} +1 -1
- package/dist/web/assets/{overview-panel-DCCDmtCQ.js → overview-panel-l3g1gT5e.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-RVlbbDRS.js +1 -0
- package/dist/web/assets/{panel-store-B4rijFKN.js → panel-store-UOV8wrfR.js} +1 -1
- package/dist/web/assets/{pdf-preview-D7yLmAZW.js → pdf-preview-B2CJ_EKv.js} +1 -1
- package/dist/web/assets/{perl-CkmpYKa_.js → perl-Da-uNqmL.js} +1 -1
- package/dist/web/assets/{php-BwZ3KDPo.js → php-JMu45mEx.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-ZWb4izpk.js +1 -0
- package/dist/web/assets/{postgres-viewer--CRN1fvO.js → postgres-viewer-23xdZYxp.js} +3 -3
- package/dist/web/assets/process-table-CUsPf7wv.js +1 -0
- package/dist/web/assets/{project-store-BuY7rxn_.js → project-store-DTUCS65m.js} +1 -1
- package/dist/web/assets/{pug-B3rBPqQo.js → pug-CLzyQhia.js} +1 -1
- package/dist/web/assets/{qml-BfDbWFmg.js → qml-BHXfGB1l.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-bsP5x1Bx.js +1 -0
- package/dist/web/assets/{razor-B1ww1r38.js → razor-BqetJLN3.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-FTF6IBhF.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-view-DSwG4OSk.js +1 -0
- package/dist/web/assets/remote-desktop-stats-overlay-Bz6xZ5y9.js +1 -0
- package/dist/web/assets/remote-desktop-warning-gate-DrYySLZ5.js +1 -0
- package/dist/web/assets/remote-desktop-window-content-Ndvezorb.js +1 -0
- package/dist/web/assets/{rst-C0MSq6m-.js → rst-iQdJZ0BP.js} +1 -1
- package/dist/web/assets/{ruby-ClIZ1wb3.js → ruby-CNwzZGdH.js} +1 -1
- package/dist/web/assets/{run-in-terminal-4efbW2Xv.js → run-in-terminal-CBXmVGWa.js} +1 -1
- package/dist/web/assets/{sas-DR0j3o4_.js → sas-BpBIT2eR.js} +1 -1
- package/dist/web/assets/{scroll-area-ZP8wcwTI.js → scroll-area-lc0ZHORr.js} +1 -1
- package/dist/web/assets/{scss-Vh3JLayR.js → scss-D1MotuEp.js} +1 -1
- package/dist/web/assets/{send-to-chat-BNrEpAyC.js → send-to-chat-DJAdlvJ8.js} +1 -1
- package/dist/web/assets/settings-store-zbPkG7o-.js +2 -0
- package/dist/web/assets/settings-tab-Cn-AqUP_.js +1 -0
- package/dist/web/assets/settings-tab-xyRpt2gq.js +3 -0
- package/dist/web/assets/{shaderlab-0rHRnQ18.js → shaderlab-BNqmcPQi.js} +1 -1
- package/dist/web/assets/{shellsession-Dxdw6D8c.js → shellsession-BBLOVOO-.js} +1 -1
- package/dist/web/assets/{soy-BHlSNj1y.js → soy-BpTGo9uy.js} +1 -1
- package/dist/web/assets/{sparql-B2QaPpni.js → sparql-D7A3yKob.js} +1 -1
- package/dist/web/assets/{sql-query-editor-R_iKjPP9.js → sql-query-editor-BE3mLYyz.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-b2onA0SP.js → sqlite-viewer-wN5RP8sd.js} +1 -1
- package/dist/web/assets/{stata-CwIxjsEJ.js → stata-B7N56ptS.js} +1 -1
- package/dist/web/assets/{surrealql-C6vo-rp-.js → surrealql-CTmnD-lv.js} +1 -1
- package/dist/web/assets/{svelte-LLdJ6rlH.js → svelte-BppwY9TR.js} +1 -1
- package/dist/web/assets/{switch-DV-BDjyk.js → switch-BmjKoIpR.js} +1 -1
- package/dist/web/assets/system-monitor-body-B2M0MC6y.js +2 -0
- package/dist/web/assets/system-monitor-tab-OYca4qaf.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-BPif0azo.js → system-monitor-window-content-CHz9WdTm.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-BnAN6m4K.js → tab-host-window-content-DKev3GJz.js} +1 -1
- package/dist/web/assets/{tab-store-tFnyPmm4.js → tab-store-DzYSsw5a.js} +1 -1
- package/dist/web/assets/team-member-sheet-BQ738Lli.js +2 -0
- package/dist/web/assets/{team-member-window-content-BSyU-5Uc.js → team-member-window-content-CK08f5cK.js} +1 -1
- package/dist/web/assets/{templ-CJGvhEP5.js → templ-CvTMKKra.js} +1 -1
- package/dist/web/assets/{terminal-tab-Ncor0Rfu.js → terminal-tab-DZ3QiYcs.js} +3 -3
- package/dist/web/assets/{tex-psXWhc4t.js → tex--c4POEUE.js} +1 -1
- package/dist/web/assets/{tool-cards-DdVP1wrD.js → tool-cards-Bw1P0MKU.js} +4 -4
- package/dist/web/assets/treemap-KZPCXAKY-Dh1oxNdQ.js +1 -0
- package/dist/web/assets/{ts-tags-D5Ar_ZCO.js → ts-tags--kx7D-Hw.js} +1 -1
- package/dist/web/assets/{twig-vJgGC-qY.js → twig-UG9uMHGd.js} +1 -1
- package/dist/web/assets/{use-blob-url-BOVGWg9F.js → use-blob-url-Bdxopwwc.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-BshnLXoY.js → use-monaco-theme-BOmd2wpe.js} +1 -1
- package/dist/web/assets/{use-open-team-member-CY3wOdWL.js → use-open-team-member-D9EOB8-i.js} +1 -1
- package/dist/web/assets/use-visual-viewport-Sfb6CZ80.js +1 -0
- package/dist/web/assets/{use-websocket-B7xjNsyD.js → use-websocket-B7Xj-Q4W.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-999ksD45.js → vendor-mermaid-0Nt-ew76.js} +1 -1
- package/dist/web/assets/{video-preview-7F72sRwf.js → video-preview-B0nbTGjQ.js} +1 -1
- package/dist/web/assets/{vue-html-BN5ZLu-H.js → vue-html-8KEW6-I9.js} +1 -1
- package/dist/web/assets/{vue-BWngFyav.js → vue-jjTomlbt.js} +1 -1
- package/dist/web/assets/{vue-vine-BZHnO8yV.js → vue-vine-C5tohCkU.js} +1 -1
- package/dist/web/assets/{window-pip-registry-BnYU83LE.js → window-pip-registry-FdZdGF7h.js} +1 -1
- package/dist/web/assets/window-store-D0x_fxr0.js +1 -0
- package/dist/web/assets/{xml-DS2beZHp.js → xml-ByFFosqL.js} +1 -1
- package/dist/web/assets/{xsl-Ciiku1Bq.js → xsl-CZUwiLkj.js} +1 -1
- package/dist/web/index.html +46 -43
- package/dist/web/sw.js +1 -1
- package/docs/architecture/ai-chat-and-providers.md +3 -0
- package/package.json +1 -1
- package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
- package/src/cli/commands/backup-cmd.ts +119 -0
- package/src/index.ts +3 -0
- package/src/providers/claude-agent-sdk.ts +29 -1
- package/src/server/index.ts +42 -15
- package/src/server/routes/accounts.ts +30 -2
- package/src/server/routes/chat.ts +8 -1
- package/src/server/routes/named-tunnel.ts +5 -2
- package/src/server/routes/remote-desktop.ts +64 -0
- package/src/server/routes/settings.ts +2 -0
- package/src/server/ws/chat.ts +126 -7
- package/src/server/ws/remote-desktop.ts +86 -0
- package/src/services/account.service.ts +106 -23
- package/src/services/db-backup/db-backup-paths.ts +66 -0
- package/src/services/db-backup/db-backup-restore.ts +105 -0
- package/src/services/db-backup/db-backup-retention.ts +96 -0
- package/src/services/db-backup/db-backup-sync.ts +113 -0
- package/src/services/db-backup/db-backup.service.ts +61 -0
- package/src/services/db.service.ts +29 -1
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/fs-credential-path-guard.ts +19 -1
- package/src/services/named-tunnel/cloudflare-dns-api.ts +20 -4
- package/src/services/named-tunnel/cloudflared-login.service.ts +2 -2
- package/src/services/named-tunnel/named-tunnel-args.ts +17 -3
- package/src/services/named-tunnel/named-tunnel-setup.service.ts +20 -2
- package/src/services/nested-subagent-spy.ts +206 -0
- package/src/services/remote-desktop/access-unit-assembler.ts +90 -0
- package/src/services/remote-desktop/avc1-codec-string.ts +18 -0
- package/src/services/remote-desktop/nal-splitter.ts +100 -0
- package/src/services/remote-desktop/remote-desktop-capture.ts +120 -0
- package/src/services/remote-desktop/remote-desktop-encoder-args.ts +42 -0
- package/src/services/remote-desktop/remote-desktop-flag.ts +12 -0
- package/src/services/remote-desktop/remote-desktop-input.ts +217 -0
- package/src/services/remote-desktop/remote-desktop-nonce.ts +38 -0
- package/src/services/remote-desktop/remote-desktop-session.ts +189 -0
- package/src/services/remote-desktop/remote-desktop-vk-map.ts +41 -0
- package/src/services/subagent-transcript-merger.ts +92 -75
- package/src/services/subprocess-retention.ts +29 -6
- package/src/services/supervisor.ts +58 -0
- package/src/services/team-member-activity/subagent-transcript-index.ts +56 -0
- package/src/services/windows-process-tree.ts +351 -351
- package/src/types/chat.ts +2 -0
- package/src/web/app.tsx +8 -0
- package/src/web/components/chat/account-dialogs.tsx +11 -4
- package/src/web/components/chat/question-card.tsx +9 -2
- package/src/web/components/chat/usage-badge.tsx +17 -3
- package/src/web/components/floating-window/window-content-registry.ts +2 -0
- package/src/web/components/floating-window/window-store-types.ts +1 -1
- package/src/web/components/layout/mobile-drawer.tsx +38 -27
- package/src/web/components/layout/nav-section-rail.tsx +10 -2
- package/src/web/components/remote-desktop/open-remote-desktop.ts +26 -0
- package/src/web/components/remote-desktop/remote-desktop-coords.ts +98 -0
- package/src/web/components/remote-desktop/remote-desktop-frame-timestamp.ts +21 -0
- package/src/web/components/remote-desktop/remote-desktop-gesture-classifiers.ts +56 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +131 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-sheet.tsx +48 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +79 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +190 -0
- package/src/web/components/remote-desktop/remote-desktop-stats-overlay.tsx +54 -0
- package/src/web/components/remote-desktop/remote-desktop-stats.ts +37 -0
- package/src/web/components/remote-desktop/remote-desktop-touch-gesture-tracker.ts +195 -0
- package/src/web/components/remote-desktop/remote-desktop-warning-gate.tsx +106 -0
- package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +86 -0
- package/src/web/components/remote-desktop/remote-desktop-ws-url.ts +22 -0
- package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +175 -0
- package/src/web/components/remote-desktop/use-remote-desktop-available.ts +29 -0
- package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +147 -0
- package/src/web/components/remote-desktop/use-remote-desktop-mobile-open-state.ts +18 -0
- package/src/web/components/remote-desktop/use-remote-desktop-touch.ts +224 -0
- package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +50 -0
- package/src/web/components/remote-desktop/use-remote-input-capture.ts +92 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-copy.ts +59 -59
- package/src/web/components/tunnels/named-tunnel/named-tunnel-section.tsx +2 -2
- package/src/web/hooks/use-notification-badge.ts +19 -11
- package/src/web/lib/feature-badges.ts +2 -0
- package/src/web/stores/settings-store.ts +25 -0
- package/src/web/stores/streaming-store.ts +21 -2
- package/dist/web/assets/ai-settings-section-DY-t1bK8.js +0 -1
- package/dist/web/assets/api-client-BogqsFna.js +0 -1
- package/dist/web/assets/api-settings-DRF9oYkn.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-nw5-j_I_.js +0 -1
- package/dist/web/assets/chat-tab-IcnU3nqQ.js +0 -15
- package/dist/web/assets/code-editor-Dno5Srkm.js +0 -10
- package/dist/web/assets/explorer-body-DB1L30pa.js +0 -2
- package/dist/web/assets/explorer-window-content-Cwlt3C3I.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-C1xf87gN.js +0 -1
- package/dist/web/assets/glide-data-grid-Dz8lx6n2.js +0 -138
- package/dist/web/assets/group-chat-tab-DFwr-pig.js +0 -1
- package/dist/web/assets/index-DWpNR3nB.css +0 -2
- package/dist/web/assets/index-oUKV5nIA.js +0 -52
- package/dist/web/assets/info-3K5VOQVL-C2z7FTDe.js +0 -1
- package/dist/web/assets/keybindings-store-d54pv2NK.js +0 -1
- package/dist/web/assets/markdown-renderer-CnE36_gd.js +0 -1
- package/dist/web/assets/mobile-bottom-sheet-CJ7ApOkI.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-BzjlS1Xn.js +0 -1
- package/dist/web/assets/notification-store-C7UBqxYP.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-DQUxN0wU.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-BOr9ek4Q.js +0 -1
- package/dist/web/assets/process-table-CXC10GSy.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-DSErxZFb.js +0 -1
- package/dist/web/assets/settings-store-OLJE7dPH.js +0 -2
- package/dist/web/assets/settings-tab-B1rvHFc6.js +0 -3
- package/dist/web/assets/settings-tab-BUlmPJ2w.js +0 -1
- package/dist/web/assets/system-monitor-body-DPYQlBEt.js +0 -2
- package/dist/web/assets/system-monitor-tab-CPQdWv27.js +0 -1
- package/dist/web/assets/team-member-sheet-Bu2wG1sm.js +0 -2
- package/dist/web/assets/treemap-KZPCXAKY-C17p0LQw.js +0 -1
- package/dist/web/assets/window-store-CQ1lghvv.js +0 -1
- /package/dist/web/assets/{abap-CjzrhAS2.js → abap-C5DFF0s3.js} +0 -0
- /package/dist/web/assets/{actionscript-3-B59Kbl-9.js → actionscript-3-B0qyRNbc.js} +0 -0
- /package/dist/web/assets/{ada-B7gItKAE.js → ada-bAJzplug.js} +0 -0
- /package/dist/web/assets/{andromeeda-Dy5zcy0Q.js → andromeeda-DUCpISy6.js} +0 -0
- /package/dist/web/assets/{apache-B6T9jrc0.js → apache-BhAr84Z_.js} +0 -0
- /package/dist/web/assets/{apex-CTsIxDQw.js → apex-CRv8YnNq.js} +0 -0
- /package/dist/web/assets/{api-client-oY1tC3tM.js → api-client-BzWM7XPR.js} +0 -0
- /package/dist/web/assets/{applescript-DuycKmmb.js → applescript-CD-Cw8bt.js} +0 -0
- /package/dist/web/assets/{apply-theme-r8tKDrCR.js → apply-theme-DJ794zSQ.js} +0 -0
- /package/dist/web/assets/{ara-CISL3599.js → ara-BUzeQYoG.js} +0 -0
- /package/dist/web/assets/{asciidoc-BXyE2Lpr.js → asciidoc-CT2hTg-v.js} +0 -0
- /package/dist/web/assets/{asm-y258phtP.js → asm-CAu35JeY.js} +0 -0
- /package/dist/web/assets/{aurora-x-Dq3Jc78M.js → aurora-x-BeFJoIA1.js} +0 -0
- /package/dist/web/assets/{awk-DxL6XSJv.js → awk-Vau-DrGN.js} +0 -0
- /package/dist/web/assets/{ayu-dark-DjoK6Evo.js → ayu-dark-CLe-OTsQ.js} +0 -0
- /package/dist/web/assets/{ayu-light-CKKXBLMI.js → ayu-light-CJbQfu4M.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-DGXSrrXB.js → ayu-mirage-GWaKwdrx.js} +0 -0
- /package/dist/web/assets/{ballerina-DZSXFmUf.js → ballerina-DWu0USI5.js} +0 -0
- /package/dist/web/assets/{bat-BNsZk6nY.js → bat-_07Huw7E.js} +0 -0
- /package/dist/web/assets/{beancount-cPl4gJct.js → beancount-EcvogrcF.js} +0 -0
- /package/dist/web/assets/{berry-DGAodERk.js → berry-BBB87SY7.js} +0 -0
- /package/dist/web/assets/{bibtex-DftVoRif.js → bibtex-DbueS87A.js} +0 -0
- /package/dist/web/assets/{bicep-Suioyyzh.js → bicep-tcmXnUo4.js} +0 -0
- /package/dist/web/assets/{bird2-DjU24I2P.js → bird2-B2qwilIR.js} +0 -0
- /package/dist/web/assets/{c-DFi8Y_nN.js → c-Z58oivSt.js} +0 -0
- /package/dist/web/assets/{c3-1eSHnH5V.js → c3-U40cGT9p.js} +0 -0
- /package/dist/web/assets/{cadence-DLLsFwQP.js → cadence-BFcD-LyM.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-in727eFQ.js → can-open-in-ppm-CdCK2c2a.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-DDj0JEiY.js → catppuccin-frappe-RkC9AX6J.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-DPyANHyR.js → catppuccin-latte-Bsi4l-nO.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-CO-h8VTT.js → catppuccin-macchiato-B8JIdTW-.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-BCmbN2IZ.js → catppuccin-mocha-oH8wwrDj.js} +0 -0
- /package/dist/web/assets/{clarity-C0pYsZjs.js → clarity-C3WNeRCR.js} +0 -0
- /package/dist/web/assets/{clipboard-DBLGKMrH.js → clipboard-B-gwJ8DF.js} +0 -0
- /package/dist/web/assets/{clojure-JFr6s8iZ.js → clojure-CFoAz7u9.js} +0 -0
- /package/dist/web/assets/{cmake-EEcLEgVy.js → cmake-Bcsqp9yR.js} +0 -0
- /package/dist/web/assets/{codeowners-Deh3Lx0u.js → codeowners-DC2Z7heP.js} +0 -0
- /package/dist/web/assets/{codeql-C317igT5.js → codeql-BfG68Al5.js} +0 -0
- /package/dist/web/assets/{common-lisp-DepbATZs.js → common-lisp-Z5P-lKQe.js} +0 -0
- /package/dist/web/assets/{coq-MxgxU5ya.js → coq-BJslqrnv.js} +0 -0
- /package/dist/web/assets/{csharp-eEFXdsAq.js → csharp-DhhZTOTs.js} +0 -0
- /package/dist/web/assets/{css-Db-syWW9.js → css-DiFIpMfE.js} +0 -0
- /package/dist/web/assets/{csv-DHeYcbaq.js → csv-DViLbHxI.js} +0 -0
- /package/dist/web/assets/{csv-parser-_JWMLGl3.js → csv-parser-B2v8MoUy.js} +0 -0
- /package/dist/web/assets/{cue-BmUNowrt.js → cue-CoG3mg2I.js} +0 -0
- /package/dist/web/assets/{cypher-XjXHgCNS.js → cypher-jTxC8NAB.js} +0 -0
- /package/dist/web/assets/{d-BWJnZtig.js → d-6hKIUAbU.js} +0 -0
- /package/dist/web/assets/{dark-plus-Db7umas1.js → dark-plus-D_-np2Sr.js} +0 -0
- /package/dist/web/assets/{dart-DSI4HYh1.js → dart-CVpxLpVn.js} +0 -0
- /package/dist/web/assets/{data-grid-types-CfVHh9gv.js → data-grid-types-Dq_H1IPa.js} +0 -0
- /package/dist/web/assets/{dax-yw1JsAni.js → dax-BPqJ8qIY.js} +0 -0
- /package/dist/web/assets/{desktop-C2QO4RvD.js → desktop-u_y5_Hr5.js} +0 -0
- /package/dist/web/assets/{diff-Drt8aEyp.js → diff-Duj1XMrd.js} +0 -0
- /package/dist/web/assets/{dist-DNRvhVdz.js → dist-CEZ4plgH.js} +0 -0
- /package/dist/web/assets/{dist-VBxOHLmN.js → dist-aDSxTR_v.js} +0 -0
- /package/dist/web/assets/{docker-ByDKbmLl.js → docker-8UGSCc7N.js} +0 -0
- /package/dist/web/assets/{dotenv-BkfOLytR.js → dotenv-DVnj8kyR.js} +0 -0
- /package/dist/web/assets/{dracula-DAmrm-Un.js → dracula-D0cEmSg7.js} +0 -0
- /package/dist/web/assets/{dracula-soft-BUOEnKqR.js → dracula-soft-DfGS5Yfr.js} +0 -0
- /package/dist/web/assets/{dream-maker-DQBIfSiO.js → dream-maker-qG8kMGTF.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-Blh5kBD3.js → emacs-lisp-iVqyMLyT.js} +0 -0
- /package/dist/web/assets/{everforest-dark-CEzhDxHq.js → everforest-dark-Cruygd5U.js} +0 -0
- /package/dist/web/assets/{everforest-light-CPk_5Khm.js → everforest-light-BiJa8RHL.js} +0 -0
- /package/dist/web/assets/{fennel-B1MT_EBZ.js → fennel-H6XSEIoz.js} +0 -0
- /package/dist/web/assets/{fish-C_HGcewt.js → fish-ho_7CxX6.js} +0 -0
- /package/dist/web/assets/{fluent-Dp1Iclmx.js → fluent-1l5ssJTF.js} +0 -0
- /package/dist/web/assets/{format-bytes-53WBtyi2.js → format-bytes-gQw5wzqa.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-B_-UA4XC.js → fortran-free-form-Bo1x233A.js} +0 -0
- /package/dist/web/assets/{gdscript-Bm_JpjS_.js → gdscript-DffFMp_k.js} +0 -0
- /package/dist/web/assets/{gdshader-C_PNmfYU.js → gdshader-CvnpAiAF.js} +0 -0
- /package/dist/web/assets/{genie-B5rmR2TF.js → genie-2eOvAEzW.js} +0 -0
- /package/dist/web/assets/{gherkin-BTJ34TJ9.js → gherkin-DL-5OUlV.js} +0 -0
- /package/dist/web/assets/{github-dark-DOkNVj9H.js → github-dark-DrTZprgG.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DTsmiyVo.js → github-dark-default-_mDH8IMP.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-CjbxAe9s.js → github-dark-dimmed-C48hJuLn.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-r_M8F_k7.js → github-dark-high-contrast-BQ-zGGNe.js} +0 -0
- /package/dist/web/assets/{github-light-BzfxyMyb.js → github-light-BYc90Yx8.js} +0 -0
- /package/dist/web/assets/{github-light-default-BOqiBIy5.js → github-light-default-Daecj4J5.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-DQzBrDM4.js → github-light-high-contrast-DoafCbsU.js} +0 -0
- /package/dist/web/assets/{gleam-BWmuAtD8.js → gleam-hUWWjmnp.js} +0 -0
- /package/dist/web/assets/{gn-CNQPjiZW.js → gn-7PJ3R70o.js} +0 -0
- /package/dist/web/assets/{gnuplot-BlHjcPMe.js → gnuplot-V-Pxck7b.js} +0 -0
- /package/dist/web/assets/{go-Bn8Ba9uz.js → go-ARF6DprD.js} +0 -0
- /package/dist/web/assets/{groovy-C1Y1_F4V.js → groovy-CKGBIPOz.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-DueOsHav.js → gruvbox-dark-hard-Cm9wqdI3.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-DaQjD538.js → gruvbox-dark-medium-Cmsd6dG8.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-CbbX2WOm.js → gruvbox-dark-soft-BBGlGGCz.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-E2M70ljZ.js → gruvbox-light-hard-BCR7KmqG.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-B_DDLY6h.js → gruvbox-light-medium-CP32bga8.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-2SZmyC1F.js → gruvbox-light-soft-qjxk8ps6.js} +0 -0
- /package/dist/web/assets/{haskell-D8DeoXtG.js → haskell-C5DzwGRa.js} +0 -0
- /package/dist/web/assets/{haxe-BmZm7B0W.js → haxe-C8f_jCVv.js} +0 -0
- /package/dist/web/assets/{hcl-CKnBM3ng.js → hcl-DetJkf6E.js} +0 -0
- /package/dist/web/assets/{hjson-Ct9t4-ci.js → hjson-DeZhwIdo.js} +0 -0
- /package/dist/web/assets/{hlsl-BZSASBqS.js → hlsl-SMIDLRyX.js} +0 -0
- /package/dist/web/assets/{horizon-CzX1AZ08.js → horizon-BMAVDxMx.js} +0 -0
- /package/dist/web/assets/{horizon-bright-CCVNeRIP.js → horizon-bright-3BaV_VGD.js} +0 -0
- /package/dist/web/assets/{houston-DBlJN7xc.js → houston-BWBbEPvb.js} +0 -0
- /package/dist/web/assets/{hy-Dyc6h6Xb.js → hy-DcsmE-Nm.js} +0 -0
- /package/dist/web/assets/{image-gallery-DujlrjOC.js → image-gallery-Bqb-wjNw.js} +0 -0
- /package/dist/web/assets/{imba-Bauru7s2.js → imba-CnkgnYMf.js} +0 -0
- /package/dist/web/assets/{ini-SjdwtaTp.js → ini-BnvTdau7.js} +0 -0
- /package/dist/web/assets/{java-D2r0ocpf.js → java-BLFACXat.js} +0 -0
- /package/dist/web/assets/{javascript-y-lof7iP.js → javascript-BKcFWENk.js} +0 -0
- /package/dist/web/assets/{json-DO4LSx5A.js → json-3AJtJADf.js} +0 -0
- /package/dist/web/assets/{json5-BEwWf-W3.js → json5-DLyDVlwe.js} +0 -0
- /package/dist/web/assets/{jsonc-OBEFv7gP.js → jsonc-B5DgF1jW.js} +0 -0
- /package/dist/web/assets/{jsonl-DOyW4Mg3.js → jsonl-CV_O71a2.js} +0 -0
- /package/dist/web/assets/{jsonnet-BL5R2CpP.js → jsonnet-DZfPLgYz.js} +0 -0
- /package/dist/web/assets/{jssm-ksPusAyg.js → jssm-Cr1zYOsY.js} +0 -0
- /package/dist/web/assets/{jsx-B6XEu6O-.js → jsx-VN-JS2_q.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-JBVe_ji_.js → kanagawa-dragon-D9CcT3xg.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-ChprO68_.js → kanagawa-lotus-DLXsB8fr.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-D_uB_R0Q.js → kanagawa-wave-weDs3Wjz.js} +0 -0
- /package/dist/web/assets/{kdl-pf5B8N_m.js → kdl-0XF56jnT.js} +0 -0
- /package/dist/web/assets/{kotlin-BdF0METC.js → kotlin-BPkH9vzk.js} +0 -0
- /package/dist/web/assets/{kusto-BneVP-OU.js → kusto-DMVpADb9.js} +0 -0
- /package/dist/web/assets/{laserwave-C4-UPIyG.js → laserwave-Cl6T2cGP.js} +0 -0
- /package/dist/web/assets/{lean-BN6Jdymq.js → lean-BvwzQGl1.js} +0 -0
- /package/dist/web/assets/{less-Cx-fdlDC.js → less-DI_03xkC.js} +0 -0
- /package/dist/web/assets/{lib-CMuBa85c.js → lib-v9x_qXgI.js} +0 -0
- /package/dist/web/assets/{libesm-BB74PRsH.js → libesm-dcbw4_18.js} +0 -0
- /package/dist/web/assets/{light-plus-DFnnn_iL.js → light-plus-hIiIsQN_.js} +0 -0
- /package/dist/web/assets/{llvm-BGxtW0Nw.js → llvm-B5hjjSWc.js} +0 -0
- /package/dist/web/assets/{loader-circle-Dpqcv0eS.js → loader-circle-DouKgIXy.js} +0 -0
- /package/dist/web/assets/{lock-DldbT31x.js → lock-CNxUGpTk.js} +0 -0
- /package/dist/web/assets/{log-kQb4eJnj.js → log-D28raQiE.js} +0 -0
- /package/dist/web/assets/{logo-DqAzaq7s.js → logo-6Cdd4ASb.js} +0 -0
- /package/dist/web/assets/{luau-Dc2cp-zS.js → luau-CK9nKpOX.js} +0 -0
- /package/dist/web/assets/{make-DYg9dwaJ.js → make-CVnxKCB6.js} +0 -0
- /package/dist/web/assets/{markdown-O4EPpS3K.js → markdown-B9mMQzD5.js} +0 -0
- /package/dist/web/assets/{material-theme-ClP8yfQ2.js → material-theme-BZSs2o0j.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-LOK55hGu.js → material-theme-darker-BeAQBzQ_.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-j72tzZqi.js → material-theme-lighter-BAe0IXcs.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-C19LKClA.js → material-theme-ocean-CiPIf0Np.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-DJ5tN2ax.js → material-theme-palenight-DJBb4_XN.js} +0 -0
- /package/dist/web/assets/{matlab-DQNZ_NU8.js → matlab-CY0dbp_s.js} +0 -0
- /package/dist/web/assets/{mdx-CJayB7XK.js → mdx-DU9zbF7h.js} +0 -0
- /package/dist/web/assets/{mermaid-Cb0TRX92.js → mermaid-UmmjGwdh.js} +0 -0
- /package/dist/web/assets/{min-dark-CWM8Bo-m.js → min-dark-C59GkzOm.js} +0 -0
- /package/dist/web/assets/{min-light-BBR1sjS2.js → min-light-B9QglrdQ.js} +0 -0
- /package/dist/web/assets/{mipsasm-Jc7u7YSS.js → mipsasm-Dc6Jsr_D.js} +0 -0
- /package/dist/web/assets/{mojo-CTB0cutd.js → mojo-D5ij-nGW.js} +0 -0
- /package/dist/web/assets/{monitor-4vWfqzuN.js → monitor-CxPV_fQI.js} +0 -0
- /package/dist/web/assets/{monokai-CvM_YNKx.js → monokai-QAtE_yCP.js} +0 -0
- /package/dist/web/assets/{moonbit-CbFEfYYM.js → moonbit-BMtIdiTL.js} +0 -0
- /package/dist/web/assets/{move-BT38p0kT.js → move-BB_jMHYZ.js} +0 -0
- /package/dist/web/assets/{narrat-55QZ_kOG.js → narrat-BvvoKGVn.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-Bg8peyqg.js → nextflow-groovy-CqxtAsnF.js} +0 -0
- /package/dist/web/assets/{night-owl-DzS77iPK.js → night-owl-CLoE9kjt.js} +0 -0
- /package/dist/web/assets/{night-owl-light-DER7fGnx.js → night-owl-light-BwIFT5Xd.js} +0 -0
- /package/dist/web/assets/{nix-BSI8YesN.js → nix-BKlnVT9r.js} +0 -0
- /package/dist/web/assets/{nord-9bn7iNWo.js → nord-B7R1KRAK.js} +0 -0
- /package/dist/web/assets/{nushell-qDVZn_lw.js → nushell-CzUme-NJ.js} +0 -0
- /package/dist/web/assets/{objective-c-Blf-ATos.js → objective-c-D-UKKiOc.js} +0 -0
- /package/dist/web/assets/{objective-cpp-Bz8bAq47.js → objective-cpp-BucWn4Fx.js} +0 -0
- /package/dist/web/assets/{ocaml-B9heAsc3.js → ocaml-EJCNXMGo.js} +0 -0
- /package/dist/web/assets/{odin-BaFYnOwO.js → odin-CEjnRU_3.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-D6B_hSe-.js → one-dark-pro-Bv469NX5.js} +0 -0
- /package/dist/web/assets/{one-light-Ba-f0wyk.js → one-light-BlzO5RWI.js} +0 -0
- /package/dist/web/assets/{openscad-UvYyL2MK.js → openscad-Czl6alLw.js} +0 -0
- /package/dist/web/assets/{pascal-B4-qvEoG.js → pascal-BI9fLvG4.js} +0 -0
- /package/dist/web/assets/{pkl-B146nqGx.js → pkl-ChQLsyjU.js} +0 -0
- /package/dist/web/assets/{plastic-L38t-Id3.js → plastic-DGL3mtdL.js} +0 -0
- /package/dist/web/assets/{play-CpCAoQRZ.js → play-DpEEBJTT.js} +0 -0
- /package/dist/web/assets/{plsql-ItOG5Cca.js → plsql-BJDjqcWp.js} +0 -0
- /package/dist/web/assets/{po-imiCTaNX.js → po-DK5O3OpF.js} +0 -0
- /package/dist/web/assets/{poimandres-Bu6sIba9.js → poimandres-CnuqZT4T.js} +0 -0
- /package/dist/web/assets/{polar-CTeGoFxV.js → polar-DCsUxSsQ.js} +0 -0
- /package/dist/web/assets/{portal-container-context-a399Hd9F.js → portal-container-context-D-495m5C.js} +0 -0
- /package/dist/web/assets/{postcss-jvFe6GNE.js → postcss-8UjdFmis.js} +0 -0
- /package/dist/web/assets/{powerquery-D9Dw1hJp.js → powerquery-CAp0CGQj.js} +0 -0
- /package/dist/web/assets/{powershell-CZqvYQtg.js → powershell-CbBsKZJP.js} +0 -0
- /package/dist/web/assets/{prisma-B4D2nO_7.js → prisma-BnNpXCJN.js} +0 -0
- /package/dist/web/assets/{prolog-CQbyUcCF.js → prolog-CC-l9E8Y.js} +0 -0
- /package/dist/web/assets/{proto-Cwpo0cW1.js → proto-CV_1k2rL.js} +0 -0
- /package/dist/web/assets/{puppet-CKxCwBod.js → puppet-2D8HU2bv.js} +0 -0
- /package/dist/web/assets/{purescript-B-MyXMMp.js → purescript-BEJAORgU.js} +0 -0
- /package/dist/web/assets/{python-D6QbSR2S.js → python-BBdbb7nn.js} +0 -0
- /package/dist/web/assets/{qmldir-BpTSVcw_.js → qmldir-D5ZYI4Ie.js} +0 -0
- /package/dist/web/assets/{qss-DBc4gbpe.js → qss-BOJVGyXE.js} +0 -0
- /package/dist/web/assets/{r-i9jMMi0s.js → r-B5dlEtkj.js} +0 -0
- /package/dist/web/assets/{racket-BWNaxU7C.js → racket-BUAEygJ6.js} +0 -0
- /package/dist/web/assets/{raku-fqTZLn7L.js → raku-BG0Ss0ID.js} +0 -0
- /package/dist/web/assets/{react-X4jUntEf.js → react-B549sK_T.js} +0 -0
- /package/dist/web/assets/{red-D-JEcuzA.js → red-C5yyQbtG.js} +0 -0
- /package/dist/web/assets/{refresh-cw-B9iv7csb.js → refresh-cw-BQU5g2CY.js} +0 -0
- /package/dist/web/assets/{reg-BbirlU1S.js → reg-DHoBWtLf.js} +0 -0
- /package/dist/web/assets/{regexp-D8VTFrtg.js → regexp-DDtJrlKv.js} +0 -0
- /package/dist/web/assets/{rel-ByQGKiNM.js → rel-CR6WJDRD.js} +0 -0
- /package/dist/web/assets/{resolve-theme-Cqb9zWYc.js → resolve-theme-4fexHWl6.js} +0 -0
- /package/dist/web/assets/{riscv-B5nqCfLE.js → riscv-2V9tC-nx.js} +0 -0
- /package/dist/web/assets/{ron-DOVh_iZR.js → ron-QLTmqGUw.js} +0 -0
- /package/dist/web/assets/{rose-pine-jp51ZUzi.js → rose-pine-be0__X3A.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-B7hkmmWy.js → rose-pine-dawn-n_WQJwsW.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-Ff063yLj.js → rose-pine-moon-t2aLtzgB.js} +0 -0
- /package/dist/web/assets/{rosmsg-Cyk7Il8z.js → rosmsg-Ce1JD8vt.js} +0 -0
- /package/dist/web/assets/{rotate-ccw-DnuPzJRE.js → rotate-ccw-CpjRo90J.js} +0 -0
- /package/dist/web/assets/{rust-DRNM5epF.js → rust-5fHH-ZoW.js} +0 -0
- /package/dist/web/assets/{sass-D_cB0-M1.js → sass-DPmGFiOJ.js} +0 -0
- /package/dist/web/assets/{save-DxonD4y1.js → save-DYLiVzU8.js} +0 -0
- /package/dist/web/assets/{scala-Crl-1kKS.js → scala-BRMVFbZF.js} +0 -0
- /package/dist/web/assets/{scheme-DCaWyEJ0.js → scheme-BMSJWevF.js} +0 -0
- /package/dist/web/assets/{sdbl-BbXNqwtX.js → sdbl-DTZ-pEHI.js} +0 -0
- /package/dist/web/assets/{send-B-t5Mz26.js → send-C_sLFt56.js} +0 -0
- /package/dist/web/assets/{shallow-4zAfs2KW.js → shallow-CmPMAPte.js} +0 -0
- /package/dist/web/assets/{shellscript-BAgksBhW.js → shellscript-6-9yg5Cz.js} +0 -0
- /package/dist/web/assets/{shield-check-CdTiK1j2.js → shield-check-CUI36aSc.js} +0 -0
- /package/dist/web/assets/{shield-off-DuetkXKM.js → shield-off-GdkkVhb3.js} +0 -0
- /package/dist/web/assets/{slack-dark-GB-ElUW_.js → slack-dark-DKJgiR7e.js} +0 -0
- /package/dist/web/assets/{slack-ochin-tU6CFD3S.js → slack-ochin-WjehVgyK.js} +0 -0
- /package/dist/web/assets/{smalltalk-DSVGUbtL.js → smalltalk-BChf4SN1.js} +0 -0
- /package/dist/web/assets/{snazzy-light-DZhi3PoS.js → snazzy-light-C1WAwOx6.js} +0 -0
- /package/dist/web/assets/{solarized-dark-D6AMCco7.js → solarized-dark-BxREZrGx.js} +0 -0
- /package/dist/web/assets/{solarized-light-DepuTWSE.js → solarized-light-DG29B1pR.js} +0 -0
- /package/dist/web/assets/{solidity-CsxN618l.js → solidity-bDbtGwdR.js} +0 -0
- /package/dist/web/assets/{splunk-CF1m0Kkv.js → splunk-Ceqx5njI.js} +0 -0
- /package/dist/web/assets/{sql-DvyMDgrB.js → sql-OwMEKbz8.js} +0 -0
- /package/dist/web/assets/{ssh-config-a8sDo-Q8.js → ssh-config-WAR3lu2k.js} +0 -0
- /package/dist/web/assets/{stylus-DCJ-6AjT.js → stylus-Cjmoz3d8.js} +0 -0
- /package/dist/web/assets/{swift-c-ZFxyPP.js → swift-S_ZSBG1q.js} +0 -0
- /package/dist/web/assets/{synthwave-84-9NPgBcuv.js → synthwave-84-DiUeWMSh.js} +0 -0
- /package/dist/web/assets/{system-verilog-DPD02IOo.js → system-verilog-RRjqPjUV.js} +0 -0
- /package/dist/web/assets/{systemd-BUr5HRtU.js → systemd-BD3SkXoU.js} +0 -0
- /package/dist/web/assets/{table-FLU8ei8b.js → table-D5Q4ap4W.js} +0 -0
- /package/dist/web/assets/{talonscript-BNBQV5YJ.js → talonscript-DWefISOm.js} +0 -0
- /package/dist/web/assets/{tasl-BETjpSyu.js → tasl-DCjSS-09.js} +0 -0
- /package/dist/web/assets/{tcl-Clp_AZiC.js → tcl-DTYqT_66.js} +0 -0
- /package/dist/web/assets/{terraform-B0kZ0lim.js → terraform-CkbP5QnR.js} +0 -0
- /package/dist/web/assets/{text-wrap-B05VGRa-.js → text-wrap-DBvQD3kc.js} +0 -0
- /package/dist/web/assets/{tokyo-night-oOJ0a7Dk.js → tokyo-night-qVSqO9xy.js} +0 -0
- /package/dist/web/assets/{toml-DOPCSumy.js → toml-D9F5Osqs.js} +0 -0
- /package/dist/web/assets/{trash-2-CTXlTflF.js → trash-2-By4VMbC4.js} +0 -0
- /package/dist/web/assets/{triangle-alert-BRq0VAfd.js → triangle-alert-CUTURFbG.js} +0 -0
- /package/dist/web/assets/{tsv-D6qUGf84.js → tsv-C5tpHoxm.js} +0 -0
- /package/dist/web/assets/{tsx-B6qYhT6O.js → tsx-CMWkGtGm.js} +0 -0
- /package/dist/web/assets/{turtle-C0Rjzw73.js → turtle-Cn4z3xOi.js} +0 -0
- /package/dist/web/assets/{typescript-BiydShEu.js → typescript-DSyAEWsn.js} +0 -0
- /package/dist/web/assets/{typespec-DjmF6Aww.js → typespec-B93Qap7y.js} +0 -0
- /package/dist/web/assets/{typst-CPuscfcL.js → typst-DtlOmFlX.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-b94q8RvF.js → use-is-mobile-B9UZNwSm.js} +0 -0
- /package/dist/web/assets/{utils-C9OjCrou.js → utils-QHe_Q6UF.js} +0 -0
- /package/dist/web/assets/{v-DE4vpLLQ.js → v-Dfzf-szS.js} +0 -0
- /package/dist/web/assets/{vala-CmUF2pS5.js → vala-CyiQZQ9f.js} +0 -0
- /package/dist/web/assets/{vb-BvcRoX4b.js → vb-C7DGSldd.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-K8kpLxdb.js → vendor-xterm-BHKF_Nsf.js} +0 -0
- /package/dist/web/assets/{verilog-BGIzyH3R.js → verilog-kF-hClY2.js} +0 -0
- /package/dist/web/assets/{vesper-CG7IrHQQ.js → vesper-BmKcADuz.js} +0 -0
- /package/dist/web/assets/{vhdl-DMSumomw.js → vhdl-CDnb16KJ.js} +0 -0
- /package/dist/web/assets/{viml-Drt7AEmU.js → viml-B8cs6Ady.js} +0 -0
- /package/dist/web/assets/{vitesse-black-DmsjfN0b.js → vitesse-black-BR6o9Gto.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-Dl55_WeJ.js → vitesse-dark-Dye14-KB.js} +0 -0
- /package/dist/web/assets/{vitesse-light-Crbv9aGg.js → vitesse-light-BLpswIxf.js} +0 -0
- /package/dist/web/assets/{vyper-CB7psEJj.js → vyper-DU51Z97W.js} +0 -0
- /package/dist/web/assets/{wasm-BY1tuM5m.js → wasm-CVqlJAR2.js} +0 -0
- /package/dist/web/assets/{wasm-Dck8KPgP.js → wasm-ChtccNfv.js} +0 -0
- /package/dist/web/assets/{wenyan-DhewQ68b.js → wenyan-DhCa3hCn.js} +0 -0
- /package/dist/web/assets/{wgsl-DlmiP456.js → wgsl-Bj1Ec4vv.js} +0 -0
- /package/dist/web/assets/{wikitext-cQCnqNgE.js → wikitext-C90DHNa0.js} +0 -0
- /package/dist/web/assets/{wit-S5MaCngB.js → wit-DUgbOD7Z.js} +0 -0
- /package/dist/web/assets/{wolfram-Bh8n_9zr.js → wolfram-BgwUqD1I.js} +0 -0
- /package/dist/web/assets/{x-YDu4m2Iq.js → x-DiOUR_mp.js} +0 -0
- /package/dist/web/assets/{yaml-BoK_17l9.js → yaml-DEQulj-8.js} +0 -0
- /package/dist/web/assets/{zenscript-DB3ZFwNJ.js → zenscript-C7Jp7TgT.js} +0 -0
- /package/dist/web/assets/{zig-Bv1BWmfT.js → zig-9x4qXYLg.js} +0 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mouse/keyboard injection via Bun FFI straight into `user32.dll` `SendInput` — no separate
|
|
3
|
+
* helper process (drops the Rust-binary build step + unsigned-exe/SmartScreen risk the phase
|
|
4
|
+
* plan flagged for a bundled injector; phase 02's SYSTEM-session helper is a different
|
|
5
|
+
* process/token anyway, so nothing here is meant to be reused as-is).
|
|
6
|
+
*
|
|
7
|
+
* Normal desktop only, current interactive session, no SYSTEM/UAC/lock-screen support.
|
|
8
|
+
*
|
|
9
|
+
* `bun:ffi` is only ever `import()`-ed and `dlopen`-ed lazily from inside `loadUser32()`, so
|
|
10
|
+
* merely importing this module (routing, types) on Linux/macOS test runners never touches
|
|
11
|
+
* `user32.dll`.
|
|
12
|
+
*/
|
|
13
|
+
import { codeToVk, MODIFIER_VK_CODES } from "./remote-desktop-vk-map.ts";
|
|
14
|
+
|
|
15
|
+
export class RemoteInputUnavailableError extends Error {
|
|
16
|
+
constructor(msg = "Remote input injection is only available on Windows") {
|
|
17
|
+
super(msg);
|
|
18
|
+
this.name = "RemoteInputUnavailableError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const INPUT_MOUSE = 0;
|
|
23
|
+
const INPUT_KEYBOARD = 1;
|
|
24
|
+
const MOUSEEVENTF_MOVE = 0x0001;
|
|
25
|
+
const MOUSEEVENTF_LEFTDOWN = 0x0002;
|
|
26
|
+
const MOUSEEVENTF_LEFTUP = 0x0004;
|
|
27
|
+
const MOUSEEVENTF_RIGHTDOWN = 0x0008;
|
|
28
|
+
const MOUSEEVENTF_RIGHTUP = 0x0010;
|
|
29
|
+
const MOUSEEVENTF_ABSOLUTE = 0x8000;
|
|
30
|
+
const MOUSEEVENTF_VIRTUALDESK = 0x4000;
|
|
31
|
+
const MOUSEEVENTF_WHEEL = 0x0800;
|
|
32
|
+
/** One notch of a real mouse wheel — `mouseData` for `MOUSEEVENTF_WHEEL` is a signed multiple
|
|
33
|
+
* of this constant, not a raw pixel delta. */
|
|
34
|
+
const WHEEL_DELTA = 120;
|
|
35
|
+
const KEYEVENTF_KEYUP = 0x0002;
|
|
36
|
+
/** `((DPI_AWARENESS_CONTEXT)-4)` — PER_MONITOR_AWARE_V2. Not a real pointer; the x64 ABI
|
|
37
|
+
* passes it in the same register/slot as a pointer arg would, bit pattern only. */
|
|
38
|
+
const DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = -4n;
|
|
39
|
+
/** Each `INPUT` struct is 40 bytes on x64 (8-byte `type` slot + 32-byte union). */
|
|
40
|
+
const INPUT_STRUCT_SIZE = 40;
|
|
41
|
+
/** Desktop access rights for input injection. `SendInput` silently no-ops when the calling
|
|
42
|
+
* thread is not attached to the *input* desktop; attaching needs the READ/WRITE/SWITCH rights
|
|
43
|
+
* plus JOURNALPLAYBACK so the desktop accepts injected input. These MUST be the real DESKTOP_*
|
|
44
|
+
* bit values — an invalid mask (e.g. an out-of-range bit) makes `OpenInputDesktop` return NULL
|
|
45
|
+
* and the attach silently fails, leaving SendInput pointed at the wrong desktop. */
|
|
46
|
+
const DESKTOP_READOBJECTS = 0x0001;
|
|
47
|
+
const DESKTOP_JOURNALPLAYBACK = 0x0020;
|
|
48
|
+
const DESKTOP_WRITEOBJECTS = 0x0080;
|
|
49
|
+
const DESKTOP_SWITCHDESKTOP = 0x0100;
|
|
50
|
+
const INPUT_DESKTOP_ACCESS =
|
|
51
|
+
DESKTOP_READOBJECTS | DESKTOP_JOURNALPLAYBACK | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP; // 0x1A1
|
|
52
|
+
|
|
53
|
+
/** WINSTA_ALL_ACCESS — enough to open the interactive window station and make it this process's
|
|
54
|
+
* station so its input desktop becomes reachable. */
|
|
55
|
+
const WINSTA_ALL_ACCESS = 0x37f;
|
|
56
|
+
|
|
57
|
+
type FfiModule = typeof import("bun:ffi");
|
|
58
|
+
type User32Symbols = {
|
|
59
|
+
SendInput: (count: number, ptr: number | bigint, size: number) => number;
|
|
60
|
+
SetProcessDpiAwarenessContext: (ctx: bigint) => boolean;
|
|
61
|
+
OpenInputDesktop: (flags: number, inherit: boolean, access: number) => number | bigint;
|
|
62
|
+
SetThreadDesktop: (hdesk: number | bigint) => boolean;
|
|
63
|
+
CloseDesktop: (hdesk: number | bigint) => boolean;
|
|
64
|
+
OpenWindowStationW: (name: number | bigint, inherit: boolean, access: number) => number | bigint;
|
|
65
|
+
SetProcessWindowStation: (hwinsta: number | bigint) => boolean;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
let ffiModule: FfiModule | null = null;
|
|
69
|
+
let user32: User32Symbols | null = null;
|
|
70
|
+
let dpiAwarenessAttempted = false;
|
|
71
|
+
|
|
72
|
+
async function loadUser32(): Promise<{ ffi: FfiModule; lib: User32Symbols }> {
|
|
73
|
+
if (process.platform !== "win32") throw new RemoteInputUnavailableError();
|
|
74
|
+
if (!ffiModule) ffiModule = await import("bun:ffi");
|
|
75
|
+
const { dlopen, FFIType } = ffiModule;
|
|
76
|
+
if (!user32) {
|
|
77
|
+
user32 = dlopen("user32.dll", {
|
|
78
|
+
SendInput: { args: [FFIType.u32, FFIType.ptr, FFIType.i32], returns: FFIType.u32 },
|
|
79
|
+
SetProcessDpiAwarenessContext: { args: [FFIType.i64], returns: FFIType.bool },
|
|
80
|
+
OpenInputDesktop: { args: [FFIType.u32, FFIType.bool, FFIType.u32], returns: FFIType.ptr },
|
|
81
|
+
SetThreadDesktop: { args: [FFIType.ptr], returns: FFIType.bool },
|
|
82
|
+
CloseDesktop: { args: [FFIType.ptr], returns: FFIType.bool },
|
|
83
|
+
OpenWindowStationW: { args: [FFIType.ptr, FFIType.bool, FFIType.u32], returns: FFIType.ptr },
|
|
84
|
+
SetProcessWindowStation: { args: [FFIType.ptr], returns: FFIType.bool },
|
|
85
|
+
}).symbols as unknown as User32Symbols;
|
|
86
|
+
}
|
|
87
|
+
if (!dpiAwarenessAttempted) {
|
|
88
|
+
dpiAwarenessAttempted = true;
|
|
89
|
+
// A second call to this in the same process returns E_ACCESSDENIED — swallow it, it just
|
|
90
|
+
// means something else (or a previous session) already set it.
|
|
91
|
+
try { user32.SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); } catch { /* already set */ }
|
|
92
|
+
}
|
|
93
|
+
return { ffi: ffiModule, lib: user32 };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function writeMouseInput(dv: DataView, offset: number, xAbs: number, yAbs: number, flags: number, mouseData = 0): void {
|
|
97
|
+
dv.setUint32(offset, INPUT_MOUSE, true);
|
|
98
|
+
dv.setInt32(offset + 8, xAbs, true);
|
|
99
|
+
dv.setInt32(offset + 12, yAbs, true);
|
|
100
|
+
dv.setInt32(offset + 16, mouseData, true); // wheel delta for MOUSEEVENTF_WHEEL, unused otherwise
|
|
101
|
+
dv.setUint32(offset + 20, flags >>> 0, true);
|
|
102
|
+
dv.setUint32(offset + 24, 0, true); // time: 0 lets the system supply it
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function writeKeyInput(dv: DataView, offset: number, vk: number, keyUp: boolean): void {
|
|
106
|
+
dv.setUint32(offset, INPUT_KEYBOARD, true);
|
|
107
|
+
dv.setUint16(offset + 8, vk, true);
|
|
108
|
+
dv.setUint16(offset + 10, 0, true); // wScan — unused, we inject by VK not scan code
|
|
109
|
+
dv.setUint32(offset + 12, keyUp ? KEYEVENTF_KEYUP : 0, true);
|
|
110
|
+
dv.setUint32(offset + 16, 0, true); // time
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Handle of the desktop this thread is currently attached to, so we can close the previous one
|
|
114
|
+
* when the active input desktop changes (lock/unlock/UAC switches it). One handle in flight. */
|
|
115
|
+
let attachedDesktop: number | bigint | null = null;
|
|
116
|
+
|
|
117
|
+
/** Attach the calling thread to the *current* input desktop before injecting. Re-run every send
|
|
118
|
+
* because the input desktop changes on lock/unlock; `CreateProcessAsUser`/process start only
|
|
119
|
+
* fixes the initial desktop, not later switches. Best-effort: on failure we still try SendInput
|
|
120
|
+
* (it just no-ops), and log once rather than per-event spam. */
|
|
121
|
+
/** Attach the PROCESS to the interactive window station ("winsta0") once, so its input desktop
|
|
122
|
+
* is reachable. Without this, a PPM process launched from a non-interactive station (a service,
|
|
123
|
+
* or some spawners) can't OpenInputDesktop at all — SendInput then goes nowhere. No-op when the
|
|
124
|
+
* process is already on winsta0 (the normal case for PPM started inside the user's session). */
|
|
125
|
+
let stationEnsured = false;
|
|
126
|
+
function ensureInteractiveStation(ffi: FfiModule, lib: User32Symbols): void {
|
|
127
|
+
if (stationEnsured) return;
|
|
128
|
+
stationEnsured = true;
|
|
129
|
+
const name = "winsta0";
|
|
130
|
+
const wide = new Uint16Array(name.length + 1);
|
|
131
|
+
for (let i = 0; i < name.length; i++) wide[i] = name.charCodeAt(i);
|
|
132
|
+
const hwinsta = lib.OpenWindowStationW(ffi.ptr(wide), false, WINSTA_ALL_ACCESS);
|
|
133
|
+
if (!hwinsta) return; // already on it, or no access — OpenInputDesktop below will tell us
|
|
134
|
+
lib.SetProcessWindowStation(hwinsta);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function attachToInputDesktop(ffi: FfiModule, lib: User32Symbols): void {
|
|
138
|
+
ensureInteractiveStation(ffi, lib);
|
|
139
|
+
const hdesk = lib.OpenInputDesktop(0, false, INPUT_DESKTOP_ACCESS);
|
|
140
|
+
if (!hdesk) return; // couldn't open the input desktop (e.g. secure/lock desktop); leave thread as-is
|
|
141
|
+
const ok = lib.SetThreadDesktop(hdesk);
|
|
142
|
+
if (ok) {
|
|
143
|
+
if (attachedDesktop && attachedDesktop !== hdesk) {
|
|
144
|
+
try { lib.CloseDesktop(attachedDesktop); } catch { /* ignore */ }
|
|
145
|
+
}
|
|
146
|
+
attachedDesktop = hdesk;
|
|
147
|
+
} else {
|
|
148
|
+
// SetThreadDesktop fails if this thread owns windows/hooks — shouldn't for the server thread.
|
|
149
|
+
try { lib.CloseDesktop(hdesk); } catch { /* ignore */ }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function sendRaw(buf: Uint8Array, count: number): Promise<void> {
|
|
154
|
+
const { ffi, lib } = await loadUser32();
|
|
155
|
+
attachToInputDesktop(ffi, lib);
|
|
156
|
+
const sent = lib.SendInput(count, ffi.ptr(buf), INPUT_STRUCT_SIZE);
|
|
157
|
+
if (sent !== count) console.warn(`[remote-desktop] SendInput accepted ${sent}/${count} events`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Move the cursor and, if this is a click, press/release the button — one absolute move
|
|
161
|
+
* per call so the button acts at the position the client actually clicked. `xFrac`/`yFrac`
|
|
162
|
+
* are 0..1 fractions of the capture (client already dropped devicePixelRatio; only the
|
|
163
|
+
* canvas-relative fraction is meaningful for host coordinates). */
|
|
164
|
+
export async function injectPointer(
|
|
165
|
+
xFrac: number,
|
|
166
|
+
yFrac: number,
|
|
167
|
+
button: "left" | "right" | null,
|
|
168
|
+
down: boolean | null,
|
|
169
|
+
): Promise<void> {
|
|
170
|
+
const xAbs = Math.round(Math.min(Math.max(xFrac, 0), 1) * 65535);
|
|
171
|
+
const yAbs = Math.round(Math.min(Math.max(yFrac, 0), 1) * 65535);
|
|
172
|
+
let flags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK;
|
|
173
|
+
if (button === "left") flags |= down ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
|
|
174
|
+
else if (button === "right") flags |= down ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
|
|
175
|
+
|
|
176
|
+
const buf = new Uint8Array(INPUT_STRUCT_SIZE);
|
|
177
|
+
writeMouseInput(new DataView(buf.buffer), 0, xAbs, yAbs, flags);
|
|
178
|
+
await sendRaw(buf, 1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Scroll the wheel. `deltaY` is in `WHEEL_DELTA` (120) units — positive rotates the wheel
|
|
182
|
+
* forward (scrolls up/away from the user), negative rotates backward (scrolls down). The
|
|
183
|
+
* client accumulates raw two-finger-drag pixels into this unit before sending, so this layer
|
|
184
|
+
* stays a dumb relay: round + clamp to a safe i16 range so a runaway client value can't wrap
|
|
185
|
+
* into a huge or negative-looking mouseData field. */
|
|
186
|
+
export async function injectWheel(deltaY: number): Promise<void> {
|
|
187
|
+
const clamped = Math.max(-32768, Math.min(32767, Math.round(deltaY)));
|
|
188
|
+
if (clamped === 0) return;
|
|
189
|
+
const buf = new Uint8Array(INPUT_STRUCT_SIZE);
|
|
190
|
+
writeMouseInput(new DataView(buf.buffer), 0, 0, 0, MOUSEEVENTF_WHEEL, clamped);
|
|
191
|
+
await sendRaw(buf, 1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Send a keydown/keyup by physical key code. Returns false for an unmapped code (caller
|
|
195
|
+
* should just drop it rather than injecting a wrong key). */
|
|
196
|
+
export async function injectKey(code: string, down: boolean): Promise<boolean> {
|
|
197
|
+
const vk = codeToVk(code);
|
|
198
|
+
if (vk === null) return false;
|
|
199
|
+
const buf = new Uint8Array(INPUT_STRUCT_SIZE);
|
|
200
|
+
writeKeyInput(new DataView(buf.buffer), 0, vk, !down);
|
|
201
|
+
await sendRaw(buf, 1);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Force keyup for every modifier VK, regardless of tracked state — the backstop for a lost
|
|
206
|
+
* keyup (blur, WS drop) leaving Shift/Ctrl/Alt/Win logically held on the host. */
|
|
207
|
+
export async function releaseAllModifiers(): Promise<void> {
|
|
208
|
+
if (process.platform !== "win32") return;
|
|
209
|
+
const buf = new Uint8Array(INPUT_STRUCT_SIZE * MODIFIER_VK_CODES.length);
|
|
210
|
+
const dv = new DataView(buf.buffer);
|
|
211
|
+
MODIFIER_VK_CODES.forEach((vk, i) => writeKeyInput(dv, i * INPUT_STRUCT_SIZE, vk, true));
|
|
212
|
+
try { await sendRaw(buf, MODIFIER_VK_CODES.length); } catch { /* best-effort */ }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function isInputAvailable(): boolean {
|
|
216
|
+
return process.platform === "win32";
|
|
217
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-use, short-TTL connection nonce — mirrors `download-token.service.ts`'s
|
|
3
|
+
* consume-once pattern.
|
|
4
|
+
*
|
|
5
|
+
* The PPM static bearer token already travels as `?token=` on every WS upgrade
|
|
6
|
+
* (`isWsUpgradeAuthorized`, `src/server/index.ts`) and proves nothing beyond "holds the one
|
|
7
|
+
* reusable app token" — it is not a real per-session grant. `POST /api/remote-desktop/session`
|
|
8
|
+
* mints one of these after re-checking `auth.enabled`; the client then presents it via the WS
|
|
9
|
+
* subprotocol header (never the query string) and it is consumed exactly once, at upgrade
|
|
10
|
+
* time, in `src/server/index.ts`.
|
|
11
|
+
*/
|
|
12
|
+
import { randomUUIDv7 } from "bun";
|
|
13
|
+
|
|
14
|
+
const TTL_MS = 30_000;
|
|
15
|
+
const nonces = new Map<string, number>();
|
|
16
|
+
|
|
17
|
+
/** Mint a nonce; caller must have already verified auth is enabled + a valid request. */
|
|
18
|
+
export function mintRemoteDesktopNonce(): string {
|
|
19
|
+
const token = randomUUIDv7();
|
|
20
|
+
nonces.set(token, Date.now());
|
|
21
|
+
cleanup();
|
|
22
|
+
return token;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Validate + consume. Returns false for missing/expired/already-used nonces. */
|
|
26
|
+
export function consumeRemoteDesktopNonce(token: string): boolean {
|
|
27
|
+
const mintedAt = nonces.get(token);
|
|
28
|
+
if (mintedAt === undefined) return false;
|
|
29
|
+
nonces.delete(token);
|
|
30
|
+
return Date.now() - mintedAt <= TTL_MS;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function cleanup(): void {
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
for (const [token, mintedAt] of nonces) {
|
|
36
|
+
if (now - mintedAt > TTL_MS) nonces.delete(token);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns one remote-desktop connection end to end: capture lifecycle, WS backpressure,
|
|
3
|
+
* heartbeat/TTL teardown, and forwarding input to the injector. "One active session per
|
|
4
|
+
* host" — a new connection evicts (and awaits) any previous one before starting its own
|
|
5
|
+
* capture so two `gdigrab` processes never contend for the same display.
|
|
6
|
+
*/
|
|
7
|
+
import { startCapture, type CaptureHandle } from "./remote-desktop-capture.ts";
|
|
8
|
+
import { avc1CodecString } from "./avc1-codec-string.ts";
|
|
9
|
+
import type { AccessUnit } from "./access-unit-assembler.ts";
|
|
10
|
+
import { injectPointer, injectKey, injectWheel, releaseAllModifiers, isInputAvailable } from "./remote-desktop-input.ts";
|
|
11
|
+
|
|
12
|
+
/** Minimal socket surface this module needs — matches Bun's `ServerWebSocket` shape closely
|
|
13
|
+
* enough to be faked in a unit test without a real connection. */
|
|
14
|
+
export interface RemoteDesktopSocket {
|
|
15
|
+
send(data: string | Uint8Array): number;
|
|
16
|
+
getBufferedAmount?(): number;
|
|
17
|
+
close(code?: number, reason?: string): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** App-level heartbeat, independent of Bun's 960s socket `idleTimeout` — a dead tunnel or a
|
|
21
|
+
* sleeping laptop must not leave ffmpeg + input access live for minutes unattended. */
|
|
22
|
+
const HEARTBEAT_INTERVAL_MS = 5_000;
|
|
23
|
+
// 30s (6 missed 5s pings): tolerant of brief network jitter and of a client whose ping timer
|
|
24
|
+
// is throttled while its tab is briefly backgrounded, without leaving an unattended session
|
|
25
|
+
// (ffmpeg + input access) live for long. The client also re-pings the moment its tab is visible.
|
|
26
|
+
const HEARTBEAT_TIMEOUT_MS = 30_000;
|
|
27
|
+
/** Above this many buffered bytes, drop delta AUs until the next keyframe rather than
|
|
28
|
+
* queueing forever — a slow WAN/tunnel link must degrade to "waits ~2s for a keyframe",
|
|
29
|
+
* never to unbounded memory growth or an ever-growing latency queue. */
|
|
30
|
+
const BACKPRESSURE_THRESHOLD_BYTES = 512 * 1024;
|
|
31
|
+
|
|
32
|
+
export class RemoteDesktopSession {
|
|
33
|
+
private capture: CaptureHandle | null = null;
|
|
34
|
+
private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
|
|
35
|
+
private lastPingAt = Date.now();
|
|
36
|
+
private droppingUntilKey = false;
|
|
37
|
+
private sentConfig = false;
|
|
38
|
+
private closed = false;
|
|
39
|
+
private readonly heldKeyCodes = new Set<string>();
|
|
40
|
+
private exitResolve: (() => void) | null = null;
|
|
41
|
+
/** Resolves once the underlying ffmpeg process has actually exited (not merely asked to). */
|
|
42
|
+
readonly exited: Promise<void>;
|
|
43
|
+
|
|
44
|
+
constructor(private readonly ws: RemoteDesktopSocket) {
|
|
45
|
+
this.exited = new Promise((resolve) => { this.exitResolve = resolve; });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async start(): Promise<void> {
|
|
49
|
+
this.capture = await startCapture({
|
|
50
|
+
onAccessUnit: (au) => this.handleAccessUnit(au),
|
|
51
|
+
// `reason` is only set when ffmpeg died on its own (crash, access denied, etc) — tell
|
|
52
|
+
// the client *why* before closing instead of leaving it to guess from a bare
|
|
53
|
+
// disconnect (this is exactly what happens today for e.g. gdigrab failing against a
|
|
54
|
+
// disconnected Windows session: "Failed to capture image (error 5)").
|
|
55
|
+
onExit: (_code, reason) => {
|
|
56
|
+
this.exitResolve?.();
|
|
57
|
+
if (!this.closed) {
|
|
58
|
+
if (reason) {
|
|
59
|
+
try { this.ws.send(JSON.stringify({ type: "error", message: `Capture failed: ${reason}` })); } catch { /* closing anyway */ }
|
|
60
|
+
}
|
|
61
|
+
this.close();
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
this.heartbeatTimer = setInterval(() => this.checkHeartbeat(), HEARTBEAT_INTERVAL_MS);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
noteClientAlive(): void {
|
|
69
|
+
this.lastPingAt = Date.now();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async handleClientMessage(raw: string): Promise<void> {
|
|
73
|
+
let msg: Record<string, unknown>;
|
|
74
|
+
try { msg = JSON.parse(raw); } catch { return; }
|
|
75
|
+
if (msg.type === "ping") { this.noteClientAlive(); return; }
|
|
76
|
+
if (msg.type === "stop") { this.close(); return; }
|
|
77
|
+
if (!isInputAvailable()) return; // part 1b unsupported on this OS — silently ignore
|
|
78
|
+
|
|
79
|
+
if (msg.type === "pointer") {
|
|
80
|
+
const { xFrac, yFrac, button, down } = msg as { xFrac?: unknown; yFrac?: unknown; button?: unknown; down?: unknown };
|
|
81
|
+
if (typeof xFrac === "number" && typeof yFrac === "number") {
|
|
82
|
+
const btn = button === "left" || button === "right" ? button : null;
|
|
83
|
+
await injectPointer(xFrac, yFrac, btn, typeof down === "boolean" ? down : null);
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (msg.type === "wheel") {
|
|
88
|
+
const { dy } = msg as { dy?: unknown };
|
|
89
|
+
if (typeof dy === "number" && Number.isFinite(dy)) await injectWheel(dy);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (msg.type === "key") {
|
|
93
|
+
const { code, down } = msg as { code?: unknown; down?: unknown };
|
|
94
|
+
if (typeof code === "string" && typeof down === "boolean") {
|
|
95
|
+
if (down) this.heldKeyCodes.add(code); else this.heldKeyCodes.delete(code);
|
|
96
|
+
await injectKey(code, down);
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (msg.type === "releaseAll") {
|
|
101
|
+
await this.releaseHeldKeys();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Force a keyup for every key this session has tracked as held, then run the modifier
|
|
106
|
+
* backstop too — `heldKeyCodes` covers whatever the client actually pressed (letters,
|
|
107
|
+
* digits, etc, not just modifiers); `releaseAllModifiers()` is a second backstop for a
|
|
108
|
+
* modifier that raced a disconnect before its keydown was ever tracked. Previously this
|
|
109
|
+
* only ran the modifier backstop and *cleared* `heldKeyCodes` without releasing them,
|
|
110
|
+
* so a held non-modifier key (e.g. a letter) stayed logically down on the host. */
|
|
111
|
+
private async releaseHeldKeys(): Promise<void> {
|
|
112
|
+
const codes = [...this.heldKeyCodes];
|
|
113
|
+
this.heldKeyCodes.clear();
|
|
114
|
+
await Promise.all(codes.map((code) => injectKey(code, false).catch(() => {})));
|
|
115
|
+
await releaseAllModifiers();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
close(): void {
|
|
119
|
+
if (this.closed) return;
|
|
120
|
+
this.closed = true;
|
|
121
|
+
if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
|
|
122
|
+
this.capture?.stop();
|
|
123
|
+
activeSessions.delete(this);
|
|
124
|
+
if (this.heldKeyCodes.size > 0) {
|
|
125
|
+
this.releaseHeldKeys().catch(() => {});
|
|
126
|
+
}
|
|
127
|
+
try { this.ws.close(); } catch { /* already closing */ }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private checkHeartbeat(): void {
|
|
131
|
+
if (Date.now() - this.lastPingAt > HEARTBEAT_TIMEOUT_MS) {
|
|
132
|
+
console.warn("[remote-desktop] heartbeat timeout — tearing down session");
|
|
133
|
+
this.close();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private handleAccessUnit(au: AccessUnit): void {
|
|
138
|
+
if (this.closed) return;
|
|
139
|
+
if (!this.sentConfig) {
|
|
140
|
+
const sps = this.capture?.cachedSps();
|
|
141
|
+
if (!sps) return; // nothing decodable yet — wait for the encoder's first SPS
|
|
142
|
+
const codec = avc1CodecString(sps);
|
|
143
|
+
if (!codec) return;
|
|
144
|
+
this.ws.send(JSON.stringify({ type: "config", codec }));
|
|
145
|
+
this.sentConfig = true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const buffered = this.ws.getBufferedAmount?.() ?? 0;
|
|
149
|
+
if (buffered > BACKPRESSURE_THRESHOLD_BYTES && !au.isKey) {
|
|
150
|
+
this.droppingUntilKey = true;
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (this.droppingUntilKey && !au.isKey) return;
|
|
154
|
+
if (au.isKey) this.droppingUntilKey = false;
|
|
155
|
+
|
|
156
|
+
const framed = new Uint8Array(1 + au.bytes.length);
|
|
157
|
+
framed[0] = au.isKey ? 1 : 0;
|
|
158
|
+
framed.set(au.bytes, 1);
|
|
159
|
+
this.ws.send(framed);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const activeSessions = new Set<RemoteDesktopSession>();
|
|
164
|
+
|
|
165
|
+
/** Evict any previous session (awaiting its ffmpeg exit, capped so a wedged process can't
|
|
166
|
+
* hang a reconnect) before starting the new one. */
|
|
167
|
+
export async function createRemoteDesktopSession(ws: RemoteDesktopSocket): Promise<RemoteDesktopSession> {
|
|
168
|
+
for (const existing of [...activeSessions]) {
|
|
169
|
+
existing.close();
|
|
170
|
+
await Promise.race([existing.exited, Bun.sleep(2000)]);
|
|
171
|
+
}
|
|
172
|
+
const session = new RemoteDesktopSession(ws);
|
|
173
|
+
await session.start();
|
|
174
|
+
activeSessions.add(session);
|
|
175
|
+
return session;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Process-exit sweep so ffmpeg never outlives a server crash/exit — a clean WS close or an
|
|
179
|
+
* idle heartbeat timeout is handled by the session itself; this covers everything else
|
|
180
|
+
* (SIGINT/SIGTERM, uncaught crash unwind). */
|
|
181
|
+
let sweepRegistered = false;
|
|
182
|
+
export function registerRemoteDesktopExitSweep(): void {
|
|
183
|
+
if (sweepRegistered) return;
|
|
184
|
+
sweepRegistered = true;
|
|
185
|
+
const sweep = () => { for (const s of [...activeSessions]) s.close(); };
|
|
186
|
+
process.on("exit", sweep);
|
|
187
|
+
process.on("SIGINT", sweep);
|
|
188
|
+
process.on("SIGTERM", sweep);
|
|
189
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `KeyboardEvent.code` (physical key, layout-independent) → Win32 virtual-key code.
|
|
3
|
+
* Only `code` is accepted from the client — `key` depends on the browser's active layout
|
|
4
|
+
* and would map wrong the moment host and client disagree on layout.
|
|
5
|
+
*/
|
|
6
|
+
const CODE_TO_VK: Record<string, number> = {
|
|
7
|
+
Backspace: 0x08, Tab: 0x09, Enter: 0x0d, ShiftLeft: 0xa0, ShiftRight: 0xa1,
|
|
8
|
+
ControlLeft: 0xa2, ControlRight: 0xa3, AltLeft: 0xa4, AltRight: 0xa5,
|
|
9
|
+
Pause: 0x13, CapsLock: 0x14, Escape: 0x1b, Space: 0x20,
|
|
10
|
+
PageUp: 0x21, PageDown: 0x22, End: 0x23, Home: 0x24,
|
|
11
|
+
ArrowLeft: 0x25, ArrowUp: 0x26, ArrowRight: 0x27, ArrowDown: 0x28,
|
|
12
|
+
Insert: 0x2d, Delete: 0x2e,
|
|
13
|
+
Digit0: 0x30, Digit1: 0x31, Digit2: 0x32, Digit3: 0x33, Digit4: 0x34,
|
|
14
|
+
Digit5: 0x35, Digit6: 0x36, Digit7: 0x37, Digit8: 0x38, Digit9: 0x39,
|
|
15
|
+
KeyA: 0x41, KeyB: 0x42, KeyC: 0x43, KeyD: 0x44, KeyE: 0x45, KeyF: 0x46,
|
|
16
|
+
KeyG: 0x47, KeyH: 0x48, KeyI: 0x49, KeyJ: 0x4a, KeyK: 0x4b, KeyL: 0x4c,
|
|
17
|
+
KeyM: 0x4d, KeyN: 0x4e, KeyO: 0x4f, KeyP: 0x50, KeyQ: 0x51, KeyR: 0x52,
|
|
18
|
+
KeyS: 0x53, KeyT: 0x54, KeyU: 0x55, KeyV: 0x56, KeyW: 0x57, KeyX: 0x58,
|
|
19
|
+
KeyY: 0x59, KeyZ: 0x5a,
|
|
20
|
+
MetaLeft: 0x5b, MetaRight: 0x5c, ContextMenu: 0x5d,
|
|
21
|
+
Numpad0: 0x60, Numpad1: 0x61, Numpad2: 0x62, Numpad3: 0x63, Numpad4: 0x64,
|
|
22
|
+
Numpad5: 0x65, Numpad6: 0x66, Numpad7: 0x67, Numpad8: 0x68, Numpad9: 0x69,
|
|
23
|
+
NumpadMultiply: 0x6a, NumpadAdd: 0x6b, NumpadSubtract: 0x6d,
|
|
24
|
+
NumpadDecimal: 0x6e, NumpadDivide: 0x6f,
|
|
25
|
+
F1: 0x70, F2: 0x71, F3: 0x72, F4: 0x73, F5: 0x74, F6: 0x75,
|
|
26
|
+
F7: 0x76, F8: 0x77, F9: 0x78, F10: 0x79, F11: 0x7a, F12: 0x7b,
|
|
27
|
+
NumLock: 0x90, ScrollLock: 0x91,
|
|
28
|
+
Semicolon: 0xba, Equal: 0xbb, Comma: 0xbc, Minus: 0xbd, Period: 0xbe,
|
|
29
|
+
Slash: 0xbf, Backquote: 0xc0, BracketLeft: 0xdb, Backslash: 0xdc,
|
|
30
|
+
BracketRight: 0xdd, Quote: 0xde,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** All modifier VK codes — used to force-release stuck modifiers on teardown (a lost keyup
|
|
34
|
+
* otherwise leaves e.g. Shift logically held on the host after the WS drops). */
|
|
35
|
+
export const MODIFIER_VK_CODES = [0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0x5b, 0x5c] as const;
|
|
36
|
+
|
|
37
|
+
/** Resolve a `KeyboardEvent.code` to its VK; null for anything unmapped (IME composition,
|
|
38
|
+
* unusual layouts) rather than guessing and injecting the wrong key. */
|
|
39
|
+
export function codeToVk(code: string): number | null {
|
|
40
|
+
return CODE_TO_VK[code] ?? null;
|
|
41
|
+
}
|