windows-development-sandbox 1.25.1 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) 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/registry.js +6 -0
  14. package/dist/bin/agent-sandbox.js +2 -2
  15. package/dist/bin/{chunk-F4PVBN23.js → chunk-6WWMBCT2.js} +3580 -1962
  16. package/dist/bin/{chunk-3GAXLATK.js → chunk-JXL6ZOO3.js} +72 -5
  17. package/dist/bin/{chunk-G3TG4ZT7.js → chunk-ZXCAVVS4.js} +1 -1
  18. package/dist/bin/claude-host.js +2 -2
  19. package/dist/bin/claude-sbx.js +2 -2
  20. package/dist/bin/codex-sbx.js +2 -2
  21. package/dist/bin/gemini-sbx.js +2 -2
  22. package/dist/bin/sandbox.js +2 -2
  23. package/dist/bin/sbx-hide.js +2 -2
  24. package/dist/bin/sbx-show.js +2 -2
  25. package/dist/cli/commands/launch/launch.js +12 -0
  26. package/dist/cli/commands/registry.js +3 -0
  27. package/dist/cli/manifest.d.ts +33 -1
  28. package/dist/cli/manifest.js +32 -1
  29. package/dist/cli/menu/coverage-exempt.js +1 -0
  30. package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
  31. package/dist/cli/menu/pages/sandbox-page.js +40 -3
  32. package/dist/cli/menu/views.js +2 -0
  33. package/dist/core/config/defaults.js +16 -3
  34. package/dist/core/schema/accounts.d.ts +3 -0
  35. package/dist/core/schema/accounts.js +1 -0
  36. package/dist/core/schema/config.d.ts +29 -13
  37. package/dist/core/schema/github.d.ts +1 -0
  38. package/dist/core/schema/github.js +3 -0
  39. package/dist/core/schema/pc-installs.d.ts +2 -1
  40. package/dist/core/schema/pc-installs.js +6 -1
  41. package/dist/core/schema/toolchains.d.ts +8 -0
  42. package/dist/core/schema/toolchains.js +19 -4
  43. package/dist/guest/accounts/adapters.js +2 -0
  44. package/dist/guest/accounts/apply.d.ts +4 -2
  45. package/dist/guest/accounts/kinds/perplexityapp.d.ts +7 -0
  46. package/dist/guest/accounts/kinds/perplexityapp.js +53 -0
  47. package/dist/guest/accounts/kinds/unity.d.ts +27 -0
  48. package/dist/guest/accounts/kinds/unity.js +164 -7
  49. package/dist/guest/apps/app-installer.js +18 -0
  50. package/dist/guest/apps/store-app.d.ts +4 -0
  51. package/dist/guest/apps/store-app.js +19 -0
  52. package/dist/guest/apps/store-check.d.ts +8 -0
  53. package/dist/guest/apps/store-check.js +33 -0
  54. package/dist/guest/boot/boot.js +1 -1
  55. package/dist/guest/boot/guide.js +18 -0
  56. package/dist/guest/browser/comet.d.ts +3 -1
  57. package/dist/guest/browser/comet.js +1 -1
  58. package/dist/guest/browser/perplexity-app.d.ts +12 -0
  59. package/dist/guest/browser/perplexity-app.js +60 -0
  60. package/dist/guest/daemon/daemon.js +2 -0
  61. package/dist/guest/devtools/devtool.d.ts +1 -1
  62. package/dist/guest/devtools/registry.js +1 -2
  63. package/dist/guest/entries/tool.js +1 -1
  64. package/dist/guest/setup/addon-mcp.d.ts +7 -2
  65. package/dist/guest/setup/addon-mcp.js +19 -9
  66. package/dist/guest/setup/github-token.js +4 -4
  67. package/dist/guest/setup/unity-agent.d.ts +11 -0
  68. package/dist/guest/setup/unity-agent.js +36 -0
  69. package/dist/guest/toolchains/engines/unity-cli.d.ts +24 -0
  70. package/dist/guest/toolchains/engines/unity-cli.js +74 -0
  71. package/dist/guest/toolchains/engines/unity-hub.d.ts +2 -0
  72. package/dist/guest/toolchains/engines/unity-hub.js +27 -0
  73. package/dist/guest/toolchains/engines/unity-release.d.ts +4 -15
  74. package/dist/guest/toolchains/engines/unity-release.js +17 -5
  75. package/dist/guest/toolchains/engines/unity-status.d.ts +11 -0
  76. package/dist/guest/toolchains/engines/unity-status.js +39 -0
  77. package/dist/guest/toolchains/engines/unity.d.ts +20 -1
  78. package/dist/guest/toolchains/engines/unity.js +250 -3
  79. package/dist/guest/toolchains/plan.js +5 -1
  80. package/dist/guest/toolchains/tools/cli.js +2 -2
  81. package/dist/host/accounts/deliver.js +4 -0
  82. package/dist/host/accounts/kinds/perplexityapp.d.ts +2 -0
  83. package/dist/host/accounts/kinds/perplexityapp.js +8 -0
  84. package/dist/host/accounts/kinds/unity.d.ts +1 -1
  85. package/dist/host/accounts/kinds/unity.js +60 -8
  86. package/dist/host/accounts/leases.d.ts +2 -1
  87. package/dist/host/accounts/sources.js +2 -0
  88. package/dist/host/apps/index.d.ts +1 -0
  89. package/dist/host/apps/index.js +2 -0
  90. package/dist/host/apps/store-package.d.ts +51 -0
  91. package/dist/host/apps/store-package.js +209 -0
  92. package/dist/host/github/deliver.d.ts +8 -3
  93. package/dist/host/github/deliver.js +42 -29
  94. package/dist/host/github/index.d.ts +1 -0
  95. package/dist/host/github/index.js +1 -0
  96. package/dist/host/github/pc-login.d.ts +27 -0
  97. package/dist/host/github/pc-login.js +129 -0
  98. package/dist/host/pc-installs/discover.d.ts +4 -0
  99. package/dist/host/pc-installs/discover.js +16 -4
  100. package/dist/host/pc-installs/source.d.ts +1 -1
  101. package/dist/host/pc-installs/unity-assets.d.ts +5 -0
  102. package/dist/host/pc-installs/unity-assets.js +18 -0
  103. package/dist/host/pc-installs/unity.d.ts +20 -0
  104. package/dist/host/pc-installs/unity.js +135 -2
  105. package/dist/runtime/agent.js +116 -11
  106. package/dist/runtime/daemon.js +2170 -976
  107. package/dist/runtime/db.js +116 -11
  108. package/dist/runtime/main.js +2543 -1364
  109. package/dist/runtime/netproxy.js +116 -11
  110. package/dist/runtime/tool.js +133 -31
  111. package/dist/runtime/view.js +116 -11
  112. package/dist/selftest/agents-web.test.js +2 -1
  113. package/dist/selftest/engines.test.js +2 -2
  114. package/dist/selftest/github.test.js +64 -1
  115. package/dist/selftest/index.js +10 -0
  116. package/dist/selftest/menu-screens.js +3 -1
  117. package/dist/selftest/persisted-installs.test.js +1 -1
  118. package/dist/selftest/service-accounts.test.js +1 -1
  119. package/dist/selftest/store-app.test.d.ts +2 -0
  120. package/dist/selftest/store-app.test.js +162 -0
  121. package/dist/selftest/support/engine-golden.js +7 -0
  122. package/dist/selftest/support/fixtures.d.ts +10 -1
  123. package/dist/selftest/support/unity-sample.d.ts +2 -0
  124. package/dist/selftest/support/unity-sample.js +80 -0
  125. package/dist/selftest/unity-accounts.test.d.ts +2 -0
  126. package/dist/selftest/unity-accounts.test.js +106 -0
  127. package/dist/selftest/unity-agent.test.d.ts +2 -0
  128. package/dist/selftest/unity-agent.test.js +47 -0
  129. package/dist/selftest/unity-page.test.d.ts +2 -0
  130. package/dist/selftest/unity-page.test.js +177 -0
  131. package/dist/selftest/unity-setup.test.d.ts +2 -0
  132. package/dist/selftest/unity-setup.test.js +49 -0
  133. package/dist/selftest/unity.test.js +238 -3
  134. package/dist/shared/accounts/index.d.ts +1 -0
  135. package/dist/shared/accounts/index.js +1 -0
  136. package/dist/shared/accounts/kinds/unity.d.ts +28 -0
  137. package/dist/shared/accounts/kinds/unity.js +88 -0
  138. package/dist/shared/accounts/kinds.d.ts +2 -2
  139. package/dist/shared/accounts/kinds.js +23 -1
  140. package/dist/shared/accounts/payloads.d.ts +52 -0
  141. package/dist/shared/accounts/payloads.js +14 -2
  142. package/dist/shared/apps/catalogue-agents-web.d.ts +7 -0
  143. package/dist/shared/apps/catalogue-agents-web.js +23 -0
  144. package/dist/shared/apps/catalogue-gamedev.d.ts +3 -0
  145. package/dist/shared/apps/catalogue-gamedev.js +31 -0
  146. package/dist/shared/apps/catalogue.d.ts +8 -0
  147. package/dist/shared/apps/index.d.ts +3 -2
  148. package/dist/shared/apps/index.js +3 -2
  149. package/dist/shared/apps/selection.d.ts +6 -0
  150. package/dist/shared/apps/selection.js +9 -0
  151. package/dist/shared/apps/store-package.d.ts +31 -0
  152. package/dist/shared/apps/store-package.js +34 -0
  153. package/dist/shared/channel/schema.d.ts +24 -0
  154. package/dist/shared/channel/secrets.d.ts +17 -0
  155. package/dist/shared/paths/pc-installs.d.ts +1 -1
  156. package/dist/shared/paths/pc-installs.js +1 -0
  157. package/dist/shared/readiness.d.ts +2 -2
  158. package/dist/shared/readiness.js +2 -2
  159. package/dist/shared/unity/asset-cache.d.ts +20 -0
  160. package/dist/shared/unity/asset-cache.js +44 -0
  161. package/dist/shared/unity/cli.d.ts +40 -0
  162. package/dist/shared/unity/cli.js +77 -0
  163. package/dist/shared/unity/editors.d.ts +46 -0
  164. package/dist/shared/unity/editors.js +89 -0
  165. package/dist/shared/unity/guide.d.ts +9 -0
  166. package/dist/shared/unity/guide.js +84 -0
  167. package/dist/shared/unity/index.d.ts +9 -0
  168. package/dist/shared/unity/index.js +11 -0
  169. package/dist/shared/unity/manifest.d.ts +10 -0
  170. package/dist/shared/unity/manifest.js +53 -0
  171. package/dist/shared/unity/mcp.d.ts +8 -0
  172. package/dist/shared/unity/mcp.js +12 -0
  173. package/dist/shared/unity/release.d.ts +28 -0
  174. package/dist/shared/unity/release.js +83 -0
  175. package/dist/shared/unity/status.d.ts +59 -0
  176. package/dist/shared/unity/status.js +45 -0
  177. package/dist/shared/unity/version.d.ts +27 -0
  178. package/dist/shared/unity/version.js +75 -0
  179. package/dist/web/index.html +1 -1
  180. package/dist/web/{main-FNE6XEHA.js → main-VPMYTSUL.js} +67 -67
  181. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  182. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  183. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  184. package/native/scripts/get-store-package.ps1 +58 -0
  185. package/package.json +1 -1
  186. package/sandbox/addons/openupm-cli/package-lock.json +2921 -0
  187. package/sandbox/addons/openupm-cli/package.json +7 -0
  188. package/sandbox/addons/unity-mcp/requirements.in +1 -0
  189. package/sandbox/addons/unity-mcp/requirements.txt +1165 -0
  190. package/sandbox/csharp/AppActivation.cs +38 -0
  191. package/sandbox/scripts/install-store-package.ps1 +28 -0
  192. package/sandbox/scripts/start-store-app.ps1 +29 -0
  193. package/schemas/config.schema.json +61 -6
  194. package/schemas/sandbox-config.schema.json +38 -5
  195. package/dist/guest/devtools/unity.d.ts +0 -2
  196. 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
+ });
@@ -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-JXL6ZOO3.js";
6
+ import "./chunk-6WWMBCT2.js";
7
7
 
8
8
  // src/bin/agent-sandbox.ts
9
9
  run(process.argv.slice(2));