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,304 @@
1
+ // The web UI's server: login and cookie, the Host / Origin / CSRF checks, static files that cannot leave their folder,
2
+ // sessions without tokens, and the terminal bridge against a fake ptyhost on loopback (the real address check is the
3
+ // backend's, so the fake is only reachable here).
4
+ import http from 'node:http';
5
+ import net from 'node:net';
6
+ import path from 'node:path';
7
+ import WebSocket from 'ws';
8
+ import { webSession } from '../cli/services/index.js';
9
+ import { cookieValue, csrfOk, hostAllowed, originAllowed, sameSecret, StaticFiles, WebAuth, WebServer, WebStore, } from '../host/web/index.js';
10
+ import { encodeFrame, FrameReader, FrameType } from '../shared/pty/index.js';
11
+ import { CSRF_HEADER, CSRF_VALUE, parseClientMessage } from '../shared/web/index.js';
12
+ const TOKEN = 'a'.repeat(64);
13
+ const ATTACH_TOKEN = 'SECRET-ATTACH-TOKEN';
14
+ /** One HTTP request with exactly the headers given (Host included). */
15
+ function send(port, method, url, headers = {}, body) {
16
+ return new Promise((resolve, reject) => {
17
+ const req = http.request({ host: '127.0.0.1', port, method, path: url, headers: { Host: '127.0.0.1:' + port, ...headers } }, (res) => {
18
+ const chunks = [];
19
+ res.on('data', (c) => chunks.push(c));
20
+ res.on('end', () => resolve({ status: res.statusCode ?? 0, headers: res.headers, body: Buffer.concat(chunks).toString('utf8') }));
21
+ });
22
+ req.on('error', reject);
23
+ req.end(body);
24
+ });
25
+ }
26
+ /** A ptyhost stand-in: records what the bridge sends, plays output, ends with EXIT when it gets the letter x. */
27
+ class FakePtyHost {
28
+ hellos = [];
29
+ inputs = [];
30
+ resizes = [];
31
+ pings = 0;
32
+ closed = 0;
33
+ server;
34
+ open = new Set();
35
+ port = 0;
36
+ constructor() {
37
+ this.server = net.createServer((socket) => {
38
+ this.open.add(socket);
39
+ const reader = new FrameReader();
40
+ socket.on('error', () => undefined);
41
+ socket.on('close', () => this.closed++);
42
+ socket.on('data', (chunk) => {
43
+ for (const frame of reader.push(chunk)) {
44
+ if (frame.type === FrameType.Hello) {
45
+ this.hellos.push(frame.payload);
46
+ // Output with a clipboard write (OSC 52) inside: the bridge must drop that part.
47
+ socket.write(encodeFrame(FrameType.Output, Buffer.from('hello \x1b]52;c;ZXZpbA==\x07world')));
48
+ }
49
+ else if (frame.type === FrameType.Input) {
50
+ this.inputs.push(frame.payload);
51
+ if (frame.payload.toString() === 'x') {
52
+ const code = Buffer.alloc(4);
53
+ code.writeInt32LE(3, 0);
54
+ socket.write(encodeFrame(FrameType.Exit, code));
55
+ }
56
+ }
57
+ else if (frame.type === FrameType.Resize)
58
+ this.resizes.push(frame.payload);
59
+ else if (frame.type === FrameType.Ping)
60
+ this.pings++;
61
+ }
62
+ });
63
+ });
64
+ }
65
+ start() {
66
+ return new Promise((resolve) => this.server.listen(0, '127.0.0.1', () => {
67
+ this.port = this.server.address().port;
68
+ resolve();
69
+ }));
70
+ }
71
+ stop() {
72
+ this.server.close();
73
+ for (const socket of this.open)
74
+ socket.destroy();
75
+ }
76
+ }
77
+ /** What a browser tab sees on the terminal socket. */
78
+ class Tab {
79
+ texts = [];
80
+ binary = [];
81
+ closeCode = null;
82
+ opened = false;
83
+ failed = false;
84
+ socket;
85
+ constructor(url, headers) {
86
+ this.socket = new WebSocket(url, { headers });
87
+ this.socket.on('open', () => (this.opened = true));
88
+ this.socket.on('unexpected-response', () => (this.failed = true));
89
+ this.socket.on('error', () => (this.failed = true));
90
+ this.socket.on('message', (data, isBinary) => {
91
+ if (isBinary)
92
+ this.binary.push(data);
93
+ else
94
+ this.texts.push(data.toString('utf8'));
95
+ });
96
+ this.socket.on('close', (code) => (this.closeCode = code));
97
+ }
98
+ }
99
+ const session = (id, attach) => ({
100
+ id,
101
+ launchId: 'launch1',
102
+ harness: 'claude',
103
+ cwd: 'C:\\Projects\\app',
104
+ phase: 'running',
105
+ message: 'running\x1b[31m',
106
+ updatedAt: '2026-01-01T00:00:00.000Z',
107
+ startedAt: '2026-01-01T00:00:00.000Z',
108
+ ...(attach ? { attach } : {}),
109
+ });
110
+ export const webSuite = {
111
+ name: 'web',
112
+ title: 'web UI server: login, Host / Origin / CSRF checks, static files, sessions, terminal bridge',
113
+ run: async (t) => {
114
+ t.section('the token and the cookie');
115
+ t.check(sameSecret(TOKEN, TOKEN) && !sameSecret(TOKEN, 'b'.repeat(64)) && !sameSecret(TOKEN, ''), 'secrets compare');
116
+ let clock = 1000;
117
+ const auth = new WebAuth(TOKEN, () => clock);
118
+ const bad = auth.login('nope');
119
+ t.check(!bad.ok && bad.status === 401, 'a wrong token is refused');
120
+ t.check(!auth.login(42).ok && !auth.login({}).ok, 'a token that is not text is refused');
121
+ const good = auth.login(TOKEN);
122
+ t.check(good.ok, 'the right token logs in');
123
+ const cookie = good.ok ? good.cookie : '';
124
+ t.check(auth.signedIn('x=1; sbxweb=' + cookie), 'the cookie signs in');
125
+ t.check(!auth.signedIn('sbxweb=' + cookie.replace(/.$/, '0')) && !auth.signedIn(undefined), 'another cookie does not');
126
+ clock += 13 * 60 * 60 * 1000;
127
+ t.check(!auth.signedIn('sbxweb=' + cookie), 'a cookie unused for 12 hours expires');
128
+ const limited = new WebAuth(TOKEN, () => clock);
129
+ for (let i = 0; i < 8; i++)
130
+ limited.login('wrong');
131
+ const locked = limited.login(TOKEN);
132
+ t.check(!locked.ok && locked.status === 429, 'too many wrong tokens lock logging in, even for the right one');
133
+ clock += 61 * 1000;
134
+ t.check(limited.login(TOKEN).ok, 'the lock ends after a minute');
135
+ t.equal(cookieValue('a=1; sbxweb=abc; b=2'), 'abc', 'the cookie is found among others');
136
+ t.section('Host, Origin and CSRF checks');
137
+ t.check(hostAllowed('127.0.0.1:47800', 47800) && hostAllowed('localhost:47800', 47800), 'loopback names are allowed');
138
+ t.check(!hostAllowed('evil.example:47800', 47800) &&
139
+ !hostAllowed('127.0.0.1:1', 47800) &&
140
+ !hostAllowed(undefined, 47800) &&
141
+ !hostAllowed('127.0.0.1.evil.example:47800', 47800), 'any other Host is refused (DNS rebinding)');
142
+ t.check(originAllowed('http://127.0.0.1:47800', '127.0.0.1:47800', 47800) &&
143
+ !originAllowed('http://evil.example', '127.0.0.1:47800', 47800) &&
144
+ !originAllowed(undefined, '127.0.0.1:47800', 47800) &&
145
+ !originAllowed('http://localhost:47800', '127.0.0.1:47800', 47800), 'the Origin must be the page itself');
146
+ t.check(csrfOk({ [CSRF_HEADER]: CSRF_VALUE }) && !csrfOk({}) && !csrfOk({ [CSRF_HEADER]: '0' }), 'the CSRF header');
147
+ t.section('browser messages');
148
+ t.check(parseClientMessage('{"t":"resize","cols":80,"rows":24}')?.t === 'resize', 'a resize is read');
149
+ t.check([
150
+ 'nope',
151
+ '{"t":"resize","cols":0,"rows":24}',
152
+ '{"t":"resize","cols":80.5,"rows":24}',
153
+ '{"t":"resize","cols":80,"rows":99999}',
154
+ '{"t":"input","data":"x"}',
155
+ 'null',
156
+ ].every((m) => parseClientMessage(m) === null), 'garbage, odd numbers and unknown kinds are refused');
157
+ t.section('static files');
158
+ const root = t.dir('web-root');
159
+ t.write(path.join(root, 'index.html'), '<!doctype html><title>x</title>');
160
+ t.write(path.join(root, 'main-ABCD1234.js'), 'console.log(1)');
161
+ t.write(path.join(root, 'notes.exe'), 'MZ');
162
+ t.write(path.join(t.temp, 'secret.txt'), 'TOP SECRET');
163
+ const files = new StaticFiles(root);
164
+ t.check(files.find('/')?.type.startsWith('text/html') === true, '/ is the app');
165
+ t.check(files.find('/main-ABCD1234.js')?.immutable === true, 'a hashed file may be cached for good');
166
+ t.check(files.find('/some/page')?.file === path.join(root, 'index.html'), 'a page of the app is the app');
167
+ t.check([
168
+ '/../secret.txt',
169
+ '/%2e%2e/secret.txt',
170
+ '/a/../../secret.txt',
171
+ '/..%2fsecret.txt',
172
+ '/..\\secret.txt',
173
+ '/x%00.js',
174
+ ].every((p) => files.find(p) === null), 'paths that leave the folder find nothing');
175
+ t.check(files.find('/notes.exe') === null && files.find('/missing.js') === null, 'unknown types and missing files find nothing');
176
+ t.section('sessions never carry a token');
177
+ const attach = { addresses: ['127.0.0.1'], port: 1234, token: ATTACH_TOKEN };
178
+ const shown = JSON.stringify(webSession(session('s1', attach), 2));
179
+ t.excludes(shown, ATTACH_TOKEN, 'the attach token is not in what the browser gets');
180
+ t.excludes(shown, '1234', 'nor the port');
181
+ t.excludes(shown, '\x1b', 'text from the sandbox is made safe');
182
+ t.section('the server');
183
+ const fake = new FakePtyHost();
184
+ await fake.start();
185
+ const endpoint = { addresses: ['127.0.0.1'], port: fake.port, token: ATTACH_TOKEN };
186
+ const listed = webSession(session('abc123'), 0);
187
+ const created = [];
188
+ const backend = {
189
+ info: () => Promise.resolve({
190
+ sandbox: 'running',
191
+ owner: null,
192
+ canUse: true,
193
+ harnesses: ['claude'],
194
+ defaultHarness: 'claude',
195
+ projects: [],
196
+ }),
197
+ sessions: () => Promise.resolve([listed]),
198
+ endpoint: (id) => (id === 'abc123' ? endpoint : null),
199
+ create: (request) => {
200
+ created.push(request);
201
+ return Promise.resolve(listed);
202
+ },
203
+ };
204
+ let stopped = false;
205
+ const server = new WebServer({ port: 0, token: TOKEN, backend, staticRoot: root, onStop: () => (stopped = true) });
206
+ await server.start();
207
+ const { port } = server;
208
+ const origin = 'http://127.0.0.1:' + port;
209
+ const json = { 'Content-Type': 'application/json' };
210
+ const ajax = { ...json, Origin: origin, [CSRF_HEADER]: CSRF_VALUE };
211
+ try {
212
+ const page = await send(port, 'GET', '/');
213
+ t.check(page.status === 200 && page.headers['content-type']?.startsWith('text/html') === true, 'the app is served');
214
+ t.includes(String(page.headers['content-security-policy']), "script-src 'self'", 'the page has a CSP without inline scripts');
215
+ t.check(!String(page.headers['content-security-policy']).includes("script-src 'self' 'unsafe-inline'") &&
216
+ page.headers['x-content-type-options'] === 'nosniff' &&
217
+ page.headers['x-frame-options'] === 'DENY', 'and the other protective headers');
218
+ t.equal((await send(port, 'GET', '/', { Host: 'evil.example:' + port })).status, 421, 'a foreign Host name is refused');
219
+ t.equal((await send(port, 'GET', '/..%2f..%2fsecret.txt')).status, 404, 'a path out of the folder is a 404');
220
+ t.equal((await send(port, 'GET', '/%2e%2e/secret.txt')).status, 404, 'an encoded path out of the folder is a 404');
221
+ t.equal((await send(port, 'POST', '/', {}, 'x')).status, 405, 'the files take only GET');
222
+ t.equal((await send(port, 'GET', '/api/sessions')).status, 401, 'the API needs the login');
223
+ t.equal((await send(port, 'GET', '/api/health')).status, 200, 'health needs none');
224
+ const noCsrf = await send(port, 'POST', '/api/login', { ...json, Origin: origin }, JSON.stringify({ token: TOKEN }));
225
+ t.equal(noCsrf.status, 403, 'login without the CSRF header is refused');
226
+ const foreign = await send(port, 'POST', '/api/login', { ...ajax, Origin: 'http://evil.example' }, JSON.stringify({ token: TOKEN }));
227
+ t.equal(foreign.status, 403, 'login from another origin is refused');
228
+ const noOrigin = await send(port, 'POST', '/api/login', { ...json, [CSRF_HEADER]: CSRF_VALUE }, JSON.stringify({ token: TOKEN }));
229
+ t.equal(noOrigin.status, 403, 'login without an Origin is refused');
230
+ t.equal((await send(port, 'POST', '/api/login', ajax, JSON.stringify({ token: 'wrong' }))).status, 401, 'a wrong token');
231
+ t.equal((await send(port, 'POST', '/api/login', { ...ajax, 'Content-Type': 'text/plain' }, '{}')).status, 415, 'JSON only');
232
+ const login = await send(port, 'POST', '/api/login', ajax, JSON.stringify({ token: TOKEN }));
233
+ const setCookie = String(login.headers['set-cookie']?.[0] ?? '');
234
+ t.check(login.status === 200 && /HttpOnly/.test(setCookie) && /SameSite=Strict/.test(setCookie), 'the right token gets a safe cookie');
235
+ const Cookie = setCookie.split(';')[0];
236
+ const list = await send(port, 'GET', '/api/sessions', { Cookie });
237
+ t.check(list.status === 200 && JSON.parse(list.body).length === 1, 'a signed-in browser lists sessions');
238
+ t.excludes(list.body, ATTACH_TOKEN, 'the list has no attach token');
239
+ t.equal((await send(port, 'GET', '/api/info', { Cookie })).status, 200, 'and reads the info');
240
+ t.equal((await send(port, 'POST', '/api/sessions', { ...json, Cookie }, '{}')).status, 403, 'a new session needs CSRF and Origin');
241
+ t.equal((await send(port, 'POST', '/api/sessions', { ...ajax, Cookie }, '{"harness":"../x","project":"p"}')).status, 400, 'odd names are refused');
242
+ const made = await send(port, 'POST', '/api/sessions', { ...ajax, Cookie }, '{"harness":"claude","project":"app"}');
243
+ t.check(made.status === 201 && created.length === 1, 'a new session is requested');
244
+ t.section('the terminal bridge');
245
+ const url = 'ws://127.0.0.1:' + port + '/api/sessions/abc123/terminal?cols=100&rows=40';
246
+ const attempts = [
247
+ new Tab(url, { Cookie }),
248
+ new Tab(url, { Origin: 'http://evil.example', Cookie }),
249
+ new Tab(url, { Origin: origin }),
250
+ new Tab('ws://127.0.0.1:' + port + '/api/sessions/nosuch/terminal', { Origin: origin, Cookie }),
251
+ ];
252
+ await t.waitFor(() => attempts.every((a) => a.failed), 5000);
253
+ t.check(attempts.every((a) => !a.opened && a.failed), 'no Origin, a foreign Origin, no cookie and an unknown session are all refused');
254
+ t.check(fake.hellos.length === 0, 'and the session was never contacted');
255
+ const tab = new Tab(url, { Origin: origin, Cookie });
256
+ await t.waitFor(() => tab.binary.length > 0, 5000);
257
+ t.check(tab.texts.includes('{"t":"ready"}'), 'the browser is told when it is connected');
258
+ const hello = fake.hellos[0];
259
+ t.check(hello !== undefined &&
260
+ hello.readInt16LE(0) === 100 &&
261
+ hello.readInt16LE(2) === 40 &&
262
+ hello.subarray(5).toString() === ATTACH_TOKEN, 'HELLO carries the attach token and the size');
263
+ const output = Buffer.concat(tab.binary).toString();
264
+ t.check(output.includes('hello ') && output.includes('world'), 'output is relayed as binary');
265
+ t.excludes(output, ']52', 'and a clipboard write (OSC 52) in it is dropped');
266
+ tab.socket.send(Buffer.from('abc'));
267
+ tab.socket.send(JSON.stringify({ t: 'resize', cols: 90, rows: 30 }));
268
+ await t.waitFor(() => fake.inputs.length > 0 && fake.resizes.length > 0, 5000);
269
+ t.check(fake.inputs[0]?.toString() === 'abc', 'keystrokes are forwarded as INPUT');
270
+ t.check(fake.resizes[0]?.readInt16LE(0) === 90 && fake.resizes[0]?.readInt16LE(2) === 30, 'a resize is forwarded');
271
+ tab.socket.send(Buffer.from('x'));
272
+ await t.waitFor(() => tab.closeCode !== null, 5000);
273
+ t.check(tab.texts.includes('{"t":"exit","code":3}'), 'EXIT reaches the browser with its code');
274
+ t.equal(tab.closeCode, 1000, 'and the socket closes cleanly');
275
+ const rude = new Tab(url, { Origin: origin, Cookie });
276
+ await t.waitFor(() => rude.binary.length > 0, 5000);
277
+ rude.socket.send('this is not json');
278
+ await t.waitFor(() => rude.closeCode !== null, 5000);
279
+ t.equal(rude.closeCode, 1008, 'a browser that sends garbage is closed');
280
+ const leaving = new Tab(url, { Origin: origin, Cookie });
281
+ await t.waitFor(() => leaving.binary.length > 0, 5000);
282
+ leaving.socket.close();
283
+ await t.waitFor(() => fake.closed >= 3, 5000);
284
+ t.check(fake.closed >= 3, 'closing the browser side closes the connection to the session');
285
+ t.section('stopping');
286
+ t.equal((await send(port, 'POST', '/api/stop', { ...json, Cookie }, '{}')).status, 403, 'stop needs CSRF and Origin too');
287
+ t.equal((await send(port, 'POST', '/api/stop', { ...ajax, Cookie }, '{}')).status, 200, 'a signed-in browser can stop it');
288
+ await t.waitFor(() => stopped, 3000);
289
+ t.check(stopped, 'the server is told to stop');
290
+ }
291
+ finally {
292
+ await server.stop();
293
+ fake.stop();
294
+ }
295
+ t.section('the state file');
296
+ const store = new WebStore(t.dir('web-state'), () => undefined);
297
+ store.write({ pid: 1234, port: 47800, token: TOKEN, startedAt: '2026-01-01T00:00:00.000Z' });
298
+ t.equal(store.read()?.port, 47800, 'pid, port and token are kept');
299
+ t.write(path.join(store.dir, 'server.json'), '{"pid":1,"port":1,"token":"short","startedAt":"x"}');
300
+ t.check(store.read() === null, 'a state file that does not fit the schema is ignored');
301
+ store.clear();
302
+ t.check(store.read() === null, 'and it can be removed');
303
+ },
304
+ };
@@ -235,6 +235,17 @@ export const APPS = [
235
235
  exe: 'lazygit.exe',
236
236
  cli: true,
237
237
  },
