@hienlh/ppm 0.18.1 → 0.18.3
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/CHANGELOG.md +22 -0
- package/CLAUDE.md +1 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +6 -1
- package/dist/web/assets/{ai-resource-editor-Bmk4P0YS.js → ai-resource-editor-3Zk3gYSZ.js} +1 -1
- package/dist/web/assets/{angular-html-C-Kjr43S.js → angular-html-jfMqUuRd.js} +1 -1
- package/dist/web/assets/{angular-ts-CaDDbZqm.js → angular-ts-wytn9PMm.js} +1 -1
- package/dist/web/assets/api-client-CzABTreM.js +1 -0
- package/dist/web/assets/{api-settings-CAhcNpj5.js → api-settings-DfNRPr1q.js} +1 -1
- package/dist/web/assets/api-settings-DlcoR5QT.js +1 -0
- package/dist/web/assets/{api-themes-CAjg_Bt9.js → api-themes-CEyYK7AK.js} +1 -1
- package/dist/web/assets/{apl-B1WsQFAb.js → apl-i-ahKx6O.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-Bs7wzj-W.js +1 -0
- package/dist/web/assets/arrow-right-DubGFkN8.js +1 -0
- package/dist/web/assets/{astro-Bdnz7nVp.js → astro-DVBCJfG6.js} +1 -1
- package/dist/web/assets/audio-preview-DG2Tw0cO.js +1 -0
- package/dist/web/assets/{blade-B2euh8sS.js → blade-BHlL8SqU.js} +1 -1
- package/dist/web/assets/{bsl-GNeMaxd0.js → bsl-D-UOfR1F.js} +1 -1
- package/dist/web/assets/button-B4MWPjWj.js +1 -0
- package/dist/web/assets/{cairo-D7G0W7-s.js → cairo-Bn6rucli.js} +1 -1
- package/dist/web/assets/chat-tab-DvbbVMBi.js +15 -0
- package/dist/web/assets/{cobol-Q6YgysnI.js → cobol-Byn9FYKu.js} +1 -1
- package/dist/web/assets/code-editor-BKg7Li-L.js +10 -0
- package/dist/web/assets/{coffee-BjufWxVc.js → coffee-DjBl4gm-.js} +1 -1
- package/dist/web/assets/{conflict-editor-DC3liBm_.js → conflict-editor-CJNtovCh.js} +3 -3
- package/dist/web/assets/{context-menu-q-RHHAWz.js → context-menu-DkQdt6YA.js} +1 -1
- package/dist/web/assets/{cpp-CqWU0gCR.js → cpp-CKzic-3E.js} +1 -1
- package/dist/web/assets/{crystal-ylayVajF.js → crystal-Bkg-Ly_g.js} +1 -1
- package/dist/web/assets/{csv-preview-BhCOjgq1.js → csv-preview-BR6gCSZJ.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-BzPfsCfd.js → data-grid-overlay-editor-B1mTl3KS.js} +1 -1
- package/dist/web/assets/{database-viewer-Z6uLyZVO.js → database-viewer-DzuwuFSr.js} +1 -1
- package/dist/web/assets/diff-viewer-DL3_tISz.js +4 -0
- package/dist/web/assets/{docx-preview-C_fSjfy_.js → docx-preview-BzgyssBV.js} +1 -1
- package/dist/web/assets/download-D9UYwY0-.js +1 -0
- package/dist/web/assets/{edge-DX4NxQfh.js → edge-DVbZnd3Y.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-DfX-9om9.js → edit-diff-preview-HTZPC9VP.js} +1 -1
- package/dist/web/assets/{elixir-CSh3NjbV.js → elixir-Dwi8Wu7W.js} +1 -1
- package/dist/web/assets/{elm-DBzwFYq9.js → elm-DtIv-HXJ.js} +1 -1
- package/dist/web/assets/{erb-yxVOb8aB.js → erb-D3HEDz1x.js} +1 -1
- package/dist/web/assets/{erlang-5XIDkYrM.js → erlang-2MjTCzGz.js} +1 -1
- package/dist/web/assets/{esm-DO4el2OJ.js → esm-CZ8B8Ba2.js} +1 -1
- package/dist/web/assets/explorer-body-CrrY1HbH.js +2 -0
- package/dist/web/assets/explorer-window-content-CvQPeiwU.js +1 -0
- package/dist/web/assets/{extension-webview-ePIM2fwM.js → extension-webview-DH1PfQF2.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-D4tE5VWY.js → fortran-fixed-form-BLyAtyPM.js} +1 -1
- package/dist/web/assets/{fsharp-By8soj6-.js → fsharp-Bxbn_gbr.js} +1 -1
- package/dist/web/assets/{gdresource-DoSwM7Hv.js → gdresource-sFMY4D2Q.js} +1 -1
- package/dist/web/assets/{git-commit-_ZiciHIx.js → git-commit-BzB9Lio5.js} +1 -1
- package/dist/web/assets/{git-log-panel-BRWmTzX1.js → git-log-panel-gUm4E3wo.js} +1 -1
- package/dist/web/assets/{git-rebase-C3Y5N9-s.js → git-rebase-CKSVd848.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-D7i54kbM.js +1 -0
- package/dist/web/assets/glide-data-grid-D8J5lwUP.js +138 -0
- package/dist/web/assets/{glimmer-js-BPBTXVlC.js → glimmer-js-BY8aExAJ.js} +1 -1
- package/dist/web/assets/{glimmer-ts-BQSZdy_D.js → glimmer-ts-Bak54dBX.js} +1 -1
- package/dist/web/assets/{glsl-CbQdxpU-.js → glsl-nR2eaJQe.js} +1 -1
- package/dist/web/assets/{graphql-GMlKikpY.js → graphql-CGPPFAU6.js} +1 -1
- package/dist/web/assets/group-chat-tab-DihiQPSi.js +1 -0
- package/dist/web/assets/{hack-DyshcLec.js → hack-CXIMX-Ol.js} +1 -1
- package/dist/web/assets/{haml-rqCqWzsq.js → haml-tPY-9Pfm.js} +1 -1
- package/dist/web/assets/{handlebars-BCKwSWjR.js → handlebars-BuW1LT7T.js} +1 -1
- package/dist/web/assets/{html-Cg29xPR1.js → html-bgVcwZ-t.js} +1 -1
- package/dist/web/assets/{html-derivative-geltIVg8.js → html-derivative-D6-IxTgn.js} +1 -1
- package/dist/web/assets/{http-pG7Vp237.js → http-CJ1zyB-h.js} +1 -1
- package/dist/web/assets/{hurl-Di3Uu_Mc.js → hurl-CmoT7l3T.js} +1 -1
- package/dist/web/assets/{hxml-CRy4lbv8.js → hxml-CztOsL8o.js} +1 -1
- package/dist/web/assets/{image-preview-CP82lor8.js → image-preview-DOQBNgwl.js} +1 -1
- package/dist/web/assets/index-BtncTUyM.js +52 -0
- package/dist/web/assets/index-Bucbpby-.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-BV41AzR9.js +1 -0
- package/dist/web/assets/{jinja-C8GyVnvd.js → jinja-B1pq3jJq.js} +1 -1
- package/dist/web/assets/{jison-BywnqPn6.js → jison-D8B5pCwA.js} +1 -1
- package/dist/web/assets/{julia-DTrO3T3B.js → julia-D0K3bwpU.js} +1 -1
- package/dist/web/assets/{just-CA3KSHPi.js → just-B9Jlddu-.js} +1 -1
- package/dist/web/assets/keybindings-store-AynYQVnV.js +1 -0
- package/dist/web/assets/{latex-BC5xUMpc.js → latex-BPJSSE6j.js} +1 -1
- package/dist/web/assets/{liquid-Chd8tSyQ.js → liquid-lv10fo4U.js} +1 -1
- package/dist/web/assets/{lua-DHaEtwLv.js → lua-PNuaIBEF.js} +1 -1
- package/dist/web/assets/markdown-renderer-BBlGpszb.js +1 -0
- package/dist/web/assets/{markdown-renderer-DDgl5zDc.js → markdown-renderer-CVGub2iS.js} +3 -3
- package/dist/web/assets/{marko-Desx_bC-.js → marko-Du3frD6X.js} +1 -1
- package/dist/web/assets/{mdc-DahWHc1t.js → mdc-BeDVcFRl.js} +1 -1
- package/dist/web/assets/media-url-C5vA0ha9.js +1 -0
- package/dist/web/assets/{mobile-bottom-sheet-BQt45q9T.js → mobile-bottom-sheet-CS3XMwFw.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-CSgc5JOk.js +1 -0
- package/dist/web/assets/{nextflow-3A7lutCA.js → nextflow-DkcXP71s.js} +1 -1
- package/dist/web/assets/{nginx-XQda6Hdy.js → nginx-C5n8N_bt.js} +1 -1
- package/dist/web/assets/{nim-DHqlpwP1.js → nim-CIhyfs-A.js} +1 -1
- package/dist/web/assets/notification-store-1ksB0YwR.js +1 -0
- package/dist/web/assets/{number-overlay-editor-DgZtViPO.js → number-overlay-editor-NgOsWJ7x.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-CaJoP44m.js +1 -0
- package/dist/web/assets/{panel-store-s6Qx49RK.js → panel-store-DCD82zc1.js} +1 -1
- package/dist/web/assets/{pdf-preview-BXDOe7bG.js → pdf-preview-DSAkWbxY.js} +1 -1
- package/dist/web/assets/{perl-M-ncjACb.js → perl-Cf31sZYE.js} +1 -1
- package/dist/web/assets/{php-CKIajrZi.js → php-Dzl_MDIW.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-DSGZmNw2.js +1 -0
- package/dist/web/assets/plus-DmjC2iRB.js +1 -0
- package/dist/web/assets/{postgres-viewer-DkHYV33h.js → postgres-viewer-DOiM_5Nv.js} +3 -3
- package/dist/web/assets/{project-store-DyaEpHVG.js → project-store-B8BkHXy5.js} +1 -1
- package/dist/web/assets/{pug-DO0u_gO4.js → pug-CxSYHrB3.js} +1 -1
- package/dist/web/assets/{qml-UEje1PAA.js → qml-DlEjGhwN.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-Be4VUTs0.js +1 -0
- package/dist/web/assets/{razor-lCs5T1Ib.js → razor-KVt5uW14.js} +1 -1
- package/dist/web/assets/resolve-theme-CJvkL8hU.js +1 -0
- package/dist/web/assets/{rst-CZX_O0z4.js → rst-D5BwNTnw.js} +1 -1
- package/dist/web/assets/{ruby-idycVAyE.js → ruby-D5fejQwd.js} +1 -1
- package/dist/web/assets/run-in-terminal-B8TDtaM_.js +1 -0
- package/dist/web/assets/{sas-o5gTfUwp.js → sas-Bj7B1CB2.js} +1 -1
- package/dist/web/assets/{scss-CRTk3iBQ.js → scss-BB0CUoCL.js} +1 -1
- package/dist/web/assets/send-to-chat-CuBAVC8l.js +1 -0
- package/dist/web/assets/{settings-store-ChqHdT2V.js → settings-store-CY-4_U3H.js} +2 -2
- package/dist/web/assets/settings-tab-D_oruJB9.js +1 -0
- package/dist/web/assets/{shaderlab-3e5ln8ci.js → shaderlab-BdjEZUMc.js} +1 -1
- package/dist/web/assets/{shellsession-CGQjQ75I.js → shellsession-Bp1OA1rR.js} +1 -1
- package/dist/web/assets/{soy-CxY0VwX2.js → soy-DlYz3wsC.js} +1 -1
- package/dist/web/assets/{sparql-CBAGAFuq.js → sparql-CdfII5Jd.js} +1 -1
- package/dist/web/assets/sql-query-editor-PWpuw-O5.js +1 -0
- package/dist/web/assets/{sqlite-viewer-Cpq06-nz.js → sqlite-viewer-1S6cwVWa.js} +1 -1
- package/dist/web/assets/{stata-BoezhOvk.js → stata-BvUA6GUW.js} +1 -1
- package/dist/web/assets/{surrealql-CHEzAhu7.js → surrealql--DLNbTyI.js} +1 -1
- package/dist/web/assets/{svelte-y6xtu2zX.js → svelte-CBfSsG4n.js} +1 -1
- package/dist/web/assets/system-monitor-tab-U0Jh9-2v.js +1 -0
- package/dist/web/assets/{tab-store-C42sktPo.js → tab-store-DAsvpx4t.js} +1 -1
- package/dist/web/assets/team-member-window-content-DSZL4GIj.js +1 -0
- package/dist/web/assets/{templ-DbG2y5yD.js → templ-Dv8zpKZW.js} +1 -1
- package/dist/web/assets/terminal-tab-uVhX8AGG.js +3 -0
- package/dist/web/assets/{tex-CeKQhrQG.js → tex-CsrOiqh1.js} +1 -1
- package/dist/web/assets/tool-cards-QuTCNfYR.js +8 -0
- package/dist/web/assets/treemap-KZPCXAKY-Cu2zgbxU.js +1 -0
- package/dist/web/assets/{ts-tags-iKKOXJjG.js → ts-tags-3T98k4EJ.js} +1 -1
- package/dist/web/assets/{twig-DK8Vrp-G.js → twig-u6QZ45FH.js} +1 -1
- package/dist/web/assets/{use-blob-url-DYtOHK7u.js → use-blob-url-BmlYYZ8L.js} +1 -1
- package/dist/web/assets/use-is-mobile-B8FA4kQf.js +1 -0
- package/dist/web/assets/use-monaco-theme-DvmINdhI.js +1 -0
- package/dist/web/assets/use-websocket-DoZdQS0G.js +1 -0
- package/dist/web/assets/{vendor-mermaid-C977xNeE.js → vendor-mermaid-Bg4vE8QA.js} +1 -1
- package/dist/web/assets/video-preview-CzU09X0C.js +1 -0
- package/dist/web/assets/{vue-JkIEObMV.js → vue-CxIsxtwn.js} +1 -1
- package/dist/web/assets/{vue-html-D8SqnU86.js → vue-html-BGL5qnuH.js} +1 -1
- package/dist/web/assets/{vue-vine-DJj7N4WD.js → vue-vine-DHPh_gMr.js} +1 -1
- package/dist/web/assets/{xml-C-00w9Rv.js → xml-C-vM2m7J.js} +1 -1
- package/dist/web/assets/{xsl-CVdCwPHV.js → xsl-DvTXNwuO.js} +1 -1
- package/dist/web/index.html +21 -20
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +1 -0
- package/package.json +1 -1
- package/scripts/dev-quick-tunnel-retry.mjs +51 -0
- package/src/providers/claude-agent-sdk.ts +6 -0
- package/src/server/helpers/media-route-handlers.ts +66 -0
- package/src/server/helpers/range-file-response.ts +86 -0
- package/src/server/index.ts +24 -17
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/routes/files.ts +40 -7
- package/src/server/routes/fs-browse.ts +43 -5
- package/src/server/routes/fs-upload.ts +32 -0
- package/src/server/routes/teams.ts +34 -0
- package/src/server/ws/chat.ts +64 -16
- package/src/server/ws/team-inbox-watcher.ts +61 -22
- package/src/server/ws/terminal.ts +24 -6
- package/src/services/fs-ops/fs-ops-upload.service.ts +120 -0
- package/src/services/media-transcode/ffmpeg-capabilities.ts +89 -0
- package/src/services/media-transcode/media-probe.ts +63 -0
- package/src/services/media-transcode/transcode-stream.ts +129 -0
- package/src/services/subagent-transcript-merger.ts +1 -1
- package/src/services/team-member-activity/member-activity.service.ts +117 -0
- package/src/services/team-member-activity/outbound-message-scanner.ts +121 -0
- package/src/services/team-member-activity/subagent-transcript-index.ts +95 -0
- package/src/services/team-member-activity/transcript-tail-summary.ts +125 -0
- package/src/shared/background-agent-status.ts +79 -0
- package/src/types/api.ts +1 -0
- package/src/types/chat.ts +6 -1
- package/src/web/components/chat/chat-history-bar.tsx +4 -2
- package/src/web/components/chat/chat-tab.tsx +1 -0
- package/src/web/components/chat/message-input.tsx +13 -6
- package/src/web/components/chat/mini-markdown.tsx +17 -0
- package/src/web/components/chat/send-message-card.tsx +134 -0
- package/src/web/components/chat/send-message-parse.ts +159 -0
- package/src/web/components/chat/team-activity-panel.tsx +107 -121
- package/src/web/components/chat/team-member-activity-row.tsx +92 -0
- package/src/web/components/chat/team-member-list.tsx +43 -0
- package/src/web/components/chat/team-member-window-content.tsx +97 -0
- package/src/web/components/chat/team-message-badges.ts +18 -0
- package/src/web/components/chat/team-message-list.tsx +88 -0
- package/src/web/components/chat/team-message-preview.ts +87 -0
- package/src/web/components/chat/tool-cards.tsx +44 -18
- package/src/web/components/editor/audio-preview.tsx +6 -7
- package/src/web/components/editor/video-player/use-video-keyboard-shortcuts.ts +62 -0
- package/src/web/components/editor/video-player/video-player-controls.tsx +103 -0
- package/src/web/components/editor/video-player/video-player.tsx +190 -0
- package/src/web/components/editor/video-player/video-settings-menu.tsx +92 -0
- package/src/web/components/editor/video-player/video-transform.ts +49 -0
- package/src/web/components/editor/video-preview.tsx +75 -14
- package/src/web/components/explorer/file-tree.tsx +6 -0
- package/src/web/components/explorer/tree-node-context-menu.tsx +5 -1
- package/src/web/components/floating-window/window-content-registry.ts +5 -0
- package/src/web/components/floating-window/window-store-types.ts +1 -1
- package/src/web/components/os-explorer/actions/explorer-actions-upload.ts +96 -0
- package/src/web/components/os-explorer/actions/use-explorer-actions.ts +23 -2
- package/src/web/components/os-explorer/actions/use-explorer-upload-actions.tsx +41 -0
- package/src/web/components/os-explorer/dnd/use-drop-transfer.tsx +10 -1
- package/src/web/components/os-explorer/dnd/use-entry-row-dnd.ts +8 -1
- package/src/web/components/os-explorer/dnd/use-external-file-drop.ts +84 -0
- package/src/web/components/os-explorer/dnd/use-path-drop-target.ts +46 -3
- package/src/web/components/os-explorer/explorer-body.tsx +1 -1
- package/src/web/components/os-explorer/explorer-context-menu.tsx +10 -0
- package/src/web/components/os-explorer/explorer-dialogs.tsx +2 -0
- package/src/web/components/os-explorer/explorer-sidebar.tsx +10 -5
- package/src/web/components/os-explorer/explorer-toolbar.tsx +32 -2
- package/src/web/components/os-explorer/mobile/mobile-bottom-toolbar.tsx +5 -1
- package/src/web/components/os-explorer/upload/collect-dropped-entries.ts +81 -0
- package/src/web/components/os-explorer/upload/upload-queue.ts +79 -0
- package/src/web/components/os-explorer/upload/use-upload-picker.tsx +74 -0
- package/src/web/components/os-explorer/views/column-row.tsx +3 -1
- package/src/web/components/os-explorer/views/column-view-column.tsx +6 -1
- package/src/web/components/os-explorer/views/icons-view-tile.tsx +1 -1
- package/src/web/components/os-explorer/views/icons-view.tsx +5 -1
- package/src/web/components/os-explorer/views/list-row.tsx +1 -1
- package/src/web/components/os-explorer/views/list-view.tsx +5 -1
- package/src/web/components/shared/markdown-code-block.tsx +3 -7
- package/src/web/components/terminal/terminal-tab.tsx +19 -25
- package/src/web/hooks/use-chat.ts +77 -15
- package/src/web/hooks/use-team-activity-feed.ts +92 -0
- package/src/web/hooks/use-terminal-command-queue.ts +108 -0
- package/src/web/hooks/use-terminal.ts +45 -3
- package/src/web/lib/fs-api.ts +9 -0
- package/src/web/lib/fs-upload-xhr.ts +62 -0
- package/src/web/lib/media-url.ts +51 -0
- package/src/web/lib/open-terminal-at.ts +22 -0
- package/src/web/lib/run-in-terminal.ts +55 -0
- package/src/web/lib/send-to-chat.ts +118 -0
- package/src/web/lib/ws-auth.ts +13 -0
- package/src/web/lib/ws-client.ts +10 -5
- package/src/web/theme/builtin/aurora.ts +8 -5
- package/.release-run.sh +0 -5
- package/dist/web/assets/api-client-CaRE4HYh.js +0 -1
- package/dist/web/assets/api-settings-DBcXfeJ8.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-B4J2-O8Q.js +0 -1
- package/dist/web/assets/audio-preview-CikvnAOI.js +0 -1
- package/dist/web/assets/chat-tab-B3-KcCn2.js +0 -13
- package/dist/web/assets/code-editor-BpbZw13T.js +0 -10
- package/dist/web/assets/dialog-CkI7rwrh.js +0 -1
- package/dist/web/assets/diff-viewer-CT1ALWt2.js +0 -4
- package/dist/web/assets/explorer-body-CQCyGEet.js +0 -2
- package/dist/web/assets/explorer-window-content-EtyTAZSh.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CtQ3yXN-.js +0 -1
- package/dist/web/assets/glide-data-grid-DCJCz4XB.js +0 -138
- package/dist/web/assets/group-chat-tab-C3UD2bcm.js +0 -1
- package/dist/web/assets/index-BYetJoXQ.js +0 -52
- package/dist/web/assets/index-D-xwjNyX.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-Cg0mIJn9.js +0 -1
- package/dist/web/assets/keybindings-store-yBWU8ZYT.js +0 -1
- package/dist/web/assets/markdown-renderer-B2D4gdok.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-BtxyynR3.js +0 -1
- package/dist/web/assets/notification-store-DKw_mGED.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-D79vAFR_.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-DYUOH68U.js +0 -1
- package/dist/web/assets/plus-9LatwJMx.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-B1eznNtd.js +0 -1
- package/dist/web/assets/resolve-theme-B0eotaPq.js +0 -1
- package/dist/web/assets/settings-tab-ESL-6MAA.js +0 -1
- package/dist/web/assets/sql-query-editor-CZdZuawP.js +0 -1
- package/dist/web/assets/system-monitor-tab-XR8NXHpv.js +0 -1
- package/dist/web/assets/terminal-tab-CCCB-_50.js +0 -3
- package/dist/web/assets/tool-cards-CsTs5go2.js +0 -7
- package/dist/web/assets/treemap-KZPCXAKY-SeI2TxLs.js +0 -1
- package/dist/web/assets/use-is-mobile-DnFSkUG2.js +0 -1
- package/dist/web/assets/use-monaco-theme-CZh7xTJZ.js +0 -1
- package/dist/web/assets/video-preview-KbRcyuAA.js +0 -1
- package/src/web/components/chat/team-activity-popover.tsx +0 -202
- /package/dist/web/assets/{abap-DrMyiWmK.js → abap-C1K0-wzc.js} +0 -0
- /package/dist/web/assets/{actionscript-3-BlSOyi0y.js → actionscript-3-CJhQVjLA.js} +0 -0
- /package/dist/web/assets/{ada-B_m96icB.js → ada-Bt8r9fME.js} +0 -0
- /package/dist/web/assets/{andromeeda-Ch3vOiZB.js → andromeeda-DF1I8qi3.js} +0 -0
- /package/dist/web/assets/{apache-CdsaXc1n.js → apache-CnlleE6P.js} +0 -0
- /package/dist/web/assets/{apex-0AnsBevk.js → apex-j0C6Xmk6.js} +0 -0
- /package/dist/web/assets/{api-client-rTAGdEKo.js → api-client-BSB14J4w.js} +0 -0
- /package/dist/web/assets/{applescript-CSxLolzy.js → applescript-DO7AnO8U.js} +0 -0
- /package/dist/web/assets/{apply-theme-CMQXnUAa.js → apply-theme-CNj1iA-v.js} +0 -0
- /package/dist/web/assets/{ara-DXZS0jA5.js → ara-i83AydZj.js} +0 -0
- /package/dist/web/assets/{asciidoc-DDy3pH2A.js → asciidoc-DbBA6-sF.js} +0 -0
- /package/dist/web/assets/{asm-Bd17hhqg.js → asm-B5S47Rgr.js} +0 -0
- /package/dist/web/assets/{aurora-x-C62HJRs-.js → aurora-x-De_IaSKw.js} +0 -0
- /package/dist/web/assets/{awk-Cc-nazni.js → awk-CfyLHXqS.js} +0 -0
- /package/dist/web/assets/{ayu-dark-BsfmNWCU.js → ayu-dark-DTLdZ1Si.js} +0 -0
- /package/dist/web/assets/{ayu-light-BZ4HLa4X.js → ayu-light-DTOfkzhu.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-Bhyjyiqr.js → ayu-mirage-DsAECCMv.js} +0 -0
- /package/dist/web/assets/{ballerina-QiWgir3v.js → ballerina-CumGp9A7.js} +0 -0
- /package/dist/web/assets/{bat-DxtlkwN3.js → bat-Ca2SBbYN.js} +0 -0
- /package/dist/web/assets/{beancount-Bt21X7M_.js → beancount-CqyXBwwF.js} +0 -0
- /package/dist/web/assets/{berry-Cksre7lO.js → berry-yl-kJr_H.js} +0 -0
- /package/dist/web/assets/{bibtex-DkakgDlR.js → bibtex-DoFFszjV.js} +0 -0
- /package/dist/web/assets/{bicep-8crmcyXU.js → bicep-CWPi4nfK.js} +0 -0
- /package/dist/web/assets/{bird2-CewQ2I_Z.js → bird2-DXew-ut0.js} +0 -0
- /package/dist/web/assets/{bot-nk1k3ELu.js → bot-CunPuEdW.js} +0 -0
- /package/dist/web/assets/{c-C_6zE8Ha.js → c-clbySDvd.js} +0 -0
- /package/dist/web/assets/{c3-DLWB9-dp.js → c3-B65jvSM1.js} +0 -0
- /package/dist/web/assets/{cadence-D8qYS6pR.js → cadence-Cm8tQTHN.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-BzVXoV-d.js → can-open-in-ppm-Cu-XYIgu.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-Bh95lNRy.js → catppuccin-frappe-DLeGslh_.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-C1PlN3G0.js → catppuccin-latte-CR5GeHws.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-CldtgdK4.js → catppuccin-macchiato-p-XgjxhP.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-BiwqxX9A.js → catppuccin-mocha-B8QOAZLl.js} +0 -0
- /package/dist/web/assets/{clarity-CGiU22jY.js → clarity-DPulbvKH.js} +0 -0
- /package/dist/web/assets/{clojure-COIC3doh.js → clojure-DFuWS20u.js} +0 -0
- /package/dist/web/assets/{cmake-DWUEQfHv.js → cmake-DejCqcu4.js} +0 -0
- /package/dist/web/assets/{codeowners-BNHiAQea.js → codeowners-BwAyqWIH.js} +0 -0
- /package/dist/web/assets/{codeql-BuKaoS0j.js → codeql-DgSwFjqI.js} +0 -0
- /package/dist/web/assets/{common-lisp-D5IKbxsd.js → common-lisp-CxIVJCtf.js} +0 -0
- /package/dist/web/assets/{coq-BMVS-p94.js → coq-DLSjSnmf.js} +0 -0
- /package/dist/web/assets/{csharp-DMlbXbM4.js → csharp-C1HHzXBp.js} +0 -0
- /package/dist/web/assets/{css-CjkJC89X.js → css-VXsMY--y.js} +0 -0
- /package/dist/web/assets/{csv-DoNVBBf0.js → csv-BEbcJdHz.js} +0 -0
- /package/dist/web/assets/{csv-parser-DHvoTjMF.js → csv-parser-r-Nk1oHT.js} +0 -0
- /package/dist/web/assets/{cue-DoTRbBg-.js → cue-BkDnFqUD.js} +0 -0
- /package/dist/web/assets/{cypher-DVsNdTT3.js → cypher-1cMqojSQ.js} +0 -0
- /package/dist/web/assets/{d-EX7BBSrv.js → d-BnYjRXtH.js} +0 -0
- /package/dist/web/assets/{dark-plus-P_YTHwlh.js → dark-plus-XfbaH7kZ.js} +0 -0
- /package/dist/web/assets/{dart-Do-rgU4S.js → dart-Ca7s-8hK.js} +0 -0
- /package/dist/web/assets/{data-grid-types-CUx8cusN.js → data-grid-types-BXwlFMzs.js} +0 -0
- /package/dist/web/assets/{dax-DxOieacu.js → dax-CmuOw-mY.js} +0 -0
- /package/dist/web/assets/{desktop-CvdV7SBf.js → desktop-C2gJ6Ovb.js} +0 -0
- /package/dist/web/assets/{diff-CNOetGen.js → diff-BCEo1Ej7.js} +0 -0
- /package/dist/web/assets/{dist-CMfLpPW_.js → dist-CUPbqNnL.js} +0 -0
- /package/dist/web/assets/{dist-DduBIVkD.js → dist-DOlBnxFX.js} +0 -0
- /package/dist/web/assets/{docker-DAlG4_q-.js → docker-DN2jlyAh.js} +0 -0
- /package/dist/web/assets/{dotenv-Dh3qf6yx.js → dotenv-NExMY2kS.js} +0 -0
- /package/dist/web/assets/{dracula-baAGmJYG.js → dracula-B9lbE01y.js} +0 -0
- /package/dist/web/assets/{dracula-soft-l7ZIyums.js → dracula-soft-CMBBfKF_.js} +0 -0
- /package/dist/web/assets/{dream-maker-DeRjdBn8.js → dream-maker-DEyDwFsd.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-CB05-jQA.js → emacs-lisp-D_qP2zS6.js} +0 -0
- /package/dist/web/assets/{everforest-dark-CT-llQBT.js → everforest-dark-DbHa2U-p.js} +0 -0
- /package/dist/web/assets/{everforest-light-ByPJkTT7.js → everforest-light-BISkJchP.js} +0 -0
- /package/dist/web/assets/{fennel-CeMarbvn.js → fennel-6B22SfJR.js} +0 -0
- /package/dist/web/assets/{file-exclamation-point-COAwgNGw.js → file-exclamation-point-a8f3_w7a.js} +0 -0
- /package/dist/web/assets/{fish-BYrt6Xtb.js → fish-CubIIvJD.js} +0 -0
- /package/dist/web/assets/{fluent-Cr5VcAwh.js → fluent-DF55zQ09.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-DQmKtrLa.js → fortran-free-form-DvOu8jAt.js} +0 -0
- /package/dist/web/assets/{gdscript-QTDKjzhY.js → gdscript-DCPxdLpq.js} +0 -0
- /package/dist/web/assets/{gdshader-Chr-5UYt.js → gdshader-CTZ381FO.js} +0 -0
- /package/dist/web/assets/{genie-kWHMWnt0.js → genie-C_QDtvgU.js} +0 -0
- /package/dist/web/assets/{gherkin-DtEBQFUo.js → gherkin-BcvK42tF.js} +0 -0
- /package/dist/web/assets/{github-dark-B5aiUta2.js → github-dark-C9afs_4w.js} +0 -0
- /package/dist/web/assets/{github-dark-default-BkxtsTqR.js → github-dark-default-CKGVrl3Z.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-B9QjR_0K.js → github-dark-dimmed-C3dQZ_LC.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-D6bO-5c0.js → github-dark-high-contrast-DW78QukY.js} +0 -0
- /package/dist/web/assets/{github-light-Cg2ZW_CJ.js → github-light-BnP2dznS.js} +0 -0
- /package/dist/web/assets/{github-light-default-eD7qUVlQ.js → github-light-default-C2fPZzPG.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-CFnlSVjd.js → github-light-high-contrast-BzIuUFR2.js} +0 -0
- /package/dist/web/assets/{gleam-BpYoOqIT.js → gleam-BpeKwEIO.js} +0 -0
- /package/dist/web/assets/{gn-BxwtZPau.js → gn-nUb_Mit0.js} +0 -0
- /package/dist/web/assets/{gnuplot-CRyiZDfl.js → gnuplot-D7I0v20p.js} +0 -0
- /package/dist/web/assets/{go-Dw1Qx94W.js → go-D7Qqska7.js} +0 -0
- /package/dist/web/assets/{groovy-zq_SbWk-.js → groovy-DS-pne5-.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-DbbNy8-Q.js → gruvbox-dark-hard-C0W01fsq.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-BQZ7Kj6C.js → gruvbox-dark-medium-Bqo6fQbg.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-vNavErXi.js → gruvbox-dark-soft-D-XSM_fu.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-AOFbCd7i.js → gruvbox-light-hard-IVAwMJRl.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-DqtxND6H.js → gruvbox-light-medium-38ATiG7i.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-Cg0basu6.js → gruvbox-light-soft-DnZr3c_0.js} +0 -0
- /package/dist/web/assets/{haskell-CcsmcOez.js → haskell-uQcywkDH.js} +0 -0
- /package/dist/web/assets/{haxe-BimYY9X4.js → haxe-c6bI4_GT.js} +0 -0
- /package/dist/web/assets/{hcl-CTrXCK40.js → hcl-CQXTiEXq.js} +0 -0
- /package/dist/web/assets/{hjson-CmqqU6Qg.js → hjson-ChETf2bz.js} +0 -0
- /package/dist/web/assets/{hlsl-CuJtX_uf.js → hlsl-BMC4JGoE.js} +0 -0
- /package/dist/web/assets/{horizon-BGeSjDYJ.js → horizon-BusFlhLW.js} +0 -0
- /package/dist/web/assets/{horizon-bright-GYMgd3R0.js → horizon-bright-2oD0Mz7W.js} +0 -0
- /package/dist/web/assets/{houston--JD_5r2q.js → houston-DdbbjqFg.js} +0 -0
- /package/dist/web/assets/{hy-C9ILOiqk.js → hy-D_SOuqw9.js} +0 -0
- /package/dist/web/assets/{image-gallery-BWFpZAAP.js → image-gallery-CrM5fHKy.js} +0 -0
- /package/dist/web/assets/{imba-B6F3yus5.js → imba-BxLOHO5I.js} +0 -0
- /package/dist/web/assets/{ini-BfkQswMZ.js → ini-ox-CFcg0.js} +0 -0
- /package/dist/web/assets/{java-DEH5avQ1.js → java-Bzo8yIal.js} +0 -0
- /package/dist/web/assets/{javascript-Dr4yHuV2.js → javascript-DHuz9VXn.js} +0 -0
- /package/dist/web/assets/{json-BuR35ewf.js → json-0_y80FD5.js} +0 -0
- /package/dist/web/assets/{json5-Ny9ZiJAC.js → json5-CEQ50pGM.js} +0 -0
- /package/dist/web/assets/{jsonc-zYVuoZkR.js → jsonc-CfrLEAKA.js} +0 -0
- /package/dist/web/assets/{jsonl-B9xthb_H.js → jsonl-fKbrANC2.js} +0 -0
- /package/dist/web/assets/{jsonnet-CCoLJbMP.js → jsonnet-DKbkPaQ2.js} +0 -0
- /package/dist/web/assets/{jssm-W0GDlQXi.js → jssm-Dxxx1Hqu.js} +0 -0
- /package/dist/web/assets/{jsx-BrLJpWC-.js → jsx-DlJL-Oj3.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-CuVwt_MO.js → kanagawa-dragon-CwN4SV1c.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-B3W_617P.js → kanagawa-lotus-BHltayiE.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-CEx4MP5W.js → kanagawa-wave-D-KXPjw6.js} +0 -0
- /package/dist/web/assets/{kdl-DilRejnB.js → kdl-DvtoJdFo.js} +0 -0
- /package/dist/web/assets/{kotlin-Bq5JtKaa.js → kotlin-CTS4MLcx.js} +0 -0
- /package/dist/web/assets/{kusto-Bhp2KYog.js → kusto-HtSE_sKm.js} +0 -0
- /package/dist/web/assets/{laserwave-WA_7c5hu.js → laserwave-DaXZG9ge.js} +0 -0
- /package/dist/web/assets/{lean-AEGcXwS-.js → lean-BYtGUfWx.js} +0 -0
- /package/dist/web/assets/{less-NPqH-26u.js → less-BoxmU_Mk.js} +0 -0
- /package/dist/web/assets/{lib-BFetOULu.js → lib-BzxL1ARZ.js} +0 -0
- /package/dist/web/assets/{libesm-tR4XlhMj.js → libesm-ah-mr-sh.js} +0 -0
- /package/dist/web/assets/{light-plus-B4xW7cgY.js → light-plus-Bt5CyUuo.js} +0 -0
- /package/dist/web/assets/{llvm-CPyWNcDE.js → llvm-CSdk9q9V.js} +0 -0
- /package/dist/web/assets/{log-CeQmiQSz.js → log-B-kp_3CL.js} +0 -0
- /package/dist/web/assets/{logo-BO0VrjBm.js → logo-DdIsXFDO.js} +0 -0
- /package/dist/web/assets/{luau-B6GT9kA1.js → luau-DUBpLlzQ.js} +0 -0
- /package/dist/web/assets/{make-CwfmN7e_.js → make-B3JVjoW_.js} +0 -0
- /package/dist/web/assets/{markdown-Bbe4FlLp.js → markdown-Seon0vHs.js} +0 -0
- /package/dist/web/assets/{material-theme-iD9XbfsB.js → material-theme-Nq-ChX1P.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-u7M3XGe7.js → material-theme-darker-CPV4ayXJ.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-BGdI48zn.js → material-theme-lighter-0kyjYqEd.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-_mk62F5q.js → material-theme-ocean-DGpAp5co.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-CxFbDyAu.js → material-theme-palenight-B0AUn0NG.js} +0 -0
- /package/dist/web/assets/{matlab-Z0RUE3V_.js → matlab-C2waytxs.js} +0 -0
- /package/dist/web/assets/{mdx-BZ5wPYzV.js → mdx-DAis6R6I.js} +0 -0
- /package/dist/web/assets/{mermaid-CY1_aP-h.js → mermaid-_rnT2YGD.js} +0 -0
- /package/dist/web/assets/{min-dark-DKWtxOre.js → min-dark-BSSjchUB.js} +0 -0
- /package/dist/web/assets/{min-light-e1iwHDfP.js → min-light-DomAFQsa.js} +0 -0
- /package/dist/web/assets/{mipsasm-BiDfQ4ir.js → mipsasm-bBgtvPyG.js} +0 -0
- /package/dist/web/assets/{mojo-DteFEHsj.js → mojo-Cp9BayW0.js} +0 -0
- /package/dist/web/assets/{monokai-BgPbE9xZ.js → monokai-CL_9gbmn.js} +0 -0
- /package/dist/web/assets/{moonbit-mEbRp-mA.js → moonbit-BS_fcr-P.js} +0 -0
- /package/dist/web/assets/{move-CogqZw7A.js → move-C79AH45U.js} +0 -0
- /package/dist/web/assets/{narrat-BD2qjmy1.js → narrat-CJ5s3BDf.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-BNX8GOD_.js → nextflow-groovy-ZHa4JrPJ.js} +0 -0
- /package/dist/web/assets/{night-owl-ed3GbziD.js → night-owl-Jkf9zFQK.js} +0 -0
- /package/dist/web/assets/{night-owl-light-B0j7O0Wn.js → night-owl-light-DRPeaG5J.js} +0 -0
- /package/dist/web/assets/{nix-DFDbcQQm.js → nix-D2Jet9DN.js} +0 -0
- /package/dist/web/assets/{nord-D93TxEQi.js → nord-CREVs22j.js} +0 -0
- /package/dist/web/assets/{nushell-3_H_XKja.js → nushell-CAiDi_mR.js} +0 -0
- /package/dist/web/assets/{objective-c-BM3u6nlm.js → objective-c-CVkmJkC1.js} +0 -0
- /package/dist/web/assets/{objective-cpp-DZ73h42g.js → objective-cpp-DIKDO1Ly.js} +0 -0
- /package/dist/web/assets/{ocaml-B1PU5gmh.js → ocaml-R9qtGQhs.js} +0 -0
- /package/dist/web/assets/{odin-D14L6aRL.js → odin-DTYNQqVr.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-Bj-94Z8v.js → one-dark-pro-BSLWUJn5.js} +0 -0
- /package/dist/web/assets/{one-light-DTdGWyOZ.js → one-light-DbkRXG3t.js} +0 -0
- /package/dist/web/assets/{openscad-Ciq554Sj.js → openscad-BlPcSnmT.js} +0 -0
- /package/dist/web/assets/{pascal-Bfd4qdGi.js → pascal-B1Y46ZZB.js} +0 -0
- /package/dist/web/assets/{pkl-CTurDUfj.js → pkl-BhJSJbFv.js} +0 -0
- /package/dist/web/assets/{plastic-BLQfZXQp.js → plastic-BzxsCTpi.js} +0 -0
- /package/dist/web/assets/{plsql-DyYfP9kQ.js → plsql-DBba7dyP.js} +0 -0
- /package/dist/web/assets/{po-CgXdT8vQ.js → po-BvgLlnNo.js} +0 -0
- /package/dist/web/assets/{poimandres-DOg5fK6s.js → poimandres-DUaI_1JP.js} +0 -0
- /package/dist/web/assets/{polar-Dv4aoL6X.js → polar-C7uILwJn.js} +0 -0
- /package/dist/web/assets/{postcss-DxugHnJD.js → postcss-BgJlpFdw.js} +0 -0
- /package/dist/web/assets/{powerquery-CR3kzRyX.js → powerquery-Dn1MIpZu.js} +0 -0
- /package/dist/web/assets/{powershell-BE91xJQB.js → powershell-Bo-DYzOS.js} +0 -0
- /package/dist/web/assets/{prisma-B_3yUlRQ.js → prisma-CTLs5IdT.js} +0 -0
- /package/dist/web/assets/{prolog-DcT781Ht.js → prolog-QRGun3eG.js} +0 -0
- /package/dist/web/assets/{proto-CSf-w5k2.js → proto-B7PI7apn.js} +0 -0
- /package/dist/web/assets/{puppet-CLGO6QWl.js → puppet-CBq0tLrP.js} +0 -0
- /package/dist/web/assets/{purescript-Bah2McKd.js → purescript-DFewOQb2.js} +0 -0
- /package/dist/web/assets/{python-DUH8qy7N.js → python-iWuxKXvT.js} +0 -0
- /package/dist/web/assets/{qmldir-YIsHnjKq.js → qmldir-cXiW3AsF.js} +0 -0
- /package/dist/web/assets/{qss-CLmoVNA4.js → qss-DASwAbBL.js} +0 -0
- /package/dist/web/assets/{r-Pon-b-vz.js → r-TU2eaIPw.js} +0 -0
- /package/dist/web/assets/{racket-BA09P5Kg.js → racket-SkRKqrLY.js} +0 -0
- /package/dist/web/assets/{raku-DlpzU7Fe.js → raku-D31lthkZ.js} +0 -0
- /package/dist/web/assets/{react-w6wINGnd.js → react-I_EwPIZi.js} +0 -0
- /package/dist/web/assets/{red-l7VuCR6d.js → red-CTcQZevb.js} +0 -0
- /package/dist/web/assets/{reg-g_k7gQEr.js → reg-D8YAsTGf.js} +0 -0
- /package/dist/web/assets/{regexp-CT17B7I7.js → regexp-CD2uvAWx.js} +0 -0
- /package/dist/web/assets/{rel-DkPCS7Bp.js → rel-CAaI-1fg.js} +0 -0
- /package/dist/web/assets/{riscv-CYuFTw30.js → riscv-DR-hJVIj.js} +0 -0
- /package/dist/web/assets/{ron-cY7u_yEu.js → ron-CuUCJ0n4.js} +0 -0
- /package/dist/web/assets/{rose-pine-Cr_h_Ubo.js → rose-pine-aFHxW48i.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-rRewaSZg.js → rose-pine-dawn-Bqz2sBRY.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-3VX5Xwca.js → rose-pine-moon-wqyh0an4.js} +0 -0
- /package/dist/web/assets/{rosmsg-Du4ie5PU.js → rosmsg-CI1ywimO.js} +0 -0
- /package/dist/web/assets/{rust-B7xWL-Eh.js → rust-p7Imk4G_.js} +0 -0
- /package/dist/web/assets/{sass-oXbOG8de.js → sass-C8JfaqCV.js} +0 -0
- /package/dist/web/assets/{save-C4z1p51e.js → save-DRPrDfTt.js} +0 -0
- /package/dist/web/assets/{scala-nlZJhKHf.js → scala-DNxCW1By.js} +0 -0
- /package/dist/web/assets/{scheme-FZ9_Zy8D.js → scheme-BYuUPJoL.js} +0 -0
- /package/dist/web/assets/{sdbl-D_lyvXiu.js → sdbl-DbPeJPUr.js} +0 -0
- /package/dist/web/assets/{shellscript-DRWRboAd.js → shellscript-DMiHlR3q.js} +0 -0
- /package/dist/web/assets/{shield-check-C5laObJv.js → shield-check-8GObuUAn.js} +0 -0
- /package/dist/web/assets/{shield-off-DULFtyLS.js → shield-off-egKAZEwR.js} +0 -0
- /package/dist/web/assets/{slack-dark-BGk2uVHu.js → slack-dark-C4xGcQXI.js} +0 -0
- /package/dist/web/assets/{slack-ochin-PAoSX041.js → slack-ochin-BzgRt_wm.js} +0 -0
- /package/dist/web/assets/{smalltalk-qP0zvICo.js → smalltalk-LDeL9PYH.js} +0 -0
- /package/dist/web/assets/{snazzy-light-B7uccpH6.js → snazzy-light-CXE7a5j_.js} +0 -0
- /package/dist/web/assets/{solarized-dark-CbICLZs3.js → solarized-dark-DgUhY74x.js} +0 -0
- /package/dist/web/assets/{solarized-light-CprWhyjj.js → solarized-light-DD9C-H-9.js} +0 -0
- /package/dist/web/assets/{solidity-B7pcm2K6.js → solidity-CAbDB4D4.js} +0 -0
- /package/dist/web/assets/{splunk-Co2LL4J_.js → splunk-kQREPfIc.js} +0 -0
- /package/dist/web/assets/{sql-cETIdGnT.js → sql-CBzeNAD8.js} +0 -0
- /package/dist/web/assets/{ssh-config-DETG6JCD.js → ssh-config-L1euxrsM.js} +0 -0
- /package/dist/web/assets/{stylus-tmTre2-O.js → stylus-DvBnYniP.js} +0 -0
- /package/dist/web/assets/{swift-BeKQbULV.js → swift-DF5NDQp6.js} +0 -0
- /package/dist/web/assets/{synthwave-84-eCL4-Xdn.js → synthwave-84-DbBhB_Hq.js} +0 -0
- /package/dist/web/assets/{system-verilog-CviwWvEx.js → system-verilog-D8s2ZW_k.js} +0 -0
- /package/dist/web/assets/{systemd-WuoFIbBS.js → systemd-a5-XZJsG.js} +0 -0
- /package/dist/web/assets/{table-CHmxuh6M.js → table-BX2ZrJfM.js} +0 -0
- /package/dist/web/assets/{talonscript-B9VBu7VI.js → talonscript-BLa0URTW.js} +0 -0
- /package/dist/web/assets/{tasl-CN7r11ky.js → tasl-DIoV4XcC.js} +0 -0
- /package/dist/web/assets/{tcl-e1cdhe3h.js → tcl-ztYAdXfQ.js} +0 -0
- /package/dist/web/assets/{terraform-BEP0mfa_.js → terraform-DvpCCiOZ.js} +0 -0
- /package/dist/web/assets/{tokyo-night-BfA_dA37.js → tokyo-night-BkHlYUv0.js} +0 -0
- /package/dist/web/assets/{toml-BrDIfNnt.js → toml-acIrxos6.js} +0 -0
- /package/dist/web/assets/{tsv-CyfGyE4i.js → tsv-BJB5EVCT.js} +0 -0
- /package/dist/web/assets/{tsx-BrFA8Iwf.js → tsx-B1MmXGT3.js} +0 -0
- /package/dist/web/assets/{turtle-9GBtR0mP.js → turtle-DFceTiqd.js} +0 -0
- /package/dist/web/assets/{typescript-BuTRZ65V.js → typescript-Dz9wU5Y1.js} +0 -0
- /package/dist/web/assets/{typespec-CRiNNRXF.js → typespec-CPuYZijj.js} +0 -0
- /package/dist/web/assets/{typst-CmpTnnkl.js → typst-CfsCIYv8.js} +0 -0
- /package/dist/web/assets/{utils-B9H0d3Fh.js → utils-DoY4yTP-.js} +0 -0
- /package/dist/web/assets/{v-DnIi2aab.js → v-CGo0jcXA.js} +0 -0
- /package/dist/web/assets/{vala-B7VxCNZP.js → vala-AsSR0hrb.js} +0 -0
- /package/dist/web/assets/{vb-DGoQ3pBT.js → vb-C7tvbXv1.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-ClRVIJ-k.js → vendor-xterm-BeaIjRrv.js} +0 -0
- /package/dist/web/assets/{verilog-zGOC4qwV.js → verilog--a33ND-p.js} +0 -0
- /package/dist/web/assets/{vesper-DXR9-06q.js → vesper-B_Z0h4pn.js} +0 -0
- /package/dist/web/assets/{vhdl-CCO0oz0l.js → vhdl-CWL5dZPN.js} +0 -0
- /package/dist/web/assets/{viml-Dxwr-QNN.js → viml-BglmT14Y.js} +0 -0
- /package/dist/web/assets/{vitesse-black-Bhah7RxE.js → vitesse-black-RKFsSqQI.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-CGK6XVwc.js → vitesse-dark-D7zDWNmc.js} +0 -0
- /package/dist/web/assets/{vitesse-light-ne5kfw8h.js → vitesse-light-MAp87W5b.js} +0 -0
- /package/dist/web/assets/{vyper-ErDFqVoP.js → vyper-CgCBYg1G.js} +0 -0
- /package/dist/web/assets/{wasm-DtWvUWGr.js → wasm-BOG7ZeTT.js} +0 -0
- /package/dist/web/assets/{wasm-Bd258nS1.js → wasm-Bwl4txwp.js} +0 -0
- /package/dist/web/assets/{wenyan-EZe1IEYT.js → wenyan-DpShPKXZ.js} +0 -0
- /package/dist/web/assets/{wgsl-B1o5_I_9.js → wgsl-BakcCblK.js} +0 -0
- /package/dist/web/assets/{wikitext-C_rwp8An.js → wikitext-NroRWVAO.js} +0 -0
- /package/dist/web/assets/{wit-gbYtF3rY.js → wit-B5CM2pg6.js} +0 -0
- /package/dist/web/assets/{wolfram-DrcQFe2n.js → wolfram-7MTFBJpR.js} +0 -0
- /package/dist/web/assets/{x-B1qHmLdR.js → x-BcqfTY_7.js} +0 -0
- /package/dist/web/assets/{yaml-CzDAObLX.js → yaml-Bwr01G2K.js} +0 -0
- /package/dist/web/assets/{zenscript-B-J1phtg.js → zenscript-Dj8GATvi.js} +0 -0
- /package/dist/web/assets/{zig-BlzNia0O.js → zig-LAE-fbS-.js} +0 -0
|
@@ -21,17 +21,28 @@ interface WatcherCallbacks {
|
|
|
21
21
|
onConfigUpdate: (teamName: string, config: unknown) => void;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/** Start watching a team's inboxes directory + config.json for changes
|
|
25
|
-
|
|
24
|
+
/** Start watching a team's inboxes directory + config.json for changes.
|
|
25
|
+
* Snapshots are seeded from what is already on disk: a watcher can attach to a
|
|
26
|
+
* team that has been running for hours, and an unseeded snapshot would replay
|
|
27
|
+
* every historical message as "new" on the first file change. */
|
|
28
|
+
export async function startTeamInboxWatcher(
|
|
26
29
|
teamName: string,
|
|
27
30
|
callbacks: WatcherCallbacks,
|
|
28
|
-
): { watchers: FSWatcher[]; cleanup: () => void } {
|
|
31
|
+
): Promise<{ watchers: FSWatcher[]; cleanup: () => void }> {
|
|
29
32
|
const inboxDir = join(TEAMS_DIR, teamName, "inboxes");
|
|
30
33
|
const configPath = join(TEAMS_DIR, teamName, "config.json");
|
|
31
34
|
const watchers: FSWatcher[] = [];
|
|
32
35
|
const inboxSnapshots = new Map<string, number>(); // filename → last known msg count
|
|
33
36
|
const debounceTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
34
37
|
|
|
38
|
+
// Seed from current on-disk state before the first change event lands.
|
|
39
|
+
for (const file of await readInboxFiles(teamName)) {
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(await Bun.file(join(inboxDir, file)).text());
|
|
42
|
+
if (Array.isArray(parsed)) inboxSnapshots.set(file, parsed.length);
|
|
43
|
+
} catch { /* mid-write or malformed — treat as empty */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
35
46
|
// Watch inboxes directory
|
|
36
47
|
try {
|
|
37
48
|
const inboxWatcher = watch(inboxDir, (_event, filename) => {
|
|
@@ -92,6 +103,27 @@ export async function readTeamConfig(teamName: string): Promise<unknown | null>
|
|
|
92
103
|
} catch { return null; }
|
|
93
104
|
}
|
|
94
105
|
|
|
106
|
+
/** Inbox filenames (`<agent>.json`) for a team, empty when the dir is absent. */
|
|
107
|
+
async function readInboxFiles(teamName: string): Promise<string[]> {
|
|
108
|
+
try {
|
|
109
|
+
return (await readdir(join(TEAMS_DIR, teamName, "inboxes"))).filter(f => f.endsWith(".json"));
|
|
110
|
+
} catch { return []; }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Member record for an agent only known by its inbox filename. */
|
|
114
|
+
function synthesizeMember(name: string, teamName: string, status: string) {
|
|
115
|
+
return { name, agentId: `${name}@${teamName}`, agentType: "teammate", model: "unknown", status };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Whether a team directory holds anything usable.
|
|
119
|
+
* config.json is optional: Claude Code now creates the team implicitly, named
|
|
120
|
+
* after the session, and writes only inboxes/. Requiring a config would make
|
|
121
|
+
* every implicitly-created team invisible. */
|
|
122
|
+
export async function teamExists(teamName: string): Promise<boolean> {
|
|
123
|
+
if (await readTeamConfig(teamName)) return true;
|
|
124
|
+
return (await readInboxFiles(teamName)).length > 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
95
127
|
/** List all teams from ~/.claude/teams/ */
|
|
96
128
|
export async function listTeams(): Promise<unknown[]> {
|
|
97
129
|
try {
|
|
@@ -99,8 +131,20 @@ export async function listTeams(): Promise<unknown[]> {
|
|
|
99
131
|
const teams = [];
|
|
100
132
|
for (const entry of entries) {
|
|
101
133
|
if (!entry.isDirectory()) continue;
|
|
102
|
-
const config = await readTeamConfig(entry.name);
|
|
103
|
-
if (config)
|
|
134
|
+
const config = await readTeamConfig(entry.name) as any;
|
|
135
|
+
if (config) {
|
|
136
|
+
teams.push({ ...config, name: config.name ?? entry.name, implicit: false });
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
// Implicit team: membership is only discoverable from inbox filenames.
|
|
140
|
+
const inboxFiles = await readInboxFiles(entry.name);
|
|
141
|
+
if (inboxFiles.length === 0) continue;
|
|
142
|
+
teams.push({
|
|
143
|
+
name: entry.name,
|
|
144
|
+
team_name: entry.name,
|
|
145
|
+
implicit: true,
|
|
146
|
+
members: inboxFiles.map(f => synthesizeMember(f.replace(".json", ""), entry.name, "active")),
|
|
147
|
+
});
|
|
104
148
|
}
|
|
105
149
|
return teams;
|
|
106
150
|
} catch { return []; }
|
|
@@ -109,14 +153,15 @@ export async function listTeams(): Promise<unknown[]> {
|
|
|
109
153
|
/** Read team detail with merged inbox messages + inferred member status */
|
|
110
154
|
export async function readTeamDetail(teamName: string): Promise<unknown | null> {
|
|
111
155
|
const config = await readTeamConfig(teamName) as any;
|
|
112
|
-
if (!config) return null;
|
|
113
|
-
|
|
114
156
|
const inboxDir = join(TEAMS_DIR, teamName, "inboxes");
|
|
157
|
+
const inboxFiles = await readInboxFiles(teamName);
|
|
158
|
+
// Only a directory with neither a config nor any inbox is "not a team".
|
|
159
|
+
if (!config && inboxFiles.length === 0) return null;
|
|
160
|
+
const base = config ?? { name: teamName, team_name: teamName, implicit: true };
|
|
161
|
+
|
|
115
162
|
const messages: unknown[] = [];
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
inboxFiles = (await readdir(inboxDir)).filter(f => f.endsWith(".json"));
|
|
119
|
-
for (const file of inboxFiles) {
|
|
163
|
+
for (const file of inboxFiles) {
|
|
164
|
+
try {
|
|
120
165
|
const content = await Bun.file(join(inboxDir, file)).text();
|
|
121
166
|
const agentName = file.replace(".json", "");
|
|
122
167
|
const parsed = JSON.parse(content);
|
|
@@ -127,8 +172,8 @@ export async function readTeamDetail(teamName: string): Promise<unknown | null>
|
|
|
127
172
|
parsedType: inferMessageType(m.text ?? ""),
|
|
128
173
|
})));
|
|
129
174
|
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
175
|
+
} catch { /* mid-write or malformed inbox — skip this agent */ }
|
|
176
|
+
}
|
|
132
177
|
|
|
133
178
|
// Sort by timestamp
|
|
134
179
|
messages.sort((a: any, b: any) =>
|
|
@@ -136,7 +181,7 @@ export async function readTeamDetail(teamName: string): Promise<unknown | null>
|
|
|
136
181
|
);
|
|
137
182
|
|
|
138
183
|
// Infer member status from inboxes
|
|
139
|
-
const members = (
|
|
184
|
+
const members = (base.members ?? []).map((m: any) => ({
|
|
140
185
|
...m,
|
|
141
186
|
status: inferMemberStatus(messages, m.name),
|
|
142
187
|
}));
|
|
@@ -145,17 +190,11 @@ export async function readTeamDetail(teamName: string): Promise<unknown | null>
|
|
|
145
190
|
for (const file of inboxFiles) {
|
|
146
191
|
const name = file.replace(".json", "");
|
|
147
192
|
if (!members.some((m: any) => m.name === name)) {
|
|
148
|
-
members.push(
|
|
149
|
-
name,
|
|
150
|
-
agentId: `${name}@${teamName}`,
|
|
151
|
-
agentType: "teammate",
|
|
152
|
-
model: "unknown",
|
|
153
|
-
status: inferMemberStatus(messages, name),
|
|
154
|
-
});
|
|
193
|
+
members.push(synthesizeMember(name, teamName, inferMemberStatus(messages, name)));
|
|
155
194
|
}
|
|
156
195
|
}
|
|
157
196
|
|
|
158
|
-
return { ...
|
|
197
|
+
return { ...base, name: base.name ?? teamName, members, messages, memberCount: members.length };
|
|
159
198
|
}
|
|
160
199
|
|
|
161
200
|
function inferMemberStatus(messages: unknown[], agentName: string): string {
|
|
@@ -1,5 +1,23 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
1
2
|
import { terminalService } from "../../services/terminal.service.ts";
|
|
2
3
|
import { resolveProjectPath } from "../helpers/resolve-project.ts";
|
|
4
|
+
import { assertAllowed, resolvePath } from "../../services/fs-path-guard.service.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Where a new shell starts. An explicit `cwd` (explorer "Open in Terminal") wins
|
|
8
|
+
* over the project root and goes through the same allowlist as the filesystem
|
|
9
|
+
* API, so a terminal cannot be spawned anywhere the explorer could not browse.
|
|
10
|
+
*/
|
|
11
|
+
function resolveStartDir(projectName: string | undefined, cwd: string | undefined): string {
|
|
12
|
+
if (cwd) {
|
|
13
|
+
const resolved = resolvePath(cwd);
|
|
14
|
+
assertAllowed(resolved);
|
|
15
|
+
if (!statSync(resolved).isDirectory()) throw new Error("Not a directory");
|
|
16
|
+
return resolved;
|
|
17
|
+
}
|
|
18
|
+
if (projectName) return resolveProjectPath(projectName);
|
|
19
|
+
throw new Error("Session not found");
|
|
20
|
+
}
|
|
3
21
|
|
|
4
22
|
/** Control message prefix for resize commands */
|
|
5
23
|
const RESIZE_PREFIX = "\x01RESIZE:";
|
|
@@ -13,18 +31,18 @@ const PONG_MSG = "\x01PONG";
|
|
|
13
31
|
* Handles terminal session attach, input, resize, and disconnect.
|
|
14
32
|
*/
|
|
15
33
|
export const terminalWebSocket = {
|
|
16
|
-
open(ws: { data: { type: string; id: string; projectName?: string }; send: (data: string) => void }) {
|
|
17
|
-
const { id, projectName } = ws.data;
|
|
34
|
+
open(ws: { data: { type: string; id: string; projectName?: string; cwd?: string }; send: (data: string) => void }) {
|
|
35
|
+
const { id, projectName, cwd } = ws.data;
|
|
18
36
|
|
|
19
37
|
let session = id !== "new" ? terminalService.get(id) : undefined;
|
|
20
38
|
|
|
21
|
-
// If session doesn't exist and
|
|
22
|
-
if (!session && projectName) {
|
|
39
|
+
// If session doesn't exist and a start directory is known, create one
|
|
40
|
+
if (!session && (projectName || cwd)) {
|
|
23
41
|
try {
|
|
24
|
-
const
|
|
42
|
+
const startDir = resolveStartDir(projectName, cwd);
|
|
25
43
|
// Create session with the requested ID — but TerminalService generates its own ID.
|
|
26
44
|
// Instead, create and return the new session ID to client.
|
|
27
|
-
const newId = terminalService.create(
|
|
45
|
+
const newId = terminalService.create(startDir);
|
|
28
46
|
session = terminalService.get(newId);
|
|
29
47
|
if (session) {
|
|
30
48
|
// Update ws.data to reflect actual session ID
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Stats } from "node:fs";
|
|
2
|
+
import { lstat, mkdir, rename, rm } from "node:fs/promises";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
assertAllowed,
|
|
6
|
+
assertNotPpmSubtreeDeep,
|
|
7
|
+
assertNotProtected,
|
|
8
|
+
resolvePath,
|
|
9
|
+
} from "../fs-path-guard.service.ts";
|
|
10
|
+
import { eexist } from "./fs-core-ops.ts";
|
|
11
|
+
|
|
12
|
+
export interface UploadResult {
|
|
13
|
+
path: string;
|
|
14
|
+
size: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Suffix for the in-progress write. The stream lands here first and is only renamed onto
|
|
19
|
+
* the real name once it ends cleanly, so a client abort or a server crash mid-transfer never
|
|
20
|
+
* leaves a truncated file where a complete one used to be (or overwrites one at all).
|
|
21
|
+
*/
|
|
22
|
+
const TMP_SUFFIX = ".ppm-upload-tmp";
|
|
23
|
+
|
|
24
|
+
async function lstatOrNull(path: string): Promise<Stats | null> {
|
|
25
|
+
try {
|
|
26
|
+
return await lstat(path);
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Pumps a web `ReadableStream` into a file via Bun's incremental writer, chunk by chunk. Reads
|
|
34
|
+
* through `getReader()` rather than `for await`: the DOM lib's `ReadableStream` type does not
|
|
35
|
+
* (yet) declare `Symbol.asyncIterator`, even though Bun implements it at runtime.
|
|
36
|
+
*/
|
|
37
|
+
async function pumpToFile(path: string, body: ReadableStream<Uint8Array>): Promise<number> {
|
|
38
|
+
const sink = Bun.file(path).writer();
|
|
39
|
+
const reader = body.getReader();
|
|
40
|
+
let size = 0;
|
|
41
|
+
for (;;) {
|
|
42
|
+
const { done, value } = await reader.read();
|
|
43
|
+
if (done) break;
|
|
44
|
+
sink.write(value);
|
|
45
|
+
size += value.byteLength;
|
|
46
|
+
}
|
|
47
|
+
await sink.end();
|
|
48
|
+
return size;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Stream an upload body straight to disk at `path`. `body` is the raw web `ReadableStream`
|
|
53
|
+
* off the request, pumped chunk-by-chunk into a Bun `FileSink` — there is no per-file size
|
|
54
|
+
* limit here, disk is the only bound, and nothing sits fully in memory.
|
|
55
|
+
*
|
|
56
|
+
* This does *not* use `Bun.write(path, new Response(body))`, the more obvious one-liner:
|
|
57
|
+
* verified against a real `Bun.serve()` request on this host (Bun 1.3.10, Windows), that call
|
|
58
|
+
* never resolves — the handler enters it and simply hangs until the client's own timeout,
|
|
59
|
+
* even for a body a few bytes long. A `FileSink` fed by the same stream's async iterator
|
|
60
|
+
* completes normally, so that is the write path here; only `Bun.write` itself is implicated
|
|
61
|
+
* (the round-trip through `Response` and back is the one thing this path skips).
|
|
62
|
+
*
|
|
63
|
+
* `FileSink` has no `createPath` option (unlike `Bun.write`), so missing parent directories
|
|
64
|
+
* are created explicitly. The immediate parent existing as a *file* is checked before that
|
|
65
|
+
* `mkdir`: libuv reports it as `ENOTDIR` on POSIX but `EEXIST` on Windows, and the client-facing
|
|
66
|
+
* answer for "there's a file in the way" should not depend on the host OS.
|
|
67
|
+
*/
|
|
68
|
+
export async function uploadFile(
|
|
69
|
+
path: string,
|
|
70
|
+
body: ReadableStream<Uint8Array> | null,
|
|
71
|
+
overwrite: boolean,
|
|
72
|
+
): Promise<UploadResult> {
|
|
73
|
+
if (!body) {
|
|
74
|
+
throw Object.assign(new Error("Missing request body"), { status: 400, code: "EINVAL" });
|
|
75
|
+
}
|
|
76
|
+
const target = resolvePath(path);
|
|
77
|
+
assertAllowed(target);
|
|
78
|
+
// Uploading into the PPM directory would let a client overwrite the config database, the
|
|
79
|
+
// auth token or the stored provider credentials.
|
|
80
|
+
await assertNotPpmSubtreeDeep(target);
|
|
81
|
+
await assertNotProtected(target);
|
|
82
|
+
|
|
83
|
+
const existing = await lstatOrNull(target);
|
|
84
|
+
if (existing) {
|
|
85
|
+
if (existing.isDirectory()) {
|
|
86
|
+
throw Object.assign(new Error(`${target} is a directory`), { status: 400, code: "EISDIR" });
|
|
87
|
+
}
|
|
88
|
+
if (!overwrite) throw eexist(target);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const parentStat = await lstatOrNull(dirname(target));
|
|
92
|
+
if (parentStat && !parentStat.isDirectory()) {
|
|
93
|
+
throw Object.assign(new Error(`${dirname(target)} is not a directory`), {
|
|
94
|
+
status: 400,
|
|
95
|
+
code: "ENOTDIR",
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const tmpPath = `${target}${TMP_SUFFIX}`;
|
|
100
|
+
await mkdir(dirname(tmpPath), { recursive: true });
|
|
101
|
+
|
|
102
|
+
let size: number;
|
|
103
|
+
try {
|
|
104
|
+
size = await pumpToFile(tmpPath, body);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
await rm(tmpPath, { force: true }).catch(() => {});
|
|
107
|
+
throw e;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
// `rename` already replaces an existing *file* destination on both POSIX and Windows —
|
|
112
|
+
// the collision was already refused above unless the caller opted into `overwrite`.
|
|
113
|
+
await rename(tmpPath, target);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
await rm(tmpPath, { force: true }).catch(() => {});
|
|
116
|
+
throw e;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { path: target, size };
|
|
120
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discover ffmpeg/ffprobe on this machine and pick the fastest working H.264 encoder.
|
|
3
|
+
*
|
|
4
|
+
* ffmpeg is an *optional* dependency: without it PPM still plays browser-native
|
|
5
|
+
* media (mp4/webm) via Range requests, it just cannot transcode AVI/MKV. The
|
|
6
|
+
* result is cached for the process lifetime — the binary does not appear
|
|
7
|
+
* mid-session, and probing hardware encoders costs a real encode each.
|
|
8
|
+
*
|
|
9
|
+
* Hardware encoders are verified by encoding a few synthetic frames rather than by
|
|
10
|
+
* grepping `ffmpeg -encoders`: the list only says the build was compiled with
|
|
11
|
+
* NVENC/QSV support, not that a compatible GPU/driver is present.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface FfmpegCapabilities {
|
|
15
|
+
ffmpeg: string | null;
|
|
16
|
+
ffprobe: string | null;
|
|
17
|
+
/** ffmpeg encoder name (`h264_nvenc`, `libx264`…) or null when nothing encodes. */
|
|
18
|
+
encoder: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Candidate encoders in preference order; each is test-encoded before being trusted. */
|
|
22
|
+
function encoderCandidates(): string[] {
|
|
23
|
+
switch (process.platform) {
|
|
24
|
+
case "win32": return ["h264_nvenc", "h264_qsv", "h264_amf", "libx264"];
|
|
25
|
+
case "darwin": return ["h264_videotoolbox", "libx264"];
|
|
26
|
+
default: return ["h264_nvenc", "h264_qsv", "libx264"];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Per-encoder quality/speed flags tuned for "watch a file inside the IDE" latency. */
|
|
31
|
+
export function encoderArgs(encoder: string): string[] {
|
|
32
|
+
switch (encoder) {
|
|
33
|
+
case "h264_nvenc": return ["-c:v", "h264_nvenc", "-preset", "p3", "-rc", "vbr", "-cq", "26", "-b:v", "0"];
|
|
34
|
+
case "h264_qsv": return ["-c:v", "h264_qsv", "-preset", "veryfast", "-global_quality", "26"];
|
|
35
|
+
case "h264_amf": return ["-c:v", "h264_amf", "-quality", "speed", "-rc", "cqp", "-qp_i", "24", "-qp_p", "26"];
|
|
36
|
+
case "h264_videotoolbox": return ["-c:v", "h264_videotoolbox", "-b:v", "6M", "-realtime", "1"];
|
|
37
|
+
default: return ["-c:v", "libx264", "-preset", "veryfast", "-crf", "23", "-maxrate", "8M", "-bufsize", "16M"];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function encoderWorks(ffmpeg: string, encoder: string): Promise<boolean> {
|
|
42
|
+
try {
|
|
43
|
+
const proc = Bun.spawn(
|
|
44
|
+
[
|
|
45
|
+
ffmpeg, "-hide_banner", "-loglevel", "error", "-nostdin",
|
|
46
|
+
// 256px: hardware encoders reject frames below ~145×49 (NVENC "Frame Dimension less than minimum").
|
|
47
|
+
"-f", "lavfi", "-i", "color=c=black:s=256x256:r=30:d=0.2",
|
|
48
|
+
"-frames:v", "3", "-pix_fmt", "yuv420p", ...encoderArgs(encoder),
|
|
49
|
+
"-f", "null", "-",
|
|
50
|
+
],
|
|
51
|
+
{ stdout: "ignore", stderr: "ignore", stdin: "ignore" },
|
|
52
|
+
);
|
|
53
|
+
// A hung driver must not stall the whole capability probe.
|
|
54
|
+
const timer = setTimeout(() => proc.kill(), 10_000);
|
|
55
|
+
const code = await proc.exited;
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
return code === 0;
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let cached: Promise<FfmpegCapabilities> | null = null;
|
|
64
|
+
|
|
65
|
+
async function detect(): Promise<FfmpegCapabilities> {
|
|
66
|
+
const ffmpeg = Bun.which("ffmpeg");
|
|
67
|
+
const ffprobe = Bun.which("ffprobe");
|
|
68
|
+
if (!ffmpeg) return { ffmpeg: null, ffprobe, encoder: null };
|
|
69
|
+
for (const candidate of encoderCandidates()) {
|
|
70
|
+
if (await encoderWorks(ffmpeg, candidate)) return { ffmpeg, ffprobe, encoder: candidate };
|
|
71
|
+
}
|
|
72
|
+
return { ffmpeg, ffprobe, encoder: null };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Cached capability lookup; the first call pays for the encoder probes. */
|
|
76
|
+
export function getFfmpegCapabilities(): Promise<FfmpegCapabilities> {
|
|
77
|
+
if (!cached) {
|
|
78
|
+
cached = detect().catch((e) => {
|
|
79
|
+
cached = null; // let a later call retry after an unexpected failure
|
|
80
|
+
throw e;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return cached;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Test hook: drop the cache so a different PATH can be probed. */
|
|
87
|
+
export function resetFfmpegCapabilitiesCache(): void {
|
|
88
|
+
cached = null;
|
|
89
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read container/codec facts about a media file with ffprobe.
|
|
3
|
+
*
|
|
4
|
+
* The transcoded stream the player receives is a live fragmented MP4 with no
|
|
5
|
+
* duration, so the UI needs the real duration from here to draw a seek bar.
|
|
6
|
+
*/
|
|
7
|
+
import { getFfmpegCapabilities } from "./ffmpeg-capabilities.ts";
|
|
8
|
+
|
|
9
|
+
export interface MediaStreamInfo {
|
|
10
|
+
codec: string;
|
|
11
|
+
width?: number;
|
|
12
|
+
height?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MediaProbe {
|
|
16
|
+
/** Seconds; null when ffprobe could not determine it. */
|
|
17
|
+
duration: number | null;
|
|
18
|
+
container: string | null;
|
|
19
|
+
video: MediaStreamInfo | null;
|
|
20
|
+
audio: MediaStreamInfo | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface FfprobeJson {
|
|
24
|
+
format?: { format_name?: string; duration?: string };
|
|
25
|
+
streams?: Array<{ codec_type?: string; codec_name?: string; width?: number; height?: number }>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Returns null when ffprobe is unavailable or the file is not media it understands. */
|
|
29
|
+
export async function probeMedia(absPath: string): Promise<MediaProbe | null> {
|
|
30
|
+
const { ffprobe } = await getFfmpegCapabilities();
|
|
31
|
+
if (!ffprobe) return null;
|
|
32
|
+
|
|
33
|
+
const proc = Bun.spawn(
|
|
34
|
+
[
|
|
35
|
+
ffprobe, "-v", "error", "-of", "json",
|
|
36
|
+
"-show_entries", "format=format_name,duration:stream=codec_type,codec_name,width,height",
|
|
37
|
+
absPath,
|
|
38
|
+
],
|
|
39
|
+
{ stdout: "pipe", stderr: "ignore", stdin: "ignore" },
|
|
40
|
+
);
|
|
41
|
+
const timer = setTimeout(() => proc.kill(), 15_000);
|
|
42
|
+
const [text, code] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
if (code !== 0) return null;
|
|
45
|
+
|
|
46
|
+
let json: FfprobeJson;
|
|
47
|
+
try {
|
|
48
|
+
json = JSON.parse(text);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const pick = (type: string): MediaStreamInfo | null => {
|
|
53
|
+
const s = json.streams?.find((x) => x.codec_type === type && x.codec_name);
|
|
54
|
+
return s ? { codec: s.codec_name!, width: s.width, height: s.height } : null;
|
|
55
|
+
};
|
|
56
|
+
const duration = Number(json.format?.duration);
|
|
57
|
+
return {
|
|
58
|
+
duration: Number.isFinite(duration) && duration > 0 ? duration : null,
|
|
59
|
+
container: json.format?.format_name ?? null,
|
|
60
|
+
video: pick("video"),
|
|
61
|
+
audio: pick("audio"),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn ffmpeg to re-encode a file the browser cannot play (AVI/MJPEG, MKV/AC3, HEVC…)
|
|
3
|
+
* into a fragmented MP4 that streams to `<video>` while it is still being produced.
|
|
4
|
+
*
|
|
5
|
+
* Why fragmented MP4 rather than HLS: a single progressive pipe needs no segmenter,
|
|
6
|
+
* no playlist, no temp files, and starts within a second. The trade-off is that the
|
|
7
|
+
* stream has no duration and cannot be seeked natively — the client handles seeking
|
|
8
|
+
* by re-requesting with `start`, which becomes `-ss` *before* `-i` (fast keyframe
|
|
9
|
+
* seek; exact-frame seeking would decode from the file start every time).
|
|
10
|
+
*
|
|
11
|
+
* Every spawned process is tied to the response body: when the browser drops the
|
|
12
|
+
* connection (tab closed, seek, next file) the stream is cancelled and only that PID
|
|
13
|
+
* is killed — never a blanket kill by image name.
|
|
14
|
+
*/
|
|
15
|
+
import { encoderArgs, getFfmpegCapabilities } from "./ffmpeg-capabilities.ts";
|
|
16
|
+
|
|
17
|
+
/** Parallel ffmpeg jobs allowed. A seek briefly overlaps old + new job, so allow a little slack. */
|
|
18
|
+
export const MAX_CONCURRENT_TRANSCODES = 3;
|
|
19
|
+
let active = 0;
|
|
20
|
+
|
|
21
|
+
/** How many transcodes are running right now (exposed for tests/diagnostics). */
|
|
22
|
+
export function activeTranscodeCount(): number {
|
|
23
|
+
return active;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class TranscodeUnavailableError extends Error {
|
|
27
|
+
constructor(msg = "ffmpeg is not installed or has no working H.264 encoder") {
|
|
28
|
+
super(msg);
|
|
29
|
+
this.name = "TranscodeUnavailableError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class TranscodeBusyError extends Error {
|
|
34
|
+
constructor() {
|
|
35
|
+
super(`Too many concurrent transcodes (max ${MAX_CONCURRENT_TRANSCODES})`);
|
|
36
|
+
this.name = "TranscodeBusyError";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface TranscodeOptions {
|
|
41
|
+
/** Seconds to skip before encoding. */
|
|
42
|
+
start?: number;
|
|
43
|
+
/** Aborts the job when the HTTP request is aborted. */
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Build the ffmpeg argv; pure so tests can assert on it without spawning. */
|
|
48
|
+
export function buildTranscodeArgs(ffmpeg: string, absPath: string, encoder: string, start = 0): string[] {
|
|
49
|
+
return [
|
|
50
|
+
ffmpeg, "-hide_banner", "-loglevel", "error", "-nostdin",
|
|
51
|
+
...(start > 0 ? ["-ss", start.toFixed(3)] : []),
|
|
52
|
+
"-i", absPath,
|
|
53
|
+
// First video + first audio track; `?` makes the audio map optional (silent clips).
|
|
54
|
+
"-map", "0:v:0", "-map", "0:a:0?",
|
|
55
|
+
// Browsers only decode 4:2:0 H.264; cap at 1080p keeping even dimensions.
|
|
56
|
+
"-vf", "scale='min(1920,iw)':-2", "-pix_fmt", "yuv420p",
|
|
57
|
+
...encoderArgs(encoder),
|
|
58
|
+
// 2s keyframe interval at 30fps → fragments start quickly after a seek.
|
|
59
|
+
"-g", "60",
|
|
60
|
+
"-c:a", "aac", "-b:a", "128k", "-ac", "2",
|
|
61
|
+
"-movflags", "frag_keyframe+empty_moov+default_base_moof",
|
|
62
|
+
"-f", "mp4", "pipe:1",
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface TranscodeJob {
|
|
67
|
+
stream: ReadableStream<Uint8Array>;
|
|
68
|
+
encoder: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Start a transcode; the returned stream is the fMP4 body to hand to `Response`. */
|
|
72
|
+
export async function startTranscode(absPath: string, opts: TranscodeOptions = {}): Promise<TranscodeJob> {
|
|
73
|
+
const caps = await getFfmpegCapabilities();
|
|
74
|
+
if (!caps.ffmpeg || !caps.encoder) throw new TranscodeUnavailableError();
|
|
75
|
+
if (active >= MAX_CONCURRENT_TRANSCODES) throw new TranscodeBusyError();
|
|
76
|
+
|
|
77
|
+
const proc = Bun.spawn(buildTranscodeArgs(caps.ffmpeg, absPath, caps.encoder, opts.start), {
|
|
78
|
+
stdout: "pipe",
|
|
79
|
+
stderr: "pipe",
|
|
80
|
+
stdin: "ignore",
|
|
81
|
+
});
|
|
82
|
+
active++;
|
|
83
|
+
let finished = false;
|
|
84
|
+
const finish = () => {
|
|
85
|
+
if (finished) return;
|
|
86
|
+
finished = true;
|
|
87
|
+
active--;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Drain stderr so ffmpeg never blocks on a full pipe; keep the tail for the log.
|
|
91
|
+
const stderrTail = new Response(proc.stderr).text().catch(() => "");
|
|
92
|
+
proc.exited.then(async (code) => {
|
|
93
|
+
finish();
|
|
94
|
+
if (code !== 0 && code !== null && !proc.killed) {
|
|
95
|
+
const tail = (await stderrTail).trim().split("\n").slice(-3).join(" | ");
|
|
96
|
+
console.warn(`[transcode] ffmpeg exited ${code} for ${absPath}: ${tail}`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const kill = () => {
|
|
101
|
+
if (!proc.killed) proc.kill();
|
|
102
|
+
finish();
|
|
103
|
+
};
|
|
104
|
+
opts.signal?.addEventListener("abort", kill, { once: true });
|
|
105
|
+
|
|
106
|
+
// Wrap stdout so a cancelled response body (client went away) kills this process.
|
|
107
|
+
//
|
|
108
|
+
// The wrapper is not optional: handing `proc.stdout` straight to `Response`, or calling
|
|
109
|
+
// `reader.cancel()` on it, segfaults Bun 1.3.x on Windows the moment the client
|
|
110
|
+
// disconnects (guarded by tests/integration/transcode-stream-client-disconnect.test.ts).
|
|
111
|
+
// Killing the process is enough — its stdout then ends and the pending read resolves `done`.
|
|
112
|
+
const reader = proc.stdout.getReader();
|
|
113
|
+
const stream = new ReadableStream<Uint8Array>({
|
|
114
|
+
async pull(controller) {
|
|
115
|
+
const { done, value } = await reader.read();
|
|
116
|
+
if (done) {
|
|
117
|
+
try { controller.close(); } catch { /* already cancelled */ }
|
|
118
|
+
finish();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
controller.enqueue(value);
|
|
122
|
+
},
|
|
123
|
+
cancel() {
|
|
124
|
+
kill();
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return { stream, encoder: caps.encoder };
|
|
129
|
+
}
|
|
@@ -78,7 +78,7 @@ function indexAgentTranscripts(subagentsDir: string): Map<string, string> {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/** Parse one agent transcript into a flat children event list (stream order). */
|
|
81
|
-
function parseAgentTranscript(filePath: string): ChatEvent[] {
|
|
81
|
+
export function parseAgentTranscript(filePath: string): ChatEvent[] {
|
|
82
82
|
const children: ChatEvent[] = [];
|
|
83
83
|
let text: string;
|
|
84
84
|
try {
|