windows-development-sandbox 1.0.5 → 1.1.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 (368) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +22 -6
  3. package/config.example.json +17 -0
  4. package/dist/api.js +2 -1
  5. package/dist/cli/args.d.ts +20 -0
  6. package/dist/cli/args.js +35 -3
  7. package/dist/cli/colors.d.ts +24 -0
  8. package/dist/cli/colors.js +42 -0
  9. package/dist/cli/commands/agent-commands.d.ts +6 -0
  10. package/dist/cli/commands/agent-commands.js +61 -0
  11. package/dist/cli/commands/context.d.ts +2 -0
  12. package/dist/cli/commands/context.js +3 -0
  13. package/dist/cli/commands/data/clean.d.ts +15 -0
  14. package/dist/cli/commands/data/clean.js +101 -0
  15. package/dist/cli/commands/data/collect.d.ts +11 -0
  16. package/dist/cli/commands/data/collect.js +54 -0
  17. package/dist/cli/commands/data/index.d.ts +4 -0
  18. package/dist/cli/commands/data/index.js +45 -0
  19. package/dist/cli/commands/data/remove.d.ts +2 -0
  20. package/dist/cli/commands/data/remove.js +30 -0
  21. package/dist/cli/commands/index.d.ts +5 -3
  22. package/dist/cli/commands/index.js +5 -3
  23. package/dist/cli/commands/launch.d.ts +26 -2
  24. package/dist/cli/commands/launch.js +220 -39
  25. package/dist/cli/commands/maintenance.js +33 -4
  26. package/dist/cli/commands/project.d.ts +29 -0
  27. package/dist/cli/commands/project.js +63 -0
  28. package/dist/cli/commands/projects.d.ts +14 -0
  29. package/dist/cli/commands/projects.js +146 -0
  30. package/dist/cli/commands/repair/checks.d.ts +2 -0
  31. package/dist/cli/commands/{repair.js → repair/checks.js} +19 -181
  32. package/dist/cli/commands/{repair.d.ts → repair/finders.d.ts} +0 -29
  33. package/dist/cli/commands/repair/finders.js +108 -0
  34. package/dist/cli/commands/repair/index.d.ts +12 -0
  35. package/dist/cli/commands/repair/index.js +61 -0
  36. package/dist/cli/commands/repair/types.d.ts +26 -0
  37. package/dist/cli/commands/repair/types.js +3 -0
  38. package/dist/cli/commands/restart.d.ts +6 -0
  39. package/dist/cli/commands/restart.js +85 -0
  40. package/dist/cli/commands/sessions.d.ts +2 -1
  41. package/dist/cli/commands/sessions.js +189 -45
  42. package/dist/cli/commands/setup/checks.d.ts +13 -0
  43. package/dist/cli/commands/setup/checks.js +71 -0
  44. package/dist/cli/commands/{setup.d.ts → setup/facts.d.ts} +1 -15
  45. package/dist/cli/commands/setup/facts.js +60 -0
  46. package/dist/cli/commands/setup/index.d.ts +2 -0
  47. package/dist/cli/commands/setup/index.js +43 -0
  48. package/dist/cli/commands/user-config.d.ts +8 -0
  49. package/dist/cli/commands/user-config.js +41 -0
  50. package/dist/cli/history.d.ts +11 -0
  51. package/dist/cli/history.js +21 -0
  52. package/dist/cli/index.d.ts +7 -1
  53. package/dist/cli/index.js +38 -13
  54. package/dist/cli/manifest.d.ts +8 -0
  55. package/dist/cli/manifest.js +35 -15
  56. package/dist/cli/menu-multi.d.ts +12 -0
  57. package/dist/cli/menu-multi.js +82 -0
  58. package/dist/cli/menu.d.ts +5 -13
  59. package/dist/cli/menu.js +149 -46
  60. package/dist/cli/progress.d.ts +9 -0
  61. package/dist/cli/progress.js +49 -0
  62. package/dist/cli/settings/edit.d.ts +13 -0
  63. package/dist/cli/settings/edit.js +19 -0
  64. package/dist/cli/settings/index.d.ts +8 -0
  65. package/dist/cli/settings/index.js +131 -0
  66. package/dist/cli/settings/prompts.d.ts +7 -0
  67. package/dist/cli/settings/prompts.js +48 -0
  68. package/dist/cli/settings/schema.d.ts +33 -0
  69. package/dist/cli/settings/schema.js +29 -0
  70. package/dist/cli/validate.d.ts +17 -0
  71. package/dist/cli/validate.js +89 -0
  72. package/dist/core/config.js +4 -1
  73. package/dist/core/disk.d.ts +16 -0
  74. package/dist/core/disk.js +55 -0
  75. package/dist/core/local-config.d.ts +8 -0
  76. package/dist/core/local-config.js +18 -5
  77. package/dist/core/project-policy.d.ts +15 -0
  78. package/dist/core/project-policy.js +35 -0
  79. package/dist/core/prompt.d.ts +4 -0
  80. package/dist/core/prompt.js +15 -0
  81. package/dist/core/safe-paths.d.ts +4 -0
  82. package/dist/core/safe-paths.js +37 -0
  83. package/dist/core/schema/common.d.ts +17 -0
  84. package/dist/core/schema/common.js +15 -0
  85. package/dist/core/schema/config.d.ts +594 -0
  86. package/dist/core/schema/config.js +63 -0
  87. package/dist/core/schema/databases.d.ts +33 -0
  88. package/dist/core/schema/databases.js +35 -0
  89. package/dist/core/schema/harness.d.ts +12 -0
  90. package/dist/core/schema/harness.js +17 -0
  91. package/dist/core/schema/json-schema.d.ts +5 -0
  92. package/dist/core/schema/json-schema.js +25 -0
  93. package/dist/core/schema/profile.d.ts +27 -0
  94. package/dist/core/schema/profile.js +29 -0
  95. package/dist/core/schema/projects.d.ts +9 -0
  96. package/dist/core/schema/projects.js +11 -0
  97. package/dist/core/schema/sandbox.d.ts +50 -0
  98. package/dist/core/schema/sandbox.js +36 -0
  99. package/dist/core/schema/toolchains.d.ts +93 -0
  100. package/dist/core/schema/toolchains.js +83 -0
  101. package/dist/core/schema.d.ts +6 -604
  102. package/dist/core/schema.js +8 -264
  103. package/dist/core/types.d.ts +29 -1
  104. package/dist/core/util.d.ts +7 -0
  105. package/dist/core/util.js +12 -3
  106. package/dist/core/workspace.d.ts +0 -5
  107. package/dist/core/workspace.js +12 -46
  108. package/dist/guest/boot-env.d.ts +4 -0
  109. package/dist/guest/boot-env.js +25 -0
  110. package/dist/guest/cmdline.d.ts +12 -0
  111. package/dist/guest/cmdline.js +23 -0
  112. package/dist/guest/daemon/exec.d.ts +5 -0
  113. package/dist/guest/daemon/exec.js +32 -0
  114. package/dist/guest/daemon/project.d.ts +14 -0
  115. package/dist/guest/daemon/project.js +132 -0
  116. package/dist/guest/daemon/session.d.ts +13 -0
  117. package/dist/guest/daemon/session.js +139 -0
  118. package/dist/guest/daemon/shutdown.d.ts +1 -0
  119. package/dist/guest/daemon/shutdown.js +21 -0
  120. package/dist/guest/daemon/state.d.ts +17 -0
  121. package/dist/guest/daemon/state.js +31 -0
  122. package/dist/guest/daemon.js +8 -141
  123. package/dist/guest/db.js +18 -3
  124. package/dist/guest/env.d.ts +2 -0
  125. package/dist/guest/env.js +4 -2
  126. package/dist/guest/guide.d.ts +6 -2
  127. package/dist/guest/guide.js +5 -4
  128. package/dist/guest/helpers.d.ts +3 -0
  129. package/dist/guest/helpers.js +26 -0
  130. package/dist/guest/main.js +17 -7
  131. package/dist/guest/ports.d.ts +6 -0
  132. package/dist/guest/ports.js +22 -0
  133. package/dist/guest/project-context.d.ts +7 -0
  134. package/dist/guest/project-context.js +10 -0
  135. package/dist/guest/project-env.d.ts +20 -0
  136. package/dist/guest/project-env.js +20 -0
  137. package/dist/guest/project-status.d.ts +5 -0
  138. package/dist/guest/project-status.js +17 -0
  139. package/dist/guest/ptyhost.d.ts +0 -18
  140. package/dist/guest/ptyhost.js +5 -63
  141. package/dist/guest/setup/agents.d.ts +5 -2
  142. package/dist/guest/setup/agents.js +22 -8
  143. package/dist/guest/setup/claude.js +11 -3
  144. package/dist/guest/setup/environment.d.ts +13 -3
  145. package/dist/guest/setup/environment.js +14 -10
  146. package/dist/guest/setup/hooks.d.ts +9 -1
  147. package/dist/guest/setup/hooks.js +28 -14
  148. package/dist/guest/toolchains/catalogue.d.ts +4 -0
  149. package/dist/guest/toolchains/catalogue.js +2 -2
  150. package/dist/guest/toolchains/cli.js +25 -17
  151. package/dist/guest/toolchains/common.d.ts +18 -1
  152. package/dist/guest/toolchains/common.js +34 -11
  153. package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
  154. package/dist/guest/toolchains/databases/dynamodb.js +52 -0
  155. package/dist/guest/toolchains/{databases.d.ts → databases/index.d.ts} +2 -3
  156. package/dist/guest/toolchains/databases/index.js +62 -0
  157. package/dist/guest/toolchains/databases/mongodb.d.ts +3 -0
  158. package/dist/guest/toolchains/databases/mongodb.js +31 -0
  159. package/dist/guest/toolchains/databases/mysql.d.ts +3 -0
  160. package/dist/guest/toolchains/databases/mysql.js +38 -0
  161. package/dist/guest/toolchains/databases/postgres.d.ts +5 -0
  162. package/dist/guest/toolchains/databases/postgres.js +38 -0
  163. package/dist/guest/toolchains/databases/redis.d.ts +3 -0
  164. package/dist/guest/toolchains/databases/redis.js +16 -0
  165. package/dist/guest/toolchains/databases/server.d.ts +14 -0
  166. package/dist/guest/toolchains/databases/server.js +52 -0
  167. package/dist/guest/toolchains/git.js +25 -16
  168. package/dist/guest/toolchains/go.js +1 -1
  169. package/dist/guest/toolchains/index.d.ts +13 -9
  170. package/dist/guest/toolchains/index.js +40 -82
  171. package/dist/guest/toolchains/java.js +5 -5
  172. package/dist/guest/toolchains/leftovers.d.ts +2 -0
  173. package/dist/guest/toolchains/leftovers.js +26 -0
  174. package/dist/guest/toolchains/managers.js +12 -10
  175. package/dist/guest/toolchains/{binstall.js → native/binstall.js} +6 -9
  176. package/dist/guest/toolchains/native/cpp.d.ts +4 -0
  177. package/dist/guest/toolchains/native/cpp.js +38 -0
  178. package/dist/guest/toolchains/native/dotnet.d.ts +4 -0
  179. package/dist/guest/toolchains/native/dotnet.js +103 -0
  180. package/dist/guest/toolchains/native/mesa.d.ts +7 -0
  181. package/dist/guest/toolchains/native/mesa.js +46 -0
  182. package/dist/guest/toolchains/native/msvc/environment.d.ts +16 -0
  183. package/dist/guest/toolchains/native/msvc/environment.js +57 -0
  184. package/dist/guest/toolchains/native/msvc/index.d.ts +6 -0
  185. package/dist/guest/toolchains/native/msvc/index.js +48 -0
  186. package/dist/guest/toolchains/native/msvc/packages.d.ts +6 -0
  187. package/dist/guest/toolchains/native/msvc/packages.js +156 -0
  188. package/dist/guest/toolchains/native/msvc/tree.d.ts +15 -0
  189. package/dist/guest/toolchains/native/msvc/tree.js +21 -0
  190. package/dist/guest/toolchains/native/rust.d.ts +3 -0
  191. package/dist/guest/toolchains/native/rust.js +45 -0
  192. package/dist/guest/toolchains/node.js +26 -6
  193. package/dist/guest/toolchains/plan.d.ts +22 -0
  194. package/dist/guest/toolchains/plan.js +96 -0
  195. package/dist/guest/toolchains/pwsh.js +1 -1
  196. package/dist/guest/toolchains/python.js +17 -10
  197. package/dist/guest/toolchains/runtimes.d.ts +2 -0
  198. package/dist/guest/toolchains/runtimes.js +6 -8
  199. package/dist/guest/toolchains/scripting/php.d.ts +3 -0
  200. package/dist/guest/toolchains/scripting/php.js +71 -0
  201. package/dist/guest/toolchains/scripting/ruby.d.ts +3 -0
  202. package/dist/guest/toolchains/scripting/ruby.js +47 -0
  203. package/dist/guest/toolchains/scripting/vcruntime.d.ts +7 -0
  204. package/dist/guest/toolchains/scripting/vcruntime.js +45 -0
  205. package/dist/guest/view.js +21 -1
  206. package/dist/host/accounts.js +14 -5
  207. package/dist/host/agent-commands.d.ts +25 -0
  208. package/dist/host/agent-commands.js +145 -0
  209. package/dist/host/attach.d.ts +17 -0
  210. package/dist/host/attach.js +69 -0
  211. package/dist/host/channel.d.ts +23 -22
  212. package/dist/host/channel.js +66 -84
  213. package/dist/host/claude-profile.js +7 -3
  214. package/dist/host/cleanup.js +2 -11
  215. package/dist/host/database-plan.d.ts +12 -0
  216. package/dist/host/database-plan.js +52 -0
  217. package/dist/host/generate.d.ts +4 -12
  218. package/dist/host/generate.js +48 -135
  219. package/dist/host/join.d.ts +28 -0
  220. package/dist/host/join.js +76 -0
  221. package/dist/host/launch/mappings.d.ts +3 -0
  222. package/dist/host/launch/mappings.js +50 -0
  223. package/dist/host/launch/projects.d.ts +15 -0
  224. package/dist/host/launch/projects.js +56 -0
  225. package/dist/host/launch/stage.d.ts +11 -0
  226. package/dist/host/launch/stage.js +66 -0
  227. package/dist/host/launch/wsb.d.ts +10 -0
  228. package/dist/host/launch/wsb.js +34 -0
  229. package/dist/host/pc-scan.d.ts +6 -0
  230. package/dist/host/pc-scan.js +37 -0
  231. package/dist/host/platform.d.ts +2 -0
  232. package/dist/host/platform.js +13 -1
  233. package/dist/host/project-scan.d.ts +1 -17
  234. package/dist/host/project-scan.js +2 -86
  235. package/dist/host/projects.d.ts +100 -0
  236. package/dist/host/projects.js +147 -0
  237. package/dist/host/repo-guard.d.ts +15 -2
  238. package/dist/host/repo-guard.js +103 -12
  239. package/dist/host/terminal.js +3 -0
  240. package/dist/host/test-guard.d.ts +6 -0
  241. package/dist/host/test-guard.js +15 -0
  242. package/dist/host/window.js +7 -3
  243. package/dist/host/wsb.d.ts +37 -0
  244. package/dist/host/wsb.js +101 -0
  245. package/dist/runtime/daemon.js +10997 -2794
  246. package/dist/runtime/db.js +6428 -464
  247. package/dist/runtime/main.js +7359 -1135
  248. package/dist/runtime/view.js +5922 -47
  249. package/dist/selftest/accounts.test.d.ts +2 -0
  250. package/dist/selftest/accounts.test.js +44 -0
  251. package/dist/selftest/agent-commands.test.d.ts +2 -0
  252. package/dist/selftest/agent-commands.test.js +47 -0
  253. package/dist/selftest/channel.test.d.ts +2 -0
  254. package/dist/selftest/channel.test.js +37 -0
  255. package/dist/selftest/cli.test.d.ts +2 -2
  256. package/dist/selftest/cli.test.js +115 -27
  257. package/dist/selftest/cmdline.test.d.ts +2 -0
  258. package/dist/selftest/cmdline.test.js +45 -0
  259. package/dist/selftest/config.test.d.ts +2 -3
  260. package/dist/selftest/config.test.js +36 -72
  261. package/dist/selftest/data.test.d.ts +2 -2
  262. package/dist/selftest/data.test.js +77 -70
  263. package/dist/selftest/detect.test.d.ts +2 -2
  264. package/dist/selftest/detect.test.js +54 -38
  265. package/dist/selftest/fixtures.d.ts +44 -4
  266. package/dist/selftest/fixtures.js +40 -23
  267. package/dist/selftest/generate.test.d.ts +2 -0
  268. package/dist/selftest/generate.test.js +63 -0
  269. package/dist/selftest/git-access.test.d.ts +2 -0
  270. package/dist/selftest/git-access.test.js +26 -0
  271. package/dist/selftest/guest-boot.test.d.ts +2 -0
  272. package/dist/selftest/guest-boot.test.js +76 -0
  273. package/dist/selftest/harness.d.ts +44 -7
  274. package/dist/selftest/harness.js +62 -21
  275. package/dist/selftest/host-sim.test.d.ts +2 -0
  276. package/dist/selftest/host-sim.test.js +93 -0
  277. package/dist/selftest/hostile-input.test.d.ts +2 -0
  278. package/dist/selftest/hostile-input.test.js +54 -0
  279. package/dist/selftest/index.d.ts +8 -1
  280. package/dist/selftest/index.js +134 -35
  281. package/dist/selftest/links.test.d.ts +2 -0
  282. package/dist/selftest/links.test.js +50 -0
  283. package/dist/selftest/live-daemon.test.d.ts +2 -0
  284. package/dist/selftest/live-daemon.test.js +91 -0
  285. package/dist/selftest/live-git.test.d.ts +2 -0
  286. package/dist/selftest/live-git.test.js +60 -0
  287. package/dist/selftest/live-projects.test.d.ts +2 -0
  288. package/dist/selftest/live-projects.test.js +127 -0
  289. package/dist/selftest/live.d.ts +65 -0
  290. package/dist/selftest/live.js +109 -0
  291. package/dist/selftest/local-config.test.d.ts +2 -0
  292. package/dist/selftest/local-config.test.js +107 -0
  293. package/dist/selftest/mirror.test.d.ts +2 -3
  294. package/dist/selftest/mirror.test.js +92 -65
  295. package/dist/selftest/network.test.d.ts +2 -0
  296. package/dist/selftest/network.test.js +28 -0
  297. package/dist/selftest/personalization.test.d.ts +2 -0
  298. package/dist/selftest/personalization.test.js +53 -0
  299. package/dist/selftest/projects.test.d.ts +2 -0
  300. package/dist/selftest/projects.test.js +254 -0
  301. package/dist/selftest/repair.test.d.ts +2 -0
  302. package/dist/selftest/repair.test.js +42 -0
  303. package/dist/selftest/repo-guard.test.d.ts +2 -0
  304. package/dist/selftest/repo-guard.test.js +63 -0
  305. package/dist/selftest/secrets.test.d.ts +2 -0
  306. package/dist/selftest/secrets.test.js +71 -0
  307. package/dist/selftest/setup.test.d.ts +2 -2
  308. package/dist/selftest/setup.test.js +45 -22
  309. package/dist/selftest/suite.d.ts +13 -0
  310. package/dist/selftest/suite.js +1 -0
  311. package/dist/selftest/sync.test.d.ts +2 -3
  312. package/dist/selftest/sync.test.js +90 -78
  313. package/dist/selftest/test-guard.test.d.ts +2 -0
  314. package/dist/selftest/test-guard.test.js +49 -0
  315. package/dist/selftest/toolchains.test.d.ts +2 -0
  316. package/dist/selftest/toolchains.test.js +103 -0
  317. package/dist/selftest/workspace.test.d.ts +2 -0
  318. package/dist/selftest/workspace.test.js +53 -0
  319. package/dist/shared/download.d.ts +3 -1
  320. package/dist/shared/download.js +19 -4
  321. package/dist/shared/paths.d.ts +5 -0
  322. package/dist/shared/paths.js +6 -0
  323. package/dist/shared/projects.d.ts +53 -0
  324. package/dist/shared/projects.js +67 -0
  325. package/dist/shared/rundir.d.ts +10 -1
  326. package/dist/shared/rundir.js +12 -4
  327. package/dist/shared/safe-remove.js +11 -0
  328. package/dist/shared/schema.d.ts +443 -0
  329. package/dist/shared/schema.js +41 -2
  330. package/dist/shared/test-guard.d.ts +4 -0
  331. package/dist/shared/test-guard.js +37 -0
  332. package/dist/sync/claude-mem.js +3 -1
  333. package/dist/sync/history.d.ts +23 -12
  334. package/dist/sync/history.js +52 -20
  335. package/dist/sync/watcher.d.ts +8 -0
  336. package/dist/sync/watcher.js +23 -4
  337. package/package.json +6 -6
  338. package/sandbox/bootstrap.ps1 +258 -258
  339. package/sandbox/cpucap.ps1 +51 -0
  340. package/sandbox/logon.cmd +18 -16
  341. package/sandbox/openssl.cs +42 -0
  342. package/sandbox/ptyhost.cs +15 -4
  343. package/sandbox/trust.cmd +35 -35
  344. package/schemas/config.schema.json +100 -1
  345. package/schemas/sandbox-config.schema.json +17 -1
  346. package/dist/cli/commands/data.d.ts +0 -46
  347. package/dist/cli/commands/data.js +0 -249
  348. package/dist/cli/commands/setup.js +0 -152
  349. package/dist/cli/settings.d.ts +0 -11
  350. package/dist/cli/settings.js +0 -156
  351. package/dist/guest/toolchains/databases.js +0 -226
  352. package/dist/guest/toolchains/msvc.d.ts +0 -10
  353. package/dist/guest/toolchains/msvc.js +0 -216
  354. package/dist/guest/toolchains/native.d.ts +0 -12
  355. package/dist/guest/toolchains/native.js +0 -220
  356. package/dist/guest/toolchains/scripting.d.ts +0 -10
  357. package/dist/guest/toolchains/scripting.js +0 -137
  358. package/dist/selftest/guest.test.d.ts +0 -3
  359. package/dist/selftest/guest.test.js +0 -61
  360. package/dist/selftest/helpers.test.d.ts +0 -2
  361. package/dist/selftest/helpers.test.js +0 -125
  362. package/dist/selftest/host.test.d.ts +0 -2
  363. package/dist/selftest/host.test.js +0 -71
  364. package/dist/selftest/live.test.d.ts +0 -7
  365. package/dist/selftest/live.test.js +0 -127
  366. package/dist/selftest/security.test.d.ts +0 -3
  367. package/dist/selftest/security.test.js +0 -143
  368. /package/dist/guest/toolchains/{binstall.d.ts → native/binstall.d.ts} +0 -0
