windows-development-sandbox 1.17.3 → 1.18.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 (315) hide show
  1. package/README.md +1 -1
  2. package/config.example.json +97 -4
  3. package/dist/app/features/accounts/accounts.def.d.ts +116 -2
  4. package/dist/app/features/accounts/accounts.def.js +80 -12
  5. package/dist/app/features/accounts/accounts.run.d.ts +29 -0
  6. package/dist/app/features/accounts/accounts.run.js +36 -6
  7. package/dist/app/features/accounts/index.d.ts +2 -0
  8. package/dist/app/features/accounts/index.js +4 -1
  9. package/dist/app/features/accounts/services.def.d.ts +111 -0
  10. package/dist/app/features/accounts/services.def.js +187 -0
  11. package/dist/app/features/accounts/services.run.d.ts +65 -0
  12. package/dist/app/features/accounts/services.run.js +212 -0
  13. package/dist/app/features/defs.d.ts +112 -1
  14. package/dist/app/features/defs.js +5 -0
  15. package/dist/app/features/settings/categories.def.js +21 -4
  16. package/dist/app/registry.js +5 -1
  17. package/dist/bin/agent-sandbox.js +2 -2
  18. package/dist/bin/{chunk-QLGGAG3D.js → chunk-BKF43GJ3.js} +51 -16
  19. package/dist/bin/{chunk-REE3EMHD.js → chunk-YD3A67RS.js} +3812 -1702
  20. package/dist/bin/{chunk-XYUVIVJT.js → chunk-Z5D32FN3.js} +1 -1
  21. package/dist/bin/claude-host.js +2 -2
  22. package/dist/bin/claude-sbx.js +2 -2
  23. package/dist/bin/codex-sbx.js +2 -2
  24. package/dist/bin/gemini-sbx.js +2 -2
  25. package/dist/bin/sandbox.js +2 -2
  26. package/dist/bin/sbx-hide.js +2 -2
  27. package/dist/bin/sbx-show.js +2 -2
  28. package/dist/cli/commands/launch/launch.js +30 -1
  29. package/dist/cli/manifest.d.ts +93 -65
  30. package/dist/cli/manifest.js +23 -5
  31. package/dist/cli/menu/pages/accounts-page.js +31 -16
  32. package/dist/cli/services/accounts-service.d.ts +14 -4
  33. package/dist/cli/services/accounts-service.js +19 -6
  34. package/dist/cli/services/data-collect.js +26 -1
  35. package/dist/core/config/defaults.js +47 -4
  36. package/dist/core/detect/toolchains.d.ts +15 -1
  37. package/dist/core/detect/toolchains.js +45 -12
  38. package/dist/core/local-config/overrides.js +6 -0
  39. package/dist/core/schema/accounts.d.ts +27 -0
  40. package/dist/core/schema/accounts.js +32 -0
  41. package/dist/core/schema/config.d.ts +239 -0
  42. package/dist/core/schema/config.js +6 -0
  43. package/dist/core/schema/pc-installs.d.ts +7 -0
  44. package/dist/core/schema/pc-installs.js +17 -0
  45. package/dist/core/schema/toolchains.d.ts +61 -0
  46. package/dist/core/schema/toolchains.js +106 -0
  47. package/dist/core/types.d.ts +18 -0
  48. package/dist/guest/accounts/adapter.d.ts +25 -0
  49. package/dist/guest/accounts/adapter.js +21 -0
  50. package/dist/guest/accounts/adapters.d.ts +5 -0
  51. package/dist/guest/accounts/adapters.js +18 -0
  52. package/dist/guest/accounts/apply.d.ts +41 -0
  53. package/dist/guest/accounts/apply.js +47 -0
  54. package/dist/guest/accounts/kinds/appstore.d.ts +2 -0
  55. package/dist/guest/accounts/kinds/appstore.js +7 -0
  56. package/dist/guest/accounts/kinds/cfx.d.ts +2 -0
  57. package/dist/guest/accounts/kinds/cfx.js +7 -0
  58. package/dist/guest/accounts/kinds/epic.d.ts +2 -0
  59. package/dist/guest/accounts/kinds/epic.js +26 -0
  60. package/dist/guest/accounts/kinds/expo.d.ts +2 -0
  61. package/dist/guest/accounts/kinds/expo.js +7 -0
  62. package/dist/guest/accounts/kinds/keystore.d.ts +2 -0
  63. package/dist/guest/accounts/kinds/keystore.js +7 -0
  64. package/dist/guest/accounts/kinds/play.d.ts +2 -0
  65. package/dist/guest/accounts/kinds/play.js +7 -0
  66. package/dist/guest/accounts/kinds/steam.d.ts +4 -0
  67. package/dist/guest/accounts/kinds/steam.js +17 -0
  68. package/dist/guest/accounts/kinds/unity.d.ts +2 -0
  69. package/dist/guest/accounts/kinds/unity.js +7 -0
  70. package/dist/guest/accounts/watch.d.ts +28 -0
  71. package/dist/guest/accounts/watch.js +78 -0
  72. package/dist/guest/apps/resolvers-gamedev.d.ts +2 -0
  73. package/dist/guest/apps/resolvers-gamedev.js +1 -0
  74. package/dist/guest/apps/resolvers-mobile.d.ts +2 -0
  75. package/dist/guest/apps/resolvers-mobile.js +1 -0
  76. package/dist/guest/apps/resolvers.js +4 -0
  77. package/dist/guest/boot/boot.d.ts +5 -0
  78. package/dist/guest/boot/boot.js +35 -1
  79. package/dist/guest/daemon/accounts.d.ts +3 -0
  80. package/dist/guest/daemon/accounts.js +25 -0
  81. package/dist/guest/daemon/daemon.js +2 -0
  82. package/dist/guest/daemon/idle-shutdown.js +3 -1
  83. package/dist/guest/daemon/sessions.js +2 -0
  84. package/dist/guest/devtools/android.d.ts +2 -0
  85. package/dist/guest/devtools/android.js +7 -0
  86. package/dist/guest/devtools/devtool.d.ts +9 -0
  87. package/dist/guest/devtools/devtool.js +5 -0
  88. package/dist/guest/devtools/fivem.d.ts +2 -0
  89. package/dist/guest/devtools/fivem.js +7 -0
  90. package/dist/guest/devtools/registry.d.ts +6 -0
  91. package/dist/guest/devtools/registry.js +12 -0
  92. package/dist/guest/devtools/steam.d.ts +2 -0
  93. package/dist/guest/devtools/steam.js +228 -0
  94. package/dist/guest/devtools/unity.d.ts +2 -0
  95. package/dist/guest/devtools/unity.js +7 -0
  96. package/dist/guest/entries/daemon.js +2 -0
  97. package/dist/guest/entries/tool.d.ts +1 -0
  98. package/dist/guest/entries/tool.js +17 -0
  99. package/dist/guest/toolchains/engines/godot.d.ts +4 -0
  100. package/dist/guest/toolchains/engines/godot.js +4 -0
  101. package/dist/guest/toolchains/engines/unity-release.d.ts +15 -0
  102. package/dist/guest/toolchains/engines/unity-release.js +6 -0
  103. package/dist/guest/toolchains/engines/unity.d.ts +4 -0
  104. package/dist/guest/toolchains/engines/unity.js +4 -0
  105. package/dist/guest/toolchains/engines/unreal-engines.d.ts +4 -0
  106. package/dist/guest/toolchains/engines/unreal-engines.js +4 -0
  107. package/dist/guest/toolchains/fivem/artifacts.d.ts +6 -0
  108. package/dist/guest/toolchains/fivem/artifacts.js +7 -0
  109. package/dist/guest/toolchains/fivem/fivem.d.ts +4 -0
  110. package/dist/guest/toolchains/fivem/fivem.js +4 -0
  111. package/dist/guest/toolchains/installers.d.ts +12 -0
  112. package/dist/guest/toolchains/installers.js +39 -0
  113. package/dist/guest/toolchains/languages/java.js +1 -1
  114. package/dist/guest/toolchains/languages/kotlin.d.ts +3 -0
  115. package/dist/guest/toolchains/languages/kotlin.js +6 -0
  116. package/dist/guest/toolchains/mobile/android-repo.d.ts +11 -0
  117. package/dist/guest/toolchains/mobile/android-repo.js +10 -0
  118. package/dist/guest/toolchains/mobile/android.d.ts +17 -0
  119. package/dist/guest/toolchains/mobile/android.js +16 -0
  120. package/dist/guest/toolchains/mobile/dart.d.ts +4 -0
  121. package/dist/guest/toolchains/mobile/dart.js +4 -0
  122. package/dist/guest/toolchains/mobile/flutter.d.ts +4 -0
  123. package/dist/guest/toolchains/mobile/flutter.js +4 -0
  124. package/dist/guest/toolchains/mobile/react-native.d.ts +4 -0
  125. package/dist/guest/toolchains/mobile/react-native.js +4 -0
  126. package/dist/guest/toolchains/native/unreal.d.ts +3 -7
  127. package/dist/guest/toolchains/native/unreal.js +16 -167
  128. package/dist/guest/toolchains/native/visual-studio.d.ts +15 -0
  129. package/dist/guest/toolchains/native/visual-studio.js +159 -0
  130. package/dist/guest/toolchains/native/vs-request.d.ts +19 -0
  131. package/dist/guest/toolchains/native/vs-request.js +46 -0
  132. package/dist/guest/toolchains/not-yet.d.ts +2 -0
  133. package/dist/guest/toolchains/not-yet.js +6 -0
  134. package/dist/guest/toolchains/persisted.d.ts +48 -0
  135. package/dist/guest/toolchains/persisted.js +159 -0
  136. package/dist/guest/toolchains/plan.js +80 -4
  137. package/dist/guest/toolchains/steam/apps.d.ts +27 -0
  138. package/dist/guest/toolchains/steam/apps.js +112 -0
  139. package/dist/guest/toolchains/steam/client.d.ts +5 -0
  140. package/dist/guest/toolchains/steam/client.js +67 -0
  141. package/dist/guest/toolchains/steam/commands.d.ts +58 -0
  142. package/dist/guest/toolchains/steam/commands.js +155 -0
  143. package/dist/guest/toolchains/steam/steam.d.ts +4 -0
  144. package/dist/guest/toolchains/steam/steam.js +61 -0
  145. package/dist/guest/toolchains/steam/steamcmd-install.d.ts +29 -0
  146. package/dist/guest/toolchains/steam/steamcmd-install.js +177 -0
  147. package/dist/guest/toolchains/steam/steamcmd.d.ts +6 -0
  148. package/dist/guest/toolchains/steam/steamcmd.js +23 -0
  149. package/dist/guest/toolchains/step.d.ts +10 -0
  150. package/dist/host/accounts/deliver.d.ts +26 -0
  151. package/dist/host/accounts/deliver.js +80 -0
  152. package/dist/host/accounts/handoff.d.ts +17 -0
  153. package/dist/host/accounts/handoff.js +110 -0
  154. package/dist/host/accounts/index.d.ts +6 -0
  155. package/dist/host/accounts/index.js +8 -0
  156. package/dist/host/accounts/kinds/appstore.d.ts +2 -0
  157. package/dist/host/accounts/kinds/appstore.js +10 -0
  158. package/dist/host/accounts/kinds/cfx.d.ts +2 -0
  159. package/dist/host/accounts/kinds/cfx.js +7 -0
  160. package/dist/host/accounts/kinds/epic.d.ts +2 -0
  161. package/dist/host/accounts/kinds/epic.js +15 -0
  162. package/dist/host/accounts/kinds/expo.d.ts +2 -0
  163. package/dist/host/accounts/kinds/expo.js +7 -0
  164. package/dist/host/accounts/kinds/keystore.d.ts +2 -0
  165. package/dist/host/accounts/kinds/keystore.js +7 -0
  166. package/dist/host/accounts/kinds/play.d.ts +2 -0
  167. package/dist/host/accounts/kinds/play.js +7 -0
  168. package/dist/host/accounts/kinds/steam.d.ts +6 -0
  169. package/dist/host/accounts/kinds/steam.js +31 -0
  170. package/dist/host/accounts/kinds/unity.d.ts +2 -0
  171. package/dist/host/accounts/kinds/unity.js +13 -0
  172. package/dist/host/accounts/leases.d.ts +40 -0
  173. package/dist/host/accounts/leases.js +62 -0
  174. package/dist/host/accounts/source.d.ts +21 -0
  175. package/dist/host/accounts/source.js +15 -0
  176. package/dist/host/accounts/sources.d.ts +5 -0
  177. package/dist/host/accounts/sources.js +18 -0
  178. package/dist/host/accounts/store.d.ts +23 -0
  179. package/dist/host/accounts/store.js +74 -0
  180. package/dist/host/audit/log.d.ts +1 -1
  181. package/dist/host/launch/generate.d.ts +2 -0
  182. package/dist/host/launch/generate.js +16 -1
  183. package/dist/host/launch/hints/android.d.ts +3 -0
  184. package/dist/host/launch/hints/android.js +3 -0
  185. package/dist/host/launch/hints/engines.d.ts +3 -0
  186. package/dist/host/launch/hints/engines.js +3 -0
  187. package/dist/host/launch/hints/fivem.d.ts +3 -0
  188. package/dist/host/launch/hints/fivem.js +3 -0
  189. package/dist/host/launch/hints/hint.d.ts +2 -0
  190. package/dist/host/launch/hints/hint.js +1 -0
  191. package/dist/host/launch/hints/index.d.ts +5 -0
  192. package/dist/host/launch/hints/index.js +29 -0
  193. package/dist/host/launch/hints/mobile.d.ts +3 -0
  194. package/dist/host/launch/hints/mobile.js +3 -0
  195. package/dist/host/launch/hints/steam.d.ts +7 -0
  196. package/dist/host/launch/hints/steam.js +59 -0
  197. package/dist/host/launch/index.d.ts +2 -2
  198. package/dist/host/launch/index.js +2 -2
  199. package/dist/host/launch/mappings.js +2 -0
  200. package/dist/host/launch/session-file.js +2 -0
  201. package/dist/host/launch/stage.js +10 -1
  202. package/dist/host/launch/toolchains.d.ts +7 -0
  203. package/dist/host/launch/toolchains.js +11 -0
  204. package/dist/host/pc-installs/discover.d.ts +19 -0
  205. package/dist/host/pc-installs/discover.js +59 -0
  206. package/dist/host/pc-installs/epic.d.ts +2 -0
  207. package/dist/host/pc-installs/epic.js +7 -0
  208. package/dist/host/pc-installs/index.d.ts +3 -0
  209. package/dist/host/pc-installs/index.js +2 -0
  210. package/dist/host/pc-installs/source.d.ts +9 -0
  211. package/dist/host/pc-installs/source.js +1 -0
  212. package/dist/host/pc-installs/steam.d.ts +16 -0
  213. package/dist/host/pc-installs/steam.js +143 -0
  214. package/dist/host/pc-installs/unity.d.ts +2 -0
  215. package/dist/host/pc-installs/unity.js +7 -0
  216. package/dist/host/pc-installs/unreal.d.ts +2 -0
  217. package/dist/host/pc-installs/unreal.js +7 -0
  218. package/dist/host/profile/accounts.d.ts +21 -6
  219. package/dist/host/profile/accounts.js +108 -28
  220. package/dist/host/profile/claude-login.d.ts +32 -2
  221. package/dist/host/profile/claude-login.js +100 -8
  222. package/dist/host/profile/index.d.ts +1 -1
  223. package/dist/host/profile/index.js +1 -1
  224. package/dist/host/secrets/launch-secrets.js +1 -0
  225. package/dist/runtime/agent.js +321 -9
  226. package/dist/runtime/daemon.js +1987 -604
  227. package/dist/runtime/db.js +334 -11
  228. package/dist/runtime/main.js +2519 -723
  229. package/dist/runtime/netproxy.js +321 -9
  230. package/dist/runtime/tool.js +28549 -0
  231. package/dist/runtime/view.js +321 -9
  232. package/dist/selftest/accounts.test.js +172 -1
  233. package/dist/selftest/agent-paths.test.js +1 -0
  234. package/dist/selftest/android.test.d.ts +2 -0
  235. package/dist/selftest/android.test.js +7 -0
  236. package/dist/selftest/app.test.js +1 -1
  237. package/dist/selftest/engines.test.d.ts +2 -0
  238. package/dist/selftest/engines.test.js +7 -0
  239. package/dist/selftest/fivem.test.d.ts +2 -0
  240. package/dist/selftest/fivem.test.js +7 -0
  241. package/dist/selftest/generate.test.js +1 -1
  242. package/dist/selftest/index.js +18 -0
  243. package/dist/selftest/menu-screens.js +1 -0
  244. package/dist/selftest/menu.test.js +2 -1
  245. package/dist/selftest/mobile.test.d.ts +2 -0
  246. package/dist/selftest/mobile.test.js +7 -0
  247. package/dist/selftest/pc-installs.test.d.ts +2 -0
  248. package/dist/selftest/pc-installs.test.js +111 -0
  249. package/dist/selftest/persisted-installs.test.d.ts +2 -0
  250. package/dist/selftest/persisted-installs.test.js +107 -0
  251. package/dist/selftest/projects.test.js +6 -4
  252. package/dist/selftest/service-accounts.test.d.ts +2 -0
  253. package/dist/selftest/service-accounts.test.js +320 -0
  254. package/dist/selftest/steam.test.d.ts +2 -0
  255. package/dist/selftest/steam.test.js +316 -0
  256. package/dist/selftest/storage-helper.test.js +31 -8
  257. package/dist/selftest/support/engine-golden.js +3 -0
  258. package/dist/selftest/support/fixtures.d.ts +87 -0
  259. package/dist/selftest/support/fixtures.js +4 -3
  260. package/dist/selftest/support/pc-memory.d.ts +2 -0
  261. package/dist/selftest/support/pc-memory.js +17 -0
  262. package/dist/selftest/support/web-preview.js +20 -1
  263. package/dist/selftest/unity.test.d.ts +2 -0
  264. package/dist/selftest/unity.test.js +7 -0
  265. package/dist/shared/accounts/fingerprint.d.ts +1 -0
  266. package/dist/shared/accounts/fingerprint.js +20 -0
  267. package/dist/shared/accounts/index.d.ts +5 -0
  268. package/dist/shared/accounts/index.js +7 -0
  269. package/dist/shared/accounts/kinds/epic.d.ts +10 -0
  270. package/dist/shared/accounts/kinds/epic.js +56 -0
  271. package/dist/shared/accounts/kinds/steam.d.ts +28 -0
  272. package/dist/shared/accounts/kinds/steam.js +99 -0
  273. package/dist/shared/accounts/kinds.d.ts +41 -0
  274. package/dist/shared/accounts/kinds.js +154 -0
  275. package/dist/shared/accounts/payloads.d.ts +151 -0
  276. package/dist/shared/accounts/payloads.js +128 -0
  277. package/dist/shared/apps/catalogue-gamedev.d.ts +2 -0
  278. package/dist/shared/apps/catalogue-gamedev.js +1 -0
  279. package/dist/shared/apps/catalogue-mobile.d.ts +2 -0
  280. package/dist/shared/apps/catalogue-mobile.js +1 -0
  281. package/dist/shared/apps/catalogue-modding.d.ts +2 -0
  282. package/dist/shared/apps/catalogue-modding.js +1 -0
  283. package/dist/shared/apps/catalogue.d.ts +1 -0
  284. package/dist/shared/apps/catalogue.js +6 -1
  285. package/dist/shared/channel/schema.d.ts +183 -0
  286. package/dist/shared/channel/secrets.d.ts +42 -0
  287. package/dist/shared/channel/secrets.js +3 -0
  288. package/dist/shared/download/downloader.d.ts +7 -0
  289. package/dist/shared/download/downloader.js +10 -3
  290. package/dist/shared/installs.d.ts +19 -0
  291. package/dist/shared/installs.js +13 -0
  292. package/dist/shared/io/bounded.d.ts +6 -0
  293. package/dist/shared/io/bounded.js +38 -0
  294. package/dist/shared/io/index.d.ts +2 -0
  295. package/dist/shared/io/index.js +2 -0
  296. package/dist/shared/io/text.d.ts +9 -0
  297. package/dist/shared/io/text.js +18 -0
  298. package/dist/shared/paths/guest-paths.js +1 -1
  299. package/dist/shared/paths/index.d.ts +1 -0
  300. package/dist/shared/paths/index.js +1 -0
  301. package/dist/shared/paths/pc-installs.d.ts +20 -0
  302. package/dist/shared/paths/pc-installs.js +19 -0
  303. package/dist/shared/vdf.d.ts +25 -0
  304. package/dist/shared/vdf.js +151 -0
  305. package/dist/sync/sync-cycle.js +9 -0
  306. package/dist/web/index.html +1 -1
  307. package/dist/web/{main-JTH3FT4O.js → main-CWIF4VQI.js} +63 -61
  308. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  309. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  310. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  311. package/package.json +1 -1
  312. package/sandbox/scripts/add-firewall-port.ps1 +13 -0
  313. package/sandbox/templates/fivem/README.txt +2 -0
  314. package/schemas/config.schema.json +485 -0
  315. package/schemas/sandbox-config.schema.json +416 -0
