windows-development-sandbox 1.0.5 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (453) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -8
  3. package/config.example.json +55 -5
  4. package/dist/api.js +2 -1
  5. package/dist/cli/args.d.ts +23 -0
  6. package/dist/cli/args.js +38 -4
  7. package/dist/cli/colors.d.ts +24 -0
  8. package/dist/cli/colors.js +42 -0
  9. package/dist/cli/commands/accounts.js +2 -2
  10. package/dist/cli/commands/agent-commands.d.ts +6 -0
  11. package/dist/cli/commands/agent-commands.js +61 -0
  12. package/dist/cli/commands/context.d.ts +2 -0
  13. package/dist/cli/commands/context.js +5 -1
  14. package/dist/cli/commands/data/clean.d.ts +15 -0
  15. package/dist/cli/commands/data/clean.js +106 -0
  16. package/dist/cli/commands/data/collect.d.ts +11 -0
  17. package/dist/cli/commands/data/collect.js +54 -0
  18. package/dist/cli/commands/data/index.d.ts +4 -0
  19. package/dist/cli/commands/data/index.js +45 -0
  20. package/dist/cli/commands/data/remove.d.ts +2 -0
  21. package/dist/cli/commands/data/remove.js +30 -0
  22. package/dist/cli/commands/index.d.ts +6 -4
  23. package/dist/cli/commands/index.js +6 -4
  24. package/dist/cli/commands/launch.d.ts +26 -2
  25. package/dist/cli/commands/launch.js +256 -42
  26. package/dist/cli/commands/maintenance.d.ts +2 -0
  27. package/dist/cli/commands/maintenance.js +67 -5
  28. package/dist/cli/commands/project.d.ts +29 -0
  29. package/dist/cli/commands/project.js +63 -0
  30. package/dist/cli/commands/projects.d.ts +14 -0
  31. package/dist/cli/commands/projects.js +146 -0
  32. package/dist/cli/commands/repair/checks.d.ts +2 -0
  33. package/dist/cli/commands/{repair.js → repair/checks.js} +19 -181
  34. package/dist/cli/commands/{repair.d.ts → repair/finders.d.ts} +0 -29
  35. package/dist/cli/commands/repair/finders.js +108 -0
  36. package/dist/cli/commands/repair/index.d.ts +12 -0
  37. package/dist/cli/commands/repair/index.js +61 -0
  38. package/dist/cli/commands/repair/types.d.ts +26 -0
  39. package/dist/cli/commands/repair/types.js +3 -0
  40. package/dist/cli/commands/restart.d.ts +6 -0
  41. package/dist/cli/commands/restart.js +85 -0
  42. package/dist/cli/commands/sessions.d.ts +2 -1
  43. package/dist/cli/commands/sessions.js +189 -45
  44. package/dist/cli/commands/setup/checks.d.ts +13 -0
  45. package/dist/cli/commands/setup/checks.js +71 -0
  46. package/dist/cli/commands/{setup.d.ts → setup/facts.d.ts} +1 -15
  47. package/dist/cli/commands/setup/facts.js +60 -0
  48. package/dist/cli/commands/setup/index.d.ts +2 -0
  49. package/dist/cli/commands/setup/index.js +43 -0
  50. package/dist/cli/commands/user-config.d.ts +8 -0
  51. package/dist/cli/commands/user-config.js +41 -0
  52. package/dist/cli/history.d.ts +11 -0
  53. package/dist/cli/history.js +21 -0
  54. package/dist/cli/index.d.ts +7 -1
  55. package/dist/cli/index.js +42 -15
  56. package/dist/cli/manifest.d.ts +8 -0
  57. package/dist/cli/manifest.js +40 -15
  58. package/dist/cli/menu/apps.d.ts +21 -0
  59. package/dist/cli/menu/apps.js +122 -0
  60. package/dist/cli/menu/command-form.d.ts +28 -0
  61. package/dist/cli/menu/command-form.js +219 -0
  62. package/dist/cli/menu/index.d.ts +26 -0
  63. package/dist/cli/menu/index.js +180 -0
  64. package/dist/cli/menu/settings/categories.d.ts +9 -0
  65. package/dist/cli/menu/settings/categories.js +28 -0
  66. package/dist/cli/menu/settings/config-file.d.ts +40 -0
  67. package/dist/cli/menu/settings/config-file.js +71 -0
  68. package/dist/cli/menu/settings/editors.d.ts +30 -0
  69. package/dist/cli/menu/settings/editors.js +305 -0
  70. package/dist/cli/menu/settings/index.d.ts +32 -0
  71. package/dist/cli/menu/settings/index.js +136 -0
  72. package/dist/cli/menu/settings/schema-node.d.ts +62 -0
  73. package/dist/cli/menu/settings/schema-node.js +104 -0
  74. package/dist/cli/menu/several.d.ts +20 -0
  75. package/dist/cli/menu/several.js +102 -0
  76. package/dist/cli/progress.d.ts +9 -0
  77. package/dist/cli/progress.js +49 -0
  78. package/dist/cli/tui/app.d.ts +39 -0
  79. package/dist/cli/tui/app.js +52 -0
  80. package/dist/cli/tui/dialogs.d.ts +57 -0
  81. package/dist/cli/tui/dialogs.js +162 -0
  82. package/dist/cli/tui/frame.d.ts +18 -0
  83. package/dist/cli/tui/frame.js +27 -0
  84. package/dist/cli/tui/index.d.ts +7 -0
  85. package/dist/cli/tui/index.js +8 -0
  86. package/dist/cli/tui/list-screen.d.ts +101 -0
  87. package/dist/cli/tui/list-screen.js +299 -0
  88. package/dist/cli/tui/terminal.d.ts +37 -0
  89. package/dist/cli/tui/terminal.js +89 -0
  90. package/dist/cli/tui/text.d.ts +20 -0
  91. package/dist/cli/tui/text.js +105 -0
  92. package/dist/cli/tui/theme.d.ts +45 -0
  93. package/dist/cli/tui/theme.js +30 -0
  94. package/dist/cli/validate.d.ts +17 -0
  95. package/dist/cli/validate.js +89 -0
  96. package/dist/core/config.js +12 -6
  97. package/dist/core/disk.d.ts +16 -0
  98. package/dist/core/disk.js +55 -0
  99. package/dist/core/local-config.d.ts +23 -0
  100. package/dist/core/local-config.js +79 -7
  101. package/dist/core/project-policy.d.ts +15 -0
  102. package/dist/core/project-policy.js +35 -0
  103. package/dist/core/prompt.d.ts +4 -0
  104. package/dist/core/prompt.js +15 -0
  105. package/dist/core/safe-paths.d.ts +4 -0
  106. package/dist/core/safe-paths.js +37 -0
  107. package/dist/core/schema/apps.d.ts +40 -0
  108. package/dist/core/schema/apps.js +27 -0
  109. package/dist/core/schema/common.d.ts +17 -0
  110. package/dist/core/schema/common.js +15 -0
  111. package/dist/core/schema/config.d.ts +659 -0
  112. package/dist/core/schema/config.js +75 -0
  113. package/dist/core/schema/databases.d.ts +33 -0
  114. package/dist/core/schema/databases.js +35 -0
  115. package/dist/core/schema/harness.d.ts +12 -0
  116. package/dist/core/schema/harness.js +17 -0
  117. package/dist/core/schema/json-schema.d.ts +5 -0
  118. package/dist/core/schema/json-schema.js +25 -0
  119. package/dist/core/schema/profile.d.ts +32 -0
  120. package/dist/core/schema/profile.js +31 -0
  121. package/dist/core/schema/projects.d.ts +9 -0
  122. package/dist/core/schema/projects.js +11 -0
  123. package/dist/core/schema/sandbox.d.ts +78 -0
  124. package/dist/core/schema/sandbox.js +64 -0
  125. package/dist/core/schema/toolchains.d.ts +96 -0
  126. package/dist/core/schema/toolchains.js +88 -0
  127. package/dist/core/schema.d.ts +6 -604
  128. package/dist/core/schema.js +8 -264
  129. package/dist/core/types.d.ts +35 -1
  130. package/dist/core/util.d.ts +9 -0
  131. package/dist/core/util.js +14 -3
  132. package/dist/core/workspace.d.ts +0 -5
  133. package/dist/core/workspace.js +33 -50
  134. package/dist/guest/apps/index.d.ts +10 -0
  135. package/dist/guest/apps/index.js +206 -0
  136. package/dist/guest/apps/resolvers.d.ts +8 -0
  137. package/dist/guest/apps/resolvers.js +90 -0
  138. package/dist/guest/audit-parse.d.ts +7 -0
  139. package/dist/guest/audit-parse.js +24 -0
  140. package/dist/guest/audit.d.ts +4 -0
  141. package/dist/guest/audit.js +47 -0
  142. package/dist/guest/boot-env.d.ts +4 -0
  143. package/dist/guest/boot-env.js +25 -0
  144. package/dist/guest/cmdline.d.ts +12 -0
  145. package/dist/guest/cmdline.js +23 -0
  146. package/dist/guest/daemon/exec.d.ts +5 -0
  147. package/dist/guest/daemon/exec.js +32 -0
  148. package/dist/guest/daemon/project.d.ts +14 -0
  149. package/dist/guest/daemon/project.js +132 -0
  150. package/dist/guest/daemon/relays.d.ts +1 -0
  151. package/dist/guest/daemon/relays.js +18 -0
  152. package/dist/guest/daemon/session.d.ts +13 -0
  153. package/dist/guest/daemon/session.js +139 -0
  154. package/dist/guest/daemon/shutdown.d.ts +1 -0
  155. package/dist/guest/daemon/shutdown.js +21 -0
  156. package/dist/guest/daemon/state.d.ts +17 -0
  157. package/dist/guest/daemon/state.js +31 -0
  158. package/dist/guest/daemon.js +12 -141
  159. package/dist/guest/db.js +18 -3
  160. package/dist/guest/env.d.ts +3 -0
  161. package/dist/guest/env.js +7 -3
  162. package/dist/guest/guide.d.ts +6 -2
  163. package/dist/guest/guide.js +5 -4
  164. package/dist/guest/helpers.d.ts +3 -0
  165. package/dist/guest/helpers.js +26 -0
  166. package/dist/guest/main.js +31 -8
  167. package/dist/guest/netproxy.d.ts +1 -0
  168. package/dist/guest/netproxy.js +100 -0
  169. package/dist/guest/ports.d.ts +6 -0
  170. package/dist/guest/ports.js +22 -0
  171. package/dist/guest/project-context.d.ts +7 -0
  172. package/dist/guest/project-context.js +10 -0
  173. package/dist/guest/project-env.d.ts +20 -0
  174. package/dist/guest/project-env.js +20 -0
  175. package/dist/guest/project-status.d.ts +5 -0
  176. package/dist/guest/project-status.js +17 -0
  177. package/dist/guest/ptyhost.d.ts +0 -18
  178. package/dist/guest/ptyhost.js +17 -65
  179. package/dist/guest/setup/agents.d.ts +5 -2
  180. package/dist/guest/setup/agents.js +22 -8
  181. package/dist/guest/setup/claude.d.ts +0 -4
  182. package/dist/guest/setup/claude.js +59 -5
  183. package/dist/guest/setup/environment.d.ts +13 -3
  184. package/dist/guest/setup/environment.js +14 -10
  185. package/dist/guest/setup/hooks.d.ts +9 -1
  186. package/dist/guest/setup/hooks.js +28 -14
  187. package/dist/guest/setup/network.d.ts +9 -0
  188. package/dist/guest/setup/network.js +97 -4
  189. package/dist/guest/setup/user.js +4 -0
  190. package/dist/guest/toolchains/catalogue.d.ts +4 -0
  191. package/dist/guest/toolchains/catalogue.js +2 -2
  192. package/dist/guest/toolchains/cli.js +25 -17
  193. package/dist/guest/toolchains/common.d.ts +18 -1
  194. package/dist/guest/toolchains/common.js +34 -11
  195. package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
  196. package/dist/guest/toolchains/databases/dynamodb.js +52 -0
  197. package/dist/guest/toolchains/{databases.d.ts → databases/index.d.ts} +2 -3
  198. package/dist/guest/toolchains/databases/index.js +62 -0
  199. package/dist/guest/toolchains/databases/mongodb.d.ts +3 -0
  200. package/dist/guest/toolchains/databases/mongodb.js +31 -0
  201. package/dist/guest/toolchains/databases/mysql.d.ts +3 -0
  202. package/dist/guest/toolchains/databases/mysql.js +38 -0
  203. package/dist/guest/toolchains/databases/postgres.d.ts +5 -0
  204. package/dist/guest/toolchains/databases/postgres.js +38 -0
  205. package/dist/guest/toolchains/databases/redis.d.ts +3 -0
  206. package/dist/guest/toolchains/databases/redis.js +16 -0
  207. package/dist/guest/toolchains/databases/server.d.ts +14 -0
  208. package/dist/guest/toolchains/databases/server.js +52 -0
  209. package/dist/guest/toolchains/git.js +25 -16
  210. package/dist/guest/toolchains/go.js +1 -1
  211. package/dist/guest/toolchains/index.d.ts +13 -9
  212. package/dist/guest/toolchains/index.js +40 -82
  213. package/dist/guest/toolchains/java.js +5 -5
  214. package/dist/guest/toolchains/leftovers.d.ts +2 -0
  215. package/dist/guest/toolchains/leftovers.js +26 -0
  216. package/dist/guest/toolchains/managers.js +12 -10
  217. package/dist/guest/toolchains/{binstall.js → native/binstall.js} +6 -9
  218. package/dist/guest/toolchains/native/cpp.d.ts +4 -0
  219. package/dist/guest/toolchains/native/cpp.js +38 -0
  220. package/dist/guest/toolchains/native/dotnet.d.ts +4 -0
  221. package/dist/guest/toolchains/native/dotnet.js +103 -0
  222. package/dist/guest/toolchains/native/mesa.d.ts +7 -0
  223. package/dist/guest/toolchains/native/mesa.js +56 -0
  224. package/dist/guest/toolchains/native/msvc/environment.d.ts +16 -0
  225. package/dist/guest/toolchains/native/msvc/environment.js +57 -0
  226. package/dist/guest/toolchains/native/msvc/index.d.ts +6 -0
  227. package/dist/guest/toolchains/native/msvc/index.js +48 -0
  228. package/dist/guest/toolchains/native/msvc/packages.d.ts +6 -0
  229. package/dist/guest/toolchains/native/msvc/packages.js +156 -0
  230. package/dist/guest/toolchains/native/msvc/tree.d.ts +15 -0
  231. package/dist/guest/toolchains/native/msvc/tree.js +21 -0
  232. package/dist/guest/toolchains/native/rust.d.ts +3 -0
  233. package/dist/guest/toolchains/native/rust.js +45 -0
  234. package/dist/guest/toolchains/node.js +26 -6
  235. package/dist/guest/toolchains/plan.d.ts +24 -0
  236. package/dist/guest/toolchains/plan.js +140 -0
  237. package/dist/guest/toolchains/pwsh.js +1 -1
  238. package/dist/guest/toolchains/python.js +43 -12
  239. package/dist/guest/toolchains/runtimes.d.ts +2 -0
  240. package/dist/guest/toolchains/runtimes.js +6 -8
  241. package/dist/guest/toolchains/scripting/php.d.ts +3 -0
  242. package/dist/guest/toolchains/scripting/php.js +71 -0
  243. package/dist/guest/toolchains/scripting/ruby.d.ts +3 -0
  244. package/dist/guest/toolchains/scripting/ruby.js +47 -0
  245. package/dist/guest/toolchains/scripting/vcruntime.d.ts +7 -0
  246. package/dist/guest/toolchains/scripting/vcruntime.js +45 -0
  247. package/dist/guest/toolchains/vscode-profile.d.ts +30 -0
  248. package/dist/guest/toolchains/vscode-profile.js +69 -0
  249. package/dist/guest/toolchains/vscode.d.ts +17 -0
  250. package/dist/guest/toolchains/vscode.js +87 -1
  251. package/dist/guest/view.js +21 -1
  252. package/dist/host/accounts.js +14 -5
  253. package/dist/host/agent-commands.d.ts +25 -0
  254. package/dist/host/agent-commands.js +145 -0
  255. package/dist/host/attach.d.ts +17 -0
  256. package/dist/host/attach.js +70 -0
  257. package/dist/host/audit/collect.d.ts +34 -0
  258. package/dist/host/audit/collect.js +126 -0
  259. package/dist/host/audit/log.d.ts +46 -0
  260. package/dist/host/audit/log.js +143 -0
  261. package/dist/host/channel.d.ts +23 -22
  262. package/dist/host/channel.js +66 -84
  263. package/dist/host/claude-profile.js +7 -3
  264. package/dist/host/cleanup.js +2 -11
  265. package/dist/host/database-plan.d.ts +12 -0
  266. package/dist/host/database-plan.js +52 -0
  267. package/dist/host/generate.d.ts +10 -12
  268. package/dist/host/generate.js +58 -135
  269. package/dist/host/join.d.ts +28 -0
  270. package/dist/host/join.js +76 -0
  271. package/dist/host/launch/mappings.d.ts +3 -0
  272. package/dist/host/launch/mappings.js +54 -0
  273. package/dist/host/launch/projects.d.ts +15 -0
  274. package/dist/host/launch/projects.js +56 -0
  275. package/dist/host/launch/stage.d.ts +11 -0
  276. package/dist/host/launch/stage.js +66 -0
  277. package/dist/host/launch/wsb.d.ts +10 -0
  278. package/dist/host/launch/wsb.js +34 -0
  279. package/dist/host/motw.d.ts +17 -0
  280. package/dist/host/motw.js +74 -0
  281. package/dist/host/pc-scan.d.ts +6 -0
  282. package/dist/host/pc-scan.js +37 -0
  283. package/dist/host/platform.d.ts +2 -0
  284. package/dist/host/platform.js +16 -1
  285. package/dist/host/port-forward.d.ts +8 -0
  286. package/dist/host/port-forward.js +37 -0
  287. package/dist/host/project-scan.d.ts +1 -17
  288. package/dist/host/project-scan.js +2 -86
  289. package/dist/host/projects.d.ts +100 -0
  290. package/dist/host/projects.js +147 -0
  291. package/dist/host/repo-guard.d.ts +25 -5
  292. package/dist/host/repo-guard.js +242 -23
  293. package/dist/host/stops.d.ts +67 -0
  294. package/dist/host/stops.js +126 -0
  295. package/dist/host/terminal.d.ts +8 -0
  296. package/dist/host/terminal.js +48 -1
  297. package/dist/host/test-guard.d.ts +6 -0
  298. package/dist/host/test-guard.js +15 -0
  299. package/dist/host/window.js +7 -3
  300. package/dist/host/workspace-view/index.d.ts +20 -0
  301. package/dist/host/workspace-view/index.js +53 -0
  302. package/dist/host/workspace-view/rules.d.ts +26 -0
  303. package/dist/host/workspace-view/rules.js +93 -0
  304. package/dist/host/workspace-view/view.d.ts +50 -0
  305. package/dist/host/workspace-view/view.js +281 -0
  306. package/dist/host/wsb.d.ts +37 -0
  307. package/dist/host/wsb.js +101 -0
  308. package/dist/runtime/daemon.js +11791 -2821
  309. package/dist/runtime/db.js +6585 -466
  310. package/dist/runtime/main.js +10238 -1724
  311. package/dist/runtime/netproxy.js +25936 -0
  312. package/dist/runtime/view.js +6183 -59
  313. package/dist/selftest/accounts.test.d.ts +2 -0
  314. package/dist/selftest/accounts.test.js +44 -0
  315. package/dist/selftest/agent-commands.test.d.ts +2 -0
  316. package/dist/selftest/agent-commands.test.js +47 -0
  317. package/dist/selftest/audit.test.d.ts +2 -0
  318. package/dist/selftest/audit.test.js +57 -0
  319. package/dist/selftest/channel.test.d.ts +2 -0
  320. package/dist/selftest/channel.test.js +44 -0
  321. package/dist/selftest/cli.test.d.ts +2 -2
  322. package/dist/selftest/cli.test.js +145 -27
  323. package/dist/selftest/cmdline.test.d.ts +2 -0
  324. package/dist/selftest/cmdline.test.js +45 -0
  325. package/dist/selftest/config.test.d.ts +2 -3
  326. package/dist/selftest/config.test.js +36 -72
  327. package/dist/selftest/data.test.d.ts +2 -2
  328. package/dist/selftest/data.test.js +77 -70
  329. package/dist/selftest/detect.test.d.ts +2 -2
  330. package/dist/selftest/detect.test.js +54 -38
  331. package/dist/selftest/fixtures.d.ts +89 -4
  332. package/dist/selftest/fixtures.js +43 -24
  333. package/dist/selftest/generate.test.d.ts +2 -0
  334. package/dist/selftest/generate.test.js +68 -0
  335. package/dist/selftest/git-access.test.d.ts +2 -0
  336. package/dist/selftest/git-access.test.js +26 -0
  337. package/dist/selftest/guest-boot.test.d.ts +2 -0
  338. package/dist/selftest/guest-boot.test.js +76 -0
  339. package/dist/selftest/harness.d.ts +44 -7
  340. package/dist/selftest/harness.js +62 -21
  341. package/dist/selftest/host-sim.test.d.ts +2 -0
  342. package/dist/selftest/host-sim.test.js +93 -0
  343. package/dist/selftest/hostile-input.test.d.ts +2 -0
  344. package/dist/selftest/hostile-input.test.js +54 -0
  345. package/dist/selftest/index.d.ts +8 -1
  346. package/dist/selftest/index.js +137 -35
  347. package/dist/selftest/links.test.d.ts +2 -0
  348. package/dist/selftest/links.test.js +50 -0
  349. package/dist/selftest/live-daemon.test.d.ts +2 -0
  350. package/dist/selftest/live-daemon.test.js +91 -0
  351. package/dist/selftest/live-git.test.d.ts +2 -0
  352. package/dist/selftest/live-git.test.js +60 -0
  353. package/dist/selftest/live-network.test.d.ts +2 -0
  354. package/dist/selftest/live-network.test.js +63 -0
  355. package/dist/selftest/live-projects.test.d.ts +2 -0
  356. package/dist/selftest/live-projects.test.js +127 -0
  357. package/dist/selftest/live.d.ts +65 -0
  358. package/dist/selftest/live.js +109 -0
  359. package/dist/selftest/local-config.test.d.ts +2 -0
  360. package/dist/selftest/local-config.test.js +120 -0
  361. package/dist/selftest/mirror.test.d.ts +2 -3
  362. package/dist/selftest/mirror.test.js +92 -65
  363. package/dist/selftest/network.test.d.ts +2 -0
  364. package/dist/selftest/network.test.js +40 -0
  365. package/dist/selftest/personalization.test.d.ts +2 -0
  366. package/dist/selftest/personalization.test.js +53 -0
  367. package/dist/selftest/projects.test.d.ts +2 -0
  368. package/dist/selftest/projects.test.js +254 -0
  369. package/dist/selftest/repair.test.d.ts +2 -0
  370. package/dist/selftest/repair.test.js +42 -0
  371. package/dist/selftest/repo-guard.test.d.ts +2 -0
  372. package/dist/selftest/repo-guard.test.js +121 -0
  373. package/dist/selftest/secrets.test.d.ts +2 -0
  374. package/dist/selftest/secrets.test.js +71 -0
  375. package/dist/selftest/setup.test.d.ts +2 -2
  376. package/dist/selftest/setup.test.js +45 -22
  377. package/dist/selftest/suite.d.ts +13 -0
  378. package/dist/selftest/suite.js +1 -0
  379. package/dist/selftest/sync.test.d.ts +2 -3
  380. package/dist/selftest/sync.test.js +130 -78
  381. package/dist/selftest/test-guard.test.d.ts +2 -0
  382. package/dist/selftest/test-guard.test.js +49 -0
  383. package/dist/selftest/toolchains.test.d.ts +2 -0
  384. package/dist/selftest/toolchains.test.js +133 -0
  385. package/dist/selftest/workspace-view.test.d.ts +2 -0
  386. package/dist/selftest/workspace-view.test.js +89 -0
  387. package/dist/selftest/workspace.test.d.ts +2 -0
  388. package/dist/selftest/workspace.test.js +53 -0
  389. package/dist/shared/apps.d.ts +49 -0
  390. package/dist/shared/apps.js +114 -0
  391. package/dist/shared/download.d.ts +3 -1
  392. package/dist/shared/download.js +19 -4
  393. package/dist/shared/netpolicy.d.ts +27 -0
  394. package/dist/shared/netpolicy.js +51 -0
  395. package/dist/shared/paths.d.ts +5 -0
  396. package/dist/shared/paths.js +6 -0
  397. package/dist/shared/projects.d.ts +53 -0
  398. package/dist/shared/projects.js +67 -0
  399. package/dist/shared/pty-client.d.ts +9 -4
  400. package/dist/shared/pty-client.js +139 -17
  401. package/dist/shared/rundir.d.ts +12 -1
  402. package/dist/shared/rundir.js +14 -4
  403. package/dist/shared/safe-remove.js +11 -0
  404. package/dist/shared/schema.d.ts +453 -0
  405. package/dist/shared/schema.js +43 -2
  406. package/dist/shared/system.d.ts +1 -0
  407. package/dist/shared/system.js +1 -0
  408. package/dist/shared/test-guard.d.ts +4 -0
  409. package/dist/shared/test-guard.js +37 -0
  410. package/dist/sync/claude-mem.js +6 -3
  411. package/dist/sync/history.d.ts +29 -12
  412. package/dist/sync/history.js +72 -25
  413. package/dist/sync/watcher.d.ts +8 -0
  414. package/dist/sync/watcher.js +99 -8
  415. package/package.json +7 -6
  416. package/sandbox/bootstrap.ps1 +258 -258
  417. package/sandbox/cargoshim.cs +56 -0
  418. package/sandbox/cpucap.cs +33 -0
  419. package/sandbox/cpucap.ps1 +20 -0
  420. package/sandbox/logon.cmd +24 -16
  421. package/sandbox/memlog.ps1 +34 -0
  422. package/sandbox/openssl.cs +42 -0
  423. package/sandbox/pagefile.cs +31 -0
  424. package/sandbox/pagefile.ps1 +21 -0
  425. package/sandbox/ptyhost.cs +120 -11
  426. package/sandbox/trust.cmd +35 -35
  427. package/schemas/config.schema.json +403 -3
  428. package/schemas/sandbox-config.schema.json +143 -13
  429. package/dist/cli/commands/data.d.ts +0 -46
  430. package/dist/cli/commands/data.js +0 -249
  431. package/dist/cli/commands/setup.js +0 -152
  432. package/dist/cli/menu.d.ts +0 -22
  433. package/dist/cli/menu.js +0 -187
  434. package/dist/cli/settings.d.ts +0 -11
  435. package/dist/cli/settings.js +0 -156
  436. package/dist/guest/toolchains/databases.js +0 -226
  437. package/dist/guest/toolchains/msvc.d.ts +0 -10
  438. package/dist/guest/toolchains/msvc.js +0 -216
  439. package/dist/guest/toolchains/native.d.ts +0 -12
  440. package/dist/guest/toolchains/native.js +0 -220
  441. package/dist/guest/toolchains/scripting.d.ts +0 -10
  442. package/dist/guest/toolchains/scripting.js +0 -137
  443. package/dist/selftest/guest.test.d.ts +0 -3
  444. package/dist/selftest/guest.test.js +0 -61
  445. package/dist/selftest/helpers.test.d.ts +0 -2
  446. package/dist/selftest/helpers.test.js +0 -125
  447. package/dist/selftest/host.test.d.ts +0 -2
  448. package/dist/selftest/host.test.js +0 -71
  449. package/dist/selftest/live.test.d.ts +0 -7
  450. package/dist/selftest/live.test.js +0 -127
  451. package/dist/selftest/security.test.d.ts +0 -3
  452. package/dist/selftest/security.test.js +0 -143
  453. /package/dist/guest/toolchains/{binstall.d.ts → native/binstall.d.ts} +0 -0
