windows-development-sandbox 1.20.0 → 1.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. package/config.example.json +8 -1
  2. package/dist/app/features/access/access.def.js +5 -2
  3. package/dist/app/features/access/access.run.js +7 -3
  4. package/dist/app/features/accounts/accounts.def.d.ts +29 -1
  5. package/dist/app/features/accounts/accounts.def.js +10 -4
  6. package/dist/app/features/accounts/accounts.run.d.ts +1 -0
  7. package/dist/app/features/accounts/accounts.run.js +7 -2
  8. package/dist/app/features/accounts/services.def.d.ts +4 -1
  9. package/dist/app/features/accounts/services.def.js +15 -14
  10. package/dist/app/features/accounts/services.run.d.ts +1 -0
  11. package/dist/app/features/accounts/services.run.js +41 -15
  12. package/dist/app/features/defs.d.ts +39 -2
  13. package/dist/app/features/engine/engine.run.js +1 -1
  14. package/dist/app/features/github/github.def.d.ts +44 -3
  15. package/dist/app/features/github/github.def.js +145 -63
  16. package/dist/app/features/github/github.run.d.ts +9 -2
  17. package/dist/app/features/github/github.run.js +43 -5
  18. package/dist/app/features/health/health.def.js +8 -2
  19. package/dist/app/features/logs/logs.def.js +0 -8
  20. package/dist/app/features/projects/projects.def.js +2 -2
  21. package/dist/app/features/projects/projects.run.js +5 -5
  22. package/dist/app/features/sandbox/sandbox.def.js +7 -3
  23. package/dist/app/features/sessions/sessions.def.d.ts +4 -4
  24. package/dist/app/features/sessions/sessions.def.js +0 -5
  25. package/dist/app/features/sessions/sessions.run.d.ts +1 -1
  26. package/dist/app/features/sessions/sessions.run.js +2 -2
  27. package/dist/app/features/settings/categories.def.js +2 -0
  28. package/dist/app/features/settings/settings-rows.js +1 -1
  29. package/dist/app/features/settings/settings.def.js +1 -1
  30. package/dist/app/features/settings/settings.run.js +3 -3
  31. package/dist/app/features/storage/storage.run.js +18 -9
  32. package/dist/app/features/updates/update-lines.js +2 -2
  33. package/dist/app/features/updates/updates.def.js +1 -1
  34. package/dist/app/features/updates/updates.run.js +1 -1
  35. package/dist/app/features/web/web.def.js +2 -2
  36. package/dist/app/features/web/web.run.js +12 -4
  37. package/dist/app/kernel/errors.d.ts +7 -0
  38. package/dist/app/kernel/errors.js +18 -3
  39. package/dist/app/kernel/rpc.d.ts +5 -0
  40. package/dist/app/kernel/view.d.ts +11 -1
  41. package/dist/bin/agent-sandbox.js +2 -2
  42. package/dist/bin/{chunk-DK4NTBUF.js → chunk-7YPEDAJD.js} +172 -48
  43. package/dist/bin/{chunk-2IMT7ZCT.js → chunk-QFSJX2KW.js} +1 -1
  44. package/dist/bin/{chunk-Z2P5OGKL.js → chunk-USTRSHIE.js} +2436 -1693
  45. package/dist/bin/claude-host.js +2 -2
  46. package/dist/bin/claude-sbx.js +2 -2
  47. package/dist/bin/codex-sbx.js +2 -2
  48. package/dist/bin/gemini-sbx.js +2 -2
  49. package/dist/bin/sandbox.js +2 -2
  50. package/dist/bin/sbx-hide.js +2 -2
  51. package/dist/bin/sbx-show.js +2 -2
  52. package/dist/cli/manifest.d.ts +2 -2
  53. package/dist/cli/manifest.js +2 -1
  54. package/dist/cli/menu/coverage-exempt.d.ts +17 -0
  55. package/dist/cli/menu/coverage-exempt.js +61 -0
  56. package/dist/cli/menu/io-menu.d.ts +2 -0
  57. package/dist/cli/menu/io-menu.js +50 -5
  58. package/dist/cli/menu/pages/accounts-page.js +1 -1
  59. package/dist/cli/menu/pages/agents-page.js +8 -2
  60. package/dist/cli/menu/pages/maintenance-page.d.ts +6 -0
  61. package/dist/cli/menu/pages/maintenance-page.js +9 -2
  62. package/dist/cli/menu/pages/sessions-page.d.ts +2 -0
  63. package/dist/cli/menu/pages/sessions-page.js +12 -4
  64. package/dist/cli/menu/pages/settings-page.js +12 -1
  65. package/dist/cli/menu/remote-menu.js +1 -1
  66. package/dist/cli/menu/shell.d.ts +5 -0
  67. package/dist/cli/menu/shell.js +13 -0
  68. package/dist/cli/report.js +2 -0
  69. package/dist/cli/tui/app.d.ts +5 -0
  70. package/dist/cli/tui/app.js +2 -0
  71. package/dist/core/config/defaults.js +3 -1
  72. package/dist/core/schema/accounts.d.ts +6 -0
  73. package/dist/core/schema/accounts.js +13 -11
  74. package/dist/core/schema/config.d.ts +20 -8
  75. package/dist/core/schema/config.js +4 -4
  76. package/dist/core/schema/github.js +1 -1
  77. package/dist/core/schema/profile.js +8 -4
  78. package/dist/core/schema/resume.js +1 -1
  79. package/dist/core/schema/sandbox.js +3 -3
  80. package/dist/core/schema/vpn.js +1 -1
  81. package/dist/core/schema/web.js +3 -3
  82. package/dist/core/types.d.ts +6 -0
  83. package/dist/core/util/errors.d.ts +11 -1
  84. package/dist/core/util/errors.js +7 -1
  85. package/dist/core/util/index.d.ts +1 -1
  86. package/dist/core/util/index.js +1 -1
  87. package/dist/guest/accounts/adapter.d.ts +2 -0
  88. package/dist/guest/accounts/adapters.js +4 -0
  89. package/dist/guest/accounts/apply.d.ts +6 -2
  90. package/dist/guest/accounts/kinds/cfx.d.ts +7 -0
  91. package/dist/guest/accounts/kinds/cfx.js +33 -3
  92. package/dist/guest/accounts/kinds/comet.d.ts +7 -0
  93. package/dist/guest/accounts/kinds/comet.js +50 -0
  94. package/dist/guest/accounts/kinds/perplexity.d.ts +2 -0
  95. package/dist/guest/accounts/kinds/perplexity.js +12 -0
  96. package/dist/guest/accounts/watch.d.ts +2 -0
  97. package/dist/guest/accounts/watch.js +12 -2
  98. package/dist/guest/agents/agent-installer.js +1 -1
  99. package/dist/guest/agents/profile-links.d.ts +2 -0
  100. package/dist/guest/agents/profile-links.js +18 -4
  101. package/dist/guest/apps/after-install.js +19 -0
  102. package/dist/guest/apps/app-installer.d.ts +11 -0
  103. package/dist/guest/apps/app-installer.js +69 -6
  104. package/dist/guest/apps/comet-unpack.d.ts +20 -0
  105. package/dist/guest/apps/comet-unpack.js +127 -0
  106. package/dist/guest/apps/index.d.ts +1 -1
  107. package/dist/guest/apps/index.js +1 -1
  108. package/dist/guest/apps/resolvers-agents-web.d.ts +2 -0
  109. package/dist/guest/apps/resolvers-agents-web.js +7 -0
  110. package/dist/guest/apps/resolvers.js +2 -0
  111. package/dist/guest/apps/shortcuts.d.ts +2 -0
  112. package/dist/guest/apps/shortcuts.js +1 -0
  113. package/dist/guest/apps/unpack.js +14 -4
  114. package/dist/guest/boot/boot.d.ts +45 -16
  115. package/dist/guest/boot/boot.js +316 -145
  116. package/dist/guest/boot/graph.d.ts +252 -0
  117. package/dist/guest/boot/graph.js +257 -0
  118. package/dist/guest/boot/guide-lines.d.ts +25 -0
  119. package/dist/guest/boot/guide-lines.js +41 -0
  120. package/dist/guest/boot/guide.d.ts +1 -4
  121. package/dist/guest/boot/guide.js +6 -8
  122. package/dist/guest/boot/runner.d.ts +77 -0
  123. package/dist/guest/boot/runner.js +215 -0
  124. package/dist/guest/browser/cdp.d.ts +71 -0
  125. package/dist/guest/browser/cdp.js +306 -0
  126. package/dist/guest/browser/comet.d.ts +28 -0
  127. package/dist/guest/browser/comet.js +155 -0
  128. package/dist/guest/daemon/project-provisioner.js +6 -1
  129. package/dist/guest/devtools/fivem.d.ts +1 -1
  130. package/dist/guest/devtools/fivem.js +243 -3
  131. package/dist/guest/entries/daemon.js +2 -0
  132. package/dist/guest/runtime/log.d.ts +12 -0
  133. package/dist/guest/runtime/log.js +29 -5
  134. package/dist/guest/runtime/pools.d.ts +24 -0
  135. package/dist/guest/runtime/pools.js +60 -0
  136. package/dist/guest/runtime/process.d.ts +4 -0
  137. package/dist/guest/runtime/process.js +38 -1
  138. package/dist/guest/runtime/status.d.ts +5 -1
  139. package/dist/guest/runtime/status.js +19 -0
  140. package/dist/guest/setup/addon-mcp.d.ts +1 -1
  141. package/dist/guest/setup/addon-mcp.js +52 -12
  142. package/dist/guest/setup/agent-addons.d.ts +3 -2
  143. package/dist/guest/setup/agent-addons.js +12 -3
  144. package/dist/guest/setup/environment.d.ts +1 -1
  145. package/dist/guest/setup/environment.js +25 -12
  146. package/dist/guest/setup/headroom.d.ts +8 -3
  147. package/dist/guest/setup/headroom.js +44 -15
  148. package/dist/guest/setup/path-plan.d.ts +28 -0
  149. package/dist/guest/setup/path-plan.js +69 -2
  150. package/dist/guest/setup/shell-path.d.ts +1 -1
  151. package/dist/guest/setup/shell-path.js +3 -3
  152. package/dist/guest/toolchains/fivem/artifacts.d.ts +48 -1
  153. package/dist/guest/toolchains/fivem/artifacts.js +97 -4
  154. package/dist/guest/toolchains/fivem/fivem.d.ts +32 -1
  155. package/dist/guest/toolchains/fivem/fivem.js +168 -3
  156. package/dist/guest/toolchains/fivem/scaffold.d.ts +35 -0
  157. package/dist/guest/toolchains/fivem/scaffold.js +105 -0
  158. package/dist/guest/toolchains/fivem/serve-plan.d.ts +34 -0
  159. package/dist/guest/toolchains/fivem/serve-plan.js +61 -0
  160. package/dist/guest/toolchains/languages/go.js +1 -1
  161. package/dist/guest/toolchains/languages/node.js +2 -1
  162. package/dist/guest/toolchains/languages/php.d.ts +2 -0
  163. package/dist/guest/toolchains/languages/php.js +13 -1
  164. package/dist/guest/toolchains/languages/python-tools.js +1 -1
  165. package/dist/guest/toolchains/languages/ruby.js +1 -1
  166. package/dist/guest/toolchains/native/binstall.js +1 -1
  167. package/dist/guest/toolchains/native/msvc/packages.js +10 -4
  168. package/dist/guest/toolchains/native/rust.d.ts +2 -0
  169. package/dist/guest/toolchains/native/rust.js +11 -4
  170. package/dist/guest/toolchains/native/unreal.d.ts +2 -0
  171. package/dist/guest/toolchains/native/unreal.js +16 -6
  172. package/dist/guest/toolchains/native/vcruntime.d.ts +2 -0
  173. package/dist/guest/toolchains/native/vcruntime.js +10 -2
  174. package/dist/guest/toolchains/native/visual-studio.d.ts +3 -0
  175. package/dist/guest/toolchains/native/visual-studio.js +44 -26
  176. package/dist/guest/toolchains/native/vs-request.d.ts +2 -0
  177. package/dist/guest/toolchains/native/vs-request.js +2 -0
  178. package/dist/guest/toolchains/plan.js +19 -8
  179. package/dist/guest/toolchains/scheduler.d.ts +2 -21
  180. package/dist/guest/toolchains/scheduler.js +7 -121
  181. package/dist/guest/toolchains/setup.d.ts +15 -2
  182. package/dist/guest/toolchains/setup.js +39 -20
  183. package/dist/guest/toolchains/split.d.ts +28 -0
  184. package/dist/guest/toolchains/split.js +60 -0
  185. package/dist/guest/toolchains/steam/client.js +2 -1
  186. package/dist/guest/toolchains/step-scheduler.d.ts +51 -0
  187. package/dist/guest/toolchains/step-scheduler.js +147 -0
  188. package/dist/guest/toolchains/step.d.ts +13 -0
  189. package/dist/guest/toolchains/tools/git.js +18 -0
  190. package/dist/guest/vpn/openvpn.js +3 -4
  191. package/dist/guest/vpn/wireguard.js +11 -4
  192. package/dist/host/accounts/deliver.js +7 -0
  193. package/dist/host/accounts/handoff.js +2 -6
  194. package/dist/host/accounts/index.d.ts +1 -1
  195. package/dist/host/accounts/index.js +1 -1
  196. package/dist/host/accounts/kinds/cfx.d.ts +1 -1
  197. package/dist/host/accounts/kinds/cfx.js +47 -4
  198. package/dist/host/accounts/kinds/comet.d.ts +2 -0
  199. package/dist/host/accounts/kinds/comet.js +6 -0
  200. package/dist/host/accounts/kinds/perplexity.d.ts +2 -0
  201. package/dist/host/accounts/kinds/perplexity.js +30 -0
  202. package/dist/host/accounts/leases.d.ts +7 -1
  203. package/dist/host/accounts/leases.js +13 -2
  204. package/dist/host/accounts/source.d.ts +7 -1
  205. package/dist/host/accounts/source.js +27 -0
  206. package/dist/host/accounts/sources.js +4 -0
  207. package/dist/host/github/deliver.d.ts +1 -1
  208. package/dist/host/github/deliver.js +20 -1
  209. package/dist/host/github/index.d.ts +2 -1
  210. package/dist/host/github/index.js +1 -0
  211. package/dist/host/github/token.d.ts +13 -1
  212. package/dist/host/github/token.js +19 -2
  213. package/dist/host/github/verify.d.ts +27 -0
  214. package/dist/host/github/verify.js +61 -0
  215. package/dist/host/launch/hints/fivem.d.ts +3 -1
  216. package/dist/host/launch/hints/fivem.js +68 -2
  217. package/dist/host/launch/session-file.js +4 -0
  218. package/dist/host/launch/toolchains.d.ts +2 -1
  219. package/dist/host/launch/toolchains.js +5 -3
  220. package/dist/host/profile/claude-login.d.ts +2 -2
  221. package/dist/host/profile/claude-login.js +4 -3
  222. package/dist/host/vpn/nordvpn.d.ts +2 -4
  223. package/dist/host/vpn/nordvpn.js +2 -24
  224. package/dist/runtime/agent.js +405 -90
  225. package/dist/runtime/daemon.js +3010 -1383
  226. package/dist/runtime/db.js +333 -90
  227. package/dist/runtime/main.js +4720 -1954
  228. package/dist/runtime/netproxy.js +286 -69
  229. package/dist/runtime/tool.js +2852 -1780
  230. package/dist/runtime/view.js +286 -69
  231. package/dist/selftest/accounts.test.js +1 -1
  232. package/dist/selftest/agent-paths.test.js +39 -1
  233. package/dist/selftest/agent-settings.test.js +6 -0
  234. package/dist/selftest/agents-web.test.d.ts +2 -0
  235. package/dist/selftest/agents-web.test.js +647 -0
  236. package/dist/selftest/app.test.js +5 -2
  237. package/dist/selftest/boot-runner.test.d.ts +2 -0
  238. package/dist/selftest/boot-runner.test.js +456 -0
  239. package/dist/selftest/channel.test.js +15 -0
  240. package/dist/selftest/fivem.test.js +249 -3
  241. package/dist/selftest/github.test.js +94 -9
  242. package/dist/selftest/guest-boot.test.js +3 -0
  243. package/dist/selftest/health.test.js +1 -1
  244. package/dist/selftest/index.js +8 -0
  245. package/dist/selftest/layout.test.js +5 -0
  246. package/dist/selftest/menu-coverage.test.d.ts +2 -0
  247. package/dist/selftest/menu-coverage.test.js +46 -0
  248. package/dist/selftest/menu-screens.js +37 -1
  249. package/dist/selftest/persisted-installs.test.js +6 -3
  250. package/dist/selftest/service-accounts.test.js +3 -2
  251. package/dist/selftest/support/engine-golden.js +27 -0
  252. package/dist/selftest/support/fixtures.d.ts +6 -0
  253. package/dist/selftest/support/web-preview.d.ts +34 -1
  254. package/dist/selftest/support/web-preview.js +21 -18
  255. package/dist/selftest/toolchains.test.js +23 -1
  256. package/dist/selftest/ui-text.test.d.ts +7 -0
  257. package/dist/selftest/ui-text.test.js +300 -0
  258. package/dist/shared/accounts/anthropic.d.ts +1 -1
  259. package/dist/shared/accounts/anthropic.js +9 -8
  260. package/dist/shared/accounts/index.d.ts +2 -0
  261. package/dist/shared/accounts/index.js +2 -0
  262. package/dist/shared/accounts/kinds/comet.d.ts +12 -0
  263. package/dist/shared/accounts/kinds/comet.js +64 -0
  264. package/dist/shared/accounts/kinds/perplexity.d.ts +8 -0
  265. package/dist/shared/accounts/kinds/perplexity.js +37 -0
  266. package/dist/shared/accounts/kinds.d.ts +24 -5
  267. package/dist/shared/accounts/kinds.js +86 -4
  268. package/dist/shared/accounts/payloads.d.ts +81 -0
  269. package/dist/shared/accounts/payloads.js +39 -1
  270. package/dist/shared/apps/agent-addons.d.ts +33 -5
  271. package/dist/shared/apps/agent-addons.js +95 -3
  272. package/dist/shared/apps/catalogue-agents-web.d.ts +14 -0
  273. package/dist/shared/apps/catalogue-agents-web.js +112 -0
  274. package/dist/shared/apps/catalogue-modding.js +32 -1
  275. package/dist/shared/apps/catalogue.d.ts +27 -3
  276. package/dist/shared/apps/catalogue.js +2 -1
  277. package/dist/shared/apps/index.d.ts +3 -1
  278. package/dist/shared/apps/index.js +3 -1
  279. package/dist/shared/channel/schema.d.ts +21 -3
  280. package/dist/shared/channel/schema.js +7 -0
  281. package/dist/shared/channel/secrets.d.ts +19 -0
  282. package/dist/shared/concurrency/index.d.ts +3 -0
  283. package/dist/shared/concurrency/index.js +3 -0
  284. package/dist/shared/concurrency/installer-retry.d.ts +6 -0
  285. package/dist/shared/concurrency/installer-retry.js +10 -0
  286. package/dist/shared/concurrency/limiter.d.ts +19 -0
  287. package/dist/shared/concurrency/limiter.js +67 -0
  288. package/dist/shared/concurrency/pools.d.ts +7 -0
  289. package/dist/shared/concurrency/pools.js +9 -0
  290. package/dist/shared/download/archive.d.ts +2 -0
  291. package/dist/shared/download/archive.js +11 -1
  292. package/dist/shared/download/bounded-fetch.d.ts +13 -0
  293. package/dist/shared/download/bounded-fetch.js +40 -0
  294. package/dist/shared/download/downloader.d.ts +5 -0
  295. package/dist/shared/download/downloader.js +50 -38
  296. package/dist/shared/download/index.d.ts +2 -1
  297. package/dist/shared/download/index.js +2 -1
  298. package/dist/shared/readiness.d.ts +2 -2
  299. package/dist/shared/readiness.js +2 -2
  300. package/dist/web/index.html +1 -1
  301. package/dist/web/{main-OUUQYNK4.js → main-CCILLX2M.js} +67 -67
  302. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  303. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  304. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  305. package/package.json +1 -1
  306. package/sandbox/addons/browser-use-mcp/requirements.in +1 -0
  307. package/sandbox/addons/browser-use-mcp/requirements.txt +2350 -0
  308. package/sandbox/addons/chrome-devtools-mcp/package-lock.json +38 -0
  309. package/sandbox/addons/chrome-devtools-mcp/package.json +7 -0
  310. package/sandbox/addons/comet-mcp/package-lock.json +1277 -0
  311. package/sandbox/addons/comet-mcp/package.json +7 -0
  312. package/sandbox/addons/perplexity-mcp/package-lock.json +1655 -0
  313. package/sandbox/addons/perplexity-mcp/package.json +7 -0
  314. package/sandbox/scripts/add-firewall-port.ps1 +18 -4
  315. package/sandbox/scripts/set-machine-environment.ps1 +30 -15
  316. package/sandbox/templates/fivem/README.txt +6 -2
  317. package/sandbox/templates/fivem/cs/Client/Client.csproj +15 -0
  318. package/sandbox/templates/fivem/cs/Client/ClientMain.cs +20 -0
  319. package/sandbox/templates/fivem/cs/Server/Server.csproj +14 -0
  320. package/sandbox/templates/fivem/cs/Server/ServerMain.cs +20 -0
  321. package/sandbox/templates/fivem/cs/fxmanifest.lua +11 -0
  322. package/sandbox/templates/fivem/js/client.js +12 -0
  323. package/sandbox/templates/fivem/js/fxmanifest.lua +9 -0
  324. package/sandbox/templates/fivem/js/server.js +6 -0
  325. package/sandbox/templates/fivem/lua/client.lua +8 -0
  326. package/sandbox/templates/fivem/lua/fxmanifest.lua +10 -0
  327. package/sandbox/templates/fivem/lua/server.lua +6 -0
  328. package/sandbox/templates/fivem/server.cfg +37 -0
  329. package/sandbox/templates/fivem/ts/build.js +17 -0
  330. package/sandbox/templates/fivem/ts/fxmanifest.lua +9 -0
  331. package/sandbox/templates/fivem/ts/package.json +16 -0
  332. package/sandbox/templates/fivem/ts/src/client/client.ts +12 -0
  333. package/sandbox/templates/fivem/ts/src/client/tsconfig.json +13 -0
  334. package/sandbox/templates/fivem/ts/src/server/server.ts +6 -0
  335. package/sandbox/templates/fivem/ts/src/server/tsconfig.json +13 -0
  336. package/schemas/config.schema.json +56 -27
  337. package/schemas/sandbox-config.schema.json +37 -14
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import os from 'node:os';
3
3
  import path from 'node:path';
