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
package/dist/runtime/view.js
CHANGED
|
@@ -26294,21 +26294,35 @@ var ToolchainsSchema = external_exports.strictObject({
|
|
|
26294
26294
|
}),
|
|
26295
26295
|
unity: external_exports.strictObject({
|
|
26296
26296
|
enabled: enabled(
|
|
26297
|
-
"Unity: the editor version a project asks for, from Unity's release API (checked against its published hash), installed
|
|
26297
|
+
"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.",
|
|
26298
26298
|
"ProjectSettings/ProjectVersion.txt"
|
|
26299
26299
|
),
|
|
26300
26300
|
acceptLicense: external_exports.boolean().describe(
|
|
26301
26301
|
"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."
|
|
26302
26302
|
),
|
|
26303
26303
|
version: external_exports.string().min(1).describe(
|
|
26304
|
-
|
|
26304
|
+
`"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).`
|
|
26305
26305
|
),
|
|
26306
26306
|
modules: external_exports.array(external_exports.string().regex(/^[a-z0-9-]+$/)).describe(
|
|
26307
26307
|
`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).`
|
|
26308
26308
|
),
|
|
26309
|
-
hub: external_exports.boolean().describe(
|
|
26309
|
+
hub: external_exports.boolean().describe(
|
|
26310
|
+
"Also Unity Hub (kept on the data disk), for signing in to Unity Personal and installing editors by hand."
|
|
26311
|
+
),
|
|
26310
26312
|
keepVersions: external_exports.number().int().min(1).max(20).describe(
|
|
26311
26313
|
"How many editor versions to keep in tools\\unity; older ones are removed (the least recently used first)."
|
|
26314
|
+
),
|
|
26315
|
+
mcp: external_exports.boolean().describe(
|
|
26316
|
+
"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."
|
|
26317
|
+
),
|
|
26318
|
+
agentSkills: external_exports.boolean().describe(
|
|
26319
|
+
"Unity's skills for Claude Code and Codex, from Unity-Technologies/skills at the commit the Unity CLI pins."
|
|
26320
|
+
),
|
|
26321
|
+
cliVersion: external_exports.string().regex(/^(latest|\d+\.\d+\.\d+(-[A-Za-z0-9.]+)?)$/).describe(
|
|
26322
|
+
'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.'
|
|
26323
|
+
),
|
|
26324
|
+
assetCache: external_exports.enum(["persisted", "pc", "local"]).describe(
|
|
26325
|
+
`"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).`
|
|
26312
26326
|
)
|
|
26313
26327
|
}),
|
|
26314
26328
|
godot: external_exports.strictObject({
|
|
@@ -26460,7 +26474,7 @@ var ToolchainsSchema = external_exports.strictObject({
|
|
|
26460
26474
|
}),
|
|
26461
26475
|
cli: external_exports.strictObject({
|
|
26462
26476
|
gh: EnabledSchema.describe(
|
|
26463
|
-
'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
|
|
26477
|
+
'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.'
|
|
26464
26478
|
),
|
|
26465
26479
|
zstd: EnabledSchema.describe('Zstandard (zstd). "auto" = off; set true to get it.'),
|
|
26466
26480
|
openssl: EnabledSchema.describe(
|
|
@@ -26515,6 +26529,9 @@ var COMET_SIGNER = {
|
|
|
26515
26529
|
subject: ['(^|, )CN="?PERPLEXITY AI, INC\\.?"?(,|$)', '(^|, )O="?PERPLEXITY AI, INC\\.?"?(,|$)'],
|
|
26516
26530
|
issuer: ["^CN=GlobalSign GCC R45 EV CodeSigning CA 2020(,|$)"]
|
|
26517
26531
|
};
|
|
26532
|
+
var PERPLEXITY_DESKTOP_PRODUCT_ID = "9P9XG917PWCJ";
|
|
26533
|
+
var PERPLEXITY_DESKTOP_PACKAGE = "PerplexityAI.PerplexityApp";
|
|
26534
|
+
var PERPLEXITY_DESKTOP_FAMILY = "PerplexityAI.PerplexityApp_3jh4kjrg4dzr2";
|
|
26518
26535
|
var EDGE_EXE = "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe";
|
|
26519
26536
|
var AGENTS_WEB_APPS = [
|
|
26520
26537
|
{
|
|
@@ -26561,6 +26578,22 @@ var AGENTS_WEB_APPS = [
|
|
|
26561
26578
|
signer: COMET_SIGNER,
|
|
26562
26579
|
userOnly: true
|
|
26563
26580
|
},
|
|
26581
|
+
{
|
|
26582
|
+
id: "perplexity-desktop",
|
|
26583
|
+
name: "Perplexity (desktop app)",
|
|
26584
|
+
category: "agents",
|
|
26585
|
+
about: "Perplexity's desktop app, with Perplexity Computer, from the Microsoft Store. The package comes from this PC (winget with your own Microsoft Store access) and is kept on the data disk; the sandbox installs it on every boot. With accounts.perplexityapp.share on, your sign-in made in a sandbox is kept for the next ones (it cannot be copied from this PC). It acts on what it reads: use it only on what you would let it act on.",
|
|
26586
|
+
size: "300 MB",
|
|
26587
|
+
source: {
|
|
26588
|
+
store: {
|
|
26589
|
+
productId: PERPLEXITY_DESKTOP_PRODUCT_ID,
|
|
26590
|
+
packageName: PERPLEXITY_DESKTOP_PACKAGE,
|
|
26591
|
+
family: PERPLEXITY_DESKTOP_FAMILY
|
|
26592
|
+
}
|
|
26593
|
+
},
|
|
26594
|
+
exe: "",
|
|
26595
|
+
userOnly: true
|
|
26596
|
+
},
|
|
26564
26597
|
{
|
|
26565
26598
|
id: "comet-mcp",
|
|
26566
26599
|
name: "Comet MCP",
|
|
@@ -26614,6 +26647,8 @@ var AGENTS_WEB_APPS = [
|
|
|
26614
26647
|
];
|
|
26615
26648
|
|
|
26616
26649
|
// src/shared/apps/catalogue-gamedev.ts
|
|
26650
|
+
var UNITY_MCP_VERSION = "10.3.0";
|
|
26651
|
+
var OPENUPM_CLI_VERSION = "4.5.3";
|
|
26617
26652
|
var GAMEDEV_APPS = [
|
|
26618
26653
|
{
|
|
26619
26654
|
id: "krita",
|
|
@@ -26683,6 +26718,34 @@ var GAMEDEV_APPS = [
|
|
|
26683
26718
|
size: "60 MB",
|
|
26684
26719
|
source: { github: "nomacs/nomacs", asset: "^nomacs-portable-win\\.zip$" },
|
|
26685
26720
|
exe: "nomacs.exe"
|
|
26721
|
+
},
|
|
26722
|
+
{
|
|
26723
|
+
id: "unity-mcp",
|
|
26724
|
+
name: "MCP for Unity",
|
|
26725
|
+
category: "agents",
|
|
26726
|
+
about: "Lets Claude Code and Codex work in an open Unity editor (CoplayDev/unity-mcp 10.3.0, MIT; Unity 2021.3 to 6.x). Your project needs its package (Unity page: Add the editor bridge). Telemetry is off.",
|
|
26727
|
+
size: "60 MB",
|
|
26728
|
+
source: { locked: "uv", package: "mcpforunityserver", version: UNITY_MCP_VERSION },
|
|
26729
|
+
exe: "mcp-for-unity.exe",
|
|
26730
|
+
service: true,
|
|
26731
|
+
mcp: {
|
|
26732
|
+
name: "sbx-unity-mcp",
|
|
26733
|
+
program: true,
|
|
26734
|
+
args: ["--transport", "stdio"],
|
|
26735
|
+
env: { DISABLE_TELEMETRY: "1", UNITY_MCP_DISABLE_TELEMETRY: "1", MCP_DISABLE_TELEMETRY: "1" },
|
|
26736
|
+
codex: true
|
|
26737
|
+
},
|
|
26738
|
+
plugsInto: "Claude Code and Codex as the MCP server sbx-unity-mcp",
|
|
26739
|
+
userOnly: true
|
|
26740
|
+
},
|
|
26741
|
+
{
|
|
26742
|
+
id: "openupm-cli",
|
|
26743
|
+
name: "openupm-cli",
|
|
26744
|
+
category: "gamedev",
|
|
26745
|
+
about: "Adds OpenUPM packages to Unity projects from the command line (openupm 4.5.3, BSD-3-Clause).",
|
|
26746
|
+
size: "15 MB",
|
|
26747
|
+
source: { locked: "npm", package: "openupm-cli", version: OPENUPM_CLI_VERSION },
|
|
26748
|
+
exe: "openupm.cmd"
|
|
26686
26749
|
}
|
|
26687
26750
|
];
|
|
26688
26751
|
|
|
@@ -27557,6 +27620,21 @@ var CORE_APPS = [
|
|
|
27557
27620
|
];
|
|
27558
27621
|
var APPS = [...CORE_APPS, ...GAMEDEV_APPS, ...MODDING_APPS, ...MOBILE_APPS, ...AGENTS_WEB_APPS];
|
|
27559
27622
|
|
|
27623
|
+
// src/shared/apps/store-package.ts
|
|
27624
|
+
var GB = 1024 * 1024 * 1024;
|
|
27625
|
+
var FILE_NAME = /^[A-Za-z0-9._~+-]{1,200}\.(msix|appx|msixbundle|appxbundle)$/i;
|
|
27626
|
+
var StorePackageFileSchema = external_exports.strictObject({
|
|
27627
|
+
name: external_exports.string().regex(FILE_NAME),
|
|
27628
|
+
role: external_exports.enum(["main", "dependency"]),
|
|
27629
|
+
sha256: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
27630
|
+
bytes: external_exports.number().int().positive().max(2 * GB)
|
|
27631
|
+
});
|
|
27632
|
+
var StorePackageManifestSchema = external_exports.strictObject({
|
|
27633
|
+
productId: external_exports.string().regex(/^[A-Z0-9]{12}$/),
|
|
27634
|
+
fetchedAt: external_exports.string().max(40),
|
|
27635
|
+
files: external_exports.array(StorePackageFileSchema).min(1).max(24)
|
|
27636
|
+
}).refine((m) => m.files.filter((f) => f.role === "main").length === 1, "exactly one file must be the app itself").refine((m) => new Set(m.files.map((f) => f.name.toLowerCase())).size === m.files.length, "file names repeat");
|
|
27637
|
+
|
|
27560
27638
|
// src/core/schema/apps.ts
|
|
27561
27639
|
var CATALOGUE_IDS = APPS.filter((a) => !a.setting).map((a) => a.id);
|
|
27562
27640
|
var AppIdSchema = external_exports.enum(CATALOGUE_IDS);
|
|
@@ -27691,6 +27769,9 @@ var VpnSchema = external_exports.strictObject({
|
|
|
27691
27769
|
var GithubSchema = external_exports.strictObject({
|
|
27692
27770
|
shareToken: external_exports.boolean().describe(
|
|
27693
27771
|
'Give each sandbox the GitHub token you saved in Accounts (best a fine-grained token limited to some repositories and permissions), so gh and https git work there without logging in. Only with provisioning.git.access "full". The agent in the sandbox can read the token and use it for whatever it allows. A project file (.sandbox-config.json) cannot turn this on.'
|
|
27772
|
+
),
|
|
27773
|
+
usePcLogin: external_exports.boolean().describe(
|
|
27774
|
+
'Give each sandbox the GitHub CLI (gh) login from this PC, when there is one, so gh and https git work there without logging in. Used when no token saved for sandboxes is handed over. Only with provisioning.git.access "full". Without a gh login on the PC nothing happens. The login reaches everything your account can, and the agent in the sandbox can read it. A project file (.sandbox-config.json) cannot turn this on.'
|
|
27694
27775
|
)
|
|
27695
27776
|
});
|
|
27696
27777
|
|
|
@@ -27739,7 +27820,9 @@ var SERVICE_SPECS = {
|
|
|
27739
27820
|
warnings: [
|
|
27740
27821
|
AGENT_CAN_USE,
|
|
27741
27822
|
ONE_PLACE,
|
|
27742
|
-
"
|
|
27823
|
+
"Unity Personal is activated by signing in: sign in to Unity once in a sandbox and the licence it makes is kept for the next ones (if Unity refuses it there, sign in again).",
|
|
27824
|
+
"A Plus or Pro serial is activated in each sandbox and handed back when the sandbox stops cleanly; a sandbox closed abruptly may keep the seat until you remove it at id.unity.com (My Seats).",
|
|
27825
|
+
"A licence file taken from this PC may not work in a sandbox: Unity ties it to the computer."
|
|
27743
27826
|
],
|
|
27744
27827
|
fields: [
|
|
27745
27828
|
{ name: "email", label: "Unity ID email", secret: false, optional: true },
|
|
@@ -27866,11 +27949,28 @@ var SERVICE_SPECS = {
|
|
|
27866
27949
|
"Several sandboxes can use it at once: the sign-in the last one hands back is kept."
|
|
27867
27950
|
],
|
|
27868
27951
|
fields: []
|
|
27952
|
+
},
|
|
27953
|
+
perplexityapp: {
|
|
27954
|
+
kind: "perplexityapp",
|
|
27955
|
+
label: "Perplexity desktop app",
|
|
27956
|
+
usedBy: ["perplexityapp"],
|
|
27957
|
+
sources: ["sandbox"],
|
|
27958
|
+
singleHolder: false,
|
|
27959
|
+
returns: true,
|
|
27960
|
+
maxBytes: 256 * 1024,
|
|
27961
|
+
warnings: [
|
|
27962
|
+
AGENT_CAN_USE + " It can act as you in the Perplexity app and spend your Perplexity Computer credits; the app acts on the pages it reads, which can try to steer it.",
|
|
27963
|
+
"Your sign-in in the Perplexity app can't be copied from this PC: Windows locks it to your PC account. Sign in once inside a sandbox and it is kept for the next ones.",
|
|
27964
|
+
"Only the perplexity.ai sign-in cookies are kept: never saved passwords, other sites, localStorage or history.",
|
|
27965
|
+
"Several sandboxes can use it at once: the sign-in the last one hands back is kept."
|
|
27966
|
+
],
|
|
27967
|
+
fields: []
|
|
27869
27968
|
}
|
|
27870
27969
|
};
|
|
27871
27970
|
var APP_USER_TEXT = {
|
|
27872
27971
|
perplexity: "the Perplexity MCP app",
|
|
27873
|
-
comet: "the Comet app"
|
|
27972
|
+
comet: "the Comet app",
|
|
27973
|
+
perplexityapp: "the Perplexity desktop app"
|
|
27874
27974
|
};
|
|
27875
27975
|
function usedByText(usedBy) {
|
|
27876
27976
|
return usedBy.map((u) => u in APP_USER_TEXT ? APP_USER_TEXT[u] : u).join(" or ");
|
|
@@ -27895,7 +27995,11 @@ var AccountsSchema = external_exports.strictObject({
|
|
|
27895
27995
|
expo: share("expo", "type the token in Accounts"),
|
|
27896
27996
|
appstore: share("appstore", "import the AuthKey.p8 file in Accounts"),
|
|
27897
27997
|
perplexity: share("perplexity", "type the API key in Accounts, or import it from a .env file"),
|
|
27898
|
-
comet: share("comet", "sign in to Comet once in a sandbox: it cannot be taken from this PC")
|
|
27998
|
+
comet: share("comet", "sign in to Comet once in a sandbox: it cannot be taken from this PC"),
|
|
27999
|
+
perplexityapp: share(
|
|
28000
|
+
"perplexityapp",
|
|
28001
|
+
"sign in to the Perplexity app once in a sandbox: it cannot be taken from this PC"
|
|
28002
|
+
)
|
|
27899
28003
|
}).describe("Service sign-ins for sandboxes (game stores, engines, app signing, Perplexity). Manage them in Accounts.");
|
|
27900
28004
|
|
|
27901
28005
|
// src/core/schema/pc-installs.ts
|
|
@@ -27922,10 +28026,11 @@ var PcInstallsSchema = external_exports.strictObject({
|
|
|
27922
28026
|
unrealAssets: external_exports.union([external_exports.literal("auto"), external_exports.boolean()]).describe(
|
|
27923
28027
|
`The Epic Games Launcher's vault cache of this PC (the Fab and Marketplace packs you downloaded), read-only in the sandbox at C:\\PcInstalls\\unreal\\Vault: what you download on your PC is there for the agents at once. "auto" = whenever the sandbox is set up for Unreal, true = always, false = never.`
|
|
27924
28028
|
),
|
|
27925
|
-
unityEditors:
|
|
27926
|
-
|
|
27927
|
-
|
|
27928
|
-
|
|
28029
|
+
unityEditors: external_exports.union([external_exports.literal("auto"), external_exports.boolean(), external_exports.array(external_exports.string().regex(/^\d+\.\d+\.\d+[abfp]\d+$/))]).describe(
|
|
28030
|
+
`Unity editors installed on this PC (with Unity Hub), read-only in the sandbox under C:\\PcInstalls\\unity: a project opens with the PC's editor without a download. "auto" = all of them whenever the sandbox is set up for Unity, true = always, false = never, or a list (e.g. ["6000.0.32f1"]).`
|
|
28031
|
+
),
|
|
28032
|
+
unityAssets: external_exports.union([external_exports.literal("auto"), external_exports.boolean()]).describe(
|
|
28033
|
+
`This PC's Unity Asset Store downloads (.unitypackage files), read-only at C:\\PcInstalls\\unity\\AssetStore: what you download on your PC is there for the agents at once. "auto" = whenever the sandbox is set up for Unity, true = always, false = never.`
|
|
27929
28034
|
)
|
|
27930
28035
|
}).describe(
|
|
27931
28036
|
"Games, engines and editors installed on this PC, mapped read-only into sandboxes (no copy, no room on the sandbox's disk)."
|
|
@@ -539,11 +539,12 @@ export const agentsWebSuite = {
|
|
|
539
539
|
'perplexity-mcp',
|
|
540
540
|
'perplexity-computer-mcp',
|
|
541
541
|
'comet',
|
|
542
|
+
'perplexity-desktop',
|
|
542
543
|
'comet-mcp',
|
|
543
544
|
'chrome-devtools-mcp',
|
|
544
545
|
'browser-use-mcp',
|
|
545
546
|
];
|
|
546
|
-
t.equal(AGENTS_WEB_APPS.map((a) => a.id), ids, 'the
|
|
547
|
+
t.equal(AGENTS_WEB_APPS.map((a) => a.id), ids, 'the seven entries');
|
|
547
548
|
for (const entry of AGENTS_WEB_APPS) {
|
|
548
549
|
t.check(APPS.filter((a) => a.id === entry.id).length === 1 &&
|
|
549
550
|
entry.userOnly === true &&
|
|
@@ -439,9 +439,9 @@ export const enginesSuite = {
|
|
|
439
439
|
t.section('the game-development apps');
|
|
440
440
|
const ids = APPS.map((a) => a.id);
|
|
441
441
|
t.check(new Set(ids).size === ids.length, 'every app id is used once');
|
|
442
|
-
t.equal(GAMEDEV_APPS.map((a) => a.id), ['krita', 'inkscape', 'audacity', 'renderdoc', 'f3d', 'imagemagick', 'nomacs'], 'Krita, Inkscape, Audacity, RenderDoc,
|
|
442
|
+
t.equal(GAMEDEV_APPS.map((a) => a.id), ['krita', 'inkscape', 'audacity', 'renderdoc', 'f3d', 'imagemagick', 'nomacs', 'unity-mcp', 'openupm-cli'], 'Krita, Inkscape, Audacity, RenderDoc, F3D, ImageMagick, nomacs, then the Unity add-ons (Blender was there)');
|
|
443
443
|
t.check(ids.includes('blender'), 'Blender is in the catalogue');
|
|
444
|
-
t.check(GAMEDEV_APPS.every((a) => a.category === 'gamedev' && a.exe && a.about.length > 20), 'each is a game-development app with its program');
|
|
444
|
+
t.check(GAMEDEV_APPS.every((a) => (a.category === 'gamedev' || a.id === 'unity-mcp') && a.exe && a.about.length > 20), 'each is a game-development app with its program');
|
|
445
445
|
const audacity = new RegExp(GAMEDEV_APPS.find((a) => a.id === 'audacity').source.asset);
|
|
446
446
|
t.check(audacity.test('audacity-win-4.0.2-x86_64.7z') &&
|
|
447
447
|
audacity.test('audacity-win-3.7.7-64bit.zip') &&
|
|
@@ -9,7 +9,7 @@ import { pageLines } from '../cli/output/index.js';
|
|
|
9
9
|
import { DEFAULTS } from '../core/config/index.js';
|
|
10
10
|
import { applyOverrides, normalizeOverrides } from '../core/local-config/index.js';
|
|
11
11
|
import { ConfigSchema, LocalOverridesSchema } from '../core/schema/index.js';
|
|
12
|
-
import { checkGithubToken, GithubToken, githubTokenAllowed, prepareGithubToken, verifyGithubToken, } from '../host/github/index.js';
|
|
12
|
+
import { checkGithubToken, GithubToken, githubTokenAllowed, parseHostsYml, prepareGithubToken, readPcGhLogin, verifyGithubToken, } from '../host/github/index.js';
|
|
13
13
|
import { UserError } from '../core/util/index.js';
|
|
14
14
|
import { report } from '../cli/report.js';
|
|
15
15
|
import { toWireError, fromWireError } from '../app/kernel/index.js';
|
|
@@ -288,6 +288,69 @@ export const githubSuite = {
|
|
|
288
288
|
shareOn.github.shareToken = true;
|
|
289
289
|
t.check(prepareGithubToken(shareOn, shareOn.storage.root, expiring, [], Date.parse('2027-01-30T00:00:00.000Z')).notes.some((n) => n.includes('expires in 5 days')) &&
|
|
290
290
|
!prepareGithubToken(shareOn, shareOn.storage.root, expiring, [], Date.parse('2026-12-01T00:00:00.000Z')).notes.some((n) => n.includes('expires')), 'a launch mentions a token that ends within 7 days, and only then');
|
|
291
|
+
t.section('the gh login on this PC (github.usePcLogin)');
|
|
292
|
+
const PC_TOKEN = 'gho_' + 'a'.repeat(36);
|
|
293
|
+
const KEYRING_TOKEN = 'gho_' + 'b'.repeat(36);
|
|
294
|
+
const hosts = (token) => [
|
|
295
|
+
'github.com:',
|
|
296
|
+
' users:',
|
|
297
|
+
' other:',
|
|
298
|
+
' oauth_token: gho_' + 'z'.repeat(36),
|
|
299
|
+
' josh:',
|
|
300
|
+
...(token ? [' oauth_token: ' + token] : []),
|
|
301
|
+
' git_protocol: https',
|
|
302
|
+
' user: josh',
|
|
303
|
+
'',
|
|
304
|
+
].join('\r\n');
|
|
305
|
+
t.equal(parseHostsYml(hosts(PC_TOKEN)), { signedIn: true, token: PC_TOKEN }, 'hosts.yml: the active user token');
|
|
306
|
+
t.equal(parseHostsYml('github.com:\n oauth_token: ' + PC_TOKEN + '\n user: josh\n'), { signedIn: true, token: PC_TOKEN }, 'hosts.yml: an older file with the token under the host');
|
|
307
|
+
t.equal(parseHostsYml('ghe.corp:\n oauth_token: ' + PC_TOKEN + '\n'), { signedIn: false, token: null }, 'hosts.yml: other hosts are not read');
|
|
308
|
+
t.equal(parseHostsYml(hosts(null)), { signedIn: true, token: null }, 'hosts.yml: a login kept in Credential Manager');
|
|
309
|
+
t.equal(parseHostsYml(''), { signedIn: false, token: null }, 'hosts.yml: empty');
|
|
310
|
+
const pcDir = t.dir('github-pc');
|
|
311
|
+
const pcEnv = { GH_CONFIG_DIR: pcDir, GH_TOKEN: 'x'.repeat(30) };
|
|
312
|
+
t.check(readPcGhLogin({ env: pcEnv, run: () => 'never' }) === null, 'no hosts.yml: no login, quietly');
|
|
313
|
+
fs.writeFileSync(path.join(pcDir, 'hosts.yml'), hosts(PC_TOKEN));
|
|
314
|
+
t.check(readPcGhLogin({ env: pcEnv, run: () => 'never' }) === PC_TOKEN, 'the token is read from hosts.yml');
|
|
315
|
+
fs.writeFileSync(path.join(pcDir, 'hosts.yml'), hosts(null));
|
|
316
|
+
t.check(readPcGhLogin({ env: pcEnv, run: () => KEYRING_TOKEN }) === null, 'a keyring login with no gh.exe found: none');
|
|
317
|
+
const exeDir = t.dir('github-pc-programs');
|
|
318
|
+
fs.mkdirSync(path.join(exeDir, 'GitHub CLI'), { recursive: true });
|
|
319
|
+
fs.writeFileSync(path.join(exeDir, 'GitHub CLI', 'gh.exe'), '');
|
|
320
|
+
const seen = [];
|
|
321
|
+
const keyring = readPcGhLogin({
|
|
322
|
+
env: { ...pcEnv, ProgramFiles: exeDir },
|
|
323
|
+
run: (exe) => (seen.push(exe), KEYRING_TOKEN + '\n'),
|
|
324
|
+
});
|
|
325
|
+
t.check(keyring === KEYRING_TOKEN && seen.length === 1 && path.isAbsolute(seen[0] ?? ''), 'a keyring login is read with the PC gh.exe, by absolute path', JSON.stringify([keyring, seen]));
|
|
326
|
+
t.check(readPcGhLogin({ env: { ...pcEnv, ProgramFiles: exeDir }, run: () => 'not a token' }) === null, 'whatever gh prints must look like a token');
|
|
327
|
+
const pcConfig = f.makeConfig();
|
|
328
|
+
pcConfig.github.usePcLogin = true;
|
|
329
|
+
const pcStore = new GithubToken(pcConfig.storage.root, SecretStore.memory());
|
|
330
|
+
const pcNoLogin = prepareGithubToken(pcConfig, pcConfig.storage.root, pcStore, [], 0, {
|
|
331
|
+
env: { GH_CONFIG_DIR: t.dir('github-pc-empty') },
|
|
332
|
+
});
|
|
333
|
+
t.equal(pcNoLogin, { notes: [] }, 'on, but the PC has no gh login: nothing is delivered, nothing is said');
|
|
334
|
+
fs.writeFileSync(path.join(pcDir, 'hosts.yml'), hosts(PC_TOKEN));
|
|
335
|
+
const pcEnvOnly = { env: { GH_CONFIG_DIR: pcDir } };
|
|
336
|
+
const pcOn = prepareGithubToken(pcConfig, pcConfig.storage.root, pcStore, [], 0, pcEnvOnly);
|
|
337
|
+
t.check(pcOn.token === PC_TOKEN && pcOn.notes.length === 1 && !JSON.stringify(pcOn.notes).includes(PC_TOKEN), 'on with a gh login: delivered, and the notes never hold it');
|
|
338
|
+
pcConfig.github.usePcLogin = false;
|
|
339
|
+
t.equal(prepareGithubToken(pcConfig, pcConfig.storage.root, pcStore, [], 0, pcEnvOnly), { notes: [] }, 'off: the PC login is not even read');
|
|
340
|
+
pcConfig.github.usePcLogin = true;
|
|
341
|
+
pcConfig.github.shareToken = true;
|
|
342
|
+
pcStore.save(FINE);
|
|
343
|
+
t.check(prepareGithubToken(pcConfig, pcConfig.storage.root, pcStore, [], 0, pcEnvOnly).token === FINE, 'a token saved for sandboxes wins over the PC login');
|
|
344
|
+
pcStore.remove();
|
|
345
|
+
t.check(prepareGithubToken(pcConfig, pcConfig.storage.root, pcStore, [], 0, pcEnvOnly).token === PC_TOKEN, 'with none saved, the PC login is used');
|
|
346
|
+
pcConfig.provisioning.git.access = 'readonly';
|
|
347
|
+
const pcStrict = prepareGithubToken(pcConfig, pcConfig.storage.root, pcStore, [], 0, pcEnvOnly);
|
|
348
|
+
t.check(pcStrict.token === undefined &&
|
|
349
|
+
!githubTokenAllowed(pcConfig) &&
|
|
350
|
+
pcStrict.notes.some((n) => n.includes('"readonly"')), 'git access readonly: the PC login is not handed over');
|
|
351
|
+
const pcSecrets = SecretsFileSchema.safeParse({ githubToken: PC_TOKEN });
|
|
352
|
+
t.check(pcSecrets.success, 'an OAuth token from the PC passes the secrets file limits');
|
|
353
|
+
t.check(DEFAULTS.github.usePcLogin === false, 'the PC login is off by default');
|
|
291
354
|
t.section('errors with an action');
|
|
292
355
|
const fixable = new UserError('Broke.', 'Do it.', { op: 'sandbox.stop', label: 'Stop it', cli: 'sandbox stop' });
|
|
293
356
|
const wire = toWireError(fixable);
|
package/dist/selftest/index.js
CHANGED
|
@@ -18,10 +18,15 @@ import { steamSuite } from './steam.test.js';
|
|
|
18
18
|
import { enginesSuite } from './engines.test.js';
|
|
19
19
|
import { mixamoSuite } from './mixamo.test.js';
|
|
20
20
|
import { unitySuite } from './unity.test.js';
|
|
21
|
+
import { unitySetupSuite } from './unity-setup.test.js';
|
|
22
|
+
import { unityAccountsSuite } from './unity-accounts.test.js';
|
|
23
|
+
import { unityPageSuite } from './unity-page.test.js';
|
|
24
|
+
import { unityAgentSuite } from './unity-agent.test.js';
|
|
21
25
|
import { fivemSuite } from './fivem.test.js';
|
|
22
26
|
import { androidSuite } from './android.test.js';
|
|
23
27
|
import { mobileSuite } from './mobile.test.js';
|
|
24
28
|
import { agentsWebSuite } from './agents-web.test.js';
|
|
29
|
+
import { storeAppSuite } from './store-app.test.js';
|
|
25
30
|
import { credentialsSuite } from './credentials.test.js';
|
|
26
31
|
import { vpnSuite } from './vpn.test.js';
|
|
27
32
|
import { githubSuite } from './github.test.js';
|
|
@@ -142,10 +147,15 @@ export const SUITES = [
|
|
|
142
147
|
enginesSuite,
|
|
143
148
|
mixamoSuite,
|
|
144
149
|
unitySuite,
|
|
150
|
+
unitySetupSuite,
|
|
151
|
+
unityAccountsSuite,
|
|
152
|
+
unityPageSuite,
|
|
153
|
+
unityAgentSuite,
|
|
145
154
|
fivemSuite,
|
|
146
155
|
androidSuite,
|
|
147
156
|
mobileSuite,
|
|
148
157
|
agentsWebSuite,
|
|
158
|
+
storeAppSuite,
|
|
149
159
|
detectSuite,
|
|
150
160
|
networkSuite,
|
|
151
161
|
personalizationSuite,
|
|
@@ -11,6 +11,7 @@ import { maintenanceOptions } from '../cli/menu/pages/maintenance-page.js';
|
|
|
11
11
|
import { sandboxOptions } from '../cli/menu/pages/sandbox-page.js';
|
|
12
12
|
import { sessionsOptions } from '../cli/menu/pages/sessions-page.js';
|
|
13
13
|
import { UNREAL_SAMPLE } from './support/unreal-sample.js';
|
|
14
|
+
import { UNITY_SAMPLE } from './support/unity-sample.js';
|
|
14
15
|
import { settingsOf, settingsOptions } from '../cli/menu/pages/settings-page.js';
|
|
15
16
|
import { CATEGORIES } from '../app/features/settings/index.js';
|
|
16
17
|
import { agentsOptions } from '../cli/menu/pages/agents-page.js';
|
|
@@ -201,6 +202,7 @@ export function renderScreens(dir) {
|
|
|
201
202
|
],
|
|
202
203
|
defaultAgent: 'claude',
|
|
203
204
|
unreal: UNREAL_SAMPLE,
|
|
205
|
+
unity: UNITY_SAMPLE,
|
|
204
206
|
};
|
|
205
207
|
const stopped = {
|
|
206
208
|
...sandbox,
|
|
@@ -401,7 +403,7 @@ export function renderScreens(dir) {
|
|
|
401
403
|
out.push(block('1 Agents · Start', draw(0, { ...agentsOptions(agentsModel, API_FOLDER), startCategory: 'start' }, size, status), size), block('1 Agents · Options (navigation focused)', draw(0, { ...agentsOptions(agentsModel, API_FOLDER), startCategory: 'options' }, size, status, 0, true), size), block('1 Agents · On this PC', draw(0, { ...agentsOptions(agentsModel, API_FOLDER), startCategory: 'host' }, size, status), size), block('2 Sessions', draw(1, sessionsOptions({ sandboxRunning: true, sessions: SESSIONS, defaultAgent: 'claude' }), size, status), size), block('2 Sessions (no sandbox)', draw(1, sessionsOptions({ sandboxRunning: false, sessions: [], defaultAgent: 'claude' }), size), size));
|
|
402
404
|
for (const category of ['status', 'databases'])
|
|
403
405
|
out.push(block('3 Sandbox (stopped) · ' + category, draw(2, { ...sandboxOptions(stopped), startCategory: category }, size), size));
|
|
404
|
-
for (const category of ['status', 'projects', 'databases', 'unreal'])
|
|
406
|
+
for (const category of ['status', 'projects', 'databases', 'unreal', 'unity'])
|
|
405
407
|
out.push(block('3 Sandbox · ' + category, draw(2, { ...sandboxOptions(sandbox), startCategory: category }, size, status), size));
|
|
406
408
|
out.push(block('4 Apps (1 unsaved)', draw(3, appsOptions(user, appsData, appsDraft, 'user'), size, status, appsDraft.count()), size));
|
|
407
409
|
for (const category of CATEGORIES)
|
|
@@ -309,7 +309,7 @@ export const persistedInstallsSuite = {
|
|
|
309
309
|
const found = decideToolchains(everything, ['ProjectVersion.txt', 'project.godot', 'fxmanifest.lua'], ['unity']);
|
|
310
310
|
t.check(NOT_READY.toolchains.every((n) => !found[n].on), '"auto" never turns on a toolchain that is not written yet, whatever the project holds');
|
|
311
311
|
const shown = settingsOf(ConfigFile.user(t.at('settings', 'config.json'))).map((r) => r.key.join('.'));
|
|
312
|
-
t.check(!shown.some((k) => /^toolchains\.(
|
|
312
|
+
t.check(!shown.some((k) => /^toolchains\.(godot|android|flutter|dart|reactNative)\./.test(k)) &&
|
|
313
313
|
shown.includes('toolchains.unreal.visualStudio') &&
|
|
314
314
|
shown.some((k) => k.startsWith('toolchains.fivem.')), 'settings do not list the toolchains that are not written yet');
|
|
315
315
|
},
|
|
@@ -274,7 +274,7 @@ export const serviceAccountsSuite = {
|
|
|
274
274
|
process.env.LOCALAPPDATA = oldLocal;
|
|
275
275
|
}
|
|
276
276
|
t.section('the account command');
|
|
277
|
-
t.check(!COMMANDS.some((c) => ['epic', 'steam'
|
|
277
|
+
t.check(!COMMANDS.some((c) => ['epic', 'steam'].includes(c.name)), 'no command of its own for a service');
|
|
278
278
|
const account = buildProgram(() => undefined).commands.find((c) => c.name() === 'account');
|
|
279
279
|
const help = account.helpInformation();
|
|
280
280
|
t.check(['status', 'import', 'login', 'logout', '--from', '--yes'].every((w) => help.includes(w)), 'sandbox account --help lists status, import, login, logout, --from and --yes', help);
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// The Perplexity desktop app, a Microsoft Store package: the catalogue entry, the sign-in kind (sandbox only, like
|
|
2
|
+
// Comet), the package manifest, the PC's fetch and staging with a fake Store tool (no download, nothing run), the
|
|
3
|
+
// sandbox's check of the kept files and the shortcut's command line. Temp folders only.
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { DEFAULTS } from '../core/config/index.js';
|
|
8
|
+
import { OFF_IS_SAFER } from '../core/local-config/index.js';
|
|
9
|
+
import { HOST_SOURCES } from '../host/accounts/index.js';
|
|
10
|
+
import { prepareStorePackages, stagedPackageDir, StorePackages } from '../host/apps/index.js';
|
|
11
|
+
import { checkedPackage } from '../guest/apps/store-check.js';
|
|
12
|
+
import { peekPendingPerplexityApp, perplexityAppPendingFile } from '../guest/accounts/kinds/perplexityapp.js';
|
|
13
|
+
import { perplexityAppShortcutArgs, restoreAppSignIn } from '../guest/browser/perplexity-app.js';
|
|
14
|
+
import { appsToInstall, appById, isStorePackageFile, PERPLEXITY_DESKTOP_FAMILY, PERPLEXITY_DESKTOP_PACKAGE, PERPLEXITY_DESKTOP_PRODUCT_ID, storeApps, StorePackageManifestSchema, storePackageRole, } from '../shared/apps/index.js';
|
|
15
|
+
import { appUsersOn, PAYLOADS, SERVICE_SPECS, serviceWanted, usedByText, } from '../shared/accounts/index.js';
|
|
16
|
+
const MAIN = 'PerplexityAI.PerplexityApp_1.2.3.0_x64__3jh4kjrg4dzr2.msix';
|
|
17
|
+
const FRAMEWORK = 'Microsoft.VCLibs.140.00_14.0.33519.0_x64__8wekyb3d8bbwe.appx';
|
|
18
|
+
const sha = (text) => crypto.createHash('sha256').update(text).digest('hex');
|
|
19
|
+
/** A fake Store tool: writes the given files into -OutDir and answers like get-store-package.ps1. */
|
|
20
|
+
function fakeStore(files, ok = true) {
|
|
21
|
+
let calls = 0;
|
|
22
|
+
const run = (_exe, args) => {
|
|
23
|
+
calls++;
|
|
24
|
+
if (!ok)
|
|
25
|
+
return Promise.resolve({
|
|
26
|
+
status: 0,
|
|
27
|
+
stdout: JSON.stringify({ ok: false, message: 'no Store access', files: [] }),
|
|
28
|
+
stderr: '',
|
|
29
|
+
});
|
|
30
|
+
const out = args[args.indexOf('-OutDir') + 1];
|
|
31
|
+
fs.mkdirSync(out, { recursive: true });
|
|
32
|
+
for (const [name, text] of Object.entries(files))
|
|
33
|
+
if (path.basename(name) === name)
|
|
34
|
+
fs.writeFileSync(path.join(out, name), text);
|
|
35
|
+
return Promise.resolve({
|
|
36
|
+
status: 0,
|
|
37
|
+
stdout: 'progress\n' + JSON.stringify({ ok: true, message: 'ok', files: Object.keys(files) }),
|
|
38
|
+
stderr: '',
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
return { run, calls: () => calls };
|
|
42
|
+
}
|
|
43
|
+
export const storeAppSuite = {
|
|
44
|
+
name: 'store-app',
|
|
45
|
+
title: 'Perplexity desktop app: Store package fetched on the PC, installed in the sandbox, sign-in kept like Comet',
|
|
46
|
+
run: async (t) => {
|
|
47
|
+
t.section('the catalogue entry');
|
|
48
|
+
const entry = appById('perplexity-desktop');
|
|
49
|
+
t.check(entry !== undefined &&
|
|
50
|
+
'store' in entry.source &&
|
|
51
|
+
entry.source.store.productId === PERPLEXITY_DESKTOP_PRODUCT_ID &&
|
|
52
|
+
entry.source.store.family === PERPLEXITY_DESKTOP_FAMILY &&
|
|
53
|
+
PERPLEXITY_DESKTOP_FAMILY.startsWith(PERPLEXITY_DESKTOP_PACKAGE + '_') &&
|
|
54
|
+
entry.userOnly === true, 'a Store source with the product and the package family, yours only');
|
|
55
|
+
t.equal(storeApps(['rtk', 'perplexity-desktop']).map((a) => a.productId), ['9P9XG917PWCJ'], 'only Store apps are fetched');
|
|
56
|
+
t.check(appsToInstall({ ...DEFAULTS.apps, install: ['perplexity-desktop'] }, 'none').entries.length === 1, 'it needs no 7-Zip and no other app');
|
|
57
|
+
t.section('the sign-in kind');
|
|
58
|
+
const spec = SERVICE_SPECS.perplexityapp;
|
|
59
|
+
t.check(spec.sources.join() === 'sandbox' &&
|
|
60
|
+
spec.returns &&
|
|
61
|
+
!spec.singleHolder &&
|
|
62
|
+
DEFAULTS.accounts.perplexityapp.share === false &&
|
|
63
|
+
OFF_IS_SAFER.includes('accounts.perplexityapp.share') &&
|
|
64
|
+
spec.warnings.some((w) => w.includes("can't be copied from this PC")), 'only from a sandbox, handed back, not shared by default, a project file can only turn it off');
|
|
65
|
+
const source = HOST_SOURCES.perplexityapp;
|
|
66
|
+
t.check(!source.fromPc && !source.fromFile && !source.fromAnswers, 'nothing is read from this PC (the cookies are sealed to it)');
|
|
67
|
+
t.check(appUsersOn(['perplexity-desktop']).join() === 'perplexityapp' &&
|
|
68
|
+
serviceWanted('perplexityapp', new Set(['perplexityapp'])) &&
|
|
69
|
+
!serviceWanted('perplexityapp', new Set(['comet'])) &&
|
|
70
|
+
usedByText(['perplexityapp']) === 'the Perplexity desktop app', 'handed over only where the app is on');
|
|
71
|
+
const cookie = { name: 's', value: 'v', domain: '.perplexity.ai', path: '/', secure: true, httpOnly: true };
|
|
72
|
+
t.check(PAYLOADS.perplexityapp.safeParse({ cookies: [cookie] }).success &&
|
|
73
|
+
!PAYLOADS.perplexityapp.safeParse({ cookies: [{ ...cookie, domain: 'example.com' }] }).success, 'the payload is the perplexity.ai cookie allow-list');
|
|
74
|
+
const pendingRoot = t.dir('pending');
|
|
75
|
+
t.write(perplexityAppPendingFile(pendingRoot), JSON.stringify({ cookies: [cookie] }));
|
|
76
|
+
t.check(peekPendingPerplexityApp(pendingRoot)?.cookies.length === 1, 'a waiting sign-in is read and left in place');
|
|
77
|
+
t.write(perplexityAppPendingFile(pendingRoot), '{"cookies":[{"domain":"example.com"}]}');
|
|
78
|
+
t.check(peekPendingPerplexityApp(pendingRoot) === null, 'one that is not the allow-listed shape is not used');
|
|
79
|
+
t.check(!(await restoreAppSignIn({ cookies: [{ ...cookie, expires: 4_000_000_000 }] }, t.dir('no-app-profile'))), 'with the app not running, nothing is written');
|
|
80
|
+
t.section('the package manifest');
|
|
81
|
+
const file = (name, role) => ({ name, role, sha256: sha(name), bytes: 10 });
|
|
82
|
+
const manifest = (files) => ({
|
|
83
|
+
productId: '9P9XG917PWCJ',
|
|
84
|
+
fetchedAt: '2026-01-01T00:00:00Z',
|
|
85
|
+
files,
|
|
86
|
+
});
|
|
87
|
+
t.check(StorePackageManifestSchema.safeParse(manifest([file(MAIN, 'main'), file(FRAMEWORK, 'dependency')])).success &&
|
|
88
|
+
!StorePackageManifestSchema.safeParse(manifest([file(FRAMEWORK, 'dependency')])).success &&
|
|
89
|
+
!StorePackageManifestSchema.safeParse(manifest([file(MAIN, 'main'), file(MAIN.toUpperCase().replace('.MSIX', '.msix'), 'main')])).success &&
|
|
90
|
+
!StorePackageManifestSchema.safeParse(manifest([file('..\\evil.msix', 'main')])).success &&
|
|
91
|
+
!StorePackageManifestSchema.safeParse(manifest([file('x.exe', 'main')])).success, 'exactly one app file, and only .msix/.appx names (no paths, no programs)');
|
|
92
|
+
t.check(storePackageRole(MAIN, PERPLEXITY_DESKTOP_PACKAGE) === 'main' &&
|
|
93
|
+
storePackageRole(FRAMEWORK, PERPLEXITY_DESKTOP_PACKAGE) === 'dependency' &&
|
|
94
|
+
isStorePackageFile(MAIN) &&
|
|
95
|
+
!isStorePackageFile('a/b.msix'), 'the app is told from its frameworks by name');
|
|
96
|
+
t.section('this PC fetches and stages the package');
|
|
97
|
+
const storageRoot = t.dir('pc', 'storage');
|
|
98
|
+
const dataPath = t.dir('pc', 'storage', 'data', 'set');
|
|
99
|
+
const app = {
|
|
100
|
+
id: 'perplexity-desktop',
|
|
101
|
+
productId: PERPLEXITY_DESKTOP_PRODUCT_ID,
|
|
102
|
+
packageName: PERPLEXITY_DESKTOP_PACKAGE,
|
|
103
|
+
};
|
|
104
|
+
const store = fakeStore({
|
|
105
|
+
[MAIN]: 'app-bytes',
|
|
106
|
+
[FRAMEWORK]: 'framework-bytes',
|
|
107
|
+
'..evil.txt': 'x',
|
|
108
|
+
'../escape.msix': 'x',
|
|
109
|
+
});
|
|
110
|
+
let now = Date.parse('2026-06-01T00:00:00Z');
|
|
111
|
+
const packages = new StorePackages({ storageRoot, dataPath, run: store.run, now: () => now });
|
|
112
|
+
const first = await packages.ensure(app);
|
|
113
|
+
t.check(first.note === 'downloaded' &&
|
|
114
|
+
first.manifest.files.length === 2 &&
|
|
115
|
+
first.manifest.files.find((f) => f.role === 'main')?.sha256 === sha('app-bytes') &&
|
|
116
|
+
packages.cached(app.productId) !== null &&
|
|
117
|
+
!fs.existsSync(packages.cacheDir(app.productId) + '.new'), "the files are hashed here, not taken on the script's word; names that are not packages are ignored");
|
|
118
|
+
const second = await packages.ensure(app);
|
|
119
|
+
t.check(store.calls() === 1 && second.note.startsWith('kept'), 'a fresh cache is not fetched again');
|
|
120
|
+
const staged = packages.stage(app, first.manifest);
|
|
121
|
+
t.check(staged === stagedPackageDir(dataPath, app.id) &&
|
|
122
|
+
fs.readFileSync(path.join(staged, MAIN), 'utf8') === 'app-bytes' &&
|
|
123
|
+
fs.existsSync(path.join(staged, 'store-package.json')), 'the package is copied into the data folder with its manifest');
|
|
124
|
+
fs.writeFileSync(path.join(staged, 'stale.msix'), 'old');
|
|
125
|
+
packages.stage(app, first.manifest);
|
|
126
|
+
t.check(!fs.existsSync(path.join(staged, 'stale.msix')), 'a file the manifest does not list is removed');
|
|
127
|
+
t.section('an old cache and a failing Store');
|
|
128
|
+
now += 40 * 24 * 3600 * 1000;
|
|
129
|
+
const failing = new StorePackages({ storageRoot, dataPath, run: fakeStore({}, false).run, now: () => now });
|
|
130
|
+
const kept = await failing.ensure(app);
|
|
131
|
+
t.check(kept.note.includes('could not be fetched') && kept.note.includes('no Store access'), 'the old package stands, and says why');
|
|
132
|
+
const emptyRoot = t.dir('pc2', 'storage');
|
|
133
|
+
const emptyData = t.dir('pc2', 'storage', 'data', 'set');
|
|
134
|
+
const outcome = await prepareStorePackages([app], {
|
|
135
|
+
storageRoot: emptyRoot,
|
|
136
|
+
dataPath: emptyData,
|
|
137
|
+
run: fakeStore({}, false).run,
|
|
138
|
+
});
|
|
139
|
+
t.check(outcome.notes.length === 1 &&
|
|
140
|
+
outcome.notes[0].includes('is not prepared') &&
|
|
141
|
+
outcome.notes[0].includes('winget download --id 9P9XG917PWCJ'), 'with nothing kept the launch goes on, with a note that says what to fix');
|
|
142
|
+
const good = await prepareStorePackages([app], {
|
|
143
|
+
storageRoot: emptyRoot,
|
|
144
|
+
dataPath: emptyData,
|
|
145
|
+
run: fakeStore({ [MAIN]: 'a' }).run,
|
|
146
|
+
});
|
|
147
|
+
t.check(good.notes[0].includes('downloaded') && fs.existsSync(path.join(stagedPackageDir(emptyData, app.id), MAIN)), 'and a working Store stages it');
|
|
148
|
+
t.section('the sandbox checks the kept files');
|
|
149
|
+
const checked = await checkedPackage(staged);
|
|
150
|
+
t.check(path.basename(checked.main) === MAIN && checked.dependencies.map((d) => path.basename(d)).join() === FRAMEWORK, 'the app and its frameworks, in order');
|
|
151
|
+
fs.writeFileSync(path.join(staged, FRAMEWORK), 'tampered');
|
|
152
|
+
t.check((await t.thrownMessage(() => checkedPackage(staged))).includes('missing or changed'), 'a changed file stops the install');
|
|
153
|
+
t.check((await t.thrownMessage(() => checkedPackage(t.dir('nothing')))).includes('not handed over'), 'no package says so');
|
|
154
|
+
t.section('the shortcut');
|
|
155
|
+
const args = perplexityAppShortcutArgs('C:\\AgentLaunch\\sandbox\\scripts\\start-store-app.ps1', 'C:\\Users\\X\\profile');
|
|
156
|
+
t.check(args.includes('-File "C:\\AgentLaunch\\sandbox\\scripts\\start-store-app.ps1"') &&
|
|
157
|
+
args.includes('-PackageName PerplexityAI.PerplexityApp') &&
|
|
158
|
+
args.includes('-UserDataDir "C:\\Users\\X\\profile"') &&
|
|
159
|
+
args.includes('-Port 0') &&
|
|
160
|
+
args.includes('-WindowStyle Hidden'), 'a hidden PowerShell start with the package, its own profile and a free DevTools port');
|
|
161
|
+
},
|
|
162
|
+
};
|
|
@@ -44,10 +44,14 @@ export const LAUNCH_FILES_GOLDEN = [
|
|
|
44
44
|
'sandbox/addons/context7/package.json',
|
|
45
45
|
'sandbox/addons/headroom/requirements.in',
|
|
46
46
|
'sandbox/addons/headroom/requirements.txt',
|
|
47
|
+
'sandbox/addons/openupm-cli/package-lock.json',
|
|
48
|
+
'sandbox/addons/openupm-cli/package.json',
|
|
47
49
|
'sandbox/addons/perplexity-mcp/package-lock.json',
|
|
48
50
|
'sandbox/addons/perplexity-mcp/package.json',
|
|
49
51
|
'sandbox/addons/playwright-mcp/package-lock.json',
|
|
50
52
|
'sandbox/addons/playwright-mcp/package.json',
|
|
53
|
+
'sandbox/addons/unity-mcp/requirements.in',
|
|
54
|
+
'sandbox/addons/unity-mcp/requirements.txt',
|
|
51
55
|
'sandbox/boot/bootstrap.ps1',
|
|
52
56
|
'sandbox/boot/bootstrap/drives.ps1',
|
|
53
57
|
'sandbox/boot/bootstrap/files.ps1',
|
|
@@ -55,6 +59,7 @@ export const LAUNCH_FILES_GOLDEN = [
|
|
|
55
59
|
'sandbox/boot/bootstrap/status.ps1',
|
|
56
60
|
'sandbox/boot/logon.cmd',
|
|
57
61
|
'sandbox/boot/trust.cmd',
|
|
62
|
+
'sandbox/csharp/AppActivation.cs',
|
|
58
63
|
'sandbox/csharp/BindMount.cs',
|
|
59
64
|
'sandbox/csharp/CargoShim.cs',
|
|
60
65
|
'sandbox/csharp/Desktop.cs',
|
|
@@ -79,6 +84,7 @@ export const LAUNCH_FILES_GOLDEN = [
|
|
|
79
84
|
'sandbox/scripts/get-signature.ps1',
|
|
80
85
|
'sandbox/scripts/get-vs-setup.ps1',
|
|
81
86
|
'sandbox/scripts/install-shell-path.ps1',
|
|
87
|
+
'sandbox/scripts/install-store-package.ps1',
|
|
82
88
|
'sandbox/scripts/link-profile-dirs.ps1',
|
|
83
89
|
'sandbox/scripts/new-shortcuts.ps1',
|
|
84
90
|
'sandbox/scripts/repair-wmi-services.ps1',
|
|
@@ -88,6 +94,7 @@ export const LAUNCH_FILES_GOLDEN = [
|
|
|
88
94
|
'sandbox/scripts/set-dns-servers.ps1',
|
|
89
95
|
'sandbox/scripts/set-machine-environment.ps1',
|
|
90
96
|
'sandbox/scripts/set-registry-values.ps1',
|
|
97
|
+
'sandbox/scripts/start-store-app.ps1',
|
|
91
98
|
'sandbox/templates/fivem/README.txt',
|
|
92
99
|
'sandbox/templates/fivem/cs/Client/Client.csproj',
|
|
93
100
|
'sandbox/templates/fivem/cs/Client/ClientMain.cs',
|