@@ -4442,7 +4442,7 @@ var require_lodash = __commonJS({
4442
4442
  function omitBy(object2, predicate) {
4443
4443
  return pickBy(object2, negate(getIteratee(predicate)));
4444
4444
  }
4445
- var pick2 = flatRest(function(object2, paths) {
4445
+ var pick3 = flatRest(function(object2, paths) {
4446
4446
  return object2 == null ? {} : basePick(object2, paths);
4447
4447
  });
4448
4448
  function pickBy(object2, predicate) {
@@ -5125,7 +5125,7 @@ var require_lodash = __commonJS({
5125
5125
  lodash.partial = partial2;
5126
5126
  lodash.partialRight = partialRight;
5127
5127
  lodash.partition = partition;
5128
- lodash.pick = pick2;
5128
+ lodash.pick = pick3;
5129
5129
  lodash.pickBy = pickBy;
5130
5130
  lodash.property = property;
5131
5131
  lodash.propertyOf = propertyOf;
@@ -25866,7 +25866,7 @@ function date4(params) {
25866
25866
  }
25867
25867
 
25868
25868
  // src/shared/paths/guest-paths.ts
25869
- var RESERVED_GUEST = /^C:\\(?:Windows|Program Files|Program Files \(x86\)|ProgramData|Users|AgentLaunch|AgentData|HostTools|nvm|Mirror)(?:\\|$)/i;
25869
+ var RESERVED_GUEST = /^C:\\(?:Windows|Program Files|Program Files \(x86\)|ProgramData|Users|AgentLaunch|AgentData|HostTools|nvm|Mirror|PcInstalls)(?:\\|$)/i;
25870
25870
  var MIRROR_LETTER = /^[D-Z]$/;
25871
25871
  var DRIVE_PATH = /^([A-Za-z]):\\(.*)$/;
25872
25872
  var BAD_CHARS = /[<>:"|?*/\x00-\x1f]/;
@@ -26265,8 +26265,119 @@ var ToolchainsSchema = external_exports.strictObject({
26265
26265
  ),
26266
26266
  keepInstallFiles: external_exports.boolean().describe(
26267
26267
  "Keep an offline copy of the Visual Studio install files in tools\\visualstudio (~10 GB, made once), so later boots install without downloading them again. Skipped while that disk has too little room."
26268
+ ),
26269
+ persistEngines: external_exports.boolean().describe(
26270
+ "Engines the Epic Games Launcher installs go to tools\\unreal\\engines (kept between sandboxes, 40-60 GB each) and are registered with the launcher again every boot. Off: they go on the sandbox disk and are lost when it closes. Engines installed on this PC can be mapped instead (pcInstalls.unrealEngines)."
26271
+ ),
26272
+ derivedDataCache: external_exports.enum(["persisted", "local"]).describe(
26273
+ `"persisted": the engine's local derived data cache (compiled shaders, 10-50 GB) in tools\\unreal\\ddc, kept between sandboxes. "local": Unreal's own place on the sandbox disk (rebuilt every boot).`
26274
+ )
26275
+ }),
26276
+ unity: external_exports.strictObject({
26277
+ enabled: enabled(
26278
+ "Unity: the editor version a project asks for, from Unity's release API (checked against its published hash), installed into tools\\unity\\<version> (6-7 GB each), with the modules you list.",
26279
+ "ProjectSettings/ProjectVersion.txt"
26280
+ ),
26281
+ acceptLicense: external_exports.boolean().describe(
26282
+ "Required to install: you accept the Unity terms of service (https://unity.com/legal/terms-of-service), and the editor needs a Unity licence (Personal is free). A project file cannot accept it for you."
26283
+ ),
26284
+ version: external_exports.string().min(1).describe(
26285
+ '"project" = the version in ProjectSettings/ProjectVersion.txt, or an editor version such as "6000.0.32f1".'
26286
+ ),
26287
+ modules: external_exports.array(external_exports.string().regex(/^[a-z0-9-]+$/)).describe(
26288
+ `Editor modules to add: "android" (4-5 GB, also the Android SDK), "webgl" (1.5 GB), "windows-il2cpp" (adds Visual Studio's C++ parts), "ios" (Xcode project export only: iOS builds need a Mac).`
26289
+ ),
26290
+ hub: external_exports.boolean().describe("Also Unity Hub, with the installed editors listed in it."),
26291
+ keepVersions: external_exports.number().int().min(1).max(20).describe(
26292
+ "How many editor versions to keep in tools\\unity; older ones are removed (the least recently used first)."
26293
+ )
26294
+ }),
26295
+ godot: external_exports.strictObject({
26296
+ enabled: enabled(
26297
+ "Godot from its official builds (checked against the published SHA-512 sums), self-contained in tools\\godot\\<version> with `godot` (the console build) on PATH.",
26298
+ "project.godot"
26299
+ ),
26300
+ version: external_exports.string().min(1).describe(
26301
+ '"project" = the version project.godot was saved with (else the newest stable), or a version such as "4.3".'
26302
+ ),
26303
+ dotnet: EnabledSchema.describe(
26304
+ 'The .NET (C#) build of Godot and the .NET SDK: true, false, or "auto" = when the project has a .csproj.'
26305
+ ),
26306
+ exportTemplates: external_exports.boolean().describe("Also the export templates for that version (~1.2 GB), for exporting games."),
26307
+ keepVersions: external_exports.number().int().min(1).max(20).describe("How many Godot versions to keep; older ones are removed (the least recently used first).")
26308
+ }),
26309
+ steam: external_exports.strictObject({
26310
+ enabled: enabled(
26311
+ "SteamCMD (`steamcmd` on PATH, Valve-signed, ~0.3 GB) and `sbx-steam` for logging in, downloading apps and workshop items, and uploading builds (ContentBuilder) and workshop items.",
26312
+ "steam_appid.txt, steam_api64.dll, steam_api.dll, or an app_build / workshop_item .vdf"
26313
+ ),
26314
+ client: external_exports.boolean().describe(
26315
+ "Also the Steam client (1-1.5 GB, in tools\\steam), for testing the Steamworks API, the overlay or in-game uploads. You sign in to it in each sandbox."
26316
+ ),
26317
+ sdkFolder: external_exports.string().describe(
26318
+ "Where the Steamworks SDK is in the sandbox (relative paths are inside the project); it sets STEAMWORKS_SDK. Empty = not set. The SDK is never downloaded (it needs a Steamworks partner login)."
26319
+ )
26320
+ }),
26321
+ fivem: external_exports.strictObject({
26322
+ enabled: enabled(
26323
+ "A FiveM / RedM server (FXServer with txAdmin) for resource development, from the Cfx.re artifacts (the game client is not supported). `sbx-fivem serve` starts it.",
26324
+ "fxmanifest.lua or __resource.lua (up to 3 folders down)"
26325
+ ),
26326
+ acceptLicense: external_exports.boolean().describe(
26327
+ "Required to install: you accept the Cfx.re platform terms (https://fivem.net/terms) and the FXServer licence. A project file cannot accept it for you."
26328
+ ),
26329
+ game: external_exports.enum(["gta5", "rdr3"]).describe('"gta5" = FiveM, "rdr3" = RedM.'),
26330
+ artifact: external_exports.string().regex(/^(recommended|optional|latest|\d+)$/).describe('The server build: "recommended", "optional", "latest", or a build number.'),
26331
+ txAdmin: external_exports.boolean().describe("Start the server with txAdmin (its web panel on port 40120)."),
26332
+ keepVersions: external_exports.number().int().min(1).max(20).describe("How many server builds to keep in tools\\fivem\\artifacts.")
26333
+ }),
26334
+ android: external_exports.strictObject({
26335
+ enabled: enabled(
26336
+ "The Android SDK (command-line tools, platform-tools, build-tools, platforms, NDK and CMake as needed) in tools\\android-sdk, with a JDK. No emulator (Windows Sandbox cannot run one): use a device over the network, or build only.",
26337
+ "a Gradle build using com.android, an AndroidManifest.xml (up to 3 folders down), a .NET MAUI project, or a Flutter / React Native / Godot Android project"
26338
+ ),
26339
+ acceptLicense: external_exports.boolean().describe(
26340
+ "Required to install: you accept the Android SDK licence (https://developer.android.com/studio/terms) and the licences sdkmanager shows. A project file cannot accept them for you."
26341
+ ),
26342
+ platforms: StringsSchema.describe(
26343
+ `Platforms to install, e.g. ["android-35"]; "project" = the compileSdk the project's Gradle files ask for.`
26344
+ ),
26345
+ buildTools: external_exports.string().min(1).describe('"latest" or a build-tools version such as "35.0.0".'),
26346
+ ndk: external_exports.union([external_exports.literal(false), external_exports.string().min(1)]).describe(
26347
+ '"auto" = when the project uses native code (ndkVersion, CMake or ndk-build), false = never, or a version.'
26348
+ ),
26349
+ cmake: external_exports.union([external_exports.literal(false), external_exports.string().min(1)]).describe('"auto" = when the project builds native code with CMake, false = never, or a version.'),
26350
+ packages: Packages("More sdkmanager packages (not the emulator or system images)", '["extras;google;usb_driver"]')
26351
+ }),
26352
+ flutter: external_exports.strictObject({
26353
+ enabled: enabled(
26354
+ "Flutter (with its Dart) from the official releases (checked against their SHA-256), with the Android SDK for Android builds.",
26355
+ "a pubspec.yaml with a flutter: section"
26356
+ ),
26357
+ version: external_exports.string().min(1).describe(
26358
+ '"project" = the version in .fvmrc (else the newest stable), "stable", "beta", or a version such as "3.24.5".'
26359
+ ),
26360
+ windowsDesktop: external_exports.boolean().describe(
26361
+ "Windows desktop builds: needs Visual Studio Build Tools' C++ parts (a real installer: it runs every boot, ~5 GB on the sandbox disk)."
26362
+ ),
26363
+ precache: StringsSchema.describe(
26364
+ 'Platforms whose engine files are downloaded ahead (flutter precache), e.g. ["android", "web"].'
26268
26365
  )
26269
26366
  }),
26367
+ dart: external_exports.strictObject({
26368
+ enabled: enabled(
26369
+ "The Dart SDK (without Flutter), checked against its published SHA-256.",
26370
+ "a pubspec.yaml without a flutter: section"
26371
+ ),
26372
+ version: external_exports.string().min(1).describe('"stable", "beta" or a version such as "3.5.4".')
26373
+ }),
26374
+ reactNative: external_exports.strictObject({
26375
+ enabled: enabled(
26376
+ "React Native / Expo: the Android SDK and JDK for Android builds (Node is always there). iOS builds run in EAS Build (the cloud).",
26377
+ "a package.json depending on react-native or expo"
26378
+ ),
26379
+ easCli: external_exports.boolean().describe("Also eas-cli (Expo Application Services) as a global npm package.")
26380
+ }),
26270
26381
  cpp: external_exports.strictObject({
26271
26382
  enabled: enabled(
26272
26383
  "MinGW-w64 GCC with CMake, Ninja, premake5 and mingw32-make (also Rust's linker).",
@@ -26286,7 +26397,8 @@ var ToolchainsSchema = external_exports.strictObject({
26286
26397
  ),
26287
26398
  netfx35: external_exports.boolean().describe(
26288
26399
  ".NET Framework 3.5 runtime (a Windows feature, enabled from Windows Update on every boot; a few minutes)."
26289
- )
26400
+ ),
26401
+ templates: Packages("Project templates (`dotnet new install`)", '["MonoGame.Templates.CSharp"]')
26290
26402
  }),
26291
26403
  go: external_exports.strictObject({
26292
26404
  enabled: enabled("Go.", "go.mod or go.work"),
@@ -26302,7 +26414,8 @@ var ToolchainsSchema = external_exports.strictObject({
26302
26414
  ),
26303
26415
  version: external_exports.string().min(1).describe(`"lts" (newest LTS) or a major version such as "21". A project's .java-version or .sdkmanrc wins.`),
26304
26416
  maven: external_exports.boolean().describe("Apache Maven (mvn). A project's mvnw works either way."),
26305
- gradle: external_exports.boolean().describe("Gradle (gradle). A project's gradlew works either way.")
26417
+ gradle: external_exports.boolean().describe("Gradle (gradle). A project's gradlew works either way."),
26418
+ kotlin: external_exports.boolean().describe("Also the Kotlin compiler (kotlinc) from JetBrains' releases. Gradle builds fetch their own.")
26306
26419
  }),
26307
26420
  ruby: external_exports.strictObject({
26308
26421
  enabled: enabled(
@@ -26373,6 +26486,15 @@ var ToolchainsSchema = external_exports.strictObject({
26373
26486
  )
26374
26487
  });
26375
26488
 
26489
+ // src/shared/apps/catalogue-gamedev.ts
26490
+ var GAMEDEV_APPS = [];
26491
+
26492
+ // src/shared/apps/catalogue-mobile.ts
26493
+ var MOBILE_APPS = [];
26494
+
26495
+ // src/shared/apps/catalogue-modding.ts
26496
+ var MODDING_APPS = [];
26497
+
26376
26498
  // src/shared/apps/catalogue.ts
26377
26499
  var gh = (github, asset) => ({ github, asset });
26378
26500
  var RTK_VERSION = "1.0.1";
@@ -26382,7 +26504,7 @@ var HEADROOM_VERSION = "0.41.0";
26382
26504
  var CCUSAGE_VERSION = "20.0.36";
26383
26505
  var PLAYWRIGHT_MCP_VERSION = "0.0.83";
26384
26506
  var CONTEXT7_MCP_VERSION = "4.4.2";
26385
- var APPS = [
26507
+ var CORE_APPS = [
26386
26508
  // ---- agent add-ons
26387
26509
  {
26388
26510
  id: "rtk",
@@ -27205,6 +27327,7 @@ var APPS = [
27205
27327
  cli: true
27206
27328
  }
27207
27329
  ];
27330
+ var APPS = [...CORE_APPS, ...GAMEDEV_APPS, ...MODDING_APPS, ...MOBILE_APPS];
27208
27331
 
27209
27332
  // src/core/schema/apps.ts
27210
27333
  var CATALOGUE_IDS = APPS.filter((a) => !a.setting).map((a) => a.id);
@@ -27343,6 +27466,185 @@ var GithubSchema = external_exports.strictObject({
27343
27466
  )
27344
27467
  });
27345
27468
 
27469
+ // src/shared/accounts/kinds.ts
27470
+ var AGENT_CAN_USE = "The agent in the sandbox can use this sign-in while it is there.";
27471
+ var ONE_PLACE = "It works in one sandbox at a time: a second sandbox starts without it while the first one runs.";
27472
+ var SERVICE_SPECS = {
27473
+ epic: {
27474
+ kind: "epic",
27475
+ label: "Epic Games",
27476
+ usedBy: ["unreal"],
27477
+ sources: ["pc", "sandbox"],
27478
+ singleHolder: true,
27479
+ returns: true,
27480
+ maxBytes: 64 * 1024,
27481
+ warnings: [
27482
+ AGENT_CAN_USE,
27483
+ "A sign-in works in one place at a time: taking this PC's signs the launcher on this PC out the first time a sandbox uses it."
27484
+ ],
27485
+ fields: []
27486
+ },
27487
+ steam: {
27488
+ kind: "steam",
27489
+ label: "Steam (SteamCMD)",
27490
+ usedBy: ["steam"],
27491
+ sources: ["file", "sandbox"],
27492
+ singleHolder: true,
27493
+ returns: true,
27494
+ maxBytes: 768 * 1024,
27495
+ warnings: [
27496
+ AGENT_CAN_USE + " It can upload builds and workshop items to your Steam apps.",
27497
+ ONE_PLACE,
27498
+ "No password is kept: sign in once with sbx-steam login in a sandbox (Steam Guard asks there)."
27499
+ ],
27500
+ fields: []
27501
+ },
27502
+ unity: {
27503
+ kind: "unity",
27504
+ label: "Unity",
27505
+ usedBy: ["unity"],
27506
+ sources: ["pc", "file", "typed", "sandbox"],
27507
+ singleHolder: true,
27508
+ returns: true,
27509
+ maxBytes: 256 * 1024,
27510
+ warnings: [
27511
+ AGENT_CAN_USE,
27512
+ ONE_PLACE,
27513
+ "A Pro seat is handed back when the sandbox stops cleanly; a sandbox closed abruptly may keep it until you return it."
27514
+ ],
27515
+ fields: [
27516
+ { name: "email", label: "Unity ID email", secret: false, optional: true },
27517
+ { name: "password", label: "Unity ID password", secret: true, optional: true },
27518
+ { name: "serial", label: "Serial (Plus or Pro; empty for Personal)", secret: true, optional: true }
27519
+ ]
27520
+ },
27521
+ cfx: {
27522
+ kind: "cfx",
27523
+ label: "Cfx.re (FiveM / RedM server key)",
27524
+ usedBy: ["fivem"],
27525
+ sources: ["typed"],
27526
+ singleHolder: false,
27527
+ returns: false,
27528
+ maxBytes: 4 * 1024,
27529
+ warnings: [AGENT_CAN_USE + " Anyone with the key can run a server under your Cfx.re account."],
27530
+ fields: [
27531
+ { name: "licenseKey", label: "Server licence key (cfxk_...)", secret: true, pattern: /^cfxk_[A-Za-z0-9_]+$/ },
27532
+ {
27533
+ name: "steamWebApiKey",
27534
+ label: "Steam Web API key (optional)",
27535
+ secret: true,
27536
+ optional: true,
27537
+ pattern: /^[A-F0-9]{32}$/
27538
+ }
27539
+ ]
27540
+ },
27541
+ keystore: {
27542
+ kind: "keystore",
27543
+ label: "Android upload keystore",
27544
+ usedBy: ["android"],
27545
+ sources: ["file"],
27546
+ singleHolder: false,
27547
+ returns: false,
27548
+ maxBytes: 512 * 1024,
27549
+ warnings: [AGENT_CAN_USE + " It can sign app updates as you; keep a backup of the keystore elsewhere."],
27550
+ fields: [
27551
+ { name: "storePassword", label: "Keystore password", secret: true },
27552
+ { name: "keyAlias", label: "Key alias", secret: false },
27553
+ { name: "keyPassword", label: "Key password", secret: true }
27554
+ ]
27555
+ },
27556
+ play: {
27557
+ kind: "play",
27558
+ label: "Google Play (service account)",
27559
+ usedBy: ["android"],
27560
+ sources: ["file"],
27561
+ singleHolder: false,
27562
+ returns: false,
27563
+ maxBytes: 128 * 1024,
27564
+ warnings: [AGENT_CAN_USE + " It can publish to Google Play with what the service account is allowed."],
27565
+ fields: []
27566
+ },
27567
+ expo: {
27568
+ kind: "expo",
27569
+ label: "Expo (EAS)",
27570
+ usedBy: ["reactNative"],
27571
+ sources: ["typed"],
27572
+ singleHolder: false,
27573
+ returns: false,
27574
+ maxBytes: 4 * 1024,
27575
+ warnings: [AGENT_CAN_USE + " It can start EAS builds and submissions on your Expo account."],
27576
+ fields: [{ name: "token", label: "Expo access token", secret: true }]
27577
+ },
27578
+ appstore: {
27579
+ kind: "appstore",
27580
+ label: "App Store Connect API key",
27581
+ usedBy: ["reactNative", "flutter"],
27582
+ sources: ["file", "typed"],
27583
+ singleHolder: false,
27584
+ returns: false,
27585
+ maxBytes: 16 * 1024,
27586
+ warnings: [AGENT_CAN_USE + " It can upload and submit apps with what the key is allowed."],
27587
+ fields: [
27588
+ { name: "issuerId", label: "Issuer ID", secret: false },
27589
+ { name: "keyId", label: "Key ID", secret: false, pattern: /^[A-Z0-9]{10}$/ }
27590
+ ]
27591
+ }
27592
+ };
27593
+
27594
+ // src/core/schema/accounts.ts
27595
+ function share(kind, how) {
27596
+ const spec = SERVICE_SPECS[kind];
27597
+ return external_exports.strictObject({
27598
+ share: external_exports.boolean().describe(
27599
+ "Hand your saved " + spec.label + " sign-in to sandboxes that use " + spec.usedBy.join(" or ") + " (" + how + "). Kept encrypted on this PC; " + spec.warnings.join(" ") + " Yours only: a project file can only turn it off."
27600
+ )
27601
+ });
27602
+ }
27603
+ var AccountsSchema = external_exports.strictObject({
27604
+ epic: share("epic", "sign in to the launcher once in a sandbox, or take this PC's: sandbox account import epic"),
27605
+ steam: share(
27606
+ "steam",
27607
+ "sign in once with sbx-steam login, or: sandbox account import steam --from <steamcmd folder>"
27608
+ ),
27609
+ unity: share("unity", "sign in once in a sandbox, or take this PC's licence: sandbox account import unity"),
27610
+ cfx: share("cfx", "sandbox account login cfx"),
27611
+ keystore: share("keystore", "sandbox account import keystore --from <upload.jks>"),
27612
+ play: share("play", "sandbox account import play --from <service-account.json>"),
27613
+ expo: share("expo", "sandbox account login expo"),
27614
+ appstore: share("appstore", "sandbox account import appstore --from <AuthKey.p8>")
27615
+ }).describe(
27616
+ "Service sign-ins for sandboxes (game stores, engines, app signing). Manage them in Accounts: sandbox account status."
27617
+ );
27618
+
27619
+ // src/core/schema/pc-installs.ts
27620
+ var pick2 = (item, what, example) => external_exports.union([external_exports.boolean(), external_exports.array(item)]).describe(
27621
+ what + " of this PC, read-only in the sandbox under C:\\PcInstalls: true = all of them, false = none, or a list (e.g. " + example + "). The agent can read their files."
27622
+ );
27623
+ var PcInstallsSchema = external_exports.strictObject({
27624
+ steamGames: pick2(
27625
+ external_exports.number().int().positive(),
27626
+ "Steam games (true maps each library; a list of app IDs maps only those games)",
27627
+ "[480, 730]"
27628
+ ),
27629
+ epicGames: pick2(
27630
+ external_exports.string().regex(/^[A-Za-z0-9._-]{1,100}$/),
27631
+ "Epic Games Launcher games (by their app name)",
27632
+ '["Fortnite"]'
27633
+ ),
27634
+ unrealEngines: pick2(
27635
+ external_exports.string().regex(/^(UE_)?\d+\.\d+$/),
27636
+ "Unreal Engine versions installed with the Epic Games Launcher",
27637
+ '["5.4"]'
27638
+ ),
27639
+ unityEditors: pick2(
27640
+ external_exports.string().regex(/^\d+\.\d+\.\d+[abfp]\d+$/),
27641
+ "Unity editors installed with Unity Hub",
27642
+ '["6000.0.32f1"]'
27643
+ )
27644
+ }).describe(
27645
+ "Games, engines and editors installed on this PC, mapped read-only into sandboxes (no copy, no room on the sandbox's disk)."
27646
+ );
27647
+
27346
27648
  // src/core/schema/config.ts
27347
27649
  var RESERVED_ENV_PREFIX = /^(AGENT_|GIT_|GH_|GITHUB_|NODE_|NPM_CONFIG_(USERCONFIG|GLOBALCONFIG|PREFIX|SCRIPT_SHELL)|SSL_|CLAUDE|ANTHROPIC_|OPENAI_|CODEX_|GEMINI_|GOOGLE_|COPILOT_|AWS_|AZURE_)/i;
27348
27650
  var RESERVED_ENV_SUFFIX = /(_PROXY|_BASE_URL|_ENDPOINT|_CA_BUNDLE|_CA_CERTS)$/i;
@@ -27404,6 +27706,8 @@ var ConfigSchema = external_exports.strictObject({
27404
27706
  network: NetworkSchema,
27405
27707
  vpn: VpnSchema,
27406
27708
  github: GithubSchema,
27709
+ accounts: AccountsSchema,
27710
+ pcInstalls: PcInstallsSchema,
27407
27711
  provisioning: ProvisioningSchema,
27408
27712
  downloads: DownloadsSchema,
27409
27713
  toolchains: ToolchainsSchema,
@@ -27456,6 +27760,8 @@ var LocalOverridesSchema = external_exports.strictObject({
27456
27760
  network: NetworkSchema.pick({ blockPrivateRanges: true, blockHosts: true }).partial(),
27457
27761
  // A project may turn the kill switch on, never off, and never choose a VPN, a server, a file or a login.
27458
27762
  vpn: VpnSchema.pick({ killSwitch: true }).partial(),
27763
+ // A project may only turn a sign-in off (policy: OFF_IS_SAFER); pcInstalls is never here (folders of this PC).
27764
+ accounts: external_exports.strictObject(import_lodash.default.mapValues(AccountsSchema.shape, (shape) => shape.partial())).partial(),
27459
27765
  provisioning: ProvisioningSchema.pick({ copyFromHost: true, fallbackToInstall: true, refresh: true }).extend({ git: GitSchema.partial(), terminal: toggle("Windows Terminal as the sandbox window.").partial() }).partial(),
27460
27766
  downloads: DownloadsSchema.partial(),
27461
27767
  // Every toolchain setting, each one optional (a project usually only switches languages on or pins a version).
@@ -27983,7 +28289,7 @@ var Downloader = class {
27983
28289
  */
27984
28290
  async file(url2, destination, expected = null) {
27985
28291
  const sums = typeof expected === "string" || expected === null ? { sha256: expected } : expected;
27986
- if (this.requireChecksum && !sums.sha256?.trim() && !sums.sha512?.trim()) {
28292
+ if (this.requireChecksum && !sums.sha256?.trim() && !sums.sha512?.trim() && !sums.sha1?.trim() && !sums.verify) {
27987
28293
  throw new Error(
27988
28294
  "Refusing to download " + path8.basename(destination) + " (" + url2 + "): no checksum is published for it, and downloads.requireChecksum is on. Turn that setting off to allow downloads that cannot be verified."
27989
28295
  );
@@ -27991,7 +28297,7 @@ var Downloader = class {
27991
28297
  const response = await fetchRetry(url2, {}, 3, 20 * 60 * 1e3);
27992
28298
  if (!response.body) throw new Error("Empty response for " + url2);
27993
28299
  fs7.mkdirSync(path8.dirname(destination), { recursive: true });
27994
- const checks = ["sha256", "sha512"].flatMap((kind) => {
28300
+ const checks = ["sha256", "sha512", "sha1"].flatMap((kind) => {
27995
28301
  const want = sums[kind]?.trim().toLowerCase();
27996
28302
  return want ? [{ want, hash: crypto2.createHash(kind) }] : [];
27997
28303
  });
@@ -28022,6 +28328,7 @@ var Downloader = class {
28022
28328
  try {
28023
28329
  await pipeline(Readable.fromWeb(response.body), tap, fs7.createWriteStream(partial2));
28024
28330
  if (checks.some((c) => c.hash.digest("hex") !== c.want)) throw new Error("Checksum mismatch for " + url2);
28331
+ if (sums.verify) await sums.verify(partial2);
28025
28332
  fs7.rmSync(destination, { force: true });
28026
28333
  fs7.renameSync(partial2, destination);
28027
28334
  } finally {
@@ -28036,7 +28343,12 @@ var Downloader = class {
28036
28343
  const work = stagingFolder(destination);
28037
28344
  try {
28038
28345
  const archive = path8.join(work, options.fileName ?? path8.basename(new URL(url2).pathname));
28039
- await this.file(url2, archive, { sha256: options.sha256, sha512: options.sha512 });
28346
+ await this.file(url2, archive, {
28347
+ sha256: options.sha256,
28348
+ sha512: options.sha512,
28349
+ sha1: options.sha1,
28350
+ ...options.verify ? { verify: options.verify } : {}
28351
+ });
28040
28352
  const extracted = path8.join(work, "x");
28041
28353
  await extractArchive(archive, extracted, options.members);
28042
28354
  let root = extracted;
@@ -2,9 +2,16 @@
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { AccountStore } from '../host/profile/index.js';
5
+ import { SecretStore } from '../host/secrets/secret-store.js';
5
6
  import { DEFAULTS } from '../core/config/index.js';
6
- import { defaultAccount } from '../app/features/accounts/index.js';
7
+ import { accountsPage, accountsText, agentPage, defaultAccount } from '../app/features/accounts/index.js';
8
+ import { fakeIO } from '../app/kernel/index.js';
9
+ import { runDefinition } from '../app/registry.js';
10
+ import { buildProgram, parseCli } from '../cli/args.js';
7
11
  import { MenuOperations } from '../cli/menu/io-menu.js';
12
+ import { menuView } from '../cli/menu/render-view.js';
13
+ import { cliInput, definitionFor } from '../cli/operations/index.js';
14
+ import { AccountsService } from '../cli/services/index.js';
8
15
  export const accountsSuite = {
9
16
  name: 'accounts',
10
17
  title: 'Claude accounts (save, switch, remove, safe names)',
@@ -101,6 +108,7 @@ export const accountsSuite = {
101
108
  const plugR = path.join(storage, 'data', 'p5', 'state', 'claude');
102
109
  on.syncPlugins(dataP('p5'));
103
110
  t.check(fs.existsSync(path.join(plugR, 'plugins', 'cache', 'new.js')), 'launch-time refresh: plugins installed in one data set reach the shared set and the next empty data set');
111
+ await apiKeyAccounts(t, f);
104
112
  t.section('removing and refusing');
105
113
  new AccountStore(storage).setDefault('work');
106
114
  new AccountStore(storage).remove('work');
@@ -114,3 +122,166 @@ export const accountsSuite = {
114
122
  await t.throws(() => new AccountStore(t.at('acct-storage')).save(otherData, 'con', fromHost), 'Invalid account name', 'an account cannot be named after a device (it would open a device)');
115
123
  },
116
124
  };
125
+ // Made-up keys with the shape of real ones (sk-ant-api03-...), each with its own last four characters.
126
+ const CONSOLE_KEY = 'sk-ant-api03-' + 'c'.repeat(80) + '-Cons';
127
+ const TYPED_KEY = 'sk-ant-api03-' + 't'.repeat(80) + '_Type';
128
+ const ENV_KEY = 'sk-ant-api03-' + 'e'.repeat(80) + '-Envy';
129
+ /** Every file below `root` whose text holds `needle` (relative paths). */
130
+ function filesHolding(root, needle) {
131
+ if (!fs.existsSync(root))
132
+ return [];
133
+ return fs
134
+ .readdirSync(root, { recursive: true, withFileTypes: true })
135
+ .filter((d) => d.isFile())
136
+ .map((d) => path.join(d.parentPath, d.name))
137
+ .filter((file) => fs.readFileSync(file, 'latin1').includes(needle))
138
+ .map((file) => path.relative(root, file).replace(/\\/g, '/'));
139
+ }
140
+ /** API key accounts: a Console login or a key, saved from the sandbox, this PC or typed, and switched to and back. */
141
+ async function apiKeyAccounts(t, f) {
142
+ t.section('API key accounts: saving');
143
+ const storage = t.at('key-storage');
144
+ const data = path.join(storage, 'data', 'k');
145
+ const claude = path.join(data, 'state', 'claude');
146
+ const store = new AccountStore(storage, false, SecretStore.memory());
147
+ t.write(path.join(claude, '.credentials.json'), '{"claudeAiOauth":{"refreshToken":"sub-1"},"mcpOAuth":{"m":1}}');
148
+ t.write(path.join(claude, '.claude.json'), '{"oauthAccount":{"emailAddress":"me@sub"},"other":1}');
149
+ store.save(data, 'sub', null);
150
+ // A Console login inside the sandbox: Claude keeps the key it made in .claude.json, no claudeAiOauth.
151
+ t.write(path.join(claude, '.credentials.json'), '{"mcpOAuth":{"m":1}}');
152
+ t.writeJson(path.join(claude, '.claude.json'), {
153
+ oauthAccount: { emailAddress: 'me@console' },
154
+ primaryApiKey: CONSOLE_KEY,
155
+ customApiKeyResponses: { approved: [CONSOLE_KEY.slice(-20)], rejected: [] },
156
+ other: 1,
157
+ });
158
+ const consoleSaved = store.save(data, 'console', null);
159
+ const consoleFile = fs.readFileSync(path.join(storage, 'accounts', 'console.json'), 'utf8');
160
+ t.check(consoleSaved.kind === 'console' &&
161
+ consoleSaved.email === 'me@console' &&
162
+ consoleSaved.keyHint === 'sk-ant-...Cons' &&
163
+ !consoleFile.includes(CONSOLE_KEY) &&
164
+ consoleFile.includes('sk-ant-...Cons') &&
165
+ store.activeFor(data) === 'console', "the sandbox's Console login is saved as a Console account: sealed, with only sk-ant-...last4 readable", consoleFile);
166
+ const typed = store.saveApiKey(data, 'typed', ' ' + TYPED_KEY + '\n');
167
+ t.check(typed.kind === 'api-key' && typed.keyHint === 'sk-ant-...Type' && store.activeFor(data) === 'console', 'a typed key is saved as an API key account (trimmed); it is not active until used');
168
+ for (const [bad, why] of [
169
+ ['hello', 'a word'],
170
+ ['sk-ant-oat01-' + 'x'.repeat(60), 'a subscription token'],
171
+ ['sk-ant-admin01-' + 'x'.repeat(60), 'an Admin key'],
172
+ ['sk-ant-api03-short', 'a short key'],
173
+ ['sk-ant-api03-' + 'x'.repeat(60) + '\u001b[31m', 'a key with control characters'],
174
+ ]) {
175
+ const message = await t.thrownMessage(() => store.saveApiKey(data, 'bad', bad));
176
+ t.check(message.includes('not an Anthropic API key') && !message.includes(bad.length > 13 ? bad.slice(13) : bad), 'a key with the wrong shape is refused without being quoted: ' + why, message);
177
+ }
178
+ // This PC: a Console key in its .claude.json, else ANTHROPIC_API_KEY; --api-key skips a subscription.
179
+ const pc = t.dir('key-pc', 'claude');
180
+ const pcState = t.at('key-pc', '.claude.json');
181
+ t.write(path.join(pc, '.credentials.json'), '{"claudeAiOauth":{"refreshToken":"pc-sub"}}');
182
+ t.write(pcState, '{"oauthAccount":{"emailAddress":"me@pc"}}');
183
+ const pcFiles = { claudeDir: pc, globalStateFile: pcState };
184
+ t.equal(store.save(data, 'pc-sub', pcFiles, { envKey: ENV_KEY }).kind, 'subscription', 'PC: a subscription first');
185
+ const pcKey = store.save(data, 'pc-env', pcFiles, { keyOnly: true, envKey: ENV_KEY });
186
+ t.check(pcKey.kind === 'api-key' && pcKey.email === null && store.read('pc-env')?.apiKey === ENV_KEY, "PC --api-key: this PC's ANTHROPIC_API_KEY is saved (its subscription is skipped)");
187
+ t.writeJson(pcState, { oauthAccount: { emailAddress: 'me@pc-console' }, primaryApiKey: CONSOLE_KEY });
188
+ t.equal(store.save(data, 'pc-console', pcFiles, { keyOnly: true, envKey: ENV_KEY }).kind, 'console', "PC --api-key: this PC's Console login comes before ANTHROPIC_API_KEY");
189
+ await t.throws(() => store.save(data, 'none', { claudeDir: t.dir('key-empty'), globalStateFile: t.at('key-empty', 'x.json') }, { keyOnly: true }), 'No Claude API key found', 'no key anywhere: says so (and how to type one)');
190
+ // The sandbox is hostile: a planted primaryApiKey that is not a key is never saved.
191
+ const hostileData = path.join(storage, 'data', 'h');
192
+ const hostile = path.join(hostileData, 'state', 'claude');
193
+ t.writeJson(path.join(hostile, '.claude.json'), {
194
+ primaryApiKey: 'sk-ant-api03-' + 'x'.repeat(40) + '\u001b]52;c;evil\u0007',
195
+ });
196
+ await t.throws(() => store.save(hostileData, 'hostile', null), 'No Claude login found', 'a primaryApiKey from the sandbox without the shape of a key is refused');
197
+ t.section('API key accounts: switching and back');
198
+ t.write(path.join(claude, '.credentials.json'), '{"claudeAiOauth":{"refreshToken":"console-made"},"mcpOAuth":{"m":2}}');
199
+ store.activate(data, 'sub');
200
+ const subState = f.readJson(path.join(claude, '.claude.json'));
201
+ const subCreds = f.readJson(path.join(claude, '.credentials.json'));
202
+ t.check(!('primaryApiKey' in subState) &&
203
+ !subState.customApiKeyResponses.approved.includes(CONSOLE_KEY.slice(-20)) &&
204
+ subState.oauthAccount.emailAddress === 'me@sub' &&
205
+ subState.other === 1 &&
206
+ subCreds.claudeAiOauth.refreshToken === 'sub-1', 'back to a subscription: the key and its approval leave the sandbox, the subscription is in again', JSON.stringify(subState));
207
+ t.check(store.read('console')?.kind === 'console' && store.read('console')?.apiKey === CONSOLE_KEY, 'a key account is never overwritten with what the sandbox holds when switching away from it');
208
+ t.write(path.join(claude, '.credentials.json'), '{"claudeAiOauth":{"refreshToken":"sub-2"},"mcpOAuth":{"m":3}}');
209
+ store.activate(data, 'typed');
210
+ const keyStateNow = f.readJson(path.join(claude, '.claude.json'));
211
+ const keyCreds = f.readJson(path.join(claude, '.credentials.json'));
212
+ t.check(keyStateNow.primaryApiKey === TYPED_KEY &&
213
+ keyStateNow.customApiKeyResponses.approved.includes(TYPED_KEY.slice(-20)) &&
214
+ !keyStateNow.customApiKeyResponses.rejected.includes(TYPED_KEY.slice(-20)) &&
215
+ !('oauthAccount' in keyStateNow) &&
216
+ keyStateNow.other === 1 &&
217
+ !('claudeAiOauth' in keyCreds) &&
218
+ keyCreds.mcpOAuth.m === 3, 'to an API key: the key goes where a Console login keeps it, pre-approved (no prompt); the subscription leaves, MCP logins stay', JSON.stringify({ keyStateNow, keyCreds }));
219
+ t.includes(JSON.stringify(store.read('sub')?.credentials), 'sub-2', "the outgoing subscription's refreshed tokens are still saved back");
220
+ t.equal(filesHolding(storage, TYPED_KEY), ['data/k/state/claude/.claude.json'], "the key is in the sandbox's Claude state only: not in the account files, the index, run\\ or logs");
221
+ store.activate(data, 'console');
222
+ const consoleState = f.readJson(path.join(claude, '.claude.json'));
223
+ t.check(consoleState.primaryApiKey === CONSOLE_KEY &&
224
+ !consoleState.customApiKeyResponses.approved.includes(TYPED_KEY.slice(-20)) &&
225
+ consoleState.oauthAccount.emailAddress === 'me@console' &&
226
+ fs.existsSync(path.join(claude, '.credentials.json')), 'key to key: the new key replaces the old one and its approval; the Console account brings its e-mail');
227
+ t.section('API key accounts: listing, renaming, removing');
228
+ const rows = store.list(data);
229
+ const kindOf = (n) => rows.find((r) => r.name === n)?.kind;
230
+ t.check(kindOf('sub') === 'subscription' && kindOf('console') === 'console' && kindOf('typed') === 'api-key', 'list says the kind of every Claude account');
231
+ const text = accountsText({
232
+ agent: 'claude',
233
+ agents: [{ name: 'claude', kept: true, canSavePc: true, accounts: rows }],
234
+ services: [],
235
+ });
236
+ t.check(text.includes('KIND') &&
237
+ text.includes('API key sk-ant-...Type') &&
238
+ text.includes('Console sk-ant-...Cons') &&
239
+ text.includes('subscription') &&
240
+ ![TYPED_KEY, CONSOLE_KEY, ENV_KEY].some((k) => text.includes(k.slice(13, 40))), 'the command line table shows the kind and sk-ant-...last4, never the key', text);
241
+ const claudeRows = { name: 'claude', kept: true, canSavePc: true, accounts: rows };
242
+ const menu = menuView(agentPage(claudeRows), { category: 'claude', refresh: false });
243
+ const page = JSON.stringify(accountsPage({ agent: 'claude', agents: [claudeRows], services: [] }));
244
+ t.check(menu.actions.some((a) => a.key === 'k') &&
245
+ page.includes('+ Save an API key') &&
246
+ page.includes('"API key"') &&
247
+ page.includes('Console sk-ant-...Cons') &&
248
+ !page.includes(TYPED_KEY.slice(13, 40)), 'menu and web: a Save an API key row (k), and each login says its kind');
249
+ store.rename('typed', 'free');
250
+ t.check(store.read('free')?.apiKey === TYPED_KEY && store.meta('free')?.kind === 'api-key' && store.meta('typed') === null, 'rename keeps the key and its kind');
251
+ store.remove('free');
252
+ t.equal(filesHolding(path.join(storage, 'accounts'), 'sk-ant-...Type'), [], 'remove deletes the key account');
253
+ t.section('API key accounts: the operation and the command line');
254
+ const ctx = {
255
+ packageRoot: t.dir('key-op', 'package'),
256
+ options: {},
257
+ loaded: { config: structuredClone(DEFAULTS), configPath: null, base: t.temp },
258
+ storageRoot: t.dir('key-op', 'storage'),
259
+ dataPath: t.dir('key-op', 'storage', 'data', 'o'),
260
+ };
261
+ const asked = fakeIO(['sk-ant-api03-nope', TYPED_KEY]);
262
+ const out = (await runDefinition('accounts.save', ctx, { agent: 'claude', name: 'typed', apiKey: true }, asked.io));
263
+ const said = JSON.stringify([out, asked.seen]);
264
+ t.check(asked.seen.questions.length === 2 &&
265
+ asked.seen.questions.every((q) => q.kind === 'secret') &&
266
+ asked.seen.questions[1].problem?.includes('sk-ant-api') === true &&
267
+ out.message === 'Saved account "typed" (API key sk-ant-...Type).' &&
268
+ !said.includes(TYPED_KEY.slice(13)), 'save --api-key asks for the key hidden, asks again on a bad one, and says only sk-ant-...last4', said);
269
+ await t.throws(() => runDefinition('accounts.save', ctx, { agent: 'codex', name: 'x', apiKey: true }, fakeIO([]).io), 'for Claude accounts only', 'save --api-key is refused for another agent');
270
+ t.write(t.at('key-op', 'pc', '.credentials.json'), '{"claudeAiOauth":{"refreshToken":"pc"}}');
271
+ ctx.loaded.config.claudeProfile.hostDir = t.at('key-op', 'pc');
272
+ ctx.loaded.config.claudeProfile.globalStateFile = t.at('key-op', 'pc', '.claude.json');
273
+ const pcSaved = new AccountsService(ctx, { ANTHROPIC_API_KEY: ENV_KEY }).save('pc-key', true, 'claude', true);
274
+ t.check(pcSaved.kind === 'api-key' && pcSaved.keyHint === 'sk-ant-...Envy', "save --from-host --api-key takes this PC's ANTHROPIC_API_KEY");
275
+ const used = (await runDefinition('accounts.use', ctx, { agent: 'claude', name: 'pc-key', noRestart: true }, fakeIO([]).io));
276
+ t.check(used.lines[0] === 'Sandbox now uses "pc-key" (API key sk-ant-...Envy).' &&
277
+ f.readJson(path.join(ctx.dataPath, 'state', 'claude', '.claude.json')).primaryApiKey === ENV_KEY, 'use switches the sandbox to the key and says which kind it is', used.lines.join('\n'));
278
+ const save = definitionFor('account', ['save']);
279
+ const help = buildProgram(() => undefined)
280
+ .commands.find((c) => c.name() === 'account')
281
+ .helpInformation();
282
+ t.check(JSON.stringify(cliInput(save, parseCli(['account', 'save', 'free', '--api-key']))) ===
283
+ JSON.stringify({ name: 'free', apiKey: true }) &&
284
+ JSON.stringify(cliInput(save, parseCli(['account', 'save', 'free', '--api-key', '--from-host']))) ===
285
+ JSON.stringify({ name: 'free', fromHost: true, apiKey: true }) &&
286
+ help.includes('--api-key'), 'command line: sandbox account save <name> --api-key [--from-host], shown in --help', help);
287
+ }
@@ -55,6 +55,7 @@ export const agentPathsSuite = {
55
55
  'C:\\AgentData\\state\\codex\\.sandbox-secrets',
56
56
  'C:\\AgentData\\state\\codex\\.sandbox\\setup_marker.json',
57
57
  'C:\\AgentData\\state\\codex\\.sandbox\\deny_read_acl_state.json',
58
+ 'C:\\AgentData\\state\\codex\\tmp\\arg0',
58
59
  ], "codex's DPAPI-sealed sandbox users and setup marker are removed at boot (CryptUnprotectData otherwise)");
59
60
  t.check(HarnessSchema.safeParse({ ...DEFAULTS.harnesses.codex, bootReset: ['..\\login'] }).success === false &&
60
61
  HarnessSchema.safeParse({ ...DEFAULTS.harnesses.codex, bootReset: ['C:\\Windows'] }).success === false, 'bootReset takes only plain paths below the agent state folder');
@@ -0,0 +1,2 @@
1
+ import type { Suite } from './support/suite.js';
2
+ export declare const androidSuite: Suite;