4
- import { spawnSync } from 'node:child_process';
4
+ import { spawn, spawnSync } from 'node:child_process';
5
5
  import { moveIntoPlace, spawnAsync, stagingFolder } from '../../shared/download/index.js';
6
6
  import { safeRemove } from '../../shared/io/safe-remove.js';
7
7
  import { robocopyErrors, robocopyFailed, robocopyMeaning } from '../../shared/io/robocopy-result.js';
@@ -41,6 +41,31 @@ export function runScript(name, args = []) {
41
41
  if (result.status !== 0)
42
42
  throw new Error(name + ' failed with exit code ' + result.status + (errors ? ': ' + errors.slice(0, 400) : ''));
43
43
  }
44
+ /** runScript without blocking the event loop (other boot tasks keep going while the script runs). */
45
+ export function runScriptAsync(name, args = []) {
46
+ return new Promise((resolve, reject) => {
47
+ const child = spawn(SYSTEM.powershell(), scriptArgs(name, args), {
48
+ stdio: ['inherit', 'inherit', 'pipe'],
49
+ windowsHide: true,
50
+ });
51
+ let errors = '';
52
+ child.stderr?.setEncoding('utf8');
53
+ child.stderr?.on('data', (chunk) => {
54
+ if (errors.length < 4096)
55
+ errors += chunk;
56
+ });
57
+ child.once('error', reject);
58
+ child.once('close', (code) => {
59
+ errors = errors.trim();
60
+ if (errors)
61
+ process.stderr.write(errors + '\n');
62
+ if (code !== 0)
63
+ reject(new Error(name + ' failed with exit code ' + code + (errors ? ': ' + errors.slice(0, 400) : '')));
64
+ else
65
+ resolve();
66
+ });
67
+ });
68
+ }
44
69
  /** runScript for what it prints ('' when it fails). */
