windows-development-sandbox 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (448) hide show
  1. package/README.md +1 -0
  2. package/config.example.json +17 -1
  3. package/dist/cli/args.d.ts +13 -2
  4. package/dist/cli/args.js +95 -24
  5. package/dist/cli/commands/accounts.js +45 -17
  6. package/dist/cli/commands/context.d.ts +2 -0
  7. package/dist/cli/commands/context.js +9 -2
  8. package/dist/cli/commands/data/clean.js +32 -9
  9. package/dist/cli/commands/index.d.ts +1 -0
  10. package/dist/cli/commands/index.js +1 -0
  11. package/dist/cli/commands/launch/add-root.d.ts +3 -2
  12. package/dist/cli/commands/launch/add-root.js +11 -10
  13. package/dist/cli/commands/launch/launch.js +57 -4
  14. package/dist/cli/commands/launch/prepare.js +3 -1
  15. package/dist/cli/commands/maintenance/audit.js +1 -1
  16. package/dist/cli/commands/maintenance/clean.d.ts +1 -1
  17. package/dist/cli/commands/maintenance/clean.js +17 -3
  18. package/dist/cli/commands/maintenance/config.d.ts +1 -1
  19. package/dist/cli/commands/maintenance/config.js +112 -10
  20. package/dist/cli/commands/maintenance/doctor.d.ts +1 -1
  21. package/dist/cli/commands/maintenance/doctor.js +2 -4
  22. package/dist/cli/commands/maintenance/migrate.d.ts +2 -1
  23. package/dist/cli/commands/maintenance/migrate.js +19 -4
  24. package/dist/cli/commands/maintenance/sync.js +2 -0
  25. package/dist/cli/commands/maintenance/update.d.ts +2 -0
  26. package/dist/cli/commands/maintenance/update.js +27 -4
  27. package/dist/cli/commands/projects/here.js +12 -7
  28. package/dist/cli/commands/projects/projects.js +26 -17
  29. package/dist/cli/commands/registry.js +7 -0
  30. package/dist/cli/commands/repair/checks/agents.d.ts +2 -0
  31. package/dist/cli/commands/repair/checks/agents.js +70 -0
  32. package/dist/cli/commands/repair/checks/common.d.ts +8 -0
  33. package/dist/cli/commands/repair/checks/common.js +22 -0
  34. package/dist/cli/commands/repair/checks/data.d.ts +2 -0
  35. package/dist/cli/commands/repair/checks/data.js +241 -0
  36. package/dist/cli/commands/repair/checks/index.d.ts +2 -0
  37. package/dist/cli/commands/repair/checks/index.js +20 -0
  38. package/dist/cli/commands/repair/checks/install.d.ts +5 -0
  39. package/dist/cli/commands/repair/checks/install.js +318 -0
  40. package/dist/cli/commands/repair/checks/machine.d.ts +2 -0
  41. package/dist/cli/commands/repair/checks/machine.js +232 -0
  42. package/dist/cli/commands/repair/checks/network.d.ts +6 -0
  43. package/dist/cli/commands/repair/checks/network.js +60 -0
  44. package/dist/cli/commands/repair/checks/pc.d.ts +4 -0
  45. package/dist/cli/commands/repair/checks/pc.js +55 -0
  46. package/dist/cli/commands/repair/checks/sandbox.d.ts +2 -0
  47. package/dist/cli/commands/repair/checks/sandbox.js +267 -0
  48. package/dist/cli/commands/repair/checks/settings.d.ts +2 -0
  49. package/dist/cli/commands/repair/checks/settings.js +169 -0
  50. package/dist/cli/commands/repair/checks/vpn.d.ts +2 -0
  51. package/dist/cli/commands/repair/checks/vpn.js +53 -0
  52. package/dist/cli/commands/repair/engine.d.ts +32 -0
  53. package/dist/cli/commands/repair/engine.js +127 -0
  54. package/dist/cli/commands/repair/env.d.ts +7 -0
  55. package/dist/cli/commands/repair/env.js +49 -0
  56. package/dist/cli/commands/repair/index.d.ts +1 -9
  57. package/dist/cli/commands/repair/index.js +2 -54
  58. package/dist/cli/commands/repair/log.d.ts +17 -0
  59. package/dist/cli/commands/repair/log.js +82 -0
  60. package/dist/cli/commands/repair/printer.d.ts +32 -0
  61. package/dist/cli/commands/repair/printer.js +132 -0
  62. package/dist/cli/commands/repair/run.d.ts +33 -0
  63. package/dist/cli/commands/repair/run.js +133 -0
  64. package/dist/cli/commands/repair/types.d.ts +97 -9
  65. package/dist/cli/commands/repair/types.js +11 -7
  66. package/dist/cli/commands/resume.js +2 -2
  67. package/dist/cli/commands/sessions/list.js +5 -2
  68. package/dist/cli/commands/sessions/status.d.ts +2 -0
  69. package/dist/cli/commands/sessions/status.js +27 -6
  70. package/dist/cli/commands/setup/index.d.ts +5 -0
  71. package/dist/cli/commands/setup/index.js +25 -1
  72. package/dist/cli/commands/storage/index.d.ts +2 -0
  73. package/dist/cli/commands/storage/index.js +2 -0
  74. package/dist/cli/commands/storage/offer.d.ts +3 -0
  75. package/dist/cli/commands/storage/offer.js +85 -0
  76. package/dist/cli/commands/storage/storage.d.ts +8 -0
  77. package/dist/cli/commands/storage/storage.js +171 -0
  78. package/dist/cli/commands/user-config.js +1 -1
  79. package/dist/cli/commands/vpn.d.ts +4 -0
  80. package/dist/cli/commands/vpn.js +75 -0
  81. package/dist/cli/commands/web.d.ts +4 -0
  82. package/dist/cli/commands/web.js +95 -0
  83. package/dist/cli/history.js +1 -1
  84. package/dist/cli/index.js +46 -3
  85. package/dist/cli/manifest.d.ts +303 -29
  86. package/dist/cli/manifest.js +173 -43
  87. package/dist/cli/menu/add-agent.d.ts +47 -0
  88. package/dist/cli/menu/add-agent.js +168 -0
  89. package/dist/cli/menu/apps.d.ts +31 -2
  90. package/dist/cli/menu/apps.js +44 -5
  91. package/dist/cli/menu/command-fields.d.ts +18 -0
  92. package/dist/cli/menu/command-fields.js +61 -0
  93. package/dist/cli/menu/command-form.d.ts +22 -0
  94. package/dist/cli/menu/command-form.js +169 -46
  95. package/dist/cli/menu/data-folder.d.ts +18 -0
  96. package/dist/cli/menu/data-folder.js +99 -0
  97. package/dist/cli/menu/menu.d.ts +1 -1
  98. package/dist/cli/menu/menu.js +11 -8
  99. package/dist/cli/menu/pages/accounts-page.js +55 -24
  100. package/dist/cli/menu/pages/agents-page.d.ts +66 -0
  101. package/dist/cli/menu/pages/agents-page.js +338 -0
  102. package/dist/cli/menu/pages/apps-page.js +1 -2
  103. package/dist/cli/menu/pages/list-page.d.ts +16 -3
  104. package/dist/cli/menu/pages/list-page.js +40 -8
  105. package/dist/cli/menu/pages/maintenance-page.d.ts +9 -0
  106. package/dist/cli/menu/pages/maintenance-page.js +161 -72
  107. package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
  108. package/dist/cli/menu/pages/sandbox-page.js +132 -43
  109. package/dist/cli/menu/pages/sessions-page.d.ts +4 -0
  110. package/dist/cli/menu/pages/sessions-page.js +41 -25
  111. package/dist/cli/menu/pages/settings-page.d.ts +8 -4
  112. package/dist/cli/menu/pages/settings-page.js +36 -13
  113. package/dist/cli/menu/settings/categories.d.ts +2 -0
  114. package/dist/cli/menu/settings/categories.js +19 -44
  115. package/dist/cli/menu/settings/collections/collection-editor.d.ts +7 -1
  116. package/dist/cli/menu/settings/collections/collection-editor.js +12 -8
  117. package/dist/cli/menu/settings/collections/map-editor.js +5 -1
  118. package/dist/cli/menu/settings/collections/object-list-editor.js +2 -2
  119. package/dist/cli/menu/settings/collections/object-map-editor.js +2 -2
  120. package/dist/cli/menu/settings/collections/string-list-editor.d.ts +11 -1
  121. package/dist/cli/menu/settings/collections/string-list-editor.js +23 -0
  122. package/dist/cli/menu/settings/index.d.ts +1 -1
  123. package/dist/cli/menu/settings/index.js +1 -1
  124. package/dist/cli/menu/settings/object-editor.d.ts +5 -1
  125. package/dist/cli/menu/settings/object-editor.js +15 -4
  126. package/dist/cli/menu/settings/path-kind.d.ts +20 -0
  127. package/dist/cli/menu/settings/path-kind.js +44 -0
  128. package/dist/cli/menu/settings/schema-node.d.ts +9 -0
  129. package/dist/cli/menu/settings/schema-node.js +26 -0
  130. package/dist/cli/menu/settings/value-editor.d.ts +5 -0
  131. package/dist/cli/menu/settings/value-editor.js +51 -18
  132. package/dist/cli/menu/several.js +7 -5
  133. package/dist/cli/menu/shell.d.ts +37 -10
  134. package/dist/cli/menu/shell.js +90 -10
  135. package/dist/cli/menu/vpn-login.d.ts +16 -0
  136. package/dist/cli/menu/vpn-login.js +66 -0
  137. package/dist/cli/output/print.d.ts +2 -0
  138. package/dist/cli/output/print.js +8 -1
  139. package/dist/cli/services/accounts-service.d.ts +2 -0
  140. package/dist/cli/services/accounts-service.js +2 -2
  141. package/dist/cli/services/health-service.d.ts +0 -6
  142. package/dist/cli/services/health-service.js +5 -42
  143. package/dist/cli/services/index.d.ts +3 -1
  144. package/dist/cli/services/index.js +3 -1
  145. package/dist/cli/services/migrate-service.js +1 -1
  146. package/dist/cli/services/update-service.d.ts +2 -0
  147. package/dist/cli/services/update-service.js +13 -6
  148. package/dist/cli/services/vpn-service.d.ts +28 -0
  149. package/dist/cli/services/vpn-service.js +46 -0
  150. package/dist/cli/services/web-service.d.ts +22 -0
  151. package/dist/cli/services/web-service.js +111 -0
  152. package/dist/cli/tui/app.d.ts +6 -3
  153. package/dist/cli/tui/app.js +12 -5
  154. package/dist/cli/tui/dialogs/path-listing.d.ts +43 -0
  155. package/dist/cli/tui/dialogs/path-listing.js +127 -0
  156. package/dist/cli/tui/dialogs/path-picker.d.ts +67 -0
  157. package/dist/cli/tui/dialogs/path-picker.js +423 -0
  158. package/dist/cli/tui/dialogs/text-screen.d.ts +6 -0
  159. package/dist/cli/tui/dialogs/text-screen.js +10 -3
  160. package/dist/cli/tui/frame.d.ts +7 -10
  161. package/dist/cli/tui/frame.js +19 -10
  162. package/dist/cli/tui/index.d.ts +4 -3
  163. package/dist/cli/tui/index.js +2 -1
  164. package/dist/cli/tui/list/list-screen.d.ts +14 -2
  165. package/dist/cli/tui/list/list-screen.js +68 -32
  166. package/dist/cli/tui/list/list-types.d.ts +8 -4
  167. package/dist/cli/tui/nav.d.ts +29 -0
  168. package/dist/cli/tui/nav.js +67 -0
  169. package/dist/cli/validate.d.ts +1 -1
  170. package/dist/cli/validate.js +3 -3
  171. package/dist/cli/version.d.ts +2 -0
  172. package/dist/cli/version.js +11 -0
  173. package/dist/core/config/defaults.js +5 -1
  174. package/dist/core/config/loader.js +12 -2
  175. package/dist/core/local-config/overrides.js +1 -0
  176. package/dist/core/prompt.d.ts +19 -0
  177. package/dist/core/prompt.js +43 -3
  178. package/dist/core/schema/common.d.ts +6 -0
  179. package/dist/core/schema/common.js +8 -0
  180. package/dist/core/schema/config.d.ts +23 -0
  181. package/dist/core/schema/config.js +10 -1
  182. package/dist/core/schema/harness.d.ts +1 -0
  183. package/dist/core/schema/harness.js +8 -2
  184. package/dist/core/schema/index.d.ts +3 -1
  185. package/dist/core/schema/index.js +3 -1
  186. package/dist/core/schema/profile.d.ts +1 -0
  187. package/dist/core/schema/profile.js +3 -0
  188. package/dist/core/schema/redact.d.ts +13 -0
  189. package/dist/core/schema/redact.js +75 -0
  190. package/dist/core/schema/sandbox.js +1 -0
  191. package/dist/core/schema/vpn.d.ts +17 -0
  192. package/dist/core/schema/vpn.js +22 -0
  193. package/dist/core/schema/web.d.ts +6 -0
  194. package/dist/core/schema/web.js +14 -0
  195. package/dist/core/types.d.ts +26 -0
  196. package/dist/guest/agents/agent-files.d.ts +49 -0
  197. package/dist/guest/agents/agent-files.js +63 -0
  198. package/dist/guest/agents/agent-installer.js +2 -9
  199. package/dist/guest/agents/local-state.d.ts +12 -0
  200. package/dist/guest/agents/local-state.js +198 -0
  201. package/dist/guest/agents/sbx-agent.d.ts +2 -0
  202. package/dist/guest/agents/sbx-agent.js +35 -0
  203. package/dist/guest/boot/boot.d.ts +4 -0
  204. package/dist/guest/boot/boot.js +50 -11
  205. package/dist/guest/daemon/daemon.js +4 -1
  206. package/dist/guest/daemon/exec.js +2 -1
  207. package/dist/guest/daemon/idle-shutdown.js +2 -0
  208. package/dist/guest/daemon/sessions.js +4 -1
  209. package/dist/guest/entries/agent.d.ts +1 -0
  210. package/dist/guest/entries/agent.js +10 -0
  211. package/dist/guest/entries/daemon.js +2 -0
  212. package/dist/guest/runtime/csc.d.ts +1 -0
  213. package/dist/guest/runtime/csc.js +1 -0
  214. package/dist/guest/runtime/secrets.d.ts +12 -0
  215. package/dist/guest/runtime/secrets.js +63 -0
  216. package/dist/guest/setup/claude-profile.d.ts +2 -1
  217. package/dist/guest/setup/claude-profile.js +12 -1
  218. package/dist/guest/setup/environment.d.ts +6 -0
  219. package/dist/guest/setup/environment.js +56 -6
  220. package/dist/guest/setup/path-plan.d.ts +32 -0
  221. package/dist/guest/setup/path-plan.js +67 -0
  222. package/dist/guest/setup/shell-path.d.ts +1 -0
  223. package/dist/guest/setup/shell-path.js +22 -0
  224. package/dist/guest/toolchains/languages/python.js +1 -1
  225. package/dist/guest/toolchains/runtimes.js +2 -2
  226. package/dist/guest/vpn/index.d.ts +1 -0
  227. package/dist/guest/vpn/index.js +1 -0
  228. package/dist/guest/vpn/openvpn.d.ts +9 -0
  229. package/dist/guest/vpn/openvpn.js +47 -0
  230. package/dist/guest/vpn/tunnel.d.ts +17 -0
  231. package/dist/guest/vpn/tunnel.js +78 -0
  232. package/dist/guest/vpn/vpn.d.ts +6 -0
  233. package/dist/guest/vpn/vpn.js +126 -0
  234. package/dist/host/channel/attach.d.ts +3 -1
  235. package/dist/host/channel/attach.js +1 -1
  236. package/dist/host/channel/channel.d.ts +7 -0
  237. package/dist/host/channel/channel.js +26 -2
  238. package/dist/host/health/acl-read.d.ts +17 -0
  239. package/dist/host/health/acl-read.js +45 -0
  240. package/dist/host/health/config-backup.d.ts +11 -0
  241. package/dist/host/health/config-backup.js +73 -0
  242. package/dist/host/health/index.d.ts +6 -0
  243. package/dist/host/health/index.js +7 -0
  244. package/dist/host/health/install-state.d.ts +33 -0
  245. package/dist/host/health/install-state.js +110 -0
  246. package/dist/host/health/path-copies.d.ts +32 -0
  247. package/dist/host/health/path-copies.js +82 -0
  248. package/dist/host/health/stale.d.ts +45 -0
  249. package/dist/host/health/stale.js +120 -0
  250. package/dist/host/health/system.d.ts +37 -0
  251. package/dist/host/health/system.js +87 -0
  252. package/dist/host/install/copies.d.ts +13 -0
  253. package/dist/host/install/copies.js +63 -0
  254. package/dist/host/install/index.d.ts +1 -0
  255. package/dist/host/install/index.js +1 -0
  256. package/dist/host/launch/generate.js +25 -1
  257. package/dist/host/launch/session-file.js +4 -1
  258. package/dist/host/launch/stage.js +1 -1
  259. package/dist/host/migrate/backup.js +3 -0
  260. package/dist/host/migrate/migrations/config-file.d.ts +2 -0
  261. package/dist/host/migrate/migrations/config-file.js +7 -0
  262. package/dist/host/migrate/migrations/follow-machine-storage.js +9 -1
  263. package/dist/host/migrate/migrations/pin-storage-root.d.ts +4 -0
  264. package/dist/host/migrate/migrations/pin-storage-root.js +46 -0
  265. package/dist/host/migrate/migrations/unpin-config-defaults.js +4 -1
  266. package/dist/host/migrate/registry.js +2 -1
  267. package/dist/host/profile/accounts.d.ts +25 -1
  268. package/dist/host/profile/accounts.js +96 -13
  269. package/dist/host/profile/agent-accounts.d.ts +5 -1
  270. package/dist/host/profile/agent-accounts.js +35 -5
  271. package/dist/host/profile/index.d.ts +2 -1
  272. package/dist/host/profile/index.js +1 -0
  273. package/dist/host/profile/plugin-share.d.ts +26 -0
  274. package/dist/host/profile/plugin-share.js +146 -0
  275. package/dist/host/projects/join.d.ts +7 -1
  276. package/dist/host/projects/join.js +21 -4
  277. package/dist/host/projects/registry.d.ts +16 -10
  278. package/dist/host/projects/registry.js +74 -6
  279. package/dist/host/sandbox/platform.d.ts +2 -0
  280. package/dist/host/sandbox/platform.js +21 -2
  281. package/dist/host/secrets/index.d.ts +2 -0
  282. package/dist/host/secrets/index.js +2 -0
  283. package/dist/host/secrets/launch-secrets.d.ts +17 -0
  284. package/dist/host/secrets/launch-secrets.js +93 -0
  285. package/dist/host/secrets/secret-store.d.ts +25 -0
  286. package/dist/host/secrets/secret-store.js +89 -0
  287. package/dist/host/sharing/acl.d.ts +16 -2
  288. package/dist/host/sharing/acl.js +35 -10
  289. package/dist/host/sharing/current.js +11 -2
  290. package/dist/host/sharing/doctor.d.ts +2 -0
  291. package/dist/host/sharing/doctor.js +44 -0
  292. package/dist/host/sharing/group-access.d.ts +6 -0
  293. package/dist/host/sharing/group-access.js +24 -0
  294. package/dist/host/sharing/index.d.ts +2 -1
  295. package/dist/host/sharing/index.js +2 -1
  296. package/dist/host/sharing/lifecycle.d.ts +8 -2
  297. package/dist/host/sharing/lifecycle.js +16 -7
  298. package/dist/host/storage/cleanup.d.ts +2 -0
  299. package/dist/host/storage/cleanup.js +2 -1
  300. package/dist/host/storage/history.d.ts +21 -0
  301. package/dist/host/storage/history.js +49 -0
  302. package/dist/host/storage/index.d.ts +4 -0
  303. package/dist/host/storage/index.js +4 -0
  304. package/dist/host/storage/move.d.ts +95 -0
  305. package/dist/host/storage/move.js +384 -0
  306. package/dist/host/storage/offer.d.ts +11 -0
  307. package/dist/host/storage/offer.js +36 -0
  308. package/dist/host/storage/root.d.ts +18 -0
  309. package/dist/host/storage/root.js +77 -0
  310. package/dist/host/tools/windows-terminal.d.ts +2 -0
  311. package/dist/host/tools/windows-terminal.js +23 -5
  312. package/dist/host/update/index.d.ts +1 -1
  313. package/dist/host/update/index.js +1 -1
  314. package/dist/host/update/notice.d.ts +2 -0
  315. package/dist/host/update/notice.js +13 -1
  316. package/dist/host/update/update-state.d.ts +6 -0
  317. package/dist/host/update/update-state.js +2 -0
  318. package/dist/host/update/updater.d.ts +19 -0
  319. package/dist/host/update/updater.js +88 -12
  320. package/dist/host/vpn/index.d.ts +4 -0
  321. package/dist/host/vpn/index.js +4 -0
  322. package/dist/host/vpn/login.d.ts +24 -0
  323. package/dist/host/vpn/login.js +79 -0
  324. package/dist/host/vpn/nordvpn.d.ts +21 -0
  325. package/dist/host/vpn/nordvpn.js +77 -0
  326. package/dist/host/vpn/prepare.d.ts +17 -0
  327. package/dist/host/vpn/prepare.js +77 -0
  328. package/dist/host/vpn/status.d.ts +12 -0
  329. package/dist/host/vpn/status.js +53 -0
  330. package/dist/host/web/auth.d.ts +38 -0
  331. package/dist/host/web/auth.js +96 -0
  332. package/dist/host/web/backend.d.ts +14 -0
  333. package/dist/host/web/backend.js +1 -0
  334. package/dist/host/web/bridge.d.ts +27 -0
  335. package/dist/host/web/bridge.js +144 -0
  336. package/dist/host/web/browser.d.ts +1 -0
  337. package/dist/host/web/browser.js +10 -0
  338. package/dist/host/web/control.d.ts +17 -0
  339. package/dist/host/web/control.js +67 -0
  340. package/dist/host/web/index.d.ts +9 -0
  341. package/dist/host/web/index.js +8 -0
  342. package/dist/host/web/runner.d.ts +14 -0
  343. package/dist/host/web/runner.js +45 -0
  344. package/dist/host/web/server.d.ts +40 -0
  345. package/dist/host/web/server.js +218 -0
  346. package/dist/host/web/state.d.ts +20 -0
  347. package/dist/host/web/state.js +41 -0
  348. package/dist/host/web/static-files.d.ts +13 -0
  349. package/dist/host/web/static-files.js +57 -0
  350. package/dist/runtime/agent.js +27614 -0
  351. package/dist/runtime/daemon.js +1012 -618
  352. package/dist/runtime/db.js +236 -106
  353. package/dist/runtime/main.js +1093 -250
  354. package/dist/runtime/netproxy.js +102 -3
  355. package/dist/runtime/view.js +102 -3
  356. package/dist/selftest/accounts.test.js +40 -0
  357. package/dist/selftest/agent-paths.test.d.ts +2 -0
  358. package/dist/selftest/agent-paths.test.js +115 -0
  359. package/dist/selftest/channel.test.js +5 -0
  360. package/dist/selftest/cli.test.js +34 -4
  361. package/dist/selftest/credentials.test.d.ts +2 -0
  362. package/dist/selftest/credentials.test.js +198 -0
  363. package/dist/selftest/generate.test.js +1 -1
  364. package/dist/selftest/health.test.d.ts +2 -0
  365. package/dist/selftest/health.test.js +470 -0
  366. package/dist/selftest/host-sim.test.js +2 -0
  367. package/dist/selftest/index.js +19 -0
  368. package/dist/selftest/layout.test.js +2 -0
  369. package/dist/selftest/menu-screens.d.ts +7 -0
  370. package/dist/selftest/menu-screens.js +65 -16
  371. package/dist/selftest/menu.test.js +574 -21
  372. package/dist/selftest/migrate.test.js +1 -1
  373. package/dist/selftest/personalization.test.js +3 -1
  374. package/dist/selftest/projects.test.js +39 -1
  375. package/dist/selftest/shared-use.test.js +64 -2
  376. package/dist/selftest/sharing.test.js +50 -1
  377. package/dist/selftest/storage.test.d.ts +2 -0
  378. package/dist/selftest/storage.test.js +363 -0
  379. package/dist/selftest/support/fixtures.d.ts +14 -0
  380. package/dist/selftest/support/releases.js +1 -1
  381. package/dist/selftest/toolchains.test.js +21 -0
  382. package/dist/selftest/update-service.test.js +87 -3
  383. package/dist/selftest/vpn.test.d.ts +2 -0
  384. package/dist/selftest/vpn.test.js +279 -0
  385. package/dist/selftest/web.test.d.ts +2 -0
  386. package/dist/selftest/web.test.js +304 -0
  387. package/dist/shared/apps/catalogue.js +31 -0
  388. package/dist/shared/channel/index.d.ts +1 -0
  389. package/dist/shared/channel/index.js +1 -0
  390. package/dist/shared/channel/run-dir.d.ts +6 -1
  391. package/dist/shared/channel/run-dir.js +12 -1
  392. package/dist/shared/channel/schema.d.ts +19 -0
  393. package/dist/shared/channel/schema.js +11 -0
  394. package/dist/shared/channel/secrets.d.ts +28 -0
  395. package/dist/shared/channel/secrets.js +44 -0
  396. package/dist/shared/vpn/download.d.ts +13 -0
  397. package/dist/shared/vpn/download.js +14 -0
  398. package/dist/shared/vpn/kill-switch.d.ts +18 -0
  399. package/dist/shared/vpn/kill-switch.js +81 -0
  400. package/dist/shared/vpn/ovpn.d.ts +23 -0
  401. package/dist/shared/vpn/ovpn.js +244 -0
  402. package/dist/shared/web/index.d.ts +1 -0
  403. package/dist/shared/web/index.js +1 -0
  404. package/dist/shared/web/protocol.d.ts +61 -0
  405. package/dist/shared/web/protocol.js +35 -0
  406. package/dist/sync/sync-cycle.js +5 -1
  407. package/dist/sync/sync-state.d.ts +1 -0
  408. package/dist/sync/sync-state.js +1 -0
  409. package/dist/sync/watcher.js +10 -0
  410. package/dist/web/index.html +13 -0
  411. package/dist/web/main-CXESL347.js +45 -0
  412. package/dist/web/main-VLR7DVQ4.css +1 -0
  413. package/native/csharp/FolderSecurity.cs +29 -0
  414. package/native/scripts/protect-data.ps1 +29 -0
  415. package/native/scripts/read-folder-sddl.ps1 +10 -0
  416. package/native/scripts/set-folder-access.ps1 +49 -7
  417. package/node_modules/ws/LICENSE +20 -0
  418. package/node_modules/ws/README.md +548 -0
  419. package/node_modules/ws/browser.js +8 -0
  420. package/node_modules/ws/index.js +22 -0
  421. package/node_modules/ws/lib/buffer-util.js +131 -0
  422. package/node_modules/ws/lib/constants.js +19 -0
  423. package/node_modules/ws/lib/event-target.js +292 -0
  424. package/node_modules/ws/lib/extension.js +203 -0
  425. package/node_modules/ws/lib/limiter.js +55 -0
  426. package/node_modules/ws/lib/permessage-deflate.js +530 -0
  427. package/node_modules/ws/lib/receiver.js +743 -0
  428. package/node_modules/ws/lib/sender.js +607 -0
  429. package/node_modules/ws/lib/stream.js +161 -0
  430. package/node_modules/ws/lib/subprotocol.js +62 -0
  431. package/node_modules/ws/lib/validation.js +152 -0
  432. package/node_modules/ws/lib/websocket-server.js +562 -0
  433. package/node_modules/ws/lib/websocket.js +1421 -0
  434. package/node_modules/ws/package.json +74 -0
  435. package/node_modules/ws/wrapper.mjs +21 -0
  436. package/package.json +15 -5
  437. package/sandbox/csharp/EnvBroadcast.cs +33 -0
  438. package/sandbox/csharp/PathSync.cs +90 -0
  439. package/sandbox/scripts/install-shell-path.ps1 +73 -0
  440. package/sandbox/scripts/set-machine-environment.ps1 +37 -4
  441. package/schemas/config.schema.json +79 -4
  442. package/schemas/sandbox-config.schema.json +14 -1
  443. package/dist/cli/commands/repair/checks.d.ts +0 -2
  444. package/dist/cli/commands/repair/checks.js +0 -246
  445. package/dist/cli/menu/pages/start-page.d.ts +0 -43
  446. package/dist/cli/menu/pages/start-page.js +0 -192
  447. package/dist/cli/tui/tab-bar.d.ts +0 -9
  448. package/dist/cli/tui/tab-bar.js +0 -33
