windows-development-sandbox 0.0.0-stage → 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 (426) hide show
  1. package/.sandbox-config.example.json +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +94 -2
  4. package/config.example.json +349 -0
  5. package/dist/api.d.ts +31 -0
  6. package/dist/api.js +31 -0
  7. package/dist/bin/agent-sandbox.d.ts +2 -0
  8. package/dist/bin/agent-sandbox.js +3 -0
  9. package/dist/bin/claude-host.d.ts +2 -0
  10. package/dist/bin/claude-host.js +5 -0
  11. package/dist/bin/claude-sbx.d.ts +2 -0
  12. package/dist/bin/claude-sbx.js +3 -0
  13. package/dist/bin/codex-sbx.d.ts +2 -0
  14. package/dist/bin/codex-sbx.js +3 -0
  15. package/dist/bin/gemini-sbx.d.ts +2 -0
  16. package/dist/bin/gemini-sbx.js +3 -0
  17. package/dist/bin/sandbox.d.ts +2 -0
  18. package/dist/bin/sandbox.js +4 -0
  19. package/dist/bin/sbx-hide.d.ts +2 -0
  20. package/dist/bin/sbx-hide.js +3 -0
  21. package/dist/bin/sbx-show.d.ts +2 -0
  22. package/dist/bin/sbx-show.js +3 -0
  23. package/dist/cli/args.d.ts +52 -0
  24. package/dist/cli/args.js +143 -0
  25. package/dist/cli/colors.d.ts +24 -0
  26. package/dist/cli/colors.js +42 -0
  27. package/dist/cli/commands/accounts.d.ts +2 -0
  28. package/dist/cli/commands/accounts.js +52 -0
  29. package/dist/cli/commands/agent-commands.d.ts +6 -0
  30. package/dist/cli/commands/agent-commands.js +61 -0
  31. package/dist/cli/commands/context.d.ts +23 -0
  32. package/dist/cli/commands/context.js +57 -0
  33. package/dist/cli/commands/data/clean.d.ts +15 -0
  34. package/dist/cli/commands/data/clean.js +101 -0
  35. package/dist/cli/commands/data/collect.d.ts +11 -0
  36. package/dist/cli/commands/data/collect.js +54 -0
  37. package/dist/cli/commands/data/index.d.ts +4 -0
  38. package/dist/cli/commands/data/index.js +45 -0
  39. package/dist/cli/commands/data/remove.d.ts +2 -0
  40. package/dist/cli/commands/data/remove.js +30 -0
  41. package/dist/cli/commands/index.d.ts +10 -0
  42. package/dist/cli/commands/index.js +10 -0
  43. package/dist/cli/commands/launch.d.ts +29 -0
  44. package/dist/cli/commands/launch.js +333 -0
  45. package/dist/cli/commands/maintenance.d.ts +8 -0
  46. package/dist/cli/commands/maintenance.js +154 -0
  47. package/dist/cli/commands/project.d.ts +29 -0
  48. package/dist/cli/commands/project.js +63 -0
  49. package/dist/cli/commands/projects.d.ts +14 -0
  50. package/dist/cli/commands/projects.js +146 -0
  51. package/dist/cli/commands/repair/checks.d.ts +2 -0
  52. package/dist/cli/commands/repair/checks.js +170 -0
  53. package/dist/cli/commands/repair/finders.d.ts +30 -0
  54. package/dist/cli/commands/repair/finders.js +108 -0
  55. package/dist/cli/commands/repair/index.d.ts +12 -0
  56. package/dist/cli/commands/repair/index.js +61 -0
  57. package/dist/cli/commands/repair/types.d.ts +26 -0
  58. package/dist/cli/commands/repair/types.js +3 -0
  59. package/dist/cli/commands/restart.d.ts +6 -0
  60. package/dist/cli/commands/restart.js +85 -0
  61. package/dist/cli/commands/sessions.d.ts +12 -0
  62. package/dist/cli/commands/sessions.js +257 -0
  63. package/dist/cli/commands/setup/checks.d.ts +13 -0
  64. package/dist/cli/commands/setup/checks.js +71 -0
  65. package/dist/cli/commands/setup/facts.d.ts +31 -0
  66. package/dist/cli/commands/setup/facts.js +60 -0
  67. package/dist/cli/commands/setup/index.d.ts +2 -0
  68. package/dist/cli/commands/setup/index.js +43 -0
  69. package/dist/cli/commands/user-config.d.ts +8 -0
  70. package/dist/cli/commands/user-config.js +41 -0
  71. package/dist/cli/history.d.ts +11 -0
  72. package/dist/cli/history.js +21 -0
  73. package/dist/cli/index.d.ts +20 -0
  74. package/dist/cli/index.js +121 -0
  75. package/dist/cli/manifest.d.ts +103 -0
  76. package/dist/cli/manifest.js +107 -0
  77. package/dist/cli/menu-multi.d.ts +12 -0
  78. package/dist/cli/menu-multi.js +82 -0
  79. package/dist/cli/menu.d.ts +14 -0
  80. package/dist/cli/menu.js +290 -0
  81. package/dist/cli/progress.d.ts +9 -0
  82. package/dist/cli/progress.js +49 -0
  83. package/dist/cli/settings/edit.d.ts +13 -0
  84. package/dist/cli/settings/edit.js +19 -0
  85. package/dist/cli/settings/index.d.ts +8 -0
  86. package/dist/cli/settings/index.js +131 -0
  87. package/dist/cli/settings/prompts.d.ts +7 -0
  88. package/dist/cli/settings/prompts.js +48 -0
  89. package/dist/cli/settings/schema.d.ts +33 -0
  90. package/dist/cli/settings/schema.js +29 -0
  91. package/dist/cli/validate.d.ts +17 -0
  92. package/dist/cli/validate.js +89 -0
  93. package/dist/core/config.d.ts +17 -0
  94. package/dist/core/config.js +106 -0
  95. package/dist/core/detect-databases.d.ts +15 -0
  96. package/dist/core/detect-databases.js +147 -0
  97. package/dist/core/detect-pwsh.d.ts +18 -0
  98. package/dist/core/detect-pwsh.js +111 -0
  99. package/dist/core/detect-toolchains.d.ts +30 -0
  100. package/dist/core/detect-toolchains.js +58 -0
  101. package/dist/core/disk.d.ts +16 -0
  102. package/dist/core/disk.js +55 -0
  103. package/dist/core/local-config.d.ts +20 -0
  104. package/dist/core/local-config.js +77 -0
  105. package/dist/core/project-policy.d.ts +15 -0
  106. package/dist/core/project-policy.js +35 -0
  107. package/dist/core/prompt.d.ts +4 -0
  108. package/dist/core/prompt.js +15 -0
  109. package/dist/core/safe-paths.d.ts +4 -0
  110. package/dist/core/safe-paths.js +37 -0
  111. package/dist/core/schema/common.d.ts +17 -0
  112. package/dist/core/schema/common.js +15 -0
  113. package/dist/core/schema/config.d.ts +594 -0
  114. package/dist/core/schema/config.js +63 -0
  115. package/dist/core/schema/databases.d.ts +33 -0
  116. package/dist/core/schema/databases.js +35 -0
  117. package/dist/core/schema/harness.d.ts +12 -0
  118. package/dist/core/schema/harness.js +17 -0
  119. package/dist/core/schema/json-schema.d.ts +5 -0
  120. package/dist/core/schema/json-schema.js +25 -0
  121. package/dist/core/schema/profile.d.ts +27 -0
  122. package/dist/core/schema/profile.js +29 -0
  123. package/dist/core/schema/projects.d.ts +9 -0
  124. package/dist/core/schema/projects.js +11 -0
  125. package/dist/core/schema/sandbox.d.ts +50 -0
  126. package/dist/core/schema/sandbox.js +36 -0
  127. package/dist/core/schema/toolchains.d.ts +93 -0
  128. package/dist/core/schema/toolchains.js +83 -0
  129. package/dist/core/schema.d.ts +6 -0
  130. package/dist/core/schema.js +8 -0
  131. package/dist/core/types.d.ts +190 -0
  132. package/dist/core/types.js +1 -0
  133. package/dist/core/util.d.ts +60 -0
  134. package/dist/core/util.js +154 -0
  135. package/dist/core/workspace.d.ts +18 -0
  136. package/dist/core/workspace.js +128 -0
  137. package/dist/guest/boot-env.d.ts +4 -0
  138. package/dist/guest/boot-env.js +25 -0
  139. package/dist/guest/cmdline.d.ts +12 -0
  140. package/dist/guest/cmdline.js +23 -0
  141. package/dist/guest/daemon/exec.d.ts +5 -0
  142. package/dist/guest/daemon/exec.js +32 -0
  143. package/dist/guest/daemon/project.d.ts +14 -0
  144. package/dist/guest/daemon/project.js +132 -0
  145. package/dist/guest/daemon/session.d.ts +13 -0
  146. package/dist/guest/daemon/session.js +139 -0
  147. package/dist/guest/daemon/shutdown.d.ts +1 -0
  148. package/dist/guest/daemon/shutdown.js +21 -0
  149. package/dist/guest/daemon/state.d.ts +17 -0
  150. package/dist/guest/daemon/state.js +31 -0
  151. package/dist/guest/daemon.d.ts +1 -0
  152. package/dist/guest/daemon.js +53 -0
  153. package/dist/guest/db.d.ts +1 -0
  154. package/dist/guest/db.js +75 -0
  155. package/dist/guest/env.d.ts +51 -0
  156. package/dist/guest/env.js +101 -0
  157. package/dist/guest/gitguard.d.ts +7 -0
  158. package/dist/guest/gitguard.js +60 -0
  159. package/dist/guest/gitrules.d.ts +2 -0
  160. package/dist/guest/gitrules.js +11 -0
  161. package/dist/guest/guide.d.ts +16 -0
  162. package/dist/guest/guide.js +69 -0
  163. package/dist/guest/helpers.d.ts +3 -0
  164. package/dist/guest/helpers.js +26 -0
  165. package/dist/guest/main.d.ts +1 -0
  166. package/dist/guest/main.js +130 -0
  167. package/dist/guest/ports.d.ts +6 -0
  168. package/dist/guest/ports.js +22 -0
  169. package/dist/guest/project-context.d.ts +7 -0
  170. package/dist/guest/project-context.js +10 -0
  171. package/dist/guest/project-env.d.ts +20 -0
  172. package/dist/guest/project-env.js +20 -0
  173. package/dist/guest/project-status.d.ts +5 -0
  174. package/dist/guest/project-status.js +17 -0
  175. package/dist/guest/ptyhost.d.ts +22 -0
  176. package/dist/guest/ptyhost.js +36 -0
  177. package/dist/guest/setup/agents.d.ts +11 -0
  178. package/dist/guest/setup/agents.js +87 -0
  179. package/dist/guest/setup/claude.d.ts +21 -0
  180. package/dist/guest/setup/claude.js +146 -0
  181. package/dist/guest/setup/environment.d.ts +16 -0
  182. package/dist/guest/setup/environment.js +69 -0
  183. package/dist/guest/setup/hooks.d.ts +9 -0
  184. package/dist/guest/setup/hooks.js +50 -0
  185. package/dist/guest/setup/network.d.ts +10 -0
  186. package/dist/guest/setup/network.js +99 -0
  187. package/dist/guest/setup/policy.d.ts +3 -0
  188. package/dist/guest/setup/policy.js +18 -0
  189. package/dist/guest/setup/user.d.ts +7 -0
  190. package/dist/guest/setup/user.js +47 -0
  191. package/dist/guest/toolchains/catalogue.d.ts +55 -0
  192. package/dist/guest/toolchains/catalogue.js +31 -0
  193. package/dist/guest/toolchains/cli.d.ts +10 -0
  194. package/dist/guest/toolchains/cli.js +72 -0
  195. package/dist/guest/toolchains/common.d.ts +54 -0
  196. package/dist/guest/toolchains/common.js +147 -0
  197. package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
  198. package/dist/guest/toolchains/databases/dynamodb.js +52 -0
  199. package/dist/guest/toolchains/databases/index.d.ts +6 -0
  200. package/dist/guest/toolchains/databases/index.js +62 -0
  201. package/dist/guest/toolchains/databases/mongodb.d.ts +3 -0
  202. package/dist/guest/toolchains/databases/mongodb.js +31 -0
  203. package/dist/guest/toolchains/databases/mysql.d.ts +3 -0
  204. package/dist/guest/toolchains/databases/mysql.js +38 -0
  205. package/dist/guest/toolchains/databases/postgres.d.ts +5 -0
  206. package/dist/guest/toolchains/databases/postgres.js +38 -0
  207. package/dist/guest/toolchains/databases/redis.d.ts +3 -0
  208. package/dist/guest/toolchains/databases/redis.js +16 -0
  209. package/dist/guest/toolchains/databases/server.d.ts +14 -0
  210. package/dist/guest/toolchains/databases/server.js +52 -0
  211. package/dist/guest/toolchains/git.d.ts +3 -0
  212. package/dist/guest/toolchains/git.js +121 -0
  213. package/dist/guest/toolchains/go.d.ts +3 -0
  214. package/dist/guest/toolchains/go.js +33 -0
  215. package/dist/guest/toolchains/index.d.ts +17 -0
  216. package/dist/guest/toolchains/index.js +80 -0
  217. package/dist/guest/toolchains/java.d.ts +8 -0
  218. package/dist/guest/toolchains/java.js +96 -0
  219. package/dist/guest/toolchains/leftovers.d.ts +2 -0
  220. package/dist/guest/toolchains/leftovers.js +26 -0
  221. package/dist/guest/toolchains/managers.d.ts +16 -0
  222. package/dist/guest/toolchains/managers.js +84 -0
  223. package/dist/guest/toolchains/native/binstall.d.ts +7 -0
  224. package/dist/guest/toolchains/native/binstall.js +52 -0
  225. package/dist/guest/toolchains/native/cpp.d.ts +4 -0
  226. package/dist/guest/toolchains/native/cpp.js +38 -0
  227. package/dist/guest/toolchains/native/dotnet.d.ts +4 -0
  228. package/dist/guest/toolchains/native/dotnet.js +103 -0
  229. package/dist/guest/toolchains/native/mesa.d.ts +7 -0
  230. package/dist/guest/toolchains/native/mesa.js +46 -0
  231. package/dist/guest/toolchains/native/msvc/environment.d.ts +16 -0
  232. package/dist/guest/toolchains/native/msvc/environment.js +57 -0
  233. package/dist/guest/toolchains/native/msvc/index.d.ts +6 -0
  234. package/dist/guest/toolchains/native/msvc/index.js +48 -0
  235. package/dist/guest/toolchains/native/msvc/packages.d.ts +6 -0
  236. package/dist/guest/toolchains/native/msvc/packages.js +156 -0
  237. package/dist/guest/toolchains/native/msvc/tree.d.ts +15 -0
  238. package/dist/guest/toolchains/native/msvc/tree.js +21 -0
  239. package/dist/guest/toolchains/native/rust.d.ts +3 -0
  240. package/dist/guest/toolchains/native/rust.js +45 -0
  241. package/dist/guest/toolchains/node.d.ts +3 -0
  242. package/dist/guest/toolchains/node.js +152 -0
  243. package/dist/guest/toolchains/plan.d.ts +22 -0
  244. package/dist/guest/toolchains/plan.js +96 -0
  245. package/dist/guest/toolchains/pwsh.d.ts +4 -0
  246. package/dist/guest/toolchains/pwsh.js +14 -0
  247. package/dist/guest/toolchains/python.d.ts +3 -0
  248. package/dist/guest/toolchains/python.js +54 -0
  249. package/dist/guest/toolchains/runtimes.d.ts +19 -0
  250. package/dist/guest/toolchains/runtimes.js +38 -0
  251. package/dist/guest/toolchains/scripting/php.d.ts +3 -0
  252. package/dist/guest/toolchains/scripting/php.js +71 -0
  253. package/dist/guest/toolchains/scripting/ruby.d.ts +3 -0
  254. package/dist/guest/toolchains/scripting/ruby.js +47 -0
  255. package/dist/guest/toolchains/scripting/vcruntime.d.ts +7 -0
  256. package/dist/guest/toolchains/scripting/vcruntime.js +45 -0
  257. package/dist/guest/toolchains/versions.d.ts +47 -0
  258. package/dist/guest/toolchains/versions.js +77 -0
  259. package/dist/guest/toolchains/vscode.d.ts +2 -0
  260. package/dist/guest/toolchains/vscode.js +53 -0
  261. package/dist/guest/view.d.ts +1 -0
  262. package/dist/guest/view.js +42 -0
  263. package/dist/host/accounts.d.ts +32 -0
  264. package/dist/host/accounts.js +128 -0
  265. package/dist/host/agent-commands.d.ts +25 -0
  266. package/dist/host/agent-commands.js +145 -0
  267. package/dist/host/attach.d.ts +17 -0
  268. package/dist/host/attach.js +69 -0
  269. package/dist/host/channel.d.ts +38 -0
  270. package/dist/host/channel.js +162 -0
  271. package/dist/host/claude-profile.d.ts +15 -0
  272. package/dist/host/claude-profile.js +107 -0
  273. package/dist/host/cleanup.d.ts +23 -0
  274. package/dist/host/cleanup.js +57 -0
  275. package/dist/host/database-plan.d.ts +12 -0
  276. package/dist/host/database-plan.js +52 -0
  277. package/dist/host/generate.d.ts +7 -0
  278. package/dist/host/generate.js +98 -0
  279. package/dist/host/join.d.ts +28 -0
  280. package/dist/host/join.js +76 -0
  281. package/dist/host/launch/mappings.d.ts +3 -0
  282. package/dist/host/launch/mappings.js +50 -0
  283. package/dist/host/launch/projects.d.ts +15 -0
  284. package/dist/host/launch/projects.js +56 -0
  285. package/dist/host/launch/stage.d.ts +11 -0
  286. package/dist/host/launch/stage.js +66 -0
  287. package/dist/host/launch/wsb.d.ts +10 -0
  288. package/dist/host/launch/wsb.js +34 -0
  289. package/dist/host/pc-scan.d.ts +6 -0
  290. package/dist/host/pc-scan.js +37 -0
  291. package/dist/host/personalize.d.ts +10 -0
  292. package/dist/host/personalize.js +97 -0
  293. package/dist/host/platform.d.ts +19 -0
  294. package/dist/host/platform.js +126 -0
  295. package/dist/host/project-scan.d.ts +7 -0
  296. package/dist/host/project-scan.js +89 -0
  297. package/dist/host/projects.d.ts +100 -0
  298. package/dist/host/projects.js +147 -0
  299. package/dist/host/repo-guard.d.ts +36 -0
  300. package/dist/host/repo-guard.js +219 -0
  301. package/dist/host/secrets.d.ts +14 -0
  302. package/dist/host/secrets.js +133 -0
  303. package/dist/host/terminal.d.ts +32 -0
  304. package/dist/host/terminal.js +130 -0
  305. package/dist/host/test-guard.d.ts +6 -0
  306. package/dist/host/test-guard.js +15 -0
  307. package/dist/host/window.d.ts +4 -0
  308. package/dist/host/window.js +72 -0
  309. package/dist/host/wsb.d.ts +37 -0
  310. package/dist/host/wsb.js +101 -0
  311. package/dist/runtime/daemon.js +28494 -0
  312. package/dist/runtime/db.js +26387 -0
  313. package/dist/runtime/gitguard.js +84 -0
  314. package/dist/runtime/main.js +28622 -0
  315. package/dist/runtime/view.js +25841 -0
  316. package/dist/selftest/accounts.test.d.ts +2 -0
  317. package/dist/selftest/accounts.test.js +44 -0
  318. package/dist/selftest/agent-commands.test.d.ts +2 -0
  319. package/dist/selftest/agent-commands.test.js +47 -0
  320. package/dist/selftest/channel.test.d.ts +2 -0
  321. package/dist/selftest/channel.test.js +37 -0
  322. package/dist/selftest/cli.test.d.ts +2 -0
  323. package/dist/selftest/cli.test.js +118 -0
  324. package/dist/selftest/cmdline.test.d.ts +2 -0
  325. package/dist/selftest/cmdline.test.js +45 -0
  326. package/dist/selftest/config.test.d.ts +2 -0
  327. package/dist/selftest/config.test.js +38 -0
  328. package/dist/selftest/data.test.d.ts +2 -0
  329. package/dist/selftest/data.test.js +80 -0
  330. package/dist/selftest/detect.test.d.ts +2 -0
  331. package/dist/selftest/detect.test.js +58 -0
  332. package/dist/selftest/fixtures.d.ts +272 -0
  333. package/dist/selftest/fixtures.js +80 -0
  334. package/dist/selftest/generate.test.d.ts +2 -0
  335. package/dist/selftest/generate.test.js +63 -0
  336. package/dist/selftest/git-access.test.d.ts +2 -0
  337. package/dist/selftest/git-access.test.js +26 -0
  338. package/dist/selftest/guest-boot.test.d.ts +2 -0
  339. package/dist/selftest/guest-boot.test.js +76 -0
  340. package/dist/selftest/harness.d.ts +51 -0
  341. package/dist/selftest/harness.js +77 -0
  342. package/dist/selftest/host-sim.test.d.ts +2 -0
  343. package/dist/selftest/host-sim.test.js +93 -0
  344. package/dist/selftest/hostile-input.test.d.ts +2 -0
  345. package/dist/selftest/hostile-input.test.js +54 -0
  346. package/dist/selftest/index.d.ts +8 -0
  347. package/dist/selftest/index.js +155 -0
  348. package/dist/selftest/links.test.d.ts +2 -0
  349. package/dist/selftest/links.test.js +50 -0
  350. package/dist/selftest/live-daemon.test.d.ts +2 -0
  351. package/dist/selftest/live-daemon.test.js +91 -0
  352. package/dist/selftest/live-git.test.d.ts +2 -0
  353. package/dist/selftest/live-git.test.js +60 -0
  354. package/dist/selftest/live-projects.test.d.ts +2 -0
  355. package/dist/selftest/live-projects.test.js +127 -0
  356. package/dist/selftest/live.d.ts +65 -0
  357. package/dist/selftest/live.js +109 -0
  358. package/dist/selftest/local-config.test.d.ts +2 -0
  359. package/dist/selftest/local-config.test.js +107 -0
  360. package/dist/selftest/mirror.test.d.ts +2 -0
  361. package/dist/selftest/mirror.test.js +101 -0
  362. package/dist/selftest/network.test.d.ts +2 -0
  363. package/dist/selftest/network.test.js +28 -0
  364. package/dist/selftest/personalization.test.d.ts +2 -0
  365. package/dist/selftest/personalization.test.js +53 -0
  366. package/dist/selftest/projects.test.d.ts +2 -0
  367. package/dist/selftest/projects.test.js +254 -0
  368. package/dist/selftest/repair.test.d.ts +2 -0
  369. package/dist/selftest/repair.test.js +42 -0
  370. package/dist/selftest/repo-guard.test.d.ts +2 -0
  371. package/dist/selftest/repo-guard.test.js +63 -0
  372. package/dist/selftest/secrets.test.d.ts +2 -0
  373. package/dist/selftest/secrets.test.js +71 -0
  374. package/dist/selftest/setup.test.d.ts +2 -0
  375. package/dist/selftest/setup.test.js +46 -0
  376. package/dist/selftest/suite.d.ts +13 -0
  377. package/dist/selftest/suite.js +1 -0
  378. package/dist/selftest/sync.test.d.ts +2 -0
  379. package/dist/selftest/sync.test.js +94 -0
  380. package/dist/selftest/test-guard.test.d.ts +2 -0
  381. package/dist/selftest/test-guard.test.js +49 -0
  382. package/dist/selftest/toolchains.test.d.ts +2 -0
  383. package/dist/selftest/toolchains.test.js +103 -0
  384. package/dist/selftest/workspace.test.d.ts +2 -0
  385. package/dist/selftest/workspace.test.js +53 -0
  386. package/dist/shared/download.d.ts +60 -0
  387. package/dist/shared/download.js +215 -0
  388. package/dist/shared/guest-paths.d.ts +30 -0
  389. package/dist/shared/guest-paths.js +61 -0
  390. package/dist/shared/netrules.d.ts +18 -0
  391. package/dist/shared/netrules.js +98 -0
  392. package/dist/shared/paths.d.ts +14 -0
  393. package/dist/shared/paths.js +28 -0
  394. package/dist/shared/projects.d.ts +53 -0
  395. package/dist/shared/projects.js +67 -0
  396. package/dist/shared/pty-client.d.ts +86 -0
  397. package/dist/shared/pty-client.js +227 -0
  398. package/dist/shared/rundir.d.ts +39 -0
  399. package/dist/shared/rundir.js +94 -0
  400. package/dist/shared/safe-remove.d.ts +10 -0
  401. package/dist/shared/safe-remove.js +66 -0
  402. package/dist/shared/schema.d.ts +527 -0
  403. package/dist/shared/schema.js +90 -0
  404. package/dist/shared/system.d.ts +15 -0
  405. package/dist/shared/system.js +19 -0
  406. package/dist/shared/test-guard.d.ts +4 -0
  407. package/dist/shared/test-guard.js +37 -0
  408. package/dist/sync/claude-mem.d.ts +8 -0
  409. package/dist/sync/claude-mem.js +79 -0
  410. package/dist/sync/history.d.ts +41 -0
  411. package/dist/sync/history.js +161 -0
  412. package/dist/sync/watcher.d.ts +21 -0
  413. package/dist/sync/watcher.js +135 -0
  414. package/hooks/post-agent/README.txt +1 -0
  415. package/hooks/pre-provision/README.txt +1 -0
  416. package/package.json +91 -4
  417. package/sandbox/bootstrap.ps1 +258 -0
  418. package/sandbox/cpucap.ps1 +51 -0
  419. package/sandbox/gitshim.cs +35 -0
  420. package/sandbox/logon.cmd +18 -0
  421. package/sandbox/openssl.cs +42 -0
  422. package/sandbox/ptyhost.cs +614 -0
  423. package/sandbox/trust.cmd +35 -0
  424. package/sandbox/vswhere.cs +83 -0
  425. package/schemas/config.schema.json +1161 -0
  426. package/schemas/sandbox-config.schema.json +968 -0