@@ -0,0 +1,57 @@
1
+ // What makes the unpacked tree behave like a Build Tools install: the vcvars environment, vcvars64.bat, vswhere.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { errMsg, mkdirp, warn } from '../../../env.js';
5
+ import { ensureCompiled } from '../../../helpers.js';
6
+ import { hostBin, ROOT } from './tree.js';
7
+ /** Where build tools look for vswhere (local to the sandbox, so it is written each boot). */
8
+ const VSWHERE_DIR = path.join(process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)', 'Microsoft Visual Studio', 'Installer');
9
+ /** Variables vcvars64.bat would set for this tree (host x64, target x64). */
10
+ export const vcvars = (root, msvc, sdk) => {
11
+ const vc = path.join(root, 'VC', 'Tools', 'MSVC', msvc);
12
+ const kits = path.join(root, 'Windows Kits', '10');
13
+ const inc = (...p) => path.join(kits, 'Include', sdk, ...p);
14
+ const lib = (...p) => path.join(kits, 'Lib', sdk, ...p);
15
+ return {
16
+ vars: {
17
+ VSINSTALLDIR: root + '\\', VCINSTALLDIR: path.join(root, 'VC') + '\\', VCToolsInstallDir: vc + '\\', VCToolsVersion: msvc, VisualStudioVersion: '17.0',
18
+ WindowsSdkDir: kits + '\\', WindowsSDKVersion: sdk + '\\', WindowsSDKLibVersion: sdk + '\\', UniversalCRTSdkDir: kits + '\\', UCRTVersion: sdk,
19
+ VSCMD_ARG_HOST_ARCH: 'x64', VSCMD_ARG_TGT_ARCH: 'x64', Platform: 'x64',
20
+ INCLUDE: [path.join(vc, 'include'), inc('ucrt'), inc('shared'), inc('um'), inc('winrt'), inc('cppwinrt')].join(';'),
21
+ LIB: [path.join(vc, 'lib', 'x64'), lib('ucrt', 'x64'), lib('um', 'x64')].join(';'),
22
+ LIBPATH: [path.join(vc, 'lib', 'x64'), path.join(vc, 'lib', 'x86', 'store', 'references')].join(';'),
23
+ },
24
+ path: [path.join(vc, 'bin', 'Hostx64', 'x64'), path.join(kits, 'bin', sdk, 'x64')],
25
+ };
26
+ };
27
+ /** vcvars64.bat / vcvarsall.bat in the usual place, for scripts that `call` them. */
28
+ export const writeVcvars = (env) => {
29
+ const dir = mkdirp(path.join(ROOT, 'VC', 'Auxiliary', 'Build'));
30
+ const lines = [
31
+ '@echo off', 'rem Written by Windows Development Sandbox for its portable MSVC (host x64, target x64).',
32
+ ...Object.entries(env.vars).map(([k, v]) => 'set "' + k + '=' + v + '"'),
33
+ 'set "PATH=' + env.path.join(';') + ';%PATH%"', 'exit /b 0',
34
+ ];
35
+ fs.writeFileSync(path.join(dir, 'vcvars64.bat'), lines.join('\r\n') + '\r\n');
36
+ fs.writeFileSync(path.join(dir, 'vcvarsall.bat'), ['@echo off', 'rem Only x64 (amd64) is installed.', 'call "%~dp0vcvars64.bat"', 'exit /b %errorlevel%'].join('\r\n') + '\r\n');
37
+ };
38
+ /** vswhere.exe where Visual Studio's installer would put it, answering with this tree. */
39
+ export const installVswhere = (msvc) => {
40
+ mkdirp(VSWHERE_DIR);
41
+ fs.copyFileSync(ensureCompiled('vswhere'), path.join(VSWHERE_DIR, 'vswhere.exe'));
42
+ const [major, minor] = msvc.split('.').slice(2);
43
+ fs.writeFileSync(path.join(VSWHERE_DIR, 'vswhere.json'), JSON.stringify({ installationPath: ROOT, installationVersion: '17.' + (minor ?? '0') + '.0.' + (major ?? '0') }));
44
+ };
45
+ /**
46
+ * cl.exe and link.exe start vctip.exe (Visual Studio's telemetry uploader), which lingers after the build and keeps the
47
+ * folder locked, so `sandbox repair` / `data clean` could not delete tools\msvc. Neither VSCMD_SKIP_SENDTELEMETRY nor
48
+ * the TurnOffSwitch registry value stops it; without the file the compiler simply skips it. Nothing else needs it.
49
+ */
50
+ export const removeTelemetry = (msvc) => {
51
+ try {
52
+ fs.rmSync(hostBin(msvc, 'vctip.exe'), { force: true });
53
+ }
54
+ catch (e) {
55
+ warn('Could not remove MSVC\'s telemetry helper (vctip.exe); it may keep tools\\msvc locked: ' + errMsg(e));
56
+ }
57
+ };
@@ -0,0 +1,6 @@
1
+ import type { Toolchains } from '../../../../core/types.js';
2
+ import type { ToolEnv } from '../../common.js';
3
+ import { type VcEnv } from './environment.js';
4
+ /** The vcvars environment of an installed tree (for steps that run before the agent's environment exists). */
5
+ export declare const installedMsvcEnv: () => VcEnv | null;
6
+ export declare const setupMsvc: (env: ToolEnv, config: Toolchains["msvc"]) => Promise<void>;
@@ -0,0 +1,48 @@
1
+ // MSVC (cl, link) and the Windows SDK without an installer (packages.ts), laid out like a Build Tools install with
2
+ // vcvars64.bat and a vswhere that points at it (environment.ts), so CMake, cargo (msvc host), node-gyp and vcvars-based
3
+ // scripts work. Use is governed by the Visual Studio licence terms: it only installs with toolchains.msvc.acceptLicense.
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { writeJsonAtomic } from '../../../../shared/rundir.js';
7
+ import { safeRemove } from '../../../../shared/safe-remove.js';
8
+ import { dryRun, exists, log } from '../../../env.js';
9
+ import { installVswhere, removeTelemetry, vcvars, writeVcvars } from './environment.js';
10
+ import { installPackages } from './packages.js';
11
+ import { hasCompiler, MARKER, readInstalled, ROOT } from './tree.js';
12
+ /** The vcvars environment of an installed tree (for steps that run before the agent's environment exists). */
13
+ export const installedMsvcEnv = () => {
14
+ const done = readInstalled();
15
+ return done && hasCompiler(done.msvc) ? vcvars(ROOT, done.msvc, done.sdk) : null;
16
+ };
17
+ /** Empties tools\msvc for a fresh install, keeping the manifest and the verified downloads of an earlier attempt. */
18
+ const clearTree = () => {
19
+ if (!exists(ROOT))
20
+ return;
21
+ for (const name of fs.readdirSync(ROOT).filter((n) => n !== '.manifest.json' && n !== '.download'))
22
+ safeRemove(path.join(ROOT, name), ROOT);
23
+ };
24
+ export const setupMsvc = async (env, config) => {
25
+ if (!config.acceptLicense) {
26
+ throw new Error('MSVC is installed only with toolchains.msvc.acceptLicense = true: its use is governed by the Visual Studio licence terms (https://visualstudio.microsoft.com/license-terms/), which need a Visual Studio licence');
27
+ }
28
+ if (dryRun) {
29
+ log('(dry run) would provision MSVC ' + config.msvcVersion + ' and Windows SDK ' + config.sdkVersion);
30
+ return;
31
+ }
32
+ const want = config.msvcVersion + '/' + config.sdkVersion;
33
+ let done = readInstalled();
34
+ if (!done || done.want !== want || !hasCompiler(done.msvc)) {
35
+ log('Installing MSVC and the Windows SDK (~1-2 GB, once; licence accepted in toolchains.msvc.acceptLicense) ...');
36
+ clearTree();
37
+ done = { ...(await installPackages(config)), want };
38
+ // Written last and atomically: without it (an interrupted install) the next boot starts over.
39
+ writeJsonAtomic(MARKER, done);
40
+ }
41
+ removeTelemetry(done.msvc);
42
+ const vc = vcvars(ROOT, done.msvc, done.sdk);
43
+ writeVcvars(vc);
44
+ installVswhere(done.msvc);
45
+ Object.assign(env.vars, vc.vars);
46
+ env.prepend.push(...vc.path);
47
+ env.summary.push('msvc: ' + done.msvc + ', Windows SDK ' + done.sdk + ' (cl, link; vcvars64.bat and vswhere work)');
48
+ };
@@ -0,0 +1,6 @@
1
+ import type { Toolchains } from '../../../../core/types.js';
2
+ /** Downloads and unpacks MSVC and the Windows SDK into tools\msvc; resolves with the toolset and SDK folder names. */
3
+ export declare const installPackages: (config: Toolchains["msvc"]) => Promise<{
4
+ msvc: string;
5
+ sdk: string;
6
+ }>;
@@ -0,0 +1,156 @@
1
+ // Downloads the MSVC compiler and the Windows SDK from Microsoft's Visual Studio channel manifest, the way mmozeiko's
2
+ // portable-msvc.py does: each payload is checked against the manifest's sha256, the compiler is unpacked from its
3
+ // .vsix files, the SDK from its .msi files with `msiexec /a`.
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { download, extractArchive, fetchJson } from '../../../../shared/download.js';
7
+ import { safeRemove } from '../../../../shared/safe-remove.js';
8
+ import { system32 } from '../../../../shared/system.js';
9
+ import { execAsync, exists, log, mkdirp } from '../../../env.js';
10
+ import { msiCabs, pickMsvcVersion, pickWindowsSdk } from '../../versions.js';
11
+ import { hostBin, ROOT } from './tree.js';
12
+ const CHANNEL = 'https://aka.ms/vs/17/release/channel';
13
+ const MANIFEST_DAY_MS = 24 * 60 * 60 * 1000;
14
+ /** The compiler's packages (host x64, target x64): tools, resources, CRT headers/libs, ASAN/PGO headers. */
15
+ const MSVC_PARTS = ['tools.hostx64.targetx64.base', 'tools.hostx64.targetx64.res.base', 'crt.headers.base', 'crt.x64.desktop.base', 'crt.x64.store.base', 'crt.source.base', 'asan.headers.base', 'pgo.headers.base'];
16
+ /** Without these the compiler is unusable; the other parts are taken when the manifest has them. */
17
+ const REQUIRED_PARTS = ['tools', 'crt.headers', 'crt.x64.desktop'];
18
+ /** The SDK's .msi files (Desktop + UCRT + Store headers/libs/tools for x64), as in portable-msvc.py. */
19
+ const SDK_MSIS = [
20
+ 'Windows SDK for Windows Store Apps Tools-x86_en-us.msi', 'Windows SDK for Windows Store Apps Headers-x86_en-us.msi',
21
+ 'Windows SDK for Windows Store Apps Headers OnecoreUap-x86_en-us.msi', 'Windows SDK for Windows Store Apps Libs-x86_en-us.msi',
22
+ 'Universal CRT Headers Libraries and Sources-x86_en-us.msi', 'Windows SDK Desktop Headers x86-x86_en-us.msi',
23
+ 'Windows SDK Desktop Headers x64-x86_en-us.msi', 'Windows SDK Desktop Libs x64-x86_en-us.msi', 'Windows SDK Desktop Tools x64-x86_en-us.msi',
24
+ ];
25
+ /** The VS manifest (~18 MB), cached for a day. */
26
+ const manifest = async () => {
27
+ const cached = path.join(ROOT, '.manifest.json');
28
+ if (exists(cached) && Date.now() - fs.statSync(cached).mtimeMs < MANIFEST_DAY_MS) {
29
+ // A download cut short leaves unreadable JSON: fetch it again instead of failing until the day is over.
30
+ try {
31
+ return JSON.parse(fs.readFileSync(cached, 'utf8'));
32
+ }
33
+ catch { /* downloaded again below */ }
34
+ }
35
+ const channel = await fetchJson(CHANNEL);
36
+ const item = channel.channelItems.find((i) => i.id === 'Microsoft.VisualStudio.Manifests.VisualStudio')?.payloads?.[0];
37
+ if (!item)
38
+ throw new Error('the Visual Studio channel has no package manifest');
39
+ mkdirp(ROOT);
40
+ // The channel's sha256 for the manifest is not the file's hash (portable-msvc.py does not check it either); the
41
+ // manifest comes over HTTPS from Microsoft, and every package payload it lists IS checked against its own sha256.
42
+ await download(item.url, cached);
43
+ return JSON.parse(fs.readFileSync(cached, 'utf8'));
44
+ };
45
+ /** .vsix contents are percent-encoded file names under Contents/; unpack them into the root, names decoded. */
46
+ const unpackVsix = async (vsix, work) => {
47
+ const out = mkdirp(path.join(work, 'vsix-' + path.basename(vsix)));
48
+ await extractArchive(vsix, out, ['Contents']);
49
+ const decode = (dir) => {
50
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
51
+ const from = path.join(dir, entry.name);
52
+ let name = entry.name;
53
+ try {
54
+ if (name.includes('%'))
55
+ name = decodeURIComponent(name);
56
+ }
57
+ catch { /* not percent-encoded after all */ }
58
+ const to = path.join(dir, name);
59
+ if (to !== from)
60
+ fs.renameSync(from, to);
61
+ if (entry.isDirectory())
62
+ decode(to);
63
+ }
64
+ };
65
+ decode(out);
66
+ fs.cpSync(path.join(out, 'Contents'), ROOT, { recursive: true, force: true });
67
+ safeRemove(out, work);
68
+ };
69
+ /** An administrative install only unpacks files; its verbose log explains a failure (e.g. a path over 260 characters). */
70
+ const unpackMsi = async (msi) => {
71
+ const logFile = msi + '.log';
72
+ try {
73
+ await execAsync(system32('msiexec.exe'), ['/a', msi, '/quiet', '/qn', 'TARGETDIR=' + ROOT, '/l*v', logFile], { stdio: 'ignore' });
74
+ }
75
+ catch (e) {
76
+ const lines = exists(logFile) ? fs.readFileSync(logFile, 'utf16le').split(/\r?\n/).filter((l) => /error|return value 3|failed/i.test(l)).slice(-4).join(' | ') : '';
77
+ throw new Error(path.basename(msi) + ' did not unpack: ' + (e instanceof Error ? e.message : String(e)) + (lines ? ' (' + lines.slice(0, 600) + ')' : ''));
78
+ }
79
+ };
80
+ /** The manifest's packages by id, with the lookups the installer needs. */
81
+ const catalogue = (m) => {
82
+ const byId = new Map();
83
+ for (const p of m.packages)
84
+ byId.set(p.id.toLowerCase(), [...(byId.get(p.id.toLowerCase()) ?? []), p]);
85
+ return {
86
+ ids: [...byId.keys()],
87
+ pick: (id) => byId.get(id.toLowerCase())?.find((p) => !p.language || p.language === 'en-US'),
88
+ };
89
+ };
90
+ /** Downloads (once, verified) into `work`; a verified file from an earlier attempt is reused. */
91
+ const payloadFetcher = (work) => async (p) => {
92
+ const file = path.join(work, path.basename(p.fileName.replace(/\\/g, '/')));
93
+ if (!exists(file))
94
+ await download(p.url, file, p.sha256);
95
+ return file;
96
+ };
97
+ /** Newest folder under VC\Tools\MSVC that holds a compiler (named after the toolset, 14.44.35207, not the package version). */
98
+ const unpackedToolset = () => {
99
+ const toolsRoot = path.join(ROOT, 'VC', 'Tools', 'MSVC');
100
+ return fs.readdirSync(toolsRoot).filter((d) => exists(hostBin(d, 'cl.exe'))).sort((a, b) => a.localeCompare(b, undefined, { numeric: true })).pop();
101
+ };
102
+ /** Downloads and unpacks MSVC and the Windows SDK into tools\msvc; resolves with the toolset and SDK folder names. */
103
+ export const installPackages = async (config) => {
104
+ const { ids, pick } = catalogue(await manifest());
105
+ const version = pickMsvcVersion(ids, config.msvcVersion);
106
+ if (!version)
107
+ throw new Error('no MSVC ' + config.msvcVersion + ' in the Visual Studio manifest');
108
+ const sdk = pickWindowsSdk(ids, config.sdkVersion);
109
+ if (!sdk)
110
+ throw new Error('no Windows SDK ' + config.sdkVersion + ' in the Visual Studio manifest');
111
+ const work = mkdirp(path.join(ROOT, '.download'));
112
+ const fetchPayload = payloadFetcher(work);
113
+ log('Downloading the MSVC ' + version + ' compiler and C/C++ libraries ...');
114
+ for (const part of MSVC_PARTS) {
115
+ const p = pick('microsoft.vc.' + version + '.' + part);
116
+ if (!p && REQUIRED_PARTS.some((r) => part.startsWith(r)))
117
+ throw new Error('MSVC ' + version + ' has no ' + part);
118
+ for (const payload of p?.payloads ?? [])
119
+ await unpackVsix(await fetchPayload(payload), work);
120
+ }
121
+ for (const payload of pick('microsoft.visualcpp.dia.sdk')?.payloads ?? [])
122
+ await unpackVsix(await fetchPayload(payload), work);
123
+ // The SDK component depends on the package that holds the .msi/.cab payloads (e.g. Win11SDK_10.0.26100).
124
+ const sdkPackage = pick(Object.keys(pick(sdk.componentId)?.dependencies ?? {})[0] ?? '');
125
+ if (!sdkPackage?.payloads)
126
+ throw new Error('the Windows SDK ' + sdk.build + ' package has no payloads');
127
+ log('Downloading the Windows SDK ' + sdk.build + ' (headers, libraries; the biggest part) ...');
128
+ const payload = (name) => sdkPackage.payloads.find((p) => p.fileName.toLowerCase() === ('installers\\' + name).toLowerCase());
129
+ const msis = [];
130
+ for (const name of SDK_MSIS) {
131
+ const p = payload(name);
132
+ if (!p)
133
+ throw new Error('the Windows SDK package has no ' + name);
134
+ const file = await fetchPayload(p);
135
+ msis.push(file);
136
+ for (const cab of msiCabs(fs.readFileSync(file))) {
137
+ const c = payload(cab);
138
+ if (c)
139
+ await fetchPayload(c);
140
+ }
141
+ }
142
+ log('Unpacking the Windows SDK (a few minutes) ...');
143
+ for (const msi of msis)
144
+ await unpackMsi(msi);
145
+ // msiexec /a also copies every .msi into the target; they are not needed.
146
+ for (const name of fs.readdirSync(ROOT).filter((n) => n.toLowerCase().endsWith('.msi')))
147
+ fs.rmSync(path.join(ROOT, name), { force: true });
148
+ safeRemove(work, ROOT);
149
+ const sdkVersion = fs.readdirSync(path.join(ROOT, 'Windows Kits', '10', 'Include')).filter((d) => d.startsWith('10.0.' + sdk.build)).sort().pop();
150
+ if (!sdkVersion)
151
+ throw new Error('the Windows SDK did not unpack (no Include\\10.0.' + sdk.build + '.*)');
152
+ const toolset = unpackedToolset();
153
+ if (!toolset)
154
+ throw new Error('MSVC ' + version + ' did not unpack (no cl.exe under VC\\Tools\\MSVC)');
155
+ return { msvc: toolset, sdk: sdkVersion };
156
+ };
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ export declare const ROOT: string;
3
+ export declare const MARKER: string;
4
+ export declare const msvcTools: (msvc: string, ...parts: string[]) => string;
5
+ export declare const hostBin: (msvc: string, ...parts: string[]) => string;
6
+ export declare const hasCompiler: (msvc: string) => boolean;
7
+ declare const Installed: z.ZodObject<{
8
+ msvc: z.ZodString;
9
+ sdk: z.ZodString;
10
+ want: z.ZodString;
11
+ }, z.core.$strip>;
12
+ export type Installed = z.infer<typeof Installed>;
13
+ /** What the last finished install recorded; null for a missing, unreadable or half-written marker. */
14
+ export declare const readInstalled: () => Installed | null;
15
+ export {};
@@ -0,0 +1,21 @@
1
+ // Where the portable MSVC lives (tools\msvc) and what the last finished install recorded about it.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { z } from 'zod';
5
+ import { exists } from '../../../env.js';
6
+ import { tool } from '../../common.js';
7
+ export const ROOT = tool('msvc');
8
+ export const MARKER = path.join(ROOT, '.installed.json');
9
+ export const msvcTools = (msvc, ...parts) => path.join(ROOT, 'VC', 'Tools', 'MSVC', msvc, ...parts);
10
+ export const hostBin = (msvc, ...parts) => msvcTools(msvc, 'bin', 'Hostx64', 'x64', ...parts);
11
+ export const hasCompiler = (msvc) => exists(hostBin(msvc, 'cl.exe'));
12
+ const Installed = z.object({ msvc: z.string().min(1), sdk: z.string().min(1), want: z.string() });
13
+ /** What the last finished install recorded; null for a missing, unreadable or half-written marker. */
14
+ export const readInstalled = () => {
15
+ try {
16
+ return Installed.safeParse(JSON.parse(fs.readFileSync(MARKER, 'utf8'))).data ?? null;
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ };
@@ -0,0 +1,3 @@
1
+ import type { Toolchains } from '../../../core/types.js';
2
+ import { type ToolEnv } from '../common.js';
3
+ export declare const setupRust: (env: ToolEnv, config: Toolchains["rust"], refresh: boolean) => Promise<void>;
@@ -0,0 +1,45 @@
1
+ // Rust: rustup + cargo, with crates installed prebuilt where possible (binstall.ts).
2
+ import path from 'node:path';
3
+ import { dryRun, execAsync, exists, log } from '../../env.js';
4
+ import { arch, downloadFile, installOnce, tool, versionOf } from '../common.js';
5
+ import { crateInstaller } from './binstall.js';
6
+ import { installedMsvcEnv } from './msvc/index.js';
7
+ export const setupRust = async (env, config, refresh) => {
8
+ const RUSTUP_HOME = tool('rust', 'rustup');
9
+ const CARGO_HOME = tool('rust', 'cargo');
10
+ const rustEnv = { env: { ...process.env, RUSTUP_HOME, CARGO_HOME } };
11
+ const rustup = path.join(CARGO_HOME, 'bin', 'rustup.exe');
12
+ if (dryRun) {
13
+ log('(dry run) would provision Rust ' + config.toolchain + ' (' + config.host + ')');
14
+ return;
15
+ }
16
+ if (!exists(rustup)) {
17
+ const init = tool('rust', 'rustup-init.exe');
18
+ log('Installing Rust with rustup (~300 MB once; the toolchain downloads after rustup itself) ...');
19
+ await downloadFile('https://static.rust-lang.org/rustup/dist/' + (arch === 'arm64' ? 'aarch64' : 'x86_64') + '-pc-windows-msvc/rustup-init.exe', init);
20
+ await execAsync(init, ['-y', '--no-modify-path', '--profile', 'default', '--default-host', config.host, '--default-toolchain', config.toolchain], rustEnv);
21
+ }
22
+ else {
23
+ // The host can change (GNU <-> msvc with toolchains.msvc): install the toolchain for it; a no-op when it is there.
24
+ await execAsync(rustup, ['set', 'default-host', config.host], { ...rustEnv, stdio: 'ignore' });
25
+ await execAsync(rustup, ['toolchain', 'install', config.toolchain, '--profile', 'default', '--no-self-update'], { ...rustEnv, stdio: 'ignore' });
26
+ await execAsync(rustup, ['default', config.toolchain], { ...rustEnv, stdio: 'ignore' });
27
+ if (refresh)
28
+ await execAsync(rustup, ['update'], rustEnv);
29
+ }
30
+ Object.assign(env.vars, { RUSTUP_HOME, CARGO_HOME });
31
+ env.prepend.push(path.join(CARGO_HOME, 'bin'));
32
+ env.summary.push('rust: ' + versionOf(path.join(CARGO_HOME, 'bin', 'rustc.exe'), ['--version'], rustEnv) + ' [' + config.host + ']');
33
+ const toolchainKey = 'rustup@' + config.toolchain;
34
+ await installOnce(env, toolchainKey + ' components', config.components, (c) => execAsync(rustup, ['component', 'add', c], rustEnv));
35
+ await installOnce(env, toolchainKey + ' targets', config.targets, (t) => execAsync(rustup, ['target', 'add', t], rustEnv));
36
+ // cargo install links with MSVC's link.exe (msvc host) or MinGW's gcc (GNU host). Those steps ran first, but their
37
+ // PATH only reaches the agent at the end, so it is put together here.
38
+ const msvc = config.host.endsWith('-msvc') ? installedMsvcEnv() : null;
39
+ const mingw = tool('mingw', 'mingw64', 'bin');
40
+ const cargoEnv = msvc
41
+ ? { env: { ...rustEnv.env, ...msvc.vars, PATH: [path.join(CARGO_HOME, 'bin'), ...msvc.path, process.env.PATH ?? ''].join(path.delimiter) } }
42
+ : { env: { ...rustEnv.env, CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER: path.join(mingw, 'gcc.exe'), PATH: [path.join(CARGO_HOME, 'bin'), mingw, process.env.PATH ?? ''].join(path.delimiter) } };
43
+ // Prebuilt through cargo-binstall where the crate publishes a Windows build, else compiled (same "cargo" keys as before).
44
+ await installOnce(env, 'cargo', config.crates, crateInstaller(CARGO_HOME, cargoEnv));
45
+ };
@@ -4,8 +4,10 @@ import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { spawn } from 'node:child_process';
6
6
  import { fetchJson, githubAsset, installArchive } from '../../shared/download.js';
7
+ import { safeRemove } from '../../shared/safe-remove.js';
7
8
  import { copyNode, dryRun, errMsg, execAsync, exists, hostToolsDir, localDir, log, mkdirp, warn } from '../env.js';
8
9
  import { arch, installOnce, resolveCached, tool } from './common.js';
10
+ import { duringSession } from '../project-context.js';
9
11
  import { pickNodeVersion, pickSpreadVersions } from './versions.js';
10
12
  const NVM_HOME = tool('nvm');
11
13
  const NVM_SYMLINK = path.join(localDir, 'nodejs');
@@ -41,7 +43,9 @@ const ensureNvm = async () => {
41
43
  if (exists(path.join(HOST_NVM, 'nvm.exe'))) {
42
44
  log('Copying nvm from this PC ...');
43
45
  mkdirp(NVM_HOME);
44
- for (const file of fs.readdirSync(HOST_NVM).filter((f) => /\.(exe|cmd|vbs)$/i.test(f)))
46
+ // nvm.exe last: it is what marks the install as done, so an interrupted copy is redone.
47
+ const files = fs.readdirSync(HOST_NVM).filter((f) => /\.(exe|cmd|vbs)$/i.test(f)).sort((a, b) => Number(a.toLowerCase() === 'nvm.exe') - Number(b.toLowerCase() === 'nvm.exe'));
48
+ for (const file of files)
45
49
  fs.copyFileSync(path.join(HOST_NVM, file), path.join(NVM_HOME, file));
46
50
  return;
47
51
  }
@@ -61,10 +65,15 @@ const installNodeVersion = async (version) => {
61
65
  copyNode(fromHost, dir);
62
66
  return;
63
67
  }
64
- log('nvm install ' + version + ' ...');
68
+ log('Downloading Node ' + version + ' (~30 MB once) ...');
65
69
  mkdirp(NVM_TEMP);
66
- const out = await nvm(['install', version]);
67
- fs.rmSync(NVM_TEMP, { recursive: true, force: true });
70
+ let out;
71
+ try {
72
+ out = await nvm(['install', version]);
73
+ }
74
+ finally {
75
+ safeRemove(NVM_TEMP, NVM_HOME);
76
+ }
68
77
  if (!exists(path.join(dir, 'node.exe')))
69
78
  throw new Error('nvm install ' + version + ' failed: ' + out.trim().split('\n').slice(-2).join(' '));
70
79
  };
@@ -102,9 +111,15 @@ export const setupNode = async (env, versions, config) => {
102
111
  warn('Node ' + version + ' from this PC: ' + errMsg(e));
103
112
  }
104
113
  }
