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,89 @@
1
+ // The Unity editors a sandbox has (this PC's, read-only, and the data disk's) and which one a project opens with. An
2
+ // editor is never swapped for another version: opening a project in another Unity upgrades its files. Pure apart from
3
+ // the file reads it is given.
4
+ import path from 'node:path';
5
+ import { UNITY_VERSION } from './version.js';
6
+ const win = path.win32;
7
+ export const ORIGIN_LABELS = {
8
+ pc: 'this PC, read-only',
9
+ data: 'the data disk, kept between sandboxes',
10
+ };
11
+ /** Folders under Editor\Data\PlaybackEngines that show a module is installed (windows-il2cpp: verify on an editor). */
12
+ const MODULE_FOLDERS = {
13
+ android: ['AndroidPlayer'],
14
+ webgl: ['WebGLSupport'],
15
+ ios: ['iOSSupport'],
16
+ 'windows-il2cpp': [
17
+ 'windowsstandalonesupport\\Variations\\win64_player_development_il2cpp',
18
+ 'windowsstandalonesupport\\Variations\\win64_development_il2cpp',
19
+ ],
20
+ };
21
+ /** The modules installed in an editor folder. */
22
+ export function editorModules(root, fs) {
23
+ const engines = win.join(root, 'Editor', 'Data', 'PlaybackEngines');
24
+ return Object.entries(MODULE_FOLDERS)
25
+ .filter(([, folders]) => folders.some((f) => fs.exists(win.join(engines, f))))
26
+ .map(([id]) => id);
27
+ }
28
+ /** A data editor counts once its install finished: .complete.json (ours) or a Hub/CLI modules.json. */
29
+ function isComplete(root, fs) {
30
+ return fs.exists(win.join(root, '.complete.json')) || fs.exists(win.join(root, 'modules.json'));
31
+ }
32
+ export function findEditors(search, fs) {
33
+ const found = [];
34
+ for (const m of search.mapped) {
35
+ const exe = win.join(m.guest, 'Editor', 'Unity.exe');
36
+ if (UNITY_VERSION.test(m.id) && fs.exists(exe))
37
+ found.push({
38
+ version: m.id,
39
+ root: m.guest,
40
+ exe,
41
+ origin: 'pc',
42
+ writable: false,
43
+ modules: editorModules(m.guest, fs),
44
+ });
45
+ }
46
+ for (const name of fs.list(search.dataRoot)) {
47
+ if (name.startsWith('.staging-') || !UNITY_VERSION.test(name))
48
+ continue;
49
+ const root = win.join(search.dataRoot, name);
50
+ const exe = win.join(root, 'Editor', 'Unity.exe');
51
+ if (isComplete(root, fs) && fs.exists(exe))
52
+ found.push({ version: name, root, exe, origin: 'data', writable: true, modules: editorModules(root, fs) });
53
+ }
54
+ return found;
55
+ }
56
+ /**
57
+ * The editor for a project: exactly the version it asks for. This PC's (read-only) when it has every module, then the
58
+ * data disk's, else install it when the licence is accepted. A PC editor that lacks modules is still used when nothing
59
+ * can be installed.
60
+ */
61
+ export function chooseEditor(editors, wanted, modules, canInstall) {
62
+ if (!wanted)
63
+ return { editor: null, install: null, reason: 'no ProjectSettings/ProjectVersion.txt' };
64
+ const v = wanted.version;
65
+ const same = editors.filter((e) => e.version === v);
66
+ const pc = same.find((e) => e.origin === 'pc');
67
+ const data = same.find((e) => e.origin === 'data');
68
+ const missing = (e) => modules.find((m) => !e?.modules.includes(m));
69
+ const asks = 'the project asks for ' + v;
70
+ if (pc && missing(pc) === undefined)
71
+ return { editor: pc, install: null, reason: asks };
72
+ if (data) {
73
+ const lack = pc ? missing(pc) : undefined;
74
+ return {
75
+ editor: data,
76
+ install: null,
77
+ reason: lack === undefined ? asks : v + ' from this PC lacks ' + lack + '; using the data disk copy',
78
+ };
79
+ }
80
+ if (canInstall)
81
+ return { editor: null, install: wanted, reason: v + ' is not here yet; installing it on the data disk' };
82
+ if (pc)
83
+ return { editor: pc, install: null, reason: asks };
84
+ return {
85
+ editor: null,
86
+ install: null,
87
+ reason: v + ' is not on this PC or the data disk; accept the Unity licence (Unity set-up settings) to install it',
88
+ };
89
+ }
@@ -0,0 +1,9 @@
1
+ import type { UnityStatus } from './status.js';
2
+ export interface UnityGuideProject {
3
+ /** The project's folder in the sandbox. */
4
+ readonly workspace: string;
5
+ }
6
+ /** The guide's Unity lines for these projects (none when Unity is not set up). */
7
+ export declare function unityGuideLines(status: UnityStatus, projects: readonly UnityGuideProject[]): string[];
8
+ /** One line about a project's editor, for the status the agent and the page show. */
9
+ export declare function unitySummaryLine(status: UnityStatus, workspace: string): string;
@@ -0,0 +1,84 @@
1
+ // The Unity lines of the sandbox guide (the managed CLAUDE.md and AGENTS.md): which editor each project opens with and
2
+ // the plain commands an agent builds, tests and imports with. Pure, from the status the sandbox wrote (status.ts).
3
+ import path from 'node:path';
4
+ import { ORIGIN_LABELS } from './editors.js';
5
+ const win = path.win32;
6
+ function sameDir(a, b) {
7
+ return a.toLowerCase() === b.toLowerCase();
8
+ }
9
+ function exeOf(root) {
10
+ return win.join(root, 'Editor', 'Unity.exe');
11
+ }
12
+ /** The guide's Unity lines for these projects (none when Unity is not set up). */
13
+ export function unityGuideLines(status, projects) {
14
+ const lines = [];
15
+ const several = projects.length > 1;
16
+ let first = null;
17
+ const bridges = new Set();
18
+ let withoutBridge = false;
19
+ for (const p of projects) {
20
+ const choice = status.projects.find((s) => sameDir(s.workspace, p.workspace));
21
+ const editor = choice?.editor ? status.editors.find((e) => sameDir(e.root, choice.editor)) : undefined;
22
+ if (!choice || !editor)
23
+ continue;
24
+ const exe = exeOf(editor.root);
25
+ const name = several ? ' (' + win.basename(p.workspace) + ')' : '';
26
+ first ??= { exe, workspace: p.workspace };
27
+ lines.push('- Unity' +
28
+ name +
29
+ ': editor ' +
30
+ editor.version +
31
+ ' at `' +
32
+ exe +
33
+ '` (' +
34
+ ORIGIN_LABELS[editor.origin] +
35
+ '; ' +
36
+ choice.reason +
37
+ '). Build or run code without opening it: `"' +
38
+ exe +
39
+ '" -batchmode -nographics -quit -projectPath "' +
40
+ p.workspace +
41
+ '" -executeMethod <Class.Method> -logFile -`; tests: `-runTests -testPlatform EditMode -testResults <file>` (without -quit). Opening the project in another Unity version upgrades its files: never do that without asking the user.' +
42
+ (editor.writable ? '' : " It is the user's PC copy, read-only: never add modules to it."));
43
+ if (choice.bridge)
44
+ bridges.add(choice.bridge);
45
+ else
46
+ withoutBridge = true;
47
+ }
48
+ if (!lines.length)
49
+ return lines;
50
+ if (status.cli)
51
+ lines.push('- `unity` (Unity CLI ' +
52
+ status.cli.version +
53
+ ') is on PATH: `unity editors -i`, `unity build`, `unity test`, `unity open <project>` (opens it signed in).');
54
+ if (bridges.has('pipeline'))
55
+ lines.push('- The open editor can be driven with `unity command` and `unity eval` (Unity Pipeline package); MCP clients use sbx-unity.');
56
+ if (bridges.has('coplay'))
57
+ lines.push('- The open editor can be driven with MCP for Unity (sbx-unity-mcp in Apps).');
58
+ if (withoutBridge)
59
+ lines.push("- To drive a running editor (scenes, GameObjects, play mode) the project needs an editor bridge package: ask the user to add it on the sandbox tool's Unity page. Never change Packages/manifest.json yourself unless asked.");
60
+ if (status.pcAssets.length && first)
61
+ lines.push("- The user's Unity Asset Store downloads (read-only): `" +
62
+ status.pcAssets[0] +
63
+ '`; import one with the editor closed: `"' +
64
+ first.exe +
65
+ '" -batchmode -nographics -quit -projectPath "' +
66
+ first.workspace +
67
+ '" -importPackage "<file>"`.' +
68
+ (status.assetCache.dir ? ' Downloads made here go to `' + status.assetCache.dir + '` (kept).' : ''));
69
+ if (status.licence.state !== 'active')
70
+ lines.push('- Unity licence: ' +
71
+ status.licence.detail +
72
+ ". Batch mode fails without one: ask the user to sign in to Unity on the sandbox tool's Unity page.");
73
+ return lines;
74
+ }
75
+ /** One line about a project's editor, for the status the agent and the page show. */
76
+ export function unitySummaryLine(status, workspace) {
77
+ const choice = status.projects.find((s) => sameDir(s.workspace, workspace));
78
+ if (!choice)
79
+ return 'Unity: not set up for this project';
80
+ const editor = choice.editor ? status.editors.find((e) => sameDir(e.root, choice.editor)) : undefined;
81
+ return editor
82
+ ? 'Unity ' + editor.version + ' (' + ORIGIN_LABELS[editor.origin] + '; ' + choice.reason + ')'
83
+ : 'Unity: no editor (' + choice.reason + ')';
84
+ }
@@ -0,0 +1,9 @@
1
+ export * from './version.js';
2
+ export * from './editors.js';
3
+ export * from './release.js';
4
+ export * from './cli.js';
5
+ export * from './mcp.js';
6
+ export * from './manifest.js';
7
+ export * from './asset-cache.js';
8
+ export * from './status.js';
9
+ export * from './guide.js';
@@ -0,0 +1,11 @@
1
+ // Unity editors, the release API, the Unity CLI, the editor bridge and the Unity status, shared by the sandbox and this
2
+ // PC (pure).
3
+ export * from './version.js';
4
+ export * from './editors.js';
5
+ export * from './release.js';
6
+ export * from './cli.js';
7
+ export * from './mcp.js';
8
+ export * from './manifest.js';
9
+ export * from './asset-cache.js';
10
+ export * from './status.js';
11
+ export * from './guide.js';
@@ -0,0 +1,10 @@
1
+ export declare const PIPELINE_PACKAGE = "com.unity.pipeline";
2
+ export declare const COPLAY_PACKAGE = "com.coplaydev.unity-mcp";
3
+ export declare const COPLAY_PACKAGE_URL = "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#v10.3.0";
4
+ /** The Pipeline version added when none is given (0.10 is the one the plan was written against). */
5
+ export declare const DEFAULT_PIPELINE_VERSION = "0.10.0";
6
+ export type Bridge = 'pipeline' | 'coplay';
7
+ /** Which bridge the project has, or null (also for a file that is not a manifest). */
8
+ export declare function bridgeOf(text: string): Bridge | null;
9
+ /** The manifest with the bridge added or removed: key order, 2-space indent, trailing newline and CRLF kept. */
10
+ export declare function withBridge(text: string, bridge: Bridge, on: boolean, pipelineVersion?: string): string;
@@ -0,0 +1,53 @@
1
+ // Packages/manifest.json and the editor bridge: the package that lets `unity command` / `unity eval` (Unity Pipeline,
2
+ // Unity 6 and later) or MCP for Unity (Coplay) drive a running editor. Edited only when the user asks. Pure text.
3
+ export const PIPELINE_PACKAGE = 'com.unity.pipeline';
4
+ export const COPLAY_PACKAGE = 'com.coplaydev.unity-mcp';
5
+ export const COPLAY_PACKAGE_URL = 'https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#v10.3.0';
6
+ /** The Pipeline version added when none is given (0.10 is the one the plan was written against). */
7
+ export const DEFAULT_PIPELINE_VERSION = '0.10.0';
8
+ const MAX_MANIFEST_BYTES = 1024 * 1024;
9
+ const SEMVER = /^\d{1,6}\.\d{1,6}\.\d{1,6}(-[0-9A-Za-z.-]{1,50})?(\+[0-9A-Za-z.-]{1,50})?$/;
10
+ function parseManifest(text) {
11
+ if (Buffer.byteLength(text) > MAX_MANIFEST_BYTES)
12
+ throw new Error('Packages/manifest.json is over 1 MB');
13
+ const json = JSON.parse(text.replace(/^\uFEFF/, ''));
14
+ if (typeof json !== 'object' || json === null || Array.isArray(json))
15
+ throw new Error('Packages/manifest.json is not an object');
16
+ const m = json;
17
+ if (m.dependencies !== undefined &&
18
+ (typeof m.dependencies !== 'object' || m.dependencies === null || Array.isArray(m.dependencies)))
19
+ throw new Error('Packages/manifest.json: dependencies is not an object');
20
+ return m;
21
+ }
22
+ /** Which bridge the project has, or null (also for a file that is not a manifest). */
23
+ export function bridgeOf(text) {
24
+ try {
25
+ const deps = parseManifest(text).dependencies ?? {};
26
+ if (PIPELINE_PACKAGE in deps)
27
+ return 'pipeline';
28
+ if (COPLAY_PACKAGE in deps)
29
+ return 'coplay';
30
+ return null;
31
+ }
32
+ catch {
33
+ // Not JSON or not a manifest: no bridge to report.
34
+ return null;
35
+ }
36
+ }
37
+ /** The manifest with the bridge added or removed: key order, 2-space indent, trailing newline and CRLF kept. */
38
+ export function withBridge(text, bridge, on, pipelineVersion) {
39
+ const m = parseManifest(text);
40
+ const name = bridge === 'pipeline' ? PIPELINE_PACKAGE : COPLAY_PACKAGE;
41
+ if (on) {
42
+ const version = pipelineVersion ?? DEFAULT_PIPELINE_VERSION;
43
+ if (bridge === 'pipeline' && !SEMVER.test(version))
44
+ throw new Error('not a package version: ' + version);
45
+ const deps = (m.dependencies ??= {});
46
+ deps[name] = bridge === 'pipeline' ? version : COPLAY_PACKAGE_URL;
47
+ }
48
+ else if (m.dependencies) {
49
+ delete m.dependencies[name];
50
+ }
51
+ const out = JSON.stringify(m, null, 2) + '\n';
52
+ return text.includes('\r\n') ? out.replace(/\n/g, '\r\n') : out;
53
+ }
@@ -0,0 +1,8 @@
1
+ export declare const UNITY_MCP_NAME = "sbx-unity";
2
+ export interface UnityMcpServer {
3
+ readonly type: 'stdio';
4
+ readonly command: string;
5
+ readonly args: readonly string[];
6
+ readonly env: Readonly<Record<string, string>>;
7
+ }
8
+ export declare function unityCliMcpServer(unityExe: string, projectPath: string | null): UnityMcpServer;
@@ -0,0 +1,12 @@
1
+ // The Unity CLI as an MCP server (`unity mcp --cli-tools`, stdio) for Claude Code and Codex. Read-only tools always;
2
+ // the open editor's tools when the project has the Unity Pipeline package.
3
+ import { UNITY_CLI_ENV } from './cli.js';
4
+ export const UNITY_MCP_NAME = 'sbx-unity';
5
+ export function unityCliMcpServer(unityExe, projectPath) {
6
+ return {
7
+ type: 'stdio',
8
+ command: unityExe,
9
+ args: ['mcp', '--cli-tools'],
10
+ env: { ...UNITY_CLI_ENV, ...(projectPath ? { UNITY_PROJECT_PATH: projectPath } : {}) },
11
+ };
12
+ }
@@ -0,0 +1,28 @@
1
+ /** The request for one editor version (Windows, x86_64, one result). */
2
+ export declare function releaseUrl(version: string): string;
3
+ export interface UnityRelease {
4
+ readonly version: string;
5
+ readonly changeset: string;
6
+ readonly editor: {
7
+ readonly url: string;
8
+ /** As Unity writes it: "md5-<base64>". */
9
+ readonly integrity: string | null;
10
+ readonly downloadBytes: number;
11
+ readonly installedBytes: number;
12
+ };
13
+ readonly modules: readonly {
14
+ readonly id: string;
15
+ readonly downloadBytes: number;
16
+ readonly installedBytes: number;
17
+ readonly integrity: string | null;
18
+ /** The ids of its sub-modules (android: the SDK, NDK and OpenJDK parts). */
19
+ readonly children: readonly string[];
20
+ /** The sub-modules' sizes together. */
21
+ readonly childDownloadBytes: number;
22
+ readonly childInstalledBytes: number;
23
+ }[];
24
+ }
25
+ /** The first release an API answer holds (the EXE installer), or null when it holds none or has another shape. */
26
+ export declare function pickRelease(json: unknown): UnityRelease | null;
27
+ /** Room an install needs: (editor download + installed + the chosen modules with their children) plus 10%. */
28
+ export declare function installNeedBytes(r: UnityRelease, modules: readonly string[]): number;
@@ -0,0 +1,83 @@
1
+ // Unity's release API (services.api.unity.com): the editor installer, its changeset and its modules for one version.
2
+ // The answer is untrusted JSON: it is parsed with zod, every list and text bounded.
3
+ import { z } from 'zod';
4
+ import { UNITY_CHANGESET } from './version.js';
5
+ const API = 'https://services.api.unity.com/unity/editor/release/v1/releases';
6
+ const MARGIN = 1.1;
7
+ /** The request for one editor version (Windows, x86_64, one result). */
8
+ export function releaseUrl(version) {
9
+ return API + '?version=' + encodeURIComponent(version) + '&platform=WINDOWS&architecture=X86_64&limit=1';
10
+ }
11
+ const Size = z.object({ value: z.number().min(0) });
12
+ const Text = (max) => z.string().max(max);
13
+ const SubModule = z.lazy(() => z.object({
14
+ id: z.string().regex(/^[A-Za-z0-9._-]{1,80}$/),
15
+ downloadSize: Size.optional(),
16
+ installedSize: Size.optional(),
17
+ integrity: Text(200).optional(),
18
+ subModules: z.array(SubModule).max(50).optional(),
19
+ }));
20
+ const ReleaseResponseSchema = z.object({
21
+ results: z
22
+ .array(z.object({
23
+ version: Text(40),
24
+ shortRevision: z.string().regex(UNITY_CHANGESET),
25
+ downloads: z
26
+ .array(z.object({
27
+ url: z.string().url().max(2000).startsWith('https://'),
28
+ type: Text(20),
29
+ integrity: Text(200).optional(),
30
+ downloadSize: Size,
31
+ installedSize: Size,
32
+ modules: z.array(SubModule).max(200).default([]),
33
+ }))
34
+ .min(1)
35
+ .max(10),
36
+ }))
37
+ .max(10),
38
+ });
39
+ function flatten(m, depth = 0) {
40
+ if (depth > 3)
41
+ return [];
42
+ return (m.subModules ?? []).flatMap((s) => [s, ...flatten(s, depth + 1)]);
43
+ }
44
+ /** The first release an API answer holds (the EXE installer), or null when it holds none or has another shape. */
45
+ export function pickRelease(json) {
46
+ const parsed = ReleaseResponseSchema.safeParse(json);
47
+ if (!parsed.success)
48
+ return null;
49
+ const r = parsed.data.results[0];
50
+ const d = r?.downloads.find((x) => x.type.toUpperCase() === 'EXE') ?? r?.downloads[0];
51
+ if (!r || !d)
52
+ return null;
53
+ return {
54
+ version: r.version,
55
+ changeset: r.shortRevision,
56
+ editor: {
57
+ url: d.url,
58
+ integrity: d.integrity ?? null,
59
+ downloadBytes: d.downloadSize.value,
60
+ installedBytes: d.installedSize.value,
61
+ },
62
+ modules: d.modules.map((m) => {
63
+ const kids = flatten(m);
64
+ return {
65
+ id: m.id,
66
+ downloadBytes: m.downloadSize?.value ?? 0,
67
+ installedBytes: m.installedSize?.value ?? 0,
68
+ integrity: m.integrity ?? null,
69
+ children: kids.map((k) => k.id),
70
+ childDownloadBytes: kids.reduce((n, k) => n + (k.downloadSize?.value ?? 0), 0),
71
+ childInstalledBytes: kids.reduce((n, k) => n + (k.installedSize?.value ?? 0), 0),
72
+ };
73
+ }),
74
+ };
75
+ }
76
+ /** Room an install needs: (editor download + installed + the chosen modules with their children) plus 10%. */
77
+ export function installNeedBytes(r, modules) {
78
+ let bytes = r.editor.downloadBytes + r.editor.installedBytes;
79
+ for (const m of r.modules)
80
+ if (modules.includes(m.id))
81
+ bytes += m.downloadBytes + m.installedBytes + m.childDownloadBytes + m.childInstalledBytes;
82
+ return Math.ceil(bytes * MARGIN);
83
+ }
@@ -0,0 +1,59 @@
1
+ import { z } from 'zod';
2
+ /** The file in tools\unity. */
3
+ export declare const UNITY_STATUS_FILE = "status.json";
4
+ export declare const UnityStatusSchema: z.ZodObject<{
5
+ updatedAt: z.ZodString;
6
+ cli: z.ZodNullable<z.ZodObject<{
7
+ version: z.ZodString;
8
+ path: z.ZodString;
9
+ }, z.core.$strip>>;
10
+ editors: z.ZodArray<z.ZodObject<{
11
+ version: z.ZodString;
12
+ root: z.ZodString;
13
+ origin: z.ZodEnum<{
14
+ pc: "pc";
15
+ data: "data";
16
+ }>;
17
+ writable: z.ZodBoolean;
18
+ modules: z.ZodArray<z.ZodString>;
19
+ sizeBytes: z.ZodNullable<z.ZodNumber>;
20
+ }, z.core.$strip>>;
21
+ projects: z.ZodArray<z.ZodObject<{
22
+ workspace: z.ZodString;
23
+ version: z.ZodNullable<z.ZodString>;
24
+ editor: z.ZodNullable<z.ZodString>;
25
+ reason: z.ZodString;
26
+ bridge: z.ZodNullable<z.ZodEnum<{
27
+ pipeline: "pipeline";
28
+ coplay: "coplay";
29
+ }>>;
30
+ }, z.core.$strip>>;
31
+ licence: z.ZodObject<{
32
+ state: z.ZodEnum<{
33
+ unknown: "unknown";
34
+ none: "none";
35
+ active: "active";
36
+ invalid: "invalid";
37
+ }>;
38
+ kind: z.ZodString;
39
+ detail: z.ZodString;
40
+ user: z.ZodNullable<z.ZodString>;
41
+ }, z.core.$strip>;
42
+ assetCache: z.ZodObject<{
43
+ mode: z.ZodEnum<{
44
+ pc: "pc";
45
+ persisted: "persisted";
46
+ local: "local";
47
+ }>;
48
+ dir: z.ZodNullable<z.ZodString>;
49
+ }, z.core.$strip>;
50
+ pcAssets: z.ZodArray<z.ZodString>;
51
+ hub: z.ZodObject<{
52
+ installed: z.ZodBoolean;
53
+ path: z.ZodNullable<z.ZodString>;
54
+ }, z.core.$strip>;
55
+ mcp: z.ZodNullable<z.ZodString>;
56
+ skills: z.ZodBoolean;
57
+ problem: z.ZodNullable<z.ZodString>;
58
+ }, z.core.$strip>;
59
+ export type UnityStatus = z.infer<typeof UnityStatusSchema>;
@@ -0,0 +1,45 @@
1
+ // What the sandbox last found for Unity (tools\unity\status.json): the CLI, the editors, which editor each project
2
+ // opens with, the licence, the Asset Store cache. The sandbox writes it; the Unity page on this PC reads it with this
3
+ // schema (bounded strings and lists) and prints its text through safeText.
4
+ import { z } from 'zod';
5
+ /** The file in tools\unity. */
6
+ export const UNITY_STATUS_FILE = 'status.json';
7
+ const Text = (max = 400) => z.string().max(max);
8
+ export const UnityStatusSchema = z.object({
9
+ updatedAt: Text(40),
10
+ cli: z.object({ version: Text(40), path: Text() }).nullable(),
11
+ editors: z
12
+ .array(z.object({
13
+ version: Text(40),
14
+ root: Text(),
15
+ origin: z.enum(['pc', 'data']),
16
+ writable: z.boolean(),
17
+ modules: z.array(Text(60)).max(20),
18
+ sizeBytes: z.number().int().min(0).nullable(),
19
+ }))
20
+ .max(32),
21
+ projects: z
22
+ .array(z.object({
23
+ workspace: Text(),
24
+ /** The version its ProjectVersion.txt asks for. */
25
+ version: Text(40).nullable(),
26
+ /** The root of the editor it opens with, null when there is none for it. */
27
+ editor: Text().nullable(),
28
+ reason: Text(),
29
+ bridge: z.enum(['pipeline', 'coplay']).nullable(),
30
+ }))
31
+ .max(64),
32
+ licence: z.object({
33
+ state: z.enum(['active', 'none', 'invalid', 'unknown']),
34
+ kind: Text(60),
35
+ detail: Text(),
36
+ user: Text(256).nullable(),
37
+ }),
38
+ assetCache: z.object({ mode: z.enum(['persisted', 'pc', 'local']), dir: Text().nullable() }),
39
+ pcAssets: z.array(Text()).max(4),
40
+ hub: z.object({ installed: z.boolean(), path: Text().nullable() }),
41
+ /** The MCP server's name when it was registered. */
42
+ mcp: Text(100).nullable(),
43
+ skills: z.boolean(),
44
+ problem: Text(600).nullable(),
45
+ });
@@ -0,0 +1,27 @@
1
+ export declare const UNITY_VERSION: RegExp;
2
+ /** A changeset (short revision) as Unity prints it. */
3
+ export declare const UNITY_CHANGESET: RegExp;
4
+ export interface UnityVersion {
5
+ readonly major: number;
6
+ readonly minor: number;
7
+ readonly patch: number;
8
+ /** a (alpha), b (beta), f (final), p (patch). */
9
+ readonly stage: 'a' | 'b' | 'f' | 'p';
10
+ readonly build: number;
11
+ }
12
+ export declare function parseUnityVersion(text: string): UnityVersion | null;
13
+ /** Negative when a is older than b, 0 when equal; an unreadable version sorts first. */
14
+ export declare function compareUnityVersions(a: string, b: string): number;
15
+ export interface ProjectVersion {
16
+ readonly version: string;
17
+ readonly changeset: string | null;
18
+ }
19
+ /** The editor a ProjectVersion.txt asks for: m_EditorVersion, and the changeset of m_EditorVersionWithRevision. */
20
+ export declare function parseProjectVersion(text: string): ProjectVersion | null;
21
+ /**
22
+ * The editor to open a project with: its own ProjectVersion.txt always wins (another version upgrades its files); the
23
+ * setting's version ("project" or e.g. "6000.0.32f1") is only for folders without one.
24
+ */
25
+ export declare function wantedVersion(projectFile: string | null, setting: string): ProjectVersion | null;
26
+ /** The Unity Pipeline package (and so `unity command` / `unity eval`) needs Unity 6 or later. */
27
+ export declare function supportsPipeline(version: string): boolean;
@@ -0,0 +1,75 @@
1
+ // Unity editor versions ("6000.0.58f2") and the ProjectSettings/ProjectVersion.txt that names one. Pure; the text read
2
+ // from a project is bounded (the first 50 lines of at most 64 KB).
3
+ export const UNITY_VERSION = /^(\d{1,4})\.(\d{1,3})\.(\d{1,3})([abfp])(\d{1,3})$/;
4
+ /** A changeset (short revision) as Unity prints it. */
5
+ export const UNITY_CHANGESET = /^[0-9a-f]{12}$/;
6
+ const MAX_PROJECT_VERSION_BYTES = 64 * 1024;
7
+ const MAX_PROJECT_VERSION_LINES = 50;
8
+ const STAGES = 'abfp';
9
+ export function parseUnityVersion(text) {
10
+ const m = UNITY_VERSION.exec(text);
11
+ if (!m)
12
+ return null;
13
+ return {
14
+ major: Number(m[1]),
15
+ minor: Number(m[2]),
16
+ patch: Number(m[3]),
17
+ stage: m[4],
18
+ build: Number(m[5]),
19
+ };
20
+ }
21
+ /** Negative when a is older than b, 0 when equal; an unreadable version sorts first. */
22
+ export function compareUnityVersions(a, b) {
23
+ const x = parseUnityVersion(a);
24
+ const y = parseUnityVersion(b);
25
+ if (!x || !y)
26
+ return x ? 1 : y ? -1 : a.localeCompare(b);
27
+ return (x.major - y.major ||
28
+ x.minor - y.minor ||
29
+ x.patch - y.patch ||
30
+ STAGES.indexOf(x.stage) - STAGES.indexOf(y.stage) ||
31
+ x.build - y.build);
32
+ }
33
+ /** The editor a ProjectVersion.txt asks for: m_EditorVersion, and the changeset of m_EditorVersionWithRevision. */
34
+ export function parseProjectVersion(text) {
35
+ let version = null;
36
+ let changeset = null;
37
+ const lines = text.slice(0, MAX_PROJECT_VERSION_BYTES).split('\n', MAX_PROJECT_VERSION_LINES);
38
+ for (const raw of lines) {
39
+ const line = raw.replace(/^\uFEFF/, '').trim();
40
+ if (line.startsWith('m_EditorVersionWithRevision:')) {
41
+ const rest = line.slice('m_EditorVersionWithRevision:'.length).trim();
42
+ const open = rest.indexOf('(');
43
+ const close = rest.indexOf(')', open);
44
+ const v = (open < 0 ? rest : rest.slice(0, open)).trim();
45
+ if (UNITY_VERSION.test(v))
46
+ version ??= v;
47
+ if (open >= 0 && close > open) {
48
+ const cs = rest.slice(open + 1, close).trim();
49
+ if (UNITY_CHANGESET.test(cs))
50
+ changeset = cs;
51
+ }
52
+ }
53
+ else if (line.startsWith('m_EditorVersion:')) {
54
+ const v = line.slice('m_EditorVersion:'.length).trim();
55
+ if (UNITY_VERSION.test(v))
56
+ version = v;
57
+ }
58
+ }
59
+ return version ? { version, changeset } : null;
60
+ }
61
+ /**
62
+ * The editor to open a project with: its own ProjectVersion.txt always wins (another version upgrades its files); the
63
+ * setting's version ("project" or e.g. "6000.0.32f1") is only for folders without one.
64
+ */
65
+ export function wantedVersion(projectFile, setting) {
66
+ const own = projectFile === null ? null : parseProjectVersion(projectFile);
67
+ if (own)
68
+ return own;
69
+ return setting !== 'project' && UNITY_VERSION.test(setting) ? { version: setting, changeset: null } : null;
70
+ }
71
+ /** The Unity Pipeline package (and so `unity command` / `unity eval`) needs Unity 6 or later. */
72
+ export function supportsPipeline(version) {
73
+ const v = parseUnityVersion(version);
74
+ return v !== null && v.major >= 6000;
75
+ }
@@ -6,7 +6,7 @@
6
6
  <title>Sandbox</title>
7
7
  <link rel="icon" href="data:," />
8
8
  <link rel="stylesheet" href="/main-XT7T6ODM.css" />
9
- <script type="module" src="/main-FNE6XEHA.js"></script>
9
+ <script type="module" src="/main-VPMYTSUL.js"></script>
10
10
  </head>
11
11
  <body>
12
12
  <div id="root"></div>