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
@@ -12,12 +12,25 @@
12
12
  import fs from 'node:fs';
13
13
  import path from 'node:path';
14
14
  import _ from 'lodash';
15
- import { guestWorkspaceAliases, projectFolderName, translatePaths } from '../shared/paths.js';
16
- import { dirExists, ensureDir, fileExists, isLink, removeLink, tryReadJson, writeJson } from '../core/util.js';
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';
17
20
  export { projectFolderName, translatePaths };
18
21
  const SESSION_DIRS = ['todos', 'plans', 'file-history'];
19
22
  const DATA_FILE = /\.(jsonl?|txt|md)$/i;
20
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
+ });
21
34
  const anyFile = () => true;
22
35
  const transcript = (name) => name.endsWith('.jsonl');
23
36
  const json = (name) => name.endsWith('.json');
@@ -43,6 +56,8 @@ const copyNewer = (from, to, options = {}) => {
43
56
  return 0;
44
57
  const accept = options.accept ?? anyFile;
45
58
  return _.sumBy(fs.readdirSync(from, { withFileTypes: true }), (entry) => {
59
+ if (!isSafeFileName(entry.name))
60
+ return 0;
46
61
  const source = path.join(from, entry.name);
47
62
  const destination = path.join(to, entry.name);
48
63
  if (entry.isDirectory())
@@ -69,23 +84,37 @@ const copyNewer = (from, to, options = {}) => {
69
84
  });
70
85
  };
71
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
+ } };
72
94
  const lineTime = (line) => { try {
73
95
  return Number(JSON.parse(line).timestamp) || 0;
74
96
  }
75
97
  catch {
76
98
  return 0;
77
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);
78
103
  /** Brings this PC's history into the sandbox state (run before the sandbox starts). Returns files copied. */
79
- export const syncHistoryIn = (p) => {
80
- const hostProject = projectFolderName(p.hostWorkspace);
81
- const guestProject = projectFolderName(p.guestWorkspace);
82
- const transform = (t) => translatePaths(t, p.hostWorkspace, p.guestWorkspace);
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]));
83
110
  const hostProjects = path.join(p.hostClaudeDir, 'projects');
84
- const names = dirExists(hostProjects) ? fs.readdirSync(hostProjects).filter((name) => name !== guestProject || hostProject === guestProject) : [];
85
- const projects = _.sumBy(names, (name) => name === hostProject
86
- ? copyNewer(path.join(hostProjects, name), path.join(p.guestClaudeDir, 'projects', guestProject), { transform })
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 })
87
114
  : copyNewer(path.join(hostProjects, name), path.join(p.guestClaudeDir, 'projects', name)));
88
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;
89
118
  // Prompt history: rebuild the sandbox copy from this PC's file plus anything only the sandbox has.
90
119
  const target = path.join(p.guestClaudeDir, 'history.jsonl');
91
120
  const hostLines = readLines(path.join(p.hostClaudeDir, 'history.jsonl')).map(transform);
@@ -99,21 +128,23 @@ export const syncHistoryIn = (p) => {
99
128
  };
100
129
  /** Copies new sandbox history back to this PC (safe while the sandbox runs: transcripts are append-only). */
101
130
  export const syncHistoryOut = (p) => {
102
- const hostProject = projectFolderName(p.hostWorkspace);
103
- // A mirrored workspace on F: really lives at C:\Mirror\F\...; tools that resolve real paths record that one.
104
- const guestNames = guestWorkspaceAliases(p.guestWorkspace);
105
- const guestProjects = new Set(guestNames.map(projectFolderName));
106
- const transform = (t) => guestNames.reduce((out, guest) => translatePaths(out, guest, p.hostWorkspace), t);
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);
107
137
  const guestProjectsDir = path.join(p.guestClaudeDir, 'projects');
108
- const names = dirExists(guestProjectsDir) && !isLink(guestProjectsDir) ? fs.readdirSync(guestProjectsDir) : [];
109
- const projects = _.sumBy(names, (name) => guestProjects.has(name)
110
- ? copyNewer(path.join(guestProjectsDir, name), path.join(p.hostClaudeDir, 'projects', hostProject), { accept: transcript, skipDirs: NO_MEMORY, transform })
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 })
111
141
  : copyNewer(path.join(guestProjectsDir, name), path.join(p.hostClaudeDir, 'projects', name), { accept: transcript, skipDirs: NO_MEMORY }));
112
142
  // Only to-do lists come back besides transcripts; plans and file backups stay in the sandbox.
113
143
  const sessions = copyNewer(path.join(p.guestClaudeDir, 'todos'), path.join(p.hostClaudeDir, 'todos'), { accept: json });
114
144
  const hostFile = path.join(p.hostClaudeDir, 'history.jsonl');
115
145
  const known = new Set(readLines(hostFile));