238
+ {
239
+ id: 'jj',
240
+ name: 'Jujutsu (jj)',
241
+ category: 'git',
242
+ about: 'Git-compatible version control; conflicts do not block a rebase. Pilot, untested with worktrees.',
243
+ size: '10 MB',
244
+ source: gh('jj-vcs/jj', '^jj-v[\\d.]+-x86_64-pc-windows-msvc\\.zip$'),
245
+ exe: 'jj.exe',
246
+ cli: true,
247
+ fullGitOnly: true,
248
+ },
238
249
  // ---- utilities
239
250
  {
240
251
  id: '7zip',
@@ -764,6 +775,26 @@ export const APPS = [
764
775
  exe: 'gitleaks.exe',
765
776
  cli: true,
766
777
  },
778
+ {
779
+ id: 'ast-grep',
780
+ name: 'ast-grep',
781
+ category: 'cli',
782
+ about: 'Search and rewrite code by its syntax tree (ast-grep, sg); prints only the matches.',
783
+ size: '15 MB',
784
+ source: gh('ast-grep/ast-grep', '^app-x86_64-pc-windows-msvc\\.zip$'),
785
+ exe: 'ast-grep.exe',
786
+ cli: true,
787
+ },
788
+ {
789
+ id: 'rust-analyzer',
790
+ name: 'rust-analyzer',
791
+ category: 'cli',
792
+ about: 'Rust language server: type errors and go-to-definition without cargo (for editors and the Claude LSP plugin).',
793
+ size: '18 MB',
794
+ source: gh('rust-lang/rust-analyzer', '^rust-analyzer-x86_64-pc-windows-msvc\\.zip$'),
795
+ exe: 'rust-analyzer.exe',
796
+ cli: true,
797
+ },
767
798
  ];