@@ -0,0 +1,37 @@
1
+ import { type TaskRow } from '../sharing/index.js';
2
+ /** The mandatory-level SID of an elevated (admin) token. */
3
+ export declare const HIGH_INTEGRITY_SID = "S-1-16-12288";
4
+ export { SHARING_GROUP } from './acl-read.js';
5
+ export interface SystemProbe {
6
+ processes(): TaskRow[];
7
+ pidAlive(pid: number): boolean;
8
+ /** Ends one process (the caller has checked it is the right one). */
9
+ stop(pid: number): void;
10
+ /** The unexpanded PATH of this user or of the PC, or null when it cannot be read. */
11
+ registryPath(scope: 'user' | 'machine'): string | null;
12
+ /** Is the automatic-update task there? null when it cannot be asked. */
13
+ updateTaskExists(): boolean | null;
14
+ /** `icacls <folder>` output, or null. */
15
+ icacls(dir: string): string | null;
16
+ /** The accounts in the sharing group, or null when it cannot be read (no such group included). */
17
+ groupMembers(): string[] | null;
18
+ /** Is this SID in the token of this very process? null when whoami fails. */
19
+ tokenHasGroup(sid: string): boolean | null;
20
+ /** Does an HTTPS request to the address answer within `ms`? */
21
+ reachable(url: string, ms: number): Promise<boolean>;
22
+ }
23
+ /** The PATH value in `reg query ... /v Path` output, or null. */
24
+ export declare function parseRegPath(text: string): string | null;
25
+ /** The accounts listed by `net localgroup <name>`: the lines between the dashes and the closing sentence. */
26
+ export declare function parseGroupMembers(text: string): string[] | null;
27
+ export declare class WindowsProbe implements SystemProbe {
28
+ processes(): TaskRow[];
29
+ pidAlive(pid: number): boolean;
30
+ stop(pid: number): void;
31
+ registryPath(scope: 'user' | 'machine'): string | null;
32
+ updateTaskExists(): boolean | null;
33
+ icacls(dir: string): string | null;
34
+ groupMembers(): string[] | null;
35
+ tokenHasGroup(sid: string): boolean | null;
36
+ reachable(url: string, ms: number): Promise<boolean>;
37
+ }
@@ -0,0 +1,87 @@
1
+ // What the health checks ask Windows (the processes, your PATH, the update task, folder permissions, the network). One
2
+ // interface so tests give their own answers on temp folders; the real one only reads, apart from stop().
3
+ import { spawnSync } from 'node:child_process';
4
+ import { SYSTEM, system32 } from '../../shared/system.js';
5
+ import { refuseInSelfTest } from '../test-guard.js';
6
+ import { listProcesses } from '../sharing/index.js';
7
+ import { SHARING_GROUP } from './acl-read.js';
8
+ const HELPER_TIMEOUT_MS = 15000;
9
+ const USER_ENVIRONMENT = 'HKCU\\Environment';
10
+ const MACHINE_ENVIRONMENT = 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment';
11
+ /** The mandatory-level SID of an elevated (admin) token. */
12
+ export const HIGH_INTEGRITY_SID = 'S-1-16-12288';
13
+ export { SHARING_GROUP } from './acl-read.js';
14
+ /** The PATH value in `reg query ... /v Path` output, or null. */
15
+ export function parseRegPath(text) {
16
+ const m = /^\s+Path\s+REG_(?:EXPAND_)?SZ\s+(.*)$/im.exec(text);
17
+ return m ? m[1].trim() : null;
18
+ }
19
+ /** The accounts listed by `net localgroup <name>`: the lines between the dashes and the closing sentence. */
20
+ export function parseGroupMembers(text) {
21
+ const lines = text.split(/\r?\n/);
22
+ const start = lines.findIndex((l) => /^-{5,}\s*$/.test(l));
23
+ if (start < 0)
24
+ return null;
25
+ const rest = lines.slice(start + 1);
26
+ const end = rest.findIndex((l) => l.trim() === '' || /^\S.*\.$/.test(l.trim()));
27
+ return rest
28
+ .slice(0, end < 0 ? rest.length : end)
29
+ .map((l) => l.trim())
30
+ .filter(Boolean);
31
+ }
32
+ function run(exe, args) {
33
+ const r = spawnSync(exe, args, { encoding: 'utf8', windowsHide: true, timeout: HELPER_TIMEOUT_MS });
34
+ return { out: (r.stdout ?? '') + (r.stderr ?? ''), status: r.error ? null : r.status };
35
+ }
36
+ export class WindowsProbe {
37
+ processes() {
38
+ return listProcesses();
39
+ }
40
+ pidAlive(pid) {
41
+ if (!Number.isInteger(pid) || pid <= 0)
42
+ return false;
43
+ try {
44
+ process.kill(pid, 0);
45
+ return true;
46
+ }
47
+ catch (e) {
48
+ return e.code === 'EPERM';
49
+ }
50
+ }
51
+ stop(pid) {
52
+ refuseInSelfTest('stop programs');
53
+ if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid)
54
+ throw new Error('Not a process to stop: ' + pid);
55
+ process.kill(pid);
56
+ }
57
+ registryPath(scope) {
58
+ const r = run(SYSTEM.reg(), ['query', scope === 'user' ? USER_ENVIRONMENT : MACHINE_ENVIRONMENT, '/v', 'Path']);
59
+ return r.status === 0 ? parseRegPath(r.out) : null;
60
+ }
61
+ updateTaskExists() {
62
+ const r = run(system32('schtasks.exe'), ['/Query', '/TN', '\\WindowsDevelopmentSandbox\\Update']);
63
+ return r.status === null ? null : r.status === 0;
64
+ }
65
+ icacls(dir) {
66
+ const r = run(SYSTEM.icacls(), [dir]);
67
+ return r.status === 0 ? r.out : null;
68
+ }
69
+ groupMembers() {
70
+ const r = run(system32('net.exe'), ['localgroup', SHARING_GROUP]);
71
+ return r.status === 0 ? parseGroupMembers(r.out) : null;
72
+ }
73
+ tokenHasGroup(sid) {
74
+ const r = run(SYSTEM.whoami(), ['/groups', '/fo', 'csv', '/nh']);
75
+ return r.status === 0 ? r.out.includes('"' + sid + '"') : null;
76
+ }
77
+ async reachable(url, ms) {
78
+ try {
79
+ const response = await fetch(url, { method: 'HEAD', signal: AbortSignal.timeout(ms) });
80
+ return response.status < 500;
81
+ }
82
+ catch {
83
+ // Any failure (DNS, proxy, timeout) means "not reachable"; the caller words what to do.
84
+ return false;
85
+ }
86
+ }
87
+ }
@@ -0,0 +1,13 @@
1
+ import { InstallLayout } from './layout.js';
2
+ /** The install for everyone (%ProgramFiles%\WindowsDevelopmentSandbox), or null. */
3
+ export declare function machineInstall(env?: NodeJS.ProcessEnv): InstallLayout | null;
4
+ /** The install for just this user (%LOCALAPPDATA%\WindowsDevelopmentSandbox), or null. */
5
+ export declare function userInstall(env?: NodeJS.ProcessEnv): InstallLayout | null;
6
+ /** The installs that exist, the one for everyone first (it comes first on the PATH too). */
7
+ export declare function managedInstalls(env?: NodeJS.ProcessEnv): InstallLayout[];
8
+ /** Where this copy runs from, for `--version`: "all users, <folder>", "just you, <folder>", "npm copy, <folder>". */
9
+ export declare function describeCopy(packageRoot: string): string;
10
+ /** The line to print when this copy is not a managed install but one exists (it is hidden by this one), else null. */
11
+ export declare function hiddenInstallLine(packageRoot: string, env?: NodeJS.ProcessEnv): string | null;
12
+ /** hiddenInstallLine, but once per version (null afterwards). `storageRoot` holds the note that it was shown. */
13
+ export declare function takeHiddenInstallNotice(storageRoot: string, packageRoot: string, version: string, env?: NodeJS.ProcessEnv): string | null;
@@ -0,0 +1,63 @@
1
+ // Which managed installs exist on this PC, and what a copy that runs from somewhere else should say about them. A
2
+ // managed install (<ProgramFiles or %LOCALAPPDATA%>\WindowsDevelopmentSandbox) is found from any copy of the program,
3
+ // so an npm copy or a checkout still sees the machine install that shares sandboxes with other users.
4
+ import path from 'node:path';
5
+ import { z } from 'zod';
6
+ import { fileExists } from '../../core/util/index.js';
7
+ import { JsonFile } from '../../shared/io/index.js';
8
+ import { InstallLayout } from './layout.js';
9
+ const FOLDER = 'WindowsDevelopmentSandbox';
10
+ function installAt(base, scope) {
11
+ if (!base || !path.isAbsolute(base))
12
+ return null;
13
+ const root = path.join(base, FOLDER);
14
+ if (!fileExists(path.join(root, 'active-version')) && !fileExists(path.join(root, 'install.json')))
15
+ return null;
16
+ return InstallLayout.at(root, scope);
17
+ }
18
+ /** The install for everyone (%ProgramFiles%\WindowsDevelopmentSandbox), or null. */
19
+ export function machineInstall(env = process.env) {
20
+ return installAt(env.ProgramFiles, 'machine');
21
+ }
22
+ /** The install for just this user (%LOCALAPPDATA%\WindowsDevelopmentSandbox), or null. */
23
+ export function userInstall(env = process.env) {
24
+ return installAt(env.LOCALAPPDATA, 'user');
25
+ }
26
+ /** The installs that exist, the one for everyone first (it comes first on the PATH too). */
27
+ export function managedInstalls(env = process.env) {
28
+ return [machineInstall(env), userInstall(env)].filter((l) => l !== null);
29
+ }
30
+ /** Where this copy runs from, for `--version`: "all users, <folder>", "just you, <folder>", "npm copy, <folder>". */
31
+ export function describeCopy(packageRoot) {
32
+ const layout = InstallLayout.detect(packageRoot);
33
+ if (layout)
34
+ return (layout.scope === 'machine' ? 'all users, ' : 'just you, ') + layout.root;
35
+ const resolved = path.resolve(packageRoot);
36
+ const npm = path.basename(path.dirname(resolved)).toLowerCase() === 'node_modules';
37
+ return (npm ? 'npm copy, ' : 'from ') + resolved;
38
+ }
39
+ /** The line to print when this copy is not a managed install but one exists (it is hidden by this one), else null. */
40
+ export function hiddenInstallLine(packageRoot, env = process.env) {
41
+ if (InstallLayout.detect(packageRoot))
42
+ return null;
43
+ const installed = managedInstalls(env)[0];
44
+ if (!installed)
45
+ return null;
46
+ return ('Running the copy in ' +
47
+ path.resolve(packageRoot) +
48
+ '; the installed one in ' +
49
+ installed.root +
50
+ ' is hidden. sandbox doctor explains.');
51
+ }
52
+ const NoticeSchema = z.object({ version: z.string() });
53
+ /** hiddenInstallLine, but once per version (null afterwards). `storageRoot` holds the note that it was shown. */
54
+ export function takeHiddenInstallNotice(storageRoot, packageRoot, version, env = process.env) {
55
+ const line = hiddenInstallLine(packageRoot, env);
56
+ if (!line)
57
+ return null;
58
+ const file = new JsonFile(NoticeSchema, path.join(storageRoot, 'copy-notice.json'));
59
+ if (file.read()?.version === version)
60
+ return null;
61
+ file.write({ version });
62
+ return line;
63
+ }
@@ -2,3 +2,4 @@ export { type InstallScope, InstallLayout, compareVersions, VERSION_PATTERN, NOD
2
2
  export { shimText, readBins, writeShims } from './shims.js';
3
3
  export { InstallRecordFile, InstallRecordSchema, type InstallRecord } from './install-record.js';
4
4
  export { UpdateTask } from './scheduled-task.js';
5
+ export { machineInstall, userInstall, managedInstalls, describeCopy, hiddenInstallLine, takeHiddenInstallNotice, } from './copies.js';
@@ -2,3 +2,4 @@ export { InstallLayout, compareVersions, VERSION_PATTERN, NODE_PATTERN } from '.
2
2
  export { shimText, readBins, writeShims } from './shims.js';
3
3
  export { InstallRecordFile, InstallRecordSchema } from './install-record.js';
4
4
  export { UpdateTask } from './scheduled-task.js';
5
+ export { machineInstall, userInstall, managedInstalls, describeCopy, hiddenInstallLine, takeHiddenInstallNotice, } from './copies.js';
@@ -6,6 +6,7 @@ import crypto from 'node:crypto';
6
6
  import { widenForProjects } from '../../core/local-config/index.js';
7
7
  import { dirExists, ensureDir, fail, writeJson } from '../../core/util/index.js';
8
8
  import { capturePersonalization, stageClaudeProfile } from '../profile/index.js';
9
+ import { clearSecrets, writeSecrets } from '../secrets/index.js';
9
10
  import { assertTestWritable } from '../test-guard.js';
10
11
  import { planBootProjects } from './boot-projects.js';
11
12
  import { planDatabases } from './database-plan.js';
@@ -15,6 +16,15 @@ import { copyRuntime, stageGitIdentity, stageHooks } from './stage.js';
15
16
  import { decideProjectToolchains } from './toolchains.js';
16
17
  import { buildWsb, resolveMemoryMb } from './wsb-file.js';
17
18
  const NO_PROFILE = { hostDir: null, staged: [], skipped: [], credentials: false, secretsRemoved: [] };
19
+ /** Moves the staged copy of this PC's Claude login out of the launch folder; its text goes in the one-shot file. */
20
+ function takeStagedCredentials(launchPath) {
21
+ const file = path.join(launchPath, 'claude-profile', '.credentials.json');
22
+ if (!fs.existsSync(file))
23
+ return undefined;
24
+ const text = fs.readFileSync(file, 'utf8');
25
+ fs.rmSync(file, { force: true });
26
+ return text;
27
+ }
18
28
  export function generateSandbox(given) {
19
29
  // Several projects at boot: the sandbox also gets what the others want and you allowed (core/local-config).
20
30
  const others = given.bootWants?.projects ?? [];
@@ -39,20 +49,34 @@ export function generateSandbox(given) {
39
49
  const memoryMb = Math.max(resolveMemoryMb(config.sandbox, o.freeMemoryBytes), ...others.map((p) => (typeof p.config.sandbox.memoryMb === 'number' ? p.config.sandbox.memoryMb : 0)));
40
50
  const launchId = Date.now() + '-' + crypto.randomBytes(3).toString('hex');
41
51
  const launchPath = path.join(storageRoot, 'launches', launchId);
42
- ensureDir(launchPath);
52
+ // Whatever must stay secret goes in a one-shot file in the data folder, never into the launch folder (the sandbox
53
+ // maps it read-only and it is kept for days). Leftovers of earlier launches go first.
43
54
  ensureDir(dataPath);
55
+ clearSecrets(storageRoot, dataPath);
56
+ ensureDir(launchPath);
44
57
  const mappingNotes = [];
45
58
  const mappings = collectMappings(o, launchPath, boot, mappingNotes);
46
59
  const profile = harnessName === 'claude'
47
60
  ? stageClaudeProfile(config.claudeProfile, launchPath, workspaceHost, config.secrets)
48
61
  : NO_PROFILE;
62
+ // This PC's Claude login leaves the launch folder for the one-shot file (with the agent environment and VPN login).
63
+ const claudeCredentials = takeStagedCredentials(launchPath);
49
64
  stageGitIdentity(launchPath);
50
65
  const personalization = config.personalization.enabled ? capturePersonalization(launchPath) : 'disabled';
51
66
  const databases = o.databases ?? planDatabases(config.databases, workspaceHost);
52
67
  const toolchains = decideProjectToolchains(o);
68
+ if (o.vpn) {
69
+ ensureDir(path.join(launchPath, 'vpn'));
70
+ fs.writeFileSync(path.join(launchPath, 'vpn', 'server.ovpn'), o.vpn.profile, 'utf8');
71
+ }
53
72
  const session = guestSession(o, { launchId, launchPath, harness, boot, mappings, profile, databases, toolchains });
54
73
  const sessionPath = path.join(launchPath, 'session.json');
55
74
  writeJson(sessionPath, session);
75
+ writeSecrets(dataPath, launchId, {
76
+ ...(harness && Object.keys(harness.environment).length ? { harnessEnvironment: harness.environment } : {}),
77
+ ...(claudeCredentials ? { claudeCredentials } : {}),
78
+ ...(o.vpn ? { vpn: o.vpn.credentials } : {}),
79
+ });
56
80
  fs.writeFileSync(path.join(launchPath, 'env.cmd'), logonEnv(session.workspace), 'utf8');
57
81
  copyRuntime(o.packageRoot, launchPath);
58
82
  stageHooks(o.packageRoot, launchPath, o.run.scripts);
@@ -32,7 +32,8 @@ export function guestSession(o, parts) {
32
32
  roots: [...parts.boot.roots, ...parts.boot.projects].map((r) => r.guest),
33
33
  ...(o.autoSession === false ? { autoSession: false } : {}),
34
34
  harnessName,
35
- harness: parts.harness,
35
+ // The environment (it may hold API keys) travels in a one-shot file in <data>\secrets, never in this file.
36
+ harness: parts.harness ? { ...parts.harness, environment: {} } : null,
36
37
  harnessArguments: o.harnessArgs,
37
38
  persistAgentState: config.storage.persistAgentState,
38
39
  networking: config.sandbox.networking,
@@ -47,6 +48,7 @@ export function guestSession(o, parts) {
47
48
  attach: config.attach,
48
49
  hooks: config.hooks,
49
50
  downloads: config.downloads,
51
+ ...(o.vpn ? { vpn: o.vpn.session } : {}),
50
52
  hostTools: hostToolsOf(o.probe),
51
53
  claudeProfile: {
52
54
  overwrite: config.claudeProfile.overwrite,
@@ -63,6 +65,7 @@ export function guestSession(o, parts) {
63
65
  auditProcesses: config.audit.enabled && config.audit.processes,
64
66
  agentHomes: Object.assign({}, ...Object.values(config.harnesses).map((h) => h.home?.env ?? {})),
65
67
  agentProfileDirs: Object.fromEntries(Object.entries(config.harnesses).flatMap(([name, h]) => h.home?.profileDirs?.length ? [[name, h.home.profileDirs]] : [])),
68
+ agents: Object.fromEntries(Object.entries(config.harnesses).map(([name, h]) => [name, { ...h, environment: {} }])),
66
69
  };
67
70
  }
68
71
  /**
@@ -26,7 +26,7 @@ export function stageScripts(target, scripts) {
26
26
  fs.copyFileSync(script, path.join(target, '90-extra-' + index + '-' + path.basename(script)));
27
27
  });
28
28
  }
29
- export const RUNTIME_FILES = ['main.js', 'daemon.js', 'view.js', 'gitguard.js', 'db.js', 'netproxy.js'];
29
+ export const RUNTIME_FILES = ['main.js', 'daemon.js', 'view.js', 'gitguard.js', 'db.js', 'netproxy.js', 'agent.js'];
30
30
  /** The bundled sandbox runtime (dist/runtime: one self-contained file per entry point) and the sandbox/ scripts. */
31
31
  export function copyRuntime(packageRoot, launchPath) {
32
32
  const runtime = path.join(packageRoot, 'dist', 'runtime');
@@ -6,6 +6,8 @@ import path from 'node:path';
6
6
  import { z } from 'zod';
7
7
  import { assertNoLinks, fail, isInsideOrEqual, isLink } from '../../core/util/index.js';
8
8
  import { JsonFile } from '../../shared/io/index.js';
9
+ import { protectPrivateFolder } from '../sharing/index.js';
10
+ import { ensureStorageRoot } from '../storage/index.js';
9
11
  const ManifestSchema = z.object({
10
12
  entries: z.array(z.object({ original: z.string(), copy: z.string(), existed: z.boolean() })),
11
13
  });
@@ -56,6 +58,7 @@ export class MigrationBackup {
56
58
  }
57
59
  /** Copies each existing file or folder (and records the ones that do not exist yet, to remove them on restore). */
58
60
  save(paths) {
61
+ ensureStorageRoot(this.storageRoot, protectPrivateFolder);
59
62
  const dataRoot = path.join(this.storageRoot, 'data');
60
63
  const entries = this.manifest.read()?.entries ?? [];
61
64
  for (const target of paths) {
@@ -6,3 +6,5 @@ export declare function readFileObject(file: string | null): Json | null;
6
6
  export declare function fullSnapshots(file: Json): DefaultsSnapshot[];
7
7
  /** Removes these values from the file, comments and the rest untouched, noting `why: <key>` for each. */
8
8
  export declare function removeValues(file: string, paths: readonly string[][], why: string, note: (line: string) => void): void;
9
+ /** Sets one value in the file (created when missing), comments and the rest untouched. */
10
+ export declare function setValue(file: string, path: string[], value: unknown): void;
@@ -1,5 +1,6 @@
1
1
  // Reading and editing a config.json (JSON with comments) for the migrations: removing a value keeps every comment.
2
2
  import fs from 'node:fs';
3
+ import nodePath from 'node:path';
3
4
  import { applyEdits, modify, parse } from 'jsonc-parser';
4
5
  import _ from 'lodash';
5
6
  import { writeFileAtomic } from '../../../shared/io/index.js';
@@ -26,3 +27,9 @@ export function removeValues(file, paths, why, note) {
26
27
  }
27
28
  writeFileAtomic(file, text);
28
29
  }
30
+ /** Sets one value in the file (created when missing), comments and the rest untouched. */
31
+ export function setValue(file, path, value) {
32
+ const text = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '{}\n';
33
+ fs.mkdirSync(nodePath.dirname(file), { recursive: true });
34
+ writeFileAtomic(file, applyEdits(text, modify(text, path, value, FORMAT)));
35
+ }
@@ -1,8 +1,11 @@
1
1
  // An all-users install keeps everyone's data under %ProgramData%\AgentSandbox (its machine config says where), so a
2
2
  // sandbox can be shared. A config.json written by an old `config init` pins storage.root to the old per-user default,
3
3
  // which wins over the machine config. Removes that pin, and only that: a storage.root you chose yourself stays.
4
+ import path from 'node:path';
4
5
  import _ from 'lodash';
5
6
  import { DEFAULTS, machineConfigPath } from '../../../core/config/index.js';
7
+ import { expandPath } from '../../../core/util/index.js';
8
+ import { holdsData } from '../../storage/root.js';
6
9
  import { fullSnapshots, readFileObject, removeValues } from './config-file.js';
7
10
  const ROOT = ['storage', 'root'];
8
11
  /** True when the machine config sets the storage folder and the user's file only pins a default of an old init. */
@@ -15,7 +18,12 @@ function pinnedOverMachine(configPath) {
15
18
  return false;
16
19
  const value = _.get(file, ROOT);
17
20
  const defaults = [_.get(DEFAULTS, ROOT), ...fullSnapshots(file).map((s) => _.get(s.defaults, ROOT))];
18
- return defaults.some((d) => _.isEqual(d, value));
21
+ if (!defaults.some((d) => _.isEqual(d, value)))
22
+ return false;
23
+ // A pin that holds your saved data while the shared folder has none is kept: sandbox storage offers the move.
24
+ const pinned = typeof value === 'string' ? expandPath(value, path.dirname(configPath ?? '')) : '';
25
+ const shared = expandPath(String(_.get(machine, ROOT)), path.dirname(machineConfigPath()));
26
+ return !(holdsData(pinned) && !holdsData(shared));
19
27
  }
20
28
  export const followMachineStorage = {
21
29
  id: '2026-10-follow-machine-storage',
@@ -0,0 +1,4 @@
1
+ import type { Migration } from '../migration.js';
2
+ /** All the conditions of the migration, as a pure check on what is on disk. */
3
+ export declare function needsPin(file: string, machineFile: string, oldRoot: string | null): boolean;
4
+ export declare const pinStorageRoot: Migration;
@@ -0,0 +1,46 @@
1
+ // 1.6 moves the default storage folder to %ProgramData%\AgentSandbox\users\<you>. Nobody's data may seem to vanish: a
2
+ // user with no storage.root of their own, no all-users install, and saved data in the old per-user folder keeps that
3
+ // folder, pinned in config.json. Moving it to the new default is offered later (sandbox storage).
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import _ from 'lodash';
7
+ import { machineConfigPath } from '../../../core/config/index.js';
8
+ import { holdsData, OLD_DEFAULT_ROOT, oldDefaultRoot } from '../../storage/root.js';
9
+ import { readFileObject, setValue } from './config-file.js';
10
+ const ROOT = ['storage', 'root'];
11
+ /** The file the pin goes in: the user's config, or the one it would be (not created until the pin is written). */
12
+ function targetFile(ctx) {
13
+ return ctx.configPath ?? path.join(ctx.userDir, 'config.json');
14
+ }
15
+ /** All the conditions of the migration, as a pure check on what is on disk. */
16
+ export function needsPin(file, machineFile, oldRoot) {
17
+ if (!oldRoot || !holdsData(oldRoot))
18
+ return false;
19
+ const machine = readFileObject(machineFile);
20
+ if (machine && _.has(machine, ROOT))
21
+ return false;
22
+ if (!fs.existsSync(file))
23
+ return true;
24
+ const parsed = readFileObject(file);
25
+ // A file that cannot be read is left alone: editing it blindly could lose the user's settings.
26
+ return parsed !== null && !_.has(parsed, ROOT);
27
+ }
28
+ export const pinStorageRoot = {
29
+ id: '2026-10-pin-storage-root',
30
+ introducedIn: '1.6.0',
31
+ scope: 'user',
32
+ needsIdleSandbox: false,
33
+ summary: 'Keeps your saved data where it is (the old per-user folder), now that the default folder is a new one.',
34
+ applies(ctx) {
35
+ return needsPin(targetFile(ctx), machineConfigPath(), oldDefaultRoot());
36
+ },
37
+ touches() {
38
+ return [];
39
+ },
40
+ async run(ctx, note) {
41
+ if (!needsPin(targetFile(ctx), machineConfigPath(), oldDefaultRoot()))
42
+ return;
43
+ setValue(targetFile(ctx), ROOT, OLD_DEFAULT_ROOT);
44
+ note('Your saved data stays in ' + OLD_DEFAULT_ROOT + ' (storage.root)');
45
+ },
46
+ };
@@ -4,6 +4,8 @@
4
4
  import _ from 'lodash';
5
5
  import { DEFAULTS } from '../../../core/config/index.js';
6
6
  import { fullSnapshots, readFileObject, removeValues } from './config-file.js';
7
+ /** storage.root is never unpinned: the old default now names the old folder, and following today's would hide it. */
8
+ const KEEP_PINNED = ['storage', 'root'];
7
9
  /** Paths of the leaves of a JSON object (arrays and scalars are leaves). */
8
10
  function leafPaths(value, prefix = []) {
9
11
  if (!_.isPlainObject(value))
@@ -15,7 +17,8 @@ function pinnedLeaves(file) {
15
17
  const snapshots = fullSnapshots(file);
16
18
  return leafPaths(file).filter((path) => {
17
19
  const value = _.get(file, path);
18
- return (_.has(DEFAULTS, path) &&
20
+ return (!_.isEqual(path, KEEP_PINNED) &&
21
+ _.has(DEFAULTS, path) &&
19
22
  !_.isEqual(value, _.get(DEFAULTS, path)) &&
20
23
  snapshots.some((snapshot) => _.has(snapshot.defaults, path) && _.isEqual(_.get(snapshot.defaults, path), value)));
21
24
  });
@@ -1,3 +1,4 @@
1
1
  import { followMachineStorage } from './migrations/follow-machine-storage.js';
2
+ import { pinStorageRoot } from './migrations/pin-storage-root.js';
2
3
  import { unpinConfigDefaults } from './migrations/unpin-config-defaults.js';
3
- export const MIGRATIONS = [unpinConfigDefaults, followMachineStorage];
4
+ export const MIGRATIONS = [unpinConfigDefaults, followMachineStorage, pinStorageRoot];
@@ -1,3 +1,4 @@
1
+ import { SecretStore } from '../secrets/index.js';
1
2
  import { type LoginFiles } from './claude-login.js';
2
3
  export interface Account {
3
4
  readonly name: string;
@@ -13,14 +14,37 @@ export interface AccountRow {
13
14
  readonly isActive: boolean;
14
15
  readonly savedAt: string;
15
16
  }
17
+ /** What lists need: no decryption. */
18
+ export interface AccountMeta {
19
+ readonly name: string;
20
+ readonly savedAt: string;
21
+ readonly email: string | null;
22
+ }
16
23
  export declare function accountsDir(storageRoot: string): string;
17
24
  /** The saved Claude logins under <storage>\accounts, and which one each sandbox data folder uses. */
18
25
  export declare class AccountStore {
19
26
  private readonly storageRoot;
20
- constructor(storageRoot: string);
27
+ private readonly sharePlugins;
28
+ private readonly secrets;
29
+ /** What the last activate() did with the shared plugins (a problem there never stops the switch). */
30
+ pluginNote: string | null;
31
+ /** @param sharePlugins claudeProfile.sharePluginsAcrossAccounts: carry Claude plugins to the account switched to */
32
+ constructor(storageRoot: string, sharePlugins?: boolean, secrets?: SecretStore);
33
+ /** With the setting on: refreshes the shared plugin set from this data set, or fills this data set from it. */
34
+ syncPlugins(dataPath: string): void;
35
+ private shareStep;
21
36
  private file;
37
+ private seal;
22
38
  private store;
39
+ /** The saved account's name, date and e-mail (nothing is decrypted). */
40
+ meta(name: string): AccountMeta | null;
41
+ /**
42
+ * The saved account with its login decrypted. A plain file from an older version is encrypted on the spot (it stays
43
+ * readable when that fails).
44
+ */
23
45
  read(name: string): Account | null;
46
+ /** Encrypts every account still saved as plain text (older versions); returns how many it did. */
47
+ sealLegacy(): number;
24
48
  defaultName(): string | null;
25
49
  /** The account a data folder uses (kept in the index of the storage folder that data folder is in). */
26
50
  activeFor(dataPath: string): string | null;