105
- await activate(main);
106
114
  Object.assign(env.vars, { NVM_HOME, NVM_SYMLINK });
107
- env.prepend.push(NVM_HOME, NVM_SYMLINK);
115
+ // At boot, nvm's symlink selects the version. A project added later must not switch it under the sessions already
116
+ // running: its own version's folder goes first on its sessions' PATH instead.
117
+ if (duringSession())
118
+ env.prepend.push(path.join(NVM_HOME, 'v' + main), NVM_HOME, NVM_SYMLINK);
119
+ else {
120
+ await activate(main);
121
+ env.prepend.push(NVM_HOME, NVM_SYMLINK);
122
+ }
108
123
  env.summary.push('node: ' + main + ' (from ' + versions.node.source + ', switch with nvm use)');
109
124
  await packageManagers(env, config, main);
110
125
  };
@@ -122,6 +137,11 @@ const packageManagers = async (env, config, version) => {
122
137
  // Without a packageManager pin, corepack's own known-good versions: the newest pnpm can be one this corepack cannot
123
138
  // start. Mapped folders allow no symlinks, so pnpm uses a flat (hoisted) node_modules instead of its linked layout.
124
139
  Object.assign(env.vars, { COREPACK_HOME: tool('corepack'), COREPACK_ENABLE_DOWNLOAD_PROMPT: '0', COREPACK_DEFAULT_TO_LATEST: '0', npm_config_node_linker: 'hoisted' });
140
+ // The shims are shared by every session: a project added later keeps the ones made at boot.
141
+ if (duringSession() && exists(tool('bin', 'pnpm.cmd'))) {
142
+ env.summary.push('pnpm, yarn: via corepack');
143
+ return;
144
+ }
125
145
  try {
126
146
  await execAsync(path.join(dir, 'node.exe'), [corepackJs, 'enable', '--install-directory', mkdirp(tool('bin')), 'pnpm', 'yarn'], { env: nodeEnv, stdio: 'ignore' });
127
147
  env.summary.push('pnpm, yarn: via corepack (package.json "packageManager" picks the version)');
@@ -0,0 +1,24 @@
1
+ import type { Config, DatabasePlan, GitAccess, Toolchains, ToolVersions } from '../../core/types.js';
2
+ import { type ToolEnv } from './common.js';
3
+ /** apps: the boot only (apps are machine-wide; a project that joins later shares them). */
4
+ export interface ToolchainOptions {
5
+ gitEnabled: boolean;
6
+ gitAccess: GitAccess;
7
+ hostGit: boolean;
8
+ refresh: boolean;
9
+ databases: DatabasePlan[];
10
+ apps?: Config['apps'];
11
+ }
12
+ /**
13
+ * shared: the step's effect is machine-wide and the same for every project (an installer, a registry entry, a
14
+ * shortcut, the git guard). A project added after boot reuses what the boot's run contributed instead of running it
15
+ * again under the sessions that already use it.
16
+ */
17
+ export interface Step {
18
+ name: string;
19
+ on: boolean;
20
+ after?: string[];
21
+ shared?: boolean;
22
+ run: (env: ToolEnv) => Promise<void> | void;
23
+ }
24
+ export declare const plan: (config: Toolchains, versions: ToolVersions, o: ToolchainOptions) => Step[];
@@ -0,0 +1,140 @@
1
+ // Which toolchain and database steps this sandbox runs, in the order their PATH entries are listed, and what each
2
+ // step depends on. The scheduler (index.ts) runs them.
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { appsNeedMesa, setupApps } from '../apps/index.js';
7
+ import { dataDir, dryRun, execAsync, exists, localDir, mkdirp } from '../env.js';
8
+ import { ensureCompiled } from '../helpers.js';
9
+ import { arch, installOnce, tool, wanted } from './common.js';
10
+ import { DATABASE_NAMES, setupDatabase } from './databases/index.js';
11
+ import { setupGit } from './git.js';
12
+ import { setupGo } from './go.js';
13
+ import { setupJava } from './java.js';
14
+ import { setupCli } from './cli.js';
15
+ import { setupPyenv, setupVolta, wantsPyenv, wantsVolta } from './managers.js';
16
+ import { setupCpp } from './native/cpp.js';
17
+ import { setupDotnet } from './native/dotnet.js';
18
+ import { setupMesa } from './native/mesa.js';
19
+ import { setupMsvc } from './native/msvc/index.js';
20
+ import { setupRust } from './native/rust.js';
21
+ import { setupNode } from './node.js';
22
+ import { setupPwsh } from './pwsh.js';
23
+ import { setupPython } from './python.js';
24
+ import { setupBun, setupDeno, setupUv, uvVars } from './runtimes.js';
25
+ import { setupPhp } from './scripting/php.js';
26
+ import { setupRuby } from './scripting/ruby.js';
27
+ import { setupVcRuntime } from './scripting/vcruntime.js';
28
+ import { setupVscode } from './vscode.js';
29
+ /** Python command-line tools through uv (each in its own environment, commands in uv's bin folder on PATH). */
30
+ const setupPythonTools = async (env, tools) => {
31
+ const uv = tool('uv', 'uv.exe');
32
+ if (!exists(uv))
33
+ throw new Error('toolchains.python.tools needs uv (toolchains.uv.enabled)');
34
+ await installOnce(env, 'uv tool', tools, (pkg) => execAsync(uv, ['tool', 'install', pkg], { env: { ...process.env, ...uvVars() } }));
35
+ };
36
+ /**
37
+ * Where cargo builds: shared with the PC's target\, one on the sandbox's disk, or a path. "separate" sets no variable:
38
+ * the cargo shim (sandbox/cargoshim.cs) gives every checkout its own target\sandbox, so git worktrees of several agents
39
+ * never overwrite each other's builds.
40
+ */
41
+ const cargoTarget = (targetDir) => {
42
+ if (targetDir === 'shared' || targetDir === 'separate')
43
+ return null;
44
+ if (targetDir === 'local')
45
+ return path.join(localDir, 'cargo-target');
46
+ return targetDir;
47
+ };
48
+ /** "separate": tools\shims\cargo\cargo.exe in front of the real cargo, which it runs with the checkout's target dir. */
49
+ const cargoShim = (env, rust) => {
50
+ const real = path.join(tool('rust', 'cargo', 'bin'), 'cargo.exe');
51
+ if (rust.targetDir !== 'separate' || !exists(real))
52
+ return;
53
+ const dir = mkdirp(tool('shims', 'cargo'));
54
+ fs.copyFileSync(ensureCompiled('cargoshim'), path.join(dir, 'cargo.exe'));
55
+ env.prepend.unshift(dir);
56
+ env.vars.AGENT_REAL_CARGO = real;
57
+ };
58
+ /**
59
+ * Cargo defaults that fit Windows, written only when there is no $CARGO_HOME\config.toml yet: fewer parallel jobs
60
+ * (parallel rustc ran out of paging file) and, for the GNU host, only line tables as debug info (full debug info made
61
+ * test executables bigger than the 2 GB Windows allows).
62
+ */
63
+ const cargoDefaults = (host) => {
64
+ const file = tool('rust', 'cargo', 'config.toml');
65
+ if (exists(file) || !exists(path.dirname(file)))
66
+ return;
67
+ // Several agents often build at once: up to 4 jobs each (8 each crashed Windows inside the sandbox on 13 Oct 2026).
68
+ const jobs = Math.max(2, Math.min(4, Math.floor(os.cpus().length / 4)));
69
+ const gnu = host.endsWith('-gnu');
70
+ fs.writeFileSync(file, [
71
+ '# Written by windows-development-sandbox once; edit freely, it is never overwritten.',
72
+ '[build]', 'jobs = ' + jobs, '',
73
+ ...(gnu ? ['[profile.dev]', 'debug = "line-tables-only"', '', '[profile.test]', 'debug = "line-tables-only"', ''] : []),
74
+ ].join('\n'));
75
+ };
76
+ /**
77
+ * Where cargo builds and where sccache keeps its cache. Cargo fingerprints hold absolute paths and the host toolchain,
78
+ * so a target\\ shared with builds on the PC rebuilds every time the side changes: "separate" (the default) keeps a
79
+ * sandbox one next to it. sccache's cache persists unless the project sets SCCACHE_DIR itself.
80
+ */
81
+ const rustCaches = (rust) => {
82
+ const target = cargoTarget(rust.targetDir);
83
+ return { ...(target ? { CARGO_TARGET_DIR: target } : {}), ...(process.env.SCCACHE_DIR ? {} : { SCCACHE_DIR: path.join(dataDir, 'cache', 'sccache') }) };
84
+ };
85
+ /** "auto": the msvc host when the MSVC step runs (as on most PCs), else GNU with MinGW. */
86
+ const rustHost = (config) => {
87
+ if (config.rust.host !== 'auto')
88
+ return config.rust.host;
89
+ const cpu = arch === 'arm64' ? 'aarch64' : 'x86_64';
90
+ return cpu + (wanted(config.msvc.enabled, 'msvc') ? '-pc-windows-msvc' : '-pc-windows-gnu');
91
+ };
92
+ export const plan = (config, versions, o) => {
93
+ const apps = Boolean(o.apps && (o.apps.install.length || Object.keys(o.apps.custom).length));
94
+ const java = wanted(config.java.enabled, 'java');
95
+ const python = wanted(config.python.enabled, 'python');
96
+ return [
97
+ { name: 'Git', on: o.gitEnabled, shared: true, run: (env) => setupGit(env, o.hostGit, o.refresh, o.gitAccess) },
98
+ // Before Node: with package.json pins, Volta's node/npm/pnpm/yarn come first on PATH.
99
+ { name: 'Volta', on: wantsVolta(config.node.volta), run: (env) => setupVolta(env) },
100
+ { name: 'Node ' + versions.node.spec, on: true, run: (env) => setupNode(env, versions, config.node) },
101
+ { name: 'Python', on: python, run: (env) => setupPython(env, config.python, versions) },
102
+ { name: 'pyenv-win', on: python && wantsPyenv(config.python.pyenv), run: (env) => setupPyenv(env) },
103
+ { name: 'C/C++', on: wanted(config.cpp.enabled, 'cpp') || wanted(config.rust.enabled, 'rust'), run: (env) => setupCpp(env, config.cpp) },
104
+ { name: 'MSVC', on: wanted(config.msvc.enabled, 'msvc'), run: (env) => setupMsvc(env, config.msvc) },
105
+ // Rust links with MSVC (msvc host) or MinGW's gcc (GNU host): whichever it uses goes first.
106
+ {
107
+ name: 'Rust', on: wanted(config.rust.enabled, 'rust'), after: ['C/C++', 'MSVC'],
108
+ run: async (env) => {
109
+ await setupRust(env, { ...config.rust, host: rustHost(config) }, o.refresh);
110
+ Object.assign(env.vars, rustCaches(config.rust));
111
+ if (!dryRun) {
112
+ cargoDefaults(rustHost(config));
113
+ cargoShim(env, config.rust);
114
+ }
115
+ },
116
+ },
117
+ { name: '.NET', on: wanted(config.dotnet.enabled, 'dotnet'), run: (env) => setupDotnet(env, config.dotnet, o.refresh) },
118
+ { name: 'Go', on: wanted(config.go.enabled, 'go'), run: (env) => setupGo(env, config.go) },
119
+ { name: 'Java', on: java, run: (env) => setupJava(env, config.java) },
120
+ { name: 'Ruby', on: wanted(config.ruby.enabled, 'ruby'), run: (env) => setupRuby(env, config.ruby) },
121
+ // PHP installs the Visual C++ runtime itself: one installer at a time.
122
+ // Many apps (bat, delta, Neovim, most GUIs, your own programs) are built against it too.
123
+ { name: 'Visual C++ runtime', on: config.vcRuntime.enabled || apps, shared: true, run: async (env) => { await setupVcRuntime(env); } },
124
+ { name: 'PHP', on: wanted(config.php.enabled, 'php'), after: ['Visual C++ runtime'], run: (env) => setupPhp(env, config.php) },
125
+ { name: 'Deno', on: wanted(config.deno.enabled, 'deno'), run: (env) => setupDeno(env) },
126
+ { name: 'Bun', on: config.bun.enabled, run: (env) => setupBun(env) },
127
+ { name: 'uv', on: config.uv.enabled, run: (env) => setupUv(env) },
128
+ { name: 'PowerShell 7', on: wanted(config.pwsh.enabled, 'pwsh'), run: (env) => setupPwsh(env, o.refresh) },
129
+ // The openssl tool comes from Git's install: after Git.
130
+ { name: 'CLI tools', on: Object.values(config.cli).some((v) => v !== false), after: ['Git'], run: (env) => setupCli(env, config.cli, o.gitAccess, o.refresh) },
131
+ { name: 'Mesa 3D', on: config.mesa.enabled || appsNeedMesa(o.apps), shared: true, run: (env) => setupMesa(env, config.mesa) },
132
+ { name: 'VS Code', on: config.vscode.enabled, shared: true, run: (env) => setupVscode(env, o.refresh) },
133
+ // Desktop apps and extra tools (apps.install / apps.custom); games and 3D apps copy Mesa's OpenGL next to them.
134
+ { name: 'Apps', on: apps, shared: true, after: ['Mesa 3D', 'Visual C++ runtime'], run: (env) => setupApps(env, o.apps, { gitAccess: o.gitAccess, refresh: o.refresh }) },
135
+ { name: 'Python tools', on: python && config.python.tools.length > 0, after: ['Python', 'uv'], run: (env) => setupPythonTools(env, config.python.tools) },
136
+ // DynamoDB Local runs on Java: the Java step installs the JDK first instead of racing it.
137
+ // One server per database in the sandbox: a project that joins later shares the one the boot started.
138
+ ...o.databases.map((db) => ({ name: DATABASE_NAMES[db.database], on: true, shared: true, after: db.database === 'dynamodb' && java ? ['Java'] : [], run: (env) => setupDatabase(env, db) })),
139
+ ];
140
+ };
@@ -8,7 +8,7 @@ import { setupSingleExe } from './runtimes.js';
8
8
  export const pwshDir = () => tool('pwsh');
9
9
  export const setupPwsh = async (env, refresh = false) => {
10
10
  // pwsh.exe sits at the zip's root next to its runtime and modules: the whole folder is kept.
11
- await setupSingleExe(env, 'pwsh', 'PowerShell/PowerShell', pwshAsset(arch), 'pwsh.exe', pwshDir(), { refresh, versionArgs: ['-Version'], note: ' (PowerShell 7; powershell is still 5.1)' });
11
+ await setupSingleExe(env, 'pwsh', 'PowerShell/PowerShell', pwshAsset(arch), 'pwsh.exe', pwshDir(), { refresh, size: '100 MB', versionArgs: ['-Version'], note: ' (PowerShell 7; powershell is still 5.1)' });
12
12
  // No update banner or telemetry from a copy that provisioning.refresh replaces anyway.
13
13
  Object.assign(env.vars, { POWERSHELL_UPDATECHECK: 'Off', POWERSHELL_TELEMETRY_OPTOUT: '1' });
14
14
  };