116
- const missing = readLines(path.join(p.guestClaudeDir, 'history.jsonl')).map(transform).filter((l) => !known.has(l));
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));
117
148
  if (missing.length) {
118
149
  ensureDir(p.hostClaudeDir);
119
150
  fs.appendFileSync(hostFile, missing.join('\n') + '\n', 'utf8');
@@ -125,5 +156,6 @@ export const syncHistoryOut = (p) => {
125
156
  * never live in the data folder the sandbox can write (a forged copy could redirect writes anywhere on this PC).
126
157
  */
127
158
  export const syncStatePath = (dataPath) => path.join(path.dirname(path.dirname(dataPath)), 'sync', path.basename(dataPath) + '.json');
128
- export const readSyncState = (dataPath) => tryReadJson(syncStatePath(dataPath));
129
- export const writeSyncState = (dataPath, state) => writeJson(syncStatePath(dataPath), state);
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);
@@ -1,5 +1,13 @@
1
1
  import type { Config } from '../core/types.js';
2
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;
3
11
  export interface SyncSummary {
4
12
  history: number;
5
13
  claudeMem: MemResult | 'disabled';
@@ -3,6 +3,8 @@
3
3
  // Its pid file and log live in host-only storage, never in the data folder the sandbox can write.
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
+ import { writeFileAtomic } from '../shared/rundir.js';
7
+ import { refuseInSelfTest } from '../host/test-guard.js';
6
8
  import { assertNoLinks, errMsg, sleep } from '../core/util.js';
7
9
  import { hostClaudeDir } from '../host/claude-profile.js';
8
10
  import { checkRepo } from '../host/repo-guard.js';
@@ -16,7 +18,21 @@ const guestMem = (dataPath) => path.join(dataPath, 'state', 'claude-mem');
16
18
  /** Sync paths for the sandbox side, after checking nothing on the way there is a link planted by the sandbox. */
17
19
  const toPaths = (state, dataPath) => {
18
20
  assertNoLinks(dataPath, guestClaude(dataPath));
19
- return { hostClaudeDir: state.hostClaudeDir, guestClaudeDir: guestClaude(dataPath), hostWorkspace: state.hostWorkspace, guestWorkspace: state.guestWorkspace };
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;
20
36
  };
21
37
  const memSync = (dataPath, state, direction) => {
22
38
  assertNoLinks(dataPath, guestMem(dataPath));
@@ -31,7 +47,7 @@ export const syncBeforeLaunch = (config, dataPath, hostWorkspace) => {
31
47
  const previous = readSyncState(dataPath);
32
48
  if (previous?.syncHistory)
33
49
  syncHistoryOut(toPaths(previous, dataPath));
34
- const state = { ...previous, hostClaudeDir: hostClaudeDir(profile), hostWorkspace, guestWorkspace: config.guestWorkspace, syncHistory: profile.syncHistory, syncClaudeMem: profile.syncClaudeMem };
50
+ const state = { ...previous, hostClaudeDir: hostClaudeDir(profile), hostWorkspace, guestWorkspace: config.guestWorkspace, syncHistory: profile.syncHistory, syncClaudeMem: profile.syncClaudeMem, projects: [] };
35
51
  if (profile.syncHistory)
36
52
  summary.history = syncHistoryIn(toPaths(state, dataPath));
37
53
  if (profile.syncClaudeMem) {
@@ -58,7 +74,9 @@ export const syncBack = (dataPath, final) => {
58
74
  const hostRunDir = (dataPath) => path.join(storageOf(dataPath), 'run');
59
75
  const watcherPidFile = (dataPath) => path.join(hostRunDir(dataPath), 'watcher-' + path.basename(dataPath) + '.pid');
60
76
  const watcherLog = (dataPath) => path.join(storageOf(dataPath), 'logs', 'host-sync-' + path.basename(dataPath) + '.log');
61
- const alive = (pid) => { try {
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 {
62
80
  process.kill(pid, 0);
63
81
  return true;
64
82
  }
@@ -75,11 +93,12 @@ export const watcherRunning = (dataPath) => {
75
93
  };
76
94
  /** Body of the detached watcher process (`agent-sandbox _watch`). */
77
95
  export const watch = async (dataPath) => {
96
+ refuseInSelfTest('start the background sync');
78
97
  if (watcherRunning(dataPath))
79
98
  return;
80
99
  fs.mkdirSync(hostRunDir(dataPath), { recursive: true });
81
100
  fs.mkdirSync(path.dirname(watcherLog(dataPath)), { recursive: true });
82
- fs.writeFileSync(watcherPidFile(dataPath), String(process.pid));
101
+ writeFileAtomic(watcherPidFile(dataPath), String(process.pid));
83
102
  const note = (message) => { try {
84
103
  fs.appendFileSync(watcherLog(dataPath), new Date().toISOString() + ' ' + message + '\n');
85
104
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windows-development-sandbox",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
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
5
  "type": "module",
6
6
  "license": "MIT",
@@ -37,14 +37,14 @@
37
37
  "hooks",
38
38
  "config.example.json",
39
39
  ".sandbox-config.example.json",
40
- "README.md"
40
+ "README.md",
41
+ "LICENSE"
41
42
  ],
42
43
  "scripts": {
43
44
  "build": "tsc -p tsconfig.json && node scripts/bundle.mjs && node scripts/schemas.mjs",
44
45
  "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
45
46
  "rebuild": "npm run clean && npm run build",
46
47
  "test": "npm run build && node dist/bin/agent-sandbox.js self-test",
47
- "prepare": "npm run build",
48
48
  "prepack": "npm run rebuild",
49
49
  "release:patch": "npm version patch && git push --follow-tags",
50
50
  "release:minor": "npm version minor && git push --follow-tags",
@@ -84,10 +84,10 @@
84
84
  },
85
85
  "repository": {
86
86
  "type": "git",
87
- "url": "git+https://github.com/JoshDiDuca/agent-sandbox.git"
87
+ "url": "git+https://github.com/JoshDiDuca/windows-development-sandbox.git"
88
88
  },
89
- "homepage": "https://github.com/JoshDiDuca/agent-sandbox#readme",
89
+ "homepage": "https://github.com/JoshDiDuca/windows-development-sandbox#readme",
90
90
  "bugs": {
91
- "url": "https://github.com/JoshDiDuca/agent-sandbox/issues"
91
+ "url": "https://github.com/JoshDiDuca/windows-development-sandbox/issues"
92
92
  }
93
93
  }