@@ -0,0 +1,34 @@
1
+ // The .wsb file Windows Sandbox starts from: memory, devices and the folders mapped in.
2
+ import os from 'node:os';
3
+ import { fail, xmlEscape } from '../../core/util.js';
4
+ import { MIRROR_ROOT, sandboxFolder } from '../../shared/guest-paths.js';
5
+ export const GUEST = { launch: 'C:\\AgentLaunch', data: 'C:\\AgentData', hostTools: 'C:\\HostTools', mirror: MIRROR_ROOT };
6
+ const toggle = (value) => (value ? 'Enable' : 'Disable');
7
+ /** "auto": free RAM minus the reserve, but never more than maxMemoryMb or maxMemoryPercent of the PC's total RAM. */
8
+ export const resolveMemoryMb = (sandbox, freeMemoryBytes = os.freemem(), totalMemoryBytes = os.totalmem()) => {
9
+ if (sandbox.memoryMb !== 'auto')
10
+ return sandbox.memoryMb;
11
+ const freeMb = Math.floor(freeMemoryBytes / 1048576);
12
+ const shareMb = Math.floor((totalMemoryBytes / 1048576) * sandbox.maxMemoryPercent / 100);
13
+ const rounded = Math.floor(Math.min(sandbox.maxMemoryMb, shareMb, freeMb - sandbox.reserveHostMb) / 1024) * 1024;
14
+ if (rounded < 4096)
15
+ fail('Not enough free RAM for the sandbox (' + freeMb + ' MB free, ' + sandbox.reserveHostMb + ' MB kept for this PC).', 'Close some apps, lower sandbox.reserveHostMb, or set sandbox.memoryMb to a fixed value (>= 4096).');
16
+ return rounded;
17
+ };
18
+ export const buildWsb = (config, memoryMb, mappings) => {
19
+ const s = config.sandbox;
20
+ // Guest paths on other drive letters (F:\x) really live under C:\Mirror\F; bootstrap.ps1 substs F: onto it at boot.
21
+ const folders = mappings.map((m) => '<MappedFolder><HostFolder>' + xmlEscape(m.host) + '</HostFolder><SandboxFolder>' + xmlEscape(sandboxFolder(m.guest)) + '</SandboxFolder><ReadOnly>' + String(m.readOnly) + '</ReadOnly></MappedFolder>').join('');
22
+ return '<Configuration>'
23
+ + '<MemoryInMB>' + memoryMb + '</MemoryInMB>'
24
+ + '<vGPU>' + toggle(s.vGpu) + '</vGPU>'
25
+ + '<Networking>' + toggle(s.networking) + '</Networking>'
26
+ + '<ClipboardRedirection>' + toggle(s.clipboard) + '</ClipboardRedirection>'
27
+ + '<AudioInput>' + toggle(s.audioInput) + '</AudioInput>'
28
+ + '<VideoInput>' + toggle(s.videoInput) + '</VideoInput>'
29
+ + '<PrinterRedirection>' + toggle(s.printerRedirection) + '</PrinterRedirection>'
30
+ + '<ProtectedClient>' + toggle(s.protectedClient) + '</ProtectedClient>'
31
+ + '<MappedFolders>' + folders + '</MappedFolders>'
32
+ + '<LogonCommand><Command>' + xmlEscape(GUEST.launch + '\\sandbox\\logon.cmd') + '</Command></LogonCommand>'
33
+ + '</Configuration>';
34
+ };
@@ -0,0 +1,6 @@
1
+ import { type Database } from '../core/detect-databases.js';
2
+ import { type AutoToolchain } from '../core/detect-toolchains.js';
3
+ /** Languages this PC has (their commands on PATH; Windows' Store stubs for python do not count). */
4
+ export declare const pcLanguages: () => AutoToolchain[];
5
+ /** Database services installed on this PC (names from the service control manager; nothing is started or changed). */
6
+ export declare const hostDatabases: () => Database[];
@@ -0,0 +1,37 @@
1
+ // What this PC has installed (languages on PATH, MSVC for Rust, database services), so `auto` settings can follow it.
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { pcHasPwsh } from '../core/detect-pwsh.js';
6
+ import { databasesFromServices } from '../core/detect-databases.js';
7
+ import { AUTO_TOOLCHAINS } from '../core/detect-toolchains.js';
8
+ import { captureOutput, isWin } from '../core/util.js';
9
+ import { system32 } from '../shared/system.js';
10
+ /** This PC builds Rust with MSVC: rustup's default host is *-msvc (read from rustup's settings; nothing is run). */
11
+ const msvcRust = () => {
12
+ const settings = path.join(process.env.RUSTUP_HOME ?? path.join(os.homedir(), '.rustup'), 'settings.toml');
13
+ try {
14
+ return /default_host_triple\s*=\s*"[^"]*-msvc"/.test(fs.readFileSync(settings, 'utf8').slice(0, 64 * 1024));
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ };
20
+ /** Languages this PC has (their commands on PATH; Windows' Store stubs for python do not count). */
21
+ export const pcLanguages = () => {
22
+ const dirs = (process.env.PATH ?? '').split(path.delimiter).filter((d) => d && !/WindowsApps/i.test(d));
23
+ const has = (...names) => dirs.some((d) => names.some((n) => fs.existsSync(path.join(d, n))));
24
+ const found = {
25
+ python: has('python.exe', 'py.exe'), rust: has('cargo.exe', 'rustc.exe'), cpp: has('gcc.exe', 'g++.exe', 'cl.exe', 'clang.exe'),
26
+ msvc: msvcRust(), pwsh: pcHasPwsh(), dotnet: has('dotnet.exe'), go: has('go.exe'), java: has('java.exe', 'javac.exe'), ruby: has('ruby.exe'), php: has('php.exe'), deno: has('deno.exe'),
27
+ };
28
+ return AUTO_TOOLCHAINS.filter((t) => found[t]);
29
+ };
30
+ /** Database services installed on this PC (names from the service control manager; nothing is started or changed). */
31
+ export const hostDatabases = () => {
32
+ if (!isWin)
33
+ return [];
34
+ const output = captureOutput(system32('sc.exe'), ['query', 'type=', 'service', 'state=', 'all'], { timeout: 10000 });
35
+ const names = [...output.matchAll(/^SERVICE_NAME:\s*(.+)$/gm)].map((m) => m[1].trim());
36
+ return databasesFromServices(names);
37
+ };
@@ -10,6 +10,8 @@ export declare const GUEST_DATA_DIR = "C:\\AgentData";
10
10
  export declare const isSandboxRunning: () => boolean;
