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,29 @@
1
+ /** optional()/default() wrappers hide the real type. */
2
+ export const unwrap = (s) => (['optional', 'default', 'nullable'].includes(s._zod.def.type) && s._zod.def.innerType ? unwrap(s._zod.def.innerType) : s);
3
+ export const kind = (s) => unwrap(s)._zod.def.type;
4
+ export const isSection = (s) => kind(s) === 'object' && Boolean(unwrap(s).shape);
5
+ /** Settings not worth editing here (the schema link). */
6
+ const HIDDEN = new Set(['$schema']);
7
+ /** The settings one level below a section (or the top level), in schema order. */
8
+ export const childrenOf = (schema) => Object.entries(unwrap(schema).shape ?? {}).filter(([name]) => !HIDDEN.has(name));
9
+ /** The description, which `.partial()` and `.optional()` wrappers may carry on the inner schema instead. */
10
+ export const describeSetting = (s) => s.description ?? unwrap(s).description;
11
+ /** Every setting that is edited with a prompt of its own (everything that is not a section), with its path. */
12
+ export const leavesOf = (schema, key = []) => childrenOf(schema).flatMap(([name, child]) => (isSection(child) ? leavesOf(child, [...key, name]) : [{ key: [...key, name], schema: child }]));
13
+ /** Which prompt edits this setting. Lists of objects and maps (mappings, harnesses, environment) fall back to JSON. */
14
+ export const promptKind = (schema) => {
15
+ const s = unwrap(schema);
16
+ const type = s._zod.def.type;
17
+ if (type === 'boolean' || type === 'enum' || type === 'union' || type === 'number' || type === 'string')
18
+ return type;
19
+ if (type === 'array' && s._zod.def.element && kind(s._zod.def.element) === 'string')
20
+ return 'string-list';
21
+ return 'json';
22
+ };
23
+ const JSON_EXAMPLES = {
24
+ harnesses: '{"mytool": {"npmPackage": "my-cli", "npmBin": "my-cli", "arguments": [], "environment": {}}}',
25
+ mappings: '[{"host": "C:\\\\data", "guest": "C:\\\\Data", "readOnly": true}]',
26
+ environment: '{"NPM_CONFIG_REGISTRY": "https://registry.example.com"}',
27
+ };
28
+ /** A one-line example of the JSON a setting expects, shown in its prompt. */
29
+ export const jsonExample = (key, schema) => JSON_EXAMPLES[key[key.length - 1]] ?? (unwrap(schema)._zod.def.type === 'array' ? '["value", ...]' : '{"name": "value"}');
@@ -0,0 +1,17 @@
1
+ import type { Context } from './commands/context.js';
2
+ /** Edit distance, for "did you mean". */
3
+ export declare const distance: (a: string, b: string) => number;
4
+ /** The closest of `choices` to `input`, when it is close enough to be a typo. */
5
+ export declare const closest: (input: string, choices: readonly string[]) => string | undefined;
6
+ /** "Unknown agent "cluade". Did you mean claude?" with the valid values as the fix. */
7
+ export declare const unknownValue: (what: string, input: string, choices: readonly string[], howToList: string) => never;
8
+ /** Node: 22, 22.1, v22.1.0, lts, lts/*, lts/iron, latest, current, node. */
9
+ export declare const validNodeSpec: (spec: string) => boolean;
10
+ /** Python 3: 3, 3.12, 3.12.4. */
11
+ export declare const validPythonSpec: (spec: string) => boolean;
12
+ export declare const checkHarness: (name: string | undefined, harnesses: string[]) => void;
13
+ export declare const PROJECT_ACTIONS: readonly ["list", "choose", "add", "remove", "include", "exclude", "all", "roots"];
14
+ /** `sandbox projects <action> [value] [folder]`: the action, then what it needs. Pure (no file checks). */
15
+ export declare const validateProjectsArgs: (args: string[]) => void;
16
+ /** Fails with what is wrong and what is valid, before anything is started. */
17
+ export declare const validateOptions: (ctx: Context) => void;
@@ -0,0 +1,89 @@
1
+ // Checks what the user typed before anything starts, so a typo fails at once with the list of valid values
2
+ // instead of halfway into a sandbox boot. Pure helpers first, then validateOptions (the one call from the dispatcher).
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { DATABASES } from '../core/detect-databases.js';
6
+ import { fail } from '../core/util.js';
7
+ import { listAccounts } from '../host/accounts.js';
8
+ /** Edit distance, for "did you mean". */
9
+ export const distance = (a, b) => {
10
+ let row = Array.from({ length: b.length + 1 }, (_, i) => i);
11
+ for (let i = 1; i <= a.length; i++) {
12
+ const next = [i];
13
+ for (let j = 1; j <= b.length; j++)
14
+ next[j] = Math.min(row[j] + 1, next[j - 1] + 1, row[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
15
+ row = next;
16
+ }
17
+ return row[b.length];
18
+ };
19
+ /** The closest of `choices` to `input`, when it is close enough to be a typo. */
20
+ export const closest = (input, choices) => {
21
+ const best = choices.map((c) => ({ c, d: distance(input.toLowerCase(), c.toLowerCase()) })).sort((x, y) => x.d - y.d)[0];
22
+ return best && best.d <= Math.max(2, Math.floor(input.length / 3)) ? best.c : undefined;
23
+ };
24
+ /** "Unknown agent "cluade". Did you mean claude?" with the valid values as the fix. */
25
+ export const unknownValue = (what, input, choices, howToList) => {
26
+ const hint = closest(input, choices);
27
+ return fail('Unknown ' + what + ' "' + input + '".' + (hint ? ' Did you mean ' + hint + '?' : ''), (choices.length ? 'Choose one of: ' + choices.join(', ') + '. ' : '') + howToList);
28
+ };
29
+ /** Node: 22, 22.1, v22.1.0, lts, lts/*, lts/iron, latest, current, node. */
30
+ export const validNodeSpec = (spec) => /^(v?\d+(\.\d+){0,2}|node|latest|current|stable|lts(\/(\*|[a-z]+))?)$/i.test(spec.trim());
31
+ /** Python 3: 3, 3.12, 3.12.4. */
32
+ export const validPythonSpec = (spec) => /^v?3(\.\d+){0,2}$/.test(spec.trim());
33
+ export const checkHarness = (name, harnesses) => {
34
+ if (name !== undefined && !harnesses.includes(name))
35
+ unknownValue('agent', name, harnesses, 'Add your own under "harnesses" in your settings (sandbox config).');
36
+ };
37
+ /** Commands that read these options; checked only for them (restart/doctor take --harness too). */
38
+ const LAUNCHING = new Set(['launch', 'generate', 'host-claude']);
39
+ export const PROJECT_ACTIONS = ['list', 'choose', 'add', 'remove', 'include', 'exclude', 'all', 'roots'];
40
+ /** `sandbox projects <action> [value] [folder]`: the action, then what it needs. Pure (no file checks). */
41
+ export const validateProjectsArgs = (args) => {
42
+ const [action = 'list', value, folder] = args;
43
+ if (!PROJECT_ACTIONS.includes(action))
44
+ unknownValue('projects action', action, PROJECT_ACTIONS, 'sandbox projects with no action lists your projects.');
45
+ if (['add', 'remove', 'include', 'exclude'].includes(action) && !value)
46
+ fail('Which folder?', 'Usage: sandbox projects ' + action + ' <folder> (. = this folder)');
47
+ if (action === 'all' && value !== undefined && !['on', 'off'].includes(value))
48
+ unknownValue('value for projects all', value, ['on', 'off'], 'sandbox projects all on|off');
49
+ if (action === 'roots' && value !== undefined && !['list', 'add', 'remove'].includes(value))
50
+ unknownValue('projects roots action', value, ['list', 'add', 'remove'], 'sandbox projects roots add|remove <folder>');
51
+ if (action === 'roots' && (value === 'add' || value === 'remove') && !folder)
52
+ fail('Which folder?', 'Usage: sandbox projects roots ' + value + ' <folder>');
53
+ };
54
+ /** Fails with what is wrong and what is valid, before anything is started. */
55
+ export const validateOptions = (ctx) => {
56
+ const o = ctx.options;
57
+ const harnesses = Object.keys(ctx.loaded.config.harnesses);
58
+ if (['launch', 'generate', 'restart', 'doctor'].includes(o.command))
59
+ checkHarness(o.harness, harnesses);
60
+ if (o.command === 'projects')
61
+ validateProjectsArgs(o.positionals);
62
+ if (!LAUNCHING.has(o.command))
63
+ return;
64
+ if (o.node !== undefined && !validNodeSpec(o.node))
65
+ fail('"' + o.node + '" is not a Node version.', 'Use a number like 22 or 22.11.0, or lts, latest.');
66
+ if (o.python !== undefined && !validPythonSpec(o.python))
67
+ fail('"' + o.python + '" is not a Python 3 version.', 'Use 3, 3.12 or 3.12.4.');
68
+ for (const db of o.databases.flatMap((d) => d.split(',')).map((d) => d.trim()).filter(Boolean)) {
69
+ if (!DATABASES.includes(db))
70
+ unknownValue('database', db, DATABASES, 'Repeat --database for several.');
71
+ }
72
+ if (o.account !== undefined) {
73
+ const names = listAccounts(ctx.storageRoot, ctx.dataPath).map((a) => a.name);
74
+ if (!names.includes(o.account))
75
+ unknownValue('account', o.account, names, names.length ? 'See them with: sandbox account list' : 'None saved yet: sign in inside the sandbox, then run sandbox account save <name>.');
76
+ }
77
+ for (const s of o.sessions) {
78
+ checkHarness(s.harness, harnesses);
79
+ if (!fs.existsSync(s.folder) || !fs.statSync(s.folder).isDirectory())
80
+ fail('The folder ' + s.folder + ' (--session ' + s.harness + '@...) does not exist.', 'Check the path after the @.');
81
+ }
82
+ if (o.sessions.length && (o.harness !== undefined || o.workspace !== undefined))
83
+ fail('--session names the agent and folder of each session, so --harness and --workspace do not apply.', 'Use only --session <agent>@<folder> (repeatable).');
84
+ if (o.workspace !== undefined && !fs.existsSync(path.resolve(o.workspace)))
85
+ fail('The folder ' + path.resolve(o.workspace) + ' does not exist.', 'Check the --workspace path (or leave it out to use the current folder).');
86
+ for (const script of o.scripts)
87
+ if (!fs.existsSync(path.resolve(script)))
88
+ fail('The script ' + path.resolve(script) + ' does not exist.', 'Check the --script path.');
89
+ };
@@ -9,10 +9,11 @@ export const DEFAULTS = {
9
9
  guestWorkspace: 'auto',
10
10
  readOnly: false,
11
11
  mappings: [],
12
- sandbox: { memoryMb: 'auto', maxMemoryMb: 32768, reserveHostMb: 8192, vGpu: false, networking: true, clipboard: true, audioInput: false, videoInput: false, printerRedirection: false, protectedClient: true, headless: false },
12
+ sandbox: { memoryMb: 'auto', maxMemoryMb: 32768, maxMemoryPercent: 80, cpuLimit: 80, reserveHostMb: 8192, vGpu: false, networking: true, clipboard: true, audioInput: false, videoInput: false, printerRedirection: false, protectedClient: true, headless: false },
13
13
  storage: { root: '%LOCALAPPDATA%\\AgentSandbox', persistAgentState: true, dataName: 'default' },
14
14
  network: { blockPrivateRanges: true },
15
15
  provisioning: { copyFromHost: true, fallbackToInstall: true, refresh: false, git: { enabled: true, access: 'full' }, terminal: { enabled: true } },
16
+ downloads: { requireChecksum: false },
16
17
  toolchains: {
17
18
  node: { version: 'host', extraVersions: [], corepack: true, volta: 'auto', globalPackages: [] },
18
19
  python: { enabled: 'auto', version: '3', python2: true, pyenv: 'auto', tools: [] },
@@ -44,6 +45,7 @@ export const DEFAULTS = {
44
45
  sqlserver: { enabled: 'auto' },
45
46
  },
46
47
  attach: { enabled: true, reuseSession: true, auto: false, autoHide: false },
48
+ agentCommands: { enabled: false, agents: 'auto' },
47
49
  secrets: { strip: true, allow: [], patterns: [], warnFiles: true },
48
50
  personalization: { enabled: true },
49
51
  claudeProfile: { enabled: true, hostDir: 'auto', include: ['settings.json', 'CLAUDE.md', 'commands', 'agents', 'skills', 'plugins'], globalStateFile: 'auto', copyCredentials: false, overwrite: true, syncHistory: true, syncClaudeMem: true },
@@ -67,6 +69,7 @@ export const DEFAULTS = {
67
69
  aider: { uvPackage: 'aider-chat', uvBin: 'aider', arguments: [], environment: {}, resumeArguments: ['--restore-chat-history'] },
68
70
  },
69
71
  hooks: { failFast: false },
72
+ projects: { all: false, exclude: [], roots: [], askToAddRoot: true, declinedRoots: [], wsbShare: true },
70
73
  };
71
74
  /** Per-user folder for config and hooks. Survives reinstalling the npm package. */
72
75
  export const userDir = () => process.env.AGENT_SANDBOX_HOME ?? path.join(homeDir(), '.agent-sandbox');
@@ -0,0 +1,16 @@
1
+ export declare const GB: number;
2
+ /** 1536 -> "1.5 KB". */
3
+ export declare const humanBytes: (bytes: number) => string;
4
+ /** Size on disk, never following links or junctions (a link counts as 0, so a planted junction cannot make us walk this PC's disk). */
5
+ export declare const sizeOf: (p: string) => number;
6
+ /** Free bytes on the drive that holds `dir` (which may not exist yet: the nearest existing parent is asked); null if unknown. */
7
+ export declare const freeBytesAt: (dir: string) => number | null;
8
+ export interface DiskUsage {
9
+ drive: string;
10
+ totalBytes: number;
11
+ freeBytes: number;
12
+ usedBytes: number;
13
+ holdsStorage: boolean;
14
+ }
15
+ /** Every local drive's size and free space, and which one holds the sandbox storage. */
16
+ export declare const diskUsage: (storageRoot: string) => DiskUsage[];
@@ -0,0 +1,55 @@
1
+ // Sizes and free space on disk, shared by `data`, `repair`, `setup` and the storage cleanup.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import _ from 'lodash';
5
+ export const GB = 1024 ** 3;
6
+ /** 1536 -> "1.5 KB". */
7
+ export const humanBytes = (bytes) => {
8
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
9
+ const i = bytes <= 0 ? 0 : Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
10
+ const value = bytes / 1024 ** i;
11
+ return (i === 0 ? String(bytes) : value.toFixed(value < 10 ? 1 : 0)) + ' ' + units[i];
12
+ };
13
+ /** Size on disk, never following links or junctions (a link counts as 0, so a planted junction cannot make us walk this PC's disk). */
14
+ export const sizeOf = (p) => {
15
+ try {
16
+ const stat = fs.lstatSync(p);
17
+ if (stat.isSymbolicLink())
18
+ return 0;
19
+ if (!stat.isDirectory())
20
+ return stat.size;
21
+ return _.sumBy(fs.readdirSync(p, { withFileTypes: true }), (d) => (d.isSymbolicLink() ? 0 : sizeOf(path.join(p, d.name))));
22
+ }
23
+ catch {
24
+ return 0;
25
+ }
26
+ };
27
+ /** Free bytes on the drive that holds `dir` (which may not exist yet: the nearest existing parent is asked); null if unknown. */
28
+ export const freeBytesAt = (dir) => {
29
+ let at = path.resolve(dir);
30
+ while (!fs.existsSync(at) && path.dirname(at) !== at)
31
+ at = path.dirname(at);
32
+ try {
33
+ const s = fs.statfsSync(at);
34
+ return s.bavail * s.bsize;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ };
40
+ /** Every local drive's size and free space, and which one holds the sandbox storage. */
41
+ export const diskUsage = (storageRoot) => {
42
+ const storageDrive = path.parse(path.resolve(storageRoot)).root.toUpperCase();
43
+ return 'CDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((letter) => letter + ':\\').flatMap((drive) => {
44
+ try {
45
+ const s = fs.statfsSync(drive);
46
+ const totalBytes = s.blocks * s.bsize;
47
+ if (!totalBytes)
48
+ return [];
49
+ return [{ drive, totalBytes, freeBytes: s.bavail * s.bsize, usedBytes: totalBytes - s.bfree * s.bsize, holdsStorage: drive.toUpperCase() === storageDrive }];
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ });
55
+ };
@@ -2,6 +2,14 @@ import type { Config, LocalConfigFile, Overrides } from './types.js';
2
2
  export declare const LOCAL_CONFIG_NAME = ".sandbox-config.json";
3
3
  /** Validates a folder's settings and resolves its relative paths against that folder. */
4
4
  export declare const normalizeOverrides: (data: unknown, dir: string, source: string) => Overrides;
5
+ /**
6
+ * Protections a project file may only make stricter, never looser. A repo can ship the file, so it must not be able to
7
+ * switch off what you turned on (or turn on what you turned off). The strictest of the two values always wins.
8
+ */
9
+ export declare const ON_IS_SAFER: string[];
10
+ export declare const OFF_IS_SAFER: string[];
11
+ export declare const GIT_STRICTNESS: Record<string, number>;
12
+ export declare const NEVER_LOOSER: string[];
5
13
  /** Applies validated overrides. Mappings with the same guest folder replace the configured ones. */
6
14
  export declare const applyOverrides: (config: Config, ov: Overrides) => Config;
7
15
  /** Human-readable list of what a local config changes, with risky items flagged. */
@@ -20,10 +20,25 @@ export const normalizeOverrides = (data, dir, source) => {
20
20
  fail(source + ' is not valid:\n - scripts: file not found: ' + missing.join(', '));
21
21
  return { ...overrides, scripts, mappings: overrides.mappings?.map((m) => ({ ...m, host: expandPath(m.host, dir) })) };
22
22
  };
23
+ /**
24
+ * Protections a project file may only make stricter, never looser. A repo can ship the file, so it must not be able to
25
+ * switch off what you turned on (or turn on what you turned off). The strictest of the two values always wins.
26
+ */
27
+ export const ON_IS_SAFER = ['readOnly', 'network.blockPrivateRanges', 'sandbox.protectedClient', 'downloads.requireChecksum'];
28
+ export const OFF_IS_SAFER = ['sandbox.networking', 'sandbox.clipboard', 'sandbox.audioInput', 'sandbox.videoInput', 'sandbox.printerRedirection', 'sandbox.vGpu', 'claudeProfile.copyCredentials'];
29
+ export const GIT_STRICTNESS = { full: 0, readonly: 1, none: 2 };
30
+ export const NEVER_LOOSER = [...ON_IS_SAFER, ...OFF_IS_SAFER, 'provisioning.git.access'];
31
+ const keepStrictest = (yours, merged) => {
32
+ ON_IS_SAFER.forEach((p) => _.set(merged, p, Boolean(_.get(yours, p)) || Boolean(_.get(merged, p))));
33
+ OFF_IS_SAFER.forEach((p) => _.set(merged, p, Boolean(_.get(yours, p)) && Boolean(_.get(merged, p))));
34
+ if (GIT_STRICTNESS[yours.provisioning.git.access] > GIT_STRICTNESS[merged.provisioning.git.access])
35
+ merged.provisioning.git.access = yours.provisioning.git.access;
36
+ };
23
37
  /** Applies validated overrides. Mappings with the same guest folder replace the configured ones. */
24
38
  export const applyOverrides = (config, ov) => {
25
39
  const { harness, mappings, scripts: _scripts, $schema: _schema, ...rest } = ov;
26
40
  const next = mergeConfig(config, rest);
41
+ keepStrictest(config, next);
27
42
  if (harness)
28
43
  next.defaultHarness = harness;
29
44
  if (mappings)
@@ -41,13 +56,11 @@ export const describeOverrides = (ov) => {
41
56
  ...flatten(sections),
42
57
  ];
43
58
  const risky = _.compact([
44
- ov.claudeProfile?.copyCredentials === true && 'claudeProfile.copyCredentials = true copies your Claude LOGIN into the sandbox',
45
- ov.network?.blockPrivateRanges === false && 'network.blockPrivateRanges = false turns OFF the LAN block',
46
59
  mappings.some((m) => !m.readOnly) && 'a READ/WRITE mapping lets the agent change files outside your workspace',
47
- ov.provisioning?.git?.access === 'full' && 'provisioning.git.access = full lets the agent commit and push',
48
- ov.sandbox?.vGpu === true && 'sandbox.vGpu = true widens the sandbox attack surface',
49
60
  ]);
50
- return [...lines, ...risky.map((r) => '!! ' + r)];
61
+ // These are only used when they are stricter than your own settings, so a project can never loosen them.
62
+ const limited = NEVER_LOOSER.filter((p) => _.has(sections, p)).map((p) => p + ' is only used if it is stricter than your own setting');
63
+ return [...lines, ...risky.map((r) => '!! ' + r), ...limited.map((r) => ' ' + r)];
51
64
  };
52
65
  export const readLocalConfig = (dir) => {
53
66
  const file = path.join(dir, LOCAL_CONFIG_NAME);
@@ -0,0 +1,15 @@
1
+ import type { Config } from './types.js';
2
+ /** The protections of a running sandbox, by setting path (`sandbox.networking`, `provisioning.git.access`, ...). */
3
+ export type Protections = Record<string, boolean | string>;
4
+ export declare const protectionsOf: (config: Config) => Protections;
5
+ export interface JoinCheck {
6
+ /** Settings this project needs stricter than the running sandbox: the launch is refused. */
7
+ stricter: string[];
8
+ /** Settings it would have looser: it gets the sandbox's (stricter) value; shown as a note. */
9
+ looser: string[];
10
+ }
11
+ /**
12
+ * Compares a project's effective protections with the running sandbox's. Hub projects are always read/write
13
+ * (Windows Sandbox maps the hub once, read/write), so a read-only project counts as stricter.
14
+ */
15
+ export declare const checkJoin: (running: Protections, wanted: Protections) => JoinCheck;
@@ -0,0 +1,35 @@
1
+ // Joining a running sandbox from another project. Its protections (network, LAN block, git access, clipboard, the
2
+ // .wsb device settings, read-only) are machine-wide: they were fixed when the sandbox booted for the first project.
3
+ // A second project may never loosen them, and a project that needs a STRICTER setting than the running sandbox has
4
+ // cannot get it there without changing it for the sessions already running, so that launch is refused with a clear
5
+ // message. A project asking for less than the sandbox has simply runs with the sandbox's (stricter) setting.
6
+ // Pure: the host checks it before registering a project, the tests drive it directly.
7
+ import _ from 'lodash';
8
+ import { GIT_STRICTNESS, NEVER_LOOSER, OFF_IS_SAFER, ON_IS_SAFER } from './local-config.js';
9
+ export const protectionsOf = (config) => Object.fromEntries(NEVER_LOOSER.map((p) => [p, _.get(config, p)]));
10
+ /** 1 = `wanted` is stricter than `running`, -1 = looser, 0 = the same. */
11
+ const compare = (setting, running, wanted) => {
12
+ if (setting === 'provisioning.git.access')
13
+ return Math.sign((GIT_STRICTNESS[String(wanted)] ?? 0) - (GIT_STRICTNESS[String(running)] ?? 0));
14
+ if (ON_IS_SAFER.includes(setting))
15
+ return Number(Boolean(wanted)) - Number(Boolean(running));
16
+ if (OFF_IS_SAFER.includes(setting))
17
+ return Number(Boolean(running)) - Number(Boolean(wanted));
18
+ return 0;
19
+ };
20
+ /**
21
+ * Compares a project's effective protections with the running sandbox's. Hub projects are always read/write
22
+ * (Windows Sandbox maps the hub once, read/write), so a read-only project counts as stricter.
23
+ */
24
+ export const checkJoin = (running, wanted) => {
25
+ const stricter = [];
26
+ const looser = [];
27
+ for (const setting of NEVER_LOOSER) {
28
+ const result = compare(setting, setting === 'readOnly' ? false : running[setting], wanted[setting]);
29
+ if (result > 0)
30
+ stricter.push(setting + ' = ' + JSON.stringify(wanted[setting]) + ' (the running sandbox has ' + JSON.stringify(setting === 'readOnly' ? false : running[setting]) + ')');
31
+ if (result < 0)
32
+ looser.push(setting + ' stays ' + JSON.stringify(running[setting]));
33
+ }
34
+ return { stricter, looser };
35
+ };
@@ -0,0 +1,4 @@
1
+ /** A yes/no question (default no). */
2
+ export declare const askYesNo: (question: string, defaultYes?: boolean) => Promise<boolean>;
3
+ /** Asks the user to type `expected` (a stronger confirmation than yes/no). */
4
+ export declare const askTyped: (question: string, expected: string) => Promise<boolean>;
@@ -0,0 +1,15 @@
1
+ // Questions asked on the terminal, all through @inquirer/prompts. Without an interactive terminal the answer is always "no".
2
+ import { confirm, input } from '@inquirer/prompts';
3
+ /** A yes/no question (default no). */
4
+ export const askYesNo = async (question, defaultYes = false) => {
5
+ if (!process.stdin.isTTY)
6
+ return false;
7
+ return confirm({ message: question, default: defaultYes });
8
+ };
9
+ /** Asks the user to type `expected` (a stronger confirmation than yes/no). */
10
+ export const askTyped = async (question, expected) => {
11
+ if (!process.stdin.isTTY)
12
+ return false;
13
+ const answer = await input({ message: question + ' Type "' + expected + '" to confirm:' });
14
+ return answer.trim().toLowerCase() === expected;
15
+ };
@@ -0,0 +1,4 @@
1
+ /** A read/write folder mapped into the sandbox must not contain, or sit inside, any protected folder. */
2
+ export declare const assertSafeMapping: (folder: string, packageRoot: string, storageRoot: string) => void;
3
+ /** The agent gets read/write access to the workspace, so refuse folders that would let it reach the tool itself, its storage, or the OS. */
4
+ export declare const assertSafeWorkspace: (workspace: string, packageRoot: string, storageRoot: string) => void;
@@ -0,0 +1,37 @@
1
+ // Which folders may be shared with the sandbox. The agent gets read/write access to what is mapped, so nothing that
2
+ // would let it reach this tool, its storage, your settings or the OS may be mapped.
3
+ import { userDir } from './config.js';
4
+ import { fail, isInsideOrEqual, isRootPath, isUserProfile } from './util.js';
5
+ /** Folders the agent must never be able to write: the tool itself, its storage and your settings for it. */
6
+ const protectedFolders = (packageRoot, storageRoot) => [
7
+ ['this tool\'s install folder', packageRoot],
8
+ ['the sandbox storage folder', storageRoot],
9
+ ['your sandbox settings folder', userDir()],
10
+ ];
11
+ /** A read/write folder mapped into the sandbox must not contain, or sit inside, any protected folder. */
12
+ export const assertSafeMapping = (folder, packageRoot, storageRoot) => {
13
+ if (isRootPath(folder) || isUserProfile(folder))
14
+ fail('A whole drive or user profile cannot be mapped into the sandbox: ' + folder, 'Map a specific folder instead.');
15
+ for (const [label, dir] of protectedFolders(packageRoot, storageRoot)) {
16
+ if (isInsideOrEqual(dir, folder) || isInsideOrEqual(folder, dir))
17
+ fail(folder + ' cannot be mapped into the sandbox: it overlaps ' + label + ' (' + dir + ').', 'Map a folder that is not inside (and does not contain) ' + dir + '.');
18
+ }
19
+ };
20
+ /** The agent gets read/write access to the workspace, so refuse folders that would let it reach the tool itself, its storage, or the OS. */
21
+ export const assertSafeWorkspace = (workspace, packageRoot, storageRoot) => {
22
+ if (isRootPath(workspace))
23
+ fail('A whole drive cannot be the workspace: ' + workspace, 'Run sandbox claude from a project folder (or pass --workspace <folder>).');
24
+ if (isUserProfile(workspace))
25
+ fail('Your whole user profile cannot be the workspace: ' + workspace, 'Run sandbox claude from a project folder (or pass --workspace <folder>).');
26
+ for (const [label, dir] of protectedFolders(packageRoot, storageRoot)) {
27
+ if (isInsideOrEqual(dir, workspace))
28
+ fail(workspace + ' cannot be the workspace: it is inside ' + label + ' (' + dir + '), so the agent could change the code that runs on this PC.', dir === packageRoot ? 'Developing windows-development-sandbox itself? Install it as a copy instead of a link: run `npm run install:global` in the repository, then sandbox claude works there.' : 'Run sandbox claude from a project folder somewhere else.');
29
+ if (isInsideOrEqual(workspace, dir))
30
+ fail(workspace + ' cannot be the workspace: it contains ' + label + ' (' + dir + '), which the agent must not be able to change.', 'Run sandbox claude from a project folder that does not contain it.');
31
+ }
32
+ for (const name of ['WINDIR', 'SystemRoot', 'ProgramFiles', 'ProgramFiles(x86)', 'ProgramData']) {
33
+ const dir = process.env[name];
34
+ if (dir && isInsideOrEqual(dir, workspace))
35
+ fail('A system folder cannot be the workspace: ' + workspace, 'Run sandbox claude from a project folder.');
36
+ }
37
+ };
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ export declare const GuestPath: z.ZodString;
3
+ export declare const Name: z.ZodString;
4
+ export declare const Strings: z.ZodArray<z.ZodString>;
5
+ export declare const toggle: (description: string) => z.ZodObject<{
6
+ enabled: z.ZodBoolean;
7
+ }, z.core.$strict>;
8
+ export declare const SchemaRef: z.ZodString;
9
+ export declare const GitAccess: z.ZodEnum<{
10
+ readonly: "readonly";
11
+ full: "full";
12
+ none: "none";
13
+ }>;
14
+ /** true, false, or "auto": only when the project uses it (found from files like go.mod in the workspace or one level down). */
15
+ export declare const Enabled: z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>;
16
+ export declare const enabled: (what: string, markers: string) => z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>;
17
+ export declare const Packages: (what: string, example: string) => z.ZodArray<z.ZodString>;
@@ -0,0 +1,15 @@
1
+ // Building blocks shared by the settings schemas.
2
+ import { z } from 'zod';
3
+ import { isValidGuestPath } from '../../shared/guest-paths.js';
4
+ // Rules (and RESERVED_GUEST) live in shared/guest-paths.ts: the host re-checks them before writing the .wsb.
5
+ export const GuestPath = z.string().refine(isValidGuestPath, 'reserved or invalid guest path (a folder under C:\\ other than Windows, Program Files, Users, AgentData, Mirror, ..., or on drive D: to Z:)');
6
+ export const Name = z.string().regex(/^[A-Za-z0-9_-]+$/, 'letters, numbers, - and _ only');
7
+ export const Strings = z.array(z.string());
8
+ export const toggle = (description) => z.strictObject({ enabled: z.boolean().describe(description) });
9
+ export const SchemaRef = z.string().describe('Path or URL of the JSON Schema for editor autocomplete and docs.');
10
+ export const GitAccess = z.enum(['full', 'readonly', 'none'])
11
+ .describe('full: normal git. readonly: status/log/diff/show/blame only (no commit, push, checkout, ...). none: no git at all; .git is hidden from the agent.');
12
+ /** true, false, or "auto": only when the project uses it (found from files like go.mod in the workspace or one level down). */
13
+ export const Enabled = z.union([z.boolean(), z.literal('auto')]);
14
+ export const enabled = (what, markers) => Enabled.describe(what + ' true, false, or "auto" = only when the project has ' + markers + '.');
15
+ export const Packages = (what, example) => Strings.describe(what + ', installed once and kept (e.g. ' + example + ').');