windows-development-sandbox 1.4.0 → 1.4.3

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 (877) hide show
  1. package/.sandbox-config.example.json +2 -1
  2. package/config.example.json +4 -1
  3. package/dist/api.js +27 -16
  4. package/dist/cli/args.d.ts +4 -6
  5. package/dist/cli/args.js +67 -34
  6. package/dist/cli/argv.d.ts +9 -0
  7. package/dist/cli/argv.js +22 -0
  8. package/dist/cli/commands/accounts.d.ts +1 -1
  9. package/dist/cli/commands/accounts.js +42 -27
  10. package/dist/cli/commands/agent-commands.d.ts +3 -3
  11. package/dist/cli/commands/agent-commands.js +27 -18
  12. package/dist/cli/commands/context.d.ts +9 -11
  13. package/dist/cli/commands/context.js +35 -32
  14. package/dist/cli/commands/data/clean.d.ts +2 -2
  15. package/dist/cli/commands/data/clean.js +27 -11
  16. package/dist/cli/commands/data/collect.d.ts +6 -3
  17. package/dist/cli/commands/data/collect.js +46 -15
  18. package/dist/cli/commands/data/index.d.ts +2 -2
  19. package/dist/cli/commands/data/index.js +30 -12
  20. package/dist/cli/commands/data/remove.d.ts +1 -1
  21. package/dist/cli/commands/data/remove.js +4 -4
  22. package/dist/cli/commands/index.d.ts +5 -4
  23. package/dist/cli/commands/index.js +5 -4
  24. package/dist/cli/commands/launch/add-root.d.ts +7 -0
  25. package/dist/cli/commands/launch/add-root.js +64 -0
  26. package/dist/cli/commands/launch/boot-progress.d.ts +7 -0
  27. package/dist/cli/commands/launch/boot-progress.js +48 -0
  28. package/dist/cli/commands/launch/boot-wants.d.ts +11 -0
  29. package/dist/cli/commands/launch/boot-wants.js +48 -0
  30. package/dist/cli/commands/launch/first-start.d.ts +10 -0
  31. package/dist/cli/commands/launch/first-start.js +53 -0
  32. package/dist/cli/commands/launch/index.d.ts +6 -0
  33. package/dist/cli/commands/launch/index.js +6 -0
  34. package/dist/cli/commands/launch/launch.d.ts +8 -0
  35. package/dist/cli/commands/launch/launch.js +211 -0
  36. package/dist/cli/commands/launch/prepare.d.ts +23 -0
  37. package/dist/cli/commands/launch/prepare.js +109 -0
  38. package/dist/cli/commands/launch/running.d.ts +45 -0
  39. package/dist/cli/commands/launch/running.js +218 -0
  40. package/dist/cli/commands/launch/summary.d.ts +8 -0
  41. package/dist/cli/commands/launch/summary.js +98 -0
  42. package/dist/cli/commands/maintenance/audit.d.ts +3 -0
  43. package/dist/cli/commands/maintenance/audit.js +58 -0
  44. package/dist/cli/commands/maintenance/clean.d.ts +2 -0
  45. package/dist/cli/commands/maintenance/clean.js +10 -0
  46. package/dist/cli/commands/maintenance/config.d.ts +2 -0
  47. package/dist/cli/commands/maintenance/config.js +145 -0
  48. package/dist/cli/commands/maintenance/doctor.d.ts +2 -0
  49. package/dist/cli/commands/maintenance/doctor.js +52 -0
  50. package/dist/cli/commands/maintenance/host-claude.d.ts +2 -0
  51. package/dist/cli/commands/maintenance/host-claude.js +4 -0
  52. package/dist/cli/commands/maintenance/index.d.ts +8 -0
  53. package/dist/cli/commands/maintenance/index.js +9 -0
  54. package/dist/cli/commands/maintenance/logs.d.ts +2 -0
  55. package/dist/cli/commands/maintenance/logs.js +44 -0
  56. package/dist/cli/commands/maintenance/sync.d.ts +2 -0
  57. package/dist/cli/commands/maintenance/sync.js +12 -0
  58. package/dist/cli/commands/maintenance/watch.d.ts +3 -0
  59. package/dist/cli/commands/maintenance/watch.js +6 -0
  60. package/dist/cli/commands/projects/describe-sessions.d.ts +3 -0
  61. package/dist/cli/commands/projects/describe-sessions.js +15 -0
  62. package/dist/cli/commands/projects/here.d.ts +38 -0
  63. package/dist/cli/commands/projects/here.js +145 -0
  64. package/dist/cli/commands/projects/index.d.ts +4 -0
  65. package/dist/cli/commands/projects/index.js +4 -0
  66. package/dist/cli/commands/projects/projects.d.ts +14 -0
  67. package/dist/cli/commands/projects/projects.js +213 -0
  68. package/dist/cli/commands/projects/restart.d.ts +15 -0
  69. package/dist/cli/commands/projects/restart.js +149 -0
  70. package/dist/cli/commands/registry.d.ts +3 -0
  71. package/dist/cli/commands/registry.js +34 -0
  72. package/dist/cli/commands/repair/checks.js +105 -29
  73. package/dist/cli/commands/repair/finders.d.ts +13 -10
  74. package/dist/cli/commands/repair/finders.js +63 -43
  75. package/dist/cli/commands/repair/index.d.ts +4 -4
  76. package/dist/cli/commands/repair/index.js +47 -21
  77. package/dist/cli/commands/repair/types.d.ts +2 -2
  78. package/dist/cli/commands/repair/types.js +7 -2
  79. package/dist/cli/commands/sessions/attach.d.ts +2 -0
  80. package/dist/cli/commands/sessions/attach.js +45 -0
  81. package/dist/cli/commands/sessions/control.d.ts +4 -0
  82. package/dist/cli/commands/sessions/control.js +62 -0
  83. package/dist/cli/commands/sessions/exec.d.ts +6 -0
  84. package/dist/cli/commands/sessions/exec.js +72 -0
  85. package/dist/cli/commands/sessions/index.d.ts +6 -0
  86. package/dist/cli/commands/sessions/index.js +9 -0
  87. package/dist/cli/commands/sessions/list.d.ts +15 -0
  88. package/dist/cli/commands/sessions/list.js +65 -0
  89. package/dist/cli/commands/sessions/status.d.ts +2 -0
  90. package/dist/cli/commands/sessions/status.js +153 -0
  91. package/dist/cli/commands/sessions/window.d.ts +3 -0
  92. package/dist/cli/commands/sessions/window.js +18 -0
  93. package/dist/cli/commands/setup/checks.d.ts +4 -4
  94. package/dist/cli/commands/setup/checks.js +36 -19
  95. package/dist/cli/commands/setup/facts.d.ts +6 -6
  96. package/dist/cli/commands/setup/facts.js +23 -11
  97. package/dist/cli/commands/setup/index.d.ts +1 -1
  98. package/dist/cli/commands/setup/index.js +25 -12
  99. package/dist/cli/commands/user-config.d.ts +5 -5
  100. package/dist/cli/commands/user-config.js +14 -12
  101. package/dist/cli/history.d.ts +5 -4
  102. package/dist/cli/history.js +23 -11
  103. package/dist/cli/index.d.ts +4 -14
  104. package/dist/cli/index.js +27 -77
  105. package/dist/cli/manifest.d.ts +464 -10
  106. package/dist/cli/manifest.js +328 -54
  107. package/dist/cli/menu/apps.d.ts +7 -5
  108. package/dist/cli/menu/apps.js +64 -16
  109. package/dist/cli/menu/command-fields.d.ts +15 -0
  110. package/dist/cli/menu/command-fields.js +23 -0
  111. package/dist/cli/menu/command-form.d.ts +3 -5
  112. package/dist/cli/menu/command-form.js +116 -48
  113. package/dist/cli/menu/home.d.ts +22 -0
  114. package/dist/cli/menu/home.js +72 -0
  115. package/dist/cli/menu/index.d.ts +2 -24
  116. package/dist/cli/menu/index.js +4 -178
  117. package/dist/cli/menu/menu.d.ts +23 -0
  118. package/dist/cli/menu/menu.js +185 -0
  119. package/dist/cli/menu/settings/categories.d.ts +2 -2
  120. package/dist/cli/menu/settings/categories.js +123 -20
  121. package/dist/cli/menu/settings/collections/collection-editor.d.ts +23 -0
  122. package/dist/cli/menu/settings/collections/collection-editor.js +89 -0
  123. package/dist/cli/menu/settings/collections/map-editor.d.ts +10 -0
  124. package/dist/cli/menu/settings/collections/map-editor.js +34 -0
  125. package/dist/cli/menu/settings/collections/object-list-editor.d.ts +10 -0
  126. package/dist/cli/menu/settings/collections/object-list-editor.js +23 -0
  127. package/dist/cli/menu/settings/collections/object-map-editor.d.ts +10 -0
  128. package/dist/cli/menu/settings/collections/object-map-editor.js +35 -0
  129. package/dist/cli/menu/settings/collections/string-list-editor.d.ts +12 -0
  130. package/dist/cli/menu/settings/collections/string-list-editor.js +39 -0
  131. package/dist/cli/menu/settings/config-file.d.ts +2 -2
  132. package/dist/cli/menu/settings/config-file.js +42 -19
  133. package/dist/cli/menu/settings/editor-actions.d.ts +2 -0
  134. package/dist/cli/menu/settings/editor-actions.js +2 -0
  135. package/dist/cli/menu/settings/format.d.ts +5 -0
  136. package/dist/cli/menu/settings/format.js +39 -0
  137. package/dist/cli/menu/settings/index.d.ts +1 -29
  138. package/dist/cli/menu/settings/index.js +1 -133
  139. package/dist/cli/menu/settings/object-editor.d.ts +12 -0
  140. package/dist/cli/menu/settings/object-editor.js +86 -0
  141. package/dist/cli/menu/settings/schema-node.d.ts +3 -1
  142. package/dist/cli/menu/settings/schema-node.js +71 -24
  143. package/dist/cli/menu/settings/settings-editor.d.ts +30 -0
  144. package/dist/cli/menu/settings/settings-editor.js +176 -0
  145. package/dist/cli/menu/settings/{editors.d.ts → value-editor.d.ts} +2 -13
  146. package/dist/cli/menu/settings/value-editor.js +137 -0
  147. package/dist/cli/menu/several.d.ts +4 -4
  148. package/dist/cli/menu/several.js +61 -20
  149. package/dist/cli/{colors.d.ts → output/colors.d.ts} +8 -8
  150. package/dist/cli/{colors.js → output/colors.js} +32 -14
  151. package/dist/cli/output/index.d.ts +3 -0
  152. package/dist/cli/output/index.js +3 -0
  153. package/dist/cli/output/print.d.ts +5 -0
  154. package/dist/cli/output/print.js +14 -0
  155. package/dist/cli/output/progress.d.ts +24 -0
  156. package/dist/cli/output/progress.js +124 -0
  157. package/dist/cli/report.d.ts +2 -0
  158. package/dist/cli/report.js +20 -0
  159. package/dist/cli/tui/app.d.ts +6 -16
  160. package/dist/cli/tui/app.js +28 -8
  161. package/dist/cli/tui/dialogs/confirm-screen.d.ts +20 -0
  162. package/dist/cli/tui/dialogs/confirm-screen.js +50 -0
  163. package/dist/cli/tui/dialogs/message-screen.d.ts +17 -0
  164. package/dist/cli/tui/dialogs/message-screen.js +55 -0
  165. package/dist/cli/tui/dialogs/paragraphs.d.ts +2 -0
  166. package/dist/cli/tui/dialogs/paragraphs.js +5 -0
  167. package/dist/cli/tui/dialogs/text-screen.d.ts +26 -0
  168. package/dist/cli/tui/dialogs/text-screen.js +107 -0
  169. package/dist/cli/tui/frame.js +3 -1
  170. package/dist/cli/tui/index.d.ts +7 -3
  171. package/dist/cli/tui/index.js +4 -2
  172. package/dist/cli/tui/list/list-screen.d.ts +29 -0
  173. package/dist/cli/tui/{list-screen.js → list/list-screen.js} +86 -23
  174. package/dist/cli/tui/{list-screen.d.ts → list/list-types.d.ts} +0 -28
  175. package/dist/cli/tui/screen.d.ts +14 -0
  176. package/dist/cli/tui/terminal.js +34 -10
  177. package/dist/cli/tui/text.d.ts +2 -1
  178. package/dist/cli/tui/text.js +37 -7
  179. package/dist/cli/tui/theme.js +27 -9
  180. package/dist/cli/validate.d.ts +8 -8
  181. package/dist/cli/validate.js +32 -21
  182. package/dist/core/config/defaults.d.ts +2 -0
  183. package/dist/core/config/defaults.js +203 -0
  184. package/dist/core/config/index.d.ts +2 -0
  185. package/dist/core/config/index.js +3 -0
  186. package/dist/core/{config.d.ts → config/loader.d.ts} +1 -2
  187. package/dist/core/config/loader.js +47 -0
  188. package/dist/core/{detect-databases.js → detect/databases.js} +111 -23
  189. package/dist/core/detect/index.d.ts +4 -0
  190. package/dist/core/detect/index.js +4 -0
  191. package/dist/core/{detect-pwsh.d.ts → detect/pwsh.d.ts} +1 -1
  192. package/dist/core/{detect-pwsh.js → detect/pwsh.js} +25 -11
  193. package/dist/core/{detect-toolchains.d.ts → detect/toolchains.d.ts} +1 -1
  194. package/dist/core/{detect-toolchains.js → detect/toolchains.js} +42 -5
  195. package/dist/{guest/toolchains → core/detect}/versions.js +28 -12
  196. package/dist/core/disk.d.ts +2 -0
  197. package/dist/core/disk.js +46 -3
  198. package/dist/core/local-config/boot-wants.d.ts +45 -0
  199. package/dist/core/local-config/boot-wants.js +120 -0
  200. package/dist/core/local-config/file.d.ts +3 -0
  201. package/dist/core/local-config/file.js +16 -0
  202. package/dist/core/local-config/index.d.ts +5 -0
  203. package/dist/core/local-config/index.js +7 -0
  204. package/dist/core/{local-config.d.ts → local-config/overrides.d.ts} +11 -11
  205. package/dist/core/{local-config.js → local-config/overrides.js} +53 -54
  206. package/dist/core/{project-policy.d.ts → local-config/policy.d.ts} +7 -3
  207. package/dist/core/{project-policy.js → local-config/policy.js} +16 -6
  208. package/dist/core/local-config/trust.d.ts +13 -0
  209. package/dist/core/local-config/trust.js +43 -0
  210. package/dist/core/package-info.d.ts +4 -0
  211. package/dist/core/package-info.js +13 -0
  212. package/dist/core/run/guest-workspace.d.ts +23 -0
  213. package/dist/core/run/guest-workspace.js +47 -0
  214. package/dist/core/run/index.d.ts +3 -0
  215. package/dist/core/run/index.js +3 -0
  216. package/dist/core/run/resolve-run.d.ts +3 -0
  217. package/dist/core/run/resolve-run.js +202 -0
  218. package/dist/core/run/versions.d.ts +13 -0
  219. package/dist/core/run/versions.js +65 -0
  220. package/dist/core/safe-paths.js +17 -5
  221. package/dist/core/schema/apps.d.ts +4 -4
  222. package/dist/core/schema/apps.js +55 -21
  223. package/dist/core/schema/common.d.ts +13 -10
  224. package/dist/core/schema/common.js +16 -10
  225. package/dist/core/schema/config.d.ts +18 -12
  226. package/dist/core/schema/config.js +78 -53
  227. package/dist/core/schema/databases.d.ts +4 -3
  228. package/dist/core/schema/databases.js +16 -12
  229. package/dist/core/schema/harness.d.ts +1 -1
  230. package/dist/core/schema/harness.js +17 -7
  231. package/dist/core/schema/index.d.ts +6 -0
  232. package/dist/core/schema/index.js +8 -0
  233. package/dist/core/schema/json-schema.js +10 -5
  234. package/dist/core/schema/profile.d.ts +4 -4
  235. package/dist/core/schema/profile.js +37 -17
  236. package/dist/core/schema/projects.d.ts +2 -1
  237. package/dist/core/schema/projects.js +16 -5
  238. package/dist/core/schema/sandbox.d.ts +12 -12
  239. package/dist/core/schema/sandbox.js +101 -37
  240. package/dist/core/schema/toolchains.d.ts +6 -1
  241. package/dist/core/schema/toolchains.js +84 -34
  242. package/dist/core/types.d.ts +36 -9
  243. package/dist/core/util/errors.d.ts +8 -0
  244. package/dist/core/util/errors.js +14 -0
  245. package/dist/core/util/fs.d.ts +8 -0
  246. package/dist/core/util/fs.js +40 -0
  247. package/dist/core/util/index.d.ts +7 -0
  248. package/dist/core/util/index.js +7 -0
  249. package/dist/core/util/jsonc.d.ts +6 -0
  250. package/dist/core/util/jsonc.js +25 -0
  251. package/dist/core/util/links.d.ts +10 -0
  252. package/dist/core/util/links.js +39 -0
  253. package/dist/core/util/paths.d.ts +7 -0
  254. package/dist/core/util/paths.js +33 -0
  255. package/dist/core/util/process.d.ts +15 -0
  256. package/dist/core/util/process.js +54 -0
  257. package/dist/core/util/text.d.ts +8 -0
  258. package/dist/core/util/text.js +8 -0
  259. package/dist/guest/{setup/agents.js → agents/agent-installer.js} +24 -4
  260. package/dist/guest/apps/after-install.d.ts +3 -0
  261. package/dist/guest/apps/after-install.js +47 -0
  262. package/dist/guest/apps/app-installer.d.ts +22 -0
  263. package/dist/guest/apps/app-installer.js +116 -0
  264. package/dist/guest/apps/find-exe.d.ts +5 -0
  265. package/dist/guest/apps/find-exe.js +41 -0
  266. package/dist/guest/apps/index.d.ts +3 -10
  267. package/dist/guest/apps/index.js +6 -206
  268. package/dist/guest/apps/resolvers.js +37 -9
  269. package/dist/guest/apps/shortcuts.d.ts +6 -0
  270. package/dist/guest/apps/shortcuts.js +28 -0
  271. package/dist/guest/apps/sources.d.ts +4 -0
  272. package/dist/guest/apps/sources.js +17 -0
  273. package/dist/guest/apps/unpack.d.ts +3 -0
  274. package/dist/guest/apps/unpack.js +47 -0
  275. package/dist/guest/{audit-parse.js → audit/parse.js} +39 -3
  276. package/dist/guest/{audit.d.ts → audit/process-audit.d.ts} +3 -1
  277. package/dist/guest/{audit.js → audit/process-audit.js} +33 -8
  278. package/dist/guest/boot/boot-env.d.ts +4 -0
  279. package/dist/guest/{boot-env.js → boot/boot-env.js} +7 -5
  280. package/dist/guest/boot/boot.d.ts +1 -0
  281. package/dist/guest/{main.js → boot/boot.js} +100 -50
  282. package/dist/guest/{guide.js → boot/guide.js} +32 -7
  283. package/dist/guest/{project-env.d.ts → boot/project-env.d.ts} +3 -3
  284. package/dist/guest/{project-env.js → boot/project-env.js} +12 -4
  285. package/dist/guest/boot/project-status.d.ts +5 -0
  286. package/dist/guest/boot/project-status.js +28 -0
  287. package/dist/guest/daemon/daemon.d.ts +1 -0
  288. package/dist/guest/daemon/daemon.js +51 -0
  289. package/dist/guest/daemon/exec.js +18 -6
  290. package/dist/guest/daemon/{shutdown.js → idle-shutdown.js} +5 -4
  291. package/dist/guest/daemon/{project.d.ts → project-provisioner.d.ts} +3 -3
  292. package/dist/guest/daemon/{project.js → project-provisioner.js} +60 -22
  293. package/dist/guest/daemon/relays.js +5 -3
  294. package/dist/guest/daemon/{session.js → sessions.js} +84 -24
  295. package/dist/guest/daemon/state.d.ts +4 -1
  296. package/dist/guest/daemon/state.js +34 -20
  297. package/dist/guest/databases/database-server.d.ts +13 -0
  298. package/dist/guest/databases/database-server.js +35 -0
  299. package/dist/guest/databases/dynamodb.d.ts +12 -0
  300. package/dist/guest/databases/dynamodb.js +81 -0
  301. package/dist/guest/databases/index.d.ts +2 -0
  302. package/dist/guest/databases/index.js +2 -0
  303. package/dist/guest/databases/mongodb.d.ts +7 -0
  304. package/dist/guest/databases/mongodb.js +46 -0
  305. package/dist/guest/databases/mysql.d.ts +7 -0
  306. package/dist/guest/databases/mysql.js +59 -0
  307. package/dist/guest/databases/names.d.ts +5 -0
  308. package/dist/guest/databases/names.js +8 -0
  309. package/dist/guest/databases/postgres.d.ts +9 -0
  310. package/dist/guest/databases/postgres.js +85 -0
  311. package/dist/guest/databases/redis.d.ts +7 -0
  312. package/dist/guest/databases/redis.js +25 -0
  313. package/dist/guest/databases/registry.d.ts +5 -0
  314. package/dist/guest/databases/registry.js +51 -0
  315. package/dist/guest/databases/sbx-db.d.ts +1 -0
  316. package/dist/guest/{db.js → databases/sbx-db.js} +13 -13
  317. package/dist/guest/{toolchains/databases → databases}/server.d.ts +5 -5
  318. package/dist/guest/{toolchains/databases → databases}/server.js +18 -13
  319. package/dist/guest/entries/daemon.js +16 -0
  320. package/dist/guest/entries/db.js +10 -0
  321. package/dist/guest/entries/gitguard.js +20 -0
  322. package/dist/guest/entries/main.d.ts +1 -0
  323. package/dist/guest/entries/main.js +24 -0
  324. package/dist/guest/entries/netproxy.d.ts +1 -0
  325. package/dist/guest/entries/netproxy.js +7 -0
  326. package/dist/guest/entries/view.d.ts +1 -0
  327. package/dist/guest/entries/view.js +11 -0
  328. package/dist/guest/{gitguard.js → git/decision.js} +49 -26
  329. package/dist/guest/network/addresses.d.ts +2 -0
  330. package/dist/guest/network/addresses.js +6 -0
  331. package/dist/guest/{setup/network.d.ts → network/allow-only.d.ts} +0 -10
  332. package/dist/guest/network/allow-only.js +84 -0
  333. package/dist/guest/network/firewall.d.ts +9 -0
  334. package/dist/guest/{setup/network.js → network/firewall.js} +58 -63
  335. package/dist/guest/network/proxy-server.d.ts +2 -0
  336. package/dist/guest/{netproxy.js → network/proxy-server.js} +37 -18
  337. package/dist/guest/{helpers.d.ts → runtime/csc.d.ts} +1 -1
  338. package/dist/guest/{helpers.js → runtime/csc.js} +7 -2
  339. package/dist/guest/runtime/fs.d.ts +2 -0
  340. package/dist/guest/runtime/fs.js +6 -0
  341. package/dist/guest/runtime/log.d.ts +3 -0
  342. package/dist/guest/runtime/log.js +22 -0
  343. package/dist/guest/runtime/paths.d.ts +13 -0
  344. package/dist/guest/runtime/paths.js +14 -0
  345. package/dist/guest/{ports.js → runtime/ports.js} +8 -2
  346. package/dist/guest/runtime/process.d.ts +15 -0
  347. package/dist/guest/runtime/process.js +59 -0
  348. package/dist/guest/{project-context.js → runtime/project-context.js} +1 -1
  349. package/dist/guest/runtime/session.d.ts +5 -0
  350. package/dist/guest/runtime/session.js +10 -0
  351. package/dist/guest/runtime/status.d.ts +7 -0
  352. package/dist/guest/runtime/status.js +57 -0
  353. package/dist/guest/{cmdline.js → sessions/cmdline.js} +10 -4
  354. package/dist/guest/{ptyhost.js → sessions/pty-host.js} +12 -5
  355. package/dist/guest/{view.js → sessions/viewer.js} +8 -11
  356. package/dist/guest/setup/claude-mem.d.ts +10 -0
  357. package/dist/guest/setup/{claude.js → claude-mem.js} +18 -78
  358. package/dist/guest/setup/{claude.d.ts → claude-profile.d.ts} +0 -10
  359. package/dist/guest/setup/claude-profile.js +89 -0
  360. package/dist/guest/setup/desktop.d.ts +2 -0
  361. package/dist/guest/setup/desktop.js +32 -0
  362. package/dist/guest/setup/environment.js +28 -5
  363. package/dist/guest/setup/{user.d.ts → git-identity.d.ts} +0 -2
  364. package/dist/guest/setup/git-identity.js +34 -0
  365. package/dist/guest/{gitrules.d.ts → setup/git-rules.d.ts} +1 -1
  366. package/dist/guest/setup/git-rules.js +50 -0
  367. package/dist/guest/setup/hooks.js +22 -5
  368. package/dist/guest/setup/{policy.js → managed-policy.js} +2 -2
  369. package/dist/guest/toolchains/catalogue.d.ts +4 -4
  370. package/dist/guest/toolchains/catalogue.js +30 -7
  371. package/dist/guest/toolchains/detect.d.ts +12 -0
  372. package/dist/guest/toolchains/detect.js +54 -0
  373. package/dist/guest/toolchains/index.d.ts +3 -17
  374. package/dist/guest/toolchains/index.js +1 -80
  375. package/dist/guest/toolchains/install.d.ts +20 -0
  376. package/dist/guest/toolchains/install.js +29 -0
  377. package/dist/guest/toolchains/languages/go.d.ts +3 -0
  378. package/dist/guest/toolchains/{go.js → languages/go.js} +27 -9
  379. package/dist/guest/toolchains/languages/java.d.ts +8 -0
  380. package/dist/guest/toolchains/{java.js → languages/java.js} +57 -18
  381. package/dist/guest/toolchains/languages/node.d.ts +3 -0
  382. package/dist/guest/toolchains/{node.js → languages/node.js} +51 -18
  383. package/dist/guest/toolchains/languages/php.d.ts +3 -0
  384. package/dist/guest/toolchains/{scripting → languages}/php.js +36 -11
  385. package/dist/guest/toolchains/languages/python-tools.d.ts +3 -0
  386. package/dist/guest/toolchains/languages/python-tools.js +12 -0
  387. package/dist/guest/toolchains/languages/python.d.ts +3 -0
  388. package/dist/guest/toolchains/{python.js → languages/python.js} +29 -16
  389. package/dist/guest/toolchains/languages/ruby.d.ts +3 -0
  390. package/dist/guest/toolchains/{scripting → languages}/ruby.js +28 -10
  391. package/dist/guest/toolchains/leftovers.d.ts +1 -1
  392. package/dist/guest/toolchains/leftovers.js +13 -11
  393. package/dist/guest/toolchains/managers.d.ts +6 -6
  394. package/dist/guest/toolchains/managers.js +38 -17
  395. package/dist/guest/toolchains/native/binstall.d.ts +3 -2
  396. package/dist/guest/toolchains/native/binstall.js +20 -8
  397. package/dist/guest/toolchains/native/cpp.d.ts +2 -2
  398. package/dist/guest/toolchains/native/cpp.js +23 -8
  399. package/dist/guest/toolchains/native/dotnet.d.ts +5 -3
  400. package/dist/guest/toolchains/native/dotnet.js +72 -24
  401. package/dist/guest/toolchains/native/mesa.d.ts +2 -2
  402. package/dist/guest/toolchains/native/mesa.js +27 -10
  403. package/dist/guest/toolchains/native/msvc/environment.d.ts +4 -4
  404. package/dist/guest/toolchains/native/msvc/environment.js +30 -16
  405. package/dist/guest/toolchains/native/msvc/index.d.ts +3 -3
  406. package/dist/guest/toolchains/native/msvc/index.js +10 -8
  407. package/dist/guest/toolchains/native/msvc/packages.d.ts +1 -1
  408. package/dist/guest/toolchains/native/msvc/packages.js +69 -23
  409. package/dist/guest/toolchains/native/msvc/tree.d.ts +6 -6
  410. package/dist/guest/toolchains/native/msvc/tree.js +15 -9
  411. package/dist/guest/toolchains/native/rust-extras.d.ts +23 -0
  412. package/dist/guest/toolchains/native/rust-extras.js +100 -0
  413. package/dist/guest/toolchains/native/rust.d.ts +2 -2
  414. package/dist/guest/toolchains/native/rust.js +41 -10
  415. package/dist/guest/toolchains/{scripting → native}/vcruntime.d.ts +2 -2
  416. package/dist/guest/toolchains/{scripting → native}/vcruntime.js +17 -7
  417. package/dist/guest/toolchains/package-ledger.d.ts +17 -0
  418. package/dist/guest/toolchains/package-ledger.js +54 -0
  419. package/dist/guest/toolchains/plan.d.ts +3 -24
  420. package/dist/guest/toolchains/plan.js +99 -111
  421. package/dist/guest/toolchains/runtimes.d.ts +6 -6
  422. package/dist/guest/toolchains/runtimes.js +31 -13
  423. package/dist/guest/toolchains/scheduler.d.ts +22 -0
  424. package/dist/guest/toolchains/scheduler.js +123 -0
  425. package/dist/guest/toolchains/setup.d.ts +10 -0
  426. package/dist/guest/toolchains/setup.js +22 -0
  427. package/dist/guest/toolchains/state-file.d.ts +3 -0
  428. package/dist/guest/toolchains/state-file.js +10 -0
  429. package/dist/guest/toolchains/step.d.ts +24 -0
  430. package/dist/guest/toolchains/step.js +1 -0
  431. package/dist/guest/toolchains/tool-env.d.ts +12 -0
  432. package/dist/guest/toolchains/tool-env.js +16 -0
  433. package/dist/guest/toolchains/tools/cli.d.ts +10 -0
  434. package/dist/guest/toolchains/{cli.js → tools/cli.js} +35 -15
  435. package/dist/guest/toolchains/tools/git.d.ts +3 -0
  436. package/dist/guest/toolchains/{git.js → tools/git.js} +38 -15
  437. package/dist/guest/toolchains/tools/pwsh.d.ts +4 -0
  438. package/dist/guest/toolchains/{pwsh.js → tools/pwsh.js} +14 -7
  439. package/dist/guest/toolchains/{vscode-profile.d.ts → tools/vscode-profile.d.ts} +6 -6
  440. package/dist/guest/toolchains/{vscode-profile.js → tools/vscode-profile.js} +28 -12
  441. package/dist/guest/toolchains/{vscode.d.ts → tools/vscode.d.ts} +6 -6
  442. package/dist/guest/toolchains/{vscode.js → tools/vscode.js} +67 -21
  443. package/dist/guest/toolchains/version-cache.d.ts +16 -0
  444. package/dist/guest/toolchains/version-cache.js +67 -0
  445. package/dist/host/agent-commands/commands.d.ts +13 -0
  446. package/dist/host/agent-commands/commands.js +33 -0
  447. package/dist/host/agent-commands/forwarders.d.ts +7 -0
  448. package/dist/host/agent-commands/forwarders.js +31 -0
  449. package/dist/host/agent-commands/index.d.ts +4 -0
  450. package/dist/host/agent-commands/index.js +4 -0
  451. package/dist/host/agent-commands/reconcile.d.ts +12 -0
  452. package/dist/host/agent-commands/reconcile.js +69 -0
  453. package/dist/host/agent-commands/user-path.d.ts +2 -0
  454. package/dist/host/agent-commands/user-path.js +33 -0
  455. package/dist/host/audit/collect.d.ts +2 -2
  456. package/dist/host/audit/collect.js +59 -22
  457. package/dist/host/audit/log.d.ts +2 -2
  458. package/dist/host/audit/log.js +24 -8
  459. package/dist/host/{attach.d.ts → channel/attach.d.ts} +2 -2
  460. package/dist/host/{attach.js → channel/attach.js} +33 -15
  461. package/dist/host/channel/channel.d.ts +51 -0
  462. package/dist/host/channel/channel.js +196 -0
  463. package/dist/host/channel/index.d.ts +2 -0
  464. package/dist/host/channel/index.js +2 -0
  465. package/dist/host/guard/agent-files.d.ts +7 -0
  466. package/dist/host/guard/agent-files.js +103 -0
  467. package/dist/host/guard/commands.d.ts +10 -0
  468. package/dist/host/guard/commands.js +123 -0
  469. package/dist/host/guard/file-hash.d.ts +4 -0
  470. package/dist/host/guard/file-hash.js +37 -0
  471. package/dist/host/guard/git-config.d.ts +5 -0
  472. package/dist/host/guard/git-config.js +62 -0
  473. package/dist/host/guard/git-dirs.d.ts +12 -0
  474. package/dist/host/guard/git-dirs.js +53 -0
  475. package/dist/host/guard/index.d.ts +5 -0
  476. package/dist/host/guard/index.js +4 -0
  477. package/dist/host/guard/mark-of-the-web.d.ts +8 -0
  478. package/dist/host/guard/mark-of-the-web.js +74 -0
  479. package/dist/host/guard/nested-repos.d.ts +14 -0
  480. package/dist/host/guard/nested-repos.js +88 -0
  481. package/dist/host/guard/quarantine.d.ts +3 -0
  482. package/dist/host/guard/quarantine.js +24 -0
  483. package/dist/host/guard/repo-guard.d.ts +10 -0
  484. package/dist/host/guard/repo-guard.js +36 -0
  485. package/dist/host/guard/store.d.ts +40 -0
  486. package/dist/host/guard/store.js +145 -0
  487. package/dist/host/guard/types.d.ts +27 -0
  488. package/dist/host/guard/types.js +3 -0
  489. package/dist/host/guard/watch-state.d.ts +9 -0
  490. package/dist/host/guard/watch-state.js +26 -0
  491. package/dist/host/launch/boot-projects.d.ts +17 -0
  492. package/dist/host/launch/{projects.js → boot-projects.js} +27 -12
  493. package/dist/host/{database-plan.d.ts → launch/database-plan.d.ts} +5 -5
  494. package/dist/host/{database-plan.js → launch/database-plan.js} +24 -13
  495. package/dist/host/launch/generate.d.ts +4 -0
  496. package/dist/host/launch/generate.js +77 -0
  497. package/dist/host/launch/index.d.ts +10 -0
  498. package/dist/host/launch/index.js +10 -0
  499. package/dist/host/launch/mappings.d.ts +7 -2
  500. package/dist/host/launch/mappings.js +83 -24
  501. package/dist/host/launch/pc-scan.d.ts +5 -0
  502. package/dist/host/{pc-scan.js → launch/pc-scan.js} +20 -13
  503. package/dist/host/launch/project-scan.d.ts +7 -0
  504. package/dist/host/{project-scan.js → launch/project-scan.js} +29 -21
  505. package/dist/host/launch/session-file.d.ts +23 -0
  506. package/dist/host/launch/session-file.js +73 -0
  507. package/dist/host/launch/stage.d.ts +5 -5
  508. package/dist/host/launch/stage.js +14 -13
  509. package/dist/host/launch/toolchains.d.ts +4 -0
  510. package/dist/host/launch/toolchains.js +11 -0
  511. package/dist/host/launch/{wsb.d.ts → wsb-file.d.ts} +4 -2
  512. package/dist/host/launch/wsb-file.js +56 -0
  513. package/dist/host/life/crash-report.d.ts +8 -0
  514. package/dist/host/life/crash-report.js +87 -0
  515. package/dist/host/life/index.d.ts +6 -0
  516. package/dist/host/life/index.js +6 -0
  517. package/dist/host/life/keep-awake.d.ts +10 -0
  518. package/dist/host/life/keep-awake.js +29 -0
  519. package/dist/host/life/life-record.d.ts +48 -0
  520. package/dist/host/life/life-record.js +76 -0
  521. package/dist/host/life/stop-events.d.ts +27 -0
  522. package/dist/host/life/stop-events.js +96 -0
  523. package/dist/host/life/stop-warning.d.ts +4 -0
  524. package/dist/host/life/stop-warning.js +25 -0
  525. package/dist/host/life/windows-update.d.ts +3 -0
  526. package/dist/host/life/windows-update.js +11 -0
  527. package/dist/host/native.d.ts +4 -0
  528. package/dist/host/native.js +11 -0
  529. package/dist/host/network/index.d.ts +1 -0
  530. package/dist/host/network/index.js +1 -0
  531. package/dist/host/{port-forward.d.ts → network/port-forward.d.ts} +1 -1
  532. package/dist/host/{port-forward.js → network/port-forward.js} +16 -10
  533. package/dist/host/profile/accounts.d.ts +37 -0
  534. package/dist/host/profile/accounts.js +146 -0
  535. package/dist/host/profile/claude-login.d.ts +22 -0
  536. package/dist/host/profile/claude-login.js +57 -0
  537. package/dist/host/{claude-profile.d.ts → profile/claude-profile.d.ts} +9 -9
  538. package/dist/host/{claude-profile.js → profile/claude-profile.js} +32 -22
  539. package/dist/host/profile/index.d.ts +7 -0
  540. package/dist/host/profile/index.js +7 -0
  541. package/dist/host/profile/personalize.d.ts +2 -0
  542. package/dist/host/profile/personalize.js +127 -0
  543. package/dist/host/profile/reg-file.d.ts +8 -0
  544. package/dist/host/profile/reg-file.js +45 -0
  545. package/dist/host/profile/secret-files.d.ts +2 -0
  546. package/dist/host/profile/secret-files.js +25 -0
  547. package/dist/host/profile/secret-patterns.d.ts +6 -0
  548. package/dist/host/profile/secret-patterns.js +128 -0
  549. package/dist/host/profile/secrets.d.ts +12 -0
  550. package/dist/host/profile/secrets.js +100 -0
  551. package/dist/host/projects/index.d.ts +4 -0
  552. package/dist/host/projects/index.js +4 -0
  553. package/dist/host/projects/join.d.ts +38 -0
  554. package/dist/host/projects/join.js +119 -0
  555. package/dist/host/projects/known.d.ts +28 -0
  556. package/dist/host/projects/known.js +55 -0
  557. package/dist/host/projects/mapper.d.ts +29 -0
  558. package/dist/host/projects/mapper.js +88 -0
  559. package/dist/host/projects/registry.d.ts +83 -0
  560. package/dist/host/projects/registry.js +136 -0
  561. package/dist/host/sandbox/doctor.d.ts +2 -0
  562. package/dist/host/sandbox/doctor.js +42 -0
  563. package/dist/host/sandbox/host-claude.d.ts +1 -0
  564. package/dist/host/sandbox/host-claude.js +26 -0
  565. package/dist/host/sandbox/index.d.ts +6 -0
  566. package/dist/host/sandbox/index.js +6 -0
  567. package/dist/host/{platform.d.ts → sandbox/platform.d.ts} +0 -5
  568. package/dist/host/sandbox/platform.js +60 -0
  569. package/dist/host/sandbox/probe.d.ts +3 -0
  570. package/dist/host/sandbox/probe.js +47 -0
  571. package/dist/host/{window.js → sandbox/window.js} +7 -5
  572. package/dist/host/{wsb.js → sandbox/wsb-cli.js} +31 -8
  573. package/dist/host/{cleanup.js → storage/cleanup.js} +27 -12
  574. package/dist/host/storage/index.d.ts +2 -0
  575. package/dist/host/storage/index.js +2 -0
  576. package/dist/host/storage/state-files.d.ts +4 -0
  577. package/dist/host/storage/state-files.js +10 -0
  578. package/dist/host/test-guard.js +1 -1
  579. package/dist/host/tools/conpty.d.ts +8 -0
  580. package/dist/host/tools/conpty.js +58 -0
  581. package/dist/host/tools/index.d.ts +2 -0
  582. package/dist/host/tools/index.js +2 -0
  583. package/dist/host/{terminal.d.ts → tools/windows-terminal.d.ts} +2 -10
  584. package/dist/host/{terminal.js → tools/windows-terminal.js} +33 -57
  585. package/dist/host/workspace-view/active-views.d.ts +3 -0
  586. package/dist/host/workspace-view/active-views.js +22 -0
  587. package/dist/host/workspace-view/index.d.ts +2 -18
  588. package/dist/host/workspace-view/index.js +2 -49
  589. package/dist/host/workspace-view/plan.d.ts +16 -0
  590. package/dist/host/workspace-view/plan.js +50 -0
  591. package/dist/host/workspace-view/rules.js +10 -3
  592. package/dist/host/workspace-view/view-state.d.ts +10 -0
  593. package/dist/host/workspace-view/view-state.js +31 -0
  594. package/dist/host/workspace-view/view.d.ts +1 -0
  595. package/dist/host/workspace-view/view.js +51 -55
  596. package/dist/runtime/daemon.js +4232 -1918
  597. package/dist/runtime/db.js +2145 -830
  598. package/dist/runtime/gitguard.js +29 -11
  599. package/dist/runtime/main.js +4760 -2155
  600. package/dist/runtime/netproxy.js +1654 -418
  601. package/dist/runtime/view.js +1515 -453
  602. package/dist/selftest/accounts.test.d.ts +1 -1
  603. package/dist/selftest/accounts.test.js +20 -17
  604. package/dist/selftest/agent-commands.test.d.ts +1 -1
  605. package/dist/selftest/agent-commands.test.js +9 -5
  606. package/dist/selftest/audit.test.d.ts +1 -1
  607. package/dist/selftest/audit.test.js +46 -13
  608. package/dist/selftest/channel.test.d.ts +1 -1
  609. package/dist/selftest/channel.test.js +91 -8
  610. package/dist/selftest/cli.test.d.ts +1 -1
  611. package/dist/selftest/cli.test.js +68 -16
  612. package/dist/selftest/cmdline.test.d.ts +1 -1
  613. package/dist/selftest/cmdline.test.js +36 -7
  614. package/dist/selftest/config.test.d.ts +1 -1
  615. package/dist/selftest/config.test.js +12 -5
  616. package/dist/selftest/data.test.d.ts +1 -1
  617. package/dist/selftest/data.test.js +35 -6
  618. package/dist/selftest/detect.test.d.ts +1 -1
  619. package/dist/selftest/detect.test.js +22 -9
  620. package/dist/selftest/generate.test.d.ts +1 -1
  621. package/dist/selftest/generate.test.js +33 -9
  622. package/dist/selftest/git-access.test.d.ts +1 -1
  623. package/dist/selftest/git-access.test.js +42 -4
  624. package/dist/selftest/guest-boot.test.d.ts +1 -1
  625. package/dist/selftest/guest-boot.test.js +25 -7
  626. package/dist/selftest/host-sim.test.d.ts +1 -1
  627. package/dist/selftest/host-sim.test.js +27 -3
  628. package/dist/selftest/hostile-input.test.d.ts +1 -1
  629. package/dist/selftest/hostile-input.test.js +5 -5
  630. package/dist/selftest/index.d.ts +1 -1
  631. package/dist/selftest/index.js +73 -16
  632. package/dist/selftest/layout.test.d.ts +2 -0
  633. package/dist/selftest/layout.test.js +131 -0
  634. package/dist/selftest/links.test.d.ts +1 -1
  635. package/dist/selftest/links.test.js +16 -11
  636. package/dist/selftest/live-daemon.test.d.ts +1 -1
  637. package/dist/selftest/live-daemon.test.js +20 -8
  638. package/dist/selftest/live-git.test.d.ts +1 -1
  639. package/dist/selftest/live-git.test.js +13 -4
  640. package/dist/selftest/live-network.test.d.ts +1 -1
  641. package/dist/selftest/live-network.test.js +24 -9
  642. package/dist/selftest/live-projects.test.d.ts +1 -1
  643. package/dist/selftest/live-projects.test.js +47 -20
  644. package/dist/selftest/live-terminal.test.d.ts +2 -0
  645. package/dist/selftest/live-terminal.test.js +195 -0
  646. package/dist/selftest/local-config.test.d.ts +1 -1
  647. package/dist/selftest/local-config.test.js +88 -15
  648. package/dist/selftest/mirror.test.d.ts +1 -1
  649. package/dist/selftest/mirror.test.js +118 -19
  650. package/dist/selftest/network.test.d.ts +1 -1
  651. package/dist/selftest/network.test.js +17 -8
  652. package/dist/selftest/personalization.test.d.ts +1 -1
  653. package/dist/selftest/personalization.test.js +12 -5
  654. package/dist/selftest/projects.test.d.ts +1 -1
  655. package/dist/selftest/projects.test.js +336 -72
  656. package/dist/selftest/repair.test.d.ts +1 -1
  657. package/dist/selftest/repair.test.js +17 -5
  658. package/dist/selftest/repo-guard.test.d.ts +1 -1
  659. package/dist/selftest/repo-guard.test.js +21 -20
  660. package/dist/selftest/secrets.test.d.ts +1 -1
  661. package/dist/selftest/secrets.test.js +57 -13
  662. package/dist/selftest/setup.test.d.ts +1 -1
  663. package/dist/selftest/setup.test.js +25 -5
  664. package/dist/selftest/{fixtures.d.ts → support/fixtures.d.ts} +7 -4
  665. package/dist/selftest/{fixtures.js → support/fixtures.js} +95 -17
  666. package/dist/selftest/{harness.js → support/harness.js} +24 -7
  667. package/dist/selftest/{live.d.ts → support/live.d.ts} +2 -2
  668. package/dist/selftest/{live.js → support/live.js} +34 -10
  669. package/dist/selftest/support/suite.js +1 -0
  670. package/dist/selftest/sync.test.d.ts +1 -1
  671. package/dist/selftest/sync.test.js +64 -26
  672. package/dist/selftest/test-guard.test.d.ts +1 -1
  673. package/dist/selftest/test-guard.test.js +43 -15
  674. package/dist/selftest/toolchains.test.d.ts +1 -1
  675. package/dist/selftest/toolchains.test.js +135 -36
  676. package/dist/selftest/workspace-view.test.d.ts +1 -1
  677. package/dist/selftest/workspace-view.test.js +48 -11
  678. package/dist/selftest/workspace.test.d.ts +1 -1
  679. package/dist/selftest/workspace.test.js +13 -4
  680. package/dist/shared/{apps.d.ts → apps/catalogue.d.ts} +1 -11
  681. package/dist/shared/apps/catalogue.js +768 -0
  682. package/dist/shared/apps/index.d.ts +2 -0
  683. package/dist/shared/apps/index.js +2 -0
  684. package/dist/shared/apps/selection.d.ts +11 -0
  685. package/dist/shared/apps/selection.js +40 -0
  686. package/dist/shared/channel/index.d.ts +2 -0
  687. package/dist/shared/channel/index.js +2 -0
  688. package/dist/shared/channel/run-dir.d.ts +30 -0
  689. package/dist/shared/channel/run-dir.js +84 -0
  690. package/dist/shared/{schema.d.ts → channel/schema.d.ts} +60 -19
  691. package/dist/shared/channel/schema.js +131 -0
  692. package/dist/shared/download/archive.d.ts +13 -0
  693. package/dist/shared/download/archive.js +57 -0
  694. package/dist/shared/download/downloader.d.ts +35 -0
  695. package/dist/shared/download/downloader.js +110 -0
  696. package/dist/shared/download/github.d.ts +16 -0
  697. package/dist/shared/download/github.js +74 -0
  698. package/dist/shared/download/http.d.ts +5 -0
  699. package/dist/shared/download/http.js +30 -0
  700. package/dist/shared/download/index.d.ts +5 -0
  701. package/dist/shared/download/index.js +5 -0
  702. package/dist/shared/download/spawn.d.ts +2 -0
  703. package/dist/shared/download/spawn.js +7 -0
  704. package/dist/shared/io/index.d.ts +3 -0
  705. package/dist/shared/io/index.js +3 -0
  706. package/dist/shared/io/json-file.d.ts +10 -0
  707. package/dist/shared/io/json-file.js +21 -0
  708. package/dist/shared/io/json.d.ts +13 -0
  709. package/dist/shared/io/json.js +49 -0
  710. package/dist/shared/{safe-remove.js → io/safe-remove.js} +32 -7
  711. package/dist/shared/{netrules.js → network/firewall-rules.js} +36 -5
  712. package/dist/shared/network/index.d.ts +2 -0
  713. package/dist/shared/network/index.js +2 -0
  714. package/dist/shared/{netpolicy.js → network/policy.js} +66 -11
  715. package/dist/shared/{paths.js → paths/claude-paths.js} +12 -2
  716. package/dist/shared/{guest-paths.d.ts → paths/guest-paths.d.ts} +21 -0
  717. package/dist/shared/paths/guest-paths.js +146 -0
  718. package/dist/shared/paths/index.d.ts +3 -0
  719. package/dist/shared/paths/index.js +3 -0
  720. package/dist/shared/{projects.d.ts → paths/projects.d.ts} +27 -4
  721. package/dist/shared/paths/projects.js +136 -0
  722. package/dist/shared/pty/client.d.ts +21 -0
  723. package/dist/shared/pty/client.js +79 -0
  724. package/dist/shared/pty/console-input.d.ts +11 -0
  725. package/dist/shared/pty/console-input.js +81 -0
  726. package/dist/shared/pty/index.d.ts +6 -0
  727. package/dist/shared/pty/index.js +6 -0
  728. package/dist/shared/pty/input-joiner.d.ts +15 -0
  729. package/dist/shared/pty/input-joiner.js +44 -0
  730. package/dist/shared/pty/output-filter.d.ts +13 -0
  731. package/dist/shared/pty/output-filter.js +140 -0
  732. package/dist/shared/pty/protocol.d.ts +36 -0
  733. package/dist/shared/pty/protocol.js +60 -0
  734. package/dist/shared/pty/terminal.d.ts +24 -0
  735. package/dist/shared/pty/terminal.js +147 -0
  736. package/dist/shared/test-guard.js +3 -1
  737. package/dist/sync/claude-mem.d.ts +1 -1
  738. package/dist/sync/claude-mem.js +9 -4
  739. package/dist/sync/history.d.ts +1 -29
  740. package/dist/sync/history.js +57 -45
  741. package/dist/sync/index.d.ts +5 -0
  742. package/dist/sync/index.js +5 -0
  743. package/dist/sync/sync-cycle.d.ts +18 -0
  744. package/dist/sync/sync-cycle.js +85 -0
  745. package/dist/sync/sync-state.d.ts +32 -0
  746. package/dist/sync/sync-state.js +37 -0
  747. package/dist/sync/watcher.d.ts +0 -18
  748. package/dist/sync/watcher.js +66 -100
  749. package/native/keep-awake.ps1 +8 -0
  750. package/native/power.cs +23 -0
  751. package/native/stop-events.ps1 +38 -0
  752. package/package.json +11 -3
  753. package/sandbox/linkgate.cs +98 -0
  754. package/sandbox/memlog.ps1 +15 -6
  755. package/sandbox/ptyhost.cs +60 -3
  756. package/schemas/config.schema.json +48 -1
  757. package/schemas/sandbox-config.schema.json +37 -3
  758. package/dist/cli/commands/launch.d.ts +0 -29
  759. package/dist/cli/commands/launch.js +0 -366
  760. package/dist/cli/commands/maintenance.d.ts +0 -10
  761. package/dist/cli/commands/maintenance.js +0 -187
  762. package/dist/cli/commands/project.d.ts +0 -29
  763. package/dist/cli/commands/project.js +0 -63
  764. package/dist/cli/commands/projects.d.ts +0 -14
  765. package/dist/cli/commands/projects.js +0 -146
  766. package/dist/cli/commands/restart.d.ts +0 -6
  767. package/dist/cli/commands/restart.js +0 -85
  768. package/dist/cli/commands/sessions.d.ts +0 -12
  769. package/dist/cli/commands/sessions.js +0 -257
  770. package/dist/cli/menu/settings/editors.js +0 -305
  771. package/dist/cli/progress.d.ts +0 -9
  772. package/dist/cli/progress.js +0 -49
  773. package/dist/cli/tui/dialogs.d.ts +0 -57
  774. package/dist/cli/tui/dialogs.js +0 -162
  775. package/dist/core/config.js +0 -109
  776. package/dist/core/schema.d.ts +0 -6
  777. package/dist/core/schema.js +0 -8
  778. package/dist/core/util.d.ts +0 -62
  779. package/dist/core/util.js +0 -156
  780. package/dist/core/workspace.d.ts +0 -18
  781. package/dist/core/workspace.js +0 -145
  782. package/dist/guest/boot-env.d.ts +0 -4
  783. package/dist/guest/daemon.js +0 -57
  784. package/dist/guest/env.d.ts +0 -52
  785. package/dist/guest/env.js +0 -103
  786. package/dist/guest/gitrules.js +0 -11
  787. package/dist/guest/project-status.d.ts +0 -5
  788. package/dist/guest/project-status.js +0 -17
  789. package/dist/guest/setup/user.js +0 -51
  790. package/dist/guest/toolchains/cli.d.ts +0 -10
  791. package/dist/guest/toolchains/common.d.ts +0 -54
  792. package/dist/guest/toolchains/common.js +0 -147
  793. package/dist/guest/toolchains/databases/dynamodb.d.ts +0 -8
  794. package/dist/guest/toolchains/databases/dynamodb.js +0 -52
  795. package/dist/guest/toolchains/databases/index.d.ts +0 -6
  796. package/dist/guest/toolchains/databases/index.js +0 -62
  797. package/dist/guest/toolchains/databases/mongodb.d.ts +0 -3
  798. package/dist/guest/toolchains/databases/mongodb.js +0 -31
  799. package/dist/guest/toolchains/databases/mysql.d.ts +0 -3
  800. package/dist/guest/toolchains/databases/mysql.js +0 -38
  801. package/dist/guest/toolchains/databases/postgres.d.ts +0 -5
  802. package/dist/guest/toolchains/databases/postgres.js +0 -38
  803. package/dist/guest/toolchains/databases/redis.d.ts +0 -3
  804. package/dist/guest/toolchains/databases/redis.js +0 -16
  805. package/dist/guest/toolchains/git.d.ts +0 -3
  806. package/dist/guest/toolchains/go.d.ts +0 -3
  807. package/dist/guest/toolchains/java.d.ts +0 -8
  808. package/dist/guest/toolchains/node.d.ts +0 -3
  809. package/dist/guest/toolchains/pwsh.d.ts +0 -4
  810. package/dist/guest/toolchains/python.d.ts +0 -3
  811. package/dist/guest/toolchains/scripting/php.d.ts +0 -3
  812. package/dist/guest/toolchains/scripting/ruby.d.ts +0 -3
  813. package/dist/host/accounts.d.ts +0 -32
  814. package/dist/host/accounts.js +0 -128
  815. package/dist/host/agent-commands.d.ts +0 -25
  816. package/dist/host/agent-commands.js +0 -145
  817. package/dist/host/channel.d.ts +0 -38
  818. package/dist/host/channel.js +0 -162
  819. package/dist/host/generate.d.ts +0 -13
  820. package/dist/host/generate.js +0 -108
  821. package/dist/host/join.d.ts +0 -28
  822. package/dist/host/join.js +0 -76
  823. package/dist/host/launch/projects.d.ts +0 -15
  824. package/dist/host/launch/wsb.js +0 -34
  825. package/dist/host/motw.d.ts +0 -17
  826. package/dist/host/motw.js +0 -74
  827. package/dist/host/pc-scan.d.ts +0 -6
  828. package/dist/host/personalize.d.ts +0 -10
  829. package/dist/host/personalize.js +0 -97
  830. package/dist/host/platform.js +0 -129
  831. package/dist/host/project-scan.d.ts +0 -7
  832. package/dist/host/projects.d.ts +0 -100
  833. package/dist/host/projects.js +0 -147
  834. package/dist/host/repo-guard.d.ts +0 -43
  835. package/dist/host/repo-guard.js +0 -347
  836. package/dist/host/secrets.d.ts +0 -14
  837. package/dist/host/secrets.js +0 -133
  838. package/dist/host/stops.d.ts +0 -67
  839. package/dist/host/stops.js +0 -126
  840. package/dist/shared/apps.js +0 -114
  841. package/dist/shared/download.d.ts +0 -60
  842. package/dist/shared/download.js +0 -215
  843. package/dist/shared/guest-paths.js +0 -61
  844. package/dist/shared/projects.js +0 -67
  845. package/dist/shared/pty-client.d.ts +0 -91
  846. package/dist/shared/pty-client.js +0 -349
  847. package/dist/shared/rundir.d.ts +0 -41
  848. package/dist/shared/rundir.js +0 -96
  849. package/dist/shared/schema.js +0 -92
  850. /package/dist/{guest/daemon.d.ts → cli/tui/list/list-types.js} +0 -0
  851. /package/dist/{guest/db.d.ts → cli/tui/screen.js} +0 -0
  852. /package/dist/core/{detect-databases.d.ts → detect/databases.d.ts} +0 -0
  853. /package/dist/{guest/toolchains → core/detect}/versions.d.ts +0 -0
  854. /package/dist/guest/{setup/agents.d.ts → agents/agent-installer.d.ts} +0 -0
  855. /package/dist/guest/{audit-parse.d.ts → audit/parse.d.ts} +0 -0
  856. /package/dist/guest/{guide.d.ts → boot/guide.d.ts} +0 -0
  857. /package/dist/guest/daemon/{shutdown.d.ts → idle-shutdown.d.ts} +0 -0
  858. /package/dist/guest/daemon/{session.d.ts → sessions.d.ts} +0 -0
  859. /package/dist/guest/{main.d.ts → entries/daemon.d.ts} +0 -0
  860. /package/dist/guest/{netproxy.d.ts → entries/db.d.ts} +0 -0
  861. /package/dist/{selftest/suite.js → guest/entries/gitguard.d.ts} +0 -0
  862. /package/dist/guest/{gitguard.d.ts → git/decision.d.ts} +0 -0
  863. /package/dist/guest/{ports.d.ts → runtime/ports.d.ts} +0 -0
  864. /package/dist/guest/{project-context.d.ts → runtime/project-context.d.ts} +0 -0
  865. /package/dist/guest/{cmdline.d.ts → sessions/cmdline.d.ts} +0 -0
  866. /package/dist/guest/{ptyhost.d.ts → sessions/pty-host.d.ts} +0 -0
  867. /package/dist/guest/{view.d.ts → sessions/viewer.d.ts} +0 -0
  868. /package/dist/guest/setup/{policy.d.ts → managed-policy.d.ts} +0 -0
  869. /package/dist/host/{window.d.ts → sandbox/window.d.ts} +0 -0
  870. /package/dist/host/{wsb.d.ts → sandbox/wsb-cli.d.ts} +0 -0
  871. /package/dist/host/{cleanup.d.ts → storage/cleanup.d.ts} +0 -0
  872. /package/dist/selftest/{harness.d.ts → support/harness.d.ts} +0 -0
  873. /package/dist/selftest/{suite.d.ts → support/suite.d.ts} +0 -0
  874. /package/dist/shared/{safe-remove.d.ts → io/safe-remove.d.ts} +0 -0
  875. /package/dist/shared/{netrules.d.ts → network/firewall-rules.d.ts} +0 -0
  876. /package/dist/shared/{netpolicy.d.ts → network/policy.d.ts} +0 -0
  877. /package/dist/shared/{paths.d.ts → paths/claude-paths.d.ts} +0 -0