768
799
  export function appById(id) {
769
800
  return APPS.find((a) => a.id === id);
@@ -1,2 +1,3 @@
1
1
  export * from './schema.js';
2
2
  export * from './run-dir.js';
3
+ export * from './secrets.js';
@@ -1,2 +1,3 @@
1
1
  export * from './schema.js';
2
2
  export * from './run-dir.js';
3
+ export * from './secrets.js';
@@ -1,4 +1,4 @@
1
- import type { GuestRequest, GuestResponse, ProjectStatus, SandboxStatus, SessionStatus } from './schema.js';
1
+ import type { GuestRequest, GuestResponse, ProjectStatus, SandboxStatus, SessionStatus, VpnStatus } from './schema.js';
2
2
  /** Ids become file names, so only a strict alphabet is allowed (no separators, no dots). */
3
3
  export declare function safeId(id: string): string;
4
4
  export declare function now(): string;
@@ -6,18 +6,23 @@ export declare function now(): string;
6
6
  export declare class RunDir {
7
7
  readonly root: string;
8
8
  readonly sandboxFile: string;
9
+ readonly vpnFile: string;
9
10
  readonly requestsDir: string;
10
11
  readonly responsesDir: string;
11
12
  readonly sessionsDir: string;
12
13
  readonly projectsDir: string;
13
14
  /** audit.processes: programs the sandbox started, in small .jsonl files the host takes in and deletes. */
14
15
  readonly auditDir: string;
16
+ /** One-shot secrets the host hands over (see secrets.ts). Next to run, not in it: shared-use users can change run. */
17
+ readonly secretsDir: string;
15
18
  constructor(dataDir: string);
16
19
  requestFile(id: string): string;
17
20
  responseFile(id: string): string;
18
21
  sessionFile(id: string): string;
19
22
  projectFile(name: string): string;
23
+ secretFile(id: string): string;
20
24
  readSandboxStatus(): SandboxStatus | null;
25
+ readVpnStatus(): VpnStatus | null;
21
26
  readSession(id: string): SessionStatus | null;
22
27
  readResponse(id: string): GuestResponse | null;
23
28
  listSessions(): SessionStatus[];
@@ -3,7 +3,7 @@
3
3
  // written atomically and parsed with the channel schemas on read. Anything that does not parse is ignored.
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
- import { GuestRequestSchema, GuestResponseSchema, IdSchema, ProjectStatusSchema, SandboxStatusSchema, SessionStatusSchema, } from './schema.js';
6
+ import { GuestRequestSchema, GuestResponseSchema, IdSchema, ProjectStatusSchema, SandboxStatusSchema, SessionStatusSchema, VpnStatusSchema, } from './schema.js';
7
7
  import { readValid } from '../io/index.js';
8
8
  /** Ids become file names, so only a strict alphabet is allowed (no separators, no dots). */
9
9
  export function safeId(id) {
@@ -33,20 +33,25 @@ const scan = (schema, dir) => {
33
33
  export class RunDir {
34
34
  root;
35
35
  sandboxFile;
36
+ vpnFile;
36
37
  requestsDir;
37
38
  responsesDir;
38
39
  sessionsDir;
39
40
  projectsDir;
40
41
  /** audit.processes: programs the sandbox started, in small .jsonl files the host takes in and deletes. */
41
42
  auditDir;
43
+ /** One-shot secrets the host hands over (see secrets.ts). Next to run, not in it: shared-use users can change run. */
44
+ secretsDir;
42
45
  constructor(dataDir) {
43
46
  this.root = path.join(dataDir, 'run');
44
47
  this.sandboxFile = path.join(this.root, 'sandbox.json');
48
+ this.vpnFile = path.join(this.root, 'vpn.json');
45
49
  this.requestsDir = path.join(this.root, 'requests');
46
50
  this.responsesDir = path.join(this.root, 'responses');
47
51
  this.sessionsDir = path.join(this.root, 'sessions');
48
52
  this.projectsDir = path.join(this.root, 'projects');
49
53
  this.auditDir = path.join(this.root, 'audit');
54
+ this.secretsDir = path.join(dataDir, 'secrets');
50
55
  }
51
56
  requestFile(id) {
52
57
  return path.join(this.requestsDir, safeId(id) + '.json');
@@ -60,9 +65,15 @@ export class RunDir {
60
65
  projectFile(name) {
61
66
  return path.join(this.projectsDir, safeId(name) + '.json');
62
67
  }
68
+ secretFile(id) {
69
+ return path.join(this.secretsDir, safeId(id) + '.json');
70
+ }
63
71
  readSandboxStatus() {
64
72
  return readValid(SandboxStatusSchema, this.sandboxFile);
65
73
  }
74
+ readVpnStatus() {
75
+ return readValid(VpnStatusSchema, this.vpnFile);
76
+ }
66
77
  readSession(id) {
67
78
  return readValid(SessionStatusSchema, this.sessionFile(id));
68
79
  }
@@ -193,6 +193,7 @@ export declare const ProjectSpecSchema: z.ZodObject<{
193
193
  loginFiles: z.ZodArray<z.ZodString>;
194
194
  hostDir: z.ZodOptional<z.ZodString>;
195
195
  }, z.core.$strict>>;
196
+ localState: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
196
197
  resumeArguments: z.ZodOptional<z.ZodArray<z.ZodString>>;
197
198
  resumeOneArguments: z.ZodOptional<z.ZodArray<z.ZodString>>;
198
199
  }, z.core.$strict>>;
@@ -349,6 +350,7 @@ export declare const GuestRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
349
350
  loginFiles: z.ZodArray<z.ZodString>;
350
351
  hostDir: z.ZodOptional<z.ZodString>;
351
352
  }, z.core.$strict>>;
353
+ localState: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
352
354
  resumeArguments: z.ZodOptional<z.ZodArray<z.ZodString>>;
353
355
  resumeOneArguments: z.ZodOptional<z.ZodArray<z.ZodString>>;
354
356
  }, z.core.$strict>>;
@@ -369,6 +371,7 @@ export declare const GuestRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
369
371
  loginFiles: z.ZodArray<z.ZodString>;
370
372
  hostDir: z.ZodOptional<z.ZodString>;
371
373
  }, z.core.$strict>>;
374
+ localState: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
372
375
  resumeArguments: z.ZodOptional<z.ZodArray<z.ZodString>>;
373
376
  resumeOneArguments: z.ZodOptional<z.ZodArray<z.ZodString>>;
374
377
  }, z.core.$strict>>;
@@ -520,6 +523,7 @@ export declare const GuestRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
520
523
  loginFiles: z.ZodArray<z.ZodString>;
521
524
  hostDir: z.ZodOptional<z.ZodString>;
522
525
  }, z.core.$strict>>;
526
+ localState: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
523
527
  resumeArguments: z.ZodOptional<z.ZodArray<z.ZodString>>;
524
528
  resumeOneArguments: z.ZodOptional<z.ZodArray<z.ZodString>>;
525
529
  }, z.core.$strict>>;