45
70
  export function scriptOutput(name, args = [], options = {}) {
46
71
  return execOutput(SYSTEM.powershell(), scriptArgs(name, args), options);
@@ -57,6 +82,18 @@ export function runScriptWithJson(name, param, data, args = []) {
57
82
  fs.rmSync(dir, { recursive: true, force: true });
58
83
  }
59
84
  }
85
+ /** runScriptWithJson without blocking the event loop. */
86
+ export async function runScriptWithJsonAsync(name, param, data, args = []) {
87
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sbx-'));
88
+ const file = path.join(dir, 'input.json');
89
+ try {
90
+ fs.writeFileSync(file, JSON.stringify(data), 'utf8');
91
+ await runScriptAsync(name, ['-' + param, file, ...args]);
92
+ }
93
+ finally {
94
+ fs.rmSync(dir, { recursive: true, force: true });
95
+ }
96
+ }
60
97
  /**
61
98
  * Copies a folder with robocopy (fast, restartable), or just the named files in it; exit codes below 8 mean success.
62
99
  */
@@ -1,7 +1,11 @@
1
1
  import type { SandboxStatus } from '../../core/types.js';
2
- import type { BootProgress } from '../../shared/channel/schema.js';
2
+ import type { BackgroundWork, BootProgress } from '../../shared/channel/schema.js';
3
3
  /**
4
4
  * Best-effort: progress reporting must never be what breaks the boot. `update` changes the structured progress
5
5
  * (a new step, what is being set up); it stays as it was otherwise.
6
6
  */
