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
@@ -1,31 +1,26 @@
1
- // How the settings are grouped on screen: categories in the sidebar, readable names for sections and toolchains. Every
2
- // top-level setting of both files must belong to a category (a self-test checks it), so nothing is ever hidden.
1
+ // How the settings are grouped on screen: categories in the navigation, readable names for sections and toolchains.
2
+ // Every top-level setting of both files must belong to a category or be covered by another section (a self-test checks
3
+ // it), so nothing is ever hidden.
3
4
  import { Text } from '../../tui/index.js';
4
5
  export const CATEGORIES = [
5
6
  {
6
7
  id: 'general',
7
8
  label: 'General',
8
- description: 'Which agent starts, where the project appears in the sandbox, and extra folders from this PC.',
9
+ description: 'Which agent starts, where the project appears in the sandbox, extra folders from this PC, and where the sandbox keeps its data.',
9
10
  keys: ['defaultHarness', 'harness', 'guestWorkspace', 'readOnly', 'mappings', 'scripts'],
10
11
  },
11
12
  {
12
13
  id: 'machine',
13
14
  label: 'Sandbox machine',
14
- description: 'Memory, CPU, window, GPU, clipboard, audio and network of the sandbox itself.',
15
- keys: ['sandbox'],
15
+ description: 'Memory, CPU, window, GPU, clipboard, audio and networking of the sandbox itself, and its desktop look (theme, accent colour, taskbar, wallpaper).',
16
+ keys: ['sandbox', 'personalization'],
16
17
  },
17
18
  {
18
19
  id: 'languages',
19
20
  label: 'Languages & tools',
20
- description: 'Toolchains installed in the sandbox. "auto" follows your project\'s files and what this PC has.',
21
+ description: 'Toolchains installed in the sandbox. "auto" follows the files of your project and what this PC has.',
21
22
  keys: ['toolchains'],
22
23
  },
23
- {
24
- id: 'apps',
25
- label: 'Apps',
26
- description: 'Desktop apps installed in the sandbox: editors, database clients, API clients, game engines ... (also: Apps catalogue on the home screen).',
27
- keys: ['apps'],
28
- },
29
24
  {
30
25
  id: 'databases',
31
26
  label: 'Databases',
@@ -34,21 +29,15 @@ export const CATEGORIES = [
34
29
  },
35
30
  {
36
31
  id: 'agents',
37
- label: 'Agents & attaching',
38
- description: 'The agents you can start, attaching your terminal to them, and the (Beta) agent commands.',
39
- keys: ['harnesses', 'attach', 'agentCommands'],
40
- },
41
- {
42
- id: 'claude',
43
- label: 'Claude profile',
44
- description: 'Which of your Claude settings, history and memory are copied in and synced back.',
45
- keys: ['claudeProfile'],
32
+ label: 'Agents & sessions',
33
+ description: 'The agents you can start (also: Agents section), attaching your terminal to them, the (Beta) web UI and agent commands, which of your Claude settings are copied in, and reopening sessions.',
34
+ keys: ['harnesses', 'attach', 'web', 'agentCommands', 'claudeProfile', 'resume'],
46
35
  },
47
36
  {
48
37
  id: 'network',
49
38
  label: 'Network',
50
- description: 'What the sandbox can reach: your LAN, only some hosts, blocked hosts, DNS, a proxy, and ports forwarded from this PC to servers inside. (No network at all: Sandbox machine > Networking.)',
51
- keys: ['network'],
39
+ description: 'What the sandbox can reach: your LAN, only some hosts, blocked hosts, DNS, a proxy, ports forwarded from this PC to servers inside, and a VPN (with a login set here). (No network at all: Sandbox machine > Networking.)',
40
+ keys: ['network', 'vpn'],
52
41
  },
53
42
  {
54
43
  id: 'security',
@@ -56,42 +45,28 @@ export const CATEGORIES = [
56
45
  description: 'Git-ignored files kept out, secrets kept out of the sandbox, download checks, what is kept on this PC, and sharing your sandbox with other Windows users.',
57
46
  keys: ['workspaceFilter', 'audit', 'secrets', 'downloads', 'storage', 'sharing'],
58
47
  },
59
- {
60
- id: 'setup',
61
- label: 'Setup & hooks',
62
- description: 'How tools get into the sandbox (copied from this PC or downloaded), Git, the terminal, and hook scripts.',
63
- keys: ['provisioning', 'hooks'],
64
- },
65
48
  {
66
49
  id: 'projects',
67
- label: 'Projects',
68
- description: 'Several projects in one sandbox: project roots mapped at every start, adding folders while it runs.',
69
- keys: ['projects'],
70
- },
71
- {
72
- id: 'resume',
73
- label: 'Reopening sessions',
74
- description: 'Open the sessions of the last sandbox again when a new one starts, or after a crash.',
75
- keys: ['resume'],
76
- },
77
- {
78
- id: 'desktop',
79
- label: 'Desktop look',
80
- description: 'Copy your theme, accent colour, taskbar and wallpaper into the sandbox.',
81
- keys: ['personalization'],
50
+ label: 'Projects & setup',
51
+ description: 'Several projects in one sandbox (roots mapped at every start, adding folders while it runs), how tools get into the sandbox (copied from this PC or downloaded), Git, the terminal, and hook scripts.',
52
+ keys: ['projects', 'provisioning', 'hooks'],
82
53
  },
83
54
  ];
55
+ /** Settings not listed here because their own section covers them (Apps: the catalogue and your own programs). */
56
+ export const SETTINGS_ELSEWHERE = ['apps'];
84
57
  /** Readable names for sections and toolchains (anything missing gets a name made from its key). */
85
58
  const NAMES = {
86
59
  sandbox: 'Sandbox machine',
87
60
  attach: 'Attaching your terminal',
88
61
  agentCommands: 'Agent commands (Beta)',
62
+ web: 'Web UI (Beta)',
89
63
  provisioning: 'Provisioning',
90
64
  downloads: 'Downloads',
91
65
  secrets: 'Secrets',
92
66
  claudeProfile: 'Claude profile',
93
67
  storage: 'Storage on this PC',
94
68
  network: 'Network',
69
+ vpn: 'VPN',
95
70
  hooks: 'Hooks',
96
71
  personalization: 'Desktop look',
97
72
  projects: 'Projects',
@@ -10,14 +10,20 @@ export declare abstract class CollectionEditor<C> {
10
10
  protected readonly path: string[];
11
11
  protected readonly node: SchemaNode;
12
12
  protected readonly editor: ValueEditor;
13
+ /** The setting's key (a list's own, without positions): what the entries are is read from it. */
14
+ protected readonly key: string[];
13
15
  protected value: C;
14
16
  private original;
15
- constructor(app: App, path: string[], node: SchemaNode, editor: ValueEditor);
17
+ constructor(app: App, path: string[], node: SchemaNode, editor: ValueEditor,
18
+ /** The setting's key (a list's own, without positions): what the entries are is read from it. */
19
+ key?: string[]);
16
20
  protected abstract entries(): ListItem<string>[];
17
21
  protected abstract add(): Promise<void>;
18
22
  protected abstract open(id: string): Promise<void>;
19
23
  protected abstract remove(id: string): void;
20
24
  protected move?(id: string, by: number): string | undefined;
21
25
  protected noun: string;
26
+ /** What leaving with changes does, shown under the list (settings: they wait for Ctrl+S). */
27
+ protected keepNote: string;
22
28
  run(start: C): Promise<Edited>;
23
29
  }
@@ -10,15 +10,21 @@ export class CollectionEditor {
10
10
  path;
11
11
  node;
12
12
  editor;
13
+ key;
13
14
  value;
14
15
  original;
15
- constructor(app, path, node, editor) {
16
+ constructor(app, path, node, editor,
17
+ /** The setting's key (a list's own, without positions): what the entries are is read from it. */
18
+ key = []) {
16
19
  this.app = app;
17
20
  this.path = path;
18
21
  this.node = node;
19
22
  this.editor = editor;
23
+ this.key = key;
20
24
  }
21
25
  noun = 'entry';
26
+ /** What leaving with changes does, shown under the list (settings: they wait for Ctrl+S). */
27
+ keepNote = 'Esc keeps it for saving with Ctrl+S';
22
28
  async run(start) {
23
29
  this.original = structuredClone(start);
24
30
  this.value = structuredClone(start);
@@ -29,9 +35,7 @@ export class CollectionEditor {
29
35
  const picked = await this.app.list({
30
36
  path: this.path,
31
37
  intro: this.node.description,
32
- status: dirty
33
- ? Theme.changed(Theme.glyph.changed + ' changed: Esc keeps it for saving with Ctrl+S')
34
- : undefined,
38
+ status: dirty ? Theme.changed(Theme.glyph.changed + ' changed: ' + this.keepNote) : undefined,
35
39
  items: [
36
40
  { value: ADD, label: '+ Add ' + this.noun, section: 'Actions' },
37
41
  ...entries.map((e) => ({
@@ -41,11 +45,11 @@ export class CollectionEditor {
41
45
  ],
42
46
  startAt: focus,
43
47
  actions: [
44
- { key: 'd', label: 'delete' },
45
- ...(this.move
48
+ { key: 'd', label: 'remove', applies: (id) => id !== ADD },
49
+ ...(this.move && entries.length > 1
46
50
  ? [
47
- { key: 'u', label: 'move up' },
48
- { key: 'j', label: 'move down' },
51
+ { key: 'u', label: 'move up', applies: (id) => id !== ADD },
52
+ { key: 'j', label: 'move down', applies: (id) => id !== ADD },
49
53
  ]
50
54
  : []),
51
55
  ],
@@ -4,7 +4,11 @@ import { formatValue } from '../format.js';
4
4
  export class MapEditor extends CollectionEditor {
5
5
  noun = 'variable';
6
6
  entries() {
7
- return Object.entries(this.value).map(([k, v]) => ({ value: k, label: k, hint: formatValue(v) }));
7
+ return Object.entries(this.value).map(([k, v]) => ({
8
+ value: k,
9
+ label: k,
10
+ hint: this.node.element?.secret ? '***' : formatValue(v),
11
+ }));
8
12
  }
9
13
  async add() {
10
14
  const name = await this.app.text({
@@ -8,12 +8,12 @@ export class ObjectListEditor extends CollectionEditor {
8
8
  return this.value.map((v, i) => ({ value: String(i), label: summary(v) }));
9
9
  }
10
10
  async add() {
11
- const edited = await new ObjectEditor(this.app, [...this.path, 'New'], this.node.element, this.editor).run(this.node.element.initial(), true);
11
+ const edited = await new ObjectEditor(this.app, [...this.path, 'New'], this.node.element, this.editor, this.key).run(this.node.element.initial(), true);
12
12
  if (edited)
13
13
  this.value.push(edited.value);
14
14
  }
15
15
  async open(id) {
16
- const edited = await new ObjectEditor(this.app, [...this.path, summary(this.value[Number(id)])], this.node.element, this.editor).run(this.value[Number(id)]);
16
+ const edited = await new ObjectEditor(this.app, [...this.path, summary(this.value[Number(id)])], this.node.element, this.editor, this.key).run(this.value[Number(id)]);
17
17
  if (edited)
18
18
  this.value[Number(id)] = edited.value;
19
19
  }
@@ -20,12 +20,12 @@ export class ObjectMapEditor extends CollectionEditor {
20
20
  });
21
21
  if (name === undefined)
22
22
  return;
23
- const edited = await new ObjectEditor(this.app, [...this.path, name], this.node.element, this.editor).run(this.node.element.initial(), true);
23
+ const edited = await new ObjectEditor(this.app, [...this.path, name], this.node.element, this.editor, this.key).run(this.node.element.initial(), true);
24
24
  if (edited)
25
25
  this.value[name] = edited.value;
26
26
  }
27
27
  async open(id) {
28
- const edited = await new ObjectEditor(this.app, [...this.path, id], this.node.element, this.editor).run(this.value[id]);
28
+ const edited = await new ObjectEditor(this.app, [...this.path, id], this.node.element, this.editor, this.key).run(this.value[id]);
29
29
  if (edited)
30
30
  this.value[id] = edited.value;
31
31
  }
@@ -1,8 +1,18 @@
1
- import { type ListItem } from '../../../tui/index.js';
1
+ import type { App, ListItem } from '../../../tui/index.js';
2
2
  import { CollectionEditor } from './collection-editor.js';
3
+ import { type PathKind } from '../path-kind.js';
4
+ import type { SchemaNode } from '../schema-node.js';
5
+ import type { ValueEditor } from '../value-editor.js';
3
6
  /** A list of strings (project roots, scripts, extra packages ...). */
4
7
  export declare class StringListEditor extends CollectionEditor<string[]> {
8
+ /** The items are folders or files on this PC: they are picked in the path picker. */
9
+ private readonly pathKind?;
5
10
  protected noun: string;
11
+ constructor(app: App, path: string[], node: SchemaNode, editor: ValueEditor, key: string[],
12
+ /** The items are folders or files on this PC: they are picked in the path picker. */
13
+ pathKind?: PathKind | undefined,
14
+ /** What leaving with changes does, when it is not "wait for Ctrl+S". */
15
+ keepNote?: string);
6
16
  protected entries(): ListItem<string>[];
7
17
  private ask;
8
18
  protected add(): Promise<void>;
@@ -1,13 +1,36 @@
1
1
  import { Theme } from '../../../tui/index.js';
2
2
  import { CollectionEditor } from './collection-editor.js';
3
+ import { askPath } from '../path-kind.js';
3
4
  /** A list of strings (project roots, scripts, extra packages ...). */
4
5
  export class StringListEditor extends CollectionEditor {
6
+ pathKind;
5
7
  noun = 'item';
8
+ constructor(app, path, node, editor, key,
9
+ /** The items are folders or files on this PC: they are picked in the path picker. */
10
+ pathKind,
11
+ /** What leaving with changes does, when it is not "wait for Ctrl+S". */
12
+ keepNote) {
13
+ super(app, path, node, editor, key);
14
+ this.pathKind = pathKind;
15
+ if (pathKind)
16
+ this.noun = pathKind === 'file' ? 'file' : 'folder';
17
+ if (keepNote)
18
+ this.keepNote = keepNote;
19
+ }
6
20
  entries() {
7
21
  return this.value.map((v, i) => ({ value: String(i), label: v || Theme.muted('(empty)') }));
8
22
  }
9
23
  async ask(initial) {
10
24
  const element = this.node.element;
25
+ if (this.pathKind)
26
+ return askPath(this.app, {
27
+ path: this.path,
28
+ title: this.pathKind === 'file' ? 'Choose a file' : 'Choose a folder',
29
+ about: this.node.description ? [this.node.description] : [],
30
+ kind: this.pathKind,
31
+ initial,
32
+ validate: (v) => element.check(v),
33
+ });
11
34
  return this.app.text({
12
35
  path: this.path,
13
36
  label: 'Value',
@@ -1,4 +1,4 @@
1
- export { CATEGORIES, nameOf } from './categories.js';
1
+ export { CATEGORIES, categoryOf, nameOf, SETTINGS_ELSEWHERE } from './categories.js';
2
2
  export { ConfigFile, editConfigText, fileValue, type SettingChange, type SettingsTarget } from './config-file.js';
3
3
  export { Draft, type DraftResult } from './draft.js';
4
4
  export { SchemaNode } from './schema-node.js';
@@ -1,4 +1,4 @@
1
- export { CATEGORIES, nameOf } from './categories.js';
1
+ export { CATEGORIES, categoryOf, nameOf, SETTINGS_ELSEWHERE } from './categories.js';
2
2
  export { ConfigFile, editConfigText, fileValue } from './config-file.js';
3
3
  export { Draft } from './draft.js';
4
4
  export { SchemaNode } from './schema-node.js';
@@ -7,6 +7,10 @@ export declare class ObjectEditor {
7
7
  private readonly path;
8
8
  private readonly node;
9
9
  private readonly editor;
10
- constructor(app: App, path: string[], node: SchemaNode, editor: ValueEditor);
10
+ /** The setting's key (the object's own, without list positions), so a field knows what it is. */
11
+ private readonly key;
12
+ constructor(app: App, path: string[], node: SchemaNode, editor: ValueEditor,
13
+ /** The setting's key (the object's own, without list positions), so a field knows what it is. */
14
+ key?: string[]);
11
15
  run(start: Record<string, unknown>, isNew?: boolean): Promise<Edited>;
12
16
  }
@@ -7,11 +7,15 @@ export class ObjectEditor {
7
7
  path;
8
8
  node;
9
9
  editor;
10
- constructor(app, path, node, editor) {
10
+ key;
11
+ constructor(app, path, node, editor,
12
+ /** The setting's key (the object's own, without list positions), so a field knows what it is. */
13
+ key = []) {
11
14
  this.app = app;
12
15
  this.path = path;
13
16
  this.node = node;
14
17
  this.editor = editor;
18
+ this.key = key;
15
19
  }
16
20
  async run(start, isNew = false) {
17
21
  const value = structuredClone(start);
@@ -30,13 +34,20 @@ export class ObjectEditor {
30
34
  ...fields.map(([name, field]) => ({
31
35
  value: name,
32
36
  label: Text.humanize(name),
33
- hint: formatValue(value[name]),
37
+ hint: field.secret && value[name] ? '***' : formatValue(value[name]),
34
38
  description: field.description,
35
39
  section: 'Fields',
40
+ flip: field.editor === 'boolean',
36
41
  })),
37
42
  ],
38
43
  startAt: focus,
39
- actions: [{ key: 'r', label: 'clear' }],
44
+ actions: [
45
+ {
46
+ key: 'r',
47
+ label: 'clear',
48
+ applies: (name) => Boolean(this.node.child(name)?.optional) && value[name] !== undefined,
49
+ },
50
+ ],
40
51
  });
41
52
  notice = undefined;
42
53
  if (picked.kind === 'back') {
@@ -58,7 +69,7 @@ export class ObjectEditor {
58
69
  delete value[name];
59
70
  continue;
60
71
  }
61
- const edited = await this.editor.edit([...this.path, Text.humanize(name)], [name], field, value[name]);
72
+ const edited = await this.editor.edit([...this.path, Text.humanize(name)], [...this.key, name], field, value[name]);
62
73
  if (edited)
63
74
  value[name] = edited.value;
64
75
  }
@@ -0,0 +1,20 @@
1
+ import type { App } from '../../tui/index.js';
2
+ import type { SchemaNode } from './schema-node.js';
3
+ /** A folder or a file. */
4
+ export type PathKind = 'dir' | 'file';
5
+ /** The kind of path a setting holds (its key without list positions), or undefined when it is not a path. */
6
+ export declare function pathKindOf(key: readonly string[]): PathKind | undefined;
7
+ /** Words that are fine instead of a path ("auto"), read from the setting's description. */
8
+ export declare function pathLiterals(node: SchemaNode): string[];
9
+ export interface AskPathOptions {
10
+ readonly path: string[];
11
+ readonly title: string;
12
+ readonly about?: string[];
13
+ readonly kind: PathKind;
14
+ readonly initial?: string;
15
+ readonly literals?: readonly string[];
16
+ /** true = fine, a string = what is wrong (shown under the path). */
17
+ readonly validate?: (path: string) => true | string;
18
+ }
19
+ /** Picks a folder or file in the path picker; typing a path (or an allowed word) still works. */
20
+ export declare function askPath(app: App, o: AskPathOptions): Promise<string | undefined>;
@@ -0,0 +1,44 @@
1
+ /** Top-level settings whose list entries are folders or files, and the single settings that are one. */
2
+ const KINDS = {
3
+ 'storage.root': 'dir',
4
+ 'projects.roots': 'dir',
5
+ 'projects.exclude': 'dir',
6
+ 'projects.declinedRoots': 'dir',
7
+ 'claudeProfile.hostDir': 'dir',
8
+ 'claudeProfile.globalStateFile': 'file',
9
+ scripts: 'file',
10
+ };
11
+ /** The kind of path a setting holds (its key without list positions), or undefined when it is not a path. */
12
+ export function pathKindOf(key) {
13
+ const id = key.join('.');
14
+ const last = key[key.length - 1] ?? '';
15
+ if (KINDS[id])
16
+ return KINDS[id];
17
+ if (key[0] === 'mappings' && last === 'host')
18
+ return 'dir';
19
+ if (last === 'hostDir')
20
+ return 'dir';
21
+ if (/(Dir|Folder)$/.test(last))
22
+ return 'dir';
23
+ if (/File$/.test(last))
24
+ return 'file';
25
+ return undefined;
26
+ }
27
+ /** Words that are fine instead of a path ("auto"), read from the setting's description. */
28
+ export function pathLiterals(node) {
29
+ return /"auto"/.test(node.description ?? '') ? ['auto'] : [];
30
+ }
31
+ /** Picks a folder or file in the path picker; typing a path (or an allowed word) still works. */
32
+ export function askPath(app, o) {
33
+ return app.pickPath({
34
+ path: o.path,
35
+ title: o.title,
36
+ about: o.about,
37
+ initial: o.initial,
38
+ files: o.kind === 'file',
39
+ use: o.kind === 'file' ? 'use this file' : 'use this folder',
40
+ literals: o.literals,
41
+ relative: true,
42
+ validate: o.validate,
43
+ });
44
+ }
@@ -20,6 +20,7 @@ type ZodLike = z.ZodType & {
20
20
  description?: string;
21
21
  minValue?: number | null;
22
22
  maxValue?: number | null;
23
+ isInt?: boolean;
23
24
  };
24
25
  /** The editor a setting gets. */
25
26
  export type EditorKind = 'boolean' | 'choice' | 'union' | 'number' | 'string' | 'string-list' | 'choice-list' | 'object-list' | 'string-map' | 'object-map' | 'object' | 'json';
@@ -35,6 +36,14 @@ export declare class SchemaNode {
35
36
  get description(): string | undefined;
36
37
  get min(): number | undefined;
37
38
  get max(): number | undefined;
39
+ /** A number that must be whole. */
40
+ get integer(): boolean;
41
+ /** A secret (an API key): shown as ***, typed with each character hidden. */
42
+ get secret(): boolean;
43
+ /** May the setting be left out (optional or with a default)? */
44
+ get optional(): boolean;
45
+ /** The numbers allowed, in words: "1 to 65535", "at least 2048", "more than 0", "" without limits. */
46
+ rangeText(): string;
38
47
  /** The fields of an object, in schema order. */
39
48
  children(): [string, SchemaNode][];
40
49
  child(name: string): SchemaNode | undefined;
@@ -35,6 +35,32 @@ export class SchemaNode {
35
35
  get max() {
36
36
  return this.inner.maxValue ?? undefined;
37
37
  }
38
+ /** A number that must be whole. */
39
+ get integer() {
40
+ return Boolean(this.inner.isInt);
41
+ }
42
+ /** A secret (an API key): shown as ***, typed with each character hidden. */
43
+ get secret() {
44
+ const meta = (z) => z.meta?.();
45
+ return meta(this.schema)?.secret === true || meta(this.inner)?.secret === true;
46
+ }
47
+ /** May the setting be left out (optional or with a default)? */
48
+ get optional() {
49
+ return ['optional', 'default'].includes(this.schema._zod.def.type);
50
+ }
51
+ /** The numbers allowed, in words: "1 to 65535", "at least 2048", "more than 0", "" without limits. */
52
+ rangeText() {
53
+ const checks = (this.inner._zod.def.checks ?? []).map((c) => c._zod.def);
54
+ const lower = checks.find((c) => c.check === 'greater_than');
55
+ const upper = checks.find((c) => c.check === 'less_than');
56
+ const lo = lower?.value;
57
+ const hi = upper?.value;
58
+ if (lo !== undefined && hi !== undefined && lower?.inclusive !== false && upper?.inclusive !== false)
59
+ return lo + ' to ' + hi;
60
+ const low = lo === undefined ? '' : (lower?.inclusive === false ? 'more than ' : 'at least ') + lo;
61
+ const high = hi === undefined ? '' : (upper?.inclusive === false ? 'less than ' : 'at most ') + hi;
62
+ return [low, high].filter(Boolean).join(' and ');
63
+ }
38
64
  /** The fields of an object, in schema order. */
39
65
  children() {
40
66
  return Object.entries(this.inner.shape ?? {})
@@ -1,5 +1,7 @@
1
1
  import { type App } from '../../tui/index.js';
2
2
  import { type SchemaNode } from './schema-node.js';
3
+ /** What is wrong with the text typed for a number setting (true = fine), in words that name the allowed range. */
4
+ export declare function numberProblem(node: SchemaNode, text: string): true | string;
3
5
  /** The new value, or undefined when you backed out without changing anything. */
4
6
  export type Edited = {
5
7
  value: unknown;
@@ -13,6 +15,9 @@ export declare class ValueEditor {
13
15
  private readonly hints;
14
16
  constructor(app: App, hints?: EditorHints);
15
17
  edit(path: string[], key: string[], node: SchemaNode, current: unknown): Promise<Edited>;
18
+ private number;
19
+ /** A folder or file on this PC: the path picker (typing a path still works). */
20
+ private folderOrFile;
16
21
  private pick;
17
22
  private suggest;
18
23
  private json;
@@ -9,6 +9,20 @@ import { MapEditor } from './collections/map-editor.js';
9
9
  import { ObjectListEditor } from './collections/object-list-editor.js';
10
10
  import { ObjectMapEditor } from './collections/object-map-editor.js';
11
11
  import { ObjectEditor } from './object-editor.js';
12
+ import { askPath, pathKindOf, pathLiterals } from './path-kind.js';
13
+ /** What is wrong with the text typed for a number setting (true = fine), in words that name the allowed range. */
14
+ export function numberProblem(node, text) {
15
+ const n = Number(text);
16
+ if (text.trim() === '' || !Number.isFinite(n))
17
+ return 'Enter a number.';
18
+ if (node.integer && !Number.isInteger(n))
19
+ return 'Enter a whole number.';
20
+ const verdict = node.check(n);
21
+ if (verdict === true)
22
+ return true;
23
+ const range = node.rangeText();
24
+ return range ? 'Use ' + range + '.' : verdict;
25
+ }
12
26
  export class ValueEditor {
13
27
  app;
14
28
  hints;
@@ -47,26 +61,19 @@ export class ValueEditor {
47
61
  ? this.edit(path, key, open, node.choiceValues.includes(current) ? undefined : current)
48
62
  : { value: picked };
49
63
  }
50
- case 'number': {
51
- const range = node.min !== undefined || node.max !== undefined
52
- ? ' (' + (node.min ?? '…') + ' to ' + (node.max ?? '…') + ')'
53
- : '';
54
- const typed = await this.app.text({
55
- path,
56
- label: Text.humanize(key[key.length - 1]) + range,
57
- about,
58
- initial: current === undefined ? '' : String(current),
59
- validate: (v) => (v.trim() === '' || !Number.isFinite(Number(v)) ? 'Enter a number.' : node.check(Number(v))),
60
- });
61
- return typed === undefined ? undefined : { value: Number(typed) };
62
- }
64
+ case 'number':
65
+ return this.number(path, key, node, current);
63
66
  case 'string': {
67
+ const kind = pathKindOf(key);
68
+ if (kind)
69
+ return this.folderOrFile(path, key, node, current, kind);
64
70
  const typed = await this.app.text({
65
71
  path,
66
72
  label: Text.humanize(key[key.length - 1]),
67
73
  about,
68
74
  initial: typeof current === 'string' ? current : '',
69
75
  allowEmpty: node.check('') === true,
76
+ masked: node.secret,
70
77
  validate: (v) => node.check(v),
71
78
  });
72
79
  return typed === undefined ? undefined : { value: typed };
@@ -82,19 +89,45 @@ export class ValueEditor {
82
89
  return picked.kind === 'multi' ? { value: picked.values } : undefined;
83
90
  }
84
91
  case 'string-list':
85
- return new StringListEditor(this.app, path, node, this).run(Array.isArray(current) ? current : []);
92
+ return new StringListEditor(this.app, path, node, this, key, pathKindOf(key)).run(Array.isArray(current) ? current : []);
86
93
  case 'string-map':
87
- return new MapEditor(this.app, path, node, this).run(_.isPlainObject(current) ? current : {});
94
+ return new MapEditor(this.app, path, node, this, key).run(_.isPlainObject(current) ? current : {});
88
95
  case 'object-list':
89
- return new ObjectListEditor(this.app, path, node, this).run(Array.isArray(current) ? current : []);
96
+ return new ObjectListEditor(this.app, path, node, this, key).run(Array.isArray(current) ? current : []);
90
97
  case 'object-map':
91
- return new ObjectMapEditor(this.app, path, node, this).run(_.isPlainObject(current) ? current : {});
98
+ return new ObjectMapEditor(this.app, path, node, this, key).run(_.isPlainObject(current) ? current : {});
92
99
  case 'object':
93
- return new ObjectEditor(this.app, path, node, this).run(_.isPlainObject(current) ? current : node.initial());
100
+ return new ObjectEditor(this.app, path, node, this, key).run(_.isPlainObject(current) ? current : node.initial());
94
101
  default:
95
102
  return this.json(path, key, node, current);
96
103
  }
97
104
  }
105
+ async number(path, key, node, current) {
106
+ const range = node.rangeText();
107
+ const typed = await this.app.text({
108
+ path,
109
+ label: Text.humanize(key[key.length - 1]),
110
+ about: node.description ? [node.description] : [],
111
+ hint: 'A ' + (node.integer ? 'whole number' : 'number') + (range ? ': ' + range : '') + '.',
112
+ accept: node.integer ? (node.min !== undefined && node.min >= 0 ? /[0-9]/ : /[-0-9]/) : /[-0-9.]/,
113
+ initial: current === undefined ? '' : String(current),
114
+ validate: (v) => numberProblem(node, v),
115
+ });
116
+ return typed === undefined ? undefined : { value: Number(typed) };
117
+ }
118
+ /** A folder or file on this PC: the path picker (typing a path still works). */
119
+ async folderOrFile(path, key, node, current, kind) {
120
+ const picked = await askPath(this.app, {
121
+ path,
122
+ title: 'Choose ' + (kind === 'file' ? 'the file' : 'the folder') + ': ' + Text.humanize(key[key.length - 1]),
123
+ about: node.description ? [node.description] : [],
124
+ kind,
125
+ initial: typeof current === 'string' && current !== 'auto' ? current : '',
126
+ literals: pathLiterals(node),
127
+ validate: (v) => node.check(v),
128
+ });
129
+ return picked === undefined ? undefined : { value: picked };
130
+ }
98
131
  async pick(path, node, current, values) {
99
132
  const picked = await this.app.choose(path, node.description ?? '', values.map((v) => ({ value: v, label: choiceLabel(v) })), current);
100
133
  return picked === undefined ? undefined : { value: picked };