wind-agent-cli 1.37.2 → 1.37.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/LICENSE +718 -718
- package/README.md +1 -1
- package/dist/api-docs.html +901 -901
- package/dist/assets/{CodeEditor-B2Wf7Fg0.js → CodeEditor-BqmxpZ_D.js} +1 -1
- package/dist/assets/{FileTree-N2IkkHlv.js → FileTree-DLE7Ioai.js} +1 -1
- package/dist/assets/{GitPanel-DZCeuFZ2.js → GitPanel-f8RMRa8D.js} +1 -1
- package/dist/assets/{MarkdownPreview-C0tqYnVu.js → MarkdownPreview-93h_1tJa.js} +1 -1
- package/dist/assets/{PluginTabContent-Bnuf-fb8.js → PluginTabContent-DU1jjkXP.js} +1 -1
- package/dist/assets/{Shell-BfFnMZ-f.js → Shell-CN_2MRIM.js} +1 -1
- package/dist/assets/{StandaloneShell-QmyYwNAl.js → StandaloneShell-D0avS8UE.js} +1 -1
- package/dist/assets/{TaskMasterPanel-fMsmvN9L.js → TaskMasterPanel-Cpf1Q5Ph.js} +3 -3
- package/dist/assets/{abnfDiagram-N423BO3Z-DjPU7HLs.js → abnfDiagram-N423BO3Z-p3aJIm4T.js} +1 -1
- package/dist/assets/{arc-Pdq6Tvr3.js → arc-CTeud6Fl.js} +1 -1
- package/dist/assets/{architectureDiagram-T3A2C74G-D0JnT0Vh.js → architectureDiagram-T3A2C74G-CG7kRCJU.js} +1 -1
- package/dist/assets/{blockDiagram-VBNYF7ZC-DpaEf3mv.js → blockDiagram-VBNYF7ZC-oSjQ1SgE.js} +1 -1
- package/dist/assets/{c4Diagram-5PPSVZJV-MpqlPGFi.js → c4Diagram-5PPSVZJV-RkzF0DJV.js} +1 -1
- package/dist/assets/channel-BfQbarn9.js +1 -0
- package/dist/assets/{chunk-2GRJ4B5K-DoSdNo8u.js → chunk-2GRJ4B5K-B07uZ07O.js} +1 -1
- package/dist/assets/{chunk-2Q5K7J3B-CE7I3uMn.js → chunk-2Q5K7J3B-DNSfO7xS.js} +1 -1
- package/dist/assets/{chunk-5RXB4S5H-B4UQ8snD.js → chunk-5RXB4S5H-BTeYcgyb.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-B55XgqHZ.js → chunk-5VM5RSS4-B7vZV0zC.js} +1 -1
- package/dist/assets/{chunk-6Q2QTUOP-BS1KpefW.js → chunk-6Q2QTUOP-DuS_xUGG.js} +1 -1
- package/dist/assets/{chunk-GF5L2VYU-DkEPedJP.js → chunk-GF5L2VYU-CKLZ3iVD.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DOtkNhMm.js → chunk-JWPE2WC7-DD2hcXvs.js} +1 -1
- package/dist/assets/{chunk-KBJHAD2P-BXLjviqM.js → chunk-KBJHAD2P-Cz-l38qp.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-C50fjNNz.js → chunk-RYQCIY6F-DGFIU1YM.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-QBYqCkb7.js → chunk-XXDRQBXY-CmfzoYFl.js} +1 -1
- package/dist/assets/classDiagram-JCYQIIEL-Bpfe3kq7.js +1 -0
- package/dist/assets/classDiagram-v2-OCEON4UE-Bpfe3kq7.js +1 -0
- package/dist/assets/{code-xml-VK1YfShP.js → code-xml-CLpHzEOP.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-DrVTo1Nh.js → cose-bilkent-JH36ORCC-DlFcLXCB.js} +1 -1
- package/dist/assets/{cynefin-VYW2F7L2-DnJa_OlQ.js → cynefin-VYW2F7L2-P1HecReO.js} +1 -1
- package/dist/assets/{cynefinDiagram-MW4NZA55-CCxBjw9E.js → cynefinDiagram-MW4NZA55-COgduz5P.js} +1 -1
- package/dist/assets/{dagre-VZM6K2ZE-C_GvE4lu.js → dagre-VZM6K2ZE-Bbb-siGp.js} +1 -1
- package/dist/assets/{diagram-7IWD3JNH-pFzs9HEu.js → diagram-7IWD3JNH-9CWGFMYR.js} +1 -1
- package/dist/assets/{diagram-B4RE2ZJO-CcehQC7x.js → diagram-B4RE2ZJO-TWK59T6V.js} +1 -1
- package/dist/assets/{diagram-LBJQPF4R-hfKcrrxe.js → diagram-LBJQPF4R-ErMC9yaa.js} +1 -1
- package/dist/assets/{diagram-Q27KOJAE-C1semJfK.js → diagram-Q27KOJAE-Dt_-C7hV.js} +1 -1
- package/dist/assets/{diagram-UB23O5K3-57hvXQXO.js → diagram-UB23O5K3-BJ1DnRAF.js} +1 -1
- package/dist/assets/{ebnfDiagram-BXEA7PRR-9KIAlAVw.js → ebnfDiagram-BXEA7PRR-CE1YaQ9s.js} +1 -1
- package/dist/assets/{erDiagram-JOGREHBK-B7OzJ767.js → erDiagram-JOGREHBK-Bqk7N9aY.js} +1 -1
- package/dist/assets/{flowDiagram-UKHOOZJN-CJV8fQPR.js → flowDiagram-UKHOOZJN-DEr2II5V.js} +1 -1
- package/dist/assets/{ganttDiagram-PKOTCBZU-CbMNWWQu.js → ganttDiagram-PKOTCBZU-CWYAUeYN.js} +1 -1
- package/dist/assets/{gitGraphDiagram-DS77QQ5N-I_vZ5waO.js → gitGraphDiagram-DS77QQ5N-tdEcEIM6.js} +1 -1
- package/dist/assets/{index-ZHwIojzi.js → index-B-sTfSZq.js} +1 -1
- package/dist/assets/{index-GFhXZyl2.js → index-BSe3UiFA.js} +1 -1
- package/dist/assets/{index-DFnVoqpx.js → index-B_FDMWar.js} +5 -5
- package/dist/assets/{index-DlSeAkw_.js → index-CWJtg2pR.js} +1 -1
- package/dist/assets/{infoDiagram-6WML65LV-Dh20Tsbq.js → infoDiagram-6WML65LV-A3IqRUnO.js} +1 -1
- package/dist/assets/{ishikawaDiagram-WSZJBQD7-DH2Sm3ko.js → ishikawaDiagram-WSZJBQD7-B2uL6bkK.js} +1 -1
- package/dist/assets/{journeyDiagram-NVQOT4AX-BNFQqlVu.js → journeyDiagram-NVQOT4AX-D9JB-hRW.js} +1 -1
- package/dist/assets/{kanban-definition-27J2QSJJ-B3iL0ztJ.js → kanban-definition-27J2QSJJ-DmlgiV7K.js} +1 -1
- package/dist/assets/{linear-Cwe9dEKA.js → linear-Ct_eoJQk.js} +1 -1
- package/dist/assets/{mermaid.core-BYRQSshi.js → mermaid.core-CQ698HNn.js} +5 -5
- package/dist/assets/{mindmap-definition-FAOFIHXS-BgfBYMzr.js → mindmap-definition-FAOFIHXS-DGtMgQhf.js} +1 -1
- package/dist/assets/{pegDiagram-VL7TDLO6-CNYpECr8.js → pegDiagram-VL7TDLO6-CO6vP4n9.js} +1 -1
- package/dist/assets/{pieDiagram-7S7Q4E2Y-DeSrxc4I.js → pieDiagram-7S7Q4E2Y-DMkt_Zfr.js} +1 -1
- package/dist/assets/{quadrantDiagram-CIZ2JOQS-BXVVQsa6.js → quadrantDiagram-CIZ2JOQS-DgNxRz6t.js} +1 -1
- package/dist/assets/{railroadDiagram-AXF67PYL-JTroIW8F.js → railroadDiagram-AXF67PYL-DKz9UqA9.js} +1 -1
- package/dist/assets/{requirementDiagram-LRYGKXZP-C1Z_inLz.js → requirementDiagram-LRYGKXZP-L84XyGgV.js} +1 -1
- package/dist/assets/{sankeyDiagram-W5VNT64P-DCJgKFUk.js → sankeyDiagram-W5VNT64P-CQqWbJ3j.js} +1 -1
- package/dist/assets/{sequenceDiagram-SI44F4Z6-D8Wrx5Vc.js → sequenceDiagram-SI44F4Z6--tnzS2tb.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-CXqD68vD.js → sizeCapture-X5ZJPWSS-DujwEOtm.js} +1 -1
- package/dist/assets/{stateDiagram-OKZ733FA-CF0Ekqjo.js → stateDiagram-OKZ733FA-BK3TcZY2.js} +1 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-_medyomK.js +1 -0
- package/dist/assets/{swimlanes-SLNWSIFB-DVIax_k4.js → swimlanes-SLNWSIFB-naX-Uv1B.js} +2 -2
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-DstIv3TC.js +8 -0
- package/dist/assets/{timeline-definition-Z64GVDOM-DSqk3ApG.js → timeline-definition-Z64GVDOM-C-vP_ddU.js} +1 -1
- package/dist/assets/{vennDiagram-T6HMQDX7-DNbTqyG5.js → vennDiagram-T6HMQDX7-DN-I7o-7.js} +1 -1
- package/dist/assets/{wardleyDiagram-T6FBY63Y-BNCpsFZV.js → wardleyDiagram-T6FBY63Y-Db9dsaXv.js} +1 -1
- package/dist/assets/{xychartDiagram-ELKLHX3M-XsVZphHr.js → xychartDiagram-ELKLHX3M-DE6OxW_p.js} +1 -1
- package/dist/clear-cache.html +85 -85
- package/dist/convert-icons.md +52 -52
- package/dist/favicon.svg +8 -8
- package/dist/generate-icons.js +48 -48
- package/dist/icons/codex-white.svg +3 -3
- package/dist/icons/codex.svg +3 -3
- package/dist/icons/cursor-white.svg +11 -11
- package/dist/icons/icon-128x128.svg +11 -11
- package/dist/icons/icon-144x144.svg +11 -11
- package/dist/icons/icon-152x152.svg +11 -11
- package/dist/icons/icon-192x192.svg +11 -11
- package/dist/icons/icon-384x384.svg +11 -11
- package/dist/icons/icon-512x512.svg +11 -11
- package/dist/icons/icon-72x72.svg +11 -11
- package/dist/icons/icon-96x96.svg +11 -11
- package/dist/icons/icon-template.svg +11 -11
- package/dist/index.html +56 -56
- package/dist/logo.svg +17 -17
- package/dist/manifest.json +60 -60
- package/dist/sw.js +124 -124
- package/dist-server/server/modules/cli/cli.service.js +36 -36
- package/dist-server/server/modules/cli/sandbox.service.js +38 -38
- package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +74 -74
- package/dist-server/server/modules/commands/commands.routes.js +24 -24
- package/dist-server/server/modules/database/migrations.js +190 -190
- package/dist-server/server/modules/database/repositories/api-keys.js +3 -3
- package/dist-server/server/modules/database/repositories/github-tokens.js +2 -2
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +28 -28
- package/dist-server/server/modules/database/repositories/notification-preferences.js +4 -4
- package/dist-server/server/modules/database/repositories/projects.db.js +61 -61
- package/dist-server/server/modules/database/repositories/provider-models.js +35 -35
- package/dist-server/server/modules/database/repositories/push-subscriptions.js +5 -5
- package/dist-server/server/modules/database/repositories/scan-state.db.js +5 -5
- package/dist-server/server/modules/database/repositories/sessions.db.js +92 -92
- package/dist-server/server/modules/database/schema.js +177 -177
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +6 -6
- package/dist-server/server/modules/git/git.routes.js +17 -17
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +12 -12
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +9 -9
- package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +17 -17
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +25 -25
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +27 -27
- package/dist-server/server/modules/providers/services/provider-token-usage.service.js +9 -9
- package/dist-server/server/modules/providers/tests/mcp.test.js +3 -3
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +96 -96
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +18 -18
- package/dist-server/server/modules/taskmaster/taskmaster.routes.js +419 -419
- package/electron/cloud.js +260 -260
- package/electron/desktopNotifications.js +378 -378
- package/electron/desktopWindow.js +766 -766
- package/electron/launcher/index.html +14 -14
- package/electron/launcher/launcher.css +801 -801
- package/electron/launcher/launcher.js +687 -687
- package/electron/localServer.js +549 -549
- package/electron/main.js +944 -944
- package/electron/preload.cjs +60 -60
- package/electron/scripts/generate-macos-icon.js +62 -62
- package/electron/serverInstaller.js +277 -277
- package/electron/tabs.js +87 -87
- package/electron/viewHost.js +331 -331
- package/package.json +250 -247
- package/public/api-docs.html +901 -901
- package/scripts/fix-node-pty.js +67 -67
- package/scripts/promote-dist-server.mjs +50 -50
- package/scripts/release/build-server-bundle.js +176 -176
- package/scripts/release/prepare-desktop-app.js +152 -152
- package/server/index.ts +399 -399
- package/server/load-env.ts +47 -47
- package/server/modules/agent/agent.module.ts +52 -52
- package/server/modules/agent/agent.routes.ts +1287 -1287
- package/server/modules/agent/index.ts +2 -2
- package/server/modules/agent/tests/agent.routes.test.ts +205 -205
- package/server/modules/assets/assets.routes.ts +150 -150
- package/server/modules/assets/index.ts +3 -3
- package/server/modules/assets/services/image-assets.service.ts +127 -127
- package/server/modules/assets/tests/image-assets.service.test.ts +74 -74
- package/server/modules/auth/auth.middleware.ts +163 -163
- package/server/modules/auth/auth.module.ts +38 -38
- package/server/modules/auth/auth.routes.ts +57 -57
- package/server/modules/auth/auth.service.ts +155 -155
- package/server/modules/auth/index.ts +9 -9
- package/server/modules/auth/tests/auth.service.test.ts +95 -95
- package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -120
- package/server/modules/browser-use/browser-use-mcp.ts +386 -386
- package/server/modules/browser-use/browser-use-runtime.ts +10 -10
- package/server/modules/browser-use/browser-use.routes.ts +96 -96
- package/server/modules/browser-use/browser-use.service.ts +836 -836
- package/server/modules/browser-use/index.ts +11 -11
- package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -10
- package/server/modules/cli/cli.module.ts +93 -93
- package/server/modules/cli/cli.service.ts +248 -248
- package/server/modules/cli/cli.ts +19 -19
- package/server/modules/cli/index.ts +2 -2
- package/server/modules/cli/sandbox.service.ts +398 -398
- package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -186
- package/server/modules/cli/tests/cli.service.test.ts +86 -86
- package/server/modules/cli/tests/sandbox.service.test.ts +58 -58
- package/server/modules/commands/commands.module.ts +22 -22
- package/server/modules/commands/commands.routes.ts +586 -586
- package/server/modules/commands/index.ts +2 -2
- package/server/modules/commands/tests/commands.test.ts +109 -109
- package/server/modules/database/connection.ts +143 -143
- package/server/modules/database/index.ts +17 -17
- package/server/modules/database/init-db.ts +17 -17
- package/server/modules/database/migrations.ts +515 -515
- package/server/modules/database/repositories/api-keys.ts +119 -119
- package/server/modules/database/repositories/app-config.ts +53 -53
- package/server/modules/database/repositories/credentials.ts +106 -106
- package/server/modules/database/repositories/github-tokens.ts +100 -100
- package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -153
- package/server/modules/database/repositories/notification-preferences.ts +117 -117
- package/server/modules/database/repositories/projects.db.ts +196 -196
- package/server/modules/database/repositories/provider-models.ts +168 -168
- package/server/modules/database/repositories/push-subscriptions.ts +80 -80
- package/server/modules/database/repositories/scan-state.db.ts +42 -42
- package/server/modules/database/repositories/sessions.db.ts +524 -524
- package/server/modules/database/repositories/users.ts +140 -140
- package/server/modules/database/repositories/vapid-keys.ts +57 -57
- package/server/modules/database/schema.ts +210 -210
- package/server/modules/database/tests/projects.db.integration.test.ts +72 -72
- package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -125
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +109 -109
- package/server/modules/database/tests/sessions.db.integration.test.ts +116 -116
- package/server/modules/file-tree/file-tree.module.ts +116 -116
- package/server/modules/file-tree/file-tree.routes.ts +258 -258
- package/server/modules/file-tree/file-tree.service.ts +675 -675
- package/server/modules/file-tree/index.ts +2 -2
- package/server/modules/file-tree/tests/file-tree.routes.test.ts +157 -157
- package/server/modules/file-tree/tests/file-tree.service.test.ts +371 -371
- package/server/modules/git/git-branch.service.ts +43 -43
- package/server/modules/git/git-parsing.service.ts +66 -66
- package/server/modules/git/git.module.ts +22 -22
- package/server/modules/git/git.routes.ts +1596 -1596
- package/server/modules/git/index.ts +2 -2
- package/server/modules/git/tests/git-branch.service.test.ts +60 -60
- package/server/modules/git/tests/git-init.routes.test.ts +132 -132
- package/server/modules/git/tests/git.test.ts +106 -106
- package/server/modules/notifications/index.ts +24 -24
- package/server/modules/notifications/notifications.routes.ts +127 -127
- package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -124
- package/server/modules/notifications/services/notification-orchestrator.service.js +310 -310
- package/server/modules/notifications/tests/notification-orchestrator.integration.test.ts +55 -55
- package/server/modules/notifications/vapid-keys.service.ts +38 -38
- package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -109
- package/server/modules/plugins/index.ts +9 -9
- package/server/modules/plugins/plugin-process.service.ts +217 -217
- package/server/modules/plugins/plugin-registry.service.ts +459 -459
- package/server/modules/plugins/plugins.module.ts +33 -33
- package/server/modules/plugins/plugins.routes.ts +66 -66
- package/server/modules/plugins/plugins.service.ts +147 -147
- package/server/modules/plugins/tests/plugins.service.test.ts +32 -32
- package/server/modules/projects/index.ts +11 -11
- package/server/modules/projects/projects.routes.ts +273 -273
- package/server/modules/projects/services/project-clone.service.ts +323 -323
- package/server/modules/projects/services/project-delete.service.ts +90 -90
- package/server/modules/projects/services/project-management.service.ts +144 -144
- package/server/modules/projects/services/project-star.service.ts +78 -78
- package/server/modules/projects/services/projects-has-taskmaster.service.ts +248 -248
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +314 -314
- package/server/modules/projects/tests/project-clone.service.test.ts +183 -183
- package/server/modules/projects/tests/project-management.service.test.ts +117 -117
- package/server/modules/projects/tests/project-star.service.test.ts +123 -123
- package/server/modules/projects/tests/projects-has-taskmaster.service.test.ts +105 -105
- package/server/modules/providers/README.md +380 -380
- package/server/modules/providers/index.ts +10 -10
- package/server/modules/providers/list/claude/claude-auth.provider.ts +160 -160
- package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
- package/server/modules/providers/list/claude/claude-models.provider.ts +294 -294
- package/server/modules/providers/list/claude/claude-runtime.provider.js +1068 -1068
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +203 -203
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +684 -684
- package/server/modules/providers/list/claude/claude-skills.provider.ts +265 -265
- package/server/modules/providers/list/claude/claude.provider.ts +30 -30
- package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -100
- package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
- package/server/modules/providers/list/codex/codex-models.provider.ts +123 -123
- package/server/modules/providers/list/codex/codex-runtime.provider.js +533 -533
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +203 -203
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +938 -938
- package/server/modules/providers/list/codex/codex-skills.provider.ts +73 -73
- package/server/modules/providers/list/codex/codex.provider.ts +28 -28
- package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
- package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +119 -119
- package/server/modules/providers/list/cursor/cursor-runtime.provider.js +385 -385
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +159 -159
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +654 -654
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +39 -39
- package/server/modules/providers/list/cursor/cursor.provider.ts +30 -30
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +110 -110
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -228
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +203 -203
- package/server/modules/providers/list/opencode/opencode-runtime.provider.js +434 -434
- package/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +261 -261
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +179 -179
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +500 -500
- package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -78
- package/server/modules/providers/list/opencode/opencode.provider.ts +30 -30
- package/server/modules/providers/provider.registry.ts +36 -36
- package/server/modules/providers/provider.routes.ts +877 -877
- package/server/modules/providers/services/mcp.service.ts +109 -109
- package/server/modules/providers/services/provider-auth.service.ts +26 -26
- package/server/modules/providers/services/provider-capabilities.service.ts +97 -97
- package/server/modules/providers/services/provider-models.service.ts +401 -401
- package/server/modules/providers/services/provider-runtime.service.ts +108 -108
- package/server/modules/providers/services/provider-token-usage.service.ts +357 -357
- package/server/modules/providers/services/session-conversations-search.service.ts +1321 -1321
- package/server/modules/providers/services/session-synchronizer.service.ts +74 -74
- package/server/modules/providers/services/sessions-watcher.service.ts +247 -247
- package/server/modules/providers/services/sessions.service.ts +471 -471
- package/server/modules/providers/services/skills.service.ts +39 -39
- package/server/modules/providers/shared/base/abstract.provider.ts +32 -32
- package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
- package/server/modules/providers/shared/skills/skills.provider.ts +287 -287
- package/server/modules/providers/tests/claude-auth.test.ts +150 -150
- package/server/modules/providers/tests/claude-sessions.test.ts +61 -61
- package/server/modules/providers/tests/codex-sessions.test.ts +231 -231
- package/server/modules/providers/tests/mcp.test.ts +349 -349
- package/server/modules/providers/tests/opencode-models.test.ts +41 -41
- package/server/modules/providers/tests/opencode-sessions.test.ts +522 -522
- package/server/modules/providers/tests/provider-attachment-history.test.ts +247 -247
- package/server/modules/providers/tests/provider-models.service.test.ts +353 -353
- package/server/modules/providers/tests/provider-runtime.service.test.ts +138 -138
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +186 -186
- package/server/modules/providers/tests/provider.routes.test.ts +244 -244
- package/server/modules/providers/tests/sessions-details.test.ts +73 -73
- package/server/modules/providers/tests/sessions.service.test.ts +127 -127
- package/server/modules/providers/tests/skills.test.ts +692 -692
- package/server/modules/settings/index.ts +2 -2
- package/server/modules/settings/settings.module.ts +50 -50
- package/server/modules/settings/settings.routes.ts +51 -51
- package/server/modules/settings/settings.service.ts +186 -186
- package/server/modules/settings/tests/settings.service.test.ts +54 -54
- package/server/modules/system/index.ts +2 -2
- package/server/modules/system/system.module.ts +62 -62
- package/server/modules/system/system.routes.ts +21 -21
- package/server/modules/system/system.service.ts +76 -76
- package/server/modules/system/tests/system.service.test.ts +118 -118
- package/server/modules/taskmaster/index.ts +2 -2
- package/server/modules/taskmaster/taskmaster.module.ts +24 -24
- package/server/modules/taskmaster/taskmaster.routes.ts +1482 -1482
- package/server/modules/taskmaster/taskmaster.service.ts +132 -132
- package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +144 -144
- package/server/modules/taskmaster/tests/taskmaster.service.test.ts +97 -97
- package/server/modules/user/index.ts +2 -2
- package/server/modules/user/tests/user.service.test.ts +61 -61
- package/server/modules/user/user.module.ts +58 -58
- package/server/modules/user/user.routes.ts +50 -50
- package/server/modules/user/user.service.ts +85 -85
- package/server/modules/voice/index.ts +2 -2
- package/server/modules/voice/tests/voice.service.test.ts +98 -98
- package/server/modules/voice/voice.module.ts +47 -47
- package/server/modules/voice/voice.routes.ts +113 -113
- package/server/modules/voice/voice.service.ts +192 -192
- package/server/modules/websocket/README.md +273 -273
- package/server/modules/websocket/index.ts +3 -3
- package/server/modules/websocket/services/chat-run-registry.service.ts +343 -343
- package/server/modules/websocket/services/chat-session-writer.service.ts +145 -145
- package/server/modules/websocket/services/chat-websocket.service.ts +425 -425
- package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +65 -65
- package/server/modules/websocket/services/shell-websocket.service.ts +502 -502
- package/server/modules/websocket/services/websocket-auth.service.ts +59 -59
- package/server/modules/websocket/services/websocket-server.service.ts +124 -124
- package/server/modules/websocket/services/websocket-state.service.ts +16 -16
- package/server/modules/websocket/services/websocket-writer.service.ts +38 -38
- package/server/modules/websocket/tests/chat-attachment-filter.test.ts +59 -59
- package/server/modules/websocket/tests/chat-run-registry.test.ts +282 -282
- package/server/modules/websocket/tests/shell-websocket.service.test.ts +100 -100
- package/server/modules/websocket/tests/websocket-heartbeat.service.test.ts +78 -78
- package/server/modules/worktrees/index.ts +2 -2
- package/server/modules/worktrees/services/worktree-create-and-open.service.ts +53 -53
- package/server/modules/worktrees/services/worktree-create.service.ts +104 -104
- package/server/modules/worktrees/services/worktree-git.service.ts +207 -207
- package/server/modules/worktrees/services/worktree-list.service.ts +143 -143
- package/server/modules/worktrees/services/worktree-merge.service.ts +157 -157
- package/server/modules/worktrees/services/worktree-open.service.ts +85 -85
- package/server/modules/worktrees/services/worktree-remove.service.ts +88 -88
- package/server/modules/worktrees/tests/worktree-create-and-open.service.test.ts +68 -68
- package/server/modules/worktrees/tests/worktree-create.service.test.ts +142 -142
- package/server/modules/worktrees/tests/worktree-git.service.test.ts +84 -84
- package/server/modules/worktrees/tests/worktree-list.service.test.ts +45 -45
- package/server/modules/worktrees/tests/worktree-merge.service.test.ts +213 -213
- package/server/modules/worktrees/tests/worktree-open.service.test.ts +122 -122
- package/server/modules/worktrees/tests/worktree-remove.service.test.ts +227 -227
- package/server/modules/worktrees/tests/worktrees.routes.test.ts +199 -199
- package/server/modules/worktrees/worktrees.module.ts +113 -113
- package/server/modules/worktrees/worktrees.routes.ts +116 -116
- package/server/shared/claude-cli-path.ts +139 -139
- package/server/shared/frontmatter.ts +18 -18
- package/server/shared/image-attachments.ts +435 -435
- package/server/shared/interfaces.ts +179 -179
- package/server/shared/tests/claude-cli-path.test.ts +61 -61
- package/server/shared/tests/image-attachments.test.ts +353 -353
- package/server/shared/tests/slice-tail-page.test.ts +42 -42
- package/server/shared/types.ts +1226 -1226
- package/server/shared/utils.ts +1161 -1161
- package/server/tsconfig.json +40 -40
- package/shared/networkHosts.js +22 -22
- package/dist/assets/channel-Bm0ZLE4z.js +0 -1
- package/dist/assets/classDiagram-JCYQIIEL-DqEOcNxw.js +0 -1
- package/dist/assets/classDiagram-v2-OCEON4UE-DqEOcNxw.js +0 -1
- package/dist/assets/stateDiagram-v2-UEYNNEHI-DARLpEFJ.js +0 -1
- package/dist/assets/swimlanesDiagram-ULZ7WXOC-CrgDgaY8.js +0 -8
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
CreateWorktreeInput,
|
|
5
|
-
CreateWorktreeResult,
|
|
6
|
-
GitCommandRunner,
|
|
7
|
-
WorktreeFileSystem,
|
|
8
|
-
} from '@/shared/types.js';
|
|
9
|
-
import { AppError, normalizeProjectPath } from '@/shared/utils.js';
|
|
10
|
-
import {
|
|
11
|
-
listWorktreePorcelainEntries,
|
|
12
|
-
validateWorktreeBranchName,
|
|
13
|
-
} from '@/modules/worktrees/services/worktree-git.service.js';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Turns a branch name into a filesystem-safe folder name:
|
|
17
|
-
* "feature/login-form" → "feature-login-form".
|
|
18
|
-
*/
|
|
19
|
-
function sanitizeBranchForDirectoryName(branch: string): string {
|
|
20
|
-
const sanitized = branch
|
|
21
|
-
.replace(/[/\\:*?"<>|\s]+/g, '-')
|
|
22
|
-
.replace(/\.+$/g, '')
|
|
23
|
-
.replace(/^-+|-+$/g, '');
|
|
24
|
-
|
|
25
|
-
if (!sanitized) {
|
|
26
|
-
throw new AppError('Branch name cannot be converted to a folder name', {
|
|
27
|
-
code: 'INVALID_WORKTREE_FOLDER_NAME',
|
|
28
|
-
statusCode: 400,
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return sanitized;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Creates a new worktree in a sibling folder of the repository:
|
|
37
|
-
* `<repoParent>/<repoName>-worktrees/<branch>`. Existing local branches are
|
|
38
|
-
* checked out directly; unknown branch names are created from `baseBranch`
|
|
39
|
-
* (falling back to the main worktree's branch).
|
|
40
|
-
*/
|
|
41
|
-
export async function createWorktree(
|
|
42
|
-
input: CreateWorktreeInput,
|
|
43
|
-
dependencies: {
|
|
44
|
-
runGit: GitCommandRunner;
|
|
45
|
-
fileSystem: WorktreeFileSystem;
|
|
46
|
-
},
|
|
47
|
-
): Promise<CreateWorktreeResult> {
|
|
48
|
-
const { fileSystem, runGit } = dependencies;
|
|
49
|
-
const branch = validateWorktreeBranchName(input.branch);
|
|
50
|
-
|
|
51
|
-
const entries = await listWorktreePorcelainEntries(input.projectPath, runGit);
|
|
52
|
-
const repositoryRoot = entries[0].path;
|
|
53
|
-
|
|
54
|
-
const checkedOutElsewhere = entries.find((entry) => entry.branch === branch);
|
|
55
|
-
if (checkedOutElsewhere) {
|
|
56
|
-
throw new AppError(`Branch "${branch}" is already checked out in another worktree`, {
|
|
57
|
-
code: 'BRANCH_ALREADY_CHECKED_OUT',
|
|
58
|
-
statusCode: 409,
|
|
59
|
-
details: checkedOutElsewhere.path,
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const worktreesContainer = path.join(
|
|
64
|
-
path.dirname(repositoryRoot),
|
|
65
|
-
`${path.basename(repositoryRoot)}-worktrees`,
|
|
66
|
-
);
|
|
67
|
-
const worktreePath = normalizeProjectPath(
|
|
68
|
-
path.join(worktreesContainer, sanitizeBranchForDirectoryName(branch)),
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
if (await fileSystem.pathExists(worktreePath)) {
|
|
72
|
-
throw new AppError(`Folder already exists: ${worktreePath}`, {
|
|
73
|
-
code: 'WORKTREE_FOLDER_EXISTS',
|
|
74
|
-
statusCode: 409,
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const { stdout: branchListOutput } = await runGit(
|
|
79
|
-
['branch', '--list', branch, '--format=%(refname:short)'],
|
|
80
|
-
repositoryRoot,
|
|
81
|
-
);
|
|
82
|
-
const branchExists = branchListOutput
|
|
83
|
-
.split('\n')
|
|
84
|
-
.some((line) => line.trim() === branch);
|
|
85
|
-
|
|
86
|
-
if (branchExists) {
|
|
87
|
-
await runGit(['worktree', 'add', worktreePath, branch], repositoryRoot);
|
|
88
|
-
} else {
|
|
89
|
-
const baseBranch = input.baseBranch?.trim() || entries[0].branch;
|
|
90
|
-
if (!baseBranch) {
|
|
91
|
-
throw new AppError('Cannot determine a base branch (main worktree is detached)', {
|
|
92
|
-
code: 'WORKTREE_BASE_BRANCH_UNKNOWN',
|
|
93
|
-
statusCode: 400,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
await runGit(
|
|
98
|
-
['worktree', 'add', worktreePath, '-b', branch, validateWorktreeBranchName(baseBranch)],
|
|
99
|
-
repositoryRoot,
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return { worktreePath, branch, createdBranch: !branchExists };
|
|
104
|
-
}
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
CreateWorktreeInput,
|
|
5
|
+
CreateWorktreeResult,
|
|
6
|
+
GitCommandRunner,
|
|
7
|
+
WorktreeFileSystem,
|
|
8
|
+
} from '@/shared/types.js';
|
|
9
|
+
import { AppError, normalizeProjectPath } from '@/shared/utils.js';
|
|
10
|
+
import {
|
|
11
|
+
listWorktreePorcelainEntries,
|
|
12
|
+
validateWorktreeBranchName,
|
|
13
|
+
} from '@/modules/worktrees/services/worktree-git.service.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Turns a branch name into a filesystem-safe folder name:
|
|
17
|
+
* "feature/login-form" → "feature-login-form".
|
|
18
|
+
*/
|
|
19
|
+
function sanitizeBranchForDirectoryName(branch: string): string {
|
|
20
|
+
const sanitized = branch
|
|
21
|
+
.replace(/[/\\:*?"<>|\s]+/g, '-')
|
|
22
|
+
.replace(/\.+$/g, '')
|
|
23
|
+
.replace(/^-+|-+$/g, '');
|
|
24
|
+
|
|
25
|
+
if (!sanitized) {
|
|
26
|
+
throw new AppError('Branch name cannot be converted to a folder name', {
|
|
27
|
+
code: 'INVALID_WORKTREE_FOLDER_NAME',
|
|
28
|
+
statusCode: 400,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return sanitized;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Creates a new worktree in a sibling folder of the repository:
|
|
37
|
+
* `<repoParent>/<repoName>-worktrees/<branch>`. Existing local branches are
|
|
38
|
+
* checked out directly; unknown branch names are created from `baseBranch`
|
|
39
|
+
* (falling back to the main worktree's branch).
|
|
40
|
+
*/
|
|
41
|
+
export async function createWorktree(
|
|
42
|
+
input: CreateWorktreeInput,
|
|
43
|
+
dependencies: {
|
|
44
|
+
runGit: GitCommandRunner;
|
|
45
|
+
fileSystem: WorktreeFileSystem;
|
|
46
|
+
},
|
|
47
|
+
): Promise<CreateWorktreeResult> {
|
|
48
|
+
const { fileSystem, runGit } = dependencies;
|
|
49
|
+
const branch = validateWorktreeBranchName(input.branch);
|
|
50
|
+
|
|
51
|
+
const entries = await listWorktreePorcelainEntries(input.projectPath, runGit);
|
|
52
|
+
const repositoryRoot = entries[0].path;
|
|
53
|
+
|
|
54
|
+
const checkedOutElsewhere = entries.find((entry) => entry.branch === branch);
|
|
55
|
+
if (checkedOutElsewhere) {
|
|
56
|
+
throw new AppError(`Branch "${branch}" is already checked out in another worktree`, {
|
|
57
|
+
code: 'BRANCH_ALREADY_CHECKED_OUT',
|
|
58
|
+
statusCode: 409,
|
|
59
|
+
details: checkedOutElsewhere.path,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const worktreesContainer = path.join(
|
|
64
|
+
path.dirname(repositoryRoot),
|
|
65
|
+
`${path.basename(repositoryRoot)}-worktrees`,
|
|
66
|
+
);
|
|
67
|
+
const worktreePath = normalizeProjectPath(
|
|
68
|
+
path.join(worktreesContainer, sanitizeBranchForDirectoryName(branch)),
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (await fileSystem.pathExists(worktreePath)) {
|
|
72
|
+
throw new AppError(`Folder already exists: ${worktreePath}`, {
|
|
73
|
+
code: 'WORKTREE_FOLDER_EXISTS',
|
|
74
|
+
statusCode: 409,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const { stdout: branchListOutput } = await runGit(
|
|
79
|
+
['branch', '--list', branch, '--format=%(refname:short)'],
|
|
80
|
+
repositoryRoot,
|
|
81
|
+
);
|
|
82
|
+
const branchExists = branchListOutput
|
|
83
|
+
.split('\n')
|
|
84
|
+
.some((line) => line.trim() === branch);
|
|
85
|
+
|
|
86
|
+
if (branchExists) {
|
|
87
|
+
await runGit(['worktree', 'add', worktreePath, branch], repositoryRoot);
|
|
88
|
+
} else {
|
|
89
|
+
const baseBranch = input.baseBranch?.trim() || entries[0].branch;
|
|
90
|
+
if (!baseBranch) {
|
|
91
|
+
throw new AppError('Cannot determine a base branch (main worktree is detached)', {
|
|
92
|
+
code: 'WORKTREE_BASE_BRANCH_UNKNOWN',
|
|
93
|
+
statusCode: 400,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
await runGit(
|
|
98
|
+
['worktree', 'add', worktreePath, '-b', branch, validateWorktreeBranchName(baseBranch)],
|
|
99
|
+
repositoryRoot,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { worktreePath, branch, createdBranch: !branchExists };
|
|
104
|
+
}
|
|
@@ -1,207 +1,207 @@
|
|
|
1
|
-
// cross-spawn: drop-in spawn with Windows .cmd/PATHEXT resolution (same choice as routes/git.js).
|
|
2
|
-
import spawn from 'cross-spawn';
|
|
3
|
-
|
|
4
|
-
import type { GitCommandResult, GitCommandRunner, WorktreePorcelainEntry } from '@/shared/types.js';
|
|
5
|
-
import { AppError, normalizeProjectPath } from '@/shared/utils.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Default `GitCommandRunner`: spawns `git <args>` in `cwd` and captures output.
|
|
9
|
-
* Rejects with an `AppError` carrying git's stderr when the command fails, so
|
|
10
|
-
* callers (and ultimately the API client) see the real git diagnostic.
|
|
11
|
-
*/
|
|
12
|
-
export function runGitCommand(args: string[], cwd: string): Promise<GitCommandResult> {
|
|
13
|
-
return new Promise((resolve, reject) => {
|
|
14
|
-
const child = spawn('git', args, { cwd, shell: false });
|
|
15
|
-
|
|
16
|
-
let stdout = '';
|
|
17
|
-
let stderr = '';
|
|
18
|
-
|
|
19
|
-
child.stdout?.on('data', (data: Buffer) => {
|
|
20
|
-
stdout += data.toString();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
child.stderr?.on('data', (data: Buffer) => {
|
|
24
|
-
stderr += data.toString();
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
child.on('error', (error) => {
|
|
28
|
-
reject(
|
|
29
|
-
new AppError(`Failed to run git: ${error.message}`, {
|
|
30
|
-
code: 'GIT_SPAWN_FAILED',
|
|
31
|
-
statusCode: 500,
|
|
32
|
-
}),
|
|
33
|
-
);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
child.on('close', (code) => {
|
|
37
|
-
if (code === 0) {
|
|
38
|
-
resolve({ stdout, stderr });
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
reject(
|
|
43
|
-
new AppError(`git ${args.join(' ')} failed`, {
|
|
44
|
-
code: 'GIT_COMMAND_FAILED',
|
|
45
|
-
statusCode: 500,
|
|
46
|
-
details: (stderr || stdout).trim(),
|
|
47
|
-
}),
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Defense-in-depth branch-name validation (mirrors routes/git.js), tightened to
|
|
55
|
-
* also reject the leading-dash / lone-dot forms git itself refuses.
|
|
56
|
-
*/
|
|
57
|
-
export function validateWorktreeBranchName(branch: string): string {
|
|
58
|
-
const trimmed = branch.trim();
|
|
59
|
-
const components = trimmed.split('/');
|
|
60
|
-
if (
|
|
61
|
-
!trimmed ||
|
|
62
|
-
trimmed.startsWith('-') ||
|
|
63
|
-
trimmed.startsWith('/') ||
|
|
64
|
-
trimmed.endsWith('/') ||
|
|
65
|
-
trimmed.endsWith('.') ||
|
|
66
|
-
trimmed === '.' ||
|
|
67
|
-
trimmed === '..' ||
|
|
68
|
-
trimmed.includes('..') ||
|
|
69
|
-
trimmed.includes('//') ||
|
|
70
|
-
components.some((component) =>
|
|
71
|
-
component.startsWith('.') || component.toLowerCase().endsWith('.lock')) ||
|
|
72
|
-
!/^[a-zA-Z0-9._/-]+$/.test(trimmed)
|
|
73
|
-
) {
|
|
74
|
-
throw new AppError('Invalid branch name', {
|
|
75
|
-
code: 'INVALID_BRANCH_NAME',
|
|
76
|
-
statusCode: 400,
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
return trimmed;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Parses `git worktree list --porcelain` output. Entries are separated by blank
|
|
84
|
-
* lines; the first entry is always the main worktree. Paths come back with
|
|
85
|
-
* forward slashes even on Windows, so they are normalized here for stable
|
|
86
|
-
* comparisons against DB-stored project paths.
|
|
87
|
-
*/
|
|
88
|
-
export function parseWorktreeListPorcelain(output: string): WorktreePorcelainEntry[] {
|
|
89
|
-
const entries: WorktreePorcelainEntry[] = [];
|
|
90
|
-
let current: WorktreePorcelainEntry | null = null;
|
|
91
|
-
|
|
92
|
-
const flush = () => {
|
|
93
|
-
if (current) {
|
|
94
|
-
entries.push(current);
|
|
95
|
-
current = null;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
for (const rawLine of output.split('\n')) {
|
|
100
|
-
const line = rawLine.trimEnd();
|
|
101
|
-
if (!line) {
|
|
102
|
-
flush();
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (line.startsWith('worktree ')) {
|
|
107
|
-
flush();
|
|
108
|
-
current = {
|
|
109
|
-
path: normalizeProjectPath(line.slice('worktree '.length)),
|
|
110
|
-
headSha: null,
|
|
111
|
-
branch: null,
|
|
112
|
-
isDetached: false,
|
|
113
|
-
isLocked: false,
|
|
114
|
-
isPrunable: false,
|
|
115
|
-
};
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (!current) {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (line.startsWith('HEAD ')) {
|
|
124
|
-
current.headSha = line.slice('HEAD '.length).trim() || null;
|
|
125
|
-
} else if (line.startsWith('branch ')) {
|
|
126
|
-
// Porcelain reports the full ref, e.g. "branch refs/heads/feature/x".
|
|
127
|
-
current.branch = line.slice('branch '.length).replace(/^refs\/heads\//, '').trim() || null;
|
|
128
|
-
} else if (line === 'detached') {
|
|
129
|
-
current.isDetached = true;
|
|
130
|
-
} else if (line === 'locked' || line.startsWith('locked ')) {
|
|
131
|
-
current.isLocked = true;
|
|
132
|
-
} else if (line === 'prunable' || line.startsWith('prunable ')) {
|
|
133
|
-
current.isPrunable = true;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
flush();
|
|
138
|
-
return entries;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Lists the repository's worktrees (main worktree first) for the repo that
|
|
143
|
-
* contains `projectPath`. Throws a 400 `AppError` when the directory is not
|
|
144
|
-
* inside a git repository.
|
|
145
|
-
*/
|
|
146
|
-
export async function listWorktreePorcelainEntries(
|
|
147
|
-
projectPath: string,
|
|
148
|
-
runGit: GitCommandRunner,
|
|
149
|
-
): Promise<WorktreePorcelainEntry[]> {
|
|
150
|
-
let output: string;
|
|
151
|
-
try {
|
|
152
|
-
const result = await runGit(['worktree', 'list', '--porcelain'], projectPath);
|
|
153
|
-
output = result.stdout;
|
|
154
|
-
} catch (error) {
|
|
155
|
-
throw new AppError('Not a git repository', {
|
|
156
|
-
code: 'NOT_A_GIT_REPOSITORY',
|
|
157
|
-
statusCode: 400,
|
|
158
|
-
details: error instanceof AppError ? error.details : String(error),
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const entries = parseWorktreeListPorcelain(output);
|
|
163
|
-
if (entries.length === 0) {
|
|
164
|
-
throw new AppError('No worktrees found for repository', {
|
|
165
|
-
code: 'WORKTREE_LIST_EMPTY',
|
|
166
|
-
statusCode: 500,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return entries;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Finds the worktree entry matching `worktreePath` (normalized comparison).
|
|
175
|
-
* Throws a 404 `AppError` when the path is not a registered worktree of the
|
|
176
|
-
* repository — this is the guard that stops arbitrary paths reaching git.
|
|
177
|
-
*/
|
|
178
|
-
export function findWorktreeEntryByPath(
|
|
179
|
-
entries: WorktreePorcelainEntry[],
|
|
180
|
-
worktreePath: string,
|
|
181
|
-
): WorktreePorcelainEntry {
|
|
182
|
-
const normalized = normalizeProjectPath(worktreePath);
|
|
183
|
-
const comparable = (value: string) =>
|
|
184
|
-
process.platform === 'win32' ? value.toLowerCase() : value;
|
|
185
|
-
|
|
186
|
-
const match = entries.find((entry) => comparable(entry.path) === comparable(normalized));
|
|
187
|
-
if (!match) {
|
|
188
|
-
throw new AppError('Path is not a worktree of this repository', {
|
|
189
|
-
code: 'WORKTREE_NOT_FOUND',
|
|
190
|
-
statusCode: 404,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return match;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Counts dirty paths (`git status --porcelain`) inside one worktree. Status
|
|
199
|
-
* failures propagate so callers never mistake an unreadable worktree for clean.
|
|
200
|
-
*/
|
|
201
|
-
export async function countChangedFiles(
|
|
202
|
-
worktreePath: string,
|
|
203
|
-
runGit: GitCommandRunner,
|
|
204
|
-
): Promise<number> {
|
|
205
|
-
const { stdout } = await runGit(['status', '--porcelain'], worktreePath);
|
|
206
|
-
return stdout.split('\n').filter((line) => line.trim().length > 0).length;
|
|
207
|
-
}
|
|
1
|
+
// cross-spawn: drop-in spawn with Windows .cmd/PATHEXT resolution (same choice as routes/git.js).
|
|
2
|
+
import spawn from 'cross-spawn';
|
|
3
|
+
|
|
4
|
+
import type { GitCommandResult, GitCommandRunner, WorktreePorcelainEntry } from '@/shared/types.js';
|
|
5
|
+
import { AppError, normalizeProjectPath } from '@/shared/utils.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Default `GitCommandRunner`: spawns `git <args>` in `cwd` and captures output.
|
|
9
|
+
* Rejects with an `AppError` carrying git's stderr when the command fails, so
|
|
10
|
+
* callers (and ultimately the API client) see the real git diagnostic.
|
|
11
|
+
*/
|
|
12
|
+
export function runGitCommand(args: string[], cwd: string): Promise<GitCommandResult> {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
const child = spawn('git', args, { cwd, shell: false });
|
|
15
|
+
|
|
16
|
+
let stdout = '';
|
|
17
|
+
let stderr = '';
|
|
18
|
+
|
|
19
|
+
child.stdout?.on('data', (data: Buffer) => {
|
|
20
|
+
stdout += data.toString();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
child.stderr?.on('data', (data: Buffer) => {
|
|
24
|
+
stderr += data.toString();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
child.on('error', (error) => {
|
|
28
|
+
reject(
|
|
29
|
+
new AppError(`Failed to run git: ${error.message}`, {
|
|
30
|
+
code: 'GIT_SPAWN_FAILED',
|
|
31
|
+
statusCode: 500,
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
child.on('close', (code) => {
|
|
37
|
+
if (code === 0) {
|
|
38
|
+
resolve({ stdout, stderr });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
reject(
|
|
43
|
+
new AppError(`git ${args.join(' ')} failed`, {
|
|
44
|
+
code: 'GIT_COMMAND_FAILED',
|
|
45
|
+
statusCode: 500,
|
|
46
|
+
details: (stderr || stdout).trim(),
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Defense-in-depth branch-name validation (mirrors routes/git.js), tightened to
|
|
55
|
+
* also reject the leading-dash / lone-dot forms git itself refuses.
|
|
56
|
+
*/
|
|
57
|
+
export function validateWorktreeBranchName(branch: string): string {
|
|
58
|
+
const trimmed = branch.trim();
|
|
59
|
+
const components = trimmed.split('/');
|
|
60
|
+
if (
|
|
61
|
+
!trimmed ||
|
|
62
|
+
trimmed.startsWith('-') ||
|
|
63
|
+
trimmed.startsWith('/') ||
|
|
64
|
+
trimmed.endsWith('/') ||
|
|
65
|
+
trimmed.endsWith('.') ||
|
|
66
|
+
trimmed === '.' ||
|
|
67
|
+
trimmed === '..' ||
|
|
68
|
+
trimmed.includes('..') ||
|
|
69
|
+
trimmed.includes('//') ||
|
|
70
|
+
components.some((component) =>
|
|
71
|
+
component.startsWith('.') || component.toLowerCase().endsWith('.lock')) ||
|
|
72
|
+
!/^[a-zA-Z0-9._/-]+$/.test(trimmed)
|
|
73
|
+
) {
|
|
74
|
+
throw new AppError('Invalid branch name', {
|
|
75
|
+
code: 'INVALID_BRANCH_NAME',
|
|
76
|
+
statusCode: 400,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return trimmed;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Parses `git worktree list --porcelain` output. Entries are separated by blank
|
|
84
|
+
* lines; the first entry is always the main worktree. Paths come back with
|
|
85
|
+
* forward slashes even on Windows, so they are normalized here for stable
|
|
86
|
+
* comparisons against DB-stored project paths.
|
|
87
|
+
*/
|
|
88
|
+
export function parseWorktreeListPorcelain(output: string): WorktreePorcelainEntry[] {
|
|
89
|
+
const entries: WorktreePorcelainEntry[] = [];
|
|
90
|
+
let current: WorktreePorcelainEntry | null = null;
|
|
91
|
+
|
|
92
|
+
const flush = () => {
|
|
93
|
+
if (current) {
|
|
94
|
+
entries.push(current);
|
|
95
|
+
current = null;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
for (const rawLine of output.split('\n')) {
|
|
100
|
+
const line = rawLine.trimEnd();
|
|
101
|
+
if (!line) {
|
|
102
|
+
flush();
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (line.startsWith('worktree ')) {
|
|
107
|
+
flush();
|
|
108
|
+
current = {
|
|
109
|
+
path: normalizeProjectPath(line.slice('worktree '.length)),
|
|
110
|
+
headSha: null,
|
|
111
|
+
branch: null,
|
|
112
|
+
isDetached: false,
|
|
113
|
+
isLocked: false,
|
|
114
|
+
isPrunable: false,
|
|
115
|
+
};
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!current) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (line.startsWith('HEAD ')) {
|
|
124
|
+
current.headSha = line.slice('HEAD '.length).trim() || null;
|
|
125
|
+
} else if (line.startsWith('branch ')) {
|
|
126
|
+
// Porcelain reports the full ref, e.g. "branch refs/heads/feature/x".
|
|
127
|
+
current.branch = line.slice('branch '.length).replace(/^refs\/heads\//, '').trim() || null;
|
|
128
|
+
} else if (line === 'detached') {
|
|
129
|
+
current.isDetached = true;
|
|
130
|
+
} else if (line === 'locked' || line.startsWith('locked ')) {
|
|
131
|
+
current.isLocked = true;
|
|
132
|
+
} else if (line === 'prunable' || line.startsWith('prunable ')) {
|
|
133
|
+
current.isPrunable = true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
flush();
|
|
138
|
+
return entries;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Lists the repository's worktrees (main worktree first) for the repo that
|
|
143
|
+
* contains `projectPath`. Throws a 400 `AppError` when the directory is not
|
|
144
|
+
* inside a git repository.
|
|
145
|
+
*/
|
|
146
|
+
export async function listWorktreePorcelainEntries(
|
|
147
|
+
projectPath: string,
|
|
148
|
+
runGit: GitCommandRunner,
|
|
149
|
+
): Promise<WorktreePorcelainEntry[]> {
|
|
150
|
+
let output: string;
|
|
151
|
+
try {
|
|
152
|
+
const result = await runGit(['worktree', 'list', '--porcelain'], projectPath);
|
|
153
|
+
output = result.stdout;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
throw new AppError('Not a git repository', {
|
|
156
|
+
code: 'NOT_A_GIT_REPOSITORY',
|
|
157
|
+
statusCode: 400,
|
|
158
|
+
details: error instanceof AppError ? error.details : String(error),
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const entries = parseWorktreeListPorcelain(output);
|
|
163
|
+
if (entries.length === 0) {
|
|
164
|
+
throw new AppError('No worktrees found for repository', {
|
|
165
|
+
code: 'WORKTREE_LIST_EMPTY',
|
|
166
|
+
statusCode: 500,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return entries;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Finds the worktree entry matching `worktreePath` (normalized comparison).
|
|
175
|
+
* Throws a 404 `AppError` when the path is not a registered worktree of the
|
|
176
|
+
* repository — this is the guard that stops arbitrary paths reaching git.
|
|
177
|
+
*/
|
|
178
|
+
export function findWorktreeEntryByPath(
|
|
179
|
+
entries: WorktreePorcelainEntry[],
|
|
180
|
+
worktreePath: string,
|
|
181
|
+
): WorktreePorcelainEntry {
|
|
182
|
+
const normalized = normalizeProjectPath(worktreePath);
|
|
183
|
+
const comparable = (value: string) =>
|
|
184
|
+
process.platform === 'win32' ? value.toLowerCase() : value;
|
|
185
|
+
|
|
186
|
+
const match = entries.find((entry) => comparable(entry.path) === comparable(normalized));
|
|
187
|
+
if (!match) {
|
|
188
|
+
throw new AppError('Path is not a worktree of this repository', {
|
|
189
|
+
code: 'WORKTREE_NOT_FOUND',
|
|
190
|
+
statusCode: 404,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return match;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Counts dirty paths (`git status --porcelain`) inside one worktree. Status
|
|
199
|
+
* failures propagate so callers never mistake an unreadable worktree for clean.
|
|
200
|
+
*/
|
|
201
|
+
export async function countChangedFiles(
|
|
202
|
+
worktreePath: string,
|
|
203
|
+
runGit: GitCommandRunner,
|
|
204
|
+
): Promise<number> {
|
|
205
|
+
const { stdout } = await runGit(['status', '--porcelain'], worktreePath);
|
|
206
|
+
return stdout.split('\n').filter((line) => line.trim().length > 0).length;
|
|
207
|
+
}
|