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.
- package/config.example.json +12 -3
- package/dist/app/features/accounts/services.def.d.ts +1 -1
- package/dist/app/features/accounts/services.def.js +1 -1
- package/dist/app/features/defs.d.ts +198 -3
- package/dist/app/features/defs.js +8 -0
- package/dist/app/features/settings/categories.def.js +1 -0
- package/dist/app/features/unity/index.d.ts +2 -0
- package/dist/app/features/unity/index.js +3 -0
- package/dist/app/features/unity/unity.def.d.ts +294 -0
- package/dist/app/features/unity/unity.def.js +471 -0
- package/dist/app/features/unity/unity.run.d.ts +142 -0
- package/dist/app/features/unity/unity.run.js +363 -0
- package/dist/app/features/unreal/unreal.run.js +13 -3
- package/dist/app/registry.js +6 -0
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-G3TG4ZT7.js → chunk-4TLSWPG7.js} +1 -1
- package/dist/bin/{chunk-3GAXLATK.js → chunk-QUHNXHVG.js} +72 -5
- package/dist/bin/{chunk-F4PVBN23.js → chunk-RSIX7C6D.js} +3633 -1972
- package/dist/bin/claude-host.js +2 -2
- package/dist/bin/claude-sbx.js +2 -2
- package/dist/bin/codex-sbx.js +2 -2
- package/dist/bin/gemini-sbx.js +2 -2
- package/dist/bin/sandbox.js +2 -2
- package/dist/bin/sbx-hide.js +2 -2
- package/dist/bin/sbx-show.js +2 -2
- package/dist/cli/commands/launch/launch.js +14 -1
- package/dist/cli/commands/registry.js +3 -0
- package/dist/cli/manifest.d.ts +33 -1
- package/dist/cli/manifest.js +32 -1
- package/dist/cli/menu/coverage-exempt.js +1 -0
- package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
- package/dist/cli/menu/pages/sandbox-page.js +40 -3
- package/dist/cli/menu/views.js +2 -0
- package/dist/core/config/defaults.js +16 -3
- package/dist/core/schema/accounts.d.ts +3 -0
- package/dist/core/schema/accounts.js +1 -0
- package/dist/core/schema/config.d.ts +29 -13
- package/dist/core/schema/github.d.ts +1 -0
- package/dist/core/schema/github.js +3 -0
- package/dist/core/schema/pc-installs.d.ts +2 -1
- package/dist/core/schema/pc-installs.js +6 -1
- package/dist/core/schema/toolchains.d.ts +8 -0
- package/dist/core/schema/toolchains.js +19 -4
- package/dist/guest/accounts/adapters.js +2 -0
- package/dist/guest/accounts/apply.d.ts +4 -2
- package/dist/guest/accounts/kinds/perplexityapp.d.ts +7 -0
- package/dist/guest/accounts/kinds/perplexityapp.js +53 -0
- package/dist/guest/accounts/kinds/unity.d.ts +27 -0
- package/dist/guest/accounts/kinds/unity.js +164 -7
- package/dist/guest/apps/app-installer.js +18 -0
- package/dist/guest/apps/store-app.d.ts +4 -0
- package/dist/guest/apps/store-app.js +19 -0
- package/dist/guest/apps/store-check.d.ts +8 -0
- package/dist/guest/apps/store-check.js +33 -0
- package/dist/guest/boot/boot.js +1 -1
- package/dist/guest/boot/guide.js +18 -0
- package/dist/guest/browser/comet.d.ts +3 -1
- package/dist/guest/browser/comet.js +1 -1
- package/dist/guest/browser/perplexity-app.d.ts +12 -0
- package/dist/guest/browser/perplexity-app.js +60 -0
- package/dist/guest/daemon/daemon.js +2 -0
- package/dist/guest/devtools/devtool.d.ts +1 -1
- package/dist/guest/devtools/registry.js +1 -2
- package/dist/guest/entries/tool.js +1 -1
- package/dist/guest/setup/addon-mcp.d.ts +7 -2
- package/dist/guest/setup/addon-mcp.js +19 -9
- package/dist/guest/setup/github-token.js +4 -4
- package/dist/guest/setup/unity-agent.d.ts +11 -0
- package/dist/guest/setup/unity-agent.js +39 -0
- package/dist/guest/toolchains/engines/epic-launcher.d.ts +1 -1
- package/dist/guest/toolchains/engines/epic-launcher.js +11 -7
- package/dist/guest/toolchains/engines/unity-cli.d.ts +24 -0
- package/dist/guest/toolchains/engines/unity-cli.js +74 -0
- package/dist/guest/toolchains/engines/unity-hub.d.ts +2 -0
- package/dist/guest/toolchains/engines/unity-hub.js +27 -0
- package/dist/guest/toolchains/engines/unity-release.d.ts +4 -15
- package/dist/guest/toolchains/engines/unity-release.js +17 -5
- package/dist/guest/toolchains/engines/unity-status.d.ts +11 -0
- package/dist/guest/toolchains/engines/unity-status.js +39 -0
- package/dist/guest/toolchains/engines/unity.d.ts +20 -1
- package/dist/guest/toolchains/engines/unity.js +250 -3
- package/dist/guest/toolchains/engines/unreal-status.js +1 -1
- package/dist/guest/toolchains/native/mesa.js +35 -11
- package/dist/guest/toolchains/native/unreal.js +1 -0
- package/dist/guest/toolchains/plan.js +5 -1
- package/dist/guest/toolchains/tools/cli.js +2 -2
- package/dist/host/accounts/deliver.d.ts +3 -0
- package/dist/host/accounts/deliver.js +23 -0
- package/dist/host/accounts/kinds/perplexityapp.d.ts +2 -0
- package/dist/host/accounts/kinds/perplexityapp.js +8 -0
- package/dist/host/accounts/kinds/unity.d.ts +1 -1
- package/dist/host/accounts/kinds/unity.js +60 -8
- package/dist/host/accounts/leases.d.ts +2 -1
- package/dist/host/accounts/source.d.ts +2 -0
- package/dist/host/accounts/source.js +2 -0
- package/dist/host/accounts/sources.js +2 -0
- package/dist/host/apps/index.d.ts +1 -0
- package/dist/host/apps/index.js +2 -0
- package/dist/host/apps/store-package.d.ts +51 -0
- package/dist/host/apps/store-package.js +209 -0
- package/dist/host/github/deliver.d.ts +8 -3
- package/dist/host/github/deliver.js +42 -29
- package/dist/host/github/index.d.ts +1 -0
- package/dist/host/github/index.js +1 -0
- package/dist/host/github/pc-login.d.ts +27 -0
- package/dist/host/github/pc-login.js +129 -0
- package/dist/host/pc-installs/discover.d.ts +4 -0
- package/dist/host/pc-installs/discover.js +19 -6
- package/dist/host/pc-installs/epic.js +3 -0
- package/dist/host/pc-installs/source.d.ts +1 -1
- package/dist/host/pc-installs/unity-assets.d.ts +5 -0
- package/dist/host/pc-installs/unity-assets.js +18 -0
- package/dist/host/pc-installs/unity.d.ts +22 -0
- package/dist/host/pc-installs/unity.js +144 -2
- package/dist/host/pc-installs/unreal.d.ts +2 -0
- package/dist/host/pc-installs/unreal.js +24 -8
- package/dist/runtime/agent.js +116 -11
- package/dist/runtime/daemon.js +2205 -987
- package/dist/runtime/db.js +116 -11
- package/dist/runtime/main.js +2578 -1375
- package/dist/runtime/netproxy.js +116 -11
- package/dist/runtime/tool.js +133 -31
- package/dist/runtime/view.js +116 -11
- package/dist/selftest/agents-web.test.js +2 -1
- package/dist/selftest/engines.test.js +32 -3
- package/dist/selftest/github.test.js +64 -1
- package/dist/selftest/index.js +10 -0
- package/dist/selftest/menu-screens.js +3 -1
- package/dist/selftest/mixamo.test.js +1 -1
- package/dist/selftest/persisted-installs.test.js +1 -1
- package/dist/selftest/service-accounts.test.js +13 -2
- package/dist/selftest/store-app.test.d.ts +2 -0
- package/dist/selftest/store-app.test.js +162 -0
- package/dist/selftest/support/engine-golden.js +7 -0
- package/dist/selftest/support/fixtures.d.ts +10 -1
- package/dist/selftest/support/unity-sample.d.ts +2 -0
- package/dist/selftest/support/unity-sample.js +80 -0
- package/dist/selftest/unity-accounts.test.d.ts +2 -0
- package/dist/selftest/unity-accounts.test.js +106 -0
- package/dist/selftest/unity-agent.test.d.ts +2 -0
- package/dist/selftest/unity-agent.test.js +47 -0
- package/dist/selftest/unity-page.test.d.ts +2 -0
- package/dist/selftest/unity-page.test.js +177 -0
- package/dist/selftest/unity-setup.test.d.ts +2 -0
- package/dist/selftest/unity-setup.test.js +49 -0
- package/dist/selftest/unity.test.js +238 -3
- package/dist/shared/accounts/index.d.ts +1 -0
- package/dist/shared/accounts/index.js +1 -0
- package/dist/shared/accounts/kinds/unity.d.ts +28 -0
- package/dist/shared/accounts/kinds/unity.js +88 -0
- package/dist/shared/accounts/kinds.d.ts +2 -2
- package/dist/shared/accounts/kinds.js +23 -1
- package/dist/shared/accounts/payloads.d.ts +52 -0
- package/dist/shared/accounts/payloads.js +14 -2
- package/dist/shared/apps/catalogue-agents-web.d.ts +7 -0
- package/dist/shared/apps/catalogue-agents-web.js +23 -0
- package/dist/shared/apps/catalogue-gamedev.d.ts +3 -0
- package/dist/shared/apps/catalogue-gamedev.js +31 -0
- package/dist/shared/apps/catalogue.d.ts +8 -0
- package/dist/shared/apps/index.d.ts +3 -2
- package/dist/shared/apps/index.js +3 -2
- package/dist/shared/apps/selection.d.ts +6 -0
- package/dist/shared/apps/selection.js +9 -0
- package/dist/shared/apps/store-package.d.ts +31 -0
- package/dist/shared/apps/store-package.js +34 -0
- package/dist/shared/channel/schema.d.ts +24 -0
- package/dist/shared/channel/secrets.d.ts +17 -0
- package/dist/shared/paths/pc-installs.d.ts +1 -1
- package/dist/shared/paths/pc-installs.js +1 -0
- package/dist/shared/readiness.d.ts +2 -2
- package/dist/shared/readiness.js +2 -2
- package/dist/shared/unity/asset-cache.d.ts +20 -0
- package/dist/shared/unity/asset-cache.js +44 -0
- package/dist/shared/unity/cli.d.ts +40 -0
- package/dist/shared/unity/cli.js +77 -0
- package/dist/shared/unity/editors.d.ts +46 -0
- package/dist/shared/unity/editors.js +89 -0
- package/dist/shared/unity/guide.d.ts +9 -0
- package/dist/shared/unity/guide.js +84 -0
- package/dist/shared/unity/index.d.ts +9 -0
- package/dist/shared/unity/index.js +11 -0
- package/dist/shared/unity/manifest.d.ts +10 -0
- package/dist/shared/unity/manifest.js +53 -0
- package/dist/shared/unity/mcp.d.ts +8 -0
- package/dist/shared/unity/mcp.js +12 -0
- package/dist/shared/unity/release.d.ts +28 -0
- package/dist/shared/unity/release.js +83 -0
- package/dist/shared/unity/status.d.ts +59 -0
- package/dist/shared/unity/status.js +45 -0
- package/dist/shared/unity/version.d.ts +27 -0
- package/dist/shared/unity/version.js +75 -0
- package/dist/shared/unreal/status.d.ts +1 -0
- package/dist/shared/unreal/status.js +2 -0
- package/dist/web/index.html +1 -1
- package/dist/web/{main-FNE6XEHA.js → main-VPMYTSUL.js} +67 -67
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
- package/native/scripts/get-store-package.ps1 +58 -0
- package/package.json +1 -1
- package/sandbox/addons/openupm-cli/package-lock.json +2921 -0
- package/sandbox/addons/openupm-cli/package.json +7 -0
- package/sandbox/addons/unity-mcp/requirements.in +1 -0
- package/sandbox/addons/unity-mcp/requirements.txt +1165 -0
- package/sandbox/csharp/AppActivation.cs +38 -0
- package/sandbox/scripts/install-store-package.ps1 +28 -0
- package/sandbox/scripts/start-store-app.ps1 +29 -0
- package/schemas/config.schema.json +61 -6
- package/schemas/sandbox-config.schema.json +38 -5
- package/dist/guest/devtools/unity.d.ts +0 -2
- package/dist/guest/devtools/unity.js +0 -7
|
@@ -13,6 +13,7 @@ import { warn } from '../runtime/log.js';
|
|
|
13
13
|
import { errMsg } from '../../core/util/errors.js';
|
|
14
14
|
import { flushAccounts } from './accounts.js';
|
|
15
15
|
import { captureLauncherEngines } from '../toolchains/engines/unreal-engines.js';
|
|
16
|
+
import { captureUnityEditors } from '../toolchains/engines/unity.js';
|
|
16
17
|
const POLL_MS = 400;
|
|
17
18
|
const MEM_SAVE_MS = 120000;
|
|
18
19
|
export class Daemon {
|
|
@@ -50,6 +51,7 @@ export class Daemon {
|
|
|
50
51
|
void flushAccounts();
|
|
51
52
|
// Engines the Epic Games Launcher installed on the data disk since: kept for the next sandbox.
|
|
52
53
|
void captureLauncherEngines().catch((e) => warn('Unreal launcher save: ' + errMsg(e)));
|
|
54
|
+
void captureUnityEditors().catch((e) => warn('Unity editors: ' + errMsg(e)));
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
handle(req) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { androidTool } from './android.js';
|
|
2
2
|
import { fivemTool } from './fivem.js';
|
|
3
3
|
import { steamTool } from './steam.js';
|
|
4
|
-
|
|
5
|
-
export const DEVTOOLS = [steamTool, fivemTool, unityTool, androidTool];
|
|
4
|
+
export const DEVTOOLS = [steamTool, fivemTool, androidTool];
|
|
6
5
|
/** The helpers for the toolchains that are on (resolved by the host; "auto" counts as off here). */
|
|
7
6
|
export function devToolsFor(t) {
|
|
8
7
|
return DEVTOOLS.filter((tool) => t[tool.name].enabled === true);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The sbx-<name> helpers (sbx-steam, sbx-fivem, sbx-
|
|
1
|
+
// The sbx-<name> helpers (sbx-steam, sbx-fivem, sbx-android): `tool.js <name> <args...>` runs that helper.
|
|
2
2
|
import { errMsg } from '../../core/util/errors.js';
|
|
3
3
|
import { DEVTOOLS, devToolByName } from '../devtools/registry.js';
|
|
4
4
|
const [name, ...args] = process.argv.slice(2);
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import { type McpServerConfig } from '../../shared/apps/index.js';
|
|
1
2
|
/** The Unreal editor's MCP server (Unreal 5.8's ModelContextProtocol plugin), set by the Unreal engines step. */
|
|
2
3
|
export declare const UNREAL_MCP_NAME = "sbx-unreal-editor";
|
|
4
|
+
/** The engines' own MCP servers: not an Apps add-on, set by the engine's step each boot, and sent to Codex too. */
|
|
5
|
+
export declare const ENGINE_MCP_NAMES: readonly ["sbx-unreal-editor", "sbx-unity"];
|
|
3
6
|
/**
|
|
4
|
-
* Adds
|
|
5
|
-
* answers while the editor runs
|
|
7
|
+
* Adds an engine's MCP server to the next writeAddonMcpServers, or takes it away (null). A server for an editor only
|
|
8
|
+
* answers while the editor runs; until then Claude Code lists it as not connected.
|
|
6
9
|
*/
|
|
10
|
+
export declare function setEngineMcp(name: (typeof ENGINE_MCP_NAMES)[number], server: McpServerConfig | null): void;
|
|
11
|
+
/** Adds the Unreal editor's MCP server (its address) to the next writeAddonMcpServers, or takes it away (null). */
|
|
7
12
|
export declare function setUnrealMcp(url: string | null): void;
|
|
8
13
|
/** Sets the add-ons' MCP servers in the sandbox's .claude.json (and Codex's config.toml); the names that are on. */
|
|
9
14
|
export declare function writeAddonMcpServers(): string[];
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { addonMcpServer, APPS, codexWithAddonMcpServers, withAddonMcpServers, } from '../../shared/apps/index.js';
|
|
9
|
+
import { UNITY_MCP_NAME } from '../../shared/unity/index.js';
|
|
9
10
|
import { writeJsonAtomic } from '../../shared/io/json.js';
|
|
10
11
|
import { cometExe } from '../browser/comet.js';
|
|
11
12
|
import { exists, mkdirp } from '../runtime/fs.js';
|
|
@@ -15,15 +16,24 @@ import { log, warn } from '../runtime/log.js';
|
|
|
15
16
|
import { installedMcpAddons } from './agent-addons.js';
|
|
16
17
|
/** The Unreal editor's MCP server (Unreal 5.8's ModelContextProtocol plugin), set by the Unreal engines step. */
|
|
17
18
|
export const UNREAL_MCP_NAME = 'sbx-unreal-editor';
|
|
18
|
-
|
|
19
|
+
/** The engines' own MCP servers: not an Apps add-on, set by the engine's step each boot, and sent to Codex too. */
|
|
20
|
+
export const ENGINE_MCP_NAMES = [UNREAL_MCP_NAME, UNITY_MCP_NAME];
|
|
21
|
+
const engineMcp = new Map();
|
|
19
22
|
/** The names of every MCP server the sandbox can add (the ones it may remove). */
|
|
20
|
-
const ADDON_MCP_NAMES = [...APPS.flatMap((a) => (a.mcp ? [a.mcp.name] : [])),
|
|
23
|
+
const ADDON_MCP_NAMES = [...APPS.flatMap((a) => (a.mcp ? [a.mcp.name] : [])), ...ENGINE_MCP_NAMES];
|
|
21
24
|
/**
|
|
22
|
-
* Adds
|
|
23
|
-
* answers while the editor runs
|
|
25
|
+
* Adds an engine's MCP server to the next writeAddonMcpServers, or takes it away (null). A server for an editor only
|
|
26
|
+
* answers while the editor runs; until then Claude Code lists it as not connected.
|
|
24
27
|
*/
|
|
28
|
+
export function setEngineMcp(name, server) {
|
|
29
|
+
if (server)
|
|
30
|
+
engineMcp.set(name, server);
|
|
31
|
+
else
|
|
32
|
+
engineMcp.delete(name);
|
|
33
|
+
}
|
|
34
|
+
/** Adds the Unreal editor's MCP server (its address) to the next writeAddonMcpServers, or takes it away (null). */
|
|
25
35
|
export function setUnrealMcp(url) {
|
|
26
|
-
|
|
36
|
+
setEngineMcp(UNREAL_MCP_NAME, url ? { type: 'http', url } : null);
|
|
27
37
|
}
|
|
28
38
|
/** Variables only this sandbox can tell an add-on's server (where Comet is). */
|
|
29
39
|
function placeEnv(id) {
|
|
@@ -46,7 +56,7 @@ export function writeAddonMcpServers() {
|
|
|
46
56
|
writeCodexAddonMcpServers(on);
|
|
47
57
|
const servers = {
|
|
48
58
|
...Object.fromEntries(on.map(({ mcp, server }) => [mcp.name, server])),
|
|
49
|
-
...(
|
|
59
|
+
...Object.fromEntries(engineMcp),
|
|
50
60
|
};
|
|
51
61
|
const file = path.join(claudeDir, '.claude.json');
|
|
52
62
|
let state = {};
|
|
@@ -85,12 +95,12 @@ function writeCodexAddonMcpServers(on) {
|
|
|
85
95
|
const servers = Object.fromEntries(on
|
|
86
96
|
.filter(({ mcp }) => mcp.codex)
|
|
87
97
|
.map(({ mcp, server }) => [mcp.name, { ...server, ...(mcp.envVars?.length ? { envVars: mcp.envVars } : {}) }]));
|
|
88
|
-
|
|
89
|
-
servers[
|
|
98
|
+
for (const [name, server] of engineMcp)
|
|
99
|
+
servers[name] = server;
|
|
90
100
|
const before = exists(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
91
101
|
if (!before && !Object.keys(servers).length)
|
|
92
102
|
return;
|
|
93
|
-
const known = [...APPS.flatMap((a) => (a.mcp?.codex ? [a.mcp.name] : [])),
|
|
103
|
+
const known = [...APPS.flatMap((a) => (a.mcp?.codex ? [a.mcp.name] : [])), ...ENGINE_MCP_NAMES];
|
|
94
104
|
const result = codexWithAddonMcpServers(before, servers, known);
|
|
95
105
|
for (const name of result.skipped)
|
|
96
106
|
warn('Codex MCP servers from Apps: ' + name + ' is already in config.toml in another form, left as it is');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// The GitHub token the host handed over (github.shareToken, only with git access "full"): gh logs
|
|
2
|
-
// folder on the sandbox's own disk (gone with the sandbox, never in the persistent state), and https
|
|
3
|
-
// token goes to gh on stdin, never in an argument, a file of ours or a log line.
|
|
1
|
+
// The GitHub token the host handed over (github.shareToken or github.usePcLogin, only with git access "full"): gh logs
|
|
2
|
+
// in with it in a folder on the sandbox's own disk (gone with the sandbox, never in the persistent state), and https
|
|
3
|
+
// git asks gh. The token goes to gh on stdin, never in an argument, a file of ours or a log line.
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
6
|
import { ghLoginCommand, ghOutputText, ghSetupGitCommand } from '../../shared/github/index.js';
|
|
@@ -61,5 +61,5 @@ export function loginGithub(token, env) {
|
|
|
61
61
|
addMachineEnvironment({ GH_CONFIG_DIR: env.GH_CONFIG_DIR }, []);
|
|
62
62
|
throw e;
|
|
63
63
|
}
|
|
64
|
-
log('gh and https git to github.com use the GitHub
|
|
64
|
+
log('gh and https git to github.com use the GitHub login from your PC (github.shareToken or github.usePcLogin); the agent can read it.');
|
|
65
65
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface UnityAgentInput {
|
|
2
|
+
readonly unityExe: string;
|
|
3
|
+
readonly projectPath: string | null;
|
|
4
|
+
readonly mcp: boolean;
|
|
5
|
+
readonly skills: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Sets up what is asked; returns the MCP server's name when it was registered and whether the skills were synced. */
|
|
8
|
+
export declare function setupUnityAgentTools(i: UnityAgentInput): Promise<{
|
|
9
|
+
mcp: string | null;
|
|
10
|
+
skills: boolean;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Unity for the agents: the Unity CLI's MCP server (sbx-unity) and its skills in the sandbox's Claude Code and Codex.
|
|
2
|
+
// The project is never changed. A failure is a warning: the editor itself still works without the agent tools.
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { UNITY_CLI_ENV, UNITY_MCP_NAME, unityCliMcpServer } from '../../shared/unity/index.js';
|
|
5
|
+
import { execAsync } from '../runtime/process.js';
|
|
6
|
+
import { log, warn } from '../runtime/log.js';
|
|
7
|
+
import { claudeDir, session, stateDir } from '../runtime/session.js';
|
|
8
|
+
import { setEngineMcp, writeAddonMcpServers } from './addon-mcp.js';
|
|
9
|
+
/** Syncs Unity's skills for one agent, with that agent's home folder in its variable; false on failure. */
|
|
10
|
+
async function syncSkills(unityExe, agent) {
|
|
11
|
+
const home = agent === 'claude' ? { CLAUDE_CONFIG_DIR: claudeDir } : { CODEX_HOME: path.join(stateDir, 'codex') };
|
|
12
|
+
try {
|
|
13
|
+
// The Unity CLI's client names: claude-code, codex.
|
|
14
|
+
await execAsync(unityExe, ['skill', 'sync', agent === 'claude' ? 'claude-code' : 'codex'], {
|
|
15
|
+
env: { ...process.env, ...UNITY_CLI_ENV, ...home },
|
|
16
|
+
});
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch (e) {
|
|
20
|
+
warn('Unity skills for ' + agent + ' were not synced: ' + (e instanceof Error ? e.message : String(e)));
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Sets up what is asked; returns the MCP server's name when it was registered and whether the skills were synced. */
|
|
25
|
+
export async function setupUnityAgentTools(i) {
|
|
26
|
+
setEngineMcp(UNITY_MCP_NAME, i.mcp ? unityCliMcpServer(i.unityExe, i.projectPath) : null);
|
|
27
|
+
const names = writeAddonMcpServers();
|
|
28
|
+
const mcp = names.includes(UNITY_MCP_NAME) ? UNITY_MCP_NAME : null;
|
|
29
|
+
let skills = false;
|
|
30
|
+
if (i.skills) {
|
|
31
|
+
const results = [await syncSkills(i.unityExe, 'claude')];
|
|
32
|
+
if (session.agents?.codex)
|
|
33
|
+
results.push(await syncSkills(i.unityExe, 'codex'));
|
|
34
|
+
skills = results.some(Boolean);
|
|
35
|
+
if (skills)
|
|
36
|
+
log('Unity skills synced for the agents');
|
|
37
|
+
}
|
|
38
|
+
return { mcp, skills };
|
|
39
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type LauncherEntry } from '../../../shared/unreal/index.js';
|
|
2
|
-
/** The launcher's program
|
|
2
|
+
/** The launcher's program: newer MSIs install into Program Files, older ones into Program Files (x86). */
|
|
3
3
|
export declare function launcherExe(): string | null;
|
|
4
4
|
/** Downloads the launcher's MSI ahead of the install (once). */
|
|
5
5
|
export declare function fetchLauncher(): Promise<void>;
|
|
@@ -22,14 +22,18 @@ const SAVED_MANIFESTS = path.join(SAVED_DIR, 'Manifests');
|
|
|
22
22
|
const SAVED_ENTRIES = path.join(SAVED_DIR, 'LauncherInstalled.json');
|
|
23
23
|
const programData = () => process.env.ProgramData ?? 'C:\\ProgramData';
|
|
24
24
|
const localAppData = () => process.env.LOCALAPPDATA ?? '';
|
|
25
|
-
/** The launcher's program
|
|
25
|
+
/** The launcher's program: newer MSIs install into Program Files, older ones into Program Files (x86). */
|
|
26
26
|
export function launcherExe() {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
const roots = [
|
|
28
|
+
process.env.ProgramFiles ?? 'C:\\Program Files',
|
|
29
|
+
process.env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)',
|
|
30
|
+
];
|
|
31
|
+
for (const root of roots)
|
|
32
|
+
for (const arch of ['Win64', 'Win32']) {
|
|
33
|
+
const exe = path.join(root, 'Epic Games', 'Launcher', 'Portal', 'Binaries', arch, 'EpicGamesLauncher.exe');
|
|
34
|
+
if (exists(exe))
|
|
35
|
+
return exe;
|
|
36
|
+
}
|
|
33
37
|
return null;
|
|
34
38
|
}
|
|
35
39
|
/** Downloads the launcher's MSI ahead of the install (once). */
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface UnityCli {
|
|
2
|
+
readonly exe: string;
|
|
3
|
+
readonly version: string;
|
|
4
|
+
}
|
|
5
|
+
export interface UnityRun {
|
|
6
|
+
readonly code: number;
|
|
7
|
+
readonly stdout: string;
|
|
8
|
+
}
|
|
9
|
+
export interface UnityRunOptions {
|
|
10
|
+
/** TEMP and TMP for the run (inside the staging folder of an install). */
|
|
11
|
+
readonly temp?: string;
|
|
12
|
+
readonly cwd?: string;
|
|
13
|
+
readonly timeoutMs?: number;
|
|
14
|
+
/** Extra variables (the asset cache). */
|
|
15
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
16
|
+
}
|
|
17
|
+
/** The pinned Unity CLI on the data disk, downloaded when it is not there yet ("latest" asks the manifest first). */
|
|
18
|
+
export declare function ensureUnityCli(version: string): Promise<UnityCli>;
|
|
19
|
+
/** Downloads the CLI ahead of its step; a failure is ignored (the step downloads again). */
|
|
20
|
+
export declare function prefetchUnityCli(version: string): Promise<void>;
|
|
21
|
+
/** Runs the CLI without blocking; the exit code and the start of its output (at most 1 MB). Never throws on a code. */
|
|
22
|
+
export declare function runUnityCli(exe: string, args: readonly string[], o?: UnityRunOptions): Promise<UnityRun>;
|
|
23
|
+
/** runUnityCli that throws with the end of the output when the exit code is not 0. */
|
|
24
|
+
export declare function runUnityCliChecked(exe: string, args: readonly string[], o?: UnityRunOptions): Promise<string>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// The Unity CLI (`unity`): its pinned build downloaded once into tools\unity\cli\<version> (checked against the SHA-256
|
|
2
|
+
// Unity publishes, and its signature when it has one), and the way every other Unity step runs it.
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fetchBounded, downloader } from '../../../shared/download/index.js';
|
|
6
|
+
import { CliManifestSchema, cliManifestUrl, pickCliBinary, UNITY_CLI_ENV, } from '../../../shared/unity/index.js';
|
|
7
|
+
import { log } from '../../runtime/log.js';
|
|
8
|
+
import { net } from '../../runtime/pools.js';
|
|
9
|
+
import { ensurePersisted } from '../persisted.js';
|
|
10
|
+
import { arch } from '../tool-env.js';
|
|
11
|
+
const MB = 1024 * 1024;
|
|
12
|
+
const MAX_MANIFEST_BYTES = 256 * 1024;
|
|
13
|
+
const MAX_OUTPUT_BYTES = 1024 * 1024;
|
|
14
|
+
/** The binary the manifest of a CLI version names for this CPU. */
|
|
15
|
+
async function resolveCli(version) {
|
|
16
|
+
const text = await net.run(() => fetchBounded(cliManifestUrl(version), MAX_MANIFEST_BYTES));
|
|
17
|
+
const parsed = CliManifestSchema.safeParse(JSON.parse(text));
|
|
18
|
+
const binary = parsed.success ? pickCliBinary(parsed.data, arch) : null;
|
|
19
|
+
if (!binary)
|
|
20
|
+
throw new Error('the Unity CLI manifest for ' + version + ' has no usable Windows build');
|
|
21
|
+
return binary;
|
|
22
|
+
}
|
|
23
|
+
/** The pinned Unity CLI on the data disk, downloaded when it is not there yet ("latest" asks the manifest first). */
|
|
24
|
+
export async function ensureUnityCli(version) {
|
|
25
|
+
const binary = await resolveCli(version);
|
|
26
|
+
const installed = await ensurePersisted({
|
|
27
|
+
name: 'unity/cli',
|
|
28
|
+
version: binary.version,
|
|
29
|
+
needBytes: 200 * MB,
|
|
30
|
+
marker: 'unity.exe',
|
|
31
|
+
what: 'Unity CLI ' + binary.version,
|
|
32
|
+
keepVersions: 2,
|
|
33
|
+
}, async (staging) => {
|
|
34
|
+
log('Downloading the Unity CLI ' + binary.version + ' ...');
|
|
35
|
+
// The CLI is not known to be signed: the SHA-256 from Unity's manifest is the check.
|
|
36
|
+
await downloader.file(binary.url, path.join(staging, 'unity.exe'), { sha256: binary.sha256 });
|
|
37
|
+
});
|
|
38
|
+
return { exe: path.join(installed.dir, 'unity.exe'), version: binary.version };
|
|
39
|
+
}
|
|
40
|
+
/** Downloads the CLI ahead of its step; a failure is ignored (the step downloads again). */
|
|
41
|
+
export async function prefetchUnityCli(version) {
|
|
42
|
+
await ensureUnityCli(version);
|
|
43
|
+
}
|
|
44
|
+
/** Runs the CLI without blocking; the exit code and the start of its output (at most 1 MB). Never throws on a code. */
|
|
45
|
+
export function runUnityCli(exe, args, o = {}) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const temp = o.temp ? { TEMP: o.temp, TMP: o.temp } : {};
|
|
48
|
+
const child = spawn(exe, [...args], {
|
|
49
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
50
|
+
windowsHide: true,
|
|
51
|
+
...(o.cwd ? { cwd: o.cwd } : {}),
|
|
52
|
+
env: { ...process.env, ...UNITY_CLI_ENV, ...temp, ...(o.env ?? {}) },
|
|
53
|
+
...(o.timeoutMs ? { timeout: o.timeoutMs } : {}),
|
|
54
|
+
});
|
|
55
|
+
let stdout = '';
|
|
56
|
+
const take = (chunk) => {
|
|
57
|
+
if (stdout.length < MAX_OUTPUT_BYTES)
|
|
58
|
+
stdout += chunk.slice(0, MAX_OUTPUT_BYTES - stdout.length);
|
|
59
|
+
};
|
|
60
|
+
child.stdout.setEncoding('utf8');
|
|
61
|
+
child.stderr.setEncoding('utf8');
|
|
62
|
+
child.stdout.on('data', take);
|
|
63
|
+
child.stderr.on('data', take);
|
|
64
|
+
child.once('error', reject);
|
|
65
|
+
child.once('close', (code) => resolve({ code: code ?? -1, stdout }));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** runUnityCli that throws with the end of the output when the exit code is not 0. */
|
|
69
|
+
export async function runUnityCliChecked(exe, args, o = {}) {
|
|
70
|
+
const r = await runUnityCli(exe, args, o);
|
|
71
|
+
if (r.code !== 0)
|
|
72
|
+
throw new Error('unity ' + args.slice(0, 2).join(' ') + ' failed with exit code ' + r.code + ': ' + r.stdout.trim().slice(-400));
|
|
73
|
+
return r.stdout;
|
|
74
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Unity Hub (toolchains.unity.hub): kept on the data disk (tools\unity\hub\3), installed by its signed installer
|
|
2
|
+
// unpacked there. For signing in to Unity Personal and installing editors by hand.
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { runInstaller } from '../../runtime/pools.js';
|
|
5
|
+
import { shortcuts } from '../../apps/shortcuts.js';
|
|
6
|
+
import { exists } from '../../runtime/fs.js';
|
|
7
|
+
import { log } from '../../runtime/log.js';
|
|
8
|
+
import { SIGNERS, signedInstaller } from '../installers.js';
|
|
9
|
+
import { ensurePersisted } from '../persisted.js';
|
|
10
|
+
import { UNITY_DIR } from './unity-status.js';
|
|
11
|
+
const HUB_URL = 'https://public-cdn.cloud.unity3d.com/hub/prod/UnityHubSetup.exe';
|
|
12
|
+
const HUB_MARKER = 'Unity Hub.exe';
|
|
13
|
+
const GB = 1024 ** 3;
|
|
14
|
+
/** The Hub's program on the data disk, installed there when it is not (only with `hub` on). */
|
|
15
|
+
export async function ensureUnityHub() {
|
|
16
|
+
const setup = await signedInstaller(HUB_URL, path.join(UNITY_DIR, 'UnityHubSetup.exe'), 'Unity Hub (~150 MB once)', SIGNERS.unity);
|
|
17
|
+
const installed = await ensurePersisted({ name: 'unity/hub', version: '3', needBytes: 2 * GB, marker: HUB_MARKER, what: 'Unity Hub', keepVersions: 1 }, async (staging) => {
|
|
18
|
+
log('Installing Unity Hub ...');
|
|
19
|
+
const code = await runInstaller(setup, ['/S', '/D=' + staging]);
|
|
20
|
+
if (code !== 0)
|
|
21
|
+
throw new Error('the Unity Hub installer failed (exit ' + code + ')');
|
|
22
|
+
});
|
|
23
|
+
const exe = path.join(installed.dir, HUB_MARKER);
|
|
24
|
+
if (exists(exe))
|
|
25
|
+
shortcuts([{ name: 'Unity Hub', exe }]);
|
|
26
|
+
return exe;
|
|
27
|
+
}
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/** The SRI hash (sha384-... or sha256-...). */
|
|
6
|
-
readonly integrity: string | null;
|
|
7
|
-
readonly sizeBytes: number | null;
|
|
8
|
-
}
|
|
9
|
-
export interface UnityRelease {
|
|
10
|
-
readonly version: string;
|
|
11
|
-
readonly changeset: string | null;
|
|
12
|
-
readonly downloads: readonly UnityDownload[];
|
|
13
|
-
}
|
|
14
|
-
/** The release of one editor version (6000.0.32f1), or null when Unity does not list it. */
|
|
15
|
-
export declare function findUnityRelease(_version: string): Promise<UnityRelease | null>;
|
|
1
|
+
import { type UnityRelease } from '../../../shared/unity/index.js';
|
|
2
|
+
export type { UnityRelease };
|
|
3
|
+
/** The release of one editor version (6000.0.58f2), or null when Unity does not list it or answers oddly. */
|
|
4
|
+
export declare function findUnityRelease(version: string): Promise<UnityRelease | null>;
|
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
// Unity's release API (services.api.unity.com
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// Unity's release API (services.api.unity.com): the editor installer, its changeset and its modules for one version.
|
|
2
|
+
// The answer is read with a size limit and checked by shared/unity/release.ts.
|
|
3
|
+
import { fetchBounded } from '../../../shared/download/index.js';
|
|
4
|
+
import { pickRelease, releaseUrl } from '../../../shared/unity/index.js';
|
|
5
|
+
const MAX_ANSWER_BYTES = 2 * 1024 * 1024;
|
|
6
|
+
/** The release of one editor version (6000.0.58f2), or null when Unity does not list it or answers oddly. */
|
|
7
|
+
export async function findUnityRelease(version) {
|
|
8
|
+
const text = await fetchBounded(releaseUrl(version), MAX_ANSWER_BYTES);
|
|
9
|
+
let json;
|
|
10
|
+
try {
|
|
11
|
+
json = JSON.parse(text);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null; // Not JSON: no release.
|
|
15
|
+
}
|
|
16
|
+
const release = pickRelease(json);
|
|
17
|
+
return release && release.version === version ? release : null;
|
|
6
18
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type UnityStatus } from '../../../shared/unity/index.js';
|
|
2
|
+
export declare const UNITY_DIR: string;
|
|
3
|
+
/** The Unity CLI, one folder per version (unity.exe). */
|
|
4
|
+
export declare const CLI_DIR: string;
|
|
5
|
+
/** The editors on the data disk: one folder per version (Editor\Unity.exe). */
|
|
6
|
+
export declare const EDITORS_DIR: string;
|
|
7
|
+
/** Asset Store downloads made in the sandbox (assetCache "persisted"). */
|
|
8
|
+
export declare const ASSET_DIR: string;
|
|
9
|
+
export declare function readUnityStatus(): UnityStatus;
|
|
10
|
+
/** Changes the status with `change` and writes it (whole, atomically). */
|
|
11
|
+
export declare function updateUnityStatus(change: (s: UnityStatus) => UnityStatus): UnityStatus;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// tools\unity\status.json: what the sandbox found for Unity, for the guide and the Unity page on the PC. The boot's
|
|
2
|
+
// project step writes its editor choice, the licence step the licence, the daemon the editors; every write keeps what
|
|
3
|
+
// the other parts wrote.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { readValid, writeJsonAtomic } from '../../../shared/io/json.js';
|
|
6
|
+
import { UNITY_STATUS_FILE, UnityStatusSchema } from '../../../shared/unity/index.js';
|
|
7
|
+
import { mkdirp } from '../../runtime/fs.js';
|
|
8
|
+
import { tool } from '../tool-env.js';
|
|
9
|
+
export const UNITY_DIR = tool('unity');
|
|
10
|
+
/** The Unity CLI, one folder per version (unity.exe). */
|
|
11
|
+
export const CLI_DIR = path.join(UNITY_DIR, 'cli');
|
|
12
|
+
/** The editors on the data disk: one folder per version (Editor\Unity.exe). */
|
|
13
|
+
export const EDITORS_DIR = path.join(UNITY_DIR, 'editors');
|
|
14
|
+
/** Asset Store downloads made in the sandbox (assetCache "persisted"). */
|
|
15
|
+
export const ASSET_DIR = path.join(UNITY_DIR, 'asset-store');
|
|
16
|
+
const FILE = path.join(UNITY_DIR, UNITY_STATUS_FILE);
|
|
17
|
+
const EMPTY = {
|
|
18
|
+
updatedAt: '',
|
|
19
|
+
cli: null,
|
|
20
|
+
editors: [],
|
|
21
|
+
projects: [],
|
|
22
|
+
licence: { state: 'unknown', kind: '', detail: 'not checked', user: null },
|
|
23
|
+
assetCache: { mode: 'local', dir: null },
|
|
24
|
+
pcAssets: [],
|
|
25
|
+
hub: { installed: false, path: null },
|
|
26
|
+
mcp: null,
|
|
27
|
+
skills: false,
|
|
28
|
+
problem: null,
|
|
29
|
+
};
|
|
30
|
+
export function readUnityStatus() {
|
|
31
|
+
return readValid(UnityStatusSchema, FILE) ?? EMPTY;
|
|
32
|
+
}
|
|
33
|
+
/** Changes the status with `change` and writes it (whole, atomically). */
|
|
34
|
+
export function updateUnityStatus(change) {
|
|
35
|
+
const next = { ...change(readUnityStatus()), updatedAt: new Date().toISOString() };
|
|
36
|
+
mkdirp(UNITY_DIR);
|
|
37
|
+
writeJsonAtomic(FILE, next);
|
|
38
|
+
return next;
|
|
39
|
+
}
|
|
@@ -1,4 +1,23 @@
|
|
|
1
|
+
import { type UnityEditor, type WantedEditor } from '../../../shared/unity/index.js';
|
|
1
2
|
import type { Toolchains } from '../../../core/types.js';
|
|
2
3
|
import type { ToolCtx } from '../step.js';
|
|
3
4
|
import type { ToolEnv } from '../tool-env.js';
|
|
4
|
-
|
|
5
|
+
import { type UnityCli } from './unity-cli.js';
|
|
6
|
+
/** Every editor this sandbox has. */
|
|
7
|
+
export declare function sandboxEditors(ctx: Pick<ToolCtx, 'pcInstalls'>): UnityEditor[];
|
|
8
|
+
/**
|
|
9
|
+
* The editor a project asks for: its ProjectVersion.txt (read bounded) always wins, since opening a project in another
|
|
10
|
+
* version upgrades its files; the version the setting names is only for folders without one.
|
|
11
|
+
*/
|
|
12
|
+
export declare function wantedEditor(config: Pick<Toolchains['unity'], 'version'>, workspace: string): WantedEditor | null;
|
|
13
|
+
/** Whether the project's editor is already here (a first install goes to the background). */
|
|
14
|
+
export declare function unityEditorSaved(config: Toolchains['unity'], ctx: Pick<ToolCtx, 'pcInstalls'>): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Installs one editor version on the data disk with the CLI (into a staging folder with TEMP inside it) and returns its
|
|
17
|
+
* folder. One heavy slot while the CLI runs.
|
|
18
|
+
*/
|
|
19
|
+
export declare function ensureUnityEditor(cli: UnityCli, version: string, changeset: string | null, modules: readonly string[], keepVersions: number): Promise<string>;
|
|
20
|
+
/** While the sandbox runs: lists the editors again (the user may have installed one with Hub) for the status. */
|
|
21
|
+
export declare function captureUnityEditors(): Promise<void>;
|
|
22
|
+
/** The editors, the project's choice, the CLI, the licence, the asset cache: set up and written to the status. */
|
|
23
|
+
export declare function setupUnity(env: ToolEnv, config: Toolchains['unity'], ctx: ToolCtx): Promise<void>;
|