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.
- 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/registry.js +6 -0
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-F4PVBN23.js → chunk-6WWMBCT2.js} +3580 -1962
- package/dist/bin/{chunk-3GAXLATK.js → chunk-JXL6ZOO3.js} +72 -5
- package/dist/bin/{chunk-G3TG4ZT7.js → chunk-ZXCAVVS4.js} +1 -1
- 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 +12 -0
- 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 +36 -0
- 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/plan.js +5 -1
- package/dist/guest/toolchains/tools/cli.js +2 -2
- package/dist/host/accounts/deliver.js +4 -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/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 +16 -4
- 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 +20 -0
- package/dist/host/pc-installs/unity.js +135 -2
- package/dist/runtime/agent.js +116 -11
- package/dist/runtime/daemon.js +2170 -976
- package/dist/runtime/db.js +116 -11
- package/dist/runtime/main.js +2543 -1364
- 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 +2 -2
- 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/persisted-installs.test.js +1 -1
- package/dist/selftest/service-accounts.test.js +1 -1
- 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/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
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// A Microsoft Store app in the sandbox: the PC put its package files in the data folder (toolsapps<id>.package,
|
|
2
|
+
// with store-package.json). Windows in the sandbox is new on every boot, so the app is installed again each boot with
|
|
3
|
+
// Add-AppxPackage (sandbox\scripts\install-store-package.ps1), from the package kept on the data disk. The files are
|
|
4
|
+
// checked against the manifest's SHA-256 first (store-check.ts). Windows checks the publisher's signature itself.
|
|
5
|
+
import { runScriptWithJsonAsync } from '../runtime/process.js';
|
|
6
|
+
import { tool } from '../toolchains/tool-env.js';
|
|
7
|
+
import { checkedPackage } from './store-check.js';
|
|
8
|
+
/** Where the PC left an app's package. */
|
|
9
|
+
export function storePackageDir(appId) {
|
|
10
|
+
return tool('apps', appId + '.package');
|
|
11
|
+
}
|
|
12
|
+
/** Installs the app from its kept package; resolves when Windows has it. */
|
|
13
|
+
export async function installStoreApp(appId, packageName) {
|
|
14
|
+
const { main, dependencies } = await checkedPackage(storePackageDir(appId));
|
|
15
|
+
await runScriptWithJsonAsync('install-store-package.ps1', 'ListFile', { main, dependencies }, [
|
|
16
|
+
'-PackageName',
|
|
17
|
+
packageName,
|
|
18
|
+
]);
|
|
19
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The SHA-256 of a file, read in pieces (the package is hundreds of MB). */
|
|
2
|
+
export declare function fileSha256(file: string): Promise<string>;
|
|
3
|
+
export interface CheckedPackage {
|
|
4
|
+
readonly main: string;
|
|
5
|
+
readonly dependencies: string[];
|
|
6
|
+
}
|
|
7
|
+
/** The package in `dir` when its manifest is valid and every file is there as hashed; throws otherwise. */
|
|
8
|
+
export declare function checkedPackage(dir: string): Promise<CheckedPackage>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// The package files of a Store app the PC left in the data folder, checked against the SHA-256 in store-package.json.
|
|
2
|
+
// No runtime imports: the self-test loads this file.
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { STORE_PACKAGE_MANIFEST, StorePackageManifestSchema } from '../../shared/apps/store-package.js';
|
|
7
|
+
import { readValid } from '../../shared/io/json.js';
|
|
8
|
+
const READ_CHUNK = 1024 * 1024;
|
|
9
|
+
/** The SHA-256 of a file, read in pieces (the package is hundreds of MB). */
|
|
10
|
+
export function fileSha256(file) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const hash = crypto.createHash('sha256');
|
|
13
|
+
const stream = fs.createReadStream(file, { highWaterMark: READ_CHUNK });
|
|
14
|
+
stream.on('data', (chunk) => hash.update(chunk));
|
|
15
|
+
stream.on('error', reject);
|
|
16
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/** The package in `dir` when its manifest is valid and every file is there as hashed; throws otherwise. */
|
|
20
|
+
export async function checkedPackage(dir) {
|
|
21
|
+
const manifest = readValid(StorePackageManifestSchema, path.join(dir, STORE_PACKAGE_MANIFEST));
|
|
22
|
+
if (!manifest)
|
|
23
|
+
throw new Error('the package was not handed over (this PC could not fetch it: see the launch notes)');
|
|
24
|
+
for (const f of manifest.files) {
|
|
25
|
+
if ((await fileSha256(path.join(dir, f.name)).catch(() => '')) !== f.sha256)
|
|
26
|
+
throw new Error(f.name + ' is missing or changed');
|
|
27
|
+
}
|
|
28
|
+
const full = (name) => path.join(dir, name);
|
|
29
|
+
return {
|
|
30
|
+
main: full(manifest.files.find((f) => f.role === 'main').name),
|
|
31
|
+
dependencies: manifest.files.filter((f) => f.role === 'dependency').map((f) => full(f.name)),
|
|
32
|
+
};
|
|
33
|
+
}
|
package/dist/guest/boot/boot.js
CHANGED
|
@@ -99,7 +99,7 @@ function writeDbCommand() {
|
|
|
99
99
|
writeDevToolCommands(dir);
|
|
100
100
|
return dir;
|
|
101
101
|
}
|
|
102
|
-
/** sbx-steam, sbx-fivem, sbx-
|
|
102
|
+
/** sbx-steam, sbx-fivem, sbx-android for the toolchains that are on: runtime\tool.js with that helper. */
|
|
103
103
|
function writeDevToolCommands(dir) {
|
|
104
104
|
const script = path.join(path.dirname(process.argv[1]), 'tool.js');
|
|
105
105
|
for (const tool of devToolsFor(session.toolchains))
|
package/dist/guest/boot/guide.js
CHANGED
|
@@ -10,6 +10,9 @@ import { projectFolderLines, toolLines } from './guide-lines.js';
|
|
|
10
10
|
import { sandboxScriptsDir } from '../../shared/scripts.js';
|
|
11
11
|
import { findUproject, unrealGuideLines } from '../../shared/unreal/index.js';
|
|
12
12
|
import { DISK } from '../toolchains/engines/unreal-engines.js';
|
|
13
|
+
import { readValid } from '../../shared/io/json.js';
|
|
14
|
+
import { UNITY_STATUS_FILE, UnityStatusSchema, unityGuideLines } from '../../shared/unity/index.js';
|
|
15
|
+
import { tool } from '../toolchains/tool-env.js';
|
|
13
16
|
import { readUnrealStatus } from '../toolchains/engines/unreal-status.js';
|
|
14
17
|
const BEGIN = '<!-- agent-sandbox:begin (generated every boot, do not edit) -->';
|
|
15
18
|
const END = '<!-- agent-sandbox:end -->';
|
|
@@ -48,6 +51,20 @@ function unrealLines(projects) {
|
|
|
48
51
|
.filter((f) => status.projects.some((p) => p.workspace.toLowerCase() === f.toLowerCase()))
|
|
49
52
|
.map((f) => ({ workspace: f, uproject: findUproject(f, DISK) })), { mixamo: { downloads: path.join(dataDir, 'downloads'), script: mixamoScript() } });
|
|
50
53
|
}
|
|
54
|
+
/** The Unity lines (editor per project, build/test/import commands) when the sandbox is set up for Unity. */
|
|
55
|
+
function unityLines(projects) {
|
|
56
|
+
const any = session.toolchains.unity.enabled === true || projects.some((p) => p.tools?.some((t) => /^unity/.test(t)));
|
|
57
|
+
if (!any)
|
|
58
|
+
return [];
|
|
59
|
+
const status = readValid(UnityStatusSchema, path.join(tool('unity'), UNITY_STATUS_FILE));
|
|
60
|
+
if (!status?.updatedAt)
|
|
61
|
+
return [];
|
|
62
|
+
const folders = [workspace, ...projects.map((p) => p.guest)];
|
|
63
|
+
return unityGuideLines(status, folders
|
|
64
|
+
.filter((f, i) => folders.findIndex((g) => g.toLowerCase() === f.toLowerCase()) === i)
|
|
65
|
+
.filter((f) => status.projects.some((p) => p.workspace.toLowerCase() === f.toLowerCase()))
|
|
66
|
+
.map((f) => ({ workspace: f })));
|
|
67
|
+
}
|
|
51
68
|
/** Deliberately tiny: it is loaded into every conversation. */
|
|
52
69
|
export function sandboxGuide(toolSummary, lan = 'blocked', projects = [], addons = []) {
|
|
53
70
|
return [
|
|
@@ -69,6 +86,7 @@ export function sandboxGuide(toolSummary, lan = 'blocked', projects = [], addons
|
|
|
69
86
|
...mirrorLine(),
|
|
70
87
|
...toolLines(session.project, toolSummary, projects),
|
|
71
88
|
...unrealLines(projects),
|
|
89
|
+
...unityLines(projects),
|
|
72
90
|
// Agent add-ons that change how the agent works (rtk, Headroom, MCP servers, ccusage).
|
|
73
91
|
...addons,
|
|
74
92
|
'- Persistent installs: `npm i -g` (per nvm version), `pip`, `cargo install`, `dotnet tool -g`, `uv tool`, `bun add -g`. Real installers must re-run each boot via a hook (ask the user).',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CometPayload } from '../../shared/accounts/kinds/comet.js';
|
|
2
|
-
import { type DevToolsEndpoint } from './cdp.js';
|
|
2
|
+
import { CdpConnection, type DevToolsEndpoint } from './cdp.js';
|
|
3
3
|
/** The port comet-mcp looks for (it connects to a Comet already listening there). */
|
|
4
4
|
export declare const COMET_MCP_PORT = 9222;
|
|
5
5
|
/** The addresses whose cookies are read back (their cookies and the parent domain's). */
|
|
@@ -16,6 +16,8 @@ export declare function cometLaunchArgs(port: number, profile?: string): string[
|
|
|
16
16
|
export declare function cometShortcutArgs(port: number, profile?: string): string;
|
|
17
17
|
/** Where a Comet running with this profile listens, from its DevToolsActivePort file; null when there is none. */
|
|
18
18
|
export declare function devToolsEndpoint(profile?: string): DevToolsEndpoint | null;
|
|
19
|
+
/** A connection to the Comet running with this profile, or null when none answers. */
|
|
20
|
+
export declare function connect(profile: string): Promise<CdpConnection | null>;
|
|
19
21
|
/**
|
|
20
22
|
* The sign-in the running Comet holds now (perplexity.ai's own cookies, asked of one of its pages), or null when
|
|
21
23
|
* Comet is not running with this profile, has no page open, or holds no sign-in. Never asks for another site's cookies.
|
|
@@ -56,7 +56,7 @@ export function devToolsEndpoint(profile = cometProfileDir()) {
|
|
|
56
56
|
return bytes ? parseDevToolsActivePort(bytes.toString('utf8')) : null;
|
|
57
57
|
}
|
|
58
58
|
/** A connection to the Comet running with this profile, or null when none answers. */
|
|
59
|
-
async function connect(profile) {
|
|
59
|
+
export async function connect(profile) {
|
|
60
60
|
const endpoint = devToolsEndpoint(profile);
|
|
61
61
|
if (!endpoint)
|
|
62
62
|
return null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type CometPayload } from '../../shared/accounts/kinds/comet.js';
|
|
2
|
+
/** The app's profile: on the sandbox's own disk, wiped with it. */
|
|
3
|
+
export declare function perplexityAppProfileDir(): string;
|
|
4
|
+
/**
|
|
5
|
+
* The powershell.exe arguments of the app's shortcut: start-store-app.ps1 with the package, the profile and a free
|
|
6
|
+
* DevTools port (0). `script` is the full path of the script in the launch folder.
|
|
7
|
+
*/
|
|
8
|
+
export declare function perplexityAppShortcutArgs(script: string, profile?: string): string;
|
|
9
|
+
/** The sign-in the running app holds now, or null (not running with this profile, no page, signed out). */
|
|
10
|
+
export declare function readAppSignIn(profile?: string): Promise<CometPayload | null>;
|
|
11
|
+
/** Writes the sign-in into the running app; false when the app is not running with this profile. */
|
|
12
|
+
export declare function restoreAppSignIn(payload: CometPayload, profile?: string): Promise<boolean>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// The Perplexity desktop app (a Microsoft Store package, Electron inside) in this sandbox: the profile folder it runs
|
|
2
|
+
// with, the command line of its shortcut, and its sign-in over the DevTools port, the way Comet's is kept
|
|
3
|
+
// (browser/comet.ts): only the allow-listed perplexity.ai cookies are read (Network.getCookies) and written back
|
|
4
|
+
// (Storage.setCookies). The profile is on the sandbox's own disk: its cookie key is sealed to this sandbox's Windows
|
|
5
|
+
// user, which is why the sign-in is not copied from the PC. No runtime imports: the sign-in adapter and the self-test
|
|
6
|
+
// load this file.
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { cookiesToRestore } from '../../shared/accounts/kinds/comet.js';
|
|
10
|
+
import { PERPLEXITY_DESKTOP_PACKAGE } from '../../shared/apps/catalogue-agents-web.js';
|
|
11
|
+
import { localAppData } from '../accounts/adapter.js';
|
|
12
|
+
import { connect, readCometSignIn } from './comet.js';
|
|
13
|
+
/** The app's profile: on the sandbox's own disk, wiped with it. */
|
|
14
|
+
export function perplexityAppProfileDir() {
|
|
15
|
+
return path.join(localAppData(), 'AgentSandbox', 'perplexity-app-profile');
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The powershell.exe arguments of the app's shortcut: start-store-app.ps1 with the package, the profile and a free
|
|
19
|
+
* DevTools port (0). `script` is the full path of the script in the launch folder.
|
|
20
|
+
*/
|
|
21
|
+
export function perplexityAppShortcutArgs(script, profile = perplexityAppProfileDir()) {
|
|
22
|
+
return [
|
|
23
|
+
'-NoLogo',
|
|
24
|
+
'-NoProfile',
|
|
25
|
+
'-WindowStyle',
|
|
26
|
+
'Hidden',
|
|
27
|
+
'-ExecutionPolicy',
|
|
28
|
+
'Bypass',
|
|
29
|
+
'-File',
|
|
30
|
+
'"' + script + '"',
|
|
31
|
+
'-PackageName',
|
|
32
|
+
PERPLEXITY_DESKTOP_PACKAGE,
|
|
33
|
+
'-UserDataDir',
|
|
34
|
+
'"' + profile + '"',
|
|
35
|
+
'-Port',
|
|
36
|
+
'0',
|
|
37
|
+
].join(' ');
|
|
38
|
+
}
|
|
39
|
+
/** The sign-in the running app holds now, or null (not running with this profile, no page, signed out). */
|
|
40
|
+
export function readAppSignIn(profile = perplexityAppProfileDir()) {
|
|
41
|
+
return readCometSignIn(profile);
|
|
42
|
+
}
|
|
43
|
+
/** Writes the sign-in into the running app; false when the app is not running with this profile. */
|
|
44
|
+
export async function restoreAppSignIn(payload, profile = perplexityAppProfileDir()) {
|
|
45
|
+
const cookies = cookiesToRestore(payload, Math.floor(Date.now() / 1000));
|
|
46
|
+
if (!cookies.length)
|
|
47
|
+
return true;
|
|
48
|
+
if (!fs.existsSync(path.join(profile, 'DevToolsActivePort')))
|
|
49
|
+
return false;
|
|
50
|
+
const cdp = await connect(profile);
|
|
51
|
+
if (!cdp)
|
|
52
|
+
return false;
|
|
53
|
+
try {
|
|
54
|
+
await cdp.call('Storage.setCookies', { cookies });
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
cdp.close();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -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,36 @@
|
|
|
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
|
+
await execAsync(unityExe, ['skill', 'sync', agent], { env: { ...process.env, ...UNITY_CLI_ENV, ...home } });
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
warn('Unity skills for ' + agent + ' were not synced: ' + (e instanceof Error ? e.message : String(e)));
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Sets up what is asked; returns the MCP server's name when it was registered and whether the skills were synced. */
|
|
22
|
+
export async function setupUnityAgentTools(i) {
|
|
23
|
+
setEngineMcp(UNITY_MCP_NAME, i.mcp ? unityCliMcpServer(i.unityExe, i.projectPath) : null);
|
|
24
|
+
const names = writeAddonMcpServers();
|
|
25
|
+
const mcp = names.includes(UNITY_MCP_NAME) ? UNITY_MCP_NAME : null;
|
|
26
|
+
let skills = false;
|
|
27
|
+
if (i.skills) {
|
|
28
|
+
const results = [await syncSkills(i.unityExe, 'claude')];
|
|
29
|
+
if (session.agents?.codex)
|
|
30
|
+
results.push(await syncSkills(i.unityExe, 'codex'));
|
|
31
|
+
skills = results.some(Boolean);
|
|
32
|
+
if (skills)
|
|
33
|
+
log('Unity skills synced for the agents');
|
|
34
|
+
}
|
|
35
|
+
return { mcp, skills };
|
|
36
|
+
}
|
|
@@ -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;
|