11
11
  export declare const sandboxFeatureInstalled: () => boolean;
12
12
  export declare const assertCanLaunch: () => void;
13
+ /** cmd.exe expands %VAR% and ^ even inside quotes, and a quote ends them: such a path could run something else. */
14
+ export declare const assertCmdSafePath: (file: string) => void;
13
15
  export declare const startSandbox: (wsbPath: string) => void;
14
16
  /** Runs a detached copy of this CLI (used for the background history/claude-mem sync watcher). */
15
17
  export declare const spawnDetached: (entry: string, args: string[]) => void;
@@ -3,6 +3,8 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { spawn, spawnSync } from 'node:child_process';
5
5
  import { SYSTEM } from '../shared/system.js';
6
+ import { refuseInSelfTest } from './test-guard.js';
7
+ import { commandsDir, realAgent } from './agent-commands.js';
6
8
  import { dirExists, ensureDir, fail, fileExists, findOnPath, homeDir, isWin, realPath } from '../core/util.js';
7
9
  const findHarnessExe = (config, harnessName) => {
8
10
  const harness = config.harnesses[harnessName];
@@ -70,18 +72,28 @@ export const assertCanLaunch = () => {
70
72
  if (isSandboxRunning())
71
73
  fail('A Windows Sandbox is already running (Windows allows one at a time).', 'Attach to it with sandbox claude -t, show it with sbx-show, or shut it down with sandbox stop.');
72
74
  };
75
+ /** cmd.exe expands %VAR% and ^ even inside quotes, and a quote ends them: such a path could run something else. */
76
+ export const assertCmdSafePath = (file) => {
77
+ if (/[%"^\r\n]/.test(file))
78
+ fail('The storage folder path has a character that cannot be started safely: ' + file, 'Set storage.root to a folder without % " ^ in its path.');
79
+ };
73
80
  export const startSandbox = (wsbPath) => {
81
+ refuseInSelfTest('start Windows Sandbox');
82
+ assertCmdSafePath(wsbPath);
74
83
  const result = spawnSync(SYSTEM.cmd(), ['/d', '/c', 'start', '""', '"' + wsbPath + '"'], { stdio: 'inherit', windowsVerbatimArguments: true });
75
84
  if (result.error)
76
85
  throw result.error;
77
86
  };
78
87
  /** Runs a detached copy of this CLI (used for the background history/claude-mem sync watcher). */
79
88
  export const spawnDetached = (entry, args) => {
89
+ refuseInSelfTest('start background programs');
80
90
  // cwd = the install folder: the caller's folder is the workspace, which the sandbox can write.
81
91
  spawn(process.execPath, [entry, ...args], { cwd: path.dirname(entry), detached: true, stdio: 'ignore', windowsHide: true }).unref();
82
92
  };
83
93
  export const hostClaude = (storageRoot, personal, args) => {
84
- const exe = findOnPath(['claude.exe', 'claude.cmd']);
94
+ refuseInSelfTest('run Claude Code on this PC');
95
+ // Never this tool's own forwarding command (agentCommands), which would start a sandbox instead.
96
+ const exe = realAgent('claude', commandsDir(storageRoot));
85
97
  if (!exe)
86
98
  fail('Claude Code is not installed on this PC (no claude.exe or claude.cmd on PATH).', 'Install it: npm install -g @anthropic-ai/claude-code');
87
99
  const env = { ...process.env };
@@ -1,23 +1,7 @@
1
- import type { Config, DatabasePlan } from '../core/types.js';
2
- import { type Database, type ProjectFile } from '../core/detect-databases.js';
3
- import { type AutoToolchain } from '../core/detect-toolchains.js';
1
+ import { type ProjectFile } from '../core/detect-databases.js';
4
2
  /** Manifest-type files in the project, up to 3 folders deep. Links are not followed (the workspace may be guest-written). */
5
3
  export declare const readProjectFiles: (root: string, depth?: number) => ProjectFile[];
6
4
  /** File names in the project and its direct sub-folders ("api/go.mod"), for toolchain detection. No links followed. */
7
5
  export declare const projectEntries: (root: string) => string[];
8
6
  /** Scripts in the project root or one folder down that set up MSVC (vcvars*.bat, vswhere): read with bounded effort. */
9
7
  export declare const msvcHint: (root: string) => string | undefined;
10
- /** Languages this PC has (their commands on PATH; Windows' Store stubs for python do not count). */
11
- export declare const pcLanguages: () => AutoToolchain[];
12
- /** Database services installed on this PC (names from the service control manager; nothing is started or changed). */
13
- export declare const hostDatabases: () => Database[];
14
- /**
15
- * What to run in this sandbox: databases set to true, plus "auto" ones the project needs. SQL Server is reported (so
16
- * the user hears about it) but cannot run inside Windows Sandbox.
17
- */
18
- export declare const planDatabases: (config: Config["databases"], workspace: string, probe?: {
19
- files?: ProjectFile[];
20
- host?: Database[];
21
- }) => DatabasePlan[];
22
- /** Every database the sandbox can run, so `sbx-db start <name>` works for ones that did not start at boot. */
23
- export declare const databaseCatalog: (config: Config["databases"]) => DatabasePlan[];
@@ -1,14 +1,7 @@
1
- // Reads a project the way detect-databases.ts needs it (only manifest/config/compose files, bounded), and looks at which
2
- // database services this PC has, so `enabled: "auto"` databases are installed only for projects that use them.
1
+ // Reads a project the way detect-databases.ts and detect-toolchains.ts need it (only manifest/config files, bounded).
3
2
  import fs from 'node:fs';
4
3
  import path from 'node:path';
5
- import { spawnSync } from 'node:child_process';
6
- import { DATABASES, databasesFromServices, detectDatabases, INTERESTING_FILE } from '../core/detect-databases.js';
7
- import os from 'node:os';
8
- import { AUTO_TOOLCHAINS } from '../core/detect-toolchains.js';
9
- import { pcHasPwsh } from '../core/detect-pwsh.js';
10
- import { isWin } from '../core/util.js';
11
- import { system32 } from '../shared/system.js';
4
+ import { INTERESTING_FILE } from '../core/detect-databases.js';
12
5
  const SKIP = /^(node_modules|\.git|\.hg|\.svn|vendor|target|bin|obj|dist|build|out|\.next|\.nuxt|\.venv|venv|env|__pycache__|\.gradle|\.idea|\.vs|coverage|packages)$/i;
13
6
  const MAX_FILES = 400;
14
7
  const MAX_BYTES = 256 * 1024;
@@ -76,16 +69,6 @@ export const projectEntries = (root) => {
76
69
  .flatMap((d) => list(path.join(root, d.name)).filter((f) => f.isFile()).map((f) => d.name + '/' + f.name));
77
70
  return [...top.filter((d) => d.isFile()).map((d) => d.name), ...below];
78
71
  };
79
- /** This PC builds Rust with MSVC: rustup's default host is *-msvc (read from rustup's settings; nothing is run). */
80
- const msvcRust = () => {
81
- const settings = path.join(process.env.RUSTUP_HOME ?? path.join(os.homedir(), '.rustup'), 'settings.toml');
82
- try {
83
- return /default_host_triple\s*=\s*"[^"]*-msvc"/.test(fs.readFileSync(settings, 'utf8').slice(0, 64 * 1024));
84
- }
85
- catch {
86
- return false;
87
- }
88
- };
89
72
  /** Scripts in the project root or one folder down that set up MSVC (vcvars*.bat, vswhere): read with bounded effort. */
90
73
  export const msvcHint = (root) => {
91
74
  const scripts = projectEntries(root).filter((e) => /\.(ps1|cmd|bat|psm1)$/i.test(e)).slice(0, 60);
@@ -104,70 +87,3 @@ export const msvcHint = (root) => {
104
87
  }
105
88
  return undefined;
106
89
  };
107
- /** Languages this PC has (their commands on PATH; Windows' Store stubs for python do not count). */
108
- export const pcLanguages = () => {
109
- const dirs = (process.env.PATH ?? '').split(path.delimiter).filter((d) => d && !/WindowsApps/i.test(d));
110
- const has = (...names) => dirs.some((d) => names.some((n) => fs.existsSync(path.join(d, n))));
111
- const found = {
112
- python: has('python.exe', 'py.exe'), rust: has('cargo.exe', 'rustc.exe'), cpp: has('gcc.exe', 'g++.exe', 'cl.exe', 'clang.exe'),
113
- msvc: msvcRust(), pwsh: pcHasPwsh(), dotnet: has('dotnet.exe'), go: has('go.exe'), java: has('java.exe', 'javac.exe'), ruby: has('ruby.exe'), php: has('php.exe'), deno: has('deno.exe'),
114
- };
115
- return AUTO_TOOLCHAINS.filter((t) => found[t]);
116
- };
117
- /** Database services installed on this PC (names from the service control manager; nothing is started or changed). */
118
- export const hostDatabases = () => {
119
- if (!isWin)
120
- return [];
121
- const result = spawnSync(system32('sc.exe'), ['query', 'type=', 'service', 'state=', 'all'], { encoding: 'utf8', windowsHide: true, timeout: 10000 });
122
- const names = [...(result.stdout ?? '').matchAll(/^SERVICE_NAME:\s*(.+)$/gm)].map((m) => m[1].trim());
123
- return databasesFromServices(names);
124
- };
125
- /** The version a compose file pins (postgres:16-alpine -> 16, mongo:7.0 -> 7.0), if any. */
126
- const pinnedVersion = (files, image) => {
127
- for (const file of files) {
128
- const match = image.exec(file.text);
129
- if (match?.[1] && /^\d+(\.\d+)?/.test(match[1]))
130
- return /^\d+(\.\d+)?/.exec(match[1])[0];
131
- }
132
- return null;
133
- };
134
- /**
135
- * What to run in this sandbox: databases set to true, plus "auto" ones the project needs. SQL Server is reported (so
136
- * the user hears about it) but cannot run inside Windows Sandbox.
137
- */
138
- export const planDatabases = (config, workspace, probe = {}) => {
139
- const autos = DATABASES.filter((db) => config[db].enabled === 'auto');
140
- const files = autos.length ? probe.files ?? readProjectFiles(workspace) : [];
141
- const detected = autos.length ? detectDatabases(files, probe.host ?? hostDatabases) : [];
142
- const plan = databasePlanner(config, files.filter((f) => /compose/i.test(f.path)));
143
- return DATABASES.flatMap((database) => {
144
- const setting = config[database].enabled;
145
- const found = detected.find((d) => d.database === database);
146
- if (setting === true)
147
- return [plan(database, ['enabled in config'])];
148
- if (setting === 'auto' && found)
149
- return [plan(database, found.reasons)];
150
- return [];
151
- });
152
- };
153
- /** Every database the sandbox can run, so `sbx-db start <name>` works for ones that did not start at boot. */
154
- export const databaseCatalog = (config) => {
155
- const plan = databasePlanner(config, []);
156
- return DATABASES.filter((db) => db !== 'sqlserver').map((db) => plan(db, ['started with sbx-db']));
157
- };
158
- /** Version (a compose file's image tag wins), port and admin UI for one database. */
159
- const databasePlanner = (config, compose) => {
160
- const versions = {
161
- postgres: pinnedVersion(compose, /image:\s*["']?(?:[\w.-]+\/)*postgres:([\w.-]+)/i) ?? (config.postgres.version === 'latest' ? 'latest' : config.postgres.version),
162
- mysql: config.mysql.version,
163
- mongodb: pinnedVersion(compose, /image:\s*["']?(?:[\w.-]+\/)*mongo:([\w.-]+)/i) ?? config.mongodb.version,
164
- redis: 'latest',
165
- dynamodb: 'latest',
166
- sqlserver: '',
167
- };
168
- const ports = { postgres: config.postgres.port, mysql: config.mysql.port, mongodb: config.mongodb.port, redis: config.redis.port, dynamodb: config.dynamodb.port, sqlserver: 1433 };
169
- return (database, reasons) => ({
170
- database, reasons, version: versions[database], port: ports[database],
171
- ...(database === 'dynamodb' && config.dynamodb.admin ? { adminPort: config.dynamodb.adminPort } : {}),
172
- });
173
- };
@@ -0,0 +1,100 @@
1
+ import { z } from 'zod';
2
+ import { PROJECTS_GUEST, type ProjectEntry, type RootMapping } from '../shared/projects.js';
3
+ import { type Wsb } from './wsb.js';
4
+ export { PROJECTS_GUEST };
5
+ export type { ProjectEntry };
6
+ declare const Entry: z.ZodObject<{
7
+ name: z.ZodString;
8
+ host: z.ZodString;
9
+ guest: z.ZodString;
10
+ primary: z.ZodBoolean;
11
+ mechanism: z.ZodEnum<{
12
+ root: "root";
13
+ launch: "launch";
14
+ boot: "boot";
15
+ wsb: "wsb";
16
+ }>;
17
+ registeredAt: z.ZodString;
18
+ }, z.core.$strict>;
19
+ declare const Registry: z.ZodObject<{
20
+ launchId: z.ZodString;
21
+ protections: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodBoolean, z.ZodString]>>;
22
+ roots: z.ZodArray<z.ZodObject<{
23
+ host: z.ZodString;
24
+ guest: z.ZodString;
25
+ }, z.core.$strict>>;
26
+ projects: z.ZodArray<z.ZodObject<{
27
+ name: z.ZodString;
28
+ host: z.ZodString;
29
+ guest: z.ZodString;
30
+ primary: z.ZodBoolean;
31
+ mechanism: z.ZodEnum<{
32
+ root: "root";
33
+ launch: "launch";
34
+ boot: "boot";
35
+ wsb: "wsb";
36
+ }>;
37
+ registeredAt: z.ZodString;
38
+ }, z.core.$strict>>;
39
+ }, z.core.$strict>;
40
+ export type RegistryEntry = z.output<typeof Entry>;
41
+ export type ProjectRegistry = z.output<typeof Registry>;
42
+ export declare const readRegistry: (storageRoot: string) => ProjectRegistry | null;
43
+ /** At launch: the launch folder, the projects mapped with it, and the roots. */
44
+ export declare const startRegistry: (storageRoot: string, o: {
45
+ launchId: string;
46
+ protections: ProjectRegistry["protections"];
47
+ primary: RootMapping;
48
+ roots: RootMapping[];
49
+ boot: RootMapping[];
50
+ }) => RegistryEntry;
51
+ /** Why a folder cannot join the running sandbox without a restart (the caller offers one). */
52
+ export declare class NeedsRestart extends Error {
53
+ readonly hostDir: string;
54
+ readonly reason: string;
55
+ constructor(hostDir: string, reason: string);
56
+ }
57
+ export interface ProjectMapper {
58
+ /** The projects of the running sandbox (the launch folder first). */
59
+ list(): RegistryEntry[];
60
+ /** Makes a folder of this PC a project of the running sandbox; throws NeedsRestart when that needs a restart. */
61
+ register(hostDir: string): RegistryEntry;
62
+ /** Forgets a project (never the launch folder). Its folder stays visible until the next start if it is mapped. */
63
+ unregister(name: string): void;
64
+ }
65
+ export interface MapperOptions {
66
+ storageRoot: string;
67
+ packageRoot: string;
68
+ wsbShare: boolean;
69
+ detect?: () => Wsb | null;
70
+ }
71
+ /** Roots first (instant), then `wsb share` where the Windows Sandbox CLI exists, else NeedsRestart. */
72
+ export declare const projectMapper: (o: MapperOptions) => ProjectMapper;
73
+ /** Which project of the running sandbox a folder on this PC is in, and where that folder is inside. */
74
+ export interface ProjectHere {
75
+ project: RegistryEntry;
76
+ guestDir: string;
77
+ }
78
+ export declare const projectHere: (storageRoot: string, hostDir: string) => ProjectHere | null;
79
+ /** The running sandbox's root containing this folder, if any. */
80
+ export declare const rootHere: (storageRoot: string, hostDir: string) => RootMapping | null;
81
+ declare const Known: z.ZodObject<{
82
+ projects: z.ZodArray<z.ZodObject<{
83
+ host: z.ZodString;
84
+ lastUsed: z.ZodString;
85
+ }, z.core.$strict>>;
86
+ }, z.core.$strict>;
87
+ export type KnownProject = z.output<typeof Known>['projects'][number];
88
+ export declare const readKnown: (storageRoot: string) => KnownProject[];
89
+ export declare const rememberProject: (storageRoot: string, hostDir: string) => void;
90
+ export declare const forgetProject: (storageRoot: string, hostDir: string) => boolean;
91
+ /**
92
+ * projects.all: the known projects mapped at boot on their own (existing folders, not excluded, not the launch folder,
93
+ * not inside it or a root, which already show them). Pure apart from checking that the folders exist.
94
+ */
95
+ export declare const allProjectsToMap: (known: string[], o: {
96
+ primary: string;
97
+ roots: string[];
98
+ exclude: string[];
99
+ exists?: (p: string) => boolean;
100
+ }) => string[];
@@ -0,0 +1,147 @@
1
+ // The projects of the running sandbox, and how another folder joins it (shared/projects.ts explains the ways in).
2
+ // Two host-only files under the storage folder, never in the data folder the sandbox can write:
3
+ // - run\projects.json: the running sandbox's projects, roots and machine-wide protections (written at launch, then
4
+ // as folders join);
5
+ // - known-projects.json: every folder this tool has started a project in (for projects.all and `sandbox projects`).
6
+ // Everything about HOW a folder becomes visible is behind ProjectMapper, so the mechanisms can change independently.
7
+ import path from 'node:path';
8
+ import { z } from 'zod';
9
+ import { assertSafeWorkspace } from '../core/safe-paths.js';
10
+ import { dirExists, fail } from '../core/util.js';
11
+ import { Id } from '../shared/schema.js';
12
+ import { readValid, writeJsonAtomic } from '../shared/rundir.js';
13
+ import { guestDirFor, hubName, isWithin, PROJECTS_GUEST, projectForHostDir } from '../shared/projects.js';
14
+ import { detectWsb, runningSandboxId, shareFolder } from './wsb.js';
15
+ export { PROJECTS_GUEST };
16
+ const AbsolutePath = z.string().max(1024).refine((p) => path.isAbsolute(p), 'must be an absolute path');
17
+ const Entry = z.strictObject({
18
+ name: Id, host: AbsolutePath, guest: z.string().max(1024), primary: z.boolean(),
19
+ mechanism: z.enum(['launch', 'root', 'boot', 'wsb']), registeredAt: z.string().max(64),
20
+ });
21
+ const Registry = z.strictObject({
22
+ launchId: z.string().max(64),
23
+ /** The running sandbox's protections (core/project-policy.ts), fixed at boot. */
24
+ protections: z.record(z.string(), z.union([z.boolean(), z.string()])),
25
+ roots: z.array(z.strictObject({ host: AbsolutePath, guest: z.string().max(1024) })).max(64),
26
+ projects: z.array(Entry).max(1024),
27
+ });
28
+ const registryFile = (storageRoot) => path.join(storageRoot, 'run', 'projects.json');
29
+ export const readRegistry = (storageRoot) => readValid(Registry, registryFile(storageRoot));
30
+ const saveRegistry = (storageRoot, registry) => writeJsonAtomic(registryFile(storageRoot), registry);
31
+ /** At launch: the launch folder, the projects mapped with it, and the roots. */
32
+ export const startRegistry = (storageRoot, o) => {
33
+ const at = new Date().toISOString();
34
+ const primary = { name: hubName(o.primary.host, []), host: o.primary.host, guest: o.primary.guest, primary: true, mechanism: 'launch', registeredAt: at };
35
+ const names = [primary.name];
36
+ const boot = o.boot.map((b) => { const name = hubName(b.host, names); names.push(name); return { name, host: b.host, guest: b.guest, primary: false, mechanism: 'boot', registeredAt: at }; });
37
+ saveRegistry(storageRoot, { launchId: o.launchId, protections: o.protections, roots: o.roots, projects: [primary, ...boot] });
38
+ return primary;
39
+ };
40
+ /** Why a folder cannot join the running sandbox without a restart (the caller offers one). */
41
+ export class NeedsRestart extends Error {
42
+ hostDir;
43
+ reason;
44
+ constructor(hostDir, reason) {
45
+ super(reason);
46
+ this.hostDir = hostDir;
47
+ this.reason = reason;
48
+ }
49
+ }
50
+ /** Roots first (instant), then `wsb share` where the Windows Sandbox CLI exists, else NeedsRestart. */
51
+ export const projectMapper = (o) => {
52
+ const registry = () => readRegistry(o.storageRoot) ?? fail('The running sandbox was started by an older version, so it cannot take another folder.', 'Stop it (sandbox stop) and start it again.');
53
+ const add = (reg, host, guest, mechanism, name) => {
54
+ const entry = { name, host, guest, primary: false, mechanism, registeredAt: new Date().toISOString() };
55
+ // Re-read right before writing: another terminal may have registered something meanwhile.
56
+ const latest = readRegistry(o.storageRoot) ?? reg;
57
+ const same = latest.projects.find((p) => p.host.toLowerCase() === host.toLowerCase());
58
+ if (same)
59
+ return same;
60
+ saveRegistry(o.storageRoot, { ...latest, projects: [...latest.projects, entry] });
61
+ return entry;
62
+ };
63
+ return {
64
+ list: () => readRegistry(o.storageRoot)?.projects ?? [],
65
+ register: (hostDir) => {
66
+ const host = path.resolve(hostDir);
67
+ const reg = registry();
68
+ const known = reg.projects.find((p) => p.host.toLowerCase() === host.toLowerCase());
69
+ if (known)
70
+ return known;
71
+ assertSafeWorkspace(host, o.packageRoot, o.storageRoot);
72
+ if (!dirExists(host))
73
+ fail('The folder does not exist: ' + host);
74
+ const name = hubName(host, reg.projects.map((p) => p.name));
75
+ // Inside a root: visible already (also a project created after the sandbox started).
76
+ const root = projectForHostDir(reg.roots, host);
77
+ if (root)
78
+ return add(reg, host, guestDirFor(root, host), 'root', name);
79
+ if (!o.wsbShare)
80
+ throw new NeedsRestart(host, 'it is not inside a project root (projects.roots), and adding folders with wsb share is off (projects.wsbShare)');
81
+ const wsb = (o.detect ?? detectWsb)();
82
+ if (!wsb?.has('share'))
83
+ throw new NeedsRestart(host, 'it is not inside a project root (projects.roots), and this Windows has no Windows Sandbox command line (wsb share needs Windows 11 24H2+ with the updated Windows Sandbox)');
84
+ const sandbox = runningSandboxId(wsb);
85
+ if (!sandbox.id)
86
+ throw new NeedsRestart(host, 'wsb share could not find the running sandbox: ' + sandbox.error);
87
+ const guest = path.win32.join(PROJECTS_GUEST, name);
88
+ const shared = shareFolder(wsb, sandbox.id, host, guest);
89
+ if (!shared.ok)
90
+ throw new NeedsRestart(host, 'wsb share failed (untested on this Windows): ' + shared.error);
91
+ return add(reg, host, guest, 'wsb', name);
92
+ },
93
+ unregister: (name) => {
94
+ const reg = registry();
95
+ const entry = reg.projects.find((p) => p.name.toLowerCase() === name.toLowerCase());
96
+ if (!entry)
97
+ fail('No project "' + name + '" in the running sandbox.', 'sandbox projects lists them.');
98
+ if (entry.primary)
99
+ fail(entry.host + ' is the folder the sandbox started from; it stays until the sandbox stops.');
100
+ saveRegistry(o.storageRoot, { ...reg, projects: reg.projects.filter((p) => p !== entry) });
101
+ },
102
+ };
103
+ };
104
+ export const projectHere = (storageRoot, hostDir) => {
105
+ const project = projectForHostDir(readRegistry(storageRoot)?.projects ?? [], hostDir);
106
+ return project ? { project, guestDir: guestDirFor(project, hostDir) } : null;
107
+ };
108
+ /** The running sandbox's root containing this folder, if any. */
109
+ export const rootHere = (storageRoot, hostDir) => projectForHostDir(readRegistry(storageRoot)?.roots ?? [], hostDir);
110
+ // ---- known projects (every folder a project was started in) ------------------------------------------------------
111
+ const Known = z.strictObject({ projects: z.array(z.strictObject({ host: AbsolutePath, lastUsed: z.string().max(64) })).max(2000) });
112
+ const knownFile = (storageRoot) => path.join(storageRoot, 'known-projects.json');
113
+ export const readKnown = (storageRoot) => readValid(Known, knownFile(storageRoot))?.projects ?? [];
114
+ export const rememberProject = (storageRoot, hostDir) => {
115
+ const host = path.resolve(hostDir);
116
+ const rest = readKnown(storageRoot).filter((p) => p.host.toLowerCase() !== host.toLowerCase());
117
+ try {
118
+ writeJsonAtomic(knownFile(storageRoot), { projects: [{ host, lastUsed: new Date().toISOString() }, ...rest].slice(0, 2000) });
119
+ }
120
+ catch { /* a convenience list */ }
121
+ };
122
+ export const forgetProject = (storageRoot, hostDir) => {
123
+ const host = path.resolve(hostDir);
124
+ const all = readKnown(storageRoot);
125
+ const rest = all.filter((p) => p.host.toLowerCase() !== host.toLowerCase());
126
+ if (rest.length !== all.length)
127
+ writeJsonAtomic(knownFile(storageRoot), { projects: rest });
128
+ return rest.length !== all.length;
129
+ };
130
+ /**
131
+ * projects.all: the known projects mapped at boot on their own (existing folders, not excluded, not the launch folder,
132
+ * not inside it or a root, which already show them). Pure apart from checking that the folders exist.
133
+ */
134
+ export const allProjectsToMap = (known, o) => {
135
+ const exists = o.exists ?? dirExists;
136
+ const out = [];
137
+ for (const host of known.map((k) => path.resolve(k))) {
138
+ if (o.exclude.some((e) => isWithin(path.resolve(e), host)))
139
+ continue;
140
+ if (isWithin(o.primary, host) || o.roots.some((r) => isWithin(r, host)))
141
+ continue;
142
+ if (out.some((x) => isWithin(x, host) || isWithin(host, x)) || !exists(host))
143
+ continue;
144
+ out.push(host);
145
+ }
146
+ return out;
147
+ };
@@ -15,8 +15,21 @@ export declare const snapshotRepo: (workspace: string) => RepoSnapshot;
15
15
  * Returns what was found (and fixed). Never touches what was already there before the sandbox ran.
16
16
  */
17
17
  export declare const guardRepo: (workspace: string, before: RepoSnapshot) => RepoFinding[];
18
- export declare const saveRepoSnapshot: (dataPath: string, workspace: string) => void;
19
- /** Re-checks the workspace of the current/last launch; findings are kept in a host-only file for `status`. */
18
+ /**
19
+ * At launch: every folder the sandbox will be able to write. The launch folder, projects mapped with it and every
20
+ * project already inside a project root are snapshotted as they are now, before the sandbox can touch them.
21
+ */
22
+ export declare const saveRepoSnapshot: (dataPath: string, workspace: string, more?: string[], roots?: string[]) => void;
23
+ /**
24
+ * A project that joins the running sandbox: snapshotted before its first session, then guarded like the launch
25
+ * folder. A project already guarded keeps its first snapshot (what was there before the sandbox could touch it).
26
+ */
27
+ export declare const addRepoSnapshot: (dataPath: string, workspace: string, o?: {
28
+ empty?: boolean;
29
+ }) => void;
30
+ /** The folders being guarded (for status). */
31
+ export declare const guardedProjects: (dataPath: string) => string[];
32
+ /** Re-checks every project of the current/last launch; findings are kept in a host-only file for `status`. Returns the new ones. */
20
33
  export declare const checkRepo: (dataPath: string) => RepoFinding[];
21
34
  export declare const repoFindings: (dataPath: string) => (RepoFinding & {
22
35
  at?: string;