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
|
@@ -55,6 +55,14 @@ export declare const ToolchainsSchema: z.ZodObject<{
|
|
|
55
55
|
modules: z.ZodArray<z.ZodString>;
|
|
56
56
|
hub: z.ZodBoolean;
|
|
57
57
|
keepVersions: z.ZodNumber;
|
|
58
|
+
mcp: z.ZodBoolean;
|
|
59
|
+
agentSkills: z.ZodBoolean;
|
|
60
|
+
cliVersion: z.ZodString;
|
|
61
|
+
assetCache: z.ZodEnum<{
|
|
62
|
+
pc: "pc";
|
|
63
|
+
persisted: "persisted";
|
|
64
|
+
local: "local";
|
|
65
|
+
}>;
|
|
58
66
|
}, z.core.$strict>;
|
|
59
67
|
godot: z.ZodObject<{
|
|
60
68
|
enabled: z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"auto">]>;
|
|
@@ -70,24 +70,39 @@ export const ToolchainsSchema = z.strictObject({
|
|
|
70
70
|
.describe('"persisted": the engine\'s local derived data cache (compiled shaders, 10-50 GB) in tools\\unreal\\ddc, kept between sandboxes. "local": Unreal\'s own place on the sandbox disk (rebuilt every boot).'),
|
|
71
71
|
}),
|
|
72
72
|
unity: z.strictObject({
|
|
73
|
-
enabled: enabled("Unity: the editor version a project asks for, from Unity's release API (checked against its published hash), installed
|
|
73
|
+
enabled: enabled("Unity: the editor version a project asks for, from Unity's release API (checked against its published hash), installed on the data disk (tools\\unity\\editors\\<version>, 8-13 GB each, kept between sandboxes), with the modules you list. This PC's editors (pcInstalls.unityEditors) are used first.", 'ProjectSettings/ProjectVersion.txt'),
|
|
74
74
|
acceptLicense: z
|
|
75
75
|
.boolean()
|
|
76
76
|
.describe('Required to install: you accept the Unity terms of service (https://unity.com/legal/terms-of-service), and the editor needs a Unity licence (Personal is free). A project file cannot accept it for you.'),
|
|
77
77
|
version: z
|
|
78
78
|
.string()
|
|
79
79
|
.min(1)
|
|
80
|
-
.describe('"project" = the version in ProjectSettings/ProjectVersion.txt, or an editor version such as "6000.0.32f1".'),
|
|
80
|
+
.describe('"project" = the version in ProjectSettings/ProjectVersion.txt, or an editor version such as "6000.0.32f1" for folders without one (a project\'s own version always wins).'),
|
|
81
81
|
modules: z
|
|
82
82
|
.array(z.string().regex(/^[a-z0-9-]+$/))
|
|
83
83
|
.describe('Editor modules to add: "android" (4-5 GB, also the Android SDK), "webgl" (1.5 GB), "windows-il2cpp" (adds Visual Studio\'s C++ parts), "ios" (Xcode project export only: iOS builds need a Mac).'),
|
|
84
|
-
hub: z
|
|
84
|
+
hub: z
|
|
85
|
+
.boolean()
|
|
86
|
+
.describe('Also Unity Hub (kept on the data disk), for signing in to Unity Personal and installing editors by hand.'),
|
|
85
87
|
keepVersions: z
|
|
86
88
|
.number()
|
|
87
89
|
.int()
|
|
88
90
|
.min(1)
|
|
89
91
|
.max(20)
|
|
90
92
|
.describe('How many editor versions to keep in tools\\unity; older ones are removed (the least recently used first).'),
|
|
93
|
+
mcp: z
|
|
94
|
+
.boolean()
|
|
95
|
+
.describe("The Unity CLI's MCP server (unity mcp) in Claude Code and Codex as sbx-unity: Unity's read-only tools always, the open editor's tools when the project has the Unity Pipeline package (Unity 6 and later). Nothing in your project changes."),
|
|
96
|
+
agentSkills: z
|
|
97
|
+
.boolean()
|
|
98
|
+
.describe("Unity's skills for Claude Code and Codex, from Unity-Technologies/skills at the commit the Unity CLI pins."),
|
|
99
|
+
cliVersion: z
|
|
100
|
+
.string()
|
|
101
|
+
.regex(/^(latest|\d+\.\d+\.\d+(-[A-Za-z0-9.]+)?)$/)
|
|
102
|
+
.describe('The Unity CLI version, checked against the SHA-256 Unity publishes: a version such as "1.0.0-beta.16", or "latest". It never updates itself.'),
|
|
103
|
+
assetCache: z
|
|
104
|
+
.enum(['persisted', 'pc', 'local'])
|
|
105
|
+
.describe('"persisted": Asset Store downloads made in the sandbox go to tools\\unity\\asset-store (kept). "pc": the sandbox uses this PC\'s Asset Store downloads (read-only; download new ones on your PC). "local": Unity\'s own place on the sandbox disk (lost when it closes).'),
|
|
91
106
|
}),
|
|
92
107
|
godot: z.strictObject({
|
|
93
108
|
enabled: enabled('Godot from its official builds (checked against the published SHA-512 sums), self-contained in tools\\godot\\<version> with `godot` (the console build) on PATH.', 'project.godot'),
|
|
@@ -218,7 +233,7 @@ export const ToolchainsSchema = z.strictObject({
|
|
|
218
233
|
}),
|
|
219
234
|
cli: z
|
|
220
235
|
.strictObject({
|
|
221
|
-
gh: EnabledSchema.describe('GitHub CLI (gh). Only with provisioning.git.access "full": gh can push and merge through the GitHub API, past a read-only git. Your PC\'s gh login is
|
|
236
|
+
gh: EnabledSchema.describe('GitHub CLI (gh). Only with provisioning.git.access "full": gh can push and merge through the GitHub API, past a read-only git. Your PC\'s gh login is copied only with github.usePcLogin (or a token you saved for sandboxes with github.shareToken); otherwise `gh auth login` inside persists (GH_CONFIG_DIR). "auto" = when the project has a .github folder.'),
|
|
222
237
|
zstd: EnabledSchema.describe('Zstandard (zstd). "auto" = off; set true to get it.'),
|
|
223
238
|
openssl: EnabledSchema.describe('`openssl` on PATH, from the copy Git for Windows ships (its mingw64\\bin is not put on PATH, so it cannot shadow MinGW\'s gcc). "auto" = on.'),
|
|
224
239
|
})
|
|
@@ -5,6 +5,7 @@ import { epicAdapter } from './kinds/epic.js';
|
|
|
5
5
|
import { expoAdapter } from './kinds/expo.js';
|
|
6
6
|
import { keystoreAdapter } from './kinds/keystore.js';
|
|
7
7
|
import { perplexityAdapter } from './kinds/perplexity.js';
|
|
8
|
+
import { perplexityAppAdapter } from './kinds/perplexityapp.js';
|
|
8
9
|
import { playAdapter } from './kinds/play.js';
|
|
9
10
|
import { steamAdapter } from './kinds/steam.js';
|
|
10
11
|
import { unityAdapter } from './kinds/unity.js';
|
|
@@ -19,4 +20,5 @@ export const ADAPTERS = {
|
|
|
19
20
|
appstore: appstoreAdapter,
|
|
20
21
|
perplexity: perplexityAdapter,
|
|
21
22
|
comet: cometAdapter,
|
|
23
|
+
perplexityapp: perplexityAppAdapter,
|
|
22
24
|
};
|
|
@@ -4,28 +4,30 @@ import type { ServiceAccounts } from '../../shared/accounts/payloads.js';
|
|
|
4
4
|
import { type GuestAdapter } from './adapter.js';
|
|
5
5
|
export declare const DeliveredSchema: z.ZodObject<{
|
|
6
6
|
fingerprints: z.ZodRecord<z.ZodEnum<{
|
|
7
|
-
unity: "unity";
|
|
8
7
|
keystore: "keystore";
|
|
9
8
|
play: "play";
|
|
10
9
|
expo: "expo";
|
|
11
10
|
appstore: "appstore";
|
|
11
|
+
unity: "unity";
|
|
12
12
|
steam: "steam";
|
|
13
13
|
epic: "epic";
|
|
14
14
|
cfx: "cfx";
|
|
15
15
|
perplexity: "perplexity";
|
|
16
16
|
comet: "comet";
|
|
17
|
+
perplexityapp: "perplexityapp";
|
|
17
18
|
}> & z.core.$partial, z.ZodString>;
|
|
18
19
|
leased: z.ZodArray<z.ZodEnum<{
|
|
19
|
-
unity: "unity";
|
|
20
20
|
keystore: "keystore";
|
|
21
21
|
play: "play";
|
|
22
22
|
expo: "expo";
|
|
23
23
|
appstore: "appstore";
|
|
24
|
+
unity: "unity";
|
|
24
25
|
steam: "steam";
|
|
25
26
|
epic: "epic";
|
|
26
27
|
cfx: "cfx";
|
|
27
28
|
perplexity: "perplexity";
|
|
28
29
|
comet: "comet";
|
|
30
|
+
perplexityapp: "perplexityapp";
|
|
29
31
|
}>>;
|
|
30
32
|
}, z.core.$strip>;
|
|
31
33
|
export type Delivered = z.output<typeof DeliveredSchema>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type CometPayload } from '../../../shared/accounts/index.js';
|
|
2
|
+
import { type GuestAdapter } from '../adapter.js';
|
|
3
|
+
/** Where the sign-in waits for the app. */
|
|
4
|
+
export declare function perplexityAppPendingFile(root?: string): string;
|
|
5
|
+
/** The sign-in waiting for the app (left in place), or null (none, or not the expected shape). */
|
|
6
|
+
export declare function peekPendingPerplexityApp(root?: string): CometPayload | null;
|
|
7
|
+
export declare const perplexityAppAdapter: GuestAdapter<'perplexityapp'>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Perplexity desktop app: the saved perplexity.ai sign-in cookies wait in secretDir('perplexityapp') (the sandbox's own
|
|
2
|
+
// disk) until the app runs; the watch then writes them into it over its DevTools port and deletes the file. While the
|
|
3
|
+
// sandbox runs, the watch asks the running app for its perplexity.ai cookies, so a sign-in made here (or renewed) is
|
|
4
|
+
// kept for the next sandbox. When the app is not running, the last sign-in seen stands.
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { cometSummary, PAYLOADS } from '../../../shared/accounts/index.js';
|
|
8
|
+
import { readBoundedFile } from '../../../shared/io/bounded.js';
|
|
9
|
+
import { readAppSignIn, restoreAppSignIn } from '../../browser/perplexity-app.js';
|
|
10
|
+
import { secretDir } from '../adapter.js';
|
|
11
|
+
/** Where the sign-in waits for the app. */
|
|
12
|
+
export function perplexityAppPendingFile(root = secretDir('perplexityapp')) {
|
|
13
|
+
return path.join(root, 'restore.json');
|
|
14
|
+
}
|
|
15
|
+
/** The sign-in waiting for the app (left in place), or null (none, or not the expected shape). */
|
|
16
|
+
export function peekPendingPerplexityApp(root) {
|
|
17
|
+
const bytes = readBoundedFile(perplexityAppPendingFile(root), 512 * 1024);
|
|
18
|
+
if (!bytes)
|
|
19
|
+
return null;
|
|
20
|
+
try {
|
|
21
|
+
const parsed = PAYLOADS.perplexityapp.safeParse(JSON.parse(bytes.toString('utf8')));
|
|
22
|
+
return parsed.success ? parsed.data : null;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null; // Not JSON: nothing to restore.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** The last sign-in seen in this process (the daemon's watch). */
|
|
29
|
+
let held = null;
|
|
30
|
+
export const perplexityAppAdapter = {
|
|
31
|
+
kind: 'perplexityapp',
|
|
32
|
+
apply(p) {
|
|
33
|
+
const file = perplexityAppPendingFile();
|
|
34
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
35
|
+
fs.writeFileSync(file, JSON.stringify(p), { encoding: 'utf8', mode: 0o600 });
|
|
36
|
+
held = p;
|
|
37
|
+
return {
|
|
38
|
+
summary: 'Perplexity app: your saved sign-in (' + cometSummary(p) + ') goes into it once it runs (reload its window)',
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
async refresh() {
|
|
42
|
+
const pending = peekPendingPerplexityApp();
|
|
43
|
+
if (pending && (await restoreAppSignIn(pending).catch(() => false)))
|
|
44
|
+
fs.rmSync(perplexityAppPendingFile(), { force: true });
|
|
45
|
+
const now = await readAppSignIn();
|
|
46
|
+
if (now)
|
|
47
|
+
held = now;
|
|
48
|
+
},
|
|
49
|
+
current() {
|
|
50
|
+
return held;
|
|
51
|
+
},
|
|
52
|
+
checkMs: 30_000,
|
|
53
|
+
};
|
|
@@ -1,2 +1,29 @@
|
|
|
1
|
+
import type { UnityStatus } from '../../../shared/unity/status.js';
|
|
1
2
|
import { type GuestAdapter } from '../adapter.js';
|
|
3
|
+
/** Runs a program to its end (or its time limit); the exit code and what it printed. Injected by the self-test. */
|
|
4
|
+
export type UnityRunner = (file: string, args: readonly string[], o: {
|
|
5
|
+
readonly timeoutMs: number;
|
|
6
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
7
|
+
}) => Promise<{
|
|
8
|
+
readonly code: number;
|
|
9
|
+
readonly stdout: string;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const runUnityProgram: UnityRunner;
|
|
12
|
+
export interface UnityDirs {
|
|
13
|
+
readonly programData: string;
|
|
14
|
+
readonly localAppData: string;
|
|
15
|
+
/** secretDir('unity'). */
|
|
16
|
+
readonly secrets: string;
|
|
17
|
+
}
|
|
18
|
+
/** The adapter for these folders and this runner (the self-test passes its own). */
|
|
19
|
+
export declare function unityAdapterFor(dirs: UnityDirs, run?: UnityRunner): GuestAdapter<'unity'>;
|
|
2
20
|
export declare const unityAdapter: GuestAdapter<'unity'>;
|
|
21
|
+
/**
|
|
22
|
+
* Activates the serial the sandbox holds once an editor exists, then asks `unity license status` where the licence
|
|
23
|
+
* stands. The detail never holds a secret.
|
|
24
|
+
*/
|
|
25
|
+
export declare function activateUnityLicence(o: {
|
|
26
|
+
readonly editorExe: string | null;
|
|
27
|
+
readonly unityExe: string | null;
|
|
28
|
+
readonly source?: string;
|
|
29
|
+
}, dirs?: UnityDirs, run?: UnityRunner): Promise<UnityStatus['licence']>;
|
|
@@ -1,7 +1,164 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
// Unity: the licence file (Unity_lic.ulf) goes to %ProgramData%\Unity and the named-user licence to
|
|
2
|
+
// %LOCALAPPDATA%\Unity\licenses, where the editor reads them. The Unity ID (and a Plus or Pro serial) are kept in
|
|
3
|
+
// secretDir('unity')\activation.json, on the sandbox's own disk, for the editor to sign in with. A serial seat is
|
|
4
|
+
// returned when the sandbox stops. Nothing here puts a password, serial or licence text in a log or a summary.
|
|
5
|
+
import { execFile } from 'node:child_process';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { MAX_ULF_BYTES, parseLicenceStatus, unityEntitlementFile, unityUlfFile, } from '../../../shared/accounts/index.js';
|
|
10
|
+
import { decodeText, readBoundedFile } from '../../../shared/io/index.js';
|
|
11
|
+
import { licenceStatusArgs, UNITY_CLI_ENV } from '../../../shared/unity/cli.js';
|
|
12
|
+
import { localAppData, secretDir } from '../adapter.js';
|
|
13
|
+
const MAX_JSON_BYTES = 16 * 1024;
|
|
14
|
+
const RETURN_MS = 120_000;
|
|
15
|
+
const ACTIVATE_MS = 180_000;
|
|
16
|
+
const STATUS_MS = 60_000;
|
|
17
|
+
const ActivationSchema = z.object({
|
|
18
|
+
email: z.string().max(256).optional(),
|
|
19
|
+
password: z.string().max(1024).optional(),
|
|
20
|
+
serial: z.string().max(64).optional(),
|
|
21
|
+
});
|
|
22
|
+
const ActivatedSchema = z.object({ editorExe: z.string().min(1).max(520), serial: z.literal(true) });
|
|
23
|
+
export const runUnityProgram = (file, args, o) => new Promise((resolve) => {
|
|
24
|
+
execFile(file, [...args], { windowsHide: true, timeout: o.timeoutMs, maxBuffer: 1024 * 1024, env: { ...process.env, ...o.env } }, (error, stdout) => {
|
|
25
|
+
const code = error ? (typeof error.code === 'number' ? error.code : -1) : 0;
|
|
26
|
+
resolve({ code, stdout: String(stdout ?? '') });
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
function readText(file) {
|
|
30
|
+
const bytes = readBoundedFile(file, MAX_ULF_BYTES);
|
|
31
|
+
return bytes ? decodeText(bytes).text : null;
|
|
32
|
+
}
|
|
33
|
+
function readSecretJson(schema, file) {
|
|
34
|
+
const bytes = readBoundedFile(file, MAX_JSON_BYTES);
|
|
35
|
+
if (!bytes)
|
|
36
|
+
return null;
|
|
37
|
+
try {
|
|
38
|
+
const parsed = schema.safeParse(JSON.parse(decodeText(bytes).text));
|
|
39
|
+
return parsed.success ? parsed.data : null;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null; // not JSON: treated as absent
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function writeText(file, text) {
|
|
46
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
47
|
+
fs.writeFileSync(file, text, 'utf8');
|
|
48
|
+
}
|
|
49
|
+
/** The adapter for these folders and this runner (the self-test passes its own). */
|
|
50
|
+
export function unityAdapterFor(dirs, run = runUnityProgram) {
|
|
51
|
+
const activation = path.join(dirs.secrets, 'activation.json');
|
|
52
|
+
const activated = path.join(dirs.secrets, 'activated.json');
|
|
53
|
+
return {
|
|
54
|
+
kind: 'unity',
|
|
55
|
+
apply(p) {
|
|
56
|
+
if (p.ulf)
|
|
57
|
+
writeText(unityUlfFile(dirs.programData), p.ulf);
|
|
58
|
+
if (p.entitlement)
|
|
59
|
+
writeText(unityEntitlementFile(dirs.localAppData), p.entitlement);
|
|
60
|
+
const sign = {};
|
|
61
|
+
if (p.email)
|
|
62
|
+
sign.email = p.email;
|
|
63
|
+
if (p.password)
|
|
64
|
+
sign.password = p.password;
|
|
65
|
+
if (p.serial)
|
|
66
|
+
sign.serial = p.serial;
|
|
67
|
+
if (Object.keys(sign).length)
|
|
68
|
+
writeText(activation, JSON.stringify(sign));
|
|
69
|
+
return {
|
|
70
|
+
summary: p.ulf || p.entitlement
|
|
71
|
+
? 'Unity: your licence file is in place'
|
|
72
|
+
: 'Unity: your Unity ID is ready to activate the serial',
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
current() {
|
|
76
|
+
const ulf = readText(unityUlfFile(dirs.programData));
|
|
77
|
+
const entitlement = readText(unityEntitlementFile(dirs.localAppData));
|
|
78
|
+
const sign = readSecretJson(ActivationSchema, activation) ?? {};
|
|
79
|
+
const out = {
|
|
80
|
+
...(ulf ? { ulf } : {}),
|
|
81
|
+
...(entitlement ? { entitlement } : {}),
|
|
82
|
+
...sign,
|
|
83
|
+
};
|
|
84
|
+
return Object.keys(out).length ? out : null;
|
|
85
|
+
},
|
|
86
|
+
async release() {
|
|
87
|
+
const marker = readSecretJson(ActivatedSchema, activated);
|
|
88
|
+
if (!marker)
|
|
89
|
+
return;
|
|
90
|
+
const sign = readSecretJson(ActivationSchema, activation);
|
|
91
|
+
if (!sign?.email || !sign.password)
|
|
92
|
+
return;
|
|
93
|
+
await run(marker.editorExe, [
|
|
94
|
+
'-batchmode',
|
|
95
|
+
'-nographics',
|
|
96
|
+
'-quit',
|
|
97
|
+
'-returnlicense',
|
|
98
|
+
'-username',
|
|
99
|
+
sign.email,
|
|
100
|
+
'-password',
|
|
101
|
+
sign.password,
|
|
102
|
+
'-logFile',
|
|
103
|
+
path.join(dirs.secrets, 'return.log'),
|
|
104
|
+
], { timeoutMs: RETURN_MS });
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function defaultDirs() {
|
|
109
|
+
return {
|
|
110
|
+
programData: process.env.ProgramData ?? process.env.PROGRAMDATA ?? 'C:\\ProgramData',
|
|
111
|
+
localAppData: localAppData(),
|
|
112
|
+
secrets: secretDir('unity'),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export const unityAdapter = unityAdapterFor(defaultDirs());
|
|
116
|
+
/**
|
|
117
|
+
* Activates the serial the sandbox holds once an editor exists, then asks `unity license status` where the licence
|
|
118
|
+
* stands. The detail never holds a secret.
|
|
119
|
+
*/
|
|
120
|
+
export async function activateUnityLicence(o, dirs = defaultDirs(), run = runUnityProgram) {
|
|
121
|
+
const sign = readSecretJson(ActivationSchema, path.join(dirs.secrets, 'activation.json'));
|
|
122
|
+
if (sign?.serial && sign.email && sign.password && o.editorExe) {
|
|
123
|
+
await run(o.editorExe, [
|
|
124
|
+
'-batchmode',
|
|
125
|
+
'-nographics',
|
|
126
|
+
'-quit',
|
|
127
|
+
'-serial',
|
|
128
|
+
sign.serial,
|
|
129
|
+
'-username',
|
|
130
|
+
sign.email,
|
|
131
|
+
'-password',
|
|
132
|
+
sign.password,
|
|
133
|
+
'-logFile',
|
|
134
|
+
path.join(dirs.secrets, 'activate.log'),
|
|
135
|
+
], { timeoutMs: ACTIVATE_MS });
|
|
136
|
+
writeText(path.join(dirs.secrets, 'activated.json'), JSON.stringify({ editorExe: o.editorExe, serial: true }));
|
|
137
|
+
}
|
|
138
|
+
const user = sign?.email ?? null;
|
|
139
|
+
if (!o.unityExe)
|
|
140
|
+
return { state: 'unknown', kind: '', detail: 'not checked', user };
|
|
141
|
+
let parsed = null;
|
|
142
|
+
try {
|
|
143
|
+
const r = await run(o.unityExe, licenceStatusArgs(), { timeoutMs: STATUS_MS, env: UNITY_CLI_ENV });
|
|
144
|
+
parsed = r.code === 0 ? JSON.parse(r.stdout) : null;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
parsed = null; // not JSON: reported as not recognised
|
|
148
|
+
}
|
|
149
|
+
const s = parseLicenceStatus(parsed);
|
|
150
|
+
if (s.state === 'active')
|
|
151
|
+
return { state: 'active', kind: s.kind, detail: 'active (' + (s.kind || 'licensed') + ')', user };
|
|
152
|
+
if (s.state === 'none') {
|
|
153
|
+
const hasFile = readText(unityUlfFile(dirs.programData)) !== null || readText(unityEntitlementFile(dirs.localAppData)) !== null;
|
|
154
|
+
return hasFile
|
|
155
|
+
? {
|
|
156
|
+
state: 'invalid',
|
|
157
|
+
kind: '',
|
|
158
|
+
detail: 'the licence file from ' + (o.source ?? 'your saved sign-in') + ' is not accepted here: sign in again',
|
|
159
|
+
user,
|
|
160
|
+
}
|
|
161
|
+
: { state: 'none', kind: '', detail: 'none: sign in to Unity once in this sandbox', user };
|
|
162
|
+
}
|
|
163
|
+
return { state: 'unknown', kind: '', detail: s.detail, user };
|
|
164
|
+
}
|
|
@@ -13,10 +13,13 @@ import { errMsg } from '../../core/util/errors.js';
|
|
|
13
13
|
import { emptyEnv, tool } from '../toolchains/tool-env.js';
|
|
14
14
|
import { uvVars } from '../toolchains/runtimes.js';
|
|
15
15
|
import { COMET_MCP_PORT, cometShortcutArgs } from '../browser/comet.js';
|
|
16
|
+
import { perplexityAppShortcutArgs } from '../browser/perplexity-app.js';
|
|
16
17
|
import { afterInstall } from './after-install.js';
|
|
17
18
|
import { installFromKept } from './comet-unpack.js';
|
|
18
19
|
import { findExe } from './find-exe.js';
|
|
20
|
+
import { installStoreApp } from './store-app.js';
|
|
19
21
|
import { shortcuts } from './shortcuts.js';
|
|
22
|
+
import { SYSTEM } from '../../shared/system.js';
|
|
20
23
|
import { signatureCheck } from '../toolchains/installers.js';
|
|
21
24
|
import { resolve } from './sources.js';
|
|
22
25
|
import { unpack } from './unpack.js';
|
|
@@ -46,6 +49,7 @@ export class AppInstaller {
|
|
|
46
49
|
async prefetch(entries) {
|
|
47
50
|
const todo = entries.filter((e) => !('remote' in e.source) &&
|
|
48
51
|
!('locked' in e.source) &&
|
|
52
|
+
!('store' in e.source) &&
|
|
49
53
|
!('npm' in e.source) &&
|
|
50
54
|
e.id !== '7zip' &&
|
|
51
55
|
(this.options.refresh || !exists(path.join(tool('apps', ...e.id.split('/')), MARKER))));
|
|
@@ -97,6 +101,20 @@ export class AppInstaller {
|
|
|
97
101
|
this.installed.push(entry.name);
|
|
98
102
|
return;
|
|
99
103
|
}
|
|
104
|
+
if ('store' in entry.source) {
|
|
105
|
+
// A Store app: Windows in the sandbox is new on every boot, so it is installed again from the PC's package.
|
|
106
|
+
log('Installing ' + entry.name + ' (Microsoft Store package) ...');
|
|
107
|
+
await installStoreApp(entry.id, entry.source.store.packageName);
|
|
108
|
+
fs.writeFileSync(path.join(mkdirp(dest), MARKER), JSON.stringify({ id: entry.id, at: new Date().toISOString() }));
|
|
109
|
+
this.gui.push({
|
|
110
|
+
name: entry.name,
|
|
111
|
+
exe: SYSTEM.powershell(),
|
|
112
|
+
args: perplexityAppShortcutArgs(path.join(launchDir, 'sandbox', 'scripts', 'start-store-app.ps1')),
|
|
113
|
+
});
|
|
114
|
+
this.installed.push(entry.name);
|
|
115
|
+
this.installedIds.push(entry.id);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
100
118
|
if (!exists(path.join(dest, MARKER)) || this.options.refresh)
|
|
101
119
|
await this.download(entry, dest);
|
|
102
120
|
// A Chromium installer that could not be unpacked is kept and installed into the sandbox each boot instead.
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Where the PC left an app's package. */
|
|
2
|
+
export declare function storePackageDir(appId: string): string;
|
|
3
|
+
/** Installs the app from its kept package; resolves when Windows has it. */
|
|
4
|
+
export declare function installStoreApp(appId: string, packageName: string): Promise<void>;
|
|
@@ -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
|
+
}
|