windows-development-sandbox 1.0.5 → 1.4.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 (453) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -8
  3. package/config.example.json +55 -5
  4. package/dist/api.js +2 -1
  5. package/dist/cli/args.d.ts +23 -0
  6. package/dist/cli/args.js +38 -4
  7. package/dist/cli/colors.d.ts +24 -0
  8. package/dist/cli/colors.js +42 -0
  9. package/dist/cli/commands/accounts.js +2 -2
  10. package/dist/cli/commands/agent-commands.d.ts +6 -0
  11. package/dist/cli/commands/agent-commands.js +61 -0
  12. package/dist/cli/commands/context.d.ts +2 -0
  13. package/dist/cli/commands/context.js +5 -1
  14. package/dist/cli/commands/data/clean.d.ts +15 -0
  15. package/dist/cli/commands/data/clean.js +106 -0
  16. package/dist/cli/commands/data/collect.d.ts +11 -0
  17. package/dist/cli/commands/data/collect.js +54 -0
  18. package/dist/cli/commands/data/index.d.ts +4 -0
  19. package/dist/cli/commands/data/index.js +45 -0
  20. package/dist/cli/commands/data/remove.d.ts +2 -0
  21. package/dist/cli/commands/data/remove.js +30 -0
  22. package/dist/cli/commands/index.d.ts +6 -4
  23. package/dist/cli/commands/index.js +6 -4
  24. package/dist/cli/commands/launch.d.ts +26 -2
  25. package/dist/cli/commands/launch.js +256 -42
  26. package/dist/cli/commands/maintenance.d.ts +2 -0
  27. package/dist/cli/commands/maintenance.js +67 -5
  28. package/dist/cli/commands/project.d.ts +29 -0
  29. package/dist/cli/commands/project.js +63 -0
  30. package/dist/cli/commands/projects.d.ts +14 -0
  31. package/dist/cli/commands/projects.js +146 -0
  32. package/dist/cli/commands/repair/checks.d.ts +2 -0
  33. package/dist/cli/commands/{repair.js → repair/checks.js} +19 -181
  34. package/dist/cli/commands/{repair.d.ts → repair/finders.d.ts} +0 -29
  35. package/dist/cli/commands/repair/finders.js +108 -0
  36. package/dist/cli/commands/repair/index.d.ts +12 -0
  37. package/dist/cli/commands/repair/index.js +61 -0
  38. package/dist/cli/commands/repair/types.d.ts +26 -0
  39. package/dist/cli/commands/repair/types.js +3 -0
  40. package/dist/cli/commands/restart.d.ts +6 -0
  41. package/dist/cli/commands/restart.js +85 -0
  42. package/dist/cli/commands/sessions.d.ts +2 -1
  43. package/dist/cli/commands/sessions.js +189 -45
  44. package/dist/cli/commands/setup/checks.d.ts +13 -0
  45. package/dist/cli/commands/setup/checks.js +71 -0
  46. package/dist/cli/commands/{setup.d.ts → setup/facts.d.ts} +1 -15
  47. package/dist/cli/commands/setup/facts.js +60 -0
  48. package/dist/cli/commands/setup/index.d.ts +2 -0
  49. package/dist/cli/commands/setup/index.js +43 -0
  50. package/dist/cli/commands/user-config.d.ts +8 -0
  51. package/dist/cli/commands/user-config.js +41 -0
  52. package/dist/cli/history.d.ts +11 -0
  53. package/dist/cli/history.js +21 -0
  54. package/dist/cli/index.d.ts +7 -1
  55. package/dist/cli/index.js +42 -15
  56. package/dist/cli/manifest.d.ts +8 -0
  57. package/dist/cli/manifest.js +40 -15
  58. package/dist/cli/menu/apps.d.ts +21 -0
  59. package/dist/cli/menu/apps.js +122 -0
  60. package/dist/cli/menu/command-form.d.ts +28 -0
  61. package/dist/cli/menu/command-form.js +219 -0
  62. package/dist/cli/menu/index.d.ts +26 -0
  63. package/dist/cli/menu/index.js +180 -0
  64. package/dist/cli/menu/settings/categories.d.ts +9 -0
  65. package/dist/cli/menu/settings/categories.js +28 -0
  66. package/dist/cli/menu/settings/config-file.d.ts +40 -0
  67. package/dist/cli/menu/settings/config-file.js +71 -0
  68. package/dist/cli/menu/settings/editors.d.ts +30 -0
  69. package/dist/cli/menu/settings/editors.js +305 -0
  70. package/dist/cli/menu/settings/index.d.ts +32 -0
  71. package/dist/cli/menu/settings/index.js +136 -0
  72. package/dist/cli/menu/settings/schema-node.d.ts +62 -0
  73. package/dist/cli/menu/settings/schema-node.js +104 -0
  74. package/dist/cli/menu/several.d.ts +20 -0
  75. package/dist/cli/menu/several.js +102 -0
  76. package/dist/cli/progress.d.ts +9 -0
  77. package/dist/cli/progress.js +49 -0
  78. package/dist/cli/tui/app.d.ts +39 -0
  79. package/dist/cli/tui/app.js +52 -0
  80. package/dist/cli/tui/dialogs.d.ts +57 -0
  81. package/dist/cli/tui/dialogs.js +162 -0
  82. package/dist/cli/tui/frame.d.ts +18 -0
  83. package/dist/cli/tui/frame.js +27 -0
  84. package/dist/cli/tui/index.d.ts +7 -0
  85. package/dist/cli/tui/index.js +8 -0
  86. package/dist/cli/tui/list-screen.d.ts +101 -0
  87. package/dist/cli/tui/list-screen.js +299 -0
  88. package/dist/cli/tui/terminal.d.ts +37 -0
  89. package/dist/cli/tui/terminal.js +89 -0
  90. package/dist/cli/tui/text.d.ts +20 -0
  91. package/dist/cli/tui/text.js +105 -0
  92. package/dist/cli/tui/theme.d.ts +45 -0
  93. package/dist/cli/tui/theme.js +30 -0
  94. package/dist/cli/validate.d.ts +17 -0
  95. package/dist/cli/validate.js +89 -0
  96. package/dist/core/config.js +12 -6
  97. package/dist/core/disk.d.ts +16 -0
  98. package/dist/core/disk.js +55 -0
  99. package/dist/core/local-config.d.ts +23 -0
  100. package/dist/core/local-config.js +79 -7
  101. package/dist/core/project-policy.d.ts +15 -0
  102. package/dist/core/project-policy.js +35 -0
  103. package/dist/core/prompt.d.ts +4 -0
  104. package/dist/core/prompt.js +15 -0
  105. package/dist/core/safe-paths.d.ts +4 -0
  106. package/dist/core/safe-paths.js +37 -0
  107. package/dist/core/schema/apps.d.ts +40 -0
  108. package/dist/core/schema/apps.js +27 -0
  109. package/dist/core/schema/common.d.ts +17 -0
  110. package/dist/core/schema/common.js +15 -0
  111. package/dist/core/schema/config.d.ts +659 -0
  112. package/dist/core/schema/config.js +75 -0
  113. package/dist/core/schema/databases.d.ts +33 -0
  114. package/dist/core/schema/databases.js +35 -0
  115. package/dist/core/schema/harness.d.ts +12 -0
  116. package/dist/core/schema/harness.js +17 -0
  117. package/dist/core/schema/json-schema.d.ts +5 -0
  118. package/dist/core/schema/json-schema.js +25 -0
  119. package/dist/core/schema/profile.d.ts +32 -0
  120. package/dist/core/schema/profile.js +31 -0
  121. package/dist/core/schema/projects.d.ts +9 -0
  122. package/dist/core/schema/projects.js +11 -0
  123. package/dist/core/schema/sandbox.d.ts +78 -0
  124. package/dist/core/schema/sandbox.js +64 -0
  125. package/dist/core/schema/toolchains.d.ts +96 -0
  126. package/dist/core/schema/toolchains.js +88 -0
  127. package/dist/core/schema.d.ts +6 -604
  128. package/dist/core/schema.js +8 -264
  129. package/dist/core/types.d.ts +35 -1
  130. package/dist/core/util.d.ts +9 -0
  131. package/dist/core/util.js +14 -3
  132. package/dist/core/workspace.d.ts +0 -5
  133. package/dist/core/workspace.js +33 -50
  134. package/dist/guest/apps/index.d.ts +10 -0
  135. package/dist/guest/apps/index.js +206 -0
  136. package/dist/guest/apps/resolvers.d.ts +8 -0
  137. package/dist/guest/apps/resolvers.js +90 -0
  138. package/dist/guest/audit-parse.d.ts +7 -0
  139. package/dist/guest/audit-parse.js +24 -0
  140. package/dist/guest/audit.d.ts +4 -0
  141. package/dist/guest/audit.js +47 -0
  142. package/dist/guest/boot-env.d.ts +4 -0
  143. package/dist/guest/boot-env.js +25 -0
  144. package/dist/guest/cmdline.d.ts +12 -0
  145. package/dist/guest/cmdline.js +23 -0
  146. package/dist/guest/daemon/exec.d.ts +5 -0
  147. package/dist/guest/daemon/exec.js +32 -0
  148. package/dist/guest/daemon/project.d.ts +14 -0
  149. package/dist/guest/daemon/project.js +132 -0
  150. package/dist/guest/daemon/relays.d.ts +1 -0
  151. package/dist/guest/daemon/relays.js +18 -0
  152. package/dist/guest/daemon/session.d.ts +13 -0
  153. package/dist/guest/daemon/session.js +139 -0
  154. package/dist/guest/daemon/shutdown.d.ts +1 -0
  155. package/dist/guest/daemon/shutdown.js +21 -0
  156. package/dist/guest/daemon/state.d.ts +17 -0
  157. package/dist/guest/daemon/state.js +31 -0
  158. package/dist/guest/daemon.js +12 -141
  159. package/dist/guest/db.js +18 -3
  160. package/dist/guest/env.d.ts +3 -0
  161. package/dist/guest/env.js +7 -3
  162. package/dist/guest/guide.d.ts +6 -2
  163. package/dist/guest/guide.js +5 -4
  164. package/dist/guest/helpers.d.ts +3 -0
  165. package/dist/guest/helpers.js +26 -0
  166. package/dist/guest/main.js +31 -8
  167. package/dist/guest/netproxy.d.ts +1 -0
  168. package/dist/guest/netproxy.js +100 -0
  169. package/dist/guest/ports.d.ts +6 -0
  170. package/dist/guest/ports.js +22 -0
  171. package/dist/guest/project-context.d.ts +7 -0
  172. package/dist/guest/project-context.js +10 -0
  173. package/dist/guest/project-env.d.ts +20 -0
  174. package/dist/guest/project-env.js +20 -0
  175. package/dist/guest/project-status.d.ts +5 -0
  176. package/dist/guest/project-status.js +17 -0
  177. package/dist/guest/ptyhost.d.ts +0 -18
  178. package/dist/guest/ptyhost.js +17 -65
  179. package/dist/guest/setup/agents.d.ts +5 -2
  180. package/dist/guest/setup/agents.js +22 -8
  181. package/dist/guest/setup/claude.d.ts +0 -4
  182. package/dist/guest/setup/claude.js +59 -5
  183. package/dist/guest/setup/environment.d.ts +13 -3
  184. package/dist/guest/setup/environment.js +14 -10
  185. package/dist/guest/setup/hooks.d.ts +9 -1
  186. package/dist/guest/setup/hooks.js +28 -14
  187. package/dist/guest/setup/network.d.ts +9 -0
  188. package/dist/guest/setup/network.js +97 -4
  189. package/dist/guest/setup/user.js +4 -0
  190. package/dist/guest/toolchains/catalogue.d.ts +4 -0
  191. package/dist/guest/toolchains/catalogue.js +2 -2
  192. package/dist/guest/toolchains/cli.js +25 -17
  193. package/dist/guest/toolchains/common.d.ts +18 -1
  194. package/dist/guest/toolchains/common.js +34 -11
  195. package/dist/guest/toolchains/databases/dynamodb.d.ts +8 -0
  196. package/dist/guest/toolchains/databases/dynamodb.js +52 -0
  197. package/dist/guest/toolchains/{databases.d.ts → databases/index.d.ts} +2 -3
  198. package/dist/guest/toolchains/databases/index.js +62 -0
  199. package/dist/guest/toolchains/databases/mongodb.d.ts +3 -0
  200. package/dist/guest/toolchains/databases/mongodb.js +31 -0
  201. package/dist/guest/toolchains/databases/mysql.d.ts +3 -0
  202. package/dist/guest/toolchains/databases/mysql.js +38 -0
  203. package/dist/guest/toolchains/databases/postgres.d.ts +5 -0
  204. package/dist/guest/toolchains/databases/postgres.js +38 -0
  205. package/dist/guest/toolchains/databases/redis.d.ts +3 -0
  206. package/dist/guest/toolchains/databases/redis.js +16 -0
  207. package/dist/guest/toolchains/databases/server.d.ts +14 -0
  208. package/dist/guest/toolchains/databases/server.js +52 -0
  209. package/dist/guest/toolchains/git.js +25 -16
  210. package/dist/guest/toolchains/go.js +1 -1
  211. package/dist/guest/toolchains/index.d.ts +13 -9
  212. package/dist/guest/toolchains/index.js +40 -82
  213. package/dist/guest/toolchains/java.js +5 -5
  214. package/dist/guest/toolchains/leftovers.d.ts +2 -0
  215. package/dist/guest/toolchains/leftovers.js +26 -0
  216. package/dist/guest/toolchains/managers.js +12 -10
  217. package/dist/guest/toolchains/{binstall.js → native/binstall.js} +6 -9
  218. package/dist/guest/toolchains/native/cpp.d.ts +4 -0
  219. package/dist/guest/toolchains/native/cpp.js +38 -0
  220. package/dist/guest/toolchains/native/dotnet.d.ts +4 -0
  221. package/dist/guest/toolchains/native/dotnet.js +103 -0
  222. package/dist/guest/toolchains/native/mesa.d.ts +7 -0
  223. package/dist/guest/toolchains/native/mesa.js +56 -0
  224. package/dist/guest/toolchains/native/msvc/environment.d.ts +16 -0
  225. package/dist/guest/toolchains/native/msvc/environment.js +57 -0
  226. package/dist/guest/toolchains/native/msvc/index.d.ts +6 -0
  227. package/dist/guest/toolchains/native/msvc/index.js +48 -0
  228. package/dist/guest/toolchains/native/msvc/packages.d.ts +6 -0
  229. package/dist/guest/toolchains/native/msvc/packages.js +156 -0
  230. package/dist/guest/toolchains/native/msvc/tree.d.ts +15 -0
  231. package/dist/guest/toolchains/native/msvc/tree.js +21 -0
  232. package/dist/guest/toolchains/native/rust.d.ts +3 -0
  233. package/dist/guest/toolchains/native/rust.js +45 -0
  234. package/dist/guest/toolchains/node.js +26 -6
  235. package/dist/guest/toolchains/plan.d.ts +24 -0
  236. package/dist/guest/toolchains/plan.js +140 -0
  237. package/dist/guest/toolchains/pwsh.js +1 -1
  238. package/dist/guest/toolchains/python.js +43 -12
  239. package/dist/guest/toolchains/runtimes.d.ts +2 -0
  240. package/dist/guest/toolchains/runtimes.js +6 -8
  241. package/dist/guest/toolchains/scripting/php.d.ts +3 -0
  242. package/dist/guest/toolchains/scripting/php.js +71 -0
  243. package/dist/guest/toolchains/scripting/ruby.d.ts +3 -0
  244. package/dist/guest/toolchains/scripting/ruby.js +47 -0
  245. package/dist/guest/toolchains/scripting/vcruntime.d.ts +7 -0
  246. package/dist/guest/toolchains/scripting/vcruntime.js +45 -0
  247. package/dist/guest/toolchains/vscode-profile.d.ts +30 -0
  248. package/dist/guest/toolchains/vscode-profile.js +69 -0
  249. package/dist/guest/toolchains/vscode.d.ts +17 -0
  250. package/dist/guest/toolchains/vscode.js +87 -1
  251. package/dist/guest/view.js +21 -1
  252. package/dist/host/accounts.js +14 -5
  253. package/dist/host/agent-commands.d.ts +25 -0
  254. package/dist/host/agent-commands.js +145 -0
  255. package/dist/host/attach.d.ts +17 -0
  256. package/dist/host/attach.js +70 -0
  257. package/dist/host/audit/collect.d.ts +34 -0
  258. package/dist/host/audit/collect.js +126 -0
  259. package/dist/host/audit/log.d.ts +46 -0
  260. package/dist/host/audit/log.js +143 -0
  261. package/dist/host/channel.d.ts +23 -22
  262. package/dist/host/channel.js +66 -84
  263. package/dist/host/claude-profile.js +7 -3
  264. package/dist/host/cleanup.js +2 -11
  265. package/dist/host/database-plan.d.ts +12 -0
  266. package/dist/host/database-plan.js +52 -0
  267. package/dist/host/generate.d.ts +10 -12
  268. package/dist/host/generate.js +58 -135
  269. package/dist/host/join.d.ts +28 -0
  270. package/dist/host/join.js +76 -0
  271. package/dist/host/launch/mappings.d.ts +3 -0
  272. package/dist/host/launch/mappings.js +54 -0
  273. package/dist/host/launch/projects.d.ts +15 -0
  274. package/dist/host/launch/projects.js +56 -0
  275. package/dist/host/launch/stage.d.ts +11 -0
  276. package/dist/host/launch/stage.js +66 -0
  277. package/dist/host/launch/wsb.d.ts +10 -0
  278. package/dist/host/launch/wsb.js +34 -0
  279. package/dist/host/motw.d.ts +17 -0
  280. package/dist/host/motw.js +74 -0
  281. package/dist/host/pc-scan.d.ts +6 -0
  282. package/dist/host/pc-scan.js +37 -0
  283. package/dist/host/platform.d.ts +2 -0
  284. package/dist/host/platform.js +16 -1
  285. package/dist/host/port-forward.d.ts +8 -0
  286. package/dist/host/port-forward.js +37 -0
  287. package/dist/host/project-scan.d.ts +1 -17
  288. package/dist/host/project-scan.js +2 -86
  289. package/dist/host/projects.d.ts +100 -0
  290. package/dist/host/projects.js +147 -0
  291. package/dist/host/repo-guard.d.ts +25 -5
  292. package/dist/host/repo-guard.js +242 -23
  293. package/dist/host/stops.d.ts +67 -0
  294. package/dist/host/stops.js +126 -0
  295. package/dist/host/terminal.d.ts +8 -0
  296. package/dist/host/terminal.js +48 -1
  297. package/dist/host/test-guard.d.ts +6 -0
  298. package/dist/host/test-guard.js +15 -0
  299. package/dist/host/window.js +7 -3
  300. package/dist/host/workspace-view/index.d.ts +20 -0
  301. package/dist/host/workspace-view/index.js +53 -0
  302. package/dist/host/workspace-view/rules.d.ts +26 -0
  303. package/dist/host/workspace-view/rules.js +93 -0
  304. package/dist/host/workspace-view/view.d.ts +50 -0
  305. package/dist/host/workspace-view/view.js +281 -0
  306. package/dist/host/wsb.d.ts +37 -0
  307. package/dist/host/wsb.js +101 -0
  308. package/dist/runtime/daemon.js +11791 -2821
  309. package/dist/runtime/db.js +6585 -466
  310. package/dist/runtime/main.js +10238 -1724
  311. package/dist/runtime/netproxy.js +25936 -0
  312. package/dist/runtime/view.js +6183 -59
  313. package/dist/selftest/accounts.test.d.ts +2 -0
  314. package/dist/selftest/accounts.test.js +44 -0
  315. package/dist/selftest/agent-commands.test.d.ts +2 -0
  316. package/dist/selftest/agent-commands.test.js +47 -0
  317. package/dist/selftest/audit.test.d.ts +2 -0
  318. package/dist/selftest/audit.test.js +57 -0
  319. package/dist/selftest/channel.test.d.ts +2 -0
  320. package/dist/selftest/channel.test.js +44 -0
  321. package/dist/selftest/cli.test.d.ts +2 -2
  322. package/dist/selftest/cli.test.js +145 -27
  323. package/dist/selftest/cmdline.test.d.ts +2 -0
  324. package/dist/selftest/cmdline.test.js +45 -0
  325. package/dist/selftest/config.test.d.ts +2 -3
  326. package/dist/selftest/config.test.js +36 -72
  327. package/dist/selftest/data.test.d.ts +2 -2
  328. package/dist/selftest/data.test.js +77 -70
  329. package/dist/selftest/detect.test.d.ts +2 -2
  330. package/dist/selftest/detect.test.js +54 -38
  331. package/dist/selftest/fixtures.d.ts +89 -4
  332. package/dist/selftest/fixtures.js +43 -24
  333. package/dist/selftest/generate.test.d.ts +2 -0
  334. package/dist/selftest/generate.test.js +68 -0
  335. package/dist/selftest/git-access.test.d.ts +2 -0
  336. package/dist/selftest/git-access.test.js +26 -0
  337. package/dist/selftest/guest-boot.test.d.ts +2 -0
  338. package/dist/selftest/guest-boot.test.js +76 -0
  339. package/dist/selftest/harness.d.ts +44 -7
  340. package/dist/selftest/harness.js +62 -21
  341. package/dist/selftest/host-sim.test.d.ts +2 -0
  342. package/dist/selftest/host-sim.test.js +93 -0
  343. package/dist/selftest/hostile-input.test.d.ts +2 -0
  344. package/dist/selftest/hostile-input.test.js +54 -0
  345. package/dist/selftest/index.d.ts +8 -1
  346. package/dist/selftest/index.js +137 -35
  347. package/dist/selftest/links.test.d.ts +2 -0
  348. package/dist/selftest/links.test.js +50 -0
  349. package/dist/selftest/live-daemon.test.d.ts +2 -0
  350. package/dist/selftest/live-daemon.test.js +91 -0
  351. package/dist/selftest/live-git.test.d.ts +2 -0
  352. package/dist/selftest/live-git.test.js +60 -0
  353. package/dist/selftest/live-network.test.d.ts +2 -0
  354. package/dist/selftest/live-network.test.js +63 -0
  355. package/dist/selftest/live-projects.test.d.ts +2 -0
  356. package/dist/selftest/live-projects.test.js +127 -0
  357. package/dist/selftest/live.d.ts +65 -0
  358. package/dist/selftest/live.js +109 -0
  359. package/dist/selftest/local-config.test.d.ts +2 -0
  360. package/dist/selftest/local-config.test.js +120 -0
  361. package/dist/selftest/mirror.test.d.ts +2 -3
  362. package/dist/selftest/mirror.test.js +92 -65
  363. package/dist/selftest/network.test.d.ts +2 -0
  364. package/dist/selftest/network.test.js +40 -0
  365. package/dist/selftest/personalization.test.d.ts +2 -0
  366. package/dist/selftest/personalization.test.js +53 -0
  367. package/dist/selftest/projects.test.d.ts +2 -0
  368. package/dist/selftest/projects.test.js +254 -0
  369. package/dist/selftest/repair.test.d.ts +2 -0
  370. package/dist/selftest/repair.test.js +42 -0
  371. package/dist/selftest/repo-guard.test.d.ts +2 -0
  372. package/dist/selftest/repo-guard.test.js +121 -0
  373. package/dist/selftest/secrets.test.d.ts +2 -0
  374. package/dist/selftest/secrets.test.js +71 -0
  375. package/dist/selftest/setup.test.d.ts +2 -2
  376. package/dist/selftest/setup.test.js +45 -22
  377. package/dist/selftest/suite.d.ts +13 -0
  378. package/dist/selftest/suite.js +1 -0
  379. package/dist/selftest/sync.test.d.ts +2 -3
  380. package/dist/selftest/sync.test.js +130 -78
  381. package/dist/selftest/test-guard.test.d.ts +2 -0
  382. package/dist/selftest/test-guard.test.js +49 -0
  383. package/dist/selftest/toolchains.test.d.ts +2 -0
  384. package/dist/selftest/toolchains.test.js +133 -0
  385. package/dist/selftest/workspace-view.test.d.ts +2 -0
  386. package/dist/selftest/workspace-view.test.js +89 -0
  387. package/dist/selftest/workspace.test.d.ts +2 -0
  388. package/dist/selftest/workspace.test.js +53 -0
  389. package/dist/shared/apps.d.ts +49 -0
  390. package/dist/shared/apps.js +114 -0
  391. package/dist/shared/download.d.ts +3 -1
  392. package/dist/shared/download.js +19 -4
  393. package/dist/shared/netpolicy.d.ts +27 -0
  394. package/dist/shared/netpolicy.js +51 -0
  395. package/dist/shared/paths.d.ts +5 -0
  396. package/dist/shared/paths.js +6 -0
  397. package/dist/shared/projects.d.ts +53 -0
  398. package/dist/shared/projects.js +67 -0
  399. package/dist/shared/pty-client.d.ts +9 -4
  400. package/dist/shared/pty-client.js +139 -17
  401. package/dist/shared/rundir.d.ts +12 -1
  402. package/dist/shared/rundir.js +14 -4
  403. package/dist/shared/safe-remove.js +11 -0
  404. package/dist/shared/schema.d.ts +453 -0
  405. package/dist/shared/schema.js +43 -2
  406. package/dist/shared/system.d.ts +1 -0
  407. package/dist/shared/system.js +1 -0
  408. package/dist/shared/test-guard.d.ts +4 -0
  409. package/dist/shared/test-guard.js +37 -0
  410. package/dist/sync/claude-mem.js +6 -3
  411. package/dist/sync/history.d.ts +29 -12
  412. package/dist/sync/history.js +72 -25
  413. package/dist/sync/watcher.d.ts +8 -0
  414. package/dist/sync/watcher.js +99 -8
  415. package/package.json +7 -6
  416. package/sandbox/bootstrap.ps1 +258 -258
  417. package/sandbox/cargoshim.cs +56 -0
  418. package/sandbox/cpucap.cs +33 -0
  419. package/sandbox/cpucap.ps1 +20 -0
  420. package/sandbox/logon.cmd +24 -16
  421. package/sandbox/memlog.ps1 +34 -0
  422. package/sandbox/openssl.cs +42 -0
  423. package/sandbox/pagefile.cs +31 -0
  424. package/sandbox/pagefile.ps1 +21 -0
  425. package/sandbox/ptyhost.cs +120 -11
  426. package/sandbox/trust.cmd +35 -35
  427. package/schemas/config.schema.json +403 -3
  428. package/schemas/sandbox-config.schema.json +143 -13
  429. package/dist/cli/commands/data.d.ts +0 -46
  430. package/dist/cli/commands/data.js +0 -249
  431. package/dist/cli/commands/setup.js +0 -152
  432. package/dist/cli/menu.d.ts +0 -22
  433. package/dist/cli/menu.js +0 -187
  434. package/dist/cli/settings.d.ts +0 -11
  435. package/dist/cli/settings.js +0 -156
  436. package/dist/guest/toolchains/databases.js +0 -226
  437. package/dist/guest/toolchains/msvc.d.ts +0 -10
  438. package/dist/guest/toolchains/msvc.js +0 -216
  439. package/dist/guest/toolchains/native.d.ts +0 -12
  440. package/dist/guest/toolchains/native.js +0 -220
  441. package/dist/guest/toolchains/scripting.d.ts +0 -10
  442. package/dist/guest/toolchains/scripting.js +0 -137
  443. package/dist/selftest/guest.test.d.ts +0 -3
  444. package/dist/selftest/guest.test.js +0 -61
  445. package/dist/selftest/helpers.test.d.ts +0 -2
  446. package/dist/selftest/helpers.test.js +0 -125
  447. package/dist/selftest/host.test.d.ts +0 -2
  448. package/dist/selftest/host.test.js +0 -71
  449. package/dist/selftest/live.test.d.ts +0 -7
  450. package/dist/selftest/live.test.js +0 -127
  451. package/dist/selftest/security.test.d.ts +0 -3
  452. package/dist/selftest/security.test.js +0 -143
  453. /package/dist/guest/toolchains/{binstall.d.ts → native/binstall.d.ts} +0 -0
