windows-development-sandbox 1.25.1 → 1.27.1

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 (211) hide show
  1. package/config.example.json +12 -3
  2. package/dist/app/features/accounts/services.def.d.ts +1 -1
  3. package/dist/app/features/accounts/services.def.js +1 -1
  4. package/dist/app/features/defs.d.ts +198 -3
  5. package/dist/app/features/defs.js +8 -0
  6. package/dist/app/features/settings/categories.def.js +1 -0
  7. package/dist/app/features/unity/index.d.ts +2 -0
  8. package/dist/app/features/unity/index.js +3 -0
  9. package/dist/app/features/unity/unity.def.d.ts +294 -0
  10. package/dist/app/features/unity/unity.def.js +471 -0
  11. package/dist/app/features/unity/unity.run.d.ts +142 -0
  12. package/dist/app/features/unity/unity.run.js +363 -0
  13. package/dist/app/features/unreal/unreal.run.js +13 -3
  14. package/dist/app/registry.js +6 -0
  15. package/dist/bin/agent-sandbox.js +2 -2
  16. package/dist/bin/{chunk-G3TG4ZT7.js → chunk-4TLSWPG7.js} +1 -1
  17. package/dist/bin/{chunk-3GAXLATK.js → chunk-QUHNXHVG.js} +72 -5
  18. package/dist/bin/{chunk-F4PVBN23.js → chunk-RSIX7C6D.js} +3633 -1972
  19. package/dist/bin/claude-host.js +2 -2
  20. package/dist/bin/claude-sbx.js +2 -2
  21. package/dist/bin/codex-sbx.js +2 -2
  22. package/dist/bin/gemini-sbx.js +2 -2
  23. package/dist/bin/sandbox.js +2 -2
  24. package/dist/bin/sbx-hide.js +2 -2
  25. package/dist/bin/sbx-show.js +2 -2
  26. package/dist/cli/commands/launch/launch.js +14 -1
  27. package/dist/cli/commands/registry.js +3 -0
  28. package/dist/cli/manifest.d.ts +33 -1
  29. package/dist/cli/manifest.js +32 -1
  30. package/dist/cli/menu/coverage-exempt.js +1 -0
  31. package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
  32. package/dist/cli/menu/pages/sandbox-page.js +40 -3
  33. package/dist/cli/menu/views.js +2 -0
  34. package/dist/core/config/defaults.js +16 -3
  35. package/dist/core/schema/accounts.d.ts +3 -0
  36. package/dist/core/schema/accounts.js +1 -0
  37. package/dist/core/schema/config.d.ts +29 -13
  38. package/dist/core/schema/github.d.ts +1 -0
  39. package/dist/core/schema/github.js +3 -0
  40. package/dist/core/schema/pc-installs.d.ts +2 -1
  41. package/dist/core/schema/pc-installs.js +6 -1
  42. package/dist/core/schema/toolchains.d.ts +8 -0
  43. package/dist/core/schema/toolchains.js +19 -4
  44. package/dist/guest/accounts/adapters.js +2 -0
  45. package/dist/guest/accounts/apply.d.ts +4 -2
  46. package/dist/guest/accounts/kinds/perplexityapp.d.ts +7 -0
  47. package/dist/guest/accounts/kinds/perplexityapp.js +53 -0
  48. package/dist/guest/accounts/kinds/unity.d.ts +27 -0
  49. package/dist/guest/accounts/kinds/unity.js +164 -7
  50. package/dist/guest/apps/app-installer.js +18 -0
  51. package/dist/guest/apps/store-app.d.ts +4 -0
  52. package/dist/guest/apps/store-app.js +19 -0
  53. package/dist/guest/apps/store-check.d.ts +8 -0
  54. package/dist/guest/apps/store-check.js +33 -0
  55. package/dist/guest/boot/boot.js +1 -1
  56. package/dist/guest/boot/guide.js +18 -0
  57. package/dist/guest/browser/comet.d.ts +3 -1
  58. package/dist/guest/browser/comet.js +1 -1
  59. package/dist/guest/browser/perplexity-app.d.ts +12 -0
  60. package/dist/guest/browser/perplexity-app.js +60 -0
  61. package/dist/guest/daemon/daemon.js +2 -0
  62. package/dist/guest/devtools/devtool.d.ts +1 -1
  63. package/dist/guest/devtools/registry.js +1 -2
  64. package/dist/guest/entries/tool.js +1 -1
  65. package/dist/guest/setup/addon-mcp.d.ts +7 -2
  66. package/dist/guest/setup/addon-mcp.js +19 -9
  67. package/dist/guest/setup/github-token.js +4 -4
  68. package/dist/guest/setup/unity-agent.d.ts +11 -0
  69. package/dist/guest/setup/unity-agent.js +39 -0
  70. package/dist/guest/toolchains/engines/epic-launcher.d.ts +1 -1
  71. package/dist/guest/toolchains/engines/epic-launcher.js +11 -7
  72. package/dist/guest/toolchains/engines/unity-cli.d.ts +24 -0
  73. package/dist/guest/toolchains/engines/unity-cli.js +74 -0
  74. package/dist/guest/toolchains/engines/unity-hub.d.ts +2 -0
  75. package/dist/guest/toolchains/engines/unity-hub.js +27 -0
  76. package/dist/guest/toolchains/engines/unity-release.d.ts +4 -15
  77. package/dist/guest/toolchains/engines/unity-release.js +17 -5
  78. package/dist/guest/toolchains/engines/unity-status.d.ts +11 -0
  79. package/dist/guest/toolchains/engines/unity-status.js +39 -0
  80. package/dist/guest/toolchains/engines/unity.d.ts +20 -1
  81. package/dist/guest/toolchains/engines/unity.js +250 -3
  82. package/dist/guest/toolchains/engines/unreal-status.js +1 -1
  83. package/dist/guest/toolchains/native/mesa.js +35 -11
  84. package/dist/guest/toolchains/native/unreal.js +1 -0
  85. package/dist/guest/toolchains/plan.js +5 -1
  86. package/dist/guest/toolchains/tools/cli.js +2 -2
  87. package/dist/host/accounts/deliver.d.ts +3 -0
  88. package/dist/host/accounts/deliver.js +23 -0
  89. package/dist/host/accounts/kinds/perplexityapp.d.ts +2 -0
  90. package/dist/host/accounts/kinds/perplexityapp.js +8 -0
  91. package/dist/host/accounts/kinds/unity.d.ts +1 -1
  92. package/dist/host/accounts/kinds/unity.js +60 -8
  93. package/dist/host/accounts/leases.d.ts +2 -1
  94. package/dist/host/accounts/source.d.ts +2 -0
  95. package/dist/host/accounts/source.js +2 -0
  96. package/dist/host/accounts/sources.js +2 -0
  97. package/dist/host/apps/index.d.ts +1 -0
  98. package/dist/host/apps/index.js +2 -0
  99. package/dist/host/apps/store-package.d.ts +51 -0
  100. package/dist/host/apps/store-package.js +209 -0
  101. package/dist/host/github/deliver.d.ts +8 -3
  102. package/dist/host/github/deliver.js +42 -29
  103. package/dist/host/github/index.d.ts +1 -0
  104. package/dist/host/github/index.js +1 -0
  105. package/dist/host/github/pc-login.d.ts +27 -0
  106. package/dist/host/github/pc-login.js +129 -0
  107. package/dist/host/pc-installs/discover.d.ts +4 -0
  108. package/dist/host/pc-installs/discover.js +19 -6
  109. package/dist/host/pc-installs/epic.js +3 -0
  110. package/dist/host/pc-installs/source.d.ts +1 -1
  111. package/dist/host/pc-installs/unity-assets.d.ts +5 -0
  112. package/dist/host/pc-installs/unity-assets.js +18 -0
  113. package/dist/host/pc-installs/unity.d.ts +22 -0
  114. package/dist/host/pc-installs/unity.js +144 -2
  115. package/dist/host/pc-installs/unreal.d.ts +2 -0
  116. package/dist/host/pc-installs/unreal.js +24 -8
  117. package/dist/runtime/agent.js +116 -11
  118. package/dist/runtime/daemon.js +2205 -987
  119. package/dist/runtime/db.js +116 -11
  120. package/dist/runtime/main.js +2578 -1375
  121. package/dist/runtime/netproxy.js +116 -11
  122. package/dist/runtime/tool.js +133 -31
  123. package/dist/runtime/view.js +116 -11
  124. package/dist/selftest/agents-web.test.js +2 -1
  125. package/dist/selftest/engines.test.js +32 -3
  126. package/dist/selftest/github.test.js +64 -1
  127. package/dist/selftest/index.js +10 -0
  128. package/dist/selftest/menu-screens.js +3 -1
  129. package/dist/selftest/mixamo.test.js +1 -1
  130. package/dist/selftest/persisted-installs.test.js +1 -1
  131. package/dist/selftest/service-accounts.test.js +13 -2
  132. package/dist/selftest/store-app.test.d.ts +2 -0
  133. package/dist/selftest/store-app.test.js +162 -0
  134. package/dist/selftest/support/engine-golden.js +7 -0
  135. package/dist/selftest/support/fixtures.d.ts +10 -1
  136. package/dist/selftest/support/unity-sample.d.ts +2 -0
  137. package/dist/selftest/support/unity-sample.js +80 -0
  138. package/dist/selftest/unity-accounts.test.d.ts +2 -0
  139. package/dist/selftest/unity-accounts.test.js +106 -0
  140. package/dist/selftest/unity-agent.test.d.ts +2 -0
  141. package/dist/selftest/unity-agent.test.js +47 -0
  142. package/dist/selftest/unity-page.test.d.ts +2 -0
  143. package/dist/selftest/unity-page.test.js +177 -0
  144. package/dist/selftest/unity-setup.test.d.ts +2 -0
  145. package/dist/selftest/unity-setup.test.js +49 -0
  146. package/dist/selftest/unity.test.js +238 -3
  147. package/dist/shared/accounts/index.d.ts +1 -0
  148. package/dist/shared/accounts/index.js +1 -0
  149. package/dist/shared/accounts/kinds/unity.d.ts +28 -0
  150. package/dist/shared/accounts/kinds/unity.js +88 -0
  151. package/dist/shared/accounts/kinds.d.ts +2 -2
  152. package/dist/shared/accounts/kinds.js +23 -1
  153. package/dist/shared/accounts/payloads.d.ts +52 -0
  154. package/dist/shared/accounts/payloads.js +14 -2
  155. package/dist/shared/apps/catalogue-agents-web.d.ts +7 -0
  156. package/dist/shared/apps/catalogue-agents-web.js +23 -0
  157. package/dist/shared/apps/catalogue-gamedev.d.ts +3 -0
  158. package/dist/shared/apps/catalogue-gamedev.js +31 -0
  159. package/dist/shared/apps/catalogue.d.ts +8 -0
  160. package/dist/shared/apps/index.d.ts +3 -2
  161. package/dist/shared/apps/index.js +3 -2
  162. package/dist/shared/apps/selection.d.ts +6 -0
  163. package/dist/shared/apps/selection.js +9 -0
  164. package/dist/shared/apps/store-package.d.ts +31 -0
  165. package/dist/shared/apps/store-package.js +34 -0
  166. package/dist/shared/channel/schema.d.ts +24 -0
  167. package/dist/shared/channel/secrets.d.ts +17 -0
  168. package/dist/shared/paths/pc-installs.d.ts +1 -1
  169. package/dist/shared/paths/pc-installs.js +1 -0
  170. package/dist/shared/readiness.d.ts +2 -2
  171. package/dist/shared/readiness.js +2 -2
  172. package/dist/shared/unity/asset-cache.d.ts +20 -0
  173. package/dist/shared/unity/asset-cache.js +44 -0
  174. package/dist/shared/unity/cli.d.ts +40 -0
  175. package/dist/shared/unity/cli.js +77 -0
  176. package/dist/shared/unity/editors.d.ts +46 -0
  177. package/dist/shared/unity/editors.js +89 -0
  178. package/dist/shared/unity/guide.d.ts +9 -0
  179. package/dist/shared/unity/guide.js +84 -0
  180. package/dist/shared/unity/index.d.ts +9 -0
  181. package/dist/shared/unity/index.js +11 -0
  182. package/dist/shared/unity/manifest.d.ts +10 -0
  183. package/dist/shared/unity/manifest.js +53 -0
  184. package/dist/shared/unity/mcp.d.ts +8 -0
  185. package/dist/shared/unity/mcp.js +12 -0
  186. package/dist/shared/unity/release.d.ts +28 -0
  187. package/dist/shared/unity/release.js +83 -0
  188. package/dist/shared/unity/status.d.ts +59 -0
  189. package/dist/shared/unity/status.js +45 -0
  190. package/dist/shared/unity/version.d.ts +27 -0
  191. package/dist/shared/unity/version.js +75 -0
  192. package/dist/shared/unreal/status.d.ts +1 -0
  193. package/dist/shared/unreal/status.js +2 -0
  194. package/dist/web/index.html +1 -1
  195. package/dist/web/{main-FNE6XEHA.js → main-VPMYTSUL.js} +67 -67
  196. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  197. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  198. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  199. package/native/scripts/get-store-package.ps1 +58 -0
  200. package/package.json +1 -1
  201. package/sandbox/addons/openupm-cli/package-lock.json +2921 -0
  202. package/sandbox/addons/openupm-cli/package.json +7 -0
  203. package/sandbox/addons/unity-mcp/requirements.in +1 -0
  204. package/sandbox/addons/unity-mcp/requirements.txt +1165 -0
  205. package/sandbox/csharp/AppActivation.cs +38 -0
  206. package/sandbox/scripts/install-store-package.ps1 +28 -0
  207. package/sandbox/scripts/start-store-app.ps1 +29 -0
  208. package/schemas/config.schema.json +61 -6
  209. package/schemas/sandbox-config.schema.json +38 -5
  210. package/dist/guest/devtools/unity.d.ts +0 -2
  211. package/dist/guest/devtools/unity.js +0 -7