7
7
  export declare function setSandboxStatus(phase: SandboxStatus['phase'], message: string, update?: Partial<BootProgress>): void;
8
+ /** Whether the boot (another process: the daemon asks) still has this work running in the background. */
9
+ export declare function backgroundRunning(name: string): boolean;
10
+ /** Lists the work that continues after the boot (the phase and message stay as they are). */
11
+ export declare function setBackgroundWork(work: readonly BackgroundWork[]): void;
@@ -8,11 +8,15 @@ import { run } from './paths.js';
8
8
  import { session, workspace } from './session.js';
9
9
  /** The boot's structured progress (the host shows it as one line); kept between status writes. */
10
10
  let progress;
11
+ /** What goes on after the boot is ready (Visual Studio installing), and the phase it is reported under. */
12
+ let background = [];
13
+ let current = { phase: 'booting', message: '' };
11
14
  /**
12
15
  * Best-effort: progress reporting must never be what breaks the boot. `update` changes the structured progress
13
16
  * (a new step, what is being set up); it stays as it was otherwise.
14
17
  */
15
18
  export function setSandboxStatus(phase, message, update) {
19
+ current = { phase, message };
16
20
  try {
17
21
  if (update)
18
22
  progress = fitProgress({ step: 1, steps: 1, title: '', active: [], done: 0, total: 0, ...progress, ...update });
@@ -35,6 +39,20 @@ function fitProgress(p) {
35
39
  })),
36
40
  };
37
41
  }
42
+ /** Whether the boot (another process: the daemon asks) still has this work running in the background. */
43
+ export function backgroundRunning(name) {
44
+ return (run.readSandboxStatus()?.background ?? []).some((w) => w.state === 'running' && w.name.includes(name));
45
+ }
46
+ /** Lists the work that continues after the boot (the phase and message stay as they are). */
47
+ export function setBackgroundWork(work) {
48
+ background = work.slice(0, 16).map((w) => ({ name: w.name.slice(0, 64), state: w.state }));
49
+ try {
50
+ writeStatus(current.phase, current.message);
51
+ }
52
+ catch (e) {
53
+ warn('Could not update sandbox status: ' + errMsg(e));
54
+ }
55
+ }
38
56
  function ownAddresses() {
39
57
  return Object.values(os.networkInterfaces())
40
58
  .flat()
@@ -53,5 +71,6 @@ function writeStatus(phase, message) {
53
71
  headless: session.headless,
54
72
  addresses: ownAddresses(),
55
73
  ...(progress ? { progress } : {}),
74
+ ...(background.length ? { background } : {}),
56
75
  });
57
76
  }
@@ -1,2 +1,2 @@
1
- /** Sets the add-ons' MCP servers in the sandbox's .claude.json; the names that are on. */
1
+ /** Sets the add-ons' MCP servers in the sandbox's .claude.json (and Codex's config.toml); the names that are on. */
2
2
  export declare function writeAddonMcpServers(): string[];