@@ -0,0 +1,143 @@
1
+ // The audit log (audit.enabled): one file per day of JSON lines in host-only storage (<storage>\audit\<data set>),
2
+ // which the sandbox cannot reach. Days before today are gzipped; days past audit.days and, past audit.maxMb, the
3
+ // oldest days are deleted. Lines are short (field names are one or two letters) and capped in length.
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import zlib from 'node:zlib';
7
+ import { z } from 'zod';
8
+ /** One event. k: proc (a program started), file (changed in a project), session, guard (repo guard), note. */
9
+ export const AuditEvent = z.object({
10
+ /** ISO time. */
11
+ t: z.string().max(40),
12
+ k: z.enum(['proc', 'file', 'session', 'guard', 'note']),
13
+ /** proc: program; file: path in the project; session: agent; guard/note: what happened. */
14
+ p: z.string().max(1024).optional(),
15
+ /** proc: its command line (capped). */
16
+ c: z.string().max(2048).optional(),
17
+ /** proc: the program that started it. */
18
+ pp: z.string().max(1024).optional(),
19
+ /** file: change | rename; session: start | end. */
20
+ a: z.string().max(32).optional(),
21
+ /** project folder (file, session). */
22
+ w: z.string().max(1024).optional(),
23
+ /** how many times it happened in this line's minute (repeats are counted, not repeated). */
24
+ n: z.number().int().min(1).max(1e9).optional(),
25
+ });
26
+ const DAY_FILE = /^(\d{4}-\d{2}-\d{2})\.jsonl(\.gz)?$/;
27
+ export class AuditLog {
28
+ dir;
29
+ limits;
30
+ constructor(dir, limits) {
31
+ this.dir = dir;
32
+ this.limits = limits;
33
+ }
34
+ /** <storage>\audit\<data set name>. */
35
+ static dirFor(storageRoot, dataPath) { return path.join(storageRoot, 'audit', path.basename(dataPath)); }
36
+ dayFile(day, gz = false) { return path.join(this.dir, day + '.jsonl' + (gz ? '.gz' : '')); }
37
+ append(events) {
38
+ if (!events.length)
39
+ return;
40
+ fs.mkdirSync(this.dir, { recursive: true });
41
+ const byDay = new Map();
42
+ for (const e of events) {
43
+ const day = e.t.slice(0, 10);
44
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(day))
45
+ continue;
46
+ byDay.set(day, [...(byDay.get(day) ?? []), JSON.stringify(e)]);
47
+ }
48
+ for (const [day, lines] of byDay)
49
+ fs.appendFileSync(this.dayFile(day), lines.join('\n') + '\n');
50
+ }
51
+ /** Compresses finished days, then deletes days past the limits. Cheap when there is nothing to do. */
52
+ maintain(today = new Date().toISOString().slice(0, 10)) {
53
+ if (!fs.existsSync(this.dir))
54
+ return;
55
+ for (const name of fs.readdirSync(this.dir)) {
56
+ const m = DAY_FILE.exec(name);
57
+ if (!m || m[2] || m[1] >= today)
58
+ continue;
59
+ const file = path.join(this.dir, name);
60
+ fs.writeFileSync(file + '.gz', zlib.gzipSync(fs.readFileSync(file)));
61
+ fs.rmSync(file);
62
+ }
63
+ const oldest = new Date(Date.parse(today) - (this.limits.days - 1) * 86400000).toISOString().slice(0, 10);
64
+ const files = fs.readdirSync(this.dir).filter((n) => DAY_FILE.test(n)).sort();
65
+ for (const name of files.filter((n) => DAY_FILE.exec(n)[1] < oldest))
66
+ fs.rmSync(path.join(this.dir, name), { force: true });
67
+ let left = fs.readdirSync(this.dir).filter((n) => DAY_FILE.test(n)).sort();
68
+ const size = () => left.reduce((sum, n) => sum + fs.statSync(path.join(this.dir, n)).size, 0);
69
+ while (left.length > 1 && size() > this.limits.maxMb * 1024 * 1024) {
70
+ fs.rmSync(path.join(this.dir, left[0]), { force: true });
71
+ left = left.slice(1);
72
+ }
73
+ }
74
+ /** Events of the last `days` days (newest last), optionally of some kinds and containing a text. */
75
+ read(o = {}) {
76
+ if (!fs.existsSync(this.dir))
77
+ return [];
78
+ const since = new Date(Date.now() - ((o.days ?? 1) - 1) * 86400000).toISOString().slice(0, 10);
79
+ const needle = o.grep?.toLowerCase();
80
+ const events = [];
81
+ for (const name of fs.readdirSync(this.dir).filter((n) => DAY_FILE.test(n) && DAY_FILE.exec(n)[1] >= since).sort()) {
82
+ const raw = fs.readFileSync(path.join(this.dir, name));
83
+ const text = name.endsWith('.gz') ? zlib.gunzipSync(raw).toString('utf8') : raw.toString('utf8');
84
+ for (const line of text.split('\n')) {
85
+ if (!line.trim())
86
+ continue;
87
+ let parsed;
88
+ try {
89
+ parsed = JSON.parse(line);
90
+ }
91
+ catch {
92
+ continue;
93
+ }
94
+ const e = AuditEvent.safeParse(parsed);
95
+ if (!e.success)
96
+ continue;
97
+ if (o.kinds?.length && !o.kinds.includes(e.data.k))
98
+ continue;
99
+ if (needle && !line.toLowerCase().includes(needle))
100
+ continue;
101
+ events.push(e.data);
102
+ }
103
+ }
104
+ return events;
105
+ }
106
+ /** Total size on disk. */
107
+ bytes() {
108
+ if (!fs.existsSync(this.dir))
109
+ return 0;
110
+ return fs.readdirSync(this.dir).filter((n) => DAY_FILE.test(n)).reduce((sum, n) => sum + fs.statSync(path.join(this.dir, n)).size, 0);
111
+ }
112
+ }
113
+ /**
114
+ * Folds repeats: the same event (kind, program/path, command line, action, project) within one minute becomes one line
115
+ * with n = how many times; after `cap` distinct lines a minute, the rest of that minute is one "note" line.
116
+ */
117
+ export const fold = (events, cap = 300) => {
118
+ const out = [];
119
+ const index = new Map();
120
+ const perMinute = new Map();
121
+ const dropped = new Map();
122
+ for (const e of events) {
123
+ const minute = e.t.slice(0, 16);
124
+ const key = [minute, e.k, e.p, e.c, e.a, e.w].join('\0');
125
+ const seen = index.get(key);
126
+ if (seen) {
127
+ seen.n = (seen.n ?? 1) + (e.n ?? 1);
128
+ continue;
129
+ }
130
+ const count = (perMinute.get(minute) ?? 0) + 1;
131
+ perMinute.set(minute, count);
132
+ if (count > cap) {
133
+ dropped.set(minute, (dropped.get(minute) ?? 0) + (e.n ?? 1));
134
+ continue;
135
+ }
136
+ const copy = { ...e };
137
+ index.set(key, copy);
138
+ out.push(copy);
139
+ }
140
+ for (const [minute, n] of dropped)
141
+ out.push({ t: minute + ':59.999Z', k: 'note', p: n + ' more events in this minute were not listed (audit cap)' });
142
+ return out;
143
+ };
@@ -1,37 +1,38 @@
1
- import os from 'node:os';
2
- import type { GuestRequest, GuestResponse, SandboxStatus, 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
- /** Waits until the sandbox finished provisioning, printing each boot step. */
10
- export declare const waitForReady: (dataPath: string, launchId: string | null, isAlive: () => boolean) => Promise<SandboxStatus>;
1
+ import type { GuestRequest, GuestResponse, Harness, SandboxStatus, SessionStatus } from '../core/types.js';
2
+ import type { ProjectSpecT } from '../shared/schema.js';
3
+ /** Where progress lines go: printed by default; a spinner passes its own. Text from the sandbox, made safe first. */
4
+ export type Progress = (line: string) => void;
5
+ /** Waits until the sandbox finished provisioning, reporting each boot step. */
6
+ export declare const waitForReady: (dataPath: string, launchId: string | null, isAlive: () => boolean, progress?: Progress) => Promise<SandboxStatus>;
11
7
  export declare const sendRequest: (dataPath: string, request: GuestRequest) => void;
12
8
  export declare const waitForResponse: (dataPath: string, requestId: string, isAlive: () => boolean, timeoutMs: number) => Promise<GuestResponse>;
13
- export declare const requestSession: (dataPath: string, harness: string, args: string[], cwd: string) => string;
14
- export declare const waitForSession: (dataPath: string, sessionId: string, isAlive: () => boolean, timeoutMs?: number) => Promise<SessionStatus>;
9
+ /** project: a project registered after boot (provisioned first). harnessSpec: the agent's definition, for an agent the sandbox did not boot with. */
10
+ export declare const requestSession: (dataPath: string, harness: string, args: string[], cwd: string, extra?: {
11
+ project?: ProjectSpecT;
12
+ harnessSpec?: Harness;
13
+ }) => string;
14
+ /**
15
+ * Provisions a registered project without starting an agent and waits for it, printing its progress (the guest's
16
+ * run\projects\<name>.json) as it goes. Throws when it failed.
17
+ */
18
+ export declare const prepareProject: (dataPath: string, project: ProjectSpecT, isAlive: () => boolean, progress?: Progress) => Promise<GuestResponse>;
19
+ export declare const waitForSession: (dataPath: string, sessionId: string, isAlive: () => boolean, timeoutMs?: number, progress?: Progress) => Promise<SessionStatus>;
15
20
  /** Runs a command inside the sandbox (in the workspace or a folder below it) and returns its output. */
16
21
  export declare const execInSandbox: (dataPath: string, command: string[], cwd: string, timeoutSeconds: number, isAlive: () => boolean) => Promise<GuestResponse>;
17
22
  /** Restarts running sessions in place (they resume via the harness's resumeArguments, e.g. claude --continue). */
18
- export declare const restartSessions: (dataPath: string, harness: string | null, isAlive: () => boolean) => Promise<GuestResponse>;
23
+ export declare const restartSessions: (dataPath: string, harness: string | null, isAlive: () => boolean, cwd?: string) => Promise<GuestResponse>;
19
24
  export declare const pingSandbox: (dataPath: string, isAlive: () => boolean) => Promise<GuestResponse>;
20
25
  export declare const runningSessions: (dataPath: string) => SessionStatus[];
21
26
  /** Most sessions the host will ever contact for one listing: a sandbox can write as many session files as it likes. */
22
27
  export declare const MAX_QUERIED_SESSIONS = 32;
23
28
  /** Running sessions of the sandbox that is running now (its launch id), newest last, capped. */
24
29
  export declare const currentSessions: (dataPath: string) => SessionStatus[];
25
- /** The session with this id (or id prefix), else the most recently started running one. */
26
- export declare const pickSession: (dataPath: string, sessionId?: string) => SessionStatus;
27
- /** Who is watching a session right now (closed terminals do not count); null when it cannot be asked. */
28
- export declare const sessionViewers: (session: SessionStatus) => Promise<Viewers | null>;
30
+ /** Sessions in this guest folder or below (one project's) first, each group newest first. */
31
+ export declare const sessionsFirstIn: (sessions: SessionStatus[], guestDir: string | null) => SessionStatus[];
29
32
  /**
30
- * The newest running session of this agent in exactly this folder that no terminal on this PC is attached to, so
31
- * sandbox claude -t picks it back up instead of starting another. A session that cannot answer is never reused.
33
+ * The session with this id (or id prefix), else the most recently started running one: in `guestDir` (the project
34
+ * you are in) when it has one, else any.
32
35
  */
33
- export declare const findUnwatchedSession: (dataPath: string, harness: string, cwd: string) => Promise<SessionStatus | null>;
34
- /** Bridges this terminal to a session. Returns the agent's exit code, or 0 when the user detaches. */
35
- export declare const attachTerminal: (session: SessionStatus) => Promise<number>;
36
+ export declare const pickSession: (dataPath: string, sessionId?: string, guestDir?: string | null) => SessionStatus;
36
37
  /** Forgets status from a previous sandbox run so nothing stale is picked up. */
37
38
  export declare const resetRunDir: (dataPath: string) => void;
@@ -1,30 +1,13 @@
1
- // Host side of the sandbox channel: follow the boot, send requests (sessions, exec, restart), and bridge this terminal
2
- // to a session's pseudo console. Everything read from the run folder is guest-written and treated as untrusted.
1
+ // Host side of the sandbox channel: follow the boot and send requests (sessions, exec, restart, ping). Attaching a
2
+ // terminal to a session is in attach.ts. Everything read from the run folder is guest-written and treated as untrusted.
3
3
  import crypto from 'node:crypto';
4
4
  import fs from 'node:fs';
5
- import net from 'node:net';
6
- import os from 'node:os';
7
- import { insideSandbox } from './platform.js';
8
- import { safeRemove } from '../shared/safe-remove.js';
9
- import { connect, DETACH_KEY, queryViewers, runTerminalClient } from '../shared/pty-client.js';
10
- import { listSessions, now, readResponse, readSandboxStatus, readSession, runPaths, writeJsonAtomic } from '../shared/rundir.js';
11
5
  import { assertNoLinks, fail, isLink, removeLink, safeText, sleep } from '../core/util.js';
6
+ import { isWithin } from '../shared/projects.js';
7
+ import { listSessions, now, readProjectStatus, readResponse, readSandboxStatus, readSession, runPaths, writeJsonAtomic } from '../shared/rundir.js';
8
+ import { safeRemove } from '../shared/safe-remove.js';
9
+ import { assertTestWritable, refuseInSelfTest } from './test-guard.js';
12
10
  const BOOT_TIMEOUT_MS = 30 * 60 * 1000;
13
- const ipToInt = (ip) => ip.split('.').reduce((n, part) => n * 256 + Number(part), 0);
14
- /**
15
- * True only for IPv4 addresses inside the subnet of one of this PC's Hyper-V adapters ("vEthernet ..."), which is
16
- * where Windows Sandbox lives. Stops a compromised sandbox from pointing the host (and your keystrokes) anywhere else.
17
- */
18
- export const isSandboxAddress = (ip, interfaces = os.networkInterfaces()) => {
19
- if (!net.isIPv4(ip))
20
- return false;
21
- return Object.entries(interfaces).some(([name, list]) => /^vEthernet/i.test(name) && (list ?? []).some((i) => {
22
- if (i.family !== 'IPv4' || i.internal)
23
- return false;
24
- const mask = ipToInt(i.netmask);
25
- return mask !== 0 && (ipToInt(i.address) & mask) === (ipToInt(ip) & mask) && i.address !== ip;
26
- }));
27
- };
28
11
  const id = () => Date.now().toString(36) + crypto.randomBytes(3).toString('hex');
29
12
  const until = async (timeoutMs, isAlive, poll, timeoutMessage) => {
30
13
  const deadline = Date.now() + timeoutMs;
@@ -38,15 +21,16 @@ const until = async (timeoutMs, isAlive, poll, timeoutMessage) => {
38
21
  }
39
22
  fail(timeoutMessage, 'Check what the sandbox is doing with sandbox logs -f (or sbx-show to see its window).');
40
23
  };
41
- /** Waits until the sandbox finished provisioning, printing each boot step. */
42
- export const waitForReady = (dataPath, launchId, isAlive) => {
24
+ const printLine = (line) => console.log(line);
25
+ /** Waits until the sandbox finished provisioning, reporting each boot step. */
26
+ export const waitForReady = (dataPath, launchId, isAlive, progress = (m) => printLine(' [sandbox] ' + m)) => {
43
27
  let last = '';
44
28
  return until(BOOT_TIMEOUT_MS, isAlive, () => {
45
29
  const status = readSandboxStatus(dataPath);
46
30
  if (!status || (launchId !== null && status.launchId !== launchId))
47
31
  return null;
48
32
  if (status.message !== last) {
49
- console.log(' [sandbox] ' + safeText(status.message).slice(0, 300));
33
+ progress(safeText(status.message).slice(0, 300));
50
34
  last = status.message;
51
35
  }
52
36
  if (status.phase === 'failed')
@@ -55,28 +39,62 @@ export const waitForReady = (dataPath, launchId, isAlive) => {
55
39
  }, 'Timed out waiting for the sandbox to finish provisioning.');
56
40
  };
57
41
  export const sendRequest = (dataPath, request) => {
42
+ assertTestWritable(dataPath);
43
+ // Asking the sandbox to power off is the one request with an effect outside it, so the self-test never sends it.
44
+ if (request.kind === 'exec' && /shutdown/i.test(request.command[0] ?? ''))
45
+ refuseInSelfTest('shut down Windows Sandbox');
58
46
  assertNoLinks(dataPath, runPaths(dataPath).requests);
59
47
  writeJsonAtomic(runPaths(dataPath).request(request.id), request);
60
48
  };
61
49
  export const waitForResponse = (dataPath, requestId, isAlive, timeoutMs) => until(timeoutMs, isAlive, () => {
62
50
  const response = readResponse(dataPath, requestId);
63
- if (response && !isLink(runPaths(dataPath).responses))
64
- fs.rmSync(runPaths(dataPath).response(requestId), { force: true });
51
+ if (response)
52
+ try {
53
+ assertNoLinks(dataPath, runPaths(dataPath).responses);
54
+ fs.rmSync(runPaths(dataPath).response(requestId), { force: true });
55
+ }
56
+ catch { /* a planted link: the answer is read, nothing is deleted */ }
65
57
  return response;
66
58
  }, 'The sandbox did not answer (its session daemon may have stopped).');
67
- export const requestSession = (dataPath, harness, args, cwd) => {
59
+ /** project: a project registered after boot (provisioned first). harnessSpec: the agent's definition, for an agent the sandbox did not boot with. */
60
+ export const requestSession = (dataPath, harness, args, cwd, extra = {}) => {
68
61
  const requestId = id();
69
- sendRequest(dataPath, { kind: 'session', id: requestId, harness, args, cwd, createdAt: now() });
62
+ sendRequest(dataPath, { kind: 'session', id: requestId, harness, args, cwd, ...(extra.project ? { project: extra.project } : {}), ...(extra.harnessSpec ? { harnessSpec: extra.harnessSpec } : {}), createdAt: now() });
70
63
  return requestId;
71
64
  };
72
- export const waitForSession = (dataPath, sessionId, isAlive, timeoutMs = 15 * 60 * 1000) => {
65
+ /**
66
+ * Provisions a registered project without starting an agent and waits for it, printing its progress (the guest's
67
+ * run\projects\<name>.json) as it goes. Throws when it failed.
68
+ */
69
+ export const prepareProject = async (dataPath, project, isAlive, progress = (m) => printLine(' [' + project.name + '] ' + m)) => {
70
+ const requestId = id();
71
+ sendRequest(dataPath, { kind: 'project', id: requestId, project, createdAt: now() });
72
+ let last = '';
73
+ const response = await until(60 * 60 * 1000, isAlive, () => {
74
+ const status = readProjectStatus(dataPath, project.name);
75
+ if (status && status.message !== last) {
76
+ progress(safeText(status.message).slice(0, 300));
77
+ last = status.message;
78
+ }
79
+ return readResponse(dataPath, requestId);
80
+ }, 'Timed out waiting for the project to be set up.');
81
+ try {
82
+ assertNoLinks(dataPath, runPaths(dataPath).responses);
83
+ fs.rmSync(runPaths(dataPath).response(requestId), { force: true });
84
+ }
85
+ catch { /* read, nothing deleted */ }
86
+ if (!response.ok)
87
+ fail(safeText(response.message), 'See sandbox logs for details.');
88
+ return response;
89
+ };
90
+ export const waitForSession = (dataPath, sessionId, isAlive, timeoutMs = 15 * 60 * 1000, progress = (m) => printLine(' [session] ' + m)) => {
73
91
  let last = '';
74
92
  return until(timeoutMs, isAlive, () => {
75
93
  const session = readSession(dataPath, sessionId);
76
94
  if (!session)
77
95
  return null;
78
96
  if (session.message !== last) {
79
- console.log(' [session] ' + safeText(session.message).slice(0, 300));
97
+ progress(safeText(session.message).slice(0, 300));
80
98
  last = session.message;
81
99
  }
82
100
  if (session.phase === 'failed')
@@ -93,9 +111,9 @@ export const execInSandbox = async (dataPath, command, cwd, timeoutSeconds, isAl
93
111
  return waitForResponse(dataPath, requestId, isAlive, (timeoutSeconds + 30) * 1000);
94
112
  };
95
113
  /** Restarts running sessions in place (they resume via the harness's resumeArguments, e.g. claude --continue). */
96
- export const restartSessions = async (dataPath, harness, isAlive) => {
114
+ export const restartSessions = async (dataPath, harness, isAlive, cwd) => {
97
115
  const requestId = id();
98
- sendRequest(dataPath, { kind: 'restart', id: requestId, harness, createdAt: now() });
116
+ sendRequest(dataPath, { kind: 'restart', id: requestId, harness, ...(cwd ? { cwd } : {}), createdAt: now() });
99
117
  return waitForResponse(dataPath, requestId, isAlive, 30000);
100
118
  };
101
119
  export const pingSandbox = async (dataPath, isAlive) => {
@@ -111,61 +129,25 @@ export const currentSessions = (dataPath) => {
111
129
  const launchId = readSandboxStatus(dataPath)?.launchId;
112
130
  return runningSessions(dataPath).filter((s) => s.launchId === launchId).slice(-MAX_QUERIED_SESSIONS);
113
131
  };
114
- /** The session with this id (or id prefix), else the most recently started running one. */
115
- export const pickSession = (dataPath, sessionId) => {
132
+ /** Sessions in this guest folder or below (one project's) first, each group newest first. */
133
+ export const sessionsFirstIn = (sessions, guestDir) => {
134
+ const newest = [...sessions].reverse();
135
+ if (!guestDir)
136
+ return newest;
137
+ return [...newest.filter((s) => isWithin(guestDir, s.cwd)), ...newest.filter((s) => !isWithin(guestDir, s.cwd))];
138
+ };
139
+ /**
140
+ * The session with this id (or id prefix), else the most recently started running one: in `guestDir` (the project
141
+ * you are in) when it has one, else any.
142
+ */
143
+ export const pickSession = (dataPath, sessionId, guestDir = null) => {
116
144
  const running = runningSessions(dataPath);
117
145
  if (running.length === 0)
118
146
  fail('No agent session is running in the sandbox.', 'Start one with sandbox claude (it opens a new session in the running sandbox).');
119
147
  if (!sessionId)
120
- return running[running.length - 1];
148
+ return sessionsFirstIn(running, guestDir)[0];
121
149
  return running.find((s) => s.id === sessionId || s.id.startsWith(sessionId)) ?? fail('No running session "' + sessionId + '".', 'Running sessions: ' + running.map((s) => s.id).join(', ') + ' (sandbox sessions lists them).');
122
150
  };
123
- /** Where this PC may connect for a session: only addresses on its own Hyper-V network (null if there are none). */
124
- const hostEndpoint = (session) => {
125
- // Inside the sandbox every session is on loopback; on the PC only addresses on its Hyper-V network are trusted.
126
- const addresses = insideSandbox() ? ['127.0.0.1'] : session.attach?.addresses.filter((a) => isSandboxAddress(a)) ?? [];
127
- return session.attach && addresses.length ? { ...session.attach, addresses } : null;
128
- };
129
- /** Who is watching a session right now (closed terminals do not count); null when it cannot be asked. */
130
- export const sessionViewers = (session) => {
131
- const endpoint = hostEndpoint(session);
132
- return endpoint ? queryViewers(endpoint) : Promise.resolve(null);
133
- };
134
- /**
135
- * The newest running session of this agent in exactly this folder that no terminal on this PC is attached to, so
136
- * sandbox claude -t picks it back up instead of starting another. A session that cannot answer is never reused.
137
- */
138
- export const findUnwatchedSession = async (dataPath, harness, cwd) => {
139
- const candidates = currentSessions(dataPath).filter((s) => s.harness === harness && s.cwd.toLowerCase() === cwd.toLowerCase()).reverse().slice(0, 8);
140
- for (const session of candidates)
141
- if ((await sessionViewers(session))?.remote === 0)
142
- return session;
143
- return null;
144
- };
145
- /** Bridges this terminal to a session. Returns the agent's exit code, or 0 when the user detaches. */
146
- export const attachTerminal = async (session) => {
147
- if (!process.stdin.isTTY || !process.stdout.isTTY)
148
- fail('--terminal / attach needs an interactive terminal.', 'Run it directly in Windows Terminal or PowerShell, not through a pipe or script.');
149
- if (!session.attach)
150
- fail('This session cannot be attached to: networking or attach is disabled for this sandbox.', 'Set sandbox.networking and attach.enabled to true, then start a new sandbox.');
151
- const endpoint = hostEndpoint(session) ?? fail('Refusing to connect: the sandbox reported no address on this PC\'s Hyper-V (vEthernet) network (' + session.attach.addresses.join(', ') + ').', 'Use the sandbox window instead (sbx-show); if this keeps happening, run sandbox doctor.');
152
- const socket = await connect(endpoint);
153
- console.log('Attaching to ' + safeText(session.harness) + ' session ' + session.id + ' (' + safeText(session.cwd) + '). Detach any time with Ctrl+]');
154
- await sleep(400);
155
- const result = await runTerminalClient(socket, session.attach.token, { detachKey: DETACH_KEY });
156
- switch (result.reason) {
157
- case 'detach':
158
- console.log('Detached. The session keeps running; reattach with: sandbox attach ' + session.id);
159
- return 0;
160
- case 'exit':
161
- console.log(safeText(session.harness) + ' exited with code ' + result.exitCode + '.');
162
- return result.exitCode;
163
- case 'error': fail('The sandbox refused the connection: ' + result.message, 'Try sandbox attach again; the session may have restarted.');
164
- default:
165
- console.log('Connection to the sandbox closed.');
166
- return 1;
167
- }
168
- };
169
151
  /** Forgets status from a previous sandbox run so nothing stale is picked up. */
170
152
  export const resetRunDir = (dataPath) => {
171
153
  const p = runPaths(dataPath);
@@ -174,7 +156,7 @@ export const resetRunDir = (dataPath) => {
174
156
  removeLink(p.run);
175
157
  return;
176
158
  }
177
- for (const target of [p.sandbox, p.requests, p.responses, p.sessions]) {
159
+ for (const target of [p.sandbox, p.requests, p.responses, p.sessions, p.projects]) {
178
160
  safeRemove(target, dataPath);
179
161
  }
180
162
  };
@@ -1,13 +1,17 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { stripMcpServers, stripSettings } from './secrets.js';
4
- import { dirExists, ensureDir, fileExists, homeDir, readJson, writeJson } from '../core/util.js';
4
+ import { dirExists, ensureDir, fileExists, homeDir, isLink, readJson, writeJson } from '../core/util.js';
5
5
  const MAX_BYTES = 250 * 1024 * 1024;
6
+ /** Real files only: a link inside ~/.claude (even a loop or a dead one) is neither followed nor counted nor copied. */
6
7
  const directorySize = (dir) => {
7
8
  let total = 0;
8
9
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
9
10
  const full = path.join(dir, entry.name);
10
- total += entry.isDirectory() ? directorySize(full) : fs.statSync(full).size;
11
+ if (entry.isDirectory())
12
+ total += directorySize(full);
13
+ else if (entry.isFile())
14
+ total += fs.statSync(full).size;
11
15
  }
12
16
  return total;
13
17
  };
@@ -78,7 +82,7 @@ export const stageClaudeProfile = (settings, launchPath, workspaceHost, secrets)
78
82
  result.skipped.push(name + ' (over 250 MB)');
79
83
  continue;
80
84
  }
81
- fs.cpSync(source, destination, { recursive: true, force: true });
85
+ fs.cpSync(source, destination, { recursive: true, force: true, filter: (from) => from === source || !isLink(from) });
82
86
  }
83
87
  else {
84
88
  result.skipped.push(name + ' (not found)');
@@ -1,21 +1,12 @@
1
1
  // Keeps storage from growing forever. Runs automatically on every launch and via `sandbox clean`.
2
2
  import fs from 'node:fs';
3
- import { safeRemove } from '../shared/safe-remove.js';
4
3
  import path from 'node:path';
5
4
  import _ from 'lodash';
5
+ import { sizeOf } from '../core/disk.js';
6
6
  import { dirExists, isLink } from '../core/util.js';
7
+ import { safeRemove } from '../shared/safe-remove.js';
7
8
  const DAY = 24 * 60 * 60 * 1000;
8
9
  export const DEFAULT_PRUNE = { launchDays: 7, logDays: 14, conflictDays: 30, keep: [] };
9
- /** Size on disk, never following links (a sandbox-planted junction must not make us walk this PC's disk). */
10
- const sizeOf = (p) => {
11
- try {
12
- const stat = fs.lstatSync(p);
13
- return stat.isDirectory() ? _.sumBy(fs.readdirSync(p), (name) => sizeOf(path.join(p, name))) : stat.isSymbolicLink() ? 0 : stat.size;
14
- }
15
- catch {
16
- return 0;
17
- }
18
- };
19
10
  const ageMs = (p, now) => { try {
20
11
  return now - fs.lstatSync(p).mtimeMs;
21
12
  }
@@ -0,0 +1,12 @@
1
+ import type { Config, DatabasePlan } from '../core/types.js';
2
+ import { type Database, type ProjectFile } from '../core/detect-databases.js';
3
+ /**
4
+ * What to run in this sandbox: databases set to true, plus "auto" ones the project needs. SQL Server is reported (so
5
+ * the user hears about it) but cannot run inside Windows Sandbox.
6
+ */
7
+ export declare const planDatabases: (config: Config["databases"], workspace: string, probe?: {
8
+ files?: ProjectFile[];
9
+ host?: Database[];
10
+ }) => DatabasePlan[];
11
+ /** Every database the sandbox can run, so `sbx-db start <name>` works for ones that did not start at boot. */
12
+ export declare const databaseCatalog: (config: Config["databases"]) => DatabasePlan[];
@@ -0,0 +1,52 @@
1
+ import { DATABASES, detectDatabases } from '../core/detect-databases.js';
2
+ import { hostDatabases } from './pc-scan.js';
3
+ import { readProjectFiles } from './project-scan.js';
4
+ /** The version a compose file pins (postgres:16-alpine -> 16, mongo:7.0 -> 7.0), if any. */
5
+ const pinnedVersion = (files, image) => {
6
+ for (const file of files) {
7
+ const match = image.exec(file.text);
8
+ if (match?.[1] && /^\d+(\.\d+)?/.test(match[1]))
9
+ return /^\d+(\.\d+)?/.exec(match[1])[0];
10
+ }
11
+ return null;
12
+ };
13
+ /**
14
+ * What to run in this sandbox: databases set to true, plus "auto" ones the project needs. SQL Server is reported (so
15
+ * the user hears about it) but cannot run inside Windows Sandbox.
16
+ */
17
+ export const planDatabases = (config, workspace, probe = {}) => {
18
+ const autos = DATABASES.filter((db) => config[db].enabled === 'auto');
19
+ const files = autos.length ? probe.files ?? readProjectFiles(workspace) : [];
20
+ const detected = autos.length ? detectDatabases(files, probe.host ?? hostDatabases) : [];
21
+ const plan = databasePlanner(config, files.filter((f) => /compose/i.test(f.path)));
22
+ return DATABASES.flatMap((database) => {
23
+ const setting = config[database].enabled;
24
+ const found = detected.find((d) => d.database === database);
25
+ if (setting === true)
26
+ return [plan(database, ['enabled in config'])];
27
+ if (setting === 'auto' && found)
28
+ return [plan(database, found.reasons)];
29
+ return [];
30
+ });
31
+ };
32
+ /** Every database the sandbox can run, so `sbx-db start <name>` works for ones that did not start at boot. */
33
+ export const databaseCatalog = (config) => {
34
+ const plan = databasePlanner(config, []);
35
+ return DATABASES.filter((db) => db !== 'sqlserver').map((db) => plan(db, ['started with sbx-db']));
36
+ };
37
+ /** Version (a compose file's image tag wins), port and admin UI for one database. */
38
+ const databasePlanner = (config, compose) => {
39
+ const versions = {
40
+ postgres: pinnedVersion(compose, /image:\s*["']?(?:[\w.-]+\/)*postgres:([\w.-]+)/i) ?? (config.postgres.version === 'latest' ? 'latest' : config.postgres.version),
41
+ mysql: config.mysql.version,
42
+ mongodb: pinnedVersion(compose, /image:\s*["']?(?:[\w.-]+\/)*mongo:([\w.-]+)/i) ?? config.mongodb.version,
43
+ redis: 'latest',
44
+ dynamodb: 'latest',
45
+ sqlserver: '',
46
+ };
47
+ const ports = { postgres: config.postgres.port, mysql: config.mysql.port, mongodb: config.mongodb.port, redis: config.redis.port, dynamodb: config.dynamodb.port, sqlserver: 1433 };
48
+ return (database, reasons) => ({
49
+ database, reasons, version: versions[database], port: ports[database],
50
+ ...(database === 'dynamodb' && config.dynamodb.admin ? { adminPort: config.dynamodb.adminPort } : {}),
51
+ });
52
+ };
@@ -1,15 +1,13 @@
1
- import type { Config, GenerateOptions, Generated, ResolvedMapping } from '../core/types.js';
2
- export declare const GUEST: {
3
- readonly launch: "C:\\AgentLaunch";
4
- readonly data: "C:\\AgentData";
5
- readonly hostTools: "C:\\HostTools";
6
- readonly mirror: "C:\\Mirror";
7
- };
8
- export declare const resolveMemoryMb: (sandbox: Config["sandbox"], freeMemoryBytes?: number) => number;
9
- export declare const buildWsb: (config: Config, memoryMb: number, mappings: ResolvedMapping[]) => string;
10
- export declare const RUNTIME_FILES: string[];
11
- /** Only the identity from ~/.gitconfig: the rest can hold credentials (helpers, tokens in insteadOf URLs). */
12
- export declare const gitIdentity: (gitconfig: string) => string;
1
+ import type { GenerateOptions, Generated } from '../core/types.js';
2
+ import { decideToolchains } from '../core/detect-toolchains.js';
3
+ /** Which toolchains the project needs ("auto" settings follow the project's files and what this PC has). */
4
+ export declare const decideProjectToolchains: (o: Pick<GenerateOptions, "run" | "pcLanguages">) => ReturnType<typeof decideToolchains>;
5
+ /**
6
+ * env.cmd, which logon.cmd calls before it opens Windows Terminal: Terminal expands "%AGENT_WORKSPACE%" (the starting
7
+ * folder of new tabs) from its own environment, and the runtime only sets the variable machine-wide after Terminal
8
+ * started. `%` is doubled for cmd; a Windows path holds no quotes.
9
+ */
10
+ export declare const logonEnv: (workspace: string) => string;
13
11
  export declare const generateSandbox: (o: GenerateOptions) => Generated & {
14
12
  personalization: string;
15
13
  };