@@ -0,0 +1,363 @@
1
+ // The Unity area's work: what the sandbox last found (tools\unity\status.json, written by the guest, so it is read with
2
+ // its schema and its text made safe), what this PC has that the next sandbox maps (its editors and Asset Store
3
+ // downloads: bounded reads), opening Unity Hub and signing in (exec requests in the running sandbox) and adding or
4
+ // removing the editor bridge in a project's Packages/manifest.json (only on request, after a confirmation).
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { humanBytes } from '../../../core/disk.js';
8
+ import { assertNoLinks, errMsg, fail, safeText } from '../../../core/util/index.js';
9
+ import { ProjectsService } from '../../../cli/services/projects-service.js';
10
+ import { SandboxChannel } from '../../../host/channel/index.js';
11
+ import { isSandboxRunning } from '../../../host/sandbox/index.js';
12
+ import { pcEnv } from '../../../host/accounts/index.js';
13
+ import { assetCacheEnv, findUnityEditorsOnPc, unityAssetCacheDirs, unityPcFiles, } from '../../../host/pc-installs/unity.js';
14
+ import { readBoundedFile, readValid, writeFileAtomic } from '../../../shared/io/index.js';
15
+ import { RunDir } from '../../../shared/channel/index.js';
16
+ import { assetPackages, bridgeOf, COPLAY_PACKAGE_URL, DEFAULT_PIPELINE_VERSION, ORIGIN_LABELS, parseProjectVersion, supportsPipeline, UNITY_STATUS_FILE, UnityStatusSchema, withBridge, } from '../../../shared/unity/index.js';
17
+ import { implement } from '../../context.js';
18
+ import { addEditorBridge, openUnityHub, removeEditorBridge, unityOverview, unitySignIn, } from './unity.def.js';
19
+ const win = path.win32;
20
+ const MAX_PACKAGES_SHOWN = 40;
21
+ const MAX_MANIFEST_READ = 1024 * 1024 + 1;
22
+ const PIPELINE_INDEX_URL = 'https://packages.unity.com/com.unity.pipeline';
23
+ const MAX_INDEX_BYTES = 2 * 1024 * 1024;
24
+ const GUEST_EXPLORER = 'C:\\Windows\\explorer.exe';
25
+ const SEMVER = /^\d{1,6}\.\d{1,6}\.\d{1,6}(-[0-9A-Za-z.-]{1,50})?(\+[0-9A-Za-z.-]{1,50})?$/;
26
+ const s = (text) => safeText(text);
27
+ const sn = (text) => (text === null ? null : safeText(text));
28
+ export function readStatus(dataPath) {
29
+ const file = path.join(dataPath, 'tools', 'unity', UNITY_STATUS_FILE);
30
+ try {
31
+ assertNoLinks(dataPath, file);
32
+ }
33
+ catch {
34
+ return null; // A link under the data folder: never followed.
35
+ }
36
+ return readValid(UnityStatusSchema, file);
37
+ }
38
+ const settingText = (v) => (Array.isArray(v) ? v.join(', ') || 'none' : String(v));
39
+ /** The overview from the sandbox's status and this PC's own look (no sandbox needed). */
40
+ export function unityOverviewOf(ctx, status, pcLook, hostFolders = []) {
41
+ const config = ctx.loaded.config;
42
+ let pc = { editors: [], assets: [] };
43
+ let problem = null;
44
+ try {
45
+ pc = pcLook();
46
+ }
47
+ catch (e) {
48
+ problem = 'Could not look at this PC: ' + errMsg(e);
49
+ }
50
+ const u = config.toolchains.unity;
51
+ const hostFolderOf = (workspace) => {
52
+ const name = win.basename(workspace).toLowerCase();
53
+ const same = hostFolders.filter((f) => win.basename(f).toLowerCase() === name);
54
+ return same.length === 1 ? same[0] : null;
55
+ };
56
+ const editorLabel = (root) => {
57
+ if (root === null)
58
+ return null;
59
+ const e = status?.editors.find((x) => x.root.toLowerCase() === root.toLowerCase());
60
+ return s(e ? 'Unity ' + e.version : root);
61
+ };
62
+ return {
63
+ setUp: status !== null,
64
+ checkedAt: status ? s(status.updatedAt) : null,
65
+ cli: status?.cli ? { version: s(status.cli.version), path: s(status.cli.path) } : null,
66
+ editors: (status?.editors ?? []).map((e) => ({
67
+ version: s(e.version),
68
+ dir: s(e.root),
69
+ where: ORIGIN_LABELS[e.origin],
70
+ writable: e.writable,
71
+ modules: e.modules.map(s),
72
+ sizeBytes: e.sizeBytes,
73
+ projects: (status?.projects ?? [])
74
+ .filter((p) => p.editor?.toLowerCase() === e.root.toLowerCase())
75
+ .map((p) => s(win.basename(p.workspace))),
76
+ })),
77
+ projects: (status?.projects ?? []).map((p) => ({
78
+ project: s(win.basename(p.workspace)),
79
+ folder: hostFolderOf(p.workspace),
80
+ version: sn(p.version),
81
+ editor: editorLabel(p.editor),
82
+ reason: s(p.reason),
83
+ bridge: p.bridge,
84
+ pipeline: p.version !== null && supportsPipeline(p.version),
85
+ })),
86
+ licence: {
87
+ state: status?.licence.state ?? 'unknown',
88
+ kind: s(status?.licence.kind ?? ''),
89
+ detail: s(status?.licence.detail ?? ''),
90
+ user: sn(status?.licence.user ?? null),
91
+ },
92
+ hub: { installed: status?.hub.installed ?? false, setting: u.hub },
93
+ assetCache: { mode: status?.assetCache.mode ?? u.assetCache, dir: sn(status?.assetCache.dir ?? null) },
94
+ agentTools: {
95
+ cliVersion: u.cliVersion,
96
+ mcp: sn(status?.mcp ?? null),
97
+ mcpSetting: u.mcp,
98
+ skills: status?.skills ?? false,
99
+ skillsSetting: u.agentSkills,
100
+ },
101
+ pc: { setting: settingText(config.pcInstalls.unityEditors), editors: pc.editors, problem },
102
+ assets: { setting: settingText(config.pcInstalls.unityAssets), folders: pc.assets },
103
+ problem: sn(status?.problem ?? null),
104
+ settings: {
105
+ enabled: settingText(u.enabled),
106
+ acceptLicense: u.acceptLicense,
107
+ version: u.version,
108
+ modules: settingText(u.modules),
109
+ hub: u.hub,
110
+ keepVersions: u.keepVersions,
111
+ mcp: u.mcp,
112
+ agentSkills: u.agentSkills,
113
+ cliVersion: u.cliVersion,
114
+ assetCache: u.assetCache,
115
+ },
116
+ };
117
+ }
118
+ /** This PC's disk for the packages (bounded lists). */
119
+ export const PC_ASSET_FS = {
120
+ list: (dir) => {
121
+ try {
122
+ return fs.readdirSync(dir).slice(0, 5000);
123
+ }
124
+ catch {
125
+ return []; // Not a folder: nothing in it.
126
+ }
127
+ },
128
+ isDir: (dir) => {
129
+ try {
130
+ return fs.lstatSync(dir).isDirectory();
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ },
136
+ size: (file) => {
137
+ try {
138
+ const st = fs.lstatSync(file);
139
+ return st.isFile() ? st.size : null;
140
+ }
141
+ catch {
142
+ return null;
143
+ }
144
+ },
145
+ };
146
+ /** What this PC has for the next sandbox: its editors and its Asset Store packages. */
147
+ function lookAtPc(ctx) {
148
+ const config = ctx.loaded.config.pcInstalls;
149
+ const pc = pcEnv();
150
+ const files = unityPcFiles(pc);
151
+ const editors = config.unityEditors === false
152
+ ? []
153
+ : findUnityEditorsOnPc(files, Array.isArray(config.unityEditors) ? config.unityEditors.map(String) : true).map((e) => ({ version: e.id, folder: e.host }));
154
+ const assets = config.unityAssets === false
155
+ ? []
156
+ : unityAssetCacheDirs(files, assetCacheEnv(pc)).map((folder) => {
157
+ const packages = assetPackages(folder, PC_ASSET_FS);
158
+ return {
159
+ folder,
160
+ packages: packages.slice(0, MAX_PACKAGES_SHOWN).map((p) => ({
161
+ publisher: p.publisher,
162
+ category: p.category,
163
+ name: p.name,
164
+ size: humanBytes(p.sizeBytes),
165
+ })),
166
+ more: Math.max(0, packages.length - MAX_PACKAGES_SHOWN),
167
+ };
168
+ });
169
+ return { editors, assets };
170
+ }
171
+ export const unityOverviewRun = implement(unityOverview, async (ctx) => {
172
+ let hostFolders = [];
173
+ try {
174
+ hostFolders = (await new ProjectsService(ctx).list()).projects.map((p) => p.folder);
175
+ }
176
+ catch {
177
+ // No project list: the page shows the projects without bridge buttons.
178
+ }
179
+ return unityOverviewOf(ctx, readStatus(ctx.dataPath), () => lookAtPc(ctx), hostFolders);
180
+ });
181
+ // ---- Unity Hub and sign-in (the running sandbox) ----
182
+ async function inSandbox(ctx, argv, seconds) {
183
+ if (!isSandboxRunning())
184
+ fail('No sandbox is running.', 'Start one for a Unity project; Unity opens in its window.');
185
+ const status = new RunDir(ctx.dataPath).readSandboxStatus();
186
+ if (status?.phase !== 'ready')
187
+ fail('The sandbox is not ready yet.', 'Try again in a moment.');
188
+ const result = await new SandboxChannel(ctx.dataPath).exec(argv, status.guestWorkspace ?? 'C:\\', seconds);
189
+ // explorer.exe returns 1 even when it started the program.
190
+ if (!result.ok && !(argv[0] === GUEST_EXPLORER && result.exitCode === 1))
191
+ fail('The sandbox could not do it: ' + safeText(result.message));
192
+ }
193
+ export const openUnityHubRun = implement(openUnityHub, async (ctx, _input, io) => {
194
+ const hub = readStatus(ctx.dataPath)?.hub;
195
+ if (!hub?.installed || !hub.path)
196
+ fail('Unity Hub is not installed in this sandbox.', 'Turn on Unity Hub in the Unity set-up settings and restart.');
197
+ io.progress('Opening Unity Hub in the sandbox…');
198
+ await inSandbox(ctx, [GUEST_EXPLORER, hub.path], 30);
199
+ return {
200
+ message: 'Unity Hub opens in the sandbox window. Sign in once; the licence is kept for the next sandboxes.',
201
+ };
202
+ });
203
+ export const unitySignInRun = implement(unitySignIn, async (ctx, _input, io) => {
204
+ const status = readStatus(ctx.dataPath);
205
+ io.progress('Opening the Unity sign-in in the sandbox…');
206
+ if (status?.hub.installed && status.hub.path)
207
+ await inSandbox(ctx, [GUEST_EXPLORER, status.hub.path], 30);
208
+ else if (status?.cli)
209
+ await inSandbox(ctx, [status.cli.path, 'auth', 'login'], 300);
210
+ else
211
+ fail('Unity is not set up in a sandbox yet.', 'Start a sandbox for a Unity project first.');
212
+ return { message: 'Sign in in the sandbox window; the licence is kept for the next sandboxes.' };
213
+ });
214
+ // ---- The editor bridge ----
215
+ /** The newest Unity Pipeline package version, from Unity's package registry (bounded, checked as a version). */
216
+ export async function latestPipelineVersion(fetcher = fetch) {
217
+ const response = await fetcher(PIPELINE_INDEX_URL, { signal: AbortSignal.timeout(15_000) });
218
+ if (!response.ok)
219
+ throw new Error('Unity answered ' + response.status);
220
+ const reader = response.body?.getReader();
221
+ if (!reader)
222
+ throw new Error('Unity sent nothing');
223
+ const chunks = [];
224
+ let total = 0;
225
+ for (;;) {
226
+ const { done, value } = await reader.read();
227
+ if (done)
228
+ break;
229
+ total += value.length;
230
+ if (total > MAX_INDEX_BYTES) {
231
+ await reader.cancel();
232
+ throw new Error('the package list is over 2 MB');
233
+ }
234
+ chunks.push(Buffer.from(value));
235
+ }
236
+ const json = JSON.parse(Buffer.concat(chunks).toString('utf8'));
237
+ const latest = json?.['dist-tags']?.latest;
238
+ if (typeof latest !== 'string' || !SEMVER.test(latest))
239
+ throw new Error('no usable version in the package list');
240
+ return latest;
241
+ }
242
+ /** A project of this PC: its manifest file and the editor version it asks for. */
243
+ export function unityProjectProblem(dir) {
244
+ if (!win.isAbsolute(dir))
245
+ return 'Give the full path of the Unity project folder.';
246
+ const version = readBoundedFile(win.join(dir, 'ProjectSettings', 'ProjectVersion.txt'), 64 * 1024);
247
+ if (!version || !parseProjectVersion(version.toString('utf8')))
248
+ return dir + ' is not a Unity project (no ProjectSettings\\ProjectVersion.txt).';
249
+ return readBoundedFile(win.join(dir, 'Packages', 'manifest.json'), MAX_MANIFEST_READ)
250
+ ? null
251
+ : dir + ' has no Packages\\manifest.json.';
252
+ }
253
+ function manifestOf(dir) {
254
+ const problem = unityProjectProblem(dir);
255
+ if (problem)
256
+ fail(problem, 'Pick the folder that has ProjectSettings and Packages in it.');
257
+ const file = win.join(dir, 'Packages', 'manifest.json');
258
+ assertNoLinks(dir, file);
259
+ const text = readBoundedFile(file, MAX_MANIFEST_READ).toString('utf8');
260
+ const version = parseProjectVersion(readBoundedFile(win.join(dir, 'ProjectSettings', 'ProjectVersion.txt'), 64 * 1024).toString('utf8'));
261
+ return { file, text, unity: version.version };
262
+ }
263
+ /** Adds the editor bridge to a project's manifest after the confirmation; the rest of the file is kept. */
264
+ export async function addBridgeToProject(dir, wanted, hooks) {
265
+ const { file, text, unity } = manifestOf(dir);
266
+ const have = bridgeOf(text);
267
+ if (have)
268
+ return { file, message: win.basename(dir) + ' has the editor bridge already (' + bridgeName(have) + ').' };
269
+ const pipeline = supportsPipeline(unity);
270
+ const bridge = wanted ?? (pipeline ? 'pipeline' : 'coplay');
271
+ if (bridge === 'pipeline' && !pipeline)
272
+ fail('Unity ' + unity + ' cannot take the Unity Pipeline package.', 'It needs Unity 6 or later: use the other bridge.');
273
+ let version = DEFAULT_PIPELINE_VERSION;
274
+ let note = '';
275
+ if (bridge === 'pipeline') {
276
+ try {
277
+ version = await hooks.pipelineVersion();
278
+ }
279
+ catch (e) {
280
+ note = ' (could not ask Unity for the newest version: ' + errMsg(e) + '; used ' + version + ')';
281
+ }
282
+ }
283
+ const what = bridge === 'pipeline'
284
+ ? 'com.unity.pipeline ' + version
285
+ : 'MCP for Unity (' + COPLAY_PACKAGE_URL.split('?')[0] + ')';
286
+ if (!(await hooks.confirm(file, what)))
287
+ return { file, message: 'Nothing was changed.' };
288
+ writeFileAtomic(file, withBridge(text, bridge, true, version));
289
+ return {
290
+ file,
291
+ message: 'Added ' +
292
+ what +
293
+ ' to ' +
294
+ file +
295
+ note +
296
+ '.' +
297
+ (bridge === 'coplay' ? ' Turn on MCP for Unity in Apps so agents can reach it.' : ''),
298
+ };
299
+ }
300
+ /** Takes the editor bridge out of a project's manifest after the confirmation. */
301
+ export async function removeBridgeFromProject(dir, hooks) {
302
+ const { file, text } = manifestOf(dir);
303
+ const have = bridgeOf(text);
304
+ if (!have)
305
+ return { file, message: win.basename(dir) + ' has no editor bridge.' };
306
+ if (!(await hooks.confirm(file, 'remove ' + bridgeName(have))))
307
+ return { file, message: 'Nothing was changed.' };
308
+ writeFileAtomic(file, withBridge(text, have, false));
309
+ return { file, message: 'Removed ' + bridgeName(have) + ' from ' + file + '.' };
310
+ }
311
+ const bridgeName = (b) => (b === 'pipeline' ? 'Unity Pipeline' : 'MCP for Unity');
312
+ async function projectOf(ctx, given, io) {
313
+ if (given)
314
+ return win.resolve(given);
315
+ let known = [];
316
+ try {
317
+ known = (await new ProjectsService(ctx).list()).projects
318
+ .map((p) => p.folder)
319
+ .filter((f) => unityProjectProblem(f) === null);
320
+ }
321
+ catch {
322
+ // No project list: ask for the folder.
323
+ }
324
+ if (known.length === 1)
325
+ return known[0];
326
+ if (known.length > 1) {
327
+ const picked = await io.choose({
328
+ title: 'Which Unity project?',
329
+ lines: [],
330
+ choices: known.map((f) => ({ value: f, label: win.basename(f), hint: f })),
331
+ });
332
+ if (picked)
333
+ return picked;
334
+ }
335
+ const dir = await io.path({
336
+ title: 'Which Unity project folder?',
337
+ lines: ['The folder that holds Assets, Packages and ProjectSettings.'],
338
+ pathKind: 'dir',
339
+ validate: (v) => unityProjectProblem(win.resolve(v)) ?? true,
340
+ });
341
+ if (!dir)
342
+ fail('No folder given.', 'Nothing was changed.');
343
+ return win.resolve(dir);
344
+ }
345
+ export const addBridgeRun = implement(addEditorBridge, async (ctx, input, io) => {
346
+ const dir = await projectOf(ctx, input.project, io);
347
+ const edit = await addBridgeToProject(dir, input.bridge, {
348
+ pipelineVersion: () => latestPipelineVersion(),
349
+ confirm: (file, what) => io.confirm({
350
+ title: 'Change the project?',
351
+ lines: ['This adds ' + what + ' to ' + file + '.', 'Nothing else in the project changes.'],
352
+ yes: 'Add',
353
+ }),
354
+ });
355
+ return { message: edit.message };
356
+ });
357
+ export const removeBridgeRun = implement(removeEditorBridge, async (ctx, input, io) => {
358
+ const dir = await projectOf(ctx, input.project, io);
359
+ const edit = await removeBridgeFromProject(dir, {
360
+ confirm: (file, what) => io.confirm({ title: 'Change the project?', lines: ['This will ' + what + ' in ' + file + '.'], yes: 'Remove' }),
361
+ });
362
+ return { message: edit.message };
363
+ });
@@ -19,7 +19,11 @@ import { ConfigFile } from '../settings/index.js';
19
19
  import { forgetEngineFolder, openEpicLauncher, registerEngineFolder, unrealOverview, } from './unreal.def.js';
20
20
  const win = path.win32;
21
21
  /** The launcher the sandbox installs (the 64-bit one of launcher 17 and later). */
22
- const GUEST_LAUNCHER = 'C:\\Program Files (x86)\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe';
22
+ /** Where the launcher installs in the sandbox: Program Files for newer launchers, (x86) for older ones. */
23
+ const GUEST_LAUNCHERS = [
24
+ 'C:\\Program Files\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe',
25
+ 'C:\\Program Files (x86)\\Epic Games\\Launcher\\Portal\\Binaries\\Win64\\EpicGamesLauncher.exe',
26
+ ];
23
27
  const MAX_PACKS_SHOWN = 40;
24
28
  const s = (text) => safeText(text);
25
29
  const sn = (text) => (text === null ? null : safeText(text));
@@ -204,10 +208,16 @@ export const openEpicLauncherRun = implement(openEpicLauncher, async (ctx, _inpu
204
208
  const status = new RunDir(ctx.dataPath).readSandboxStatus();
205
209
  if (status?.phase !== 'ready')
206
210
  fail('The sandbox is not ready yet.', 'Try again in a moment.');
207
- if (!readStatus(ctx.dataPath)?.launcher.installed)
211
+ const launcher = readStatus(ctx.dataPath)?.launcher;
212
+ if (!launcher?.installed)
208
213
  fail('The Epic Games Launcher is not installed in this sandbox.', 'It is installed when the sandbox is set up for Unreal with the licence accepted (Unreal set-up settings).');
209
214
  io.progress('Opening the Epic Games Launcher in the sandbox…');
210
- const result = await new SandboxChannel(ctx.dataPath).exec(['C:\\Windows\\explorer.exe', GUEST_LAUNCHER], status.guestWorkspace ?? 'C:\\', 30);
215
+ const result = await new SandboxChannel(ctx.dataPath).exec(
216
+ // The guest's status only picks one of the known paths.
217
+ [
218
+ 'C:\\Windows\\explorer.exe',
219
+ GUEST_LAUNCHERS.find((p) => p.toLowerCase() === launcher.exe?.toLowerCase()) ?? GUEST_LAUNCHERS[0],
220
+ ], status.guestWorkspace ?? 'C:\\', 30);
211
221
  // explorer.exe returns 1 even when it started the program.
212
222
  if (!result.ok && result.exitCode !== 1)
213
223
  fail('The sandbox could not open it: ' + safeText(result.message));
@@ -21,6 +21,7 @@ import { vpnLoginRun, vpnLogoutRun, vpnStatusRun } from './features/vpn/index.js
21
21
  import { githubLoginRun, githubLogoutRun, githubStatusRun } from './features/github/index.js';
22
22
  import { accessStatusRun, allowDeviceRun, lanOffRun, lanOnRun, revokeAccessRun, revokeAllAccessRun, } from './features/access/index.js';
23
23
  import { forgetEngineFolderRun, openEpicLauncherRun, registerEngineFolderRun, unrealOverviewRun, } from './features/unreal/index.js';
24
+ import { addBridgeRun, openUnityHubRun, removeBridgeRun, unityOverviewRun, unitySignInRun, } from './features/unity/index.js';
24
25
  export const IMPLEMENTATIONS = [
25
26
  sandboxStatusRun,
26
27
  showWindowRun,
@@ -114,6 +115,11 @@ export const IMPLEMENTATIONS = [
114
115
  registerEngineFolderRun,
115
116
  forgetEngineFolderRun,
116
117
  openEpicLauncherRun,
118
+ unityOverviewRun,
119
+ openUnityHubRun,
120
+ unitySignInRun,
121
+ addBridgeRun,
122
+ removeBridgeRun,
117
123
  ];
118
124
  export function implementationOf(id) {
119
125
  return IMPLEMENTATIONS.find((i) => i.def.id === id);
@@ -2,8 +2,8 @@
2
2
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
3
3
  import {
4
4
  run
5
- } from "./chunk-3GAXLATK.js";
6
- import "./chunk-F4PVBN23.js";
5
+ } from "./chunk-QUHNXHVG.js";
6
+ import "./chunk-RSIX7C6D.js";
7
7
 
8
8
  // src/bin/agent-sandbox.ts
9
9
  run(process.argv.slice(2));
@@ -1,7 +1,7 @@
1
1
  import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
2
2
  import {
3
3
  attach
4
- } from "./chunk-F4PVBN23.js";
4
+ } from "./chunk-RSIX7C6D.js";
5
5
  export {
6
6
  attach
7
7
  };
@@ -223,6 +223,9 @@ import {
223
223
  takeHiddenInstallNotice,
224
224
  toWireError,
225
225
  trustStorePath,
226
+ unityOverview,
227
+ unityPage,
228
+ unityView,
226
229
  unrealOverview,
227
230
  unrealPage,
228
231
  unrealView,
@@ -254,7 +257,7 @@ import {
254
257
  withOperationCommands,
255
258
  working,
256
259
  writeJson
257
- } from "./chunk-F4PVBN23.js";
260
+ } from "./chunk-RSIX7C6D.js";
258
261
 
259
262
  // node_modules/commander/lib/error.js
260
263
  var require_error = __commonJS({
@@ -3987,7 +3990,7 @@ var COMMAND_LIST = [
3987
3990
  },
3988
3991
  {
3989
3992
  name: "name",
3990
- description: "account name, or the service (status, import, login, logout): epic, steam, unity, cfx, keystore, play, expo, appstore, perplexity or comet",
3993
+ description: "account name, or the service (status, import, login, logout): epic, steam, unity, cfx, keystore, play, expo, appstore, perplexity, comet or perplexityapp",
3991
3994
  required: false,
3992
3995
  choices: "accounts"
3993
3996
  }
@@ -4095,6 +4098,37 @@ var COMMAND_LIST = [
4095
4098
  ],
4096
4099
  options: []
4097
4100
  },
4101
+ {
4102
+ name: "unity",
4103
+ summary: "Unity: the sandbox's editors, this PC's editors and Asset Store downloads, the licence, the editor bridge, Unity Hub",
4104
+ group: "Projects & accounts",
4105
+ json,
4106
+ examples: [
4107
+ "sandbox unity editors, which project opens with which, the licence",
4108
+ "sandbox unity sign-in sign in to Unity in the running sandbox",
4109
+ "sandbox unity add-bridge D:\\Game add the editor bridge to a project (asks first)"
4110
+ ],
4111
+ args: [
4112
+ {
4113
+ name: "action",
4114
+ description: "status, hub, sign-in, add-bridge, remove-bridge",
4115
+ required: false,
4116
+ choices: ["status"]
4117
+ },
4118
+ {
4119
+ name: "project",
4120
+ description: "(add-bridge, remove-bridge) the Unity project folder",
4121
+ required: false,
4122
+ when: []
4123
+ }
4124
+ ],
4125
+ options: [
4126
+ text("--bridge", "name", "(add-bridge) pipeline (Unity 6 and later) or coplay (older Unity)", {
4127
+ when: "add-bridge",
4128
+ label: "Bridge"
4129
+ })
4130
+ ]
4131
+ },
4098
4132
  {
4099
4133
  name: "engine",
4100
4134
  summary: "(beta) what runs your sandbox (Windows Sandbox or HCS) and what each engine can do on this PC",
@@ -6768,6 +6802,7 @@ var MENU_VIEWS = [
6768
6802
  { view: appsView, section: "Apps" },
6769
6803
  { view: projectsView, section: "Sandbox" },
6770
6804
  { view: unrealView, section: "Sandbox" },
6805
+ { view: unityView, section: "Sandbox" },
6771
6806
  { view: healthView, section: "Maintenance" },
6772
6807
  { view: dataView, section: "Maintenance" },
6773
6808
  { view: storageView, section: "Maintenance" },
@@ -8649,11 +8684,19 @@ var CATEGORIES3 = [
8649
8684
  id: "unreal",
8650
8685
  label: "Unreal Engine",
8651
8686
  description: "Engines, which project uses which, this PC's engines and assets, Visual Studio and the debugger."
8687
+ },
8688
+ {
8689
+ id: "unity",
8690
+ label: "Unity",
8691
+ description: "Editors, this PC's editors and Asset Store downloads, the licence, the editor bridge and Unity Hub."
8652
8692
  }
8653
8693
  ];
8654
8694
  function unrealMenu(m) {
8655
8695
  return m.unreal ? menuView(unrealPage(m.unreal), { category: "unreal", prefix: "unreal:", refresh: false }) : void 0;
8656
8696
  }
8697
+ function unityMenu(m) {
8698
+ return m.unity ? menuView(unityPage(m.unity), { category: "unity", prefix: "unity:", refresh: false }) : void 0;
8699
+ }
8657
8700
  function statusView(m) {
8658
8701
  return menuView(sandboxPage(m.report), { category: "status", prefix: "status:", refresh: false });
8659
8702
  }
@@ -8663,6 +8706,7 @@ function projectsView2(m) {
8663
8706
  function viewFor(m, category) {
8664
8707
  if (category === "projects") return projectsView2(m);
8665
8708
  if (category === "unreal") return unrealMenu(m);
8709
+ if (category === "unity") return unityMenu(m);
8666
8710
  if (category === "status" || category === void 0) return statusView(m);
8667
8711
  return void 0;
8668
8712
  }
@@ -8700,12 +8744,32 @@ function sandboxOptions(m) {
8700
8744
  disabled: "l looks again."
8701
8745
  }
8702
8746
  ];
8747
+ const unity = unityMenu(m);
8748
+ const noUnity = unity ? [] : [
8749
+ {
8750
+ value: "info:unity",
8751
+ label: "Could not read the Unity set-up",
8752
+ category: "unity",
8753
+ section: "Unity",
8754
+ description: "l looks again.",
8755
+ disabled: "l looks again."
8756
+ }
8757
+ ];
8703
8758
  return {
8704
8759
  path: [],
8705
- items: [...statusItems, ...projects.items, ...databases, ...noDatabases, ...unreal?.items ?? [], ...noUnreal],
8760
+ items: [
8761
+ ...statusItems,
8762
+ ...projects.items,
8763
+ ...databases,
8764
+ ...noDatabases,
8765
+ ...unreal?.items ?? [],
8766
+ ...noUnreal,
8767
+ ...unity?.items ?? [],
8768
+ ...noUnity
8769
+ ],
8706
8770
  categories: CATEGORIES3,
8707
8771
  empty: "Nothing here yet.",
8708
- actions: (category) => category === "projects" ? [...projects.actions, REFRESH] : category === "databases" ? [REFRESH] : category === "unreal" ? [...unreal?.actions ?? [], REFRESH] : [...status.actions, REFRESH],
8772
+ actions: (category) => category === "projects" ? [...projects.actions, REFRESH] : category === "databases" ? [REFRESH] : category === "unreal" ? [...unreal?.actions ?? [], REFRESH] : category === "unity" ? [...unity?.actions ?? [], REFRESH] : [...status.actions, REFRESH],
8709
8773
  enterLabel: (category, id) => viewFor(m, category)?.enterLabel(category, id) || enterLabel2(id ?? "", m)
8710
8774
  };
8711
8775
  }
@@ -8759,7 +8823,8 @@ var SandboxPage = class extends ListPage {
8759
8823
  projects: await readQuery(listProjects, this.ctx),
8760
8824
  databases,
8761
8825
  defaultAgent: this.defaultAgent(),
8762
- unreal: await readQuery(unrealOverview, this.ctx).catch(() => null)
8826
+ unreal: await readQuery(unrealOverview, this.ctx).catch(() => null),
8827
+ unity: await readQuery(unityOverview, this.ctx).catch(() => null)
8763
8828
  };
8764
8829
  }
8765
8830
  /** The folder `sbx-db` runs in: the sandbox's own workspace. */
@@ -10531,6 +10596,8 @@ var HANDLERS = {
10531
10596
  engine,
10532
10597
  // The bare command shows the overview; its actions (add-engine, forget-engine, launcher) are the operations' hints.
10533
10598
  unreal: definitionHandler(unrealOverview),
10599
+ // Same for Unity: its actions (hub, sign-in, add-bridge, remove-bridge) are the operations' hints.
10600
+ unity: definitionHandler(unityOverview),
10534
10601
  "host-claude": claudeHost,
10535
10602
  host: hostCommand,
10536
10603
  _watch: watcher,