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
@@ -6,7 +6,8 @@ import _ from 'lodash';
6
6
  import { generateSandbox } from '../../../host/launch/index.js';
7
7
  import { findSecretFiles } from '../../../host/profile/index.js';
8
8
  import { RepoGuardStore } from '../../../host/guard/index.js';
9
- import { assertCanLaunch, isSandboxRunning, spawnDetached, startSandbox, probeHost, hideSandboxWindow, } from '../../../host/sandbox/index.js';
9
+ import { hostPublicIp, prepareVpn, readVpnLine, vpnNotes } from '../../../host/vpn/index.js';
10
+ import { assertCanLaunch, isSandboxRunning, spawnDetached, startSandbox, confirmSandboxStarted, probeHost, hideSandboxWindow, } from '../../../host/sandbox/index.js';
10
11
  import { KnownProjects, ProjectRegistry } from '../../../host/projects/index.js';
11
12
  import { LifeRecord, SessionLedger } from '../../../host/life/index.js';
12
13
  import { isWithin } from '../../../shared/paths/index.js';
@@ -19,6 +20,7 @@ import { c, table } from '../../output/index.js';
19
20
  import { syncAgentCommands } from '../agent-commands.js';
20
21
  import { requireOnPc } from '../context.js';
21
22
  import { joinHere } from '../projects/index.js';
23
+ import { offerSetupWhenOff } from '../setup/index.js';
22
24
  import { useSharedSandbox } from '../shared-use.js';
23
25
  import { beginSharing, currentUserName, currentUserSid, listProcesses, runningOwner, sessionOf, sharingDeps, takeSharingNotice, } from '../../../host/sharing/index.js';
24
26
  import { packageVersion } from '../../../core/package-info.js';