@@ -1,23 +1,40 @@
1
- // The agent add-ons' MCP servers (Playwright, Context7) in the sandbox's own Claude Code: user-scope servers named
2
- // sbx-... in its .claude.json, set every boot while the add-on is on and removed again when it is off. Your servers
3
- // (from this PC or added in the sandbox) are never touched, and nothing on this PC changes.
1
+ // The agent add-ons' MCP servers (Playwright, Context7, Perplexity ...) in the sandbox's own Claude Code: user-scope
2
+ // servers named sbx-... in its .claude.json, set every boot while the add-on is on and removed again when it is off.
3
+ // The add-ons marked `codex` also go into the sandbox's Codex config.toml as [mcp_servers.sbx-...] tables. Your servers
4
+ // (from this PC or added in the sandbox) are never touched, and nothing on this PC changes. No secret is written:
5
+ // a server that needs a key reads it from the agent's environment (PERPLEXITY_API_KEY).
4
6
  import fs from 'node:fs';
5
7
  import path from 'node:path';
6
- import { addonMcpServer, APPS, withAddonMcpServers } from '../../shared/apps/index.js';
8
+ import { addonMcpServer, APPS, codexWithAddonMcpServers, withAddonMcpServers, } from '../../shared/apps/index.js';
7
9
  import { writeJsonAtomic } from '../../shared/io/json.js';
8
- import { claudeDir } from '../runtime/session.js';
10
+ import { cometExe } from '../browser/comet.js';
11
+ import { exists, mkdirp } from '../runtime/fs.js';
12
+ import { dryRun } from '../runtime/paths.js';
13
+ import { claudeDir, session, stateDir } from '../runtime/session.js';
9
14
  import { log, warn } from '../runtime/log.js';
10
15
  import { installedMcpAddons } from './agent-addons.js';
11
16
  /** The names of every MCP server an add-on can add (the ones the sandbox may remove). */
12
17
  const ADDON_MCP_NAMES = APPS.flatMap((a) => (a.mcp ? [a.mcp.name] : []));