@@ -0,0 +1,37 @@
1
+ // Defence in depth for the self-test: while AGENT_SANDBOX_SELFTEST=1 is set, nothing may act on the real PC. Every write or
2
+ // delete on the host is limited to the temp folder the tests use (outward actions refuse in host/test-guard.ts). The
3
+ // self-test sets the variable for itself and its child processes. No imports from core/: the sandbox bundles this file too.
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ export const selfTestRunning = () => process.env.AGENT_SANDBOX_SELFTEST === '1';
8
+ /** Resolves links in the part of the path that exists, so C:\Users\WDAGUT~1 and C:\Users\WDAGUtilityAccount compare equal. */
9
+ const canonical = (p) => {
10
+ let rest = '';
11
+ let current = path.resolve(p);
12
+ for (;;) {
13
+ try {
14
+ return path.join(fs.realpathSync.native(current), rest).toLowerCase();
15
+ }
16
+ catch {
17
+ const parent = path.dirname(current);
18
+ if (parent === current)
19
+ return path.resolve(p).toLowerCase();
20
+ rest = path.join(path.basename(current), rest);
21
+ current = parent;
22
+ }
23
+ }
24
+ };
25
+ /** Why the self-test may not write, copy or delete at `target` (anywhere but the temp folder), or null. */
26
+ export const testWriteRefusal = (target) => {
27
+ if (!selfTestRunning())
28
+ return null;
29
+ const temp = canonical(os.tmpdir());
30
+ const t = canonical(target);
31
+ return t === temp || t.startsWith(temp + path.sep) ? null : 'The self-test refused to change ' + target + ': it only works in temp folders, so it can never change this PC.';
32
+ };
33
+ export const assertTestWritable = (target) => {
34
+ const refusal = testWriteRefusal(target);
35
+ if (refusal)
36
+ throw new Error(refusal);
37
+ };
@@ -0,0 +1,8 @@
1
+ import type { SyncState } from './history.js';
2
+ export type MemResult = 'none' | 'copied-in' | 'copied-out' | 'unchanged' | 'conflict';
3
+ export declare const hostClaudeMemDir: () => string;
4
+ /**
5
+ * Direction is decided by which side's database changed since the last sync. If BOTH changed, the side about to be
6
+ * overwritten is kept as `<dir>.conflict-<time>` first, so nothing is ever lost.
7
+ */
8
+ export declare const syncClaudeMem: (hostDir: string, store: string, state: SyncState, direction: "in" | "out") => MemResult;
@@ -0,0 +1,79 @@
1
+ // claude-mem data (~/.claude-mem) between this PC and the sandbox's persistent store.
2
+ //
3
+ // IN (this PC -> sandbox): the whole folder is mirrored; nothing flows to your PC in that direction.
4
+ // OUT (sandbox -> this PC): ONLY the SQLite database files come back. Never settings, never the Chroma vector store:
5
+ // Chroma keeps Python pickle files, and a crafted pickle runs code when loaded, so copying it back would let a
6
+ // compromised sandbox execute code on your PC. claude-mem rebuilds its vector index from the database by itself.
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import _ from 'lodash';
10
+ import { dirExists, homeDir, isLink } from '../core/util.js';
11
+ import { safeRemove } from '../shared/safe-remove.js';
12
+ import { assertTestWritable } from '../host/test-guard.js';
13
+ const SKIP_IN = /(^logs$|\.pid$|\.port$|\.lock$|^worker.*\.json$|\.incoming$|\.conflict-\d+$)/i;
14
+ /** The only files that may come back from the sandbox. */
15
+ const DATABASE = /^claude-mem\.db(-wal|-shm)?$/i;
16
+ const MAX_DB_BYTES = 2 * 1024 * 1024 * 1024;
17
+ export const hostClaudeMemDir = () => process.env.CLAUDE_MEM_DATA_DIR ?? path.join(homeDir(), '.claude-mem');
18
+ /** Plain database files at the top of a claude-mem folder (links and anything else are ignored). */
19
+ const databaseFiles = (dir) => dirExists(dir) && !isLink(dir) ? fs.readdirSync(dir).filter((f) => DATABASE.test(f) && fs.lstatSync(path.join(dir, f)).isFile()) : [];
20
+ /** Newest database file time: the only thing that matters for deciding the direction. */
21
+ const stampOf = (dir) => _.max(databaseFiles(dir).map((f) => fs.statSync(path.join(dir, f)).mtimeMs)) ?? 0;
22
+ /** Host -> sandbox: replace the sandbox store with a copy of this PC's folder (staging folder, then swap). */
23
+ const mirrorIn = (hostDir, store) => {
24
+ const incoming = store + '.incoming';
25
+ safeRemove(incoming, path.dirname(store));
26
+ fs.cpSync(hostDir, incoming, { recursive: true, filter: (src) => src === hostDir || (!SKIP_IN.test(path.basename(src)) && !isLink(src)) });
27
+ safeRemove(store, path.dirname(store));
28
+ fs.renameSync(incoming, store);
29
+ };
30
+ /** Sandbox -> host: copy only the database files, each via a temp file + rename; everything else on this PC stays. */
31
+ const copyDatabaseOut = (store, hostDir) => {
32
+ fs.mkdirSync(hostDir, { recursive: true });
33
+ for (const name of databaseFiles(store)) {
34
+ const source = path.join(store, name);
35
+ if (fs.statSync(source).size > MAX_DB_BYTES)
36
+ throw new Error('claude-mem database in the sandbox is suspiciously large; not copied back: ' + source);
37
+ const temp = path.join(hostDir, name + '.from-sandbox');
38
+ fs.copyFileSync(source, temp);
39
+ fs.renameSync(temp, path.join(hostDir, name));
40
+ }
41
+ };
42
+ /** Keeps a copy of this PC's databases before they are replaced after both sides changed. */
43
+ const parkHostDatabases = (hostDir) => {
44
+ const parked = hostDir + '.conflict-' + Date.now();
45
+ fs.mkdirSync(parked, { recursive: true });
46
+ for (const name of databaseFiles(hostDir))
47
+ fs.copyFileSync(path.join(hostDir, name), path.join(parked, name));
48
+ };
49
+ /**
50
+ * Direction is decided by which side's database changed since the last sync. If BOTH changed, the side about to be
51
+ * overwritten is kept as `<dir>.conflict-<time>` first, so nothing is ever lost.
52
+ */
53
+ export const syncClaudeMem = (hostDir, store, state, direction) => {
54
+ assertTestWritable(direction === 'in' ? store : hostDir);
55
+ if (isLink(store))
56
+ throw new Error('Refusing to sync claude-mem: ' + store + ' is a link.');
57
+ const hostStamp = stampOf(hostDir);
58
+ const guestStamp = stampOf(store);
59
+ if (hostStamp === 0 && guestStamp === 0 && !dirExists(hostDir))
60
+ return 'none';
61
+ const hostChanged = hostStamp > (state.memHostStamp ?? 0) + 1;
62
+ const guestChanged = state.memGuestStamp !== undefined && guestStamp > state.memGuestStamp + 1;
63
+ let result = 'unchanged';
64
+ if (direction === 'in' && dirExists(hostDir) && (hostChanged || guestStamp === 0)) {
65
+ if (guestChanged)
66
+ fs.cpSync(store, store + '.conflict-' + Date.now(), { recursive: true });
67
+ mirrorIn(hostDir, store);
68
+ result = guestChanged ? 'conflict' : 'copied-in';
69
+ }
70
+ else if (direction === 'out' && guestChanged) {
71
+ if (hostChanged)
72
+ parkHostDatabases(hostDir);
73
+ copyDatabaseOut(store, hostDir);
74
+ result = hostChanged ? 'conflict' : 'copied-out';
75
+ }
76
+ state.memHostStamp = stampOf(hostDir);
77
+ state.memGuestStamp = stampOf(store);
78
+ return result;
79
+ };
@@ -0,0 +1,41 @@
1
+ import { z } from 'zod';
2
+ import { projectFolderName, translatePaths } from '../shared/paths.js';
3
+ export { projectFolderName, translatePaths };
4
+ declare const SyncStateSchema: z.ZodObject<{
5
+ hostClaudeDir: z.ZodString;
6
+ hostWorkspace: z.ZodString;
7
+ guestWorkspace: z.ZodString;
8
+ syncHistory: z.ZodOptional<z.ZodBoolean>;
9
+ syncClaudeMem: z.ZodOptional<z.ZodBoolean>;
10
+ memHostStamp: z.ZodOptional<z.ZodNumber>;
11
+ memGuestStamp: z.ZodOptional<z.ZodNumber>;
12
+ projects: z.ZodOptional<z.ZodArray<z.ZodObject<{
13
+ hostWorkspace: z.ZodString;
14
+ guestWorkspace: z.ZodString;
15
+ }, z.core.$strip>>>;
16
+ }, z.core.$strip>;
17
+ export type SyncState = z.output<typeof SyncStateSchema>;
18
+ export interface WorkspacePair {
19
+ hostWorkspace: string;
20
+ guestWorkspace: string;
21
+ }
22
+ /** extra: projects registered after boot (C:\Projects\<name> -> their real folder on this PC). */
23
+ export interface SyncPaths extends WorkspacePair {
24
+ hostClaudeDir: string;
25
+ guestClaudeDir: string;
26
+ extra?: WorkspacePair[];
27
+ }
28
+ /** Brings this PC's history into the sandbox state (run before the sandbox starts). Returns files copied. */
29
+ export declare const syncHistoryIn: (p: SyncPaths, options?: {
30
+ promptHistory?: boolean;
31
+ }) => number;
32
+ /** Copies new sandbox history back to this PC (safe while the sandbox runs: transcripts are append-only). */
33
+ export declare const syncHistoryOut: (p: SyncPaths) => number;
34
+ /**
35
+ * Host-only: <storage>\sync\<dataName>.json. It records which folder on this PC history flows back into, so it must
36
+ * never live in the data folder the sandbox can write (a forged copy could redirect writes anywhere on this PC).
37
+ */
38
+ export declare const syncStatePath: (dataPath: string) => string;
39
+ /** A state file that is missing, oversized or not in this shape counts as no state (nothing syncs back until the next launch). */
40
+ export declare const readSyncState: (dataPath: string) => SyncState | null;
41
+ export declare const writeSyncState: (dataPath: string, state: SyncState) => void;
@@ -0,0 +1,161 @@
1
+ // Two-way sync of Claude Code history between this PC and the sandbox's persistent state.
2
+ //
3
+ // Claude keeps transcripts per working folder (~/.claude/projects/<folder with every non-alphanumeric char as '-'>)
4
+ // and writes absolute paths inside them. The sandbox sees the workspace under a different path, so the current
5
+ // project's folder is renamed and paths inside its files are translated in both directions. Everything else is
6
+ // copied as-is, newest file wins, and modification times are preserved so repeated syncs are cheap.
7
+ //
8
+ // Safety (the sandbox side is untrusted):
9
+ // - links are never followed or written through, on either side;
10
+ // - what comes BACK is limited to transcripts (.jsonl), prompt history and to-do lists (.json), each capped in
11
+ // size, and never Claude's memory folders. Nothing executable or instruction-like flows to this PC.
12
+ import fs from 'node:fs';
13
+ import path from 'node:path';
14
+ import _ from 'lodash';
15
+ import { z } from 'zod';
16
+ import { guestWorkspaceAliases, isSafeFileName, projectFolderName, translatePaths } from '../shared/paths.js';
17
+ import { readValid, writeJsonAtomic } from '../shared/rundir.js';
18
+ import { assertTestWritable } from '../host/test-guard.js';
19
+ import { dirExists, ensureDir, fileExists, isLink, removeLink } from '../core/util.js';
20
+ export { projectFolderName, translatePaths };
21
+ const SESSION_DIRS = ['todos', 'plans', 'file-history'];
22
+ const DATA_FILE = /\.(jsonl?|txt|md)$/i;
23
+ const MAX_FILE_BYTES = 100 * 1024 * 1024;
24
+ /** Host-only record of the last launch: which folders map to each other and what syncs (from the effective config). */
25
+ const AbsolutePath = z.string().max(1024).refine((p) => path.isAbsolute(p), 'must be an absolute path');
26
+ const SyncStateSchema = z.object({
27
+ hostClaudeDir: AbsolutePath, hostWorkspace: AbsolutePath, guestWorkspace: z.string().max(1024),
28
+ syncHistory: z.boolean().optional(), syncClaudeMem: z.boolean().optional(),
29
+ /** Newest claude-mem database time on each side right after the last sync (detects edits on both sides). */
30
+ memHostStamp: z.number().optional(), memGuestStamp: z.number().optional(),
31
+ /** Projects registered while the sandbox ran (their history goes back to their own folders). */
32
+ projects: z.array(z.object({ hostWorkspace: AbsolutePath, guestWorkspace: z.string().max(1024) })).max(256).optional(),
33
+ });
34
+ const anyFile = () => true;
35
+ const transcript = (name) => name.endsWith('.jsonl');
36
+ const json = (name) => name.endsWith('.json');
37
+ /** Claude's auto-memory is loaded into every host session, so the sandbox must never write it. */
38
+ const NO_MEMORY = /^memory$/i;
39
+ const newerThan = (source, destination) => {
40
+ try {
41
+ return source.mtimeMs > fs.statSync(destination).mtimeMs + 1;
42
+ }
43
+ catch {
44
+ return true;
45
+ }
46
+ };
47
+ /** Writes a file, replacing a link at that path instead of writing through it. */
48
+ const writeSafely = (destination, write) => {
49
+ if (isLink(destination))
50
+ removeLink(destination);
51
+ write();
52
+ };
53
+ /** Copies files that are newer in `from`, optionally translating text content, and keeps the source mtime. */
54
+ const copyNewer = (from, to, options = {}) => {
55
+ if (!dirExists(from) || isLink(from) || isLink(to))
56
+ return 0;
57
+ const accept = options.accept ?? anyFile;
58
+ return _.sumBy(fs.readdirSync(from, { withFileTypes: true }), (entry) => {
59
+ if (!isSafeFileName(entry.name))
60
+ return 0;
61
+ const source = path.join(from, entry.name);
62
+ const destination = path.join(to, entry.name);
63
+ if (entry.isDirectory())
64
+ return options.skipDirs?.test(entry.name) ? 0 : copyNewer(source, destination, options);
65
+ if (!entry.isFile() || !accept(entry.name))
66
+ return 0;
67
+ const stat = fs.statSync(source);
68
+ if (stat.size > MAX_FILE_BYTES || !newerThan(stat, destination))
69
+ return 0;
70
+ ensureDir(to);
71
+ writeSafely(destination, () => {
72
+ if (!options.transform || !DATA_FILE.test(entry.name)) {
73
+ fs.copyFileSync(source, destination);
74
+ return;
75
+ }
76
+ let text = fs.readFileSync(source, 'utf8');
77
+ // A transcript can be mid-write; only take complete lines, the rest arrives with the next sync.
78
+ if (entry.name.endsWith('.jsonl') && !text.endsWith('\n'))
79
+ text = text.slice(0, text.lastIndexOf('\n') + 1);
80
+ fs.writeFileSync(destination, options.transform(text), 'utf8');
81
+ });
82
+ fs.utimesSync(destination, stat.atime, stat.mtime);
83
+ return 1;
84
+ });
85
+ };
86
+ const readLines = (file) => fileExists(file) && !isLink(file) && fs.statSync(file).size <= MAX_FILE_BYTES ? fs.readFileSync(file, 'utf8').split(/\r?\n/).filter((l) => l.trim() !== '') : [];
87
+ const isJsonObject = (line) => { try {
88
+ const v = JSON.parse(line);
89
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
90
+ }
91
+ catch {
92
+ return false;
93
+ } };
94
+ const lineTime = (line) => { try {
95
+ return Number(JSON.parse(line).timestamp) || 0;
96
+ }
97
+ catch {
98
+ return 0;
99
+ } };
100
+ /** Every workspace pair of the sandbox: the launch folder's, then the projects registered later (most specific first). */
101
+ const pairsOf = (p) => _.sortBy([{ hostWorkspace: p.hostWorkspace, guestWorkspace: p.guestWorkspace }, ...(p.extra ?? [])], (x) => -x.hostWorkspace.length);
102
+ const translateAll = (text, pairs) => pairs.reduce((out, [from, to]) => translatePaths(out, from, to), text);
103
+ /** Brings this PC's history into the sandbox state (run before the sandbox starts). Returns files copied. */
104
+ export const syncHistoryIn = (p, options = {}) => {
105
+ assertTestWritable(p.guestClaudeDir);
106
+ const pairs = pairsOf(p);
107
+ const byHost = new Map(pairs.map((x) => [projectFolderName(x.hostWorkspace), projectFolderName(x.guestWorkspace)]));
108
+ const guestNames = new Set(pairs.map((x) => projectFolderName(x.guestWorkspace)));
109
+ const transform = (t) => translateAll(t, pairs.map((x) => [x.hostWorkspace, x.guestWorkspace]));
110
+ const hostProjects = path.join(p.hostClaudeDir, 'projects');
111
+ const names = dirExists(hostProjects) ? fs.readdirSync(hostProjects).filter((name) => isSafeFileName(name) && (!guestNames.has(name) || byHost.has(name))) : [];
112
+ const projects = _.sumBy(names, (name) => byHost.has(name)
113
+ ? copyNewer(path.join(hostProjects, name), path.join(p.guestClaudeDir, 'projects', byHost.get(name)), { transform })
114
+ : copyNewer(path.join(hostProjects, name), path.join(p.guestClaudeDir, 'projects', name)));
115
+ const sessions = _.sumBy(SESSION_DIRS, (dir) => copyNewer(path.join(p.hostClaudeDir, dir), path.join(p.guestClaudeDir, dir)));
116
+ if (options.promptHistory === false)
117
+ return projects + sessions;
118
+ // Prompt history: rebuild the sandbox copy from this PC's file plus anything only the sandbox has.
119
+ const target = path.join(p.guestClaudeDir, 'history.jsonl');
120
+ const hostLines = readLines(path.join(p.hostClaudeDir, 'history.jsonl')).map(transform);
121
+ const known = new Set(hostLines);
122
+ const merged = _.sortBy([...hostLines, ...readLines(target).filter((l) => !known.has(l))], lineTime);
123
+ if (merged.length) {
124
+ ensureDir(p.guestClaudeDir);
125
+ writeSafely(target, () => fs.writeFileSync(target, merged.join('\n') + '\n', 'utf8'));
126
+ }
127
+ return projects + sessions;
128
+ };
129
+ /** Copies new sandbox history back to this PC (safe while the sandbox runs: transcripts are append-only). */
130
+ export const syncHistoryOut = (p) => {
131
+ assertTestWritable(p.hostClaudeDir);
132
+ // A mirrored workspace on F: really lives at C:\Mirror\F\...; tools that resolve real paths record that one. A
133
+ // project registered later is in the hub (C:\Projects\<name>) and goes back to its real folder on this PC.
134
+ const pairs = pairsOf(p).flatMap((x) => guestWorkspaceAliases(x.guestWorkspace).map((guest) => [guest, x.hostWorkspace]));
135
+ const byGuest = new Map(pairs.map(([guest, host]) => [projectFolderName(guest), projectFolderName(host)]));
136
+ const transform = (t) => translateAll(t, pairs);
137
+ const guestProjectsDir = path.join(p.guestClaudeDir, 'projects');
138
+ const names = dirExists(guestProjectsDir) && !isLink(guestProjectsDir) ? fs.readdirSync(guestProjectsDir).filter(isSafeFileName) : [];
139
+ const projects = _.sumBy(names, (name) => byGuest.has(name)
140
+ ? copyNewer(path.join(guestProjectsDir, name), path.join(p.hostClaudeDir, 'projects', byGuest.get(name)), { accept: transcript, skipDirs: NO_MEMORY, transform })
141
+ : copyNewer(path.join(guestProjectsDir, name), path.join(p.hostClaudeDir, 'projects', name), { accept: transcript, skipDirs: NO_MEMORY }));
142
+ // Only to-do lists come back besides transcripts; plans and file backups stay in the sandbox.
143
+ const sessions = copyNewer(path.join(p.guestClaudeDir, 'todos'), path.join(p.hostClaudeDir, 'todos'), { accept: json });
144
+ const hostFile = path.join(p.hostClaudeDir, 'history.jsonl');
145
+ const known = new Set(readLines(hostFile));
146
+ // Only prompt-history entries (one JSON object per line) come back, whatever else the sandbox wrote in that file.
147
+ const missing = readLines(path.join(p.guestClaudeDir, 'history.jsonl')).filter(isJsonObject).map(transform).filter((l) => !known.has(l));
148
+ if (missing.length) {
149
+ ensureDir(p.hostClaudeDir);
150
+ fs.appendFileSync(hostFile, missing.join('\n') + '\n', 'utf8');
151
+ }
152
+ return projects + sessions + missing.length;
153
+ };
154
+ /**
155
+ * Host-only: <storage>\sync\<dataName>.json. It records which folder on this PC history flows back into, so it must
156
+ * never live in the data folder the sandbox can write (a forged copy could redirect writes anywhere on this PC).
157
+ */
158
+ export const syncStatePath = (dataPath) => path.join(path.dirname(path.dirname(dataPath)), 'sync', path.basename(dataPath) + '.json');
159
+ /** A state file that is missing, oversized or not in this shape counts as no state (nothing syncs back until the next launch). */
160
+ export const readSyncState = (dataPath) => readValid(SyncStateSchema, syncStatePath(dataPath));
161
+ export const writeSyncState = (dataPath, state) => writeJsonAtomic(syncStatePath(dataPath), state);
@@ -0,0 +1,21 @@
1
+ import type { Config } from '../core/types.js';
2
+ import { type MemResult } from './claude-mem.js';
3
+ /**
4
+ * A project registered while the sandbox runs: its history comes in now (the sandbox's copy only gains files), and
5
+ * from now on syncs back to its own folder on this PC.
6
+ */
7
+ export declare const syncProjectIn: (dataPath: string, project: {
8
+ hostWorkspace: string;
9
+ guestWorkspace: string;
10
+ }) => number;
11
+ export interface SyncSummary {
12
+ history: number;
13
+ claudeMem: MemResult | 'disabled';
14
+ }
15
+ /** Before launch: finish syncing the previous run out, then bring this PC's history (and claude-mem) in. */
16
+ export declare const syncBeforeLaunch: (config: Config, dataPath: string, hostWorkspace: string) => SyncSummary;
17
+ /** Syncs the sandbox's history (and, when `final`, claude-mem) back to this PC, as configured for the last launch. */
18
+ export declare const syncBack: (dataPath: string, final: boolean) => SyncSummary;
19
+ export declare const watcherRunning: (dataPath: string) => boolean;
20
+ /** Body of the detached watcher process (`agent-sandbox _watch`). */
21
+ export declare const watch: (dataPath: string) => Promise<void>;
@@ -0,0 +1,135 @@
1
+ // When history and claude-mem move: in before every launch, out every minute while the sandbox runs (history) and
2
+ // once more when it closes (history + claude-mem). The watcher runs detached on the host for the sandbox's lifetime.
3
+ // Its pid file and log live in host-only storage, never in the data folder the sandbox can write.
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { writeFileAtomic } from '../shared/rundir.js';
7
+ import { refuseInSelfTest } from '../host/test-guard.js';
8
+ import { assertNoLinks, errMsg, sleep } from '../core/util.js';
9
+ import { hostClaudeDir } from '../host/claude-profile.js';
10
+ import { checkRepo } from '../host/repo-guard.js';
11
+ import { isSandboxRunning } from '../host/platform.js';
12
+ import { hostClaudeMemDir, syncClaudeMem } from './claude-mem.js';
13
+ import { readSyncState, syncHistoryIn, syncHistoryOut, writeSyncState } from './history.js';
14
+ const WATCH_INTERVAL_MS = 60 * 1000;
15
+ const storageOf = (dataPath) => path.dirname(path.dirname(dataPath));
16
+ const guestClaude = (dataPath) => path.join(dataPath, 'state', 'claude');
17
+ const guestMem = (dataPath) => path.join(dataPath, 'state', 'claude-mem');
18
+ /** Sync paths for the sandbox side, after checking nothing on the way there is a link planted by the sandbox. */
19
+ const toPaths = (state, dataPath) => {
20
+ assertNoLinks(dataPath, guestClaude(dataPath));
21
+ return { hostClaudeDir: state.hostClaudeDir, guestClaudeDir: guestClaude(dataPath), hostWorkspace: state.hostWorkspace, guestWorkspace: state.guestWorkspace, extra: state.projects ?? [] };
22
+ };
23
+ /**
24
+ * A project registered while the sandbox runs: its history comes in now (the sandbox's copy only gains files), and
25
+ * from now on syncs back to its own folder on this PC.
26
+ */
27
+ export const syncProjectIn = (dataPath, project) => {
28
+ const state = readSyncState(dataPath);
29
+ if (!state)
30
+ return 0;
31
+ const projects = [...(state.projects ?? []).filter((p) => p.hostWorkspace.toLowerCase() !== project.hostWorkspace.toLowerCase()), project];
32
+ const next = { ...state, projects };
33
+ writeSyncState(dataPath, next);
34
+ // Prompt history is left alone: sessions are appending to the sandbox's copy right now.
35
+ return state.syncHistory ? syncHistoryIn(toPaths(next, dataPath), { promptHistory: false }) : 0;
36
+ };
37
+ const memSync = (dataPath, state, direction) => {
38
+ assertNoLinks(dataPath, guestMem(dataPath));
39
+ return syncClaudeMem(hostClaudeMemDir(), guestMem(dataPath), state, direction);
40
+ };
41
+ /** Before launch: finish syncing the previous run out, then bring this PC's history (and claude-mem) in. */
42
+ export const syncBeforeLaunch = (config, dataPath, hostWorkspace) => {
43
+ const profile = config.claudeProfile;
44
+ const summary = { history: 0, claudeMem: 'disabled' };
45
+ if (!config.storage.persistAgentState)
46
+ return summary;
47
+ const previous = readSyncState(dataPath);
48
+ if (previous?.syncHistory)
49
+ syncHistoryOut(toPaths(previous, dataPath));
50
+ const state = { ...previous, hostClaudeDir: hostClaudeDir(profile), hostWorkspace, guestWorkspace: config.guestWorkspace, syncHistory: profile.syncHistory, syncClaudeMem: profile.syncClaudeMem, projects: [] };
51
+ if (profile.syncHistory)
52
+ summary.history = syncHistoryIn(toPaths(state, dataPath));
53
+ if (profile.syncClaudeMem) {
54
+ if (previous?.syncClaudeMem)
55
+ memSync(dataPath, state, 'out');
56
+ summary.claudeMem = memSync(dataPath, state, 'in');
57
+ }
58
+ writeSyncState(dataPath, state);
59
+ return summary;
60
+ };
61
+ /** Syncs the sandbox's history (and, when `final`, claude-mem) back to this PC, as configured for the last launch. */
62
+ export const syncBack = (dataPath, final) => {
63
+ const state = readSyncState(dataPath);
64
+ const summary = { history: 0, claudeMem: 'disabled' };
65
+ if (!state)
66
+ return summary;
67
+ if (state.syncHistory)
68
+ summary.history = syncHistoryOut(toPaths(state, dataPath));
69
+ if (final && state.syncClaudeMem)
70
+ summary.claudeMem = memSync(dataPath, state, 'out');
71
+ writeSyncState(dataPath, state);
72
+ return summary;
73
+ };
74
+ const hostRunDir = (dataPath) => path.join(storageOf(dataPath), 'run');
75
+ const watcherPidFile = (dataPath) => path.join(hostRunDir(dataPath), 'watcher-' + path.basename(dataPath) + '.pid');
76
+ const watcherLog = (dataPath) => path.join(storageOf(dataPath), 'logs', 'host-sync-' + path.basename(dataPath) + '.log');
77
+ // Signal 0 only asks whether the process exists. pid 0 would mean "my own process group", so it never counts.
78
+ const alive = (pid) => { if (!Number.isInteger(pid) || pid <= 0)
79
+ return false; try {
80
+ process.kill(pid, 0);
81
+ return true;
82
+ }
83
+ catch {
84
+ return false;
85
+ } };
86
+ export const watcherRunning = (dataPath) => {
87
+ try {
88
+ return alive(Number(fs.readFileSync(watcherPidFile(dataPath), 'utf8')));
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ };
94
+ /** Body of the detached watcher process (`agent-sandbox _watch`). */
95
+ export const watch = async (dataPath) => {
96
+ refuseInSelfTest('start the background sync');
97
+ if (watcherRunning(dataPath))
98
+ return;
99
+ fs.mkdirSync(hostRunDir(dataPath), { recursive: true });
100
+ fs.mkdirSync(path.dirname(watcherLog(dataPath)), { recursive: true });
101
+ writeFileAtomic(watcherPidFile(dataPath), String(process.pid));
102
+ const note = (message) => { try {
103
+ fs.appendFileSync(watcherLog(dataPath), new Date().toISOString() + ' ' + message + '\n');
104
+ }
105
+ catch { /* ignore */ } };
106
+ const sync = (final) => {
107
+ try {
108
+ for (const finding of checkRepo(dataPath))
109
+ note('SECURITY: neutralised a git ' + finding.kind + ' added inside the sandbox: ' + finding.detail);
110
+ }
111
+ catch (e) {
112
+ note('repo guard failed: ' + errMsg(e));
113
+ }
114
+ try {
115
+ const s = syncBack(dataPath, final);
116
+ if (s.history || final)
117
+ note((final ? 'sandbox closed; final sync: ' : '') + 'history ' + s.history + ', claude-mem ' + s.claudeMem);
118
+ }
119
+ catch (e) {
120
+ note('sync failed: ' + errMsg(e));
121
+ }
122
+ };
123
+ try {
124
+ await sleep(30000);
125
+ while (isSandboxRunning()) {
126
+ sync(false);
127
+ await sleep(WATCH_INTERVAL_MS);
128
+ }
129
+ await sleep(3000);
130
+ sync(true);
131
+ }
132
+ finally {
133
+ fs.rmSync(watcherPidFile(dataPath), { force: true });
134
+ }
135
+ };
@@ -0,0 +1 @@
1
+ Put .ps1 / .mjs / .cmd files here to run inside the sandbox AFTER the agent exits (name order).
@@ -0,0 +1 @@
1
+ Put .ps1 / .mjs / .cmd files here to run inside the sandbox BEFORE tools are provisioned (name order).
package/package.json CHANGED
@@ -1,6 +1,93 @@
1
1
  {
2
2
  "name": "windows-development-sandbox",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.1.0",
4
+ "description": "A disposable Windows Sandbox for development, with or without an AI agent (Claude Code, Codex, Gemini, opencode, ...): the folder you run it from is the workspace, toolchains and databases for your project install themselves, and nothing else on your PC is reachable.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "os": [
8
+ "win32"
9
+ ],
10
+ "engines": {
11
+ "node": ">=20"
12
+ },
13
+ "bin": {
14
+ "sandbox": "dist/bin/sandbox.js",
15
+ "windows-development-sandbox": "dist/bin/sandbox.js",
16
+ "agent-sandbox": "dist/bin/agent-sandbox.js",
17
+ "claude-sbx": "dist/bin/claude-sbx.js",
18
+ "claude-sandbox": "dist/bin/claude-sbx.js",
19
+ "codex-sbx": "dist/bin/codex-sbx.js",
20
+ "codex-sandbox": "dist/bin/codex-sbx.js",
21
+ "gemini-sbx": "dist/bin/gemini-sbx.js",
22
+ "gemini-sandbox": "dist/bin/gemini-sbx.js",
23
+ "claude-host": "dist/bin/claude-host.js",
24
+ "sbx-show": "dist/bin/sbx-show.js",
25
+ "sbx-hide": "dist/bin/sbx-hide.js"
26
+ },
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/api.d.ts",
30
+ "default": "./dist/api.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "sandbox",
36
+ "schemas",
37
+ "hooks",
38
+ "config.example.json",
39
+ ".sandbox-config.example.json",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsc -p tsconfig.json && node scripts/bundle.mjs && node scripts/schemas.mjs",
45
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
46
+ "rebuild": "npm run clean && npm run build",
47
+ "test": "npm run build && node dist/bin/agent-sandbox.js self-test",
48
+ "prepack": "npm run rebuild",
49
+ "release:patch": "npm version patch && git push --follow-tags",
50
+ "release:minor": "npm version minor && git push --follow-tags",
51
+ "release:major": "npm version major && git push --follow-tags",
52
+ "install:global": "node scripts/install-global.mjs",
53
+ "publish:npm": "node scripts/publish-npm.mjs",
54
+ "doctor": "node dist/bin/agent-sandbox.js doctor"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "keywords": [
60
+ "claude",
61
+ "claude-code",
62
+ "windows-sandbox",
63
+ "sandbox",
64
+ "agent",
65
+ "codex",
66
+ "gemini",
67
+ "development",
68
+ "opencode",
69
+ "databases",
70
+ "dynamodb"
71
+ ],
72
+ "dependencies": {
73
+ "@inquirer/prompts": "^7.10.1",
74
+ "commander": "^14.0.3",
75
+ "jsonc-parser": "^3.3.1",
76
+ "lodash": "^4.18.1",
77
+ "zod": "^4.6.5"
78
+ },
79
+ "devDependencies": {
80
+ "@types/lodash": "^4.17.26",
81
+ "@types/node": "^22.0.0",
82
+ "esbuild": "^0.28.2",
83
+ "typescript": "^5.8.0"
84
+ },
85
+ "repository": {
86
+ "type": "git",
87
+ "url": "git+https://github.com/JoshDiDuca/windows-development-sandbox.git"
88
+ },
89
+ "homepage": "https://github.com/JoshDiDuca/windows-development-sandbox#readme",
90
+ "bugs": {
91
+ "url": "https://github.com/JoshDiDuca/windows-development-sandbox/issues"
92
+ }
93
+ }