@@ -597,6 +601,21 @@ export declare const ProjectStatusSchema: z.ZodObject<{
597
601
  }, z.core.$strip>>;
598
602
  summary: z.ZodArray<z.ZodString>;
599
603
  }, z.core.$strip>;
604
+ /** The guest writes run/vpn.json: whether the VPN tunnel is up (validated and capped on the host). */
605
+ export declare const VpnStatusSchema: z.ZodObject<{
606
+ state: z.ZodEnum<{
607
+ failed: "failed";
608
+ starting: "starting";
609
+ off: "off";
610
+ up: "up";
611
+ }>;
612
+ message: z.ZodString;
613
+ killSwitch: z.ZodBoolean;
614
+ server: z.ZodOptional<z.ZodString>;
615
+ publicIp: z.ZodOptional<z.ZodString>;
616
+ updatedAt: z.ZodString;
617
+ }, z.core.$strip>;
618
+ export type VpnStatus = z.output<typeof VpnStatusSchema>;
600
619
  export type ProjectSpec = z.output<typeof ProjectSpecSchema>;
601
620
  export type ProjectStatus = z.output<typeof ProjectStatusSchema>;
602
621
  export type AttachEndpoint = z.output<typeof AttachEndpointSchema>;
@@ -131,3 +131,14 @@ export const ProjectStatusSchema = z.object({
131
131
  databases: z.array(z.object({ database: Text(32), port: z.number().int() })).max(16),
132
132
  summary: z.array(Text(512)).max(64),
133
133
  });