@@ -2,8 +2,8 @@
2
2
  // PowerShell 7 profile. The Windows Terminal part is only tried on temp copies.
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
- import { toRegFile } from '../host/personalize.js';
6
- import { PWSH_PROFILE_GUID, syncPwshProfile, withPwshProfile } from '../host/terminal.js';
5
+ import { toRegFile } from '../host/profile/index.js';
6
+ import { PWSH_PROFILE_GUID, syncPwshProfile, withPwshProfile } from '../host/tools/index.js';
7
7
  export const personalizationSuite = {
8
8
  name: 'personalization',
9
9
  title: 'registry personalisation (.reg) and the Windows Terminal PowerShell 7 profile',
@@ -27,9 +27,16 @@ export const personalizationSuite = {
27
27
  t.check(Boolean(shownProfiles?.defaults) && shown?.theme === 'dark', 'the rest of the settings are kept');
28
28
  t.equal(withPwshProfile(shown, true), null, 'nothing is rewritten when the profile is already there');
29
29
  t.equal(withPwshProfile({ profiles: { defaults: {} } }, false), null, 'nothing is rewritten when pwsh was never wanted');
30
- const edited = { profiles: { list: [{ guid: 'other' }, { guid: PWSH_PROFILE_GUID.toUpperCase(), name: 'My pwsh', fontSize: 14 }] } };
30
+ const edited = {
31
+ profiles: { list: [{ guid: 'other' }, { guid: PWSH_PROFILE_GUID.toUpperCase(), name: 'My pwsh', fontSize: 14 }] },
32
+ };
31
33
  const hidden = withPwshProfile(edited, false)?.profiles;
32
- t.equal({ hidden: hidden?.list[1].hidden, name: hidden?.list[1].name, fontSize: hidden?.list[1].fontSize, count: hidden?.list.length }, { hidden: true, name: 'My pwsh', fontSize: 14, count: 2 }, 'turning pwsh off hides the profile and keeps the user\'s edits');
34
+ t.equal({
35
+ hidden: hidden?.list[1].hidden,
36
+ name: hidden?.list[1].name,
37
+ fontSize: hidden?.list[1].fontSize,
38
+ count: hidden?.list.length,
39
+ }, { hidden: true, name: 'My pwsh', fontSize: 14, count: 2 }, "turning pwsh off hides the profile and keeps the user's edits");
33
40
  const oldForm = withPwshProfile({ profiles: [{ guid: 'a' }] }, true)?.profiles;
34
41
  t.equal(oldForm.list.length, 2, 'the old array form of "profiles" works');
35
42
  t.section('Windows Terminal settings.json on disk (JSONC, temp copy)');
@@ -42,7 +49,7 @@ export const personalizationSuite = {
42
49
  t.write(path.join(data, 'tools', 'pwsh', 'pwsh.exe'), 'MZ');
43
50
  syncPwshProfile(data, true);
44
51
  const added = read();
45
- t.equal(added.theme, 'light', 'once pwsh is installed the profile is added and the user\'s settings are kept');
52
+ t.equal(added.theme, 'light', "once pwsh is installed the profile is added and the user's settings are kept");
46
53
  t.equal(added.profiles.list.map((p) => p.guid), ['{x}', PWSH_PROFILE_GUID], 'the profile is added after the existing ones');
47
54
  syncPwshProfile(data, false);
48
55
  t.equal(read().profiles.list[1].hidden, true, 'the profile is hidden when pwsh is turned off');
@@ -1,2 +1,2 @@
1
- import type { Suite } from './suite.js';
1
+ import type { Suite } from './support/suite.js';
2
2
  export declare const projectsSuite: Suite;
@@ -3,33 +3,37 @@
3
3
  // environment, history sync and the repo guard for every project. Offline: temp folders only.
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
- import { LocalOverrides } from '../core/schema.js';
7
- import { checkJoin, protectionsOf } from '../core/project-policy.js';
8
- import { sessionsFirstIn } from '../host/channel.js';
9
- import { buildWsb } from '../host/launch/wsb.js';
10
- import { projectSpec, joinRunning } from '../host/join.js';
11
- import { allProjectsToMap, forgetProject, NeedsRestart, projectHere, projectMapper, readKnown, readRegistry, rememberProject, startRegistry } from '../host/projects.js';
12
- import { checkRepo, saveRepoSnapshot } from '../host/repo-guard.js';
13
- import { findWsb, parseCommands, parseIp, parseList, parseVersion } from '../host/wsb.js';
14
- import { baseEnvFor } from '../guest/project-env.js';
15
- import { guestDirFor, hostDirFor, hubName, insideProjects, isBelow, isWithin, planProjectMappings, projectForGuestDir, projectForHostDir, PROJECTS_GUEST } from '../shared/projects.js';
16
- import { GuestRequest, ProjectSpec } from '../shared/schema.js';
17
- import { projectFolderName } from '../shared/paths.js';
18
- import { syncHistoryOut } from '../sync/history.js';
6
+ import { LocalOverridesSchema } from '../core/schema/index.js';
7
+ import { allowStorePath, checkJoin, describeMissing, missingAtBoot, missingTip, nothingMissing, protectionsOf, saveAllowed, } from '../core/local-config/index.js';
8
+ import { sessionsFirstIn } from '../host/channel/index.js';
9
+ import { buildWsb } from '../host/launch/index.js';
10
+ import { projectSpec, joinRunning } from '../host/projects/index.js';
11
+ import { allProjectsToMap, KnownProjects } from '../host/projects/index.js';
12
+ import { NeedsRestart, projectMapper } from '../host/projects/index.js';
13
+ import { parseWsbBoot, ProjectRegistry } from '../host/projects/index.js';
14
+ import { RepoGuardStore } from '../host/guard/index.js';
15
+ import { findWsb, parseCommands, parseIp, parseList, parseVersion } from '../host/sandbox/index.js';
16
+ import { baseEnvFor } from '../guest/boot/project-env.js';
17
+ import { guestDirFor, hostDirFor, hubName, insideProjects, isBelow, isWithin, planProjectMappings, projectForGuestDir, projectForHostDir, PROJECTS_GUEST, rootGuestDir, } from '../shared/paths/index.js';
18
+ import { GuestRequestSchema, ProjectSpecSchema } from '../shared/channel/index.js';
19
+ import { projectFolderName } from '../shared/paths/index.js';
20
+ import { syncHistoryOut } from '../sync/index.js';
19
21
  import { validateOptions, validateProjectsArgs } from '../cli/validate.js';
20
22
  import { parseCli, parseSessionWant } from '../cli/args.js';
21
23
  import { candidateFolders, severalAgentsArgv } from '../cli/menu/index.js';
22
- import { generateSandbox } from '../host/generate.js';
24
+ import { generateSandbox } from '../host/launch/index.js';
23
25
  import { askedWhen } from '../cli/manifest.js';
24
- import { describeSessions } from '../cli/commands/restart.js';
26
+ import { describeSessions } from '../cli/commands/projects/index.js';
25
27
  /** The error `fn` throws, or null. */
26
- const caught = (fn) => { try {
27
- fn();
28
- return null;
29
- }
30
- catch (e) {
31
- return e;
32
- } };
28
+ const caught = (fn) => {
29
+ try {
30
+ fn();
31
+ return null;
32
+ }
33
+ catch (e) {
34
+ return e;
35
+ }
36
+ };
33
37
  export const projectsSuite = {
34
38
  name: 'projects',
35
39
  title: 'several projects in one sandbox (paths, joining, policy, specs, --session)',
@@ -41,7 +45,10 @@ export const projectsSuite = {
41
45
  t.equal(hubName('F:\\Code\\api', ['API', 'api-2']), 'api-3', 'project ids are unique (case-insensitive)');
42
46
  t.equal(hubName('F:\\Code\\...', []), 'project', 'project ids are never empty');
43
47
  t.equal(hubName('F:\\' + 'x'.repeat(80), []).length, 48, 'project ids are capped at 48 characters');
44
- const projects = [{ host: 'F:\\Code', guest: 'C:\\Projects\\Code' }, { host: 'F:\\Code\\api', guest: 'C:\\Projects\\api' }];
48
+ const projects = [
49
+ { host: 'F:\\Code', guest: 'C:\\Projects\\Code' },
50
+ { host: 'F:\\Code\\api', guest: 'C:\\Projects\\api' },
51
+ ];
45
52
  t.equal(projectForHostDir(projects, 'f:\\code\\api\\src')?.guest, 'C:\\Projects\\api', 'the most specific mapped folder wins');
46
53
  t.equal(projectForHostDir(projects, 'F:\\Code\\web')?.guest, 'C:\\Projects\\Code', 'a folder inside a mapped folder belongs to it');
47
54
  t.equal(projectForHostDir(projects, 'F:\\Other'), null, 'unrelated folders belong to no project');
@@ -57,25 +64,32 @@ export const projectsSuite = {
57
64
  t.check(isBelow('C:\\a', 'C:\\a\\b') && !isBelow('C:\\a', 'C:\\a'), 'isBelow is strict');
58
65
  const plan = planProjectMappings(['F:\\Code', 'E:\\Work\\Code', 'C:\\Users\\me\\src'], ['D:\\x\\api', 'D:\\y\\api'], false);
59
66
  t.equal(plan.roots.map((r) => r.guest), ['C:\\Projects\\Code', 'C:\\Projects\\Code-2', 'C:\\Projects\\src'], 'roots get unique C:\\Projects\\<name> folders');
60
- t.equal(plan.projects.map((p) => p.guest), ['C:\\Projects\\api', 'C:\\Projects\\api-2'], 'projects get unique C:\\Projects\\<name> folders');
67
+ t.equal(plan.projects.map((p) => p.guest), ['C:\\Workspace\\api', 'C:\\Workspace\\api-2'], 'projects mapped on their own get unique C:\\Workspace\\<name> folders (where they are as the launch folder)');
68
+ t.equal(planProjectMappings([], ['D:\\x\\app'], false, ['C:\\Workspace\\app']).projects[0].guest, 'C:\\Workspace\\app-2', "a project mapped on its own never lands on the launch folder's place");
69
+ t.equal(planProjectMappings(['F:\\Code'], ['D:\\x\\api'], true).projects[0].guest, 'D:\\x\\api', 'with "mirror" a project mapped on its own appears at its PC path, as it would as the launch folder');
70
+ t.equal(rootGuestDir(['F:\\Code'], false, 'F:\\Code\\web\\src'), 'C:\\Projects\\Code\\web\\src', "a folder inside a root is placed under the root's guest folder");
71
+ t.equal(rootGuestDir(['F:\\Code'], false, 'F:\\Other'), null, 'a folder in no root has no root place');
61
72
  const mirror = planProjectMappings(['F:\\Code', 'C:\\Users\\me\\src'], [], true);
62
73
  t.equal(mirror.roots.map((r) => r.guest), ['F:\\Code', 'C:\\Projects\\src'], 'with "mirror" a root appears at its PC path when the sandbox can show it there (C:\\Users\\... cannot)');
63
74
  t.section('projects: tighten-only protections when joining a running sandbox');
64
75
  const base = protectionsOf(f.baseConfig);
65
76
  const same = checkJoin(base, base);
66
77
  t.equal({ stricter: same.stricter, looser: same.looser }, { stricter: [], looser: [] }, 'the same settings join without notes');
67
- const readonlyGit = protectionsOf({ ...f.baseConfig, provisioning: { ...f.baseConfig.provisioning, git: { ...f.baseConfig.provisioning.git, access: 'readonly' } } });
78
+ const readonlyGit = protectionsOf({
79
+ ...f.baseConfig,
80
+ provisioning: { ...f.baseConfig.provisioning, git: { ...f.baseConfig.provisioning.git, access: 'readonly' } },
81
+ });
68
82
  t.check(checkJoin(base, readonlyGit).stricter.some((s) => s.startsWith('provisioning.git.access')), 'a project needing stricter git access than the running sandbox is refused (git access is machine-wide)');
69
83
  const loosening = checkJoin(readonlyGit, base);
70
84
  t.equal(loosening.stricter.length, 0, 'a project asking for less git access is not refused...');
71
- t.check(loosening.looser.some((s) => s.includes('provisioning.git.access stays "readonly"')), '... it gets the sandbox\'s stricter value, with a note: never loosened', JSON.stringify(loosening.looser));
85
+ t.check(loosening.looser.some((s) => s.includes('provisioning.git.access stays "readonly"')), "... it gets the sandbox's stricter value, with a note: never loosened", JSON.stringify(loosening.looser));
72
86
  const offline = protectionsOf({ ...f.baseConfig, sandbox: { ...f.baseConfig.sandbox, networking: false } });
73
87
  t.check(checkJoin(base, offline).stricter.some((s) => s.startsWith('sandbox.networking')), 'networking off is stricter than a sandbox with networking');
74
88
  t.check(checkJoin(offline, base).looser.some((s) => s.startsWith('sandbox.networking')), 'networking on is looser than an offline sandbox');
75
89
  const readOnly = protectionsOf({ ...f.baseConfig, readOnly: true });
76
90
  t.check(checkJoin(readOnly, readOnly).stricter.some((s) => s.startsWith('readOnly')), 'a read-only project cannot join (other projects are mapped read/write), even when the launch folder was read-only');
77
- t.check(!LocalOverrides.safeParse({ projects: { roots: ['C:\\'] } }).success, 'a project\'s .sandbox-config.json can never set projects.roots (they map folders of this PC)');
78
- t.check(!LocalOverrides.safeParse({ projects: { all: true } }).success, 'a project\'s .sandbox-config.json can never set projects.all');
91
+ t.check(!LocalOverridesSchema.safeParse({ projects: { roots: ['C:\\'] } }).success, "a project's .sandbox-config.json can never set projects.roots (they map folders of this PC)");
92
+ t.check(!LocalOverridesSchema.safeParse({ projects: { all: true } }).success, "a project's .sandbox-config.json can never set projects.all");
79
93
  // A project file that loosens git access is clamped by keepStrictest first, then joins without being refused.
80
94
  const strictUser = structuredClone(f.baseConfig);
81
95
  strictUser.provisioning.git.access = 'readonly';
@@ -94,24 +108,35 @@ export const projectsSuite = {
94
108
  const root = t.at('proj-root');
95
109
  const inRoot = t.dir('proj-root', 'web');
96
110
  const outside = t.dir('proj-outside');
97
- const primary = startRegistry(storage, { launchId: '1700000000000-abcdef', protections: base, primary: { host: f.workA, guest: 'C:\\Workspace\\work-a' }, roots: [{ host: root, guest: 'C:\\Projects\\proj-root' }], boot: [{ host: f.workB, guest: 'C:\\Projects\\work-b' }] });
98
- const registry = readRegistry(storage);
111
+ const primary = new ProjectRegistry(storage).start({
112
+ launchId: '1700000000000-abcdef',
113
+ protections: base,
114
+ primary: { host: f.workA, guest: 'C:\\Workspace\\work-a' },
115
+ roots: [{ host: root, guest: 'C:\\Projects\\proj-root' }],
116
+ boot: [{ host: f.workB, guest: 'C:\\Projects\\work-b' }],
117
+ });
118
+ const registry = new ProjectRegistry(storage).read();
99
119
  t.check(primary.primary, 'at launch the launch folder is the primary project');
100
120
  t.equal(registry?.projects.length, 2, 'at launch the registry holds the launch folder and the projects mapped with it');
101
121
  t.equal({ mechanism: registry?.projects[1].mechanism, name: registry?.projects[1].name }, { mechanism: 'boot', name: 'work-b' }, 'a project mapped at boot is registered by its mechanism and name');
102
- t.equal(projectHere(storage, path.join(f.workA, 'src'))?.guestDir, 'C:\\Workspace\\work-a\\src', 'projectHere: a folder inside the launch folder');
103
- t.equal(projectHere(storage, f.workB)?.project.mechanism, 'boot', 'projectHere: a project mapped at boot');
104
- t.equal(projectHere(storage, outside), null, 'projectHere: a folder in no project');
122
+ t.equal(new ProjectRegistry(storage).here(path.join(f.workA, 'src'))?.guestDir, 'C:\\Workspace\\work-a\\src', 'projectHere: a folder inside the launch folder');
123
+ t.equal(new ProjectRegistry(storage).here(f.workB)?.project.mechanism, 'boot', 'projectHere: a project mapped at boot');
124
+ t.equal(new ProjectRegistry(storage).here(outside), null, 'projectHere: a folder in no project');
105
125
  const noWsb = projectMapper({ storageRoot: storage, packageRoot: f.install, wsbShare: true, detect: () => null });
106
126
  const web = noWsb.register(inRoot);
107
127
  t.equal({ mechanism: web.mechanism, guest: web.guest }, { mechanism: 'root', guest: 'C:\\Projects\\proj-root\\web' }, 'a folder inside a root joins at once (also one created after boot)');
108
128
  t.equal(noWsb.register(inRoot).name, web.name, 'registering the same folder again gives the same project');
109
- t.equal(readRegistry(storage)?.projects.filter((p) => p.host === inRoot).length, 1, 'a folder is registered once');
129
+ t.equal(new ProjectRegistry(storage).read()?.projects.filter((p) => p.host === inRoot).length, 1, 'a folder is registered once');
110
130
  const needs = caught(() => noWsb.register(outside));
111
131
  t.check(needs instanceof NeedsRestart && needs.reason.includes('wsb share needs Windows 11 24H2'), 'a folder outside every root without the Windows Sandbox CLI needs a restart (and says why)');
112
132
  const shareOff = caught(() => projectMapper({ storageRoot: storage, packageRoot: f.install, wsbShare: false }).register(outside));
113
133
  t.check(shareOff instanceof NeedsRestart && shareOff.reason.includes('projects.wsbShare'), 'projects.wsbShare off goes straight to the restart offer');
114
- const fakeWsb = { exe: t.at('no-such-wsb.exe'), version: '0.1.0', commands: ['share', 'list'], has: (c) => c === 'share' || c === 'list' };
134
+ const fakeWsb = {
135
+ exe: t.at('no-such-wsb.exe'),
136
+ version: '0.1.0',
137
+ commands: ['share', 'list'],
138
+ has: (c) => c === 'share' || c === 'list',
139
+ };
115
140
  const listFails = caught(() => projectMapper({ storageRoot: storage, packageRoot: f.install, wsbShare: true, detect: () => fakeWsb }).register(outside));
116
141
  t.check(listFails instanceof NeedsRestart && listFails.reason.includes('wsb share could not find the running sandbox'), 'when wsb cannot tell which sandbox is ours, it falls back to the restart offer with the reason');
117
142
  await t.throws(() => noWsb.register(storage), 'cannot be the workspace', 'joining uses the launch-folder safety checks: not the storage folder');
@@ -123,17 +148,40 @@ export const projectsSuite = {
123
148
  const webRun = await f.run({ cwd: inRoot, trustConfig: true });
124
149
  const joined = await join(inRoot, resolveFor(webRun));
125
150
  t.equal(joined.spec?.workspace, 'C:\\Projects\\proj-root\\web', 'joining a project builds a spec with its guest folder...');
126
- t.check(ProjectSpec.safeParse(joined.spec).success && joined.spec?.harnesses.claude !== undefined, '... that is valid and carries the agent');
127
- t.check(readKnown(storage).some((k) => k.host === inRoot), 'joined projects are remembered (projects.all, sandbox projects)');
151
+ t.check(ProjectSpecSchema.safeParse(joined.spec).success && joined.spec?.harnesses.claude !== undefined, '... that is valid and carries the agent');
152
+ t.check(new KnownProjects(storage).list().some((k) => k.host === inRoot), 'joined projects are remembered (projects.all, sandbox projects)');
128
153
  const launchFolder = await join(path.join(f.workA, 'sub'), () => Promise.reject(new Error('must not resolve')));
129
154
  t.equal(launchFolder.spec, null, 'the launch folder (and below) needs no spec: its sessions use the boot environment');
130
- await t.throws(() => join(tightDir, resolveFor(tight)), 'stricter protection than the running sandbox', 'a project whose .sandbox-config.json tightens git access beyond the running sandbox is refused, with the reason');
131
- t.check(readRegistry(storage)?.projects.every((p) => p.host !== tightDir) === true, 'a refused project is not registered');
155
+ await t.throws(() => join(tightDir, resolveFor(tight)), 'stricter than the running sandbox', 'a project whose .sandbox-config.json tightens git access beyond the running sandbox is refused without your consent, with the reason');
156
+ t.check(new ProjectRegistry(storage).read()?.projects.every((p) => p.host !== tightDir) === true, 'a refused project is not registered');
157
+ let asked = [];
158
+ const agreed = await t.thrownMessage(() => joinRunning({
159
+ storageRoot: storage,
160
+ dataPath: data,
161
+ packageRoot: f.install,
162
+ hostDir: tightDir,
163
+ wsbShare: false,
164
+ resolve: resolveFor(tight),
165
+ acceptStricter: async (_name, stricter) => {
166
+ asked = stricter;
167
+ return true;
168
+ },
169
+ }));
170
+ t.check(asked.some((s) => s.startsWith('provisioning.git.access')) &&
171
+ !agreed.includes('stricter than the running sandbox'), 'with your consent it is not refused for its stricter settings (it goes on to joining: here a restart, as its folder is outside every root)', agreed);
132
172
  const outsideRun = await f.run({ cwd: outside });
133
173
  await t.throws(() => join(outside, resolveFor(outsideRun)), 'projects.wsbShare', 'a folder that cannot be shown lets NeedsRestart reach the caller');
134
174
  t.section('projects: the spec and request the guest gets');
135
175
  const spec = projectSpec(webRun, web, false);
136
- const sessionRequest = (extra) => GuestRequest.safeParse({ kind: 'session', id: 'r1', harness: 'claude', args: [], cwd: spec.workspace, createdAt: new Date().toISOString(), ...extra });
176
+ const sessionRequest = (extra) => GuestRequestSchema.safeParse({
177
+ kind: 'session',
178
+ id: 'r1',
179
+ harness: 'claude',
180
+ args: [],
181
+ cwd: spec.workspace,
182
+ createdAt: new Date().toISOString(),
183
+ ...extra,
184
+ });
137
185
  t.check(sessionRequest({ project: spec }).success, 'a session request carries a project spec');
138
186
  t.check(sessionRequest({ project: spec, harnessSpec: f.baseConfig.harnesses.codex }).success, 'a session request carries an agent definition');
139
187
  t.check(!sessionRequest({ project: { ...spec, name: '..\\evil' } }).success, 'specs are validated: project ids are file-name safe');
@@ -142,29 +190,57 @@ export const projectsSuite = {
142
190
  t.check(!sessionRequest({ project: { ...spec, databases: manyDatabases } }).success, 'specs are validated: lists are capped');
143
191
  const manyAgents = Object.fromEntries(Array.from({ length: 9 }, (_, i) => ['a' + i, f.baseConfig.harnesses.codex]));
144
192
  t.check(!sessionRequest({ project: { ...spec, harnesses: manyAgents } }).success, 'at most 8 agent definitions in one spec');
145
- t.check(GuestRequest.safeParse({ kind: 'project', id: 'p1', project: spec, createdAt: 'x' }).success, '"project" requests (set up without an agent) carry a spec');
146
- t.check(!GuestRequest.safeParse({ kind: 'project', id: 'p1', createdAt: 'x' }).success, '"project" requests need a spec');
147
- t.check(GuestRequest.safeParse({ kind: 'restart', id: 'x', harness: null, cwd: 'C:\\Projects\\a', createdAt: 'x' }).success, 'restart can be scoped to one project\'s folder');
148
- t.check(GuestRequest.safeParse({ kind: 'restart', id: 'x', harness: null, createdAt: 'x' }).success, 'restart still works without a folder');
193
+ t.check(GuestRequestSchema.safeParse({ kind: 'project', id: 'p1', project: spec, createdAt: 'x' }).success, '"project" requests (set up without an agent) carry a spec');
194
+ t.check(!GuestRequestSchema.safeParse({ kind: 'project', id: 'p1', createdAt: 'x' }).success, '"project" requests need a spec');
195
+ t.check(GuestRequestSchema.safeParse({ kind: 'restart', id: 'x', harness: null, cwd: 'C:\\Projects\\a', createdAt: 'x' })
196
+ .success, "restart can be scoped to one project's folder");
197
+ t.check(GuestRequestSchema.safeParse({ kind: 'restart', id: 'x', harness: null, createdAt: 'x' }).success, 'restart still works without a folder');
149
198
  t.section('projects: which projects are mapped at boot');
150
- const toMap = allProjectsToMap(['C:\\a', 'C:\\a\\b', 'C:\\work-a\\x', 'C:\\r\\p', 'C:\\ex\\y', 'C:\\gone'], { primary: 'C:\\work-a', roots: ['C:\\r'], exclude: ['C:\\ex'], exists: (p) => p !== 'C:\\gone' });
151
- t.equal(toMap, ['C:\\a'], 'projects.all: known projects except excluded, missing, nested, the launch folder\'s and the roots\'');
199
+ const toMap = allProjectsToMap(['C:\\a', 'C:\\a\\b', 'C:\\work-a\\x', 'C:\\r\\p', 'C:\\ex\\y', 'C:\\gone'], {
200
+ primary: 'C:\\work-a',
201
+ roots: ['C:\\r'],
202
+ exclude: ['C:\\ex'],
203
+ exists: (p) => p !== 'C:\\gone',
204
+ });
205
+ t.equal(toMap, ['C:\\a'], "projects.all: known projects except excluded, missing, nested, the launch folder's and the roots'");
152
206
  const rootConfig = structuredClone(f.baseConfig);
153
207
  rootConfig.projects = { ...rootConfig.projects, roots: [root, t.at('no-such-root')], all: true };
154
- rememberProject(f.baseConfig.storage.root, outside);
208
+ new KnownProjects(f.baseConfig.storage.root).remember(outside);
155
209
  const gen = f.generate(await f.run({}, rootConfig));
156
210
  const session = f.readJson(gen.sessionPath);
157
211
  const wsb = fs.readFileSync(gen.wsbPath, 'utf8');
158
212
  t.equal(gen.projects.roots.map((r) => r.guest), ['C:\\Projects\\proj-root'], 'roots are mapped as C:\\Projects\\<name>');
159
213
  t.check(gen.projects.notes.some((n) => n.includes('no-such-root')), 'a missing root is skipped with a note', JSON.stringify(gen.projects.notes));
160
214
  t.check(gen.projects.projects.some((p) => p.host === outside), 'projects.all maps the known projects too');
161
- t.includes(wsb, '<HostFolder>' + root + '</HostFolder><SandboxFolder>C:\\Projects\\proj-root</SandboxFolder><ReadOnly>false</ReadOnly>', 'roots are mapped read/write in the .wsb');
162
- t.equal({ projectsDir: session.projectsDir, roots: session.roots, projectId: session.projectId }, { projectsDir: PROJECTS_GUEST, roots: ['C:\\Projects\\proj-root'], projectId: 'work-a' }, 'session.json tells the guest where other projects can be');
215
+ t.includes(wsb, '<HostFolder>' +
216
+ root +
217
+ '</HostFolder><SandboxFolder>C:\\Projects\\proj-root</SandboxFolder><ReadOnly>false</ReadOnly>', 'roots are mapped read/write in the .wsb');
218
+ t.equal({ projectsDir: session.projectsDir, roots: session.roots, projectId: session.projectId }, {
219
+ projectsDir: PROJECTS_GUEST,
220
+ roots: ['C:\\Projects\\proj-root', 'C:\\Workspace\\proj-outside'],
221
+ projectId: 'work-a',
222
+ }, 'session.json tells the guest where other projects can be');
223
+ // The same project is at the same place whether it started the sandbox or joined one another project started.
224
+ const fromRoot = f.generate(await f.run({ cwd: inRoot }, rootConfig));
225
+ const joinedAt = guestDirFor(gen.projects.roots[0], inRoot);
226
+ t.equal([f.readJson(fromRoot.sessionPath).workspace, joinedAt], ['C:\\Projects\\proj-root\\web', 'C:\\Projects\\proj-root\\web'], 'a project in a root: the same guest folder as the launch folder and when another project started the sandbox');
227
+ t.check(!fromRoot.mappings.some((m) => m.host.toLowerCase() === inRoot.toLowerCase()), 'a launch folder inside a root is shown by the root (no second mapping of it)');
228
+ t.includes(fs.readFileSync(path.join(fromRoot.launchPath, 'env.cmd'), 'utf8'), 'AGENT_WORKSPACE=C:\\Projects\\proj-root\\web', 'env.cmd: AGENT_WORKSPACE follows');
229
+ const asLaunch = f.readJson(f.generate(await f.run({ cwd: outside })).sessionPath).workspace;
230
+ t.equal([asLaunch, gen.projects.projects.find((p) => p.host === outside)?.guest], ['C:\\Workspace\\proj-outside', 'C:\\Workspace\\proj-outside'], 'a project outside the roots: the same guest folder as the launch folder and when mapped at boot by another');
231
+ const mirrorRoot = structuredClone(rootConfig);
232
+ mirrorRoot.guestWorkspace = 'mirror';
233
+ t.equal(f.readJson(f.generate(await f.run({ cwd: inRoot }, mirrorRoot)).sessionPath).workspace, rootGuestDir([root], true, inRoot), 'with "mirror" a launch folder inside a root is where the root shows it (no overlapping mappings)');
234
+ const filtered = structuredClone(rootConfig);
235
+ filtered.workspaceFilter = { ...filtered.workspaceFilter, enabled: true };
236
+ const filteredRun = await f.run({ cwd: inRoot }, filtered);
237
+ t.equal(filteredRun.config.guestWorkspace, 'C:\\Workspace\\web', 'workspaceFilter: a launch folder inside a root keeps its own (filtered) mapping');
238
+ t.check(filteredRun.notes.some((n) => n.includes('C:\\Projects\\proj-root\\web')), 'and a note says where it is when another project starts the sandbox', JSON.stringify(filteredRun.notes));
163
239
  const badRoot = structuredClone(f.baseConfig);
164
240
  badRoot.projects = { ...badRoot.projects, roots: [f.baseConfig.storage.root] };
165
241
  await t.throws(async () => f.generate(await f.run({}, badRoot)), 'projects.roots', 'a root that would expose the storage folder is refused');
166
- t.check(forgetProject(f.baseConfig.storage.root, outside), '`sandbox projects remove` forgets a project...');
167
- t.check(!readKnown(f.baseConfig.storage.root).some((k) => k.host === outside), '... so it is no longer known (its folder is untouched)');
242
+ t.check(new KnownProjects(f.baseConfig.storage.root).forget(outside), '`sandbox projects remove` forgets a project...');
243
+ t.check(!new KnownProjects(f.baseConfig.storage.root).list().some((k) => k.host === outside), '... so it is no longer known (its folder is untouched)');
168
244
  t.includes(buildWsb(f.baseConfig, 4096, []), '<MappedFolders></MappedFolders>', 'no projects: nothing extra mapped');
169
245
  t.section('projects: repo guard for every project the sandbox can write');
170
246
  const guardData = t.at('guard-data');
@@ -172,13 +248,13 @@ export const projectsSuite = {
172
248
  const old = path.join(rootG, 'old');
173
249
  t.write(path.join(old, '.git', 'hooks', 'pre-commit'), 'mine');
174
250
  t.write(path.join(old, '.git', 'config'), '[core]\n\tbare = false\n');
175
- saveRepoSnapshot(guardData, f.workA, [], [rootG]);
251
+ new RepoGuardStore(guardData).save(f.workA, [], [rootG]);
176
252
  t.write(path.join(old, '.git', 'hooks', 'post-checkout'), 'planted');
177
253
  const fresh = path.join(rootG, 'created-later');
178
254
  t.write(path.join(fresh, '.git', 'hooks', 'pre-push'), 'planted');
179
255
  t.write(path.join(fresh, '.git', 'config'), '[core]\n\tfsmonitor = evil.exe\n\tbare = false\n');
180
256
  t.write(path.join(fresh, 'node.exe'), 'planted');
181
- const found = checkRepo(guardData);
257
+ const found = new RepoGuardStore(guardData).check();
182
258
  t.check(fs.existsSync(path.join(old, '.git', 'hooks', 'pre-commit')), 'a hook that was there before the sandbox ran stays');
183
259
  t.check(!fs.existsSync(path.join(old, '.git', 'hooks', 'post-checkout')), 'a hook added inside the sandbox to a project in a root is quarantined');
184
260
  t.check(!fs.existsSync(path.join(fresh, '.git', 'hooks', 'pre-push')), 'a project that appeared in a root after the start has its planted hooks removed');
@@ -190,17 +266,61 @@ export const projectsSuite = {
190
266
  const guestClaude = t.at('hist-guest');
191
267
  const bHost = t.at('hist-b');
192
268
  t.write(path.join(guestClaude, 'projects', projectFolderName('C:\\Projects\\hist-b'), 's.jsonl'), JSON.stringify({ cwd: 'C:\\Projects\\hist-b\\src' }) + '\n');
193
- syncHistoryOut({ hostClaudeDir: hostClaude, guestClaudeDir: guestClaude, hostWorkspace: f.workA, guestWorkspace: 'C:\\Workspace\\work-a', extra: [{ hostWorkspace: bHost, guestWorkspace: 'C:\\Projects\\hist-b' }] });
269
+ syncHistoryOut({
270
+ hostClaudeDir: hostClaude,
271
+ guestClaudeDir: guestClaude,
272
+ hostWorkspace: f.workA,
273
+ guestWorkspace: 'C:\\Workspace\\work-a',
274
+ extra: [{ hostWorkspace: bHost, guestWorkspace: 'C:\\Projects\\hist-b' }],
275
+ });
194
276
  const back = path.join(hostClaude, 'projects', projectFolderName(bHost), 's.jsonl');
195
- t.check(fs.existsSync(back), 'a joined project\'s history goes back to ITS folder on this PC');
196
- t.includes(fs.existsSync(back) ? fs.readFileSync(back, 'utf8') : '', JSON.stringify(bHost + '\\src').slice(1, -1), 'a joined project\'s history paths are translated');
197
- const env = baseEnvFor({ basePath: 'C:\\Windows', toolVars: ['NVM_HOME', 'CARGO_HOME'], steps: {}, summary: [], lan: 'blocked' }, { Path: 'C:\\tools\\a;C:\\Windows', NVM_HOME: 'x', CARGO_HOME: 'y', CARGO_TARGET_DIR: 'C:\\Workspace\\a\\target', AGENT_WORKSPACE: 'C:\\Workspace\\a', TEMP: 'C:\\t' });
198
- t.equal({ PATH: env.PATH, Path: env.Path, NVM_HOME: env.NVM_HOME, CARGO_TARGET_DIR: env.CARGO_TARGET_DIR, AGENT_WORKSPACE: env.AGENT_WORKSPACE, TEMP: env.TEMP }, { PATH: 'C:\\Windows', Path: undefined, NVM_HOME: undefined, CARGO_TARGET_DIR: undefined, AGENT_WORKSPACE: undefined, TEMP: 'C:\\t' }, 'a joined project starts from the boot\'s base environment: none of the first project\'s tools, workspace or target folder');
277
+ t.check(fs.existsSync(back), "a joined project's history goes back to ITS folder on this PC");
278
+ t.includes(fs.existsSync(back) ? fs.readFileSync(back, 'utf8') : '', JSON.stringify(bHost + '\\src').slice(1, -1), "a joined project's history paths are translated");
279
+ const env = baseEnvFor({ basePath: 'C:\\Windows', toolVars: ['NVM_HOME', 'CARGO_HOME'], steps: {}, summary: [], lan: 'blocked' }, {
280
+ Path: 'C:\\tools\\a;C:\\Windows',
281
+ NVM_HOME: 'x',
282
+ CARGO_HOME: 'y',
283
+ CARGO_TARGET_DIR: 'C:\\Workspace\\a\\target',
284
+ AGENT_WORKSPACE: 'C:\\Workspace\\a',
285
+ TEMP: 'C:\\t',
286
+ });
287
+ t.equal({
288
+ PATH: env.PATH,
289
+ Path: env.Path,
290
+ NVM_HOME: env.NVM_HOME,
291
+ CARGO_TARGET_DIR: env.CARGO_TARGET_DIR,
292
+ AGENT_WORKSPACE: env.AGENT_WORKSPACE,
293
+ TEMP: env.TEMP,
294
+ }, {
295
+ PATH: 'C:\\Windows',
296
+ Path: undefined,
297
+ NVM_HOME: undefined,
298
+ CARGO_TARGET_DIR: undefined,
299
+ AGENT_WORKSPACE: undefined,
300
+ TEMP: 'C:\\t',
301
+ }, "a joined project starts from the boot's base environment: none of the first project's tools, workspace or target folder");
199
302
  t.section('projects: commands');
200
- const status = (id, cwd, startedAt) => ({ id, launchId: 'l', harness: 'claude', cwd, phase: 'running', message: '', updatedAt: startedAt, startedAt });
201
- const sessions = [status('a1', 'C:\\Workspace\\a', '1'), status('b1', 'C:\\Projects\\b', '2'), status('a2', 'C:\\Workspace\\a\\x', '3'), status('b2', 'C:\\Projects\\b\\y', '4')];
202
- t.equal(sessionsFirstIn(sessions, 'C:\\Projects\\b').map((x) => x.id), ['b2', 'b1', 'a2', 'a1'], 'attach and sessions put this project\'s sessions first, newest first');
203
- t.equal(describeSessions([{ harness: 'claude', cwd: 'C:\\Workspace\\api', project: 'api' }, { harness: 'codex', cwd: 'C:\\Projects\\web' }]), '2 sessions (claude in api, codex in web)', 'the restart offer lists the sessions that would end');
303
+ const status = (id, cwd, startedAt) => ({
304
+ id,
305
+ launchId: 'l',
306
+ harness: 'claude',
307
+ cwd,
308
+ phase: 'running',
309
+ message: '',
310
+ updatedAt: startedAt,
311
+ startedAt,
312
+ });
313
+ const sessions = [
314
+ status('a1', 'C:\\Workspace\\a', '1'),
315
+ status('b1', 'C:\\Projects\\b', '2'),
316
+ status('a2', 'C:\\Workspace\\a\\x', '3'),
317
+ status('b2', 'C:\\Projects\\b\\y', '4'),
318
+ ];
319
+ t.equal(sessionsFirstIn(sessions, 'C:\\Projects\\b').map((x) => x.id), ['b2', 'b1', 'a2', 'a1'], "attach and sessions put this project's sessions first, newest first");
320
+ t.equal(describeSessions([
321
+ { harness: 'claude', cwd: 'C:\\Workspace\\api', project: 'api' },
322
+ { harness: 'codex', cwd: 'C:\\Projects\\web' },
323
+ ]), '2 sessions (claude in api, codex in web)', 'the restart offer lists the sessions that would end');
204
324
  await t.throws(() => validateProjectsArgs(['bogus']), 'projects action', '`sandbox projects` rejects an unknown action');
205
325
  await t.throws(() => validateProjectsArgs(['add']), 'Which folder', '`sandbox projects add` needs a folder');
206
326
  await t.throws(() => validateProjectsArgs(['all', 'maybe']), 'on', '`sandbox projects all` needs on or off');
@@ -208,7 +328,10 @@ export const projectsSuite = {
208
328
  validateProjectsArgs([]);
209
329
  validateProjectsArgs(['roots']);
210
330
  validateProjectsArgs(['all', 'on']);
211
- t.check(askedWhen(['add', 'remove'], 'add') && !askedWhen(['add'], 'list') && askedWhen(undefined, 'x') && askedWhen('save', 'save'), 'the menu asks an argument only for the actions it belongs to');
331
+ t.check(askedWhen(['add', 'remove'], 'add') &&
332
+ !askedWhen(['add'], 'list') &&
333
+ askedWhen(undefined, 'x') &&
334
+ askedWhen('save', 'save'), 'the menu asks an argument only for the actions it belongs to');
212
335
  t.section('projects: several sessions at once (--session agent@folder)');
213
336
  const want = parseSessionWant('claude@C:\\code\\a@b', 'C:\\here');
214
337
  t.equal({ harness: want.harness, folder: want.folder }, { harness: 'claude', folder: 'C:\\code\\a@b' }, '--session: the first @ splits agent from folder');
@@ -216,26 +339,167 @@ export const projectsSuite = {
216
339
  t.equal(parseSessionWant('gemini@sub', 'C:\\here').folder, 'C:\\here\\sub', '--session: relative folders become absolute');
217
340
  await t.throws(() => parseSessionWant('@C:\\x'), 'names no agent', '--session without an agent is refused');
218
341
  const own = parseSessionWant('codex@C:\\code\\web|--model o3 "two words"', 'C:\\here');
219
- t.equal({ harness: own.harness, folder: own.folder, args: own.args }, { harness: 'codex', folder: 'C:\\code\\web', args: ['--model', 'o3', 'two words'] }, '--session: after | come that agent\'s own arguments (quotes keep words together)');
342
+ t.equal({ harness: own.harness, folder: own.folder, args: own.args }, { harness: 'codex', folder: 'C:\\code\\web', args: ['--model', 'o3', 'two words'] }, "--session: after | come that agent's own arguments (quotes keep words together)");
220
343
  t.equal(parseSessionWant('claude@C:\\x').args, undefined, '--session: no | means no own arguments');
221
- const both = parseCli(['launch', '--session', 'claude@C:\\a|--continue', '--session', 'codex@C:\\b', '--', '--verbose']);
344
+ const both = parseCli([
345
+ 'launch',
346
+ '--session',
347
+ 'claude@C:\\a|--continue',
348
+ '--session',
349
+ 'codex@C:\\b',
350
+ '--',
351
+ '--verbose',
352
+ ]);
222
353
  t.equal(both.passthrough, ['--verbose'], 'arguments after -- go to every session...');
223
354
  t.equal(both.sessions.map((s) => s.args ?? []), [['--continue'], []], '... arguments after | only to that one');
224
- const multi = parseCli(['launch', '--session', 'claude@' + f.workA, '--session', 'codex@' + f.workB, '--session', 'claude@' + f.workB, '-t', '--', '--continue']);
225
- t.equal({ command: multi.command, sessions: multi.sessions.length, terminal: multi.terminal, second: multi.sessions[1].harness, passthrough: multi.passthrough }, { command: 'launch', sessions: 3, terminal: true, second: 'codex', passthrough: ['--continue'] }, 'several --session options parse in order, with -t and agent arguments');
226
- const validationContext = (options) => ({ packageRoot: f.install, entry: '', options, loaded: { config: f.baseConfig, configPath: null, base: t.temp }, storageRoot: f.baseConfig.storage.root, dataPath: t.at('vdata') });
355
+ const multi = parseCli([
356
+ 'launch',
357
+ '--session',
358
+ 'claude@' + f.workA,
359
+ '--session',
360
+ 'codex@' + f.workB,
361
+ '--session',
362
+ 'claude@' + f.workB,
363
+ '-t',
364
+ '--',
365
+ '--continue',
366
+ ]);
367
+ t.equal({
368
+ command: multi.command,
369
+ sessions: multi.sessions.length,
370
+ terminal: multi.terminal,
371
+ second: multi.sessions[1].harness,
372
+ passthrough: multi.passthrough,
373
+ }, { command: 'launch', sessions: 3, terminal: true, second: 'codex', passthrough: ['--continue'] }, 'several --session options parse in order, with -t and agent arguments');
374
+ const validationContext = (options) => ({
375
+ packageRoot: f.install,
376
+ entry: '',
377
+ options,
378
+ loaded: { config: f.baseConfig, configPath: null, base: t.temp },
379
+ storageRoot: f.baseConfig.storage.root,
380
+ dataPath: t.at('vdata'),
381
+ });
227
382
  await t.throws(() => validateOptions(validationContext(parseCli(['launch', '--session', 'claud@' + f.workA]))), 'Did you mean', 'an unknown agent in --session suggests the closest one');
228
383
  await t.throws(() => validateOptions(validationContext(parseCli(['launch', '--session', 'claude@' + t.at('nope')]))), 'does not exist', 'a missing --session folder is refused before anything starts');
229
384
  await t.throws(() => validateOptions(validationContext(parseCli(['launch', '--session', 'claude@' + f.workA, '--harness', 'codex']))), 'do not apply', '--session cannot be mixed with --harness/--workspace');
230
385
  validateOptions(validationContext(multi));
231
- t.equal(severalAgentsArgv([{ harness: 'claude', folder: 'C:\\a' }, { harness: 'codex', folder: 'C:\\b c' }], { headless: true, attachFirst: true }).join(' '), 'launch --session claude@C:\\a --session codex@C:\\b c --headless -t', 'the menu builds the same command line (so repeat-last works)');
386
+ t.equal(severalAgentsArgv([
387
+ { harness: 'claude', folder: 'C:\\a' },
388
+ { harness: 'codex', folder: 'C:\\b c' },
389
+ ], { headless: true, attachFirst: true }).join(' '), 'launch --session claude@C:\\a --session codex@C:\\b c --headless -t', 'the menu builds the same command line (so repeat-last works)');
232
390
  t.equal(candidateFolders(validationContext(parseCli(['launch'])), f.workA)[0], path.resolve(f.workA), 'the menu offers this folder first, then known projects and the folders in your roots');
233
- const multiGen = generateSandbox({ packageRoot: t.packageRoot, run: await f.run(), harnessArgs: [], probe: f.probe, extraProjects: [f.workB, path.join(f.workA, 'sub'), f.workB], autoSession: false });
391
+ const multiGen = generateSandbox({
392
+ packageRoot: t.packageRoot,
393
+ run: await f.run(),
394
+ harnessArgs: [],
395
+ probe: f.probe,
396
+ extraProjects: [f.workB, path.join(f.workA, 'sub'), f.workB],
397
+ autoSession: false,
398
+ });
234
399
  const multiSession = f.readJson(multiGen.sessionPath);
235
400
  const multiWsb = fs.readFileSync(multiGen.wsbPath, 'utf8');
236
- t.equal(multiGen.projects.projects.map((p) => p.guest), ['C:\\Projects\\work-b'], 'a multi-project launch maps every other project once (folders inside the launch folder and duplicates need nothing)');
237
- t.includes(multiWsb, '<HostFolder>' + f.workB + '</HostFolder><SandboxFolder>C:\\Projects\\work-b</SandboxFolder>', 'a multi-project launch maps the other project in the .wsb');
401
+ t.equal(multiGen.projects.projects.map((p) => p.guest), ['C:\\Workspace\\work-b'], 'a multi-project launch maps every other project once (folders inside the launch folder and duplicates need nothing)');
402
+ t.includes(multiWsb, '<HostFolder>' + f.workB + '</HostFolder><SandboxFolder>C:\\Workspace\\work-b</SandboxFolder>', 'a multi-project launch maps the other project in the .wsb');
238
403
  t.equal({ autoSession: multiSession.autoSession, workspaceHost: multiSession.workspaceHost }, { autoSession: false, workspaceHost: f.workA }, 'the boot provisions the launch folder only; the host then opens every session (each project set up on demand, in parallel)');
404
+ t.section('projects: what a joining project wants at boot (vGPU, memory, its mappings)');
405
+ // A games project: its approved file asks for vGPU (you allowed it for this project) and maps a folder of its own.
406
+ const games = t.dir('proj-games');
407
+ t.dir('proj-games', 'assets');
408
+ f.writeLocal(games, {
409
+ sandbox: { vGpu: true },
410
+ mappings: [{ host: 'assets', guest: 'F:\\GameAssets', readOnly: true }],
411
+ });
412
+ saveAllowed(allowStorePath(f.baseConfig.storage.root), path.join(games, '.sandbox-config.json'), 'sandbox.vGpu', true, true);
413
+ const gamesRun = await f.run({ cwd: games, trustConfig: true });
414
+ t.equal(gamesRun.config.sandbox.vGpu, true, 'the vGPU you allowed for the project is in its effective config');
415
+ const launchedRun = await f.run();
416
+ const runningBoot = {
417
+ memoryMb: 8192,
418
+ mappings: [{ host: f.workA, guest: 'C:\\Workspace\\work-a', readOnly: false }],
419
+ };
420
+ const wanted = missingAtBoot(protectionsOf(launchedRun.config), runningBoot, gamesRun.config, fs.existsSync);
421
+ t.equal({ settings: wanted.settings, memoryMb: wanted.memoryMb, mappings: wanted.mappings.map((m) => m.guest) }, { settings: ['sandbox.vGpu'], memoryMb: null, mappings: ['F:\\GameAssets'] }, "missingAtBoot: vGPU and the project's folder are missing from a sandbox started without them (the missing optional mapping is no wish)");
422
+ const mappedMirror = missingAtBoot(protectionsOf(gamesRun.config), {
423
+ memoryMb: 8192,
424
+ mappings: [{ host: path.join(games, 'assets'), guest: 'C:\\Mirror\\F\\GameAssets', readOnly: true }],
425
+ }, gamesRun.config, fs.existsSync);
426
+ t.check(nothingMissing(mappedMirror), 'missingAtBoot: nothing missing when the sandbox has vGPU and the folder (F:\\x is C:\\Mirror\\F\\x in the .wsb)', JSON.stringify(mappedMirror));
427
+ const bigger = structuredClone(launchedRun.config);
428
+ bigger.sandbox.memoryMb = 16384;
429
+ t.equal(missingAtBoot(protectionsOf(launchedRun.config), runningBoot, bigger).memoryMb, 16384, 'missingAtBoot: a larger fixed memory size is missing too');
430
+ const twoFolders = {
431
+ settings: ['sandbox.vGpu'],
432
+ memoryMb: null,
433
+ mappings: [
434
+ { host: 'C:\\Steam\\common', guest: 'C:\\Steam', readOnly: true },
435
+ { host: 'F:\\SteamLibrary', guest: 'F:\\SteamLibrary', readOnly: true },
436
+ ],
437
+ };
438
+ t.equal(describeMissing('mw2-agent', twoFolders), 'mw2-agent wants vGPU and 2 folders (C:\\Steam\\common, F:\\SteamLibrary), but this sandbox started without them.', 'the message names each missing item');
439
+ t.equal(missingTip(twoFolders), 'Set sandbox.vGpu (or the mappings) in your own settings to have them in every sandbox.', 'the tip names the settings');
440
+ t.equal(describeMissing('api', { settings: ['sandbox.vGpu'], memoryMb: null, mappings: [] }), 'api wants vGPU, but this sandbox started without it.', 'one item reads naturally');
441
+ t.equal(parseWsbBoot(buildWsb(f.baseConfig, 6144, [{ host: 'C:\\a&b', guest: 'F:\\x', readOnly: true }])), { memoryMb: 6144, mappings: [{ host: 'C:\\a&b', guest: 'C:\\Mirror\\F\\x', readOnly: true }], widened: {} }, 'a sandbox started by an older version: memory and mapped folders are read back from its .wsb');
442
+ const widenedJoin = checkJoin({ ...base, 'sandbox.vGpu': true }, base, ['sandbox.vGpu']);
443
+ t.equal({ stricter: widenedJoin.stricter, shared: widenedJoin.shared }, { stricter: [], shared: ['sandbox.vGpu'] }, 'vGPU turned on at boot for another project you allowed it for: a project without it shares it instead of being refused');
444
+ t.check(checkJoin({ ...base, 'sandbox.vGpu': true }, base).stricter.some((s) => s.startsWith('sandbox.vGpu')), '... without that record a project without vGPU is still refused (it needs it off)');
445
+ // joinRunning asks before anything is registered: restart (NeedsRestart with what is missing), or continue without.
446
+ const storage2 = t.dir('proj-storage2');
447
+ const data2 = t.dir('proj-storage2', 'data', 'testdata');
448
+ new ProjectRegistry(storage2).start({
449
+ launchId: '1700000000001-abcdef',
450
+ protections: protectionsOf(launchedRun.config),
451
+ primary: { host: f.workA, guest: 'C:\\Workspace\\work-a' },
452
+ roots: [{ host: path.dirname(games), guest: 'C:\\Projects\\temp' }],
453
+ boot: [],
454
+ atBoot: { ...runningBoot, widened: {} },
455
+ });
456
+ const joinGames = (choice) => joinRunning({
457
+ storageRoot: storage2,
458
+ dataPath: data2,
459
+ packageRoot: f.install,
460
+ hostDir: games,
461
+ wsbShare: false,
462
+ resolve: resolveFor(gamesRun),
463
+ decide: async () => choice,
464
+ });
465
+ const restartFor = await joinGames('restart').then(() => null, (e) => e);
466
+ t.check(restartFor instanceof NeedsRestart &&
467
+ restartFor.missing?.settings.includes('sandbox.vGpu') === true &&
468
+ restartFor.missing.mappings.length === 1, 'choosing "Restart with them" hands the restart offer what is missing');
469
+ await t.throws(() => joinGames('cancel'), 'Cancelled', '"Cancel" stops with a short line');
470
+ t.check(!new ProjectRegistry(storage2).read()?.projects.some((p) => p.host === games), 'neither registers the project');
471
+ const continued = await joinGames('continue');
472
+ t.equal(continued.missing?.settings, ['sandbox.vGpu'], '"Continue anyway" joins without them and says what is missing');
473
+ // The restart that adds it (and any launch with it mapped at boot) boots with the union.
474
+ const unionGen = generateSandbox({
475
+ packageRoot: t.packageRoot,
476
+ run: launchedRun,
477
+ harnessArgs: [],
478
+ probe: f.probe,
479
+ extraProjects: [games],
480
+ autoSession: false,
481
+ bootWants: { projects: [{ name: 'proj-games', config: gamesRun.config }], keepOff: [] },
482
+ });
483
+ const unionWsb = fs.readFileSync(unionGen.wsbPath, 'utf8');
484
+ t.includes(unionWsb, '<vGPU>Enable</vGPU>', 'the restart plan: vGPU on because the added project asks for it (and you allowed it)');
485
+ t.includes(unionWsb, '<HostFolder>' +
486
+ path.join(games, 'assets') +
487
+ '</HostFolder><SandboxFolder>C:\\Mirror\\F\\GameAssets</SandboxFolder><ReadOnly>true</ReadOnly>', "the restart plan: the added project's mapping is mapped");
488
+ t.includes(unionWsb, '<HostFolder>' + games + '</HostFolder><SandboxFolder>C:\\Workspace\\proj-games</SandboxFolder>', 'the restart plan: the added project itself is mapped (where it is as the launch folder)');
489
+ t.equal(unionGen.widened, { 'sandbox.vGpu': 'proj-games' }, 'the registry learns which boot setting was turned on for which project');
490
+ t.check(unionGen.projects.notes.some((n) => n.startsWith('vGPU is on in this sandbox')), 'the launch says vGPU is on for every project and why', JSON.stringify(unionGen.projects.notes));
491
+ t.check(nothingMissing(missingAtBoot(protectionsOf(unionGen.bootConfig), { memoryMb: unionGen.memoryMb, mappings: unionGen.mappings }, gamesRun.config, fs.existsSync)), 'after that restart the project joins with nothing missing');
492
+ const keptOff = generateSandbox({
493
+ packageRoot: t.packageRoot,
494
+ run: launchedRun,
495
+ harnessArgs: [],
496
+ probe: f.probe,
497
+ extraProjects: [games],
498
+ autoSession: false,
499
+ bootWants: { projects: [{ name: 'proj-games', config: gamesRun.config }], keepOff: ['sandbox.vGpu'] },
500
+ });
501
+ t.includes(fs.readFileSync(keptOff.wsbPath, 'utf8'), '<vGPU>Disable</vGPU>', "a device the launch folder's own file switched off stays off");
502
+ t.includes(fs.readFileSync(f.generate(launchedRun).wsbPath, 'utf8'), '<vGPU>Disable</vGPU>', 'without other projects nothing changes');
239
503
  t.section('projects: wsb (Windows Sandbox command line) adapter');
240
504
  t.equal([parseVersion('Windows Sandbox CLI version 0.4.1.0\n'), parseVersion('nothing')], ['0.4.1.0', null], 'wsb --version');
241
505
  const help = 'Description:\n Windows Sandbox command line\n\nUsage:\n wsb [command] [options]\n\nOptions:\n --version Show version information\n\nCommands:\n start Starts a sandbox\n list Lists sandboxes\n exec Runs a command\n share Shares a folder\n stop Stops a sandbox\n connect Connects\n ip Shows the IP\n';