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,45 @@
1
+ // The Visual C++ runtime (vcruntime140.dll & co.) that PHP and other native tools need.
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { spawnAsync } from '../../../shared/download.js';
5
+ import { SYSTEM } from '../../../shared/system.js';
6
+ import { dryRun, execOutput, exists, log } from '../../env.js';
7
+ import { downloadFile, tool } from '../common.js';
8
+ const VC_REDIST = 'https://aka.ms/vs/17/release/vc_redist.x64.exe';
9
+ const MICROSOFT_SIGNER = 'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US';
10
+ /** 1638: a newer version is already installed; 3010: installed, a restart would finish it (not needed for the DLLs). */
11
+ const OK_EXIT_CODES = [0, 1638, 3010];
12
+ /** The subject of a valid Authenticode signature, or '' when the file is unsigned or tampered with. */
13
+ const signerOf = (file) => execOutput(SYSTEM.powershell(), ['-NoLogo', '-NoProfile', '-Command',
14
+ "$s = Get-AuthenticodeSignature -LiteralPath '" + file.replace(/'/g, "''") + "'; if ($s.Status -eq 'Valid') { $s.SignerCertificate.Subject }"]).trim();
15
+ /**
16
+ * A real installer (it writes to System32), so it runs on every boot; the installer itself is kept in persistent
17
+ * storage and only run when Microsoft's signature on it checks out. Resolves true when this call ran the installer.
18
+ * Only the "Visual C++ runtime" step reports it (`summary`); PHP calls it again just to be sure.
19
+ */
20
+ export const setupVcRuntime = async (env, summary = true) => {
21
+ if (dryRun) {
22
+ log('(dry run) would install the Visual C++ runtime');
23
+ return false;
24
+ }
25
+ const system32 = path.join(process.env.WINDIR ?? 'C:\\Windows', 'System32');
26
+ const missing = !exists(path.join(system32, 'vcruntime140.dll')) || !exists(path.join(system32, 'msvcp140.dll'));
27
+ if (missing) {
28
+ const installer = tool('vcredist', 'vc_redist.x64.exe');
29
+ if (!exists(installer)) {
30
+ log('Downloading the Visual C++ runtime (~25 MB once) ...');
31
+ await downloadFile(VC_REDIST, installer);
32
+ }
33
+ if (signerOf(installer) !== MICROSOFT_SIGNER) {
34
+ fs.rmSync(installer, { force: true });
35
+ throw new Error('vc_redist.x64.exe is not signed by Microsoft; deleted it (it is downloaded again next boot)');
36
+ }
37
+ log('Installing the Visual C++ runtime ...');
38
+ const code = await spawnAsync(installer, ['/install', '/quiet', '/norestart']);
39
+ if (!OK_EXIT_CODES.includes(code ?? -1) || !exists(path.join(system32, 'vcruntime140.dll')))
40
+ throw new Error('the Visual C++ runtime installer failed (exit ' + code + ')');
41
+ }
42
+ if (summary)
43
+ env.summary.push('vc++ runtime: installed');
44
+ return missing;
45
+ };
@@ -0,0 +1,47 @@
1
+ export interface NodeRelease {
2
+ version: string;
3
+ lts: string | false;
4
+ }
5
+ /** Turns an .nvmrc-style spec (20, v20.11.1, lts/*, lts/iron, node, latest) into an exact version like 20.11.1. */
6
+ export declare const pickNodeVersion: (releases: NodeRelease[], spec: string) => string | null;
7
+ /**
8
+ * Which of this PC's nvm versions to copy along with `current`: at most `max` in total, current included, the rest
9
+ * spread across the range (newest of each major first, then evenly spaced from oldest to newest).
10
+ */
11
+ export declare const pickSpreadVersions: (available: string[], current: string, max: number) => string[];
12
+ /** Newest stable version matching a prefix spec ("3" -> 3.13.1, "3.12" -> 3.12.10). */
13
+ export declare const pickPackageVersion: (all: string[], spec: string) => string | null;
14
+ /** Version spec from a version file's text (.ruby-version "ruby-3.3.1", .java-version "temurin-21", ...): its first x[.y[.z]]. */
15
+ export declare const specFromVersionFile: (text: string) => string | null;
16
+ /** Java major version from .java-version ("21", "17.0.9", "1.8", "temurin-21") or .sdkmanrc ("java=21.0.2-tem"). */
17
+ export declare const javaMajorFrom: (text: string, sdkmanrc?: boolean) => string | null;
18
+ export interface GoRelease {
19
+ version: string;
20
+ stable: boolean;
21
+ files: {
22
+ filename: string;
23
+ os: string;
24
+ arch: string;
25
+ kind: string;
26
+ sha256: string;
27
+ }[];
28
+ }
29
+ /** "stable" / "latest" -> newest stable release; "1.23" -> newest 1.23.x; "1.23.2" -> exactly that. Returns go.dev's entry. */
30
+ export declare const pickGoRelease: (releases: GoRelease[], spec: string) => GoRelease | null;
31
+ /**
32
+ * Does the project use a language? `entries` are file names in the workspace root and one folder down ("api/go.mod");
33
+ * markers are exact names or "*.ext".
34
+ */
35
+ export declare const projectUses: (entries: readonly string[], markers: readonly string[]) => boolean;
36
+ /** Newest MSVC tools version among VS manifest package ids ("microsoft.vc.14.44.17.14.tools.hostx64.targetx64.base"). */
37
+ export declare const pickMsvcVersion: (packageIds: string[], spec?: string) => string | null;
38
+ /** Newest Windows SDK build among component ids ("microsoft.visualstudio.component.windows11sdk.26100"). */
39
+ export declare const pickWindowsSdk: (packageIds: string[], spec?: string) => {
40
+ build: string;
41
+ componentId: string;
42
+ } | null;
43
+ /** The .cab files an SDK .msi refers to (their names are inside the msi as plain "<32 hex>.cab"). */
44
+ export declare const msiCabs: (msi: Buffer) => string[];
45
+ /** The oldest CMake many projects (OpenMoHAA among them) accept. */
46
+ export declare const CMAKE_FLOOR = "3.25";
47
+ export declare const cmakeTooOld: (version: string) => boolean;
@@ -0,0 +1,77 @@
1
+ // Version picking for the toolchains. Pure functions only (no environment, no I/O), so the host and the tests can
2
+ // import them without loading the guest runtime.
3
+ /** Turns an .nvmrc-style spec (20, v20.11.1, lts/*, lts/iron, node, latest) into an exact version like 20.11.1. */
4
+ export const pickNodeVersion = (releases, spec) => {
5
+ const s = spec.trim().toLowerCase().replace(/^v/, '');
6
+ const hit = ['node', 'latest', 'current', 'stable'].includes(s) ? releases[0]
7
+ : s === 'lts' || s === 'lts/*' ? releases.find((r) => r.lts)
8
+ : s.startsWith('lts/') ? releases.find((r) => typeof r.lts === 'string' && r.lts.toLowerCase() === s.slice(4))
9
+ : releases.find((r) => r.version === 'v' + s || r.version.startsWith('v' + s + '.'));
10
+ return hit ? hit.version.slice(1) : null;
11
+ };
12
+ /**
13
+ * Which of this PC's nvm versions to copy along with `current`: at most `max` in total, current included, the rest
14
+ * spread across the range (newest of each major first, then evenly spaced from oldest to newest).
15
+ */
16
+ export const pickSpreadVersions = (available, current, max) => {
17
+ const byVersion = (a, b) => a.localeCompare(b, undefined, { numeric: true });
18
+ const exact = available.filter((v) => /^\d+\.\d+\.\d+$/.test(v) && v !== current).sort(byVersion);
19
+ const newestPerMajor = exact.filter((v, i) => exact[i + 1]?.split('.')[0] !== v.split('.')[0]);
20
+ const pool = newestPerMajor.filter((v) => v.split('.')[0] !== current.split('.')[0]);
21
+ const want = Math.max(0, max - 1);
22
+ const picked = want >= pool.length ? pool
23
+ : want === 1 ? [pool[pool.length - 1]]
24
+ : Array.from({ length: want }, (_, i) => pool[Math.round(i * (pool.length - 1) / (want - 1))]);
25
+ return [current, ...picked];
26
+ };
27
+ /** Newest stable version matching a prefix spec ("3" -> 3.13.1, "3.12" -> 3.12.10). */
28
+ export const pickPackageVersion = (all, spec) => {
29
+ const s = spec.trim().replace(/^python-?/i, '');
30
+ const stable = all.filter((v) => /^\d+(\.\d+)*$/.test(v)).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
31
+ return stable.find((v) => v === s || v.startsWith(s + '.')) ?? null;
32
+ };
33
+ /** Version spec from a version file's text (.ruby-version "ruby-3.3.1", .java-version "temurin-21", ...): its first x[.y[.z]]. */
34
+ export const specFromVersionFile = (text) => /(\d+(?:\.\d+){0,2})/.exec(text.split(/\r?\n/).map((l) => l.replace(/#.*$/, '').trim()).find(Boolean) ?? '')?.[1] ?? null;
35
+ /** Java major version from .java-version ("21", "17.0.9", "1.8", "temurin-21") or .sdkmanrc ("java=21.0.2-tem"). */
36
+ export const javaMajorFrom = (text, sdkmanrc = false) => {
37
+ const line = sdkmanrc ? /^\s*java\s*=\s*(.+)$/m.exec(text)?.[1] : text;
38
+ const spec = line ? specFromVersionFile(line) : null;
39
+ if (!spec)
40
+ return null;
41
+ const [major, minor] = spec.split('.');
42
+ return major === '1' && minor ? minor : major;
43
+ };
44
+ /** "stable" / "latest" -> newest stable release; "1.23" -> newest 1.23.x; "1.23.2" -> exactly that. Returns go.dev's entry. */
45
+ export const pickGoRelease = (releases, spec) => {
46
+ const s = spec.trim().toLowerCase().replace(/^go/, '');
47
+ const stable = releases.filter((r) => r.stable);
48
+ if (s === 'stable' || s === 'latest')
49
+ return stable[0] ?? null;
50
+ return stable.find((r) => r.version === 'go' + s || r.version.startsWith('go' + s + '.')) ?? null;
51
+ };
52
+ /**
53
+ * Does the project use a language? `entries` are file names in the workspace root and one folder down ("api/go.mod");
54
+ * markers are exact names or "*.ext".
55
+ */
56
+ export const projectUses = (entries, markers) => entries.some((entry) => {
57
+ const name = entry.split(/[\/]/).pop().toLowerCase();
58
+ return markers.some((m) => (m.startsWith('*.') ? name.endsWith(m.slice(1).toLowerCase()) : name === m.toLowerCase()));
59
+ });
60
+ /** Newest MSVC tools version among VS manifest package ids ("microsoft.vc.14.44.17.14.tools.hostx64.targetx64.base"). */
61
+ export const pickMsvcVersion = (packageIds, spec = 'latest') => {
62
+ const versions = packageIds.map((id) => /^microsoft\.vc\.(\d+\.\d+\.\d+\.\d+)\.tools\.hostx64\.targetx64\.base$/i.exec(id)?.[1]).filter((v) => Boolean(v));
63
+ const sorted = [...new Set(versions)].sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
64
+ return (spec === 'latest' ? sorted[0] : sorted.find((v) => v === spec || v.startsWith(spec + '.'))) ?? null;
65
+ };
66
+ /** Newest Windows SDK build among component ids ("microsoft.visualstudio.component.windows11sdk.26100"). */
67
+ export const pickWindowsSdk = (packageIds, spec = 'latest') => {
68
+ const found = packageIds.map((id) => ({ id, build: /^microsoft\.visualstudio\.component\.windows1[01]sdk\.(\d+)$/i.exec(id)?.[1] })).filter((x) => Boolean(x.build));
69
+ const sorted = found.sort((a, b) => Number(b.build) - Number(a.build));
70
+ const hit = spec === 'latest' ? sorted[0] : sorted.find((x) => x.build === spec.replace(/^10\.0\./, '').split('.')[0]);
71
+ return hit ? { build: hit.build, componentId: hit.id } : null;
72
+ };
73
+ /** The .cab files an SDK .msi refers to (their names are inside the msi as plain "<32 hex>.cab"). */
74
+ export const msiCabs = (msi) => [...new Set([...msi.toString('latin1').matchAll(/([0-9a-f]{32}\.cab)/gi)].map((m) => m[1].toLowerCase()))];
75
+ /** The oldest CMake many projects (OpenMoHAA among them) accept. */
76
+ export const CMAKE_FLOOR = '3.25';
77
+ export const cmakeTooOld = (version) => /^\d+\.\d+/.test(version) && version.localeCompare(CMAKE_FLOOR, undefined, { numeric: true }) < 0;
@@ -0,0 +1,2 @@
1
+ import { type ToolEnv } from './common.js';
2
+ export declare const setupVscode: (env: ToolEnv, refresh: boolean) => Promise<void>;
@@ -0,0 +1,53 @@
1
+ // VS Code for debugging inside the sandbox: the portable zip build, checksum-verified. A `data` folder next to Code.exe
2
+ // switches it to portable mode, so settings and extensions live in C:\AgentData\tools\vscode\data and persist.
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { fetchJson, installArchive } from '../../shared/download.js';
7
+ import { dryRun, exists, isWin, log, mkdirp, powershell } from '../env.js';
8
+ import { arch, tool } from './common.js';
9
+ /** Newer builds keep their files under a commit-hash folder, so the installed version is recorded here. */
10
+ const VERSION_MARKER = '.agent-sandbox-version';
11
+ const desktopShortcut = (exe) => {
12
+ if (!isWin)
13
+ return;
14
+ const link = path.join(os.homedir(), 'Desktop', 'Visual Studio Code.lnk');
15
+ const q = (s) => "'" + s.replace(/'/g, "''") + "'";
16
+ powershell('$s = (New-Object -ComObject WScript.Shell).CreateShortcut(' + q(link) + '); $s.TargetPath = ' + q(exe) + '; $s.Arguments = ' + q('"' + (process.env.AGENT_WORKSPACE ?? '') + '"') + '; $s.Save()');
17
+ };
18
+ export const setupVscode = async (env, refresh) => {
19
+ const root = tool('vscode');
20
+ const exe = path.join(root, 'Code.exe');
21
+ if (dryRun) {
22
+ log('(dry run) would provision VS Code');
23
+ return;
24
+ }
25
+ if (!exists(exe) || refresh) {
26
+ const release = await fetchJson('https://update.code.visualstudio.com/api/update/win32-' + (arch === 'arm64' ? 'arm64' : 'x64') + '-archive/stable/latest');
27
+ log('Downloading VS Code ' + release.productVersion + ' (~150 MB once) ...');
28
+ // Keep settings/extensions across a refresh: move the portable data folder aside and back, also when the install
29
+ // fails (finally). A folder still parked from an interrupted earlier run is the one to keep.
30
+ const keep = path.join(root, 'data');
31
+ const parked = root + '-data';
32
+ if (exists(keep)) {
33
+ fs.rmSync(parked, { recursive: true, force: true });
34
+ fs.renameSync(keep, parked);
35
+ }
36
+ try {
37
+ await installArchive(release.url, root, { sha256: release.sha256hash, locate: 'Code.exe', fileName: 'vscode.zip' });
38
+ fs.writeFileSync(path.join(root, VERSION_MARKER), release.productVersion);
39
+ }
40
+ finally {
41
+ if (exists(parked)) {
42
+ mkdirp(root);
43
+ fs.rmSync(keep, { recursive: true, force: true });
44
+ fs.renameSync(parked, keep);
45
+ }
46
+ }
47
+ }
48
+ mkdirp(path.join(root, 'data'));
49
+ env.prepend.push(path.join(root, 'bin'));
50
+ desktopShortcut(exe);
51
+ const version = exists(path.join(root, VERSION_MARKER)) ? fs.readFileSync(path.join(root, VERSION_MARKER), 'utf8').trim() : 'installed';
52
+ env.summary.push('vscode: ' + version + ' (portable; run code . or use the desktop shortcut)');
53
+ };
@@ -0,0 +1 @@
1
+ export declare const viewSession: (id: string) => Promise<number>;
@@ -0,0 +1,42 @@
1
+ // A terminal tab inside the sandbox that shows one agent session (same pseudo console the host can attach to).
2
+ // Usage: node view.js <sessionId>
3
+ import { connect, runTerminalClient } from '../shared/pty-client.js';
4
+ import { readSession } from '../shared/rundir.js';
5
+ import { dataDir } from './env.js';
6
+ /** A session that is still being set up (its project provisioning, its agent installing): show the progress until it runs. */
7
+ const waitUntilStarted = async (id) => {
8
+ let last = '';
9
+ for (let i = 0; i < 3600; i++) {
10
+ const s = readSession(dataDir, id);
11
+ if (s && s.phase !== 'starting')
12
+ return s;
13
+ if (s && s.message !== last) {
14
+ console.log(' ' + s.message);
15
+ last = s.message;
16
+ }
17
+ await new Promise((r) => setTimeout(r, 500));
18
+ }
19
+ return readSession(dataDir, id);
20
+ };
21
+ export const viewSession = async (id) => {
22
+ const session = await waitUntilStarted(id);
23
+ if (session?.phase === 'failed') {
24
+ console.error('Session ' + id + ' could not start: ' + session.message);
25
+ await new Promise((r) => setTimeout(r, 30000));
26
+ return 1;
27
+ }
28
+ if (!session?.attach) {
29
+ console.error('Session ' + id + ' is not running.');
30
+ return 1;
31
+ }
32
+ const socket = await connect({ ...session.attach, addresses: ['127.0.0.1'] });
33
+ const result = await runTerminalClient(socket, session.attach.token);
34
+ if (result.reason === 'exit')
35
+ console.log(session.harness + ' exited with code ' + result.exitCode + '.');
36
+ else if (result.reason === 'error')
37
+ console.log('Session error: ' + result.message);
38
+ return result.reason === 'exit' ? result.exitCode : 1;
39
+ };
40
+ if (process.argv[1]?.endsWith('view.js') && process.argv[2]) {
41
+ viewSession(process.argv[2]).then((code) => { process.exitCode = code; }, (e) => { console.error(e instanceof Error ? e.message : String(e)); process.exitCode = 1; });
42
+ }
@@ -0,0 +1,32 @@
1
+ export interface Account {
2
+ name: string;
3
+ savedAt: string;
4
+ email: string | null;
5
+ credentials: unknown;
6
+ oauthAccount: unknown;
7
+ }
8
+ export interface AccountRow {
9
+ name: string;
10
+ email: string | null;
11
+ isDefault: boolean;
12
+ isActive: boolean;
13
+ savedAt: string;
14
+ }
15
+ export declare const accountsDir: (storageRoot: string) => string;
16
+ export declare const sandboxClaudeDir: (dataPath: string) => string;
17
+ export declare const readAccount: (storageRoot: string, name: string) => Account | null;
18
+ export declare const defaultAccount: (storageRoot: string) => string | null;
19
+ export declare const activeAccount: (dataPath: string) => string | null;
20
+ export declare const listAccounts: (storageRoot: string, dataPath: string) => AccountRow[];
21
+ /** Saves the sandbox's current login (or this PC's, with fromHost) under `name`. */
22
+ export declare const saveAccount: (storageRoot: string, dataPath: string, name: string, fromHost: {
23
+ claudeDir: string;
24
+ globalStateFile: string;
25
+ } | null) => Account;
26
+ /**
27
+ * Makes `name` the sandbox's login. Claude refreshes (rotates) tokens while it runs, so the outgoing account's
28
+ * current tokens are saved back first; otherwise its stored refresh token could already be invalid.
29
+ */
30
+ export declare const activateAccount: (storageRoot: string, dataPath: string, name: string) => Account;
31
+ export declare const setDefaultAccount: (storageRoot: string, name: string | null) => void;
32
+ export declare const removeAccount: (storageRoot: string, name: string) => void;
@@ -0,0 +1,128 @@
1
+ // Several Claude logins for the sandbox. Each account is a saved copy of Claude's .credentials.json plus the
2
+ // `oauthAccount` block of .claude.json, kept in host-only storage (<storage>\accounts, never mapped into a sandbox).
3
+ // Switching writes them into the sandbox's persistent Claude state; when the sandbox is running the agent is then
4
+ // restarted in place with --continue so the conversation carries on.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { z } from 'zod';
8
+ import { isSafeFileName } from '../shared/paths.js';
9
+ import { assertTestWritable } from './test-guard.js';
10
+ import { readValid, writeJsonAtomic } from '../shared/rundir.js';
11
+ import { assertNoLinks, fail, fileExists, isLink, removeLink, tryReadJson, writeJson } from '../core/util.js';
12
+ const NAME = /^[A-Za-z0-9_-]{1,40}$/;
13
+ const AccountSchema = z.object({ name: z.string().regex(NAME), savedAt: z.string().max(64), email: z.string().max(200).nullable(), credentials: z.looseObject({}), oauthAccount: z.unknown() });
14
+ const IndexSchema = z.object({ default: z.string().regex(NAME).optional(), active: z.record(z.string().max(200), z.string().regex(NAME)).optional() });
15
+ const ACCOUNT_CACHES = ['remote-settings.json', 'policy-limits.json', 'policy-limits.json.stamp.json'];
16
+ const MAX_LOGIN_BYTES = 1024 * 1024;
17
+ /** .claude.json holds per-project state and can be large; it is still capped so a hostile file cannot exhaust memory. */
18
+ const MAX_STATE_BYTES = 256 * 1024 * 1024;
19
+ export const accountsDir = (storageRoot) => path.join(storageRoot, 'accounts');
20
+ export const sandboxClaudeDir = (dataPath) => path.join(dataPath, 'state', 'claude');
21
+ const storageOf = (dataPath) => path.dirname(path.dirname(dataPath));
22
+ const dataName = (dataPath) => path.basename(dataPath);
23
+ const accountFile = (storageRoot, name) => path.join(accountsDir(storageRoot), name + '.json');
24
+ const indexFile = (storageRoot) => path.join(accountsDir(storageRoot), 'index.json');
25
+ const readIndex = (storageRoot) => readValid(IndexSchema, indexFile(storageRoot)) ?? {};
26
+ const updateIndex = (storageRoot, change) => writeJsonAtomic(indexFile(storageRoot), change(readIndex(storageRoot)));
27
+ /** A file name of its own: not "index" (the index file), not a Windows device name such as con or nul. */
28
+ const validName = (name) => NAME.test(name) && name.toLowerCase() !== 'index' && isSafeFileName(name + '.json');
29
+ const assertName = (name) => { if (!validName(name))
30
+ fail('Invalid account name "' + name + '".', 'Use letters, numbers, - and _ only (max 40), e.g. work or personal.'); };
31
+ const emailOf = (oauthAccount) => {
32
+ const email = oauthAccount?.emailAddress;
33
+ return typeof email === 'string' ? email.slice(0, 200) : null;
34
+ };
35
+ export const readAccount = (storageRoot, name) => (validName(name) ? readValid(AccountSchema, accountFile(storageRoot, name)) : null);
36
+ export const defaultAccount = (storageRoot) => readIndex(storageRoot).default ?? null;
37
+ export const activeAccount = (dataPath) => readIndex(storageOf(dataPath)).active?.[dataName(dataPath)] ?? null;
38
+ const setActive = (dataPath, name) => updateIndex(storageOf(dataPath), (i) => ({ ...i, active: { ...i.active, [dataName(dataPath)]: name } }));
39
+ export const listAccounts = (storageRoot, dataPath) => {
40
+ const dir = accountsDir(storageRoot);
41
+ if (!fs.existsSync(dir))
42
+ return [];
43
+ const def = defaultAccount(storageRoot);
44
+ const active = activeAccount(dataPath);
45
+ return fs.readdirSync(dir)
46
+ .filter((f) => f.endsWith('.json') && f !== 'index.json')
47
+ .map((f) => readAccount(storageRoot, f.slice(0, -5)))
48
+ .filter((a) => a !== null)
49
+ .map((a) => ({ name: a.name, email: a.email, isDefault: a.name === def, isActive: a.name === active, savedAt: a.savedAt }));
50
+ };
51
+ /** Reads a JSON file that may be sandbox-written: no links, size-capped. */
52
+ const readUntrusted = (file, max) => (fileExists(file) && !isLink(file) && fs.statSync(file).size <= max ? tryReadJson(file) : null);
53
+ /** The login currently in a Claude config folder (+ its global state file). */
54
+ const readLogin = (claudeDir, globalStateFile) => {
55
+ const credentials = readUntrusted(path.join(claudeDir, '.credentials.json'), MAX_LOGIN_BYTES);
56
+ if (!credentials || typeof credentials !== 'object')
57
+ fail('No Claude login found in ' + claudeDir + '.', 'Sign in to Claude first (inside the sandbox, or on this PC with --from-host), then save it again.');
58
+ return { credentials, oauthAccount: readUntrusted(globalStateFile, MAX_STATE_BYTES)?.oauthAccount ?? null };
59
+ };
60
+ const store = (storageRoot, name, login) => {
61
+ const account = { name, savedAt: new Date().toISOString(), email: emailOf(login.oauthAccount), ...login };
62
+ writeJsonAtomic(accountFile(storageRoot, name), account);
63
+ return account;
64
+ };
65
+ const sandboxLogin = (dataPath) => {
66
+ assertNoLinks(dataPath, sandboxClaudeDir(dataPath));
67
+ return { claudeDir: sandboxClaudeDir(dataPath), globalStateFile: path.join(sandboxClaudeDir(dataPath), '.claude.json') };
68
+ };
69
+ /** Saves the sandbox's current login (or this PC's, with fromHost) under `name`. */
70
+ export const saveAccount = (storageRoot, dataPath, name, fromHost) => {
71
+ assertName(name);
72
+ const source = fromHost ?? sandboxLogin(dataPath);
73
+ const account = store(storageRoot, name, readLogin(source.claudeDir, source.globalStateFile));
74
+ if (!fromHost)
75
+ setActive(dataPath, name);
76
+ if (!defaultAccount(storageRoot))
77
+ setDefaultAccount(storageRoot, name);
78
+ return account;
79
+ };
80
+ /** Replaces a file that may be a sandbox-planted link instead of writing through it. */
81
+ const writeSandboxJson = (file, value) => {
82
+ assertTestWritable(file);
83
+ if (isLink(file))
84
+ removeLink(file);
85
+ writeJson(file, value);
86
+ };
87
+ /**
88
+ * Makes `name` the sandbox's login. Claude refreshes (rotates) tokens while it runs, so the outgoing account's
89
+ * current tokens are saved back first; otherwise its stored refresh token could already be invalid.
90
+ */
91
+ export const activateAccount = (storageRoot, dataPath, name) => {
92
+ const account = readAccount(storageRoot, name) ?? fail('Unknown account "' + name + '".', 'Saved accounts: ' + (listAccounts(storageRoot, dataPath).map((a) => a.name).join(', ') || 'none yet; save one with sandbox account save <name>'));
93
+ const { claudeDir, globalStateFile } = sandboxLogin(dataPath);
94
+ const outgoing = activeAccount(dataPath);
95
+ if (outgoing && outgoing !== name && readAccount(storageRoot, outgoing) && fileExists(path.join(claudeDir, '.credentials.json'))) {
96
+ try {
97
+ store(storageRoot, outgoing, readLogin(claudeDir, globalStateFile));
98
+ }
99
+ catch { /* nothing usable to save back */ }
100
+ }
101
+ fs.mkdirSync(claudeDir, { recursive: true });
102
+ // Caches tied to the previous account (Claude deletes these on /logout); otherwise its org policy could linger.
103
+ for (const cache of ACCOUNT_CACHES) {
104
+ const file = path.join(claudeDir, cache);
105
+ if (isLink(file))
106
+ removeLink(file);
107
+ else
108
+ fs.rmSync(file, { force: true });
109
+ }
110
+ writeSandboxJson(path.join(claudeDir, '.credentials.json'), account.credentials);
111
+ const state = readUntrusted(globalStateFile, MAX_STATE_BYTES);
112
+ if (account.oauthAccount && (state || !fileExists(globalStateFile)))
113
+ writeSandboxJson(globalStateFile, { ...(state ?? {}), oauthAccount: account.oauthAccount });
114
+ setActive(dataPath, name);
115
+ return account;
116
+ };
117
+ export const setDefaultAccount = (storageRoot, name) => {
118
+ if (name !== null && !readAccount(storageRoot, name))
119
+ fail('Unknown account "' + name + '".', 'See your accounts with: sandbox account list');
120
+ updateIndex(storageRoot, (i) => ({ ...i, default: name ?? undefined }));
121
+ };
122
+ export const removeAccount = (storageRoot, name) => {
123
+ if (!readAccount(storageRoot, name))
124
+ fail('Unknown account "' + name + '".', 'See your accounts with: sandbox account list');
125
+ fs.rmSync(accountFile(storageRoot, name));
126
+ if (defaultAccount(storageRoot) === name)
127
+ setDefaultAccount(storageRoot, null);
128
+ };
@@ -0,0 +1,25 @@
1
+ import type { Config } from '../core/types.js';
2
+ export declare const commandsDir: (storageRoot: string) => string;
3
+ export interface AgentCommand {
4
+ harness: string;
5
+ command: string;
6
+ real: string;
7
+ }
8
+ /** The agent's own executable on this PC, skipping this tool's forwarding files. */
9
+ export declare const realAgent: (command: string, skipDir: string, pathValue?: string) => string | null;
10
+ /** The agents that get a forwarding command: installed on this PC, and listed (or "auto" = all of them). */
11
+ export declare const agentCommands: (config: Config, storageRoot: string, pathValue?: string) => AgentCommand[];
12
+ /** The .cmd file for one agent (cmd, PowerShell and Windows Terminal all run it). */
13
+ export declare const forwardingCmd: (a: AgentCommand, node: string, entry: string) => string;
14
+ /** The same for Git Bash, which looks for a file without an extension. */
15
+ export declare const forwardingSh: (a: AgentCommand, node: string, entry: string) => string;
16
+ export interface Reconciled {
17
+ on: boolean;
18
+ commands: AgentCommand[];
19
+ changed: boolean;
20
+ }
21
+ /**
22
+ * Makes this PC match agentCommands: on = a forwarding file per installed agent and the folder first on your PATH;
23
+ * off = no files, no PATH entry. Only touches files it wrote and its own PATH entry; does nothing when already right.
24
+ */
25
+ export declare const reconcileAgentCommands: (config: Config, storageRoot: string, node: string, entry: string) => Reconciled;
@@ -0,0 +1,145 @@
1
+ // (Beta) Agent commands: with agentCommands.enabled, typing an agent's own command on this PC (claude, codex, ...) starts
2
+ // it in a hidden sandbox for the folder you are in and attaches that terminal. Small forwarding files in
3
+ // <storage>\agent-commands go first on YOUR PATH (so they win over npm's and the installers' copies); turning it off
4
+ // removes the files and that one PATH entry. SANDBOX_AGENT_DIRECT=1 runs the real agent on this PC instead.
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { spawnSync } from 'node:child_process';
8
+ import { ensureDir, fileExists } from '../core/util.js';
9
+ import { writeFileAtomic } from '../shared/rundir.js';
10
+ import { safeRemove } from '../shared/safe-remove.js';
11
+ import { SYSTEM } from '../shared/system.js';
12
+ import { refuseInSelfTest } from './test-guard.js';
13
+ export const commandsDir = (storageRoot) => path.join(storageRoot, 'agent-commands');
14
+ /** Marks files this tool wrote, so only those are ever replaced or removed. */
15
+ const MARKER = 'windows-development-sandbox agent command';
16
+ const EXTENSIONS = ['.exe', '.cmd', '.bat'];
17
+ /** cmd.exe expands % and ^ even inside quotes: such paths cannot go into a forwarding file safely. */
18
+ const cmdSafe = (p) => !/[%"^\r\n]/.test(p);
19
+ /** The agent's own executable on this PC, skipping this tool's forwarding files. */
20
+ export const realAgent = (command, skipDir, pathValue = process.env.PATH ?? process.env.Path ?? '') => {
21
+ const skip = path.resolve(skipDir).toLowerCase();
22
+ const dirs = pathValue.split(path.delimiter).filter((d) => d && path.resolve(d).toLowerCase() !== skip);
23
+ for (const dir of dirs)
24
+ for (const ext of EXTENSIONS) {
25
+ const candidate = path.join(dir, command + ext);
26
+ if (fileExists(candidate))
27
+ return candidate;
28
+ }
29
+ return null;
30
+ };
31
+ /** The agents that get a forwarding command: installed on this PC, and listed (or "auto" = all of them). */
32
+ export const agentCommands = (config, storageRoot, pathValue) => {
33
+ const wanted = config.agentCommands.agents;
34
+ return Object.entries(config.harnesses).flatMap(([harness, h]) => {
35
+ if (wanted !== 'auto' && !wanted.includes(harness))
36
+ return [];
37
+ const command = h.npmBin ?? (h.hostExecutable ? path.parse(h.hostExecutable).name : harness);
38
+ const real = /^[A-Za-z0-9._-]+$/.test(command) ? realAgent(command, commandsDir(storageRoot), pathValue) : null;
39
+ return real && cmdSafe(real) ? [{ harness, command, real }] : [];
40
+ });
41
+ };
42
+ /** The .cmd file for one agent (cmd, PowerShell and Windows Terminal all run it). */
43
+ export const forwardingCmd = (a, node, entry) => [
44
+ '@echo off',
45
+ 'rem ' + MARKER + ' (Beta). Turn off: sandbox agent-commands off. Run the real one: set SANDBOX_AGENT_DIRECT=1',
46
+ 'if "%SANDBOX_AGENT_DIRECT%"=="1" goto direct',
47
+ '"' + node + '" "' + entry + '" launch --harness ' + a.harness + ' --headless --terminal -- %*',
48
+ 'exit /b %errorlevel%',
49
+ ':direct',
50
+ 'call "' + a.real + '" %*',
51
+ '',
52
+ ].join('\r\n');
53
+ /** The same for Git Bash, which looks for a file without an extension. */
54
+ export const forwardingSh = (a, node, entry) => {
55
+ const posix = (p) => p.replace(/^([A-Za-z]):/, (_m, d) => '/' + d.toLowerCase()).replace(/\\/g, '/');
56
+ const q = (s) => "'" + s.replace(/'/g, "'\\''") + "'";
57
+ return [
58
+ '#!/bin/sh',
59
+ '# ' + MARKER + ' (Beta). Turn off: sandbox agent-commands off. Run the real one: SANDBOX_AGENT_DIRECT=1',
60
+ 'if [ "$SANDBOX_AGENT_DIRECT" = "1" ]; then exec ' + q(posix(a.real)) + ' "$@"; fi',
61
+ 'exec ' + q(posix(node)) + ' ' + q(posix(entry)) + ' launch --harness ' + a.harness + ' --headless --terminal -- "$@"',
62
+ '',
63
+ ].join('\n');
64
+ };
65
+ /** Runs a PowerShell script that edits YOUR PATH (HKCU\Environment) unexpanded, then tells Windows it changed. */
66
+ const editUserPath = (dir, add) => {
67
+ refuseInSelfTest('change your PATH');
68
+ const quoted = "'" + dir.replace(/'/g, "''") + "'";
69
+ const script = [
70
+ '$dir = ' + quoted,
71
+ "$key = [Microsoft.Win32.Registry]::CurrentUser.CreateSubKey('Environment')",
72
+ 'try {',
73
+ " $raw = [string]$key.GetValue('Path', '', [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)",
74
+ " $rest = @($raw -split ';' | Where-Object { $_ -and $_.TrimEnd('\\') -ine $dir.TrimEnd('\\') })",
75
+ // First, so these commands win over the agents' own folders (also on YOUR PATH).
76
+ add ? ' $new = (@($dir) + $rest) -join \';\'' : " $new = $rest -join ';'",
77
+ ' if ($new -eq $raw.TrimEnd(\';\')) { exit 0 }',
78
+ " if ($new.Length -gt 32000) { Write-Error 'PATH too long'; exit 3 }",
79
+ " $kind = if ($key.GetValueNames() -contains 'Path') { $key.GetValueKind('Path') } else { [Microsoft.Win32.RegistryValueKind]::ExpandString }",
80
+ " $key.SetValue('Path', $new, $kind)",
81
+ '} finally { $key.Close() }',
82
+ "Add-Type -Namespace WdsAgents -Name Native -MemberDefinition '[System.Runtime.InteropServices.DllImport(\"user32.dll\", CharSet = System.Runtime.InteropServices.CharSet.Auto)] public static extern System.IntPtr SendMessageTimeout(System.IntPtr hWnd, uint msg, System.UIntPtr wParam, string lParam, uint flags, uint timeout, out System.UIntPtr result);'",
83
+ '$ignored = [UIntPtr]::Zero',
84
+ "[void][WdsAgents.Native]::SendMessageTimeout([IntPtr]0xffff, 0x1A, [UIntPtr]::Zero, 'Environment', 2, 5000, [ref]$ignored)",
85
+ ].join('\n');
86
+ const result = spawnSync(SYSTEM.powershell(), ['-NoProfile', '-NonInteractive', '-OutputFormat', 'Text', '-Command', script], { encoding: 'utf8', windowsHide: true });
87
+ if (result.status === 3)
88
+ throw new Error('Your PATH is too long to add ' + dir + '.');
89
+ if (result.status !== 0)
90
+ throw new Error('Could not ' + (add ? 'add ' + dir + ' to' : 'remove ' + dir + ' from') + ' your PATH: ' + (result.stderr || result.error?.message || 'exit ' + result.status).trim());
91
+ };
92
+ const ownFiles = (dir) => fs.existsSync(dir)
93
+ ? fs.readdirSync(dir).map((f) => path.join(dir, f)).filter((f) => { try {
94
+ return fs.lstatSync(f).isFile() && fs.readFileSync(f, 'utf8').includes(MARKER);
95
+ }
96
+ catch {
97
+ return false;
98
+ } })
99
+ : [];
100
+ /**
101
+ * Makes this PC match agentCommands: on = a forwarding file per installed agent and the folder first on your PATH;
102
+ * off = no files, no PATH entry. Only touches files it wrote and its own PATH entry; does nothing when already right.
103
+ */
104
+ export const reconcileAgentCommands = (config, storageRoot, node, entry) => {
105
+ const dir = commandsDir(storageRoot);
106
+ if (!config.agentCommands.enabled) {
107
+ if (!fs.existsSync(dir))
108
+ return { on: false, commands: [], changed: false };
109
+ for (const f of ownFiles(dir))
110
+ fs.rmSync(f, { force: true });
111
+ editUserPath(dir, false);
112
+ if (fs.readdirSync(dir).length === 0)
113
+ safeRemove(dir, storageRoot);
114
+ return { on: false, commands: [], changed: true };
115
+ }
116
+ if (!cmdSafe(node) || !cmdSafe(entry))
117
+ throw new Error('This tool is installed in a folder whose path has % " or ^, which cannot go into a command file safely.');
118
+ const commands = agentCommands(config, storageRoot);
119
+ ensureDir(dir);
120
+ const wanted = new Map();
121
+ for (const a of commands) {
122
+ wanted.set(path.join(dir, a.command + '.cmd'), forwardingCmd(a, node, entry));
123
+ wanted.set(path.join(dir, a.command), forwardingSh(a, node, entry));
124
+ }
125
+ let changed = false;
126
+ for (const f of ownFiles(dir))
127
+ if (!wanted.has(f)) {
128
+ fs.rmSync(f, { force: true });
129
+ changed = true;
130
+ }
131
+ for (const [file, text] of wanted) {
132
+ const current = fileExists(file) ? fs.readFileSync(file, 'utf8') : null;
133
+ // A file of the same name that this tool did not write is never replaced.
134
+ if (current !== null && !current.includes(MARKER))
135
+ continue;
136
+ if (current !== text) {
137
+ writeFileAtomic(file, text);
138
+ changed = true;
139
+ }
140
+ }
141
+ const onPath = (process.env.PATH ?? '').split(path.delimiter).some((d) => path.resolve(d).toLowerCase() === path.resolve(dir).toLowerCase());
142
+ if (changed || !onPath)
143
+ editUserPath(dir, true);
144
+ return { on: true, commands, changed: changed || !onPath };
145
+ };
@@ -0,0 +1,17 @@
1
+ import os from 'node:os';
2
+ import type { SessionStatus } from '../core/types.js';
3
+ import { type Viewers } from '../shared/pty-client.js';
4
+ /**
5
+ * True only for IPv4 addresses inside the subnet of one of this PC's Hyper-V adapters ("vEthernet ..."), which is
6
+ * where Windows Sandbox lives. Stops a compromised sandbox from pointing the host (and your keystrokes) anywhere else.
7
+ */
8
+ export declare const isSandboxAddress: (ip: string, interfaces?: NodeJS.Dict<os.NetworkInterfaceInfo[]>) => boolean;
9
+ /** Who is watching a session right now (closed terminals do not count); null when it cannot be asked. */
10
+ export declare const sessionViewers: (session: SessionStatus) => Promise<Viewers | null>;
11
+ /**
12
+ * The newest running session of this agent in exactly this folder that no terminal on this PC is attached to, so
13
+ * sandbox claude -t picks it back up instead of starting another. A session that cannot answer is never reused.
14
+ */
15
+ export declare const findUnwatchedSession: (dataPath: string, harness: string, cwd: string) => Promise<SessionStatus | null>;
16
+ /** Bridges this terminal to a session. Returns the agent's exit code, or 0 when the user detaches. */
17
+ export declare const attachTerminal: (session: SessionStatus) => Promise<number>;