13
- /** Sets the add-ons' MCP servers in the sandbox's .claude.json; the names that are on. */
14
- export function writeAddonMcpServers() {
15
- const servers = {};
16
- for (const { id, folder } of installedMcpAddons()) {
18
+ /** Variables only this sandbox can tell an add-on's server (where Comet is). */
19
+ function placeEnv(id) {
20
+ if (id !== 'comet-mcp')
21
+ return {};
22
+ const exe = cometExe();
23
+ return exe ? { COMET_PATH: exe } : {};
24
+ }
25
+ /** The servers of the add-ons that are on and installed, with their catalogue entry. */
26
+ function addonServers() {
27
+ return installedMcpAddons().flatMap(({ id, folder, exe }) => {
17
28
  const mcp = APPS.find((a) => a.id === id)?.mcp;
18
- if (mcp)
19
- servers[mcp.name] = addonMcpServer(folder, mcp);
20
- }
29
+ const server = mcp ? addonMcpServer(folder, mcp, { exe, env: placeEnv(id) }) : null;
30
+ return mcp && server ? [{ mcp, server }] : [];
31
+ });
32
+ }
33
+ /** Sets the add-ons' MCP servers in the sandbox's .claude.json (and Codex's config.toml); the names that are on. */
34
+ export function writeAddonMcpServers() {
35
+ const on = addonServers();
36
+ writeCodexAddonMcpServers(on);
37
+ const servers = Object.fromEntries(on.map(({ mcp, server }) => [mcp.name, server]));
21
38
  const file = path.join(claudeDir, '.claude.json');
22
39
  let state = {};
23
40
  if (fs.existsSync(file)) {
@@ -47,3 +64,26 @@ export function writeAddonMcpServers() {
47
64
  log('Claude Code MCP servers from Apps: ' + names.join(', '));
48
65
  return names;
49
66
  }
67
+ /** The add-ons marked for Codex in its config.toml (state\codex), when Codex is one of this sandbox's agents. */
68
+ function writeCodexAddonMcpServers(on) {
69
+ if (dryRun || !session.agents?.codex)
70
+ return;
71
+ const file = path.join(stateDir, 'codex', 'config.toml');
72
+ const servers = Object.fromEntries(on
73
+ .filter(({ mcp }) => mcp.codex)
74
+ .map(({ mcp, server }) => [mcp.name, { ...server, ...(mcp.envVars?.length ? { envVars: mcp.envVars } : {}) }]));
75
+ const before = exists(file) ? fs.readFileSync(file, 'utf8') : '';
76
+ if (!before && !Object.keys(servers).length)
77
+ return;
78
+ const known = APPS.flatMap((a) => (a.mcp?.codex ? [a.mcp.name] : []));
79
+ const result = codexWithAddonMcpServers(before, servers, known);
80
+ for (const name of result.skipped)
81
+ warn('Codex MCP servers from Apps: ' + name + ' is already in config.toml in another form, left as it is');
82
+ if (result.changed) {
83
+ mkdirp(path.dirname(file));
84
+ fs.writeFileSync(file, result.toml, 'utf8');
85
+ }
86
+ const names = Object.keys(servers).filter((n) => !result.skipped.includes(n));
87
+ if (names.length)
88
+ log('Codex MCP servers from Apps: ' + names.join(', '));
89
+ }
@@ -1,9 +1,10 @@
1
- /** The add-on's program when it is chosen and installed, else null. */
1
+ /** The add-on's program when it is chosen and installed (a remote server: its folder once set up), else null. */
2
2
  export declare function installedAddon(id: string): string | null;
3
3
  /** rtk.exe when rtk is chosen and installed, else null. */
4
4
  export declare function installedRtk(): string | null;
5
- /** The add-ons with an MCP server that are chosen and installed: their folder and server. */
5
+ /** The add-ons with an MCP server that are chosen and installed: their folder and program. */
6
6
  export declare function installedMcpAddons(): {
7
7
  id: string;
8
8
  folder: string;
9
+ exe: string;
9
10
  }[];
@@ -1,22 +1,31 @@
1
1
  // Agent add-ons in this sandbox: an add-on is plugged into the agents only while it is chosen (apps.install) AND its
2
2
  // program is really there (a failed download adds no hook, proxy or MCP server that would fail on every use).
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
3
5
  import { APPS, appById } from '../../shared/apps/index.js';
4
6
  import { findExe } from '../apps/find-exe.js';
5
7
  import { dryRun } from '../runtime/paths.js';
6
8
  import { session } from '../runtime/session.js';
7
9
  import { tool } from '../toolchains/tool-env.js';
8
- /** The add-on's program when it is chosen and installed, else null. */
10
+ /** The add-on's program when it is chosen and installed (a remote server: its folder once set up), else null. */
9
11
  export function installedAddon(id) {
10
12
  const entry = appById(id);
11
13
  if (dryRun || !entry || !(session.apps?.install ?? []).includes(id))
12
14
  return null;
15
+ if ('remote' in entry.source) {
16
+ const folder = tool('apps', id);
17
+ return fs.existsSync(path.join(folder, '.agent-sandbox-app')) ? folder : null;
18
+ }
13
19
  return findExe(tool('apps', id), entry.exe);
14
20
  }
15
21
  /** rtk.exe when rtk is chosen and installed, else null. */
16
22
  export function installedRtk() {
17
23
  return installedAddon('rtk');
18
24
  }
19
- /** The add-ons with an MCP server that are chosen and installed: their folder and server. */
25
+ /** The add-ons with an MCP server that are chosen and installed: their folder and program. */
20
26
  export function installedMcpAddons() {
21
- return APPS.filter((a) => a.mcp && installedAddon(a.id)).map((a) => ({ id: a.id, folder: tool('apps', a.id) }));
27
+ return APPS.flatMap((a) => {
28
+ const exe = a.mcp ? installedAddon(a.id) : null;
29
+ return exe ? [{ id: a.id, folder: tool('apps', a.id), exe }] : [];
30
+ });
22
31
  }
@@ -7,7 +7,7 @@ export interface EnvTarget {
7
7
  }
8
8
  export declare function agentEnv(tools: Pick<ToolEnv, 'vars' | 'prepend' | 'append'>, target?: EnvTarget): NodeJS.ProcessEnv;
9
9
  /** Makes the agent environment machine-wide, so new terminal tabs and anything the agent starts see the same tools. */
10
- export declare function persistEnvironment(env: NodeJS.ProcessEnv, toolVars: string[]): void;
10
+ export declare function persistEnvironment(env: NodeJS.ProcessEnv, toolVars: string[], additive?: boolean): Promise<void>;
11
11
  /**
12
12
  * Adds variables and PATH folders machine-wide after boot (`sbx-db start`): new terminal tabs see them.
13
13
  * additive: for a project registered later. Only variables not set yet, and folders go to the END of PATH, so what
@@ -3,13 +3,13 @@ import path from 'node:path';
3
3
  import { claudeDir, session, stateDir, workspace } from '../runtime/session.js';
4
4
  import { dataDir, dryRun, isWin, testMode, toolsDir } from '../runtime/paths.js';
5
5
  import { exists, mkdirp } from '../runtime/fs.js';
6
- import { execOutput, runScriptWithJson } from '../runtime/process.js';
6
+ import { execOutput, runScriptWithJson, runScriptWithJsonAsync } from '../runtime/process.js';
7
7
  import { SYSTEM } from '../../shared/system.js';
8
8
  import { warn } from '../runtime/log.js';
9
9
  import { errMsg } from '../../core/util/errors.js';
10
10
  import { localStateVars } from '../agents/local-state.js';
11
11
  import { agentPaths } from '../agents/agent-files.js';
12
- import { appendMissing, expandVars, isPinnedNodeDir, parseRegPath, pathKey, planPath } from './path-plan.js';
12
+ import { appendMissing, expandVars, isPinnedNodeDir, msvcBeforeGitUsr, parseRegPath, pathKey, planPath, } from './path-plan.js';
13
13
  /** Older launch folders have no agentHomes: these two were always set. */
14
14
  const LEGACY_HOMES = { CODEX_HOME: 'codex', GEMINI_CLI_HOME: 'gemini' };
15
15
  /** Every agent's home variable (CODEX_HOME ...) with the folder it points to in the persistent state. */
@@ -57,26 +57,39 @@ export function agentEnv(tools, target = { workspace, project: session.project,
57
57
  // The runner's own Node (bootstrap.ps1 puts it first on PATH) is not exposed: `node` is whatever nvm selected.
58
58
  const runners = [path.join(toolsDir, 'node'), path.dirname(process.execPath)].map((d) => d.toLowerCase());
59
59
  const base = (target.base.PATH ?? '').split(path.delimiter).filter((d) => d && !runners.includes(d.toLowerCase()));
60
- env.PATH = [...new Set([...tools.prepend, ...base, ...tools.append])].join(path.delimiter);
60
+ // MSVC's link.exe before Git's coreutils link.exe, wherever each came from.
61
+ env.PATH = msvcBeforeGitUsr([...new Set([...tools.prepend, ...base, ...tools.append])]).join(path.delimiter);
61
62
  return env;
62
63
  }
63
64
  /** Makes the agent environment machine-wide, so new terminal tabs and anything the agent starts see the same tools. */
64
- export function persistEnvironment(env, toolVars) {
65
+ export async function persistEnvironment(env, toolVars, additive = false) {
65
66
  if (!isWin || dryRun || testMode)
66
67
  return;
67
68
  const vars = Object.fromEntries([...toolVars, ...AGENT_VARS, ...Object.keys(agentHomes()), ...Object.keys(localStateVars())]
68
69
  .filter((n) => env[n] !== undefined)
69
70
  .map((n) => [n, env[n]]));
70
71
  try {
71
- runScriptWithJson('set-machine-environment.ps1', 'SettingsFile', {
72
+ await runScriptWithJsonAsync('set-machine-environment.ps1', 'SettingsFile', {
72
73
  vars,
73
- path: planPath({
74
- entries: [env.PATH ?? '', ...installedAgentDirs()],
75
- exists,
76
- drop: (dir) => isPinnedNodeDir(dir, nvmHome()),
77
- }).join(path.delimiter),
78
- add: [],
79
- additive: false,
74
+ ...(additive
75
+ ? {
76
+ // Visual Studio done: variables not set yet, new folders at the end of the machine PATH.
77
+ path: null,
78
+ add: planPath({
79
+ entries: (env.PATH ?? '').split(path.delimiter),
80
+ exists,
81
+ drop: (dir) => isPinnedNodeDir(dir, nvmHome()),
82
+ }),
83
+ }
84
+ : {
85
+ path: planPath({
86
+ entries: [env.PATH ?? '', ...installedAgentDirs()],
87
+ exists,
88
+ drop: (dir) => isPinnedNodeDir(dir, nvmHome()),
89
+ }).join(path.delimiter),
90
+ add: [],
91
+ }),
92
+ additive,
80
93
  });
81
94
  }
82
95
  catch (e) {
@@ -1,7 +1,12 @@
1
1
  /** The proxy's log (it redacts keys and tokens in what it logs). */
2
2
  export declare const HEADROOM_LOG: string;
3
3
  /**
4
- * Starts the proxy when Headroom is chosen and installed; its base URL once it answers, else null (Claude Code then
5
- * talks to the API directly).
4
+ * Starts the proxy process when Headroom is chosen and installed (nothing waits for it). True when the proxy is
5
+ * running or starting; `awaitHeadroom` then waits until it answers. The environment is the agent's.
6
6
  */
7
- export declare function startHeadroom(env: NodeJS.ProcessEnv): Promise<string | null>;
7
+ export declare function spawnHeadroom(env: NodeJS.ProcessEnv): Promise<boolean>;
8
+ /**
9
+ * Waits until the proxy answers as Headroom; its base URL, else null (Claude Code then talks to the API directly).
10
+ * Starts it first when `spawnHeadroom` did not.
11
+ */
12
+ export declare function awaitHeadroom(env: NodeJS.ProcessEnv): Promise<string | null>;
@@ -28,31 +28,60 @@ function answersAsHeadroom(port) {
28
28
  req.on('error', () => resolve(false));
29
29
  });
30
30
  }
31
+ /** The one start of the proxy in this process (the early start, the one after Apps and the wait share it). */
32
+ let starting = null;
33
+ /** Whether the proxy is installed and may be used (the agent environment does not name another API address). */
34
+ function usable(env) {
35
+ const exe = installedAddon('headroom');
36
+ return exe && !env.ANTHROPIC_BASE_URL ? exe : null;
37
+ }
31
38
  /**
32
- * Starts the proxy when Headroom is chosen and installed; its base URL once it answers, else null (Claude Code then
33
- * talks to the API directly).
39
+ * Starts the proxy process when Headroom is chosen and installed (nothing waits for it). True when the proxy is
40
+ * running or starting; `awaitHeadroom` then waits until it answers. The environment is the agent's.
34
41
  */
35
- export async function startHeadroom(env) {
36
- const exe = installedAddon('headroom');
42
+ export async function spawnHeadroom(env) {
43
+ const attempt = (starting ??= startProxy(env));
44
+ const started = await attempt;
45
+ // Not installed (yet): a later call, after the Apps step, may still start it.
46
+ if (!started && starting === attempt)
47
+ starting = null;
48
+ return started;
49
+ }
50
+ async function startProxy(env) {
51
+ const exe = usable(env);
37
52
  if (!exe)
38
- return null;
39
- if (env.ANTHROPIC_BASE_URL) {
40
- log('Headroom: not used, ANTHROPIC_BASE_URL is set in the agent environment (' + env.ANTHROPIC_BASE_URL + ')');
41
- return null;
42
- }
43
- const port = HEADROOM_PORT;
44
- if (!(await portOpen(port))) {
45
- const out = fs.openSync(HEADROOM_LOG, 'a');
46
- spawn(exe, headroomArgs(port), {
53
+ return false;
54
+ if (await portOpen(HEADROOM_PORT))
55
+ return true;
56
+ const out = fs.openSync(HEADROOM_LOG, 'a');
57
+ try {
58
+ spawn(exe, headroomArgs(HEADROOM_PORT), {
47
59
  detached: true,
48
60
  stdio: ['ignore', out, out],
49
61
  windowsHide: true,
50
62
  env: headroomEnv(env),
51
63
  }).unref();
64
+ }
65
+ finally {
52
66
  fs.closeSync(out);
53
- if (!(await waitForPort(port, 90)))
54
- throw new Error('the Headroom proxy did not start on port ' + port + '; see ' + HEADROOM_LOG);
55
67
  }
68
+ return true;
69
+ }
70
+ /**
71
+ * Waits until the proxy answers as Headroom; its base URL, else null (Claude Code then talks to the API directly).
72
+ * Starts it first when `spawnHeadroom` did not.
73
+ */
74
+ export async function awaitHeadroom(env) {
75
+ if (!installedAddon('headroom'))
76
+ return null;
77
+ if (env.ANTHROPIC_BASE_URL) {
78
+ log('Headroom: not used, ANTHROPIC_BASE_URL is set in the agent environment (' + env.ANTHROPIC_BASE_URL + ')');
79
+ return null;
80
+ }
81
+ const port = HEADROOM_PORT;
82
+ await spawnHeadroom(env);
83
+ if (!(await waitForPort(port, 90)))
84
+ throw new Error('the Headroom proxy did not start on port ' + port + '; see ' + HEADROOM_LOG);
56
85
  if (!(await answersAsHeadroom(port)))
57
86
  throw new Error('port ' + port + ' is taken by another program; Claude Code talks to the API directly');
58
87
  log('Headroom: Claude Code reaches the API through ' + headroomUrl(port) + ' (log: ' + HEADROOM_LOG + ')');
@@ -30,3 +30,31 @@ export declare function rebuildPath(o: {
30
30
  export declare function parseRegPath(output: string): string;
31
31
  /** A folder of one pinned Node version (nvm's `v24.22.0`): never machine-wide, `nvm use` would not move it. */
32
32
  export declare function isPinnedNodeDir(folder: string, nvmHome: string): boolean;
33
+ /** MSVC's compiler folder (`...\VC\Tools\MSVC\<version>\bin\Host<arch>\<arch>`): where the real link.exe lives. */
34
+ export declare function isMsvcToolsDir(folder: string): boolean;
35
+ /** Git for Windows' `usr\bin` (coreutils): its link.exe is not MSVC's linker, and it has many other lookalikes. */
36
+ export declare function isGitUsrBin(folder: string): boolean;
37
+ /**
38
+ * The same folders with MSVC's tools before Git's `usr\bin`, so `link` is the linker and not coreutils' `link` (a
39
+ * msvc Rust build then died with os error 1392). Nothing else moves: MSVC folders found after the first Git `usr\bin`
40
+ * are put just before it, in their own order.
41
+ */
42
+ export declare function msvcBeforeGitUsr(folders: readonly string[]): string[];
43
+ /** How `installedToolPaths` looks at the tools folder (injected so it is a pure function). */
44
+ export interface ToolsFs {
45
+ readonly exists: (file: string) => boolean;
46
+ /** Names in a folder ([] when it is missing). */
47
+ readonly list: (folder: string) => readonly string[];
48
+ }
49
+ /**
50
+ * What a toolchain that is already installed in the persistent tools folder adds to PATH even when the project in this
51
+ * session did not ask for it (python and rust: another project of the same sandbox, a terminal tab, a script that
52
+ * calls `python`). Only PATH entries and the two variables rust's own tools read, nothing is installed or changed,
53
+ * and the folders go at the END so they never shadow what a project's own toolchains put first. `skip` names the
54
+ * toolchains whose own step already runs (they bring their own entries).
55
+ */
56
+ export declare function installedToolPaths(toolsDir: string, fsApi: ToolsFs, skip?: ReadonlySet<'python' | 'rust'>): {
57
+ readonly append: string[];
58
+ readonly vars: Record<string, string>;
59
+ readonly summary: string[];
60
+ };
@@ -30,7 +30,7 @@ export function planPath(input) {
30
30
  seen.add(key);
31
31
  result.push(entry);
32
32
  }
33
- return result;
33
+ return msvcBeforeGitUsr(result);
34
34
  }
35
35
  /** `base` with the folders of `extra` it lacks added at the end (a process keeps its own order and entries). */
36
36
  export function appendMissing(base, extra, skip = () => false) {
@@ -42,7 +42,7 @@ export function appendMissing(base, extra, skip = () => false) {
42
42
  seen.add(key);
43
43
  return true;
44
44
  });
45
- return [...splitPath(base), ...added].join(';');
45
+ return msvcBeforeGitUsr([...splitPath(base), ...added]).join(';');
46
46
  }
47
47
  /**
48
48
  * What a new shell gets: folders only this process has first (a parent put them there on purpose), then the machine
@@ -65,3 +65,70 @@ export function isPinnedNodeDir(folder, nvmHome) {
65
65
  const home = pathKey(nvmHome);
66
66
  return key.startsWith(home + '\\') && /^v\d/.test(path.win32.basename(key));
67
67
  }
68
+ /** MSVC's compiler folder (`...\VC\Tools\MSVC\<version>\bin\Host<arch>\<arch>`): where the real link.exe lives. */
69
+ export function isMsvcToolsDir(folder) {
70
+ return /\\vc\\tools\\msvc\\[^\\]+\\bin\\host[^\\]+\\[^\\]+$/.test(pathKey(folder));
71
+ }
72
+ /** Git for Windows' `usr\bin` (coreutils): its link.exe is not MSVC's linker, and it has many other lookalikes. */
73
+ export function isGitUsrBin(folder) {
74
+ const key = pathKey(folder);
75
+ return key.endsWith('\\usr\\bin') && /\\git(\\|$)/.test(key);
76
+ }
77
+ /**
78
+ * The same folders with MSVC's tools before Git's `usr\bin`, so `link` is the linker and not coreutils' `link` (a
79
+ * msvc Rust build then died with os error 1392). Nothing else moves: MSVC folders found after the first Git `usr\bin`
80
+ * are put just before it, in their own order.
81
+ */
82
+ export function msvcBeforeGitUsr(folders) {
83
+ const firstGit = folders.findIndex(isGitUsrBin);
84
+ if (firstGit < 0 || !folders.slice(firstGit).some(isMsvcToolsDir))
85
+ return [...folders];
86
+ const late = folders.slice(firstGit).filter(isMsvcToolsDir);
87
+ return [...folders.slice(0, firstGit), ...late, ...folders.slice(firstGit).filter((f) => !isMsvcToolsDir(f))];
88
+ }
89
+ /** Compares "3.9.1" and "3.15.2" by their numbers. */
90
+ function compareVersions(a, b) {
91
+ const x = a.split('.').map((n) => Number.parseInt(n, 10) || 0);
92
+ const y = b.split('.').map((n) => Number.parseInt(n, 10) || 0);
93
+ for (let i = 0; i < Math.max(x.length, y.length); i++) {
94
+ const d = (x[i] ?? 0) - (y[i] ?? 0);
95
+ if (d !== 0)
96
+ return d;
97
+ }
98
+ return 0;
99
+ }
100
+ /**
101
+ * What a toolchain that is already installed in the persistent tools folder adds to PATH even when the project in this
102
+ * session did not ask for it (python and rust: another project of the same sandbox, a terminal tab, a script that
103
+ * calls `python`). Only PATH entries and the two variables rust's own tools read, nothing is installed or changed,
104
+ * and the folders go at the END so they never shadow what a project's own toolchains put first. `skip` names the
105
+ * toolchains whose own step already runs (they bring their own entries).
106
+ */
107
+ export function installedToolPaths(toolsDir, fsApi, skip = new Set()) {
108
+ const join = (...parts) => path.win32.join(toolsDir, ...parts);
109
+ const append = [];
110
+ const vars = {};
111
+ const summary = [];
112
+ if (!skip.has('python')) {
113
+ const newest = fsApi
114
+ .list(join('python'))
115
+ .filter((n) => /^python-\d+(\.\d+)*$/.test(n) && fsApi.exists(join('python', n, 'python.exe')))
116
+ .map((n) => n.slice('python-'.length))
117
+ .sort(compareVersions)
118
+ .pop();
119
+ if (newest) {
120
+ const dir = join('python', 'python-' + newest);
121
+ append.push(dir, join('python', 'python-' + newest, 'Scripts'));
122
+ if (fsApi.exists(join('shims', 'py.cmd')))
123
+ append.push(join('shims'));
124
+ summary.push('python: ' + newest + ' (already installed here, on PATH for every project)');
125
+ }
126
+ }
127
+ if (!skip.has('rust') && fsApi.exists(join('rust', 'cargo', 'bin', 'rustup.exe'))) {
128
+ append.push(join('rust', 'cargo', 'bin'));
129
+ vars.RUSTUP_HOME = join('rust', 'rustup');
130
+ vars.CARGO_HOME = join('rust', 'cargo');
131
+ summary.push('rust: rustup (already installed here, on PATH for every project)');
132
+ }
133
+ return { append, vars, summary };
134
+ }
@@ -1 +1 @@
1
- export declare function installShellPath(): void;
1
+ export declare function installShellPath(): Promise<void>;
@@ -5,13 +5,13 @@ import path from 'node:path';
5
5
  import { dryRun, isWin, localDir, testMode, toolsDir } from '../runtime/paths.js';
6
6
  import { mkdirp } from '../runtime/fs.js';
7
7
  import { ensureCompiled } from '../runtime/csc.js';
8
- import { runScript } from '../runtime/process.js';
9
- export function installShellPath() {
8
+ import { runScriptAsync } from '../runtime/process.js';
9
+ export async function installShellPath() {
10
10
  if (!isWin || dryRun || testMode)
11
11
  return;
12
12
  const helper = path.join(mkdirp(path.join(localDir, 'bin')), 'pathsync.exe');
13
13
  fs.copyFileSync(ensureCompiled('pathsync'), helper);
14
- runScript('install-shell-path.ps1', [
14
+ await runScriptAsync('install-shell-path.ps1', [
15
15
  '-Helper',
16
16
  helper,
17
17
  '-PwshDir',
@@ -1,6 +1,53 @@
1
+ import { z } from 'zod';
2
+ export declare const VERSIONS_URL = "https://changelogs-live.fivem.net/api/changelog/versions/win32/server";
3
+ export declare const ARTIFACTS_PAGE = "https://runtime.fivem.net/artifacts/fivem/build_server_windows/master/";
4
+ /** The subject of the signature on FXServer.exe (checked on build 35245, 2026-11). */
5
+ export declare const FXSERVER_SIGNER: RegExp;
6
+ export declare const CHANNELS: readonly ["recommended", "optional", "latest", "critical"];
7
+ export type Channel = (typeof CHANNELS)[number];
1
8
  export interface FivemArtifact {
2
9
  readonly build: number;
3
10
  readonly url: string;
11
+ /** The channel that names it, when one does. */
12
+ readonly channel?: Channel;
13
+ /** The txAdmin version it bundles, when the versions API says. */
14
+ readonly txAdmin?: string;
4
15
  }
16
+ /** The versions API's answer (its other fields, such as support_policy, are not needed). */
17
+ export declare const VersionsSchema: z.ZodObject<{
18
+ recommended: z.ZodString;
19
+ recommended_download: z.ZodString;
20
+ recommended_txadmin: z.ZodOptional<z.ZodString>;
21
+ optional: z.ZodString;
22
+ optional_download: z.ZodString;
23
+ optional_txadmin: z.ZodOptional<z.ZodString>;
24
+ latest: z.ZodString;
25
+ latest_download: z.ZodString;
26
+ latest_txadmin: z.ZodOptional<z.ZodString>;
27
+ critical: z.ZodString;
28
+ critical_download: z.ZodString;
29
+ critical_txadmin: z.ZodOptional<z.ZodString>;
30
+ }, z.core.$strip>;
31
+ /** Every channel's build, from the versions API's JSON (throws on any other shape). */
32
+ export declare function parseVersions(json: unknown): Record<Channel, FivemArtifact>;
33
+ /** The builds the artifacts page links to (newest first), as server.zip downloads. Reads at most 4 MB of it. */
34
+ export declare function parseArtifactsPage(html: string): FivemArtifact[];
35
+ /** What a setting names: a channel, or a build number. */
36
+ export declare function artifactSetting(setting: string): Channel | number | null;
37
+ /** The build a setting names among the channels and the page's builds, or null. */
38
+ export declare function chooseArtifact(setting: string, channels: Record<Channel, FivemArtifact>, page?: readonly FivemArtifact[]): FivemArtifact | null;
39
+ export interface ArtifactSources {
40
+ readonly versions?: () => Promise<unknown>;
41
+ readonly page?: () => Promise<string>;
42
+ }
43
+ /** The channels from the versions API. */
44
+ export declare function fetchChannels(src?: ArtifactSources): Promise<Record<Channel, FivemArtifact>>;
5
45
  /** The build an artifact setting names, or null when it cannot be told. */
6
- export declare function pickArtifact(_setting: string): Promise<FivemArtifact | null>;
46
+ export declare function pickArtifact(setting: string, src?: ArtifactSources): Promise<FivemArtifact | null>;
47
+ export type ArtifactTrust = 'signed' | 'unsigned-allowed' | 'refused';
48
+ /**
49
+ * Whether a downloaded build may be used. The artifacts have no checksums: the Authenticode signature on FXServer.exe
50
+ * stands in for one, so downloads.requireChecksum refuses a build whose FXServer.exe is not validly signed by Rockstar
51
+ * Games; without that setting an unsigned one is used (with a warning).
52
+ */
53
+ export declare function artifactTrust(requireChecksum: boolean, signerSubject: string): ArtifactTrust;