windows-development-sandbox 1.27.0 → 1.27.3

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 (93) hide show
  1. package/dist/app/context.d.ts +5 -0
  2. package/dist/app/context.js +13 -0
  3. package/dist/app/features/accounts/accounts.def.d.ts +16 -0
  4. package/dist/app/features/accounts/accounts.run.d.ts +4 -0
  5. package/dist/app/features/accounts/services.def.d.ts +15 -1
  6. package/dist/app/features/accounts/services.def.js +67 -31
  7. package/dist/app/features/accounts/services.run.d.ts +7 -2
  8. package/dist/app/features/accounts/services.run.js +25 -6
  9. package/dist/app/features/defs.d.ts +17 -0
  10. package/dist/app/features/unreal/unreal.run.js +13 -3
  11. package/dist/app/registry.d.ts +1 -1
  12. package/dist/app/registry.js +15 -2
  13. package/dist/bin/agent-sandbox.js +2 -2
  14. package/dist/bin/{chunk-JXL6ZOO3.js → chunk-4M5SZZ5P.js} +46 -41
  15. package/dist/bin/{chunk-ZXCAVVS4.js → chunk-FA5VWLP4.js} +1 -1
  16. package/dist/bin/{chunk-6WWMBCT2.js → chunk-RU7XRN62.js} +303 -112
  17. package/dist/bin/claude-host.js +2 -2
  18. package/dist/bin/claude-sbx.js +2 -2
  19. package/dist/bin/codex-sbx.js +2 -2
  20. package/dist/bin/gemini-sbx.js +2 -2
  21. package/dist/bin/sandbox.js +2 -2
  22. package/dist/bin/sbx-hide.js +2 -2
  23. package/dist/bin/sbx-show.js +2 -2
  24. package/dist/cli/commands/launch/launch.js +2 -1
  25. package/dist/cli/menu/menu.js +6 -1
  26. package/dist/cli/menu/pages/accounts-page.js +9 -5
  27. package/dist/guest/apps/app-installer.js +7 -5
  28. package/dist/guest/apps/comet-unpack.js +2 -2
  29. package/dist/guest/runtime/pools.d.ts +4 -4
  30. package/dist/guest/runtime/pools.js +4 -15
  31. package/dist/guest/runtime/process.d.ts +5 -2
  32. package/dist/guest/runtime/process.js +23 -3
  33. package/dist/guest/setup/unity-agent.js +4 -1
  34. package/dist/guest/toolchains/engines/epic-launcher.d.ts +7 -1
  35. package/dist/guest/toolchains/engines/epic-launcher.js +30 -8
  36. package/dist/guest/toolchains/engines/unreal-engines.js +2 -1
  37. package/dist/guest/toolchains/engines/unreal-status.js +1 -1
  38. package/dist/guest/toolchains/installers.d.ts +3 -3
  39. package/dist/guest/toolchains/installers.js +5 -5
  40. package/dist/guest/toolchains/native/mesa.js +35 -11
  41. package/dist/guest/toolchains/native/unreal.js +1 -0
  42. package/dist/guest/toolchains/native/vcruntime.js +2 -2
  43. package/dist/guest/toolchains/native/vs-persist.d.ts +2 -2
  44. package/dist/guest/toolchains/signature.d.ts +2 -0
  45. package/dist/guest/toolchains/signature.js +9 -2
  46. package/dist/guest/toolchains/step-scheduler.js +1 -1
  47. package/dist/host/accounts/deliver.d.ts +3 -0
  48. package/dist/host/accounts/deliver.js +27 -0
  49. package/dist/host/accounts/source.d.ts +2 -0
  50. package/dist/host/accounts/source.js +2 -0
  51. package/dist/host/pc-installs/discover.js +8 -4
  52. package/dist/host/pc-installs/epic.js +3 -0
  53. package/dist/host/pc-installs/unity.d.ts +2 -0
  54. package/dist/host/pc-installs/unity.js +12 -3
  55. package/dist/host/pc-installs/unreal.d.ts +5 -1
  56. package/dist/host/pc-installs/unreal.js +51 -9
  57. package/dist/runtime/agent.js +34 -22
  58. package/dist/runtime/daemon.js +134 -65
  59. package/dist/runtime/db.js +32 -16
  60. package/dist/runtime/main.js +136 -67
  61. package/dist/runtime/netproxy.js +26 -12
  62. package/dist/runtime/tool.js +32 -15
  63. package/dist/runtime/view.js +26 -12
  64. package/dist/selftest/app.test.js +20 -2
  65. package/dist/selftest/boot-runner.test.js +15 -1
  66. package/dist/selftest/engines.test.js +52 -2
  67. package/dist/selftest/menu-screens.js +52 -1
  68. package/dist/selftest/menu.test.js +29 -3
  69. package/dist/selftest/mixamo.test.js +1 -1
  70. package/dist/selftest/service-accounts.test.js +88 -2
  71. package/dist/selftest/ui-text.test.js +4 -0
  72. package/dist/shared/accounts/kinds.js +9 -9
  73. package/dist/shared/apps/catalogue-gamedev.js +1 -0
  74. package/dist/shared/apps/catalogue.d.ts +2 -0
  75. package/dist/shared/concurrency/pools.js +1 -1
  76. package/dist/shared/download/archive.js +4 -2
  77. package/dist/shared/download/index.d.ts +1 -1
  78. package/dist/shared/download/index.js +1 -1
  79. package/dist/shared/download/spawn.d.ts +7 -1
  80. package/dist/shared/download/spawn.js +16 -1
  81. package/dist/shared/paths/pc-installs.d.ts +8 -0
  82. package/dist/shared/unreal/launcher-files.d.ts +7 -0
  83. package/dist/shared/unreal/launcher-files.js +32 -1
  84. package/dist/shared/unreal/status.d.ts +1 -0
  85. package/dist/shared/unreal/status.js +2 -0
  86. package/dist/web/index.html +1 -1
  87. package/dist/web/{main-VPMYTSUL.js → main-NKM7ECSB.js} +59 -59
  88. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  89. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  90. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  91. package/package.json +1 -1
  92. package/schemas/config.schema.json +9 -9
  93. package/schemas/sandbox-config.schema.json +9 -9