@@ -50,6 +52,11 @@ class Launcher {
50
52
  requireOnPc('launch');
51
53
  if (!generateOnly)
52
54
  this.syncAgentCommands();
55
+ if (!generateOnly) {
56
+ const setUp = await offerSetupWhenOff(this.ctx);
57
+ if (setUp !== undefined)
58
+ return setUp;
59
+ }
53
60
  if (!generateOnly && isSandboxRunning()) {
54
61
  // Another user's sandbox: an agent session in a project it has, when it is shared; nothing else.
55
62
  const ctx = await useSharedSandbox(this.ctx, options.noAgent ? { kind: 'owner-only', what: 'start a sandbox' } : { kind: 'use' });
@@ -120,6 +127,15 @@ class Launcher {
120
127
  await maybeAddRoot(ctx, run);
121
128
  const prepared = generateOnly ? null : await prepare(ctx, run);
122
129
  const bootWants = await bootWantsFor(ctx, run, extras.extraProjects);
130
+ // The VPN (vpn.enabled): its server and profile are fetched and checked here, before anything is written.
131
+ const vpn = generateOnly
132
+ ? null
133
+ : await prepareVpn({
134
+ config,
135
+ storageRoot: ctx.storageRoot,
136
+ sandboxRunning: isSandboxRunning(),
137
+ base: run.workspaceHost,
138
+ });
123
139
  const generated = generateSandbox({
124
140
  packageRoot: ctx.packageRoot,
125
141
  run,
@@ -128,11 +144,14 @@ class Launcher {
128
144
  extraProjects: extras.extraProjects,
129
145
  autoSession: extras.autoSession,
130
146
  bootWants: bootWants.wants,
147
+ ...(vpn ? { vpn } : {}),
131
148
  });
132
149
  const notes = [...run.notes, ...bootWants.notes, ...generated.projects.notes, ...generated.mappingNotes];
133
150
  for (const note of notes)
134
151
  console.log(note);
135
152
  this.warnSecretFiles(run);
153
+ for (const line of vpnNotes(config.vpn))
154
+ console.log(c.yellow(line));
136
155
  table(launchSummary(run, generated, prepared));
137
156
  if (generateOnly)
138
157
  return 0;
@@ -141,6 +160,7 @@ class Launcher {
141
160
  this.record(run, generated);
142
161
  this.share(generated);
143
162
  startSandbox(generated.wsbPath);
163
+ await confirmSandboxStarted(generated.wsbPath);
144
164
  this.startWatcher();
145
165
  if (reopen.length)
146
166
  await this.reopenAfterBoot(generated.launchId, reopen);
@@ -188,8 +208,12 @@ class Launcher {
188
208
  */
189
209
  share(generated) {
190
210
  const deps = sharingDeps(this.ctx.packageRoot);
191
- if (!deps)
211
+ if (!deps) {
212
+ if (this.ctx.loaded.config.sharing.otherUsers)
213
+ console.log(c.yellow('This sandbox is not shared: sharing.otherUsers is on, but no all-users install was found on this PC. ' +
214
+ 'Install for everyone (install.ps1 -Scope AllUsers) to share sandboxes.'));
192
215
  return;
216
+ }
193
217
  // Only the shared tree may be opened to the group: never a storage folder of your own (your profile, say).
194
218
  const inSharedTree = isWithin(deps.usersDir, this.ctx.dataPath);
195
219
  if (this.ctx.loaded.config.sharing.otherUsers && !inSharedTree)
@@ -199,6 +223,9 @@ class Launcher {
199
223
  deps.usersDir +
200
224
  '. Remove storage.root from your config to use the shared folder.'));
201
225
  const shared = this.ctx.loaded.config.sharing.otherUsers && inSharedTree;
226
+ // Only an opened folder has permissions to write; a closed one is checked in a moment and usually needs nothing.
227
+ if (shared)
228
+ console.log('Setting folder permissions...');
202
229
  const rows = listProcesses();
203
230
  beginSharing(deps, {
204
231
  user: currentUserName(),
@@ -230,6 +257,26 @@ class Launcher {
230
257
  if (!watcherRunning(this.ctx.dataPath))
231
258
  spawnDetached(this.ctx.entry, ['_watch', ...(config ? ['--config', path.resolve(config)] : [])]);
232
259
  }
260
+ /** Waits for the boot; the VPN's outcome is said whether the boot worked or not (a failed tunnel can be why). */
261
+ async bootThenReportVpn(run, launchId, timed) {
262
+ try {
263
+ await waitForBoot(this.ctx.dataPath, launchId, timed);
264
+ }
265
+ catch (e) {
266
+ await this.reportVpn(run);
267
+ throw e;
268
+ }
269
+ await this.reportVpn(run);
270
+ }
271
+ /** Says whether the VPN came up (the sandbox reports it; its address is compared with this PC's). */
272
+ async reportVpn(run) {
273
+ if (!run.config.vpn.enabled)
274
+ return;
275
+ const line = readVpnLine(this.ctx.dataPath, await hostPublicIp());
276
+ if (!line)
277
+ return;
278
+ console.log(line.level === 'ok' ? c.green(line.text) : line.level === 'warn' ? c.yellow(line.text) : c.red(line.text));
279
+ }
233
280
  /** Waits for the boot and attaches when asked to; otherwise says where the agent opens. */
234
281
  async afterStart(run, generated) {
235
282
  const { ctx, options, extras } = this;
@@ -241,7 +288,7 @@ class Launcher {
241
288
  if (options.terminal || options.noAgent || extras.waitUntilReady) {
242
289
  if (headsUp)
243
290
  console.log(headsUp);
244
- await waitForBoot(dataPath, generated.launchId, Boolean(headsUp));
291
+ await this.bootThenReportVpn(run, generated.launchId, Boolean(headsUp));
245
292
  if (extras.waitUntilReady)
246
293
  return 0;
247
294
  // Without an agent there is no session to attach to: the sandbox itself is the result.
@@ -253,7 +300,13 @@ class Launcher {
253
300
  }
254
301
  return attachHere(ctx, await waitForStart(ctx, generated.launchId, 'Starting ' + run.harnessName));
255
302
  }
256
- if (headsUp)
303
+ // The VPN's outcome is worth waiting for: a failed tunnel with the kill switch on means no internet.
304
+ if (config.vpn.enabled) {
305
+ if (headsUp)
306
+ console.log(headsUp);
307
+ await this.bootThenReportVpn(run, generated.launchId, Boolean(headsUp));
308
+ }
309
+ else if (headsUp)
257
310
  console.log(headsUp + '\nFollow along: sandbox logs -f');
258
311
  if (hidden)
259
312
  console.log((await hidden)
@@ -77,10 +77,12 @@ async function prepareWorkspaceView(ctx, run) {
77
77
  export async function prepare(ctx, run) {
78
78
  const { options, dataPath, storageRoot } = ctx;
79
79
  const { config } = run;
80
- const accounts = new AccountStore(storageRoot);
80
+ const accounts = new AccountStore(storageRoot, config.claudeProfile.sharePluginsAcrossAccounts);
81
81
  const account = options.account ?? accounts.defaultName();
82
82
  if (account && run.harnessName === 'claude' && account !== accounts.activeFor(dataPath))
83
83
  accounts.activate(dataPath, account);
84
+ else if (run.harnessName === 'claude')
85
+ accounts.syncPlugins(dataPath);
84
86
  const terminal = config.provisioning.terminal.enabled
85
87
  ? await ensureTerminal(dataPath, config.provisioning.refresh, { pwsh: config.toolchains.pwsh.enabled !== false })
86
88
  : { exe: null, message: 'disabled' };
@@ -28,7 +28,7 @@ export async function audit(ctx) {
28
28
  if (!events.length) {
29
29
  console.log(settings.enabled
30
30
  ? 'Nothing in the audit log for the last ' + ctx.options.days + ' day(s).'
31
- : 'The audit log is off. Turn it on in the menu (Settings > Security & privacy > Audit log) or with audit.enabled in your config.');
31
+ : 'The audit log is off. Turn it on in the menu (Settings > Security & privacy > Audit log) or with: sandbox config set audit.enabled true');
32
32
  return 0;
33
33
  }
34
34
  for (const e of events) {
@@ -1,2 +1,2 @@
1
1
  import { type Context } from '../context.js';
2
- export declare function clean(ctx: Context): number;
2
+ export declare function clean(ctx: Context): Promise<number>;
@@ -1,10 +1,24 @@
1
+ // `sandbox clean`: old launch folders, logs and leftovers. Shows what and how much, then asks (--yes skips).
1
2
  import _ from 'lodash';
3
+ import { askRequired } from '../../../core/prompt.js';
2
4
  import { describePrune, pruneStorage } from '../../../host/storage/index.js';
3
5
  import { isSandboxRunning } from '../../../host/sandbox/index.js';
4
6
  import { RunDir } from '../../../shared/channel/index.js';
5
- export function clean(ctx) {
7
+ import { nextStep } from '../../output/index.js';
8
+ export async function clean(ctx) {
6
9
  const running = isSandboxRunning() ? new RunDir(ctx.dataPath).readSandboxStatus()?.launchId : undefined;
7
- console.log('Cleanup: ' +
8
- describePrune(pruneStorage(ctx.storageRoot, { launchDays: ctx.options.days, keep: _.compact([running]) })));
10
+ const options = { launchDays: ctx.options.days, keep: _.compact([running]) };
11
+ const found = pruneStorage(ctx.storageRoot, { ...options, dryRun: true });
12
+ if (found.launches + found.logs + found.leftovers === 0) {
13
+ console.log('Nothing to clean.');
14
+ return 0;
15
+ }
16
+ console.log('Can be removed: ' + describePrune(found).replace(' removed', '') + '.');
17
+ if (!ctx.options.yes && !(await askRequired('Delete them?', 'Add --yes to delete without asking.'))) {
18
+ console.log('Nothing removed.');
19
+ return 1;
20
+ }
21
+ console.log('Removed ' + describePrune(pruneStorage(ctx.storageRoot, options)).replace(' removed', '') + '.');
22
+ nextStep('sandbox data (see what else is saved, and clean it)');
9
23
  return 0;
10
24
  }
@@ -1,2 +1,2 @@
1
1
  import { type Context } from '../context.js';
2
- export declare function config(ctx: Context): number;
2
+ export declare function config(ctx: Context): number | Promise<number>;
@@ -1,21 +1,119 @@
1
+ import { spawn } from 'node:child_process';
1
2
  import fs from 'node:fs';
2
3
  import path from 'node:path';
3
4
  import { pathToFileURL } from 'node:url';
5
+ import _ from 'lodash';
4
6
  import { DEFAULTS, userConfigPath, userDir } from '../../../core/config/index.js';
7
+ import { redactSecrets } from '../../../core/schema/index.js';
5
8
  import { allowedAnswer, allowStorePath, applyOverrides, describeOverrides, isTrusted, LOCAL_CONFIG_NAME, loosenedRequests, normalizeOverrides, OUTSIDE_FOLDER, outsideKey, outsideMappings, readLocalConfig, trustStorePath, } from '../../../core/local-config/index.js';
6
9
  import { guestWorkspaceOf, previewGuest } from '../../../core/run/index.js';
10
+ import { askChoice } from '../../../core/prompt.js';
7
11
  import { dirExists, fail, safeText, writeJson } from '../../../core/util/index.js';
8
- import { c, table } from '../../output/index.js';
9
- function initConfig() {
12
+ import { system32 } from '../../../shared/system.js';
13
+ import { refuseInSelfTest } from '../../../host/test-guard.js';
14
+ import { closest } from '../../validate.js';
15
+ import { c, nextStep, table } from '../../output/index.js';
16
+ import { ConfigFile } from '../../menu/settings/index.js';
17
+ /** The settings file exists already: open it, start over, or leave it (no terminal: say how). */
18
+ async function existingFile(file) {
19
+ return askChoice(file + ' exists already.', [
20
+ { name: 'Open it', value: 'open' },
21
+ { name: 'Overwrite it with the defaults (your changes are lost)', value: 'overwrite' },
22
+ { name: 'Cancel', value: 'cancel' },
23
+ ], 'Open it with: sandbox config path. To start over, delete it first.');
24
+ }
25
+ async function initConfig() {
10
26
  const file = userConfigPath();
11
- if (fs.existsSync(file))
12
- fail(file + ' already exists.');
27
+ if (fs.existsSync(file)) {
28
+ const answer = await existingFile(file);
29
+ if (answer === 'cancel')
30
+ return 0;
31
+ if (answer === 'open') {
32
+ refuseInSelfTest('open files');
33
+ spawn(system32('notepad.exe'), [file], { detached: true, stdio: 'ignore' }).unref();
34
+ console.log('Opened ' + file + '.');
35
+ return 0;
36
+ }
37
+ }
13
38
  fs.mkdirSync(path.join(userDir(), 'hooks', 'pre-agent'), { recursive: true });
14
39
  // The schema sits next to the file (a local path), so editors trust it and show docs and autocomplete.
15
40
  writeJson(file, { $schema: './config.schema.json', ...DEFAULTS });
16
41
  console.log('Created ' + file + '. It lists every setting with its default; delete the ones you do not change.');
17
42
  console.log('Your own hook scripts go in ' + path.join(userDir(), 'hooks') + '\\{pre-provision,pre-agent,post-agent}.');
18
- console.log("For editor help in every project's .sandbox-config.json, run: sandbox config schema");
43
+ nextStep("sandbox config schema (editor help in every project's .sandbox-config.json)");
44
+ return 0;
45
+ }
46
+ /** The setting a key names, as the schema knows it; an unknown key says what is close. */
47
+ function settingNode(key) {
48
+ const root = ConfigFile.user(null).schema;
49
+ const keyPath = key.split('.');
50
+ let node = root;
51
+ for (const part of keyPath)
52
+ node = node?.child(part);
53
+ if (node)
54
+ return { path: keyPath, node };
55
+ const known = root.leaves().map((l) => l.key.join('.'));
56
+ const hint = closest(key, known);
57
+ return fail('There is no setting "' + key + '".' + (hint ? ' Did you mean ' + hint + '?' : ''), 'Settings are named like sandbox.memoryMb. See them all with: sandbox config show');
58
+ }
59
+ /** What was typed for a setting as the value it stands for: true/false/on/off, a number, JSON, or plain text. */
60
+ function settingValue(node, text) {
61
+ const word = text.trim().toLowerCase();
62
+ if (node.type === 'string')
63
+ return text;
64
+ if (node.type === 'boolean' || node.options.some((o) => o.type === 'boolean')) {
65
+ if (['true', 'on', 'yes'].includes(word))
66
+ return true;
67
+ if (['false', 'off', 'no'].includes(word))
68
+ return false;
69
+ }
70
+ try {
71
+ return JSON.parse(text);
72
+ }
73
+ catch {
74
+ // Not JSON: a bare word such as auto or a path is the text itself.
75
+ return node.type === 'array' ? text.split(',').map((t) => t.trim()) : text;
76
+ }
77
+ }
78
+ /** What a setting accepts, in words: its fixed values, a number and its range, true or false. */
79
+ function allowedText(node) {
80
+ const values = node.choiceValues.map((v) => String(v));
81
+ const open = node.openOption ?? (node.type === 'number' ? node : undefined);
82
+ const range = (n) => (n.rangeText() ? ' (' + n.rangeText() + ')' : '');
83
+ const more = open ? (open.type === 'number' ? 'a number' + range(open) : 'text') : '';
84
+ if (node.type === 'boolean')
85
+ return 'Use true or false.';
86
+ const parts = [...values, ...(more ? [more] : [])];
87
+ return parts.length ? 'Allowed: ' + parts.join(', or ') + '.' : 'Check the value.';
88
+ }
89
+ function setSettingCommand(ctx, key, text) {
90
+ if (!key || text === undefined)
91
+ fail('Which setting and value?', 'Usage: sandbox config set <setting> <value>');
92
+ const { path: keyPath, node } = settingNode(key);
93
+ if (node.isSection)
94
+ fail('"' + key + '" is a group of settings.', 'Set one of them, e.g. ' + node.leaves(keyPath)[0]?.key.join('.'));
95
+ const value = settingValue(node, text);
96
+ const problem = node.check(value);
97
+ if (problem !== true) {
98
+ fail('"' + text + '" does not work for ' + key + ': ' + problem, allowedText(node));
99
+ }
100
+ const outcome = ConfigFile.user(ctx.loaded.configPath).set(keyPath, value);
101
+ if (typeof outcome === 'object')
102
+ fail('Could not change ' + key + ': ' + outcome.problems.join('; '), 'Nothing was written.');
103
+ console.log(key +
104
+ ' is ' +
105
+ JSON.stringify(_.get(redactSecrets(_.set({}, keyPath, value)), keyPath)) +
106
+ (outcome === 'unchanged' ? ' (it was already)' : '') +
107
+ '.');
108
+ return 0;
109
+ }
110
+ function getSettingCommand(ctx, key) {
111
+ if (!key)
112
+ fail('Which setting?', 'Usage: sandbox config get <setting> (all of them: sandbox config show)');
113
+ const { path: keyPath } = settingNode(key);
114
+ // Secret values (an agent's API keys) are never printed.
115
+ const value = _.get(redactSecrets(ctx.loaded.config), keyPath);
116
+ console.log(typeof value === 'string' ? value : JSON.stringify(value, null, 2));
19
117
  return 0;
20
118
  }
21
119
  /**
@@ -72,9 +170,9 @@ function previewFolders(ctx, dir, filePath, overrides) {
72
170
  if (!outside.includes(m))
73
171
  return m.guest ? 'inside the project' : 'inside the project (already in the sandbox there)';
74
172
  if (policy === true)
75
- return 'outside the project, allowed (projects.allowOutsideMappings is true)';
173
+ return 'outside the project, allowed (your settings allow it)';
76
174
  if (policy === false)
77
- return c.red('outside the project: refused (projects.allowOutsideMappings is false)');
175
+ return c.red('outside the project: refused (your settings do not allow it)');
78
176
  return allowedAnswer(store, filePath, OUTSIDE_FOLDER, outsideKey(m)) === true
79
177
  ? 'outside the project, allowed (you said yes)'
80
178
  : c.yellow('outside the project: the next interactive launch asks you');
@@ -110,7 +208,7 @@ function previewFolders(ctx, dir, filePath, overrides) {
110
208
  }
111
209
  }
112
210
  export function config(ctx) {
113
- const [action = 'summary'] = ctx.options.positionals;
211
+ const [action = 'summary', key, value] = ctx.options.positionals;
114
212
  switch (action) {
115
213
  case 'summary':
116
214
  table([
@@ -133,13 +231,17 @@ export function config(ctx) {
133
231
  case 'init':
134
232
  return initConfig();
135
233
  case 'show':
136
- console.log(JSON.stringify(ctx.loaded.config, null, 2));
234
+ console.log(JSON.stringify(redactSecrets(ctx.loaded.config), null, 2));
137
235
  return 0;
236
+ case 'get':
237
+ return getSettingCommand(ctx, key);
238
+ case 'set':
239
+ return setSettingCommand(ctx, key, value);
138
240
  case 'schema':
139
241
  return printSchemaSettings();
140
242
  case 'check':
141
243
  return checkLocalConfig(ctx);
142
244
  default:
143
- fail('Unknown config action "' + action + '".', 'Use one of: sandbox config show | path | init | schema | check');
245
+ fail('Unknown config action "' + action + '".', 'Use one of: sandbox config show | get | set | path | init | schema | check');
144
246
  }
145
247
  }
@@ -1,2 +1,2 @@
1
1
  import { type Context } from '../context.js';
2
- export declare function doctor(ctx: Context): number;
2
+ export declare function doctor(ctx: Context): Promise<number>;
@@ -1,6 +1,4 @@
1
- import { table } from '../../output/index.js';
2
- import { HealthService } from '../../services/index.js';
1
+ import { runDoctor } from '../repair/index.js';
3
2
  export function doctor(ctx) {
4
- table(new HealthService(ctx).doctor());
5
- return 0;
3
+ return runDoctor(ctx);
6
4
  }
@@ -1,9 +1,10 @@
1
1
  import { MigrateService } from '../../services/index.js';
2
- import type { Context } from '../context.js';
2
+ import { type Context } from '../context.js';
3
3
  /** `sandbox migrate`: the work, with the service and the output given so a test can drive it. */
4
4
  export declare function runMigrate(service: MigrateService, options: {
5
5
  readonly dryRun: boolean;
6
6
  readonly restore?: string;
7
+ readonly yes?: boolean;
7
8
  }, print: (line: string) => void): Promise<number>;
8
9
  export declare function migrate(ctx: Context): Promise<number>;
9
10
  /**
@@ -1,15 +1,28 @@
1
1
  import { userDir } from '../../../core/config/index.js';
2
2
  import { autoMigrates, continuesAfterFailedMigration, MigrateService, } from '../../services/index.js';
3
+ import { askRequired } from '../../../core/prompt.js';
3
4
  import { c } from '../../output/index.js';
4
5
  import { errMsg, UserError } from '../../../core/util/index.js';
5
6
  import { insideSandbox } from '../../../host/sandbox/index.js';
6
- /** Where this context keeps the saved settings. */
7
+ import { holdsData, oldDefaultRoot } from '../../../host/storage/index.js';
8
+ import { refreshStorage } from '../context.js';
9
+ /**
10
+ * Where this context keeps the saved settings. Backups go where the data is: an upgrade that has not pinned the old
11
+ * folder yet would otherwise fill the new, empty default folder with them.
12
+ */
7
13
  function whereOf(ctx) {
8
- return { storageRoot: ctx.storageRoot, userDir: userDir(), configPath: ctx.loaded.configPath };
14
+ const old = oldDefaultRoot();
15
+ const unpinned = old !== null && !holdsData(ctx.storageRoot) && holdsData(old) && !ctx.loaded.layers?.machine;
16
+ return { storageRoot: unpinned ? old : ctx.storageRoot, userDir: userDir(), configPath: ctx.loaded.configPath };
9
17
  }
10
18
  /** `sandbox migrate`: the work, with the service and the output given so a test can drive it. */
11
19
  export async function runMigrate(service, options, print) {
12
20
  if (options.restore) {
21
+ if (!options.yes &&
22
+ !(await askRequired('Put back the files saved in ' + options.restore + '? Your current settings files are replaced.', 'Add --yes to put them back without asking.'))) {
23
+ print('Nothing was changed.');
24
+ return 1;
25
+ }
13
26
  service.restore(options.restore);
14
27
  print('Put back the files saved in ' + options.restore + '.');
15
28
  return 0;
@@ -31,7 +44,7 @@ export async function runMigrate(service, options, print) {
31
44
  return 0;
32
45
  }
33
46
  export function migrate(ctx) {
34
- return runMigrate(new MigrateService(whereOf(ctx)), { dryRun: ctx.options.dryRun, restore: ctx.options.restore }, (line) => console.log(line));
47
+ return runMigrate(new MigrateService(whereOf(ctx)), { dryRun: ctx.options.dryRun, restore: ctx.options.restore, yes: ctx.options.yes }, (line) => console.log(line));
35
48
  }
36
49
  /**
37
50
  * Before a command: brings the settings up to date when the version changed (one small file is read otherwise).
@@ -44,7 +57,9 @@ export async function migrateBeforeCommand(ctx, command) {
44
57
  try {
45
58
  if (!service.versionChanged())
46
59
  return;
47
- await service.run((line) => console.error(c.dim(line)));
60
+ const result = await service.run((line) => console.error(c.dim(line)));
61
+ if (result.applied.length)
62
+ refreshStorage(ctx);
48
63
  }
49
64
  catch (e) {
50
65
  if (!continuesAfterFailedMigration(command))
@@ -1,5 +1,6 @@
1
1
  import { isSandboxRunning } from '../../../host/sandbox/index.js';
2
2
  import { syncBack } from '../../../sync/index.js';
3
+ import { nextStep } from '../../output/index.js';
3
4
  export function sync(ctx) {
4
5
  const running = isSandboxRunning();
5
6
  const result = syncBack(ctx.dataPath, !running);
@@ -8,5 +9,6 @@ export function sync(ctx) {
8
9
  ' item(s) synced back. claude-mem: ' +
9
10
  result.claudeMem +
10
11
  (running ? ' (claude-mem syncs when the sandbox closes)' : ''));
12
+ nextStep('sandbox claude (carry on where you left off)');
11
13
  return 0;
12
14
  }
@@ -6,6 +6,8 @@ export interface UpdateRequest {
6
6
  readonly rollback: boolean;
7
7
  readonly auto?: 'on' | 'off';
8
8
  readonly unattended: boolean;
9
+ /** Do not ask before going back. */
10
+ readonly yes?: boolean;
9
11
  }
10
12
  /** One sentence for what an update run did, and whether it counts as done. */
11
13
  export declare function describeResult(result: UpdateResult | null, unattended: boolean): {
@@ -1,4 +1,5 @@
1
1
  import { userDir } from '../../../core/config/index.js';
2
+ import { askRequired } from '../../../core/prompt.js';
2
3
  import { errMsg } from '../../../core/util/index.js';
3
4
  import { PACKAGE_ROOT } from '../../../core/package-info.js';
4
5
  import { insideSandbox, spawnDetached } from '../../../host/sandbox/index.js';
@@ -23,8 +24,14 @@ export function describeResult(result, unattended) {
23
24
  result.version +
24
25
  ' failed: ' +
25
26
  result.reason.replace(/\.?\s*$/, '.') +
26
- ' Still on the version you had.' +
27
- (result.blocked ? ' It is not tried again; the next release is.' : ' It is tried again later.'),
27
+ (result.rolledBack
28
+ ? ' The change was undone: you are on the version you had.'
29
+ : ' Going back to your previous version did not fully work: run sandbox update --rollback or the installer.') +
30
+ (result.autoUpdateOff
31
+ ? ' Automatic updates are off; turn them back on in Maintenance > Updates or with sandbox update --auto on.'
32
+ : result.blocked
33
+ ? ' It is not tried again; the next release is.'
34
+ : ' It is tried again later.'),
28
35
  code: 1,
29
36
  };
30
37
  switch (result.reason) {
@@ -52,12 +59,22 @@ export async function runUpdate(service, request, print) {
52
59
  print('This copy was installed with npm. Update it with: ' + NPM_UPDATE_COMMAND);
53
60
  return 0;
54
61
  }
62
+ // The scheduled task (it may outlive a removal that needed admin) and background starts only run when updates are on.
63
+ if (request.unattended && !request.auto && !service.status().autoUpdate)
64
+ return 0;
55
65
  if (request.auto) {
56
66
  service.setAuto(request.auto === 'on');
57
67
  print('Automatic updates are ' + request.auto + '.');
58
68
  return 0;
59
69
  }
60
70
  if (request.rollback) {
71
+ const { version, state } = service.status();
72
+ const target = state.previous ? 'version ' + state.previous : 'the version before the last update';
73
+ if (!request.yes &&
74
+ !(await askRequired('Go back from version ' + version + ' to ' + target + '?', 'Add --yes to go back without asking.'))) {
75
+ print('Nothing was changed.');
76
+ return 1;
77
+ }
61
78
  print('Went back to version ' + service.rollback() + '.');
62
79
  return 0;
63
80
  }
@@ -77,10 +94,16 @@ export async function runUpdate(service, request, print) {
77
94
  }
78
95
  export function update(options) {
79
96
  const service = UpdateService.forPackage(PACKAGE_ROOT, options.feed, { userDir: userDir() });
80
- return runUpdate(service, { check: options.check, rollback: options.rollback, auto: options.autoUpdate, unattended: options.unattended }, (line) => console.log(line));
97
+ return runUpdate(service, {
98
+ check: options.check,
99
+ rollback: options.rollback,
100
+ auto: options.autoUpdate,
101
+ unattended: options.unattended,
102
+ yes: options.yes,
103
+ }, (line) => console.log(line));
81
104
  }
82
105
  /** Commands that never look for updates first: they are how updates run, or they must stay quiet. */
83
- const NO_STARTUP_UPDATE = ['self-test', 'update', '_watch'];
106
+ const NO_STARTUP_UPDATE = ['self-test', 'update', '_watch', '_web'];
84
107
  /**
85
108
  * At the start of a command: tells this user once about an update that ran without them, and (current-user installs
86
109
  * with automatic updates on) starts the daily check in the background. Never stops the command.
@@ -3,13 +3,13 @@
3
3
  // folder a project of the running sandbox when it can without a restart (launch, start, exec, db).
4
4
  import path from 'node:path';
5
5
  import { select } from '@inquirer/prompts';
6
- import { describeMissing, missingTip } from '../../../core/local-config/index.js';
6
+ import { describeMissing, missingTip, protectionsOf } from '../../../core/local-config/index.js';
7
7
  import { askYesNo } from '../../../core/prompt.js';
8
8
  import { fail, isInsideOrEqual } from '../../../core/util/index.js';
9
9
  import { ownerName } from '../../../host/sharing/index.js';
10
10
  import { resolveRun } from '../../../core/run/index.js';
11
11
  import { SandboxChannel } from '../../../host/channel/index.js';
12
- import { BorrowedRegistry, ProjectRegistry, joinRunning, } from '../../../host/projects/index.js';
12
+ import { BorrowedRegistry, ProjectRegistry, joinRunning, runningRegistry, } from '../../../host/projects/index.js';
13
13
  import { c, working } from '../../output/index.js';
14
14
  import { describeSessions } from './describe-sessions.js';
15
15
  import { guestPathFor } from '../context.js';
@@ -21,17 +21,22 @@ export function registryOf(ctx) {
21
21
  export function hereDir(ctx) {
22
22
  return path.resolve(ctx.options.workspace ?? process.cwd());
23
23
  }
24
+ /**
25
+ * Your running sandbox's registry, rebuilt from its launch folder when the file is missing, unreadable or an earlier
26
+ * sandbox's (another copy or version of this tool started it). null only for a sandbox without one at all.
27
+ */
28
+ function ownRegistry(ctx) {
29
+ return runningRegistry(ctx.storageRoot, ctx.dataPath, protectionsOf(ctx.loaded.config));
30
+ }
24
31
  /** Where this folder is in the running sandbox, if it is in it at all (nothing is registered). */
25
32
  export function locateHere(ctx, status) {
26
33
  const dir = hereDir(ctx);
34
+ const own = ctx.borrowed ? null : ownRegistry(ctx);
27
35
  const here = registryOf(ctx).here(dir);
28
36
  if (here)
29
37
  return here;
30
38
  // A sandbox started before projects existed: only its launch folder.
31
- if (!ctx.borrowed &&
32
- !new ProjectRegistry(ctx.storageRoot).read() &&
33
- status &&
34
- isInsideOrEqual(status.workspaceHost, dir))
39
+ if (!ctx.borrowed && !own && status && isInsideOrEqual(status.workspaceHost, dir))
35
40
  return { project: null, guestDir: guestPathFor(dir, status.workspaceHost, status.guestWorkspace) };
36
41
  return { project: null, guestDir: null };
37
42
  }
@@ -57,7 +62,7 @@ export async function joinHere(ctx, status, o = {}) {
57
62
  return { project: known.project, guestDir: known.guestDir, spec: null, harness: null, notes: [] };
58
63
  }
59
64
  // A sandbox started before projects existed: only its launch folder (guestPathFor explains why otherwise).
60
- if (!new ProjectRegistry(storageRoot).read())
65
+ if (!ownRegistry(ctx))
61
66
  return {
62
67
  project: null,
63
68
  guestDir: guestPathFor(dir, status.workspaceHost, status.guestWorkspace),