134
+ /** The guest writes run/vpn.json: whether the VPN tunnel is up (validated and capped on the host). */
135
+ export const VpnStatusSchema = z.object({
136
+ state: z.enum(['off', 'starting', 'up', 'failed']),
137
+ message: Text(2000),
138
+ killSwitch: z.boolean(),
139
+ /** The server's name, e.g. uk2201.nordvpn.com. */
140
+ server: Text(128).optional(),
141
+ /** What the sandbox's traffic looks like from outside, once the tunnel is up. */
142
+ publicIp: Text(64).optional(),
143
+ updatedAt: TimeSchema,
144
+ });
@@ -0,0 +1,28 @@
1
+ import { z } from 'zod';
2
+ export declare const SecretsFileSchema: z.ZodObject<{
3
+ harnessEnvironment: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
4
+ harnessSpecEnvironment: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
5
+ projectHarnessEnvironment: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>>>;
6
+ claudeCredentials: z.ZodOptional<z.ZodString>;
7
+ vpn: z.ZodOptional<z.ZodObject<{
8
+ username: z.ZodString;
9
+ password: z.ZodString;
10
+ }, z.core.$strict>>;
11
+ }, z.core.$strict>;
12
+ export type SecretsFile = z.output<typeof SecretsFileSchema>;
13
+ interface WithHarnesses {
14
+ readonly harnessSpec?: {
15
+ environment: Record<string, string>;
16
+ };
17
+ readonly project?: {
18
+ harnesses: Record<string, {
19
+ environment: Record<string, string>;
20
+ }>;
21
+ };
22
+ }
23
+ /**
24
+ * A session or project request with the agent environment values from its secrets file put back (the reverse of
25
+ * host/secrets withoutSecrets). In memory only: the sandbox never writes them out again.
26
+ */
27
+ export declare function mergeRequestSecrets<R extends WithHarnesses>(request: R, secrets: SecretsFile): R;
28
+ export {};
@@ -0,0 +1,44 @@
1
+ // What the host hands the sandbox that must not sit in session.json or in anything in the launch folder (which is
2
+ // mapped read-only into the sandbox and kept for days): one file per launch or request in <data>\secrets\<id>.json,
3
+ // which the sandbox reads and deletes at once. The folder is next to run\ (not in it): other users of a shared
4
+ // sandbox may change run\, never this folder. The agent in the sandbox can still read what it was given (its own
5
+ // environment, its login): that is inherent. Every part is optional.
6
+ import { z } from 'zod';
7
+ const Environment = z.record(z.string().max(256), z.string().max(65536));
8
+ export const SecretsFileSchema = z.strictObject({
9
+ /** The agent the sandbox booted with: its harness environment. */
10
+ harnessEnvironment: Environment.optional(),
11
+ /** harnessSpec of a session request (an agent the sandbox did not boot with). */
12
+ harnessSpecEnvironment: Environment.optional(),
13
+ /** project.harnesses of a request, by agent name. */
14
+ projectHarnessEnvironment: z.record(z.string().max(64), Environment).optional(),
15
+ /** The login of this PC's Claude (claudeProfile.copyCredentials): the text of .credentials.json. */
16
+ claudeCredentials: z
17
+ .string()
18
+ .max(1024 * 1024)
19
+ .optional(),
20
+ /** VPN service credentials (vpn.provider's login), only for the launch's own sandbox. */
21
+ vpn: z.strictObject({ username: z.string().min(1).max(256), password: z.string().min(1).max(1024) }).optional(),
22
+ });
23
+ /** The environment a harness gets: its own, then the values that travelled in the secrets file. */
24
+ const withEnvironment = (harness, environment) => (environment ? { ...harness, environment: { ...harness.environment, ...environment } } : harness);
25
+ /**
26
+ * A session or project request with the agent environment values from its secrets file put back (the reverse of
27
+ * host/secrets withoutSecrets). In memory only: the sandbox never writes them out again.
28
+ */
29
+ export function mergeRequestSecrets(request, secrets) {
30
+ let merged = request;
31
+ if (request.harnessSpec && secrets.harnessSpecEnvironment)
32
+ merged = { ...merged, harnessSpec: withEnvironment(request.harnessSpec, secrets.harnessSpecEnvironment) };
33
+ if (request.project && secrets.projectHarnessEnvironment) {
34
+ const given = secrets.projectHarnessEnvironment;
35
+ merged = {
36
+ ...merged,
37
+ project: {
38
+ ...request.project,
39
+ harnesses: Object.fromEntries(Object.entries(request.project.harnesses).map(([name, h]) => [name, withEnvironment(h, given[name])])),
40
+ },
41
+ };
42
+ }
43
+ return merged;
44
+ }
@@ -0,0 +1,13 @@
1
+ /** The release page lists no checksums: these were computed from the files on swupdate.openvpn.org (https). */
2
+ export declare const OPENVPN_MSI: {
3
+ readonly version: "2.7.8";
4
+ readonly x64: {
5
+ readonly file: "OpenVPN-2.7.8-I001-amd64.msi";
6
+ readonly sha256: "1ea61a3056905b90607d4b0f084c04e5cf3043e07cf192d9cd44b0474f29cc49";
7
+ };
8
+ readonly arm64: {
9
+ readonly file: "OpenVPN-2.7.8-I001-arm64.msi";
10
+ readonly sha256: "6fc0e8899cc5275ee297fb837bc84aa2bc49f8ef96d7ad93ee3fee1e7163a25c";
11
+ };
12
+ readonly base: "https://swupdate.openvpn.org/community/releases/";
13
+ };
@@ -0,0 +1,14 @@
1
+ // The OpenVPN download entry (shared: the sandbox installs it, doctor checks that its host is reachable).
2
+ /** The release page lists no checksums: these were computed from the files on swupdate.openvpn.org (https). */
3
+ export const OPENVPN_MSI = {
4
+ version: '2.7.8',
5
+ x64: {
6
+ file: 'OpenVPN-2.7.8-I001-amd64.msi',
7
+ sha256: '1ea61a3056905b90607d4b0f084c04e5cf3043e07cf192d9cd44b0474f29cc49',
8
+ },
9
+ arm64: {
10
+ file: 'OpenVPN-2.7.8-I001-arm64.msi',
11
+ sha256: '6fc0e8899cc5275ee297fb837bc84aa2bc49f8ef96d7ad93ee3fee1e7163a25c',
12
+ },
13
+ base: 'https://swupdate.openvpn.org/community/releases/',
14
+ };
@@ -0,0 +1,18 @@
1
+ export declare const VPN_RULE_PREFIX = "AgentSandbox-VPN-";
2
+ export interface KillSwitchServer {
3
+ readonly ip: string;
4
+ readonly port: number;
5
+ readonly proto: 'udp' | 'tcp';
6
+ }
7
+ /** The rule names phase 1 and 2 may create (the servers get a number each). */
8
+ export declare function ruleNames(serverCount: number): string[];
9
+ /** Servers worth a rule: valid IPv4 addresses and ports, each endpoint once. */
10
+ export declare function cleanServers(servers: readonly KillSwitchServer[]): KillSwitchServer[];
11
+ /** Phase 1: block everything that goes out, then allow only what the tunnel and the attach channel need. */
12
+ export declare function killSwitchCommands(servers: readonly KillSwitchServer[], ptyHostExe: string | null): string[][];
13
+ /** Phase 2: everything that leaves from the tunnel's own address goes (it is inside the tunnel). */
14
+ export declare function tunnelCommands(tunnelIp: string): string[][];
15
+ /** Takes the kill switch away again: its rules go and outbound traffic is allowed by default as before. */
16
+ export declare function removeKillSwitchCommands(): string[][];
17
+ /** The tunnel's own IPv4 address from OpenVPN's log (its "net_addr_v4_add" or "ifconfig" line), or null. */
18
+ export declare function tunnelAddressFromLog(log: string): string | null;