@@ -22806,10 +22806,10 @@ var esm_default2 = createPrompt((config3, done) => {
22806
22806
  const answer = theme.style.answer(theme.style.renderSelectedChoices(selection, items));
22807
22807
  return [prefix, message, answer].filter(Boolean).join(" ");
22808
22808
  }
22809
- let helpLine;
22809
+ let helpLine2;
22810
22810
  if (theme.helpMode !== "never" && instructions !== false) {
22811
22811
  if (typeof instructions === "string") {
22812
- helpLine = instructions;
22812
+ helpLine2 = instructions;
22813
22813
  } else {
22814
22814
  const keys = [
22815
22815
  ["\u2191\u2193", "navigate"],
@@ -22820,7 +22820,7 @@ var esm_default2 = createPrompt((config3, done) => {
22820
22820
  if (shortcuts.invert)
22821
22821
  keys.push([shortcuts.invert, "invert"]);
22822
22822
  keys.push(["\u23CE", "submit"]);
22823
- helpLine = theme.style.keysHelpTip(keys);
22823
+ helpLine2 = theme.style.keysHelpTip(keys);
22824
22824
  }
22825
22825
  }
22826
22826
  const lines = [
@@ -22829,7 +22829,7 @@ var esm_default2 = createPrompt((config3, done) => {
22829
22829
  " ",
22830
22830
  description ? theme.style.description(description) : "",
22831
22831
  errorMsg ? theme.style.error(errorMsg) : "",
22832
- helpLine
22832
+ helpLine2
22833
22833
  ].filter(Boolean).join("\n").trimEnd();
22834
22834
  return `${lines}${cursorHide}`;
22835
22835
  });
@@ -23687,13 +23687,13 @@ var esm_default10 = createPrompt((config3, done) => {
23687
23687
  }
23688
23688
  });
23689
23689
  const message = theme.style.message(config3.message, status);
23690
- let helpLine;
23690
+ let helpLine2;
23691
23691
  if (theme.helpMode !== "never") {
23692
23692
  if (config3.instructions) {
23693
23693
  const { pager, navigation } = config3.instructions;
23694
- helpLine = theme.style.help(searchResults.length > pageSize ? pager : navigation);
23694
+ helpLine2 = theme.style.help(searchResults.length > pageSize ? pager : navigation);
23695
23695
  } else {
23696
- helpLine = theme.style.keysHelpTip([
23696
+ helpLine2 = theme.style.keysHelpTip([
23697
23697
  ["\u2191\u2193", "navigate"],
23698
23698
  ["\u23CE", "select"]
23699
23699
  ]);
@@ -23735,7 +23735,7 @@ var esm_default10 = createPrompt((config3, done) => {
23735
23735
  error62 ?? page,
23736
23736
  " ",
23737
23737
  description ? theme.style.description(description) : "",
23738
- helpLine
23738
+ helpLine2
23739
23739
  ].filter(Boolean).join("\n").trimEnd();
23740
23740
  return [header, body];
23741
23741
  });
@@ -23857,13 +23857,13 @@ var esm_default11 = createPrompt((config3, done) => {
23857
23857
  clearTimeout(searchTimeoutRef.current);
23858
23858
  }, []);
23859
23859
  const message = theme.style.message(config3.message, status);
23860
- let helpLine;
23860
+ let helpLine2;
23861
23861
  if (theme.helpMode !== "never") {
23862
23862
  if (config3.instructions) {
23863
23863
  const { pager, navigation } = config3.instructions;
23864
- helpLine = theme.style.help(items.length > pageSize ? pager : navigation);
23864
+ helpLine2 = theme.style.help(items.length > pageSize ? pager : navigation);
23865
23865
  } else {
23866
- helpLine = theme.style.keysHelpTip([
23866
+ helpLine2 = theme.style.keysHelpTip([
23867
23867
  ["\u2191\u2193", "navigate"],
23868
23868
  ["\u23CE", "select"]
23869
23869
  ]);
@@ -23899,7 +23899,7 @@ var esm_default11 = createPrompt((config3, done) => {
23899
23899
  page,
23900
23900
  " ",
23901
23901
  description ? theme.style.description(description) : "",
23902
- helpLine
23902
+ helpLine2
23903
23903
  ].filter(Boolean).join("\n").trimEnd();
23904
23904
  return `${lines}${cursorHide}`;
23905
23905
  });
@@ -46814,6 +46814,7 @@ var GAMEDEV_APPS = [
46814
46814
  size: "100 MB",
46815
46815
  source: { github: "f3d-app/f3d", asset: "^F3D-[\\d.]+-Windows-x86_64\\.zip$" },
46816
46816
  exe: "f3d.exe",
46817
+ onPath: true,
46817
46818
  needs: ["mesa"]
46818
46819
  },
46819
46820
  {
@@ -47961,7 +47962,7 @@ var SERVICE_SPECS = {
47961
47962
  },
47962
47963
  steam: {
47963
47964
  kind: "steam",
47964
- label: "Steam (SteamCMD)",
47965
+ label: "Steam",
47965
47966
  usedBy: ["steam"],
47966
47967
  sources: ["file", "sandbox"],
47967
47968
  singleHolder: true,
@@ -47997,7 +47998,7 @@ var SERVICE_SPECS = {
47997
47998
  },
47998
47999
  cfx: {
47999
48000
  kind: "cfx",
48000
- label: "Cfx.re (FiveM / RedM server key)",
48001
+ label: "FiveM key",
48001
48002
  usedBy: ["fivem"],
48002
48003
  sources: ["typed", "file"],
48003
48004
  singleHolder: false,
@@ -48026,7 +48027,7 @@ var SERVICE_SPECS = {
48026
48027
  },
48027
48028
  keystore: {
48028
48029
  kind: "keystore",
48029
- label: "Android upload keystore",
48030
+ label: "Android keystore",
48030
48031
  usedBy: ["android"],
48031
48032
  sources: ["file"],
48032
48033
  singleHolder: false,
@@ -48041,7 +48042,7 @@ var SERVICE_SPECS = {
48041
48042
  },
48042
48043
  play: {
48043
48044
  kind: "play",
48044
- label: "Google Play (service account)",
48045
+ label: "Google Play",
48045
48046
  usedBy: ["android"],
48046
48047
  sources: ["file"],
48047
48048
  singleHolder: false,
@@ -48052,7 +48053,7 @@ var SERVICE_SPECS = {
48052
48053
  },
48053
48054
  expo: {
48054
48055
  kind: "expo",
48055
- label: "Expo (EAS)",
48056
+ label: "Expo",
48056
48057
  usedBy: ["reactNative"],
48057
48058
  sources: ["typed"],
48058
48059
  singleHolder: false,
@@ -48063,7 +48064,7 @@ var SERVICE_SPECS = {
48063
48064
  },
48064
48065
  appstore: {
48065
48066
  kind: "appstore",
48066
- label: "App Store Connect API key",
48067
+ label: "App Store Connect",
48067
48068
  usedBy: ["reactNative", "flutter"],
48068
48069
  sources: ["file", "typed"],
48069
48070
  singleHolder: false,
@@ -48077,7 +48078,7 @@ var SERVICE_SPECS = {
48077
48078
  },
48078
48079
  perplexity: {
48079
48080
  kind: "perplexity",
48080
- label: "Perplexity API key",
48081
+ label: "Perplexity API",
48081
48082
  usedBy: ["perplexity"],
48082
48083
  sources: ["pc", "file", "typed"],
48083
48084
  singleHolder: false,
@@ -48101,7 +48102,7 @@ var SERVICE_SPECS = {
48101
48102
  },
48102
48103
  comet: {
48103
48104
  kind: "comet",
48104
- label: "Comet (Perplexity browser)",
48105
+ label: "Comet",
48105
48106
  usedBy: ["comet"],
48106
48107
  sources: ["sandbox"],
48107
48108
  singleHolder: false,
@@ -48117,7 +48118,7 @@ var SERVICE_SPECS = {
48117
48118
  },
48118
48119
  perplexityapp: {
48119
48120
  kind: "perplexityapp",
48120
- label: "Perplexity desktop app",
48121
+ label: "Perplexity app",
48121
48122
  usedBy: ["perplexityapp"],
48122
48123
  sources: ["sandbox"],
48123
48124
  singleHolder: false,
@@ -56560,7 +56561,7 @@ function clearLiveRecord(storageRoot, dataPath) {
56560
56561
  import fs71 from "node:fs";
56561
56562
  import path94 from "node:path";
56562
56563
  import crypto14 from "node:crypto";
56563
- import os16 from "node:os";
56564
+ import os17 from "node:os";
56564
56565
 
56565
56566
  // src/shared/accounts/fingerprint.ts
56566
56567
  import crypto8 from "node:crypto";
@@ -58861,10 +58862,20 @@ function scriptArgs(name2, args = []) {
58861
58862
  }
58862
58863
 
58863
58864
  // src/shared/download/spawn.ts
58865
+ import os14 from "node:os";
58864
58866
  import { spawn as spawn9 } from "node:child_process";
58867
+ function lowerPriority(pid) {
58868
+ if (pid === void 0) return;
58869
+ try {
58870
+ os14.setPriority(pid, os14.constants.priority.PRIORITY_BELOW_NORMAL);
58871
+ } catch {
58872
+ }
58873
+ }
58865
58874
  function spawnAsync(file2, args, options = {}) {
58875
+ const { lowPriority, ...spawnOptions } = options;
58866
58876
  return new Promise((resolve, reject) => {
58867
- const child = spawn9(file2, args, { stdio: "ignore", windowsHide: true, ...options });
58877
+ const child = spawn9(file2, args, { stdio: "ignore", windowsHide: true, ...spawnOptions });
58878
+ if (lowPriority) lowerPriority(child.pid);
58868
58879
  child.once("error", reject);
58869
58880
  child.once("close", (code) => resolve(code ?? -1));
58870
58881
  });
@@ -58885,13 +58896,16 @@ function extractArchive(archive, destination, members2 = []) {
58885
58896
  }
58886
58897
  async function extract(archive, destination, members2) {
58887
58898
  fs57.mkdirSync(destination, { recursive: true });
58888
- let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2]);
58899
+ let status = await spawnAsync(SYSTEM.tar(), ["-xf", archive, "-C", destination, ...members2], {
58900
+ lowPriority: true
58901
+ });
58889
58902
  if (status !== 0 && /\.(zip|nupkg)$/i.test(archive)) {
58890
58903
  const zip = archive.endsWith(".zip") ? archive : archive + ".zip";
58891
58904
  if (zip !== archive) fs57.copyFileSync(archive, zip);
58892
58905
  status = await spawnAsync(
58893
58906
  SYSTEM.powershell(),
58894
- scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination])
58907
+ scriptArgs("expand-archive.ps1", ["-Archive", zip, "-Destination", destination]),
58908
+ { lowPriority: true }
58895
58909
  );
58896
58910
  }
58897
58911
  if (status !== 0) throw new Error("Could not extract " + archive);
@@ -59789,10 +59803,10 @@ function pcHasPwsh(env = process.env, exists2 = fs60.existsSync) {
59789
59803
 
59790
59804
  // src/host/launch/pc-scan.ts
59791
59805
  import fs61 from "node:fs";
59792
- import os14 from "node:os";
59806
+ import os15 from "node:os";
59793
59807
  import path84 from "node:path";
59794
59808
  function msvcRust() {
59795
- const settings = path84.join(process.env.RUSTUP_HOME ?? path84.join(os14.homedir(), ".rustup"), "settings.toml");
59809
+ const settings = path84.join(process.env.RUSTUP_HOME ?? path84.join(os15.homedir(), ".rustup"), "settings.toml");
59796
59810
  try {
59797
59811
  return /default_host_triple\s*=\s*"[^"]*-msvc"/.test(fs61.readFileSync(settings, "utf8").slice(0, 64 * 1024));
59798
59812
  } catch {
@@ -59959,7 +59973,7 @@ function databasePlanner(config3, compose2) {
59959
59973
  import path89 from "node:path";
59960
59974
 
59961
59975
  // src/host/launch/wsb-file.ts
59962
- import os15 from "node:os";
59976
+ import os16 from "node:os";
59963
59977
  var GUEST = {
59964
59978
  launch: "C:\\AgentLaunch",
59965
59979
  data: "C:\\AgentData",
@@ -60002,7 +60016,7 @@ function memoryNote(decision) {
60002
60016
  if (decision.limitedBy !== "commit" || decision.withoutCommitMb - decision.mb < 2048) return null;
60003
60017
  return "The sandbox gets " + decision.mb / 1024 + " GB of memory instead of " + decision.withoutCommitMb / 1024 + " GB: this PC has only " + Math.floor((decision.commitAvailableMb ?? 0) / 1024) + " GB of memory (RAM plus paging file) left to hand out, and if it runs out Windows closes the sandbox. A bigger Windows paging file, or closing other programs, gives it more.";
60004
60018
  }
60005
- function resolveMemoryMb(sandbox, freeMemoryBytes = os15.freemem(), totalMemoryBytes = os15.totalmem(), commitAvailableMb = null) {
60019
+ function resolveMemoryMb(sandbox, freeMemoryBytes = os16.freemem(), totalMemoryBytes = os16.totalmem(), commitAvailableMb = null) {
60006
60020
  if (sandbox.memoryMb !== "auto") return sandbox.memoryMb;
60007
60021
  const decision = decideMemory(
60008
60022
  sandbox,
@@ -60915,7 +60929,7 @@ function generateSandbox(given, opened) {
60915
60929
  resolveMemoryMb(config3.sandbox, o.freeMemoryBytes, void 0, commitAvailableMb),
60916
60930
  ...others.map((p) => typeof p.config.sandbox.memoryMb === "number" ? p.config.sandbox.memoryMb : 0)
60917
60931
  );
60918
- const memoryMb = cappedMemoryMb(askedMb, config3.sandbox, Math.floor(os16.totalmem() / 1048576));
60932
+ const memoryMb = cappedMemoryMb(askedMb, config3.sandbox, Math.floor(os17.totalmem() / 1048576));
60919
60933
  const launchId = o.launchId ?? newLaunchId();
60920
60934
  const launchPath = path94.join(storageRoot, "launches", launchId);
60921
60935
  ensureDir(dataPath);
@@ -60988,8 +61002,8 @@ function generateSandbox(given, opened) {
60988
61002
  memoryNote: residentNote(askedMb, memoryMb) ?? (config3.sandbox.memoryMb === "auto" ? memoryNote(
60989
61003
  decideMemory(
60990
61004
  config3.sandbox,
60991
- Math.floor((o.freeMemoryBytes ?? os16.freemem()) / 1048576),
60992
- Math.floor(os16.totalmem() / 1048576),
61005
+ Math.floor((o.freeMemoryBytes ?? os17.freemem()) / 1048576),
61006
+ Math.floor(os17.totalmem() / 1048576),
60993
61007
  commitAvailableMb
60994
61008
  )
60995
61009
  ) : null),
@@ -62605,7 +62619,8 @@ function pcEnv() {
62605
62619
  programData: env.ProgramData ?? env.PROGRAMDATA ?? "C:\\ProgramData",
62606
62620
  programFiles: env.ProgramFiles ?? "C:\\Program Files",
62607
62621
  env,
62608
- userVariable: savedUserVariable
62622
+ userVariable: savedUserVariable,
62623
+ drives: () => diskUsage(env.ProgramData ?? "C:\\").map((d) => d.drive)
62609
62624
  };
62610
62625
  }
62611
62626
  function notAvailableYet(kind) {
@@ -62926,6 +62941,21 @@ function howToSave(spec) {
62926
62941
  if (spec.returns) return "sign in once in this sandbox, or: sandbox account import " + spec.kind;
62927
62942
  return spec.sources.includes("typed") ? "save one: sandbox account login " + spec.kind : "save one: sandbox account import " + spec.kind + " --from <file>";
62928
62943
  }
62944
+ function takeFromPc(kind, pc, store, saved) {
62945
+ const source = HOST_SOURCES[kind];
62946
+ if (!pc || !source.fromPc) return saved;
62947
+ if (saved && store.info(kind)?.source !== "pc") return saved;
62948
+ let payload = null;
62949
+ try {
62950
+ payload = source.fromPc(pc);
62951
+ } catch {
62952
+ return saved;
62953
+ }
62954
+ if (!payload) return saved;
62955
+ if (!saved || fingerprint(payload) !== fingerprint(saved))
62956
+ store.save(kind, payload, "pc", source.summary(payload));
62957
+ return payload;
62958
+ }
62929
62959
  function prepareServiceAccounts(i) {
62930
62960
  const store = i.store ?? new ServiceAccountStore(i.storageRoot);
62931
62961
  const leases = i.leases ?? new AccountLeases(i.storageRoot);
@@ -62955,6 +62985,7 @@ function prepareServiceAccounts(i) {
62955
62985
  } catch {
62956
62986
  unreadable = true;
62957
62987
  }
62988
+ if (!unreadable) payload = takeFromPc(kind, i.pc, store, payload);
62958
62989
  if (spec.singleHolder) {
62959
62990
  const claim2 = leases.claim(kind, dataSet, i.launchId, payload ? fingerprint(payload) : null, i.isRunning);
62960
62991
  if (!claim2.ok) {
@@ -63164,9 +63195,6 @@ async function prepareStorePackages(apps, o) {
63164
63195
  return { notes };
63165
63196
  }
63166
63197
 
63167
- // src/host/pc-installs/epic.ts
63168
- import path117 from "node:path";
63169
-
63170
63198
  // src/shared/unreal/engines.ts
63171
63199
  import path113 from "node:path";
63172
63200
  var win = path113.win32;
@@ -63212,6 +63240,9 @@ var stripBom2 = (text) => text.replace(/^\uFEFF/, "");
63212
63240
  function launcherInstalledFile(programData) {
63213
63241
  return win2.join(programData, "Epic", "UnrealEngineLauncher", "LauncherInstalled.dat");
63214
63242
  }
63243
+ function launcherManifestsDir(programData) {
63244
+ return win2.join(programData, "Epic", "EpicGamesLauncher", "Data", "Manifests");
63245
+ }
63215
63246
  var EntrySchema = external_exports.looseObject({
63216
63247
  InstallLocation: external_exports.string().max(1024),
63217
63248
  AppName: external_exports.string().max(200),
@@ -63247,6 +63278,41 @@ var ItemSchema = external_exports.looseObject({
63247
63278
  InstallSize: external_exports.number().int().min(0).optional(),
63248
63279
  bIsIncompleteInstall: external_exports.boolean().optional()
63249
63280
  });
63281
+ function itemInstallLocation(text) {
63282
+ return itemInfo(text)?.installLocation ?? null;
63283
+ }
63284
+ function itemInfo(text) {
63285
+ try {
63286
+ const item = ItemSchema.parse(JSON.parse(stripBom2(text)));
63287
+ const at = item.InstallLocation;
63288
+ return at && win2.isAbsolute(at) ? { installLocation: at, sizeBytes: item.InstallSize ?? null } : null;
63289
+ } catch {
63290
+ return null;
63291
+ }
63292
+ }
63293
+ var ITEM_NAME = /^[A-Za-z0-9_-]{1,80}\.item$/;
63294
+ function relocateItem(text, from, to) {
63295
+ let item;
63296
+ try {
63297
+ item = JSON.parse(stripBom2(text));
63298
+ } catch {
63299
+ return null;
63300
+ }
63301
+ if (!item || typeof item !== "object" || Array.isArray(item)) return null;
63302
+ const base = win2.resolve(from).toLowerCase();
63303
+ const out = {};
63304
+ for (const [key3, value] of Object.entries(item)) {
63305
+ if (typeof value !== "string" || !win2.isAbsolute(value)) {
63306
+ out[key3] = value;
63307
+ continue;
63308
+ }
63309
+ const full = win2.resolve(value);
63310
+ const low = full.toLowerCase();
63311
+ if (low === base || low.startsWith(base + "\\")) out[key3] = to + full.slice(base.length);
63312
+ }
63313
+ const at = typeof out.InstallLocation === "string" ? out.InstallLocation : "";
63314
+ return at.toLowerCase() === to.toLowerCase() ? JSON.stringify(out, null, " ") : null;
63315
+ }
63250
63316
 
63251
63317
  // src/shared/unreal/status.ts
63252
63318
  var UNREAL_STATUS_FILE = "status.json";
@@ -63275,6 +63341,8 @@ var UnrealStatusSchema = external_exports.object({
63275
63341
  ).max(64),
63276
63342
  launcher: external_exports.object({
63277
63343
  installed: external_exports.boolean(),
63344
+ /** Its program (Program Files or Program Files (x86), by launcher version); null when there is none. */
63345
+ exe: Text3().nullable().default(null),
63278
63346
  /** Where the launcher installs engines; null when it could not be set. */
63279
63347
  installLocation: Text3().nullable(),
63280
63348
  /** Why engines would not go to the data disk (shown loudly), null when they do. */
@@ -63388,12 +63456,14 @@ function vaultPacks(vault, fs141) {
63388
63456
  }
63389
63457
 
63390
63458
  // src/host/pc-installs/epic.ts
63459
+ import path117 from "node:path";
63391
63460
  var APP = /^[A-Za-z0-9._-]{1,100}$/;
63392
63461
  function findEpicGames(text, wanted) {
63393
63462
  const names = wanted === true ? null : new Set(wanted.map((w) => String(w).toLowerCase()));
63394
63463
  const found = [];
63395
63464
  for (const i of text ? launcherInstallations(text) : []) {
63396
63465
  if (!APP.test(i.appName) || /^\.+$/.test(i.appName) || /^UE_\d/.test(i.appName)) continue;
63466
+ if (i.ids?.namespaceId.toLowerCase() === "ue" || /^UE_\d/i.test(path117.win32.basename(i.installLocation))) continue;
63397
63467
  if (names && !names.has(i.appName.toLowerCase())) continue;
63398
63468
  if (found.some((f) => f.id.toLowerCase() === i.appName.toLowerCase())) continue;
63399
63469
  found.push({
@@ -63804,10 +63874,12 @@ function listedEditors(files) {
63804
63874
  }
63805
63875
  function findUnityEditorsOnPc(files, wanted) {
63806
63876
  const candidates = [];
63807
- const hubFolders = [win8.join(files.programFiles, "Unity", "Hub", "Editor"), secondaryRoot(files)].filter(
63808
- (d) => d !== null
63809
- );
63810
- for (const folder of hubFolders)
63877
+ const hubFolders = [
63878
+ win8.join(files.programFiles, "Unity", "Hub", "Editor"),
63879
+ secondaryRoot(files),
63880
+ ...files.otherHubFolders ?? []
63881
+ ].filter((d) => d !== null);
63882
+ for (const folder of new Set(hubFolders.map((d) => win8.resolve(d))))
63811
63883
  for (const name2 of files.list(folder).slice(0, MAX_LISTED).sort())
63812
63884
  if (UNITY_VERSION.test(name2)) candidates.push({ version: name2, root: win8.join(folder, name2) });
63813
63885
  candidates.push(...listedEditors(files));
@@ -63853,7 +63925,11 @@ function unityPcFiles(pc) {
63853
63925
  isDir,
63854
63926
  list: listDir,
63855
63927
  appData: pc.appData,
63856
- programFiles: pc.programFiles ?? "C:\\Program Files"
63928
+ programFiles: pc.programFiles ?? "C:\\Program Files",
63929
+ otherHubFolders: (pc.drives?.() ?? []).flatMap((d) => [
63930
+ win8.join(d, "Program Files", "Unity", "Hub", "Editor"),
63931
+ win8.join(d, "Unity", "Hub", "Editor")
63932
+ ])
63857
63933
  };
63858
63934
  }
63859
63935
  function assetCacheEnv(pc) {
@@ -63902,6 +63978,25 @@ var versionOf = (dir, files) => {
63902
63978
  const bytes = files.read(win9.join(dir, "Engine", "Build", "Build.version"), 64 * 1024);
63903
63979
  return bytes ? parseBuildVersion(bytes.toString("utf8")) : null;
63904
63980
  };
63981
+ var MAX_ITEM_BYTES2 = 64 * 1024;
63982
+ function launcherItemOf(dir, files) {
63983
+ if (!files.manifests) return {};
63984
+ const want = win9.resolve(dir).toLowerCase();
63985
+ for (const name2 of files.list(files.manifests).filter((n) => ITEM_NAME.test(n))) {
63986
+ const bytes = files.read(win9.join(files.manifests, name2), MAX_ITEM_BYTES2);
63987
+ const at = bytes ? itemInstallLocation(bytes.toString("utf8")) : null;
63988
+ if (bytes && at && win9.resolve(at).toLowerCase() === want)
63989
+ return { launcherItem: { name: name2, text: bytes.toString("utf8") } };
63990
+ }
63991
+ return {};
63992
+ }
63993
+ function launcherIdsOf(dir, files) {
63994
+ const want = win9.resolve(dir).toLowerCase();
63995
+ const entry = (files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : []).find(
63996
+ (i) => APP_NAME.test(i.appName) && i.ids && win9.resolve(i.installLocation).toLowerCase() === want
63997
+ );
63998
+ return entry?.ids ? { launcher: entry.ids } : {};
63999
+ }
63905
64000
  var isWanted = (wanted, v) => wanted === true || wanted.map((w) => String(w).replace(/^UE_/i, "")).includes(v.major + "." + v.minor);
63906
64001
  function findUnrealInstalls(files, wanted) {
63907
64002
  const found = [];
@@ -63921,13 +64016,21 @@ function findUnrealInstalls(files, wanted) {
63921
64016
  version: versionText(v),
63922
64017
  host: win9.normalize(dir),
63923
64018
  guest: "",
63924
- ...ids ? { launcher: ids } : {}
64019
+ ...ids ? { launcher: ids } : {},
64020
+ ...launcherItemOf(dir, files)
63925
64021
  });
63926
64022
  };
63927
- for (const i of files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : [])
63928
- if (APP_NAME.test(i.appName)) installed3(i.installLocation, i.ids ?? void 0);
63929
- for (const name2 of files.list(files.defaultRoot).filter((n) => APP_NAME.test(n)).sort())
63930
- installed3(win9.join(files.defaultRoot, name2));
64023
+ const listed = files.launcherInstalled ? launcherInstallations(files.launcherInstalled) : [];
64024
+ for (const i of listed) if (APP_NAME.test(i.appName)) installed3(i.installLocation, i.ids ?? void 0);
64025
+ const roots = [files.defaultRoot, ...files.otherRoots ?? [], ...listed.map((i) => win9.dirname(i.installLocation))];
64026
+ const seen = /* @__PURE__ */ new Set();
64027
+ for (const root of roots) {
64028
+ const key3 = win9.resolve(root).toLowerCase();
64029
+ if (seen.has(key3) || seen.size >= 32) continue;
64030
+ seen.add(key3);
64031
+ for (const name2 of files.list(root).filter((n) => APP_NAME.test(n)).sort())
64032
+ installed3(win9.join(root, name2));
64033
+ }
63931
64034
  return found;
63932
64035
  }
63933
64036
  function pcSourceBuilds(files) {
@@ -63959,7 +64062,10 @@ function findEngineFolders(files, folders) {
63959
64062
  host: dir,
63960
64063
  guest: "",
63961
64064
  writable: true,
63962
- ...source ? { buildId: source.buildId } : {}
64065
+ ...source ? { buildId: source.buildId } : {},
64066
+ // An engine the launcher installed keeps its ids and manifest, so the sandbox's launcher lists it too.
64067
+ ...launcherIdsOf(dir, { launcherInstalled: files.launcherInstalled ?? null }),
64068
+ ...files.list ? launcherItemOf(dir, { manifests: files.manifests, list: files.list, read: files.read }) : {}
63963
64069
  });
63964
64070
  }
63965
64071
  return found;
@@ -63983,6 +64089,11 @@ function unrealPcFiles(pc) {
63983
64089
  launcherInstalled: dat ? dat.toString("utf8") : null,
63984
64090
  builds: query.status === 0 ? String(query.stdout ?? "") : "",
63985
64091
  defaultRoot: win9.join(pc.programFiles ?? "C:\\Program Files", "Epic Games"),
64092
+ otherRoots: (pc.drives?.() ?? []).flatMap((d) => [
64093
+ win9.join(d, "Program Files", "Epic Games"),
64094
+ win9.join(d, "Epic Games")
64095
+ ]),
64096
+ manifests: launcherManifestsDir(pc.programData),
63986
64097
  list: listDir2,
63987
64098
  read: readBoundedFile
63988
64099
  };
@@ -64066,11 +64177,11 @@ var unrealVault = {
64066
64177
  // src/host/pc-installs/discover.ts
64067
64178
  var MAX_PC_INSTALLS = 40;
64068
64179
  var PC_INSTALL_SOURCES = [
64069
- steamInstalls,
64070
- epicInstalls,
64071
64180
  unrealFolders,
64072
64181
  unrealInstalls,
64073
64182
  unrealVault,
64183
+ steamInstalls,
64184
+ epicInstalls,
64074
64185
  unityInstalls,
64075
64186
  unityAssets
64076
64187
  ];
@@ -64121,8 +64232,9 @@ function discoverPcInstalls(s3, o, pc = pcEnv()) {
64121
64232
  readOnly: !writable,
64122
64233
  label: writable ? "this PC, writable" : "this PC, read-only"
64123
64234
  });
64124
- const { host: _host, ...shown } = install;
64125
- items.push({ ...shown, guest });
64235
+ const { host: _host, launcherItem, ...shown } = install;
64236
+ const moved = launcherItem ? relocateItem(launcherItem.text, install.host, guest) : null;
64237
+ items.push({ ...shown, guest, ...moved ? { launcherItem: { name: launcherItem.name, text: moved } } : {} });
64126
64238
  } catch (e) {
64127
64239
  notes.push("pcInstalls: " + install.name + " is not mapped: " + errMsg(e));
64128
64240
  }
@@ -64832,7 +64944,7 @@ var SandboxChannel = class {
64832
64944
 
64833
64945
  // src/host/channel/attach.ts
64834
64946
  import net4 from "node:net";
64835
- import os17 from "node:os";
64947
+ import os18 from "node:os";
64836
64948
 
64837
64949
  // src/shared/pty/protocol.ts
64838
64950
  var FrameType = {
@@ -65478,7 +65590,7 @@ function runTerminalClient(socket, token, options = {}) {
65478
65590
 
65479
65591
  // src/host/channel/attach.ts
65480
65592
  var ipToInt = (ip) => ip.split(".").reduce((n, part) => n * 256 + Number(part), 0);
65481
- function isSandboxAddress(ip, interfaces = os17.networkInterfaces()) {
65593
+ function isSandboxAddress(ip, interfaces = os18.networkInterfaces()) {
65482
65594
  if (!net4.isIPv4(ip)) return false;
65483
65595
  return Object.entries(interfaces).some(
65484
65596
  ([name2, list2]) => /^vEthernet/i.test(name2) && !/WSL/i.test(name2) && (list2 ?? []).some((i) => {
@@ -66319,7 +66431,7 @@ var AuditCollector = class {
66319
66431
  };
66320
66432
 
66321
66433
  // src/sync/watcher.ts
66322
- import os18 from "node:os";
66434
+ import os19 from "node:os";
66323
66435
  var WATCH_INTERVAL_MS = 60 * 1e3;
66324
66436
  var FIRST_SYNC_MS = 3e4;
66325
66437
  var AUDIT_FLUSH_MS = 1e4;
@@ -66378,7 +66490,7 @@ var Watcher = class {
66378
66490
  memoryStream = null;
66379
66491
  watchdog = WATCHDOG_START;
66380
66492
  watchdogBusy = false;
66381
- lastCpu = os18.cpus();
66493
+ lastCpu = os19.cpus();
66382
66494
  lastHostLog = 0;
66383
66495
  /** Runs until the sandbox has closed and the final sync is done (or the end deadline passed). */
66384
66496
  async run() {
@@ -66498,7 +66610,7 @@ var Watcher = class {
66498
66610
  try {
66499
66611
  const settings = readLoadSettings(this.storageRoot, this.dataPath);
66500
66612
  const memory = this.memoryStream?.latest() ?? await readHostMemoryAsync(this.storageRoot);
66501
- const cpus = os18.cpus();
66613
+ const cpus = os19.cpus();
66502
66614
  const cpuPercent = cpuPercentBetween(this.lastCpu, cpus);
66503
66615
  this.lastCpu = cpus;
66504
66616
  const nowMs = Date.now();
@@ -69223,7 +69335,7 @@ function continuesAfterFailedMigration(command2) {
69223
69335
 
69224
69336
  // src/cli/services/update-service.ts
69225
69337
  import fs112 from "node:fs";
69226
- import os19 from "node:os";
69338
+ import os20 from "node:os";
69227
69339
  import path156 from "node:path";
69228
69340
 
69229
69341
  // src/host/update/release-source.ts
@@ -69658,7 +69770,7 @@ var Updater = class {
69658
69770
  var NPM_UPDATE_COMMAND = "npm install -g windows-development-sandbox";
69659
69771
  var AUTO_CHECK_HOURS = 24;
69660
69772
  var SeenSchema = external_exports.object({ seen: external_exports.string() });
69661
- function runsAsSystem(env = process.env, username = os19.userInfo().username) {
69773
+ function runsAsSystem(env = process.env, username = os20.userInfo().username) {
69662
69774
  return username.toUpperCase() === "SYSTEM" || (env.USERNAME ?? "").endsWith("$");
69663
69775
  }
69664
69776
  var UpdateService = class _UpdateService {
@@ -70281,7 +70393,7 @@ import { spawnSync as spawnSync29 } from "node:child_process";
70281
70393
 
70282
70394
  // src/cli/commands/setup/facts.ts
70283
70395
  var import_lodash27 = __toESM(require_lodash(), 1);
70284
- import os20 from "node:os";
70396
+ import os21 from "node:os";
70285
70397
  var FEATURE = "Containers-DisposableClientVM";
70286
70398
  var ENABLE_COMMAND = "Enable-WindowsOptionalFeature -Online -FeatureName " + FEATURE + " -All -NoRestart";
70287
70399
  function parseRegQuery(text) {
@@ -70329,7 +70441,7 @@ function gatherFacts(storageRoot) {
70329
70441
  sandboxExe: fileExists(system32("WindowsSandbox.exe")),
70330
70442
  virtualizationFirmware: probe.virtualizationFirmware ?? null,
70331
70443
  hypervisorPresent: probe.hypervisorPresent ?? null,
70332
- totalRamBytes: os20.totalmem(),
70444
+ totalRamBytes: os21.totalmem(),
70333
70445
  freeDiskBytes: freeBytesAt(storageRoot),
70334
70446
  storageRoot,
70335
70447
  nodeVersion: process.version
@@ -71347,6 +71459,12 @@ import path162 from "node:path";
71347
71459
 
71348
71460
  // src/app/context.ts
71349
71461
  var implement = implementer();
71462
+ function reloadSettings(ctx) {
71463
+ try {
71464
+ ctx.loaded = loadConfig(ctx.loaded.configPath ?? ctx.options.config);
71465
+ } catch {
71466
+ }
71467
+ }
71350
71468
  function typedConfirm(io) {
71351
71469
  return (prompt, expected) => io.confirm({
71352
71470
  title: prompt,
@@ -73054,13 +73172,13 @@ function fingerprintOf(der) {
73054
73172
  // src/host/remote/firewall.ts
73055
73173
  import crypto22 from "node:crypto";
73056
73174
  import fs119 from "node:fs";
73057
- import os22 from "node:os";
73175
+ import os23 from "node:os";
73058
73176
  import path168 from "node:path";
73059
73177
  import { spawn as spawn13 } from "node:child_process";
73060
73178
 
73061
73179
  // src/host/remote/lan.ts
73062
73180
  import net7 from "node:net";
73063
- import os21 from "node:os";
73181
+ import os22 from "node:os";
73064
73182
  var toInt = (ip) => ip.split(".").reduce((n, part) => n * 256 + Number(part), 0) >>> 0;
73065
73183
  function isPrivateIPv4(ip) {
73066
73184
  if (!net7.isIPv4(ip)) return false;
@@ -73077,7 +73195,7 @@ function plainIPv4(ip) {
73077
73195
  function virtualAdapter(name2) {
73078
73196
  return /^vEthernet/i.test(name2) || /Hyper-V|WSL/i.test(name2);
73079
73197
  }
73080
- function privateAddresses(interfaces = os21.networkInterfaces()) {
73198
+ function privateAddresses(interfaces = os22.networkInterfaces()) {
73081
73199
  const out = [];
73082
73200
  for (const [adapter, list2] of Object.entries(interfaces)) {
73083
73201
  if (virtualAdapter(adapter)) continue;
@@ -73087,7 +73205,7 @@ function privateAddresses(interfaces = os21.networkInterfaces()) {
73087
73205
  }
73088
73206
  return out;
73089
73207
  }
73090
- function chooseLanAddress(configured, interfaces = os21.networkInterfaces()) {
73208
+ function chooseLanAddress(configured, interfaces = os22.networkInterfaces()) {
73091
73209
  const own2 = privateAddresses(interfaces);
73092
73210
  if (configured) {
73093
73211
  if (!isPrivateIPv4(configured))
@@ -73115,7 +73233,7 @@ function sameSubnet(a, b, netmask) {
73115
73233
  const mask = toInt(netmask);
73116
73234
  return mask !== 0 && (toInt(a) & mask) === (toInt(b) & mask);
73117
73235
  }
73118
- function lanPeerProblem(peer, local, bound, interfaces = os21.networkInterfaces()) {
73236
+ function lanPeerProblem(peer, local, bound, interfaces = os22.networkInterfaces()) {
73119
73237
  const ip = plainIPv4(peer);
73120
73238
  if (plainIPv4(local) !== bound.address) return "reached another address of this PC";
73121
73239
  if (!isPrivateIPv4(ip)) return "not a private address";
@@ -73166,7 +73284,7 @@ var OutcomeSchema3 = external_exports.object({
73166
73284
  commands: external_exports.array(external_exports.string().max(1e3)).max(4).optional()
73167
73285
  });
73168
73286
  async function runScript2(request2, dryRun, run3) {
73169
- const dir = fs119.mkdtempSync(path168.join(os22.tmpdir(), "agent-sandbox-firewall-"));
73287
+ const dir = fs119.mkdtempSync(path168.join(os23.tmpdir(), "agent-sandbox-firewall-"));
73170
73288
  try {
73171
73289
  const file2 = path168.join(dir, "request.json");
73172
73290
  fs119.writeFileSync(file2, JSON.stringify(request2, null, 2), "utf8");
@@ -74759,7 +74877,7 @@ var RemoteGate = class {
74759
74877
  };
74760
74878
 
74761
74879
  // src/host/remote/tls-store.ts
74762
- import os23 from "node:os";
74880
+ import os24 from "node:os";
74763
74881
  import path171 from "node:path";
74764
74882
  var TlsFileSchema = external_exports.strictObject({
74765
74883
  cert: external_exports.string().max(8e3),
@@ -74801,7 +74919,7 @@ var TlsStore = class {
74801
74919
  }
74802
74920
  /** A new certificate (the old one is gone). */
74803
74921
  renew(addresses) {
74804
- const made = makeCertificate({ addresses, dnsNames: [os23.hostname()], now: this.now() });
74922
+ const made = makeCertificate({ addresses, dnsNames: [os24.hostname()], now: this.now() });
74805
74923
  this.file.write({
74806
74924
  cert: made.cert,
74807
74925
  key: this.secrets.seal(made.key),
@@ -76297,12 +76415,16 @@ var ServiceRowSchema = external_exports.object({
76297
76415
  canImport: external_exports.boolean().describe("It can be taken from this PC or a file (sandbox account import)"),
76298
76416
  canLogin: external_exports.boolean().describe("It can be typed (sandbox account login)"),
76299
76417
  needsFile: external_exports.boolean().describe("Importing it needs --from <file>"),
76418
+ fromPc: external_exports.boolean().describe("This PC's own program can give it (the next launch takes it when sharing is on)"),
76419
+ hasFileSource: external_exports.boolean().describe("It can be read from a file you name"),
76420
+ onPc: external_exports.boolean().nullable().describe("This PC has one to take (null: it cannot be taken from this PC). Only the presence, never the sign-in"),
76421
+ sandboxOnly: external_exports.boolean().describe("Only a sandbox can give it: sign in once inside one and it is kept"),
76300
76422
  ready: external_exports.boolean().describe("Its sign-in is written; false: the menu and the web leave it out (the CLI says coming)")
76301
76423
  });
76302
76424
  var ServicesListSchema = external_exports.object({ services: external_exports.array(ServiceRowSchema) });
76303
76425
  var STATE_TEXT = {
76304
76426
  none: "not saved",
76305
- saved: "saved (encrypted)",
76427
+ saved: "saved",
76306
76428
  unreadable: "cannot be read"
76307
76429
  };
76308
76430
  var STATE_TONE = {
@@ -76311,10 +76433,10 @@ var STATE_TONE = {
76311
76433
  unreadable: "danger"
76312
76434
  };
76313
76435
  var SOURCE_TEXT = {
76314
- pc: "taken from this PC",
76436
+ pc: "from this PC",
76315
76437
  file: "from a file",
76316
- typed: "typed in",
76317
- sandbox: "signed in (or renewed) in a sandbox"
76438
+ typed: "by typing it",
76439
+ sandbox: "from a sandbox"
76318
76440
  };
76319
76441
  var day = (iso) => iso ? iso.slice(0, 10) : "";
76320
76442
  function servicesText(o) {
@@ -76347,7 +76469,8 @@ var serviceImport = defineOperation({
76347
76469
  summary: "save a service sign-in from this PC's program or a file, for sandboxes",
76348
76470
  input: external_exports.object({
76349
76471
  name: KindSchema,
76350
- from: external_exports.string().max(1e3).optional().describe("(import) the file or folder to take the sign-in from")
76472
+ from: external_exports.string().max(1e3).optional().describe("(import) the file or folder to take the sign-in from"),
76473
+ file: external_exports.boolean().optional().describe("(import) ask for a file even when this PC has one to take")
76351
76474
  }),
76352
76475
  output: MessageSchema2,
76353
76476
  text: (o) => o.message,
@@ -76376,6 +76499,12 @@ var SERVICE_OPERATIONS = [serviceImport, serviceLogin, serviceLogout];
76376
76499
  function serviceCategory(kind) {
76377
76500
  return "service:" + kind;
76378
76501
  }
76502
+ var PC_HELP = {
76503
+ epic: "Sign in to the Epic launcher here, with Remember me on.",
76504
+ steam: "Log in once with SteamCMD in C:\\steamcmd.",
76505
+ unity: "Sign in to the Unity Hub here.",
76506
+ perplexity: "Set PERPLEXITY_API_KEY for your Windows user."
76507
+ };
76379
76508
  function serviceActions(s3) {
76380
76509
  return [
76381
76510
  action(
@@ -76388,51 +76517,75 @@ function serviceActions(s3) {
76388
76517
  importance: 1
76389
76518
  }
76390
76519
  ),
76391
- ...s3.canImport ? [action(serviceImport, { name: s3.kind }, { key: "i", verb: "import", importance: 1 })] : [],
76392
- ...s3.canLogin ? [action(serviceLogin, { name: s3.kind }, { key: "n", verb: "sign in", importance: 1 })] : [],
76520
+ ...s3.fromPc ? [
76521
+ action(
76522
+ serviceImport,
76523
+ { name: s3.kind },
76524
+ { key: "i", verb: "take from this PC", label: "Take this PC's sign-in", importance: 1 }
76525
+ )
76526
+ ] : [],
76527
+ ...s3.hasFileSource ? [
76528
+ action(
76529
+ serviceImport,
76530
+ { name: s3.kind, file: true },
76531
+ {
76532
+ key: s3.fromPc ? "f" : "i",
76533
+ verb: "import a file",
76534
+ label: "Import from a file\u2026",
76535
+ importance: s3.fromPc ? 2 : 1
76536
+ }
76537
+ )
76538
+ ] : [],
76539
+ ...s3.canLogin ? [
76540
+ action(
76541
+ serviceLogin,
76542
+ { name: s3.kind },
76543
+ { key: "n", verb: "type it in", label: "Type the sign-in\u2026", importance: 1 }
76544
+ )
76545
+ ] : [],
76393
76546
  ...s3.state !== "none" ? [action(serviceLogout, { name: s3.kind }, { key: "x", verb: "forget", importance: 2 })] : []
76394
76547
  ];
76395
76548
  }
76396
- function howTo(s3) {
76397
- const ways = [...s3.canImport ? ["import it"] : [], ...s3.canLogin ? ["type it in"] : []];
76398
- return ways.length ? ways.join(" or ") : "sign in once inside a sandbox (it can't be copied from this PC: Windows locks it to your PC account)";
76549
+ function helpLine(s3) {
76550
+ if (s3.state !== "none") return void 0;
76551
+ if (s3.sandboxOnly) return "Sign in once inside a sandbox; it is kept for the next ones.";
76552
+ if (s3.fromPc && s3.onPc)
76553
+ return s3.share ? "The next sandbox takes this PC's." : "Share it and the next sandbox takes this PC's.";
76554
+ if (s3.fromPc) return PC_HELP[s3.kind] ?? "Not found on this PC.";
76555
+ return s3.canLogin ? "Type it in, or import it from a file." : "Import it from a file.";
76399
76556
  }
76400
76557
  function serviceSection(s3) {
76401
76558
  const facts2 = [
76402
76559
  {
76403
76560
  id: s3.kind + ":share",
76404
- label: "Shared with sandboxes",
76561
+ label: "Shared",
76405
76562
  value: s3.share ? "on" : "off",
76406
76563
  tone: s3.share ? "ok" : "muted",
76407
- description: "For sandboxes that use " + s3.usedFor + ". " + s3.warnings.join(" ")
76564
+ description: "The agent can use it."
76408
76565
  },
76409
76566
  {
76410
76567
  id: s3.kind + ":state",
76411
76568
  label: "Sign-in",
76412
76569
  value: STATE_TEXT[s3.state] + (s3.savedAt ? " " + day(s3.savedAt) : ""),
76413
76570
  tone: STATE_TONE[s3.state],
76414
- description: s3.state === "none" ? "Save one: " + howTo(s3) + "." : void 0
76571
+ description: s3.state === "unreadable" ? "Only the Windows user that saved it can read it: save it again." : helpLine(s3) ?? (s3.source ? "Saved " + (SOURCE_TEXT[s3.source] ?? s3.source) + "." : void 0)
76415
76572
  },
76416
- ...s3.source ? [{ id: s3.kind + ":source", label: "From", value: SOURCE_TEXT[s3.source] ?? s3.source }] : [],
76573
+ ...s3.onPc === null ? [] : [
76574
+ {
76575
+ id: s3.kind + ":pc",
76576
+ label: "On this PC",
76577
+ value: s3.onPc ? "found" : "not found",
76578
+ tone: s3.onPc ? "ok" : "muted"
76579
+ }
76580
+ ],
76417
76581
  ...s3.summary ? [{ id: s3.kind + ":summary", label: "Account", value: s3.summary }] : [],
76418
76582
  ...s3.heldBy ? [{ id: s3.kind + ":held", label: "In use by", value: "sandbox " + s3.heldBy, tone: "accent" }] : []
76419
76583
  ];
76420
- const warnings = s3.warnings.map((w, i) => ({
76421
- id: s3.kind + ":warning:" + i,
76422
- label: w,
76423
- tone: "warn",
76424
- group: "Before you share it"
76425
- }));
76426
- const notes = [
76427
- ...s3.state === "unreadable" ? [{ text: "Only the Windows user that saved it can read it: save it again.", tone: "muted" }] : []
76428
- ];
76429
76584
  return {
76430
76585
  id: serviceCategory(s3.kind),
76431
76586
  title: s3.label,
76432
76587
  summary: (s3.share ? "shared" : "not shared") + " \xB7 " + STATE_TEXT[s3.state],
76433
76588
  facts: facts2,
76434
- items: warnings,
76435
- notes,
76436
76589
  actions: serviceActions(s3)
76437
76590
  };
76438
76591
  }
@@ -76747,7 +76900,17 @@ var accountsView = defineView({
76747
76900
  // src/app/features/accounts/services.run.ts
76748
76901
  import fs124 from "node:fs";
76749
76902
  import path174 from "node:path";
76750
- function serviceRow(kind, config3, store, leases) {
76903
+ function presentOnPc(kind, pc) {
76904
+ const source = HOST_SOURCES[kind];
76905
+ if (!source.fromPc) return null;
76906
+ if (!pc) return false;
76907
+ try {
76908
+ return source.fromPc(pc) !== null;
76909
+ } catch {
76910
+ return false;
76911
+ }
76912
+ }
76913
+ function serviceRow(kind, config3, store, leases, pc) {
76751
76914
  const spec = SERVICE_SPECS[kind];
76752
76915
  const source = HOST_SOURCES[kind];
76753
76916
  const state = store.state(kind);
@@ -76768,6 +76931,10 @@ function serviceRow(kind, config3, store, leases) {
76768
76931
  canImport: Boolean(source.fromPc || source.fromFile),
76769
76932
  canLogin: Boolean(source.fromAnswers),
76770
76933
  needsFile: Boolean(source.fromFile && !source.fromPc),
76934
+ fromPc: Boolean(source.fromPc),
76935
+ hasFileSource: Boolean(source.fromFile),
76936
+ onPc: presentOnPc(kind, pc),
76937
+ sandboxOnly: !source.fromPc && !source.fromFile && !source.fromAnswers,
76771
76938
  ready: isKindReady(kind)
76772
76939
  };
76773
76940
  }
@@ -76815,7 +76982,7 @@ function refuseSandboxOnly(spec) {
76815
76982
  function savedMessage(kind, share2) {
76816
76983
  return "Saved your " + SERVICE_SPECS[kind].label + " sign-in (encrypted for your Windows user)." + (share2 ? "" : " Sandboxes get it once you turn on Share with sandboxes.");
76817
76984
  }
76818
- async function importServiceFlow(io, kind, from, deps) {
76985
+ async function importServiceFlow(io, kind, from, deps, wantFile = false) {
76819
76986
  const spec = SERVICE_SPECS[kind];
76820
76987
  const source = HOST_SOURCES[kind];
76821
76988
  if (!source.fromPc && !source.fromFile) refuseSandboxOnly(spec);
@@ -76823,7 +76990,7 @@ async function importServiceFlow(io, kind, from, deps) {
76823
76990
  failWith(spec.label + " cannot be imported.", "Type it instead.", {
76824
76991
  op: "accounts.service.login",
76825
76992
  input: { name: kind },
76826
- label: "Sign in\u2026",
76993
+ label: "Type the sign-in\u2026",
76827
76994
  cli: "sandbox account login " + kind
76828
76995
  });
76829
76996
  let file2 = from === void 0 ? void 0 : path174.resolve(from);
@@ -76834,7 +77001,7 @@ async function importServiceFlow(io, kind, from, deps) {
76834
77001
  return false;
76835
77002
  }
76836
77003
  };
76837
- const useFile = Boolean(source.fromFile && (file2 ? isFile(file2) : !source.fromPc));
77004
+ const useFile = Boolean(source.fromFile && (file2 ? isFile(file2) : wantFile || !source.fromPc));
76838
77005
  if (useFile && !file2) {
76839
77006
  const asked = await io.path({ title: "The " + spec.label + " file:", pathKind: "file" });
76840
77007
  if (asked === void 0) cancelled();
@@ -76911,7 +77078,8 @@ var listServicesRun = implement(listServices, async (ctx, input2) => {
76911
77078
  if (name2 !== void 0 && name2 !== "" && !isServiceKind(name2))
76912
77079
  fail('There is no service called "' + safeText(input2.name ?? "") + '".', "Use one of: " + SERVICE_KINDS.join(", "));
76913
77080
  const kinds = name2 ? [name2] : [...SERVICE_KINDS];
76914
- return { services: kinds.map((k) => serviceRow(k, ctx.loaded.config, store, leases)) };
77081
+ const pc = pcEnv();
77082
+ return { services: kinds.map((k) => serviceRow(k, ctx.loaded.config, store, leases, pc)) };
76915
77083
  });
76916
77084
  var serviceImportRun = implement(serviceImport, async (ctx, input2, io) => {
76917
77085
  requireOnPc("account import");
@@ -76921,11 +77089,17 @@ var serviceImportRun = implement(serviceImport, async (ctx, input2, io) => {
76921
77089
  "import",
76922
77090
  (s3) => Boolean(HOST_SOURCES[s3.kind].fromPc || HOST_SOURCES[s3.kind].fromFile)
76923
77091
  );
76924
- return importServiceFlow(io, kind, input2.from, {
76925
- store: new ServiceAccountStore(ctx.storageRoot),
76926
- pc: pcEnv(),
76927
- share: ctx.loaded.config.accounts[kind].share
76928
- });
77092
+ return importServiceFlow(
77093
+ io,
77094
+ kind,
77095
+ input2.from,
77096
+ {
77097
+ store: new ServiceAccountStore(ctx.storageRoot),
77098
+ pc: pcEnv(),
77099
+ share: ctx.loaded.config.accounts[kind].share
77100
+ },
77101
+ input2.file ?? false
77102
+ );
76929
77103
  });
76930
77104
  var serviceLoginRun = implement(serviceLogin, async (ctx, input2, io) => {
76931
77105
  requireOnPc("account login");
@@ -83285,7 +83459,7 @@ var vpnLogoutRun = implement(vpnLogout, async (ctx, _input, io) => {
83285
83459
  });
83286
83460
 
83287
83461
  // src/app/features/github/github.run.ts
83288
- import os24 from "node:os";
83462
+ import os25 from "node:os";
83289
83463
  async function saveGithubTokenFlow(io, store, verify = verifyGithubToken) {
83290
83464
  io.log("Make a GitHub token only for sandboxes:");
83291
83465
  GITHUB_STEPS.forEach((s3, i) => io.log(" " + (i + 1) + ". " + s3));
@@ -83346,7 +83520,7 @@ var githubStatusRun = implement(githubStatus, async (ctx) => {
83346
83520
  expiresAt: token === "none" ? null : store.check()?.expiresAt ?? null,
83347
83521
  repoCount: token === "none" ? null : store.check()?.repoCount ?? null,
83348
83522
  checkedAt: token === "none" ? null : store.check()?.checkedAt ?? null,
83349
- tokenUrl: tokenUrlFor(os24.hostname())
83523
+ tokenUrl: tokenUrlFor(os25.hostname())
83350
83524
  };
83351
83525
  });
83352
83526
  var githubLoginRun = implement(githubLogin, async (ctx, _input, io) => {
@@ -83686,7 +83860,10 @@ var unrealView = defineView({
83686
83860
  // src/app/features/unreal/unreal.run.ts
83687
83861
  import path192 from "node:path";
83688
83862
  var win10 = path192.win32;
83689
- var GUEST_LAUNCHER = "C:\\Program Files (x86)\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe";
83863
+ var GUEST_LAUNCHERS = [
83864
+ "C:\\Program Files\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe",
83865
+ "C:\\Program Files (x86)\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe"
83866
+ ];
83690
83867
  var MAX_PACKS_SHOWN = 40;
83691
83868
  var s = (text) => safeText(text);
83692
83869
  var sn = (text) => text === null ? null : safeText(text);
@@ -83848,14 +84025,19 @@ var openEpicLauncherRun = implement(openEpicLauncher, async (ctx, _input, io) =>
83848
84025
  fail("No sandbox is running.", "Start one for a project with a .uproject; the launcher opens in its window.");
83849
84026
  const status = new RunDir(ctx.dataPath).readSandboxStatus();
83850
84027
  if (status?.phase !== "ready") fail("The sandbox is not ready yet.", "Try again in a moment.");
83851
- if (!readStatus(ctx.dataPath)?.launcher.installed)
84028
+ const launcher = readStatus(ctx.dataPath)?.launcher;
84029
+ if (!launcher?.installed)
83852
84030
  fail(
83853
84031
  "The Epic Games Launcher is not installed in this sandbox.",
83854
84032
  "It is installed when the sandbox is set up for Unreal with the licence accepted (Unreal set-up settings)."
83855
84033
  );
83856
84034
  io.progress("Opening the Epic Games Launcher in the sandbox\u2026");
83857
84035
  const result = await new SandboxChannel(ctx.dataPath).exec(
83858
- ["C:\\Windows\\explorer.exe", GUEST_LAUNCHER],
84036
+ // The guest's status only picks one of the known paths.
84037
+ [
84038
+ "C:\\Windows\\explorer.exe",
84039
+ GUEST_LAUNCHERS.find((p) => p.toLowerCase() === launcher.exe?.toLowerCase()) ?? GUEST_LAUNCHERS[0]
84040
+ ],
83859
84041
  status.guestWorkspace ?? "C:\\",
83860
84042
  30
83861
84043
  );
@@ -84694,7 +84876,14 @@ function implementationOf(id) {
84694
84876
  function runDefinition(id, ctx, input2, io) {
84695
84877
  const impl = implementationOf(id);
84696
84878
  if (!impl) throw new Error("No implementation for " + id);
84697
- return invoke(impl, ctx, input2, io);
84879
+ return invokeAndRefresh(impl, ctx, input2, io);
84880
+ }
84881
+ async function invokeAndRefresh(impl, ctx, input2, io) {
84882
+ try {
84883
+ return await invoke(impl, ctx, input2, io);
84884
+ } finally {
84885
+ if (impl.def.kind === "operation") reloadSettings(ctx);
84886
+ }
84698
84887
  }
84699
84888
  async function readQuery(def, ctx, input2 = {}) {
84700
84889
  return await runDefinition(def.id, ctx, input2, NO_QUESTIONS);
@@ -84719,7 +84908,7 @@ function createRpcHost(ctx) {
84719
84908
  if (!impl) throw new Error("Not allowed on the web: " + id);
84720
84909
  const need = accessNeeded(impl.def, input2);
84721
84910
  if (!allows(caller, need)) fail(refusalText(caller, need));
84722
- return invoke(impl, ctx, input2, io);
84911
+ return invokeAndRefresh(impl, ctx, input2, io);
84723
84912
  }
84724
84913
  };
84725
84914
  }
@@ -85146,7 +85335,7 @@ async function attachOpened(ctx, session) {
85146
85335
  nextStep("sandbox attach " + session);
85147
85336
  return 0;
85148
85337
  }
85149
- const { attach: attach2 } = await import("./chunk-ZXCAVVS4.js");
85338
+ const { attach: attach2 } = await import("./chunk-FA5VWLP4.js");
85150
85339
  return attach2({ ...ctx, options: { ...ctx.options, positionals: [session] } });
85151
85340
  }
85152
85341
  function definitionHandler(def) {
@@ -85694,7 +85883,8 @@ var Launcher = class {
85694
85883
  dataPath: run3.dataPath,
85695
85884
  launchId,
85696
85885
  // A new launch needs no sandbox running, so a lease held elsewhere is from one that stopped.
85697
- isRunning: () => isSandboxRunning()
85886
+ isRunning: () => isSandboxRunning(),
85887
+ pc: pcEnv()
85698
85888
  });
85699
85889
  const store = generateOnly ? null : await prepareStorePackages(storeApps(config3.apps.install), {
85700
85890
  storageRoot: ctx.storageRoot,
@@ -86105,6 +86295,7 @@ export {
86105
86295
  NAV,
86106
86296
  withOperationCommands,
86107
86297
  SandboxChannel,
86298
+ reloadSettings,
86108
86299
  esm_default4 as esm_default,
86109
86300
  esm_default11 as esm_default2,
86110
86301
  askChoice,