windows-development-sandbox 1.25.1 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/config.example.json +12 -3
  2. package/dist/app/features/accounts/services.def.d.ts +1 -1
  3. package/dist/app/features/accounts/services.def.js +1 -1
  4. package/dist/app/features/defs.d.ts +198 -3
  5. package/dist/app/features/defs.js +8 -0
  6. package/dist/app/features/settings/categories.def.js +1 -0
  7. package/dist/app/features/unity/index.d.ts +2 -0
  8. package/dist/app/features/unity/index.js +3 -0
  9. package/dist/app/features/unity/unity.def.d.ts +294 -0
  10. package/dist/app/features/unity/unity.def.js +471 -0
  11. package/dist/app/features/unity/unity.run.d.ts +142 -0
  12. package/dist/app/features/unity/unity.run.js +363 -0
  13. package/dist/app/registry.js +6 -0
  14. package/dist/bin/agent-sandbox.js +2 -2
  15. package/dist/bin/{chunk-F4PVBN23.js → chunk-6WWMBCT2.js} +3580 -1962
  16. package/dist/bin/{chunk-3GAXLATK.js → chunk-JXL6ZOO3.js} +72 -5
  17. package/dist/bin/{chunk-G3TG4ZT7.js → chunk-ZXCAVVS4.js} +1 -1
  18. package/dist/bin/claude-host.js +2 -2
  19. package/dist/bin/claude-sbx.js +2 -2
  20. package/dist/bin/codex-sbx.js +2 -2
  21. package/dist/bin/gemini-sbx.js +2 -2
  22. package/dist/bin/sandbox.js +2 -2
  23. package/dist/bin/sbx-hide.js +2 -2
  24. package/dist/bin/sbx-show.js +2 -2
  25. package/dist/cli/commands/launch/launch.js +12 -0
  26. package/dist/cli/commands/registry.js +3 -0
  27. package/dist/cli/manifest.d.ts +33 -1
  28. package/dist/cli/manifest.js +32 -1
  29. package/dist/cli/menu/coverage-exempt.js +1 -0
  30. package/dist/cli/menu/pages/sandbox-page.d.ts +3 -0
  31. package/dist/cli/menu/pages/sandbox-page.js +40 -3
  32. package/dist/cli/menu/views.js +2 -0
  33. package/dist/core/config/defaults.js +16 -3
  34. package/dist/core/schema/accounts.d.ts +3 -0
  35. package/dist/core/schema/accounts.js +1 -0
  36. package/dist/core/schema/config.d.ts +29 -13
  37. package/dist/core/schema/github.d.ts +1 -0
  38. package/dist/core/schema/github.js +3 -0
  39. package/dist/core/schema/pc-installs.d.ts +2 -1
  40. package/dist/core/schema/pc-installs.js +6 -1
  41. package/dist/core/schema/toolchains.d.ts +8 -0
  42. package/dist/core/schema/toolchains.js +19 -4
  43. package/dist/guest/accounts/adapters.js +2 -0
  44. package/dist/guest/accounts/apply.d.ts +4 -2
  45. package/dist/guest/accounts/kinds/perplexityapp.d.ts +7 -0
  46. package/dist/guest/accounts/kinds/perplexityapp.js +53 -0
  47. package/dist/guest/accounts/kinds/unity.d.ts +27 -0
  48. package/dist/guest/accounts/kinds/unity.js +164 -7
  49. package/dist/guest/apps/app-installer.js +18 -0
  50. package/dist/guest/apps/store-app.d.ts +4 -0
  51. package/dist/guest/apps/store-app.js +19 -0
  52. package/dist/guest/apps/store-check.d.ts +8 -0
  53. package/dist/guest/apps/store-check.js +33 -0
  54. package/dist/guest/boot/boot.js +1 -1
  55. package/dist/guest/boot/guide.js +18 -0
  56. package/dist/guest/browser/comet.d.ts +3 -1
  57. package/dist/guest/browser/comet.js +1 -1
  58. package/dist/guest/browser/perplexity-app.d.ts +12 -0
  59. package/dist/guest/browser/perplexity-app.js +60 -0
  60. package/dist/guest/daemon/daemon.js +2 -0
  61. package/dist/guest/devtools/devtool.d.ts +1 -1
  62. package/dist/guest/devtools/registry.js +1 -2
  63. package/dist/guest/entries/tool.js +1 -1
  64. package/dist/guest/setup/addon-mcp.d.ts +7 -2
  65. package/dist/guest/setup/addon-mcp.js +19 -9
  66. package/dist/guest/setup/github-token.js +4 -4
  67. package/dist/guest/setup/unity-agent.d.ts +11 -0
  68. package/dist/guest/setup/unity-agent.js +36 -0
  69. package/dist/guest/toolchains/engines/unity-cli.d.ts +24 -0
  70. package/dist/guest/toolchains/engines/unity-cli.js +74 -0
  71. package/dist/guest/toolchains/engines/unity-hub.d.ts +2 -0
  72. package/dist/guest/toolchains/engines/unity-hub.js +27 -0
  73. package/dist/guest/toolchains/engines/unity-release.d.ts +4 -15
  74. package/dist/guest/toolchains/engines/unity-release.js +17 -5
  75. package/dist/guest/toolchains/engines/unity-status.d.ts +11 -0
  76. package/dist/guest/toolchains/engines/unity-status.js +39 -0
  77. package/dist/guest/toolchains/engines/unity.d.ts +20 -1
  78. package/dist/guest/toolchains/engines/unity.js +250 -3
  79. package/dist/guest/toolchains/plan.js +5 -1
  80. package/dist/guest/toolchains/tools/cli.js +2 -2
  81. package/dist/host/accounts/deliver.js +4 -0
  82. package/dist/host/accounts/kinds/perplexityapp.d.ts +2 -0
  83. package/dist/host/accounts/kinds/perplexityapp.js +8 -0
  84. package/dist/host/accounts/kinds/unity.d.ts +1 -1
  85. package/dist/host/accounts/kinds/unity.js +60 -8
  86. package/dist/host/accounts/leases.d.ts +2 -1
  87. package/dist/host/accounts/sources.js +2 -0
  88. package/dist/host/apps/index.d.ts +1 -0
  89. package/dist/host/apps/index.js +2 -0
  90. package/dist/host/apps/store-package.d.ts +51 -0
  91. package/dist/host/apps/store-package.js +209 -0
  92. package/dist/host/github/deliver.d.ts +8 -3
  93. package/dist/host/github/deliver.js +42 -29
  94. package/dist/host/github/index.d.ts +1 -0
  95. package/dist/host/github/index.js +1 -0
  96. package/dist/host/github/pc-login.d.ts +27 -0
  97. package/dist/host/github/pc-login.js +129 -0
  98. package/dist/host/pc-installs/discover.d.ts +4 -0
  99. package/dist/host/pc-installs/discover.js +16 -4
  100. package/dist/host/pc-installs/source.d.ts +1 -1
  101. package/dist/host/pc-installs/unity-assets.d.ts +5 -0
  102. package/dist/host/pc-installs/unity-assets.js +18 -0
  103. package/dist/host/pc-installs/unity.d.ts +20 -0
  104. package/dist/host/pc-installs/unity.js +135 -2
  105. package/dist/runtime/agent.js +116 -11
  106. package/dist/runtime/daemon.js +2170 -976
  107. package/dist/runtime/db.js +116 -11
  108. package/dist/runtime/main.js +2543 -1364
  109. package/dist/runtime/netproxy.js +116 -11
  110. package/dist/runtime/tool.js +133 -31
  111. package/dist/runtime/view.js +116 -11
  112. package/dist/selftest/agents-web.test.js +2 -1
  113. package/dist/selftest/engines.test.js +2 -2
  114. package/dist/selftest/github.test.js +64 -1
  115. package/dist/selftest/index.js +10 -0
  116. package/dist/selftest/menu-screens.js +3 -1
  117. package/dist/selftest/persisted-installs.test.js +1 -1
  118. package/dist/selftest/service-accounts.test.js +1 -1
  119. package/dist/selftest/store-app.test.d.ts +2 -0
  120. package/dist/selftest/store-app.test.js +162 -0
  121. package/dist/selftest/support/engine-golden.js +7 -0
  122. package/dist/selftest/support/fixtures.d.ts +10 -1
  123. package/dist/selftest/support/unity-sample.d.ts +2 -0
  124. package/dist/selftest/support/unity-sample.js +80 -0
  125. package/dist/selftest/unity-accounts.test.d.ts +2 -0
  126. package/dist/selftest/unity-accounts.test.js +106 -0
  127. package/dist/selftest/unity-agent.test.d.ts +2 -0
  128. package/dist/selftest/unity-agent.test.js +47 -0
  129. package/dist/selftest/unity-page.test.d.ts +2 -0
  130. package/dist/selftest/unity-page.test.js +177 -0
  131. package/dist/selftest/unity-setup.test.d.ts +2 -0
  132. package/dist/selftest/unity-setup.test.js +49 -0
  133. package/dist/selftest/unity.test.js +238 -3
  134. package/dist/shared/accounts/index.d.ts +1 -0
  135. package/dist/shared/accounts/index.js +1 -0
  136. package/dist/shared/accounts/kinds/unity.d.ts +28 -0
  137. package/dist/shared/accounts/kinds/unity.js +88 -0
  138. package/dist/shared/accounts/kinds.d.ts +2 -2
  139. package/dist/shared/accounts/kinds.js +23 -1
  140. package/dist/shared/accounts/payloads.d.ts +52 -0
  141. package/dist/shared/accounts/payloads.js +14 -2
  142. package/dist/shared/apps/catalogue-agents-web.d.ts +7 -0
  143. package/dist/shared/apps/catalogue-agents-web.js +23 -0
  144. package/dist/shared/apps/catalogue-gamedev.d.ts +3 -0
  145. package/dist/shared/apps/catalogue-gamedev.js +31 -0
  146. package/dist/shared/apps/catalogue.d.ts +8 -0
  147. package/dist/shared/apps/index.d.ts +3 -2
  148. package/dist/shared/apps/index.js +3 -2
  149. package/dist/shared/apps/selection.d.ts +6 -0
  150. package/dist/shared/apps/selection.js +9 -0
  151. package/dist/shared/apps/store-package.d.ts +31 -0
  152. package/dist/shared/apps/store-package.js +34 -0
  153. package/dist/shared/channel/schema.d.ts +24 -0
  154. package/dist/shared/channel/secrets.d.ts +17 -0
  155. package/dist/shared/paths/pc-installs.d.ts +1 -1
  156. package/dist/shared/paths/pc-installs.js +1 -0
  157. package/dist/shared/readiness.d.ts +2 -2
  158. package/dist/shared/readiness.js +2 -2
  159. package/dist/shared/unity/asset-cache.d.ts +20 -0
  160. package/dist/shared/unity/asset-cache.js +44 -0
  161. package/dist/shared/unity/cli.d.ts +40 -0
  162. package/dist/shared/unity/cli.js +77 -0
  163. package/dist/shared/unity/editors.d.ts +46 -0
  164. package/dist/shared/unity/editors.js +89 -0
  165. package/dist/shared/unity/guide.d.ts +9 -0
  166. package/dist/shared/unity/guide.js +84 -0
  167. package/dist/shared/unity/index.d.ts +9 -0
  168. package/dist/shared/unity/index.js +11 -0
  169. package/dist/shared/unity/manifest.d.ts +10 -0
  170. package/dist/shared/unity/manifest.js +53 -0
  171. package/dist/shared/unity/mcp.d.ts +8 -0
  172. package/dist/shared/unity/mcp.js +12 -0
  173. package/dist/shared/unity/release.d.ts +28 -0
  174. package/dist/shared/unity/release.js +83 -0
  175. package/dist/shared/unity/status.d.ts +59 -0
  176. package/dist/shared/unity/status.js +45 -0
  177. package/dist/shared/unity/version.d.ts +27 -0
  178. package/dist/shared/unity/version.js +75 -0
  179. package/dist/web/index.html +1 -1
  180. package/dist/web/{main-FNE6XEHA.js → main-VPMYTSUL.js} +67 -67
  181. package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
  182. package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
  183. package/native/csharp/StorageHelper/AssemblyInfo.cs +3 -3
  184. package/native/scripts/get-store-package.ps1 +58 -0
  185. package/package.json +1 -1
  186. package/sandbox/addons/openupm-cli/package-lock.json +2921 -0
  187. package/sandbox/addons/openupm-cli/package.json +7 -0
  188. package/sandbox/addons/unity-mcp/requirements.in +1 -0
  189. package/sandbox/addons/unity-mcp/requirements.txt +1165 -0
  190. package/sandbox/csharp/AppActivation.cs +38 -0
  191. package/sandbox/scripts/install-store-package.ps1 +28 -0
  192. package/sandbox/scripts/start-store-app.ps1 +29 -0
  193. package/schemas/config.schema.json +61 -6
  194. package/schemas/sandbox-config.schema.json +38 -5
  195. package/dist/guest/devtools/unity.d.ts +0 -2
  196. package/dist/guest/devtools/unity.js +0 -7
@@ -1,7 +1,242 @@
1
+ // Unity: project versions, the editor choice, the release API, the Unity CLI, the editor bridge, the Asset Store cache,
2
+ // the status, the guide lines and the licence file helpers (pure; nothing is downloaded or run).
3
+ import fs from 'node:fs';
4
+ import { parseLicenceStatus, ulfSummary, UnityPayloadSchema, unityEntitlementFile, unitySummary, unityUlfFile, } from '../shared/accounts/index.js';
5
+ import { isKindReady, isToolchainReady } from '../shared/readiness.js';
6
+ import { assetPackages, bridgeOf, chooseEditor, cliManifestUrl, CliManifestSchema, COPLAY_PACKAGE, COPLAY_PACKAGE_URL, compareUnityVersions, editorModules, editorsAddArgs, installArgs, installModulesArgs, installNeedBytes, installPathArgs, licenceStatusArgs, ORIGIN_LABELS, parseProjectVersion, pcAssetCacheDirs, pickCliBinary, pickRelease, PIPELINE_PACKAGE, releaseUrl, supportsPipeline, UNITY_CLI_ENV, unityCliMcpServer, unityGuideLines, UnityStatusSchema, wantedVersion, withBridge, } from '../shared/unity/index.js';
7
+ const editor = (version, origin, modules = []) => {
8
+ const root = origin === 'pc' ? 'C:\\PcInstalls\\unity\\' + version : 'C:\\AgentData\\tools\\unity\\editors\\' + version;
9
+ return { version, root, exe: root + '\\Editor\\Unity.exe', origin, writable: origin === 'data', modules };
10
+ };
11
+ const RELEASE = {
12
+ results: [
13
+ {
14
+ version: '6000.0.58f2',
15
+ shortRevision: '92dee566b325',
16
+ downloads: [
17
+ {
18
+ url: 'https://download.unity3d.com/download_unity/92dee566b325/Windows64EditorInstaller/UnitySetup64-6000.0.58f2.exe',
19
+ type: 'EXE',
20
+ integrity: 'md5-AAAAAAAAAAAAAAAAAAAAAA==',
21
+ downloadSize: { value: 4_000_000_000 },
22
+ installedSize: { value: 8_000_000_000 },
23
+ modules: [
24
+ {
25
+ id: 'android',
26
+ downloadSize: { value: 100 },
27
+ installedSize: { value: 200 },
28
+ integrity: 'md5-BBBBBBBBBBBBBBBBBBBBBB==',
29
+ subModules: [
30
+ { id: 'android-sdk-ndk-tools', downloadSize: { value: 10 }, installedSize: { value: 20 } },
31
+ { id: 'android-open-jdk', downloadSize: { value: 1 }, installedSize: { value: 2 } },
32
+ ],
33
+ },
34
+ { id: 'webgl', downloadSize: { value: 50 }, installedSize: { value: 60 } },
35
+ ],
36
+ },
37
+ ],
38
+ },
39
+ ],
40
+ };
41
+ const SHA = 'a'.repeat(64);
42
+ function status(over = {}) {
43
+ return {
44
+ updatedAt: 'now',
45
+ cli: { version: '1.0.0-beta.16', path: 'C:\\AgentData\\tools\\unity\\cli\\1.0.0-beta.16\\unity.exe' },
46
+ editors: [
47
+ { ...editor('6000.0.58f2', 'pc'), modules: [], sizeBytes: null },
48
+ { ...editor('2022.3.40f1', 'data'), modules: [], sizeBytes: null },
49
+ ],
50
+ projects: [
51
+ {
52
+ workspace: 'C:\\Workspace\\G',
53
+ version: '6000.0.58f2',
54
+ editor: 'C:\\PcInstalls\\unity\\6000.0.58f2',
55
+ reason: 'the project asks for 6000.0.58f2',
56
+ bridge: null,
57
+ },
58
+ ],
59
+ licence: { state: 'active', kind: 'personal', detail: 'Personal', user: null },
60
+ assetCache: { mode: 'persisted', dir: 'C:\\AgentData\\tools\\unity\\asset-store' },
61
+ pcAssets: [],
62
+ hub: { installed: false, path: null },
63
+ mcp: null,
64
+ skills: false,
65
+ problem: null,
66
+ ...over,
67
+ };
68
+ }
1
69
  export const unitySuite = {
2
70
  name: 'unity',
3
- title: 'Unity: editors, modules, the release API and the licence (not available yet)',
4
- run: (t) => {
5
- t.check(true, 'unity: not available yet');
71
+ title: 'Unity: editors, the release API, the CLI, the editor bridge, the guide and the licence file',
72
+ run: async (t) => {
73
+ t.section('ProjectVersion.txt');
74
+ t.equal(parseProjectVersion('m_EditorVersion: 6000.0.58f2\nm_EditorVersionWithRevision: 6000.0.58f2 (92dee566b325)\n'), { version: '6000.0.58f2', changeset: '92dee566b325' }, 'version and changeset');
75
+ t.equal(parseProjectVersion('m_EditorVersion: 2022.3.40f1\n'), { version: '2022.3.40f1', changeset: null }, 'without a revision');
76
+ t.equal(wantedVersion('m_EditorVersion: 2022.3.40f1\n', '6000.0.58f2'), { version: '2022.3.40f1', changeset: null }, "a project's own version wins over the setting");
77
+ t.equal(wantedVersion(null, '6000.0.58f2'), { version: '6000.0.58f2', changeset: null }, 'the setting is for folders without one');
78
+ t.equal(wantedVersion(null, 'project'), null, '"project" and no file: nothing');
79
+ t.equal(parseProjectVersion('m_EditorVersion: 2022.3.40f1\r\nm_EditorVersionWithRevision: 2022.3.40f1 (0123456789ab)\r\n'), { version: '2022.3.40f1', changeset: '0123456789ab' }, 'CRLF');
80
+ t.equal(parseProjectVersion('hello\nm_EditorVersion: banana\n'), null, 'garbage');
81
+ t.equal(parseProjectVersion('x'.repeat(70 * 1024) + '\nm_EditorVersion: 6000.0.58f2\n'), null, 'past 64 KB');
82
+ t.equal(parseProjectVersion('\n'.repeat(60) + 'm_EditorVersion: 6000.0.58f2\n'), null, 'past 50 lines');
83
+ t.section('versions');
84
+ t.check(compareUnityVersions('2022.3.9f1', '2022.3.10f1') < 0, 'patch numbers compare as numbers');
85
+ t.check(compareUnityVersions('6000.0.1f1', '2022.3.40f1') > 0, 'newer major is newer');
86
+ t.check(compareUnityVersions('6000.0.1b1', '6000.0.1f1') < 0, 'beta before final');
87
+ t.check(compareUnityVersions('6000.0.1f1', '6000.0.1f1') === 0, 'equal');
88
+ t.check(supportsPipeline('6000.0.58f2') && !supportsPipeline('2022.3.40f1'), 'the pipeline needs Unity 6');
89
+ t.section('chooseEditor');
90
+ const pc = editor('6000.0.58f2', 'pc', ['android']);
91
+ const data = editor('6000.0.58f2', 'data', ['android', 'webgl']);
92
+ const other = editor('2022.3.40f1', 'data');
93
+ const want = { version: '6000.0.58f2', changeset: '92dee566b325' };
94
+ t.equal(chooseEditor([pc, data], null, [], true).reason, 'no ProjectSettings/ProjectVersion.txt', 'no version file');
95
+ const a = chooseEditor([pc, data], want, ['android'], true);
96
+ t.equal([a.editor?.origin, a.reason], ['pc', 'the project asks for 6000.0.58f2'], 'the PC copy first');
97
+ const b = chooseEditor([pc, data], want, ['android', 'webgl'], true);
98
+ t.equal([b.editor?.origin, b.reason], ['data', '6000.0.58f2 from this PC lacks webgl; using the data disk copy'], 'the PC copy lacks a module');
99
+ const c = chooseEditor([other], want, [], true);
100
+ t.equal([c.editor, c.install, c.reason], [null, want, '6000.0.58f2 is not here yet; installing it on the data disk'], 'install it, never another version');
101
+ const d = chooseEditor([other], want, [], false);
102
+ t.equal([d.editor, d.install, d.reason], [
103
+ null,
104
+ null,
105
+ '6000.0.58f2 is not on this PC or the data disk; accept the Unity licence (Unity set-up settings) to install it',
106
+ ], 'no licence accepted');
107
+ t.equal(chooseEditor([pc], want, ['webgl'], false).editor?.origin, 'pc', 'a PC copy missing modules beats none');
108
+ t.section('editorModules');
109
+ const root = t.dir('editor');
110
+ const engines = ['Editor', 'Data', 'PlaybackEngines'];
111
+ t.dir('editor', ...engines, 'AndroidPlayer');
112
+ t.dir('editor', ...engines, 'windowsstandalonesupport', 'Variations', 'win64_development_il2cpp');
113
+ t.equal(editorModules(root, { exists: (f) => fs.existsSync(f), list: (d) => fs.readdirSync(d) }), ['android', 'windows-il2cpp'], 'modules found by their folders');
114
+ t.section('release API');
115
+ t.includes(releaseUrl('6000.0.58f2'), 'version=6000.0.58f2&platform=WINDOWS', 'release url');
116
+ t.includes(releaseUrl('a&b'), 'version=a%26b', 'release url encodes');
117
+ const rel = pickRelease(RELEASE);
118
+ t.equal(rel?.changeset, '92dee566b325', 'changeset');
119
+ t.equal(rel?.editor.integrity, 'md5-AAAAAAAAAAAAAAAAAAAAAA==', 'md5 integrity kept');
120
+ t.equal(rel?.modules[0]?.children, ['android-sdk-ndk-tools', 'android-open-jdk'], 'android children');
121
+ t.equal(pickRelease({ results: [] }), null, 'no result');
122
+ t.equal(pickRelease('nope'), null, 'not an object');
123
+ if (rel) {
124
+ t.equal(installNeedBytes(rel, []), Math.ceil(12_000_000_000 * 1.1), 'editor only');
125
+ t.equal(installNeedBytes(rel, ['android']), Math.ceil((12_000_000_000 + 100 + 200 + 11 + 22) * 1.1), 'module and children');
126
+ }
127
+ t.section('Unity CLI');
128
+ t.equal(cliManifestUrl('latest'), 'https://public-cdn.cloud.unity3d.com/hub/prod/cli/latest-beta.json', 'latest');
129
+ t.equal(cliManifestUrl('1.0.0-beta.16'), 'https://public-cdn.cloud.unity3d.com/hub/prod/cli/1.0.0-beta.16/latest.json', 'pinned');
130
+ await t.throws(() => cliManifestUrl('../x'), 'not a Unity CLI version', 'a bad version');
131
+ const manifest = (filename, sha256) => CliManifestSchema.parse({ version: '1.0.0-beta.16', binaries: { 'win32-x64': { filename, sha256, size: 44 } } });
132
+ t.equal(pickCliBinary(manifest('unity-windows-x64.exe', SHA), 'x64'), {
133
+ version: '1.0.0-beta.16',
134
+ url: 'https://public-cdn.cloud.unity3d.com/hub/prod/cli/1.0.0-beta.16/unity-windows-x64.exe',
135
+ sha256: SHA,
136
+ size: 44,
137
+ }, 'the x64 build');
138
+ t.equal(pickCliBinary(manifest('..\\evil.exe', SHA), 'x64'), null, 'a bad file name');
139
+ t.equal(pickCliBinary(manifest('unity-windows-x64.exe', 'abc'), 'x64'), null, 'a bad sha');
140
+ t.equal(pickCliBinary(manifest('unity-windows-x64.exe', SHA), 'arm64'), null, 'no build for this CPU');
141
+ t.equal(installArgs('6000.0.58f2', '92dee566b325', ['android'], 'C:\\S'), ['install', '6000.0.58f2', '-c', '92dee566b325', '-m', 'android', '--cm', '--download-directory', 'C:\\S'], 'install args');
142
+ t.equal(installArgs('6000.0.58f2', null, [], 'C:\\S'), ['install', '6000.0.58f2', '--download-directory', 'C:\\S'], 'install args, bare');
143
+ t.equal(installModulesArgs('6000.0.58f2', ['webgl', 'ios'], 'C:\\S'), ['install-modules', '-e', '6000.0.58f2', '-m', 'webgl', 'ios', '--cm', '--download-directory', 'C:\\S'], 'install-modules args');
144
+ t.equal(installPathArgs('C:\\E'), ['install-path', '--set', 'C:\\E'], 'install-path args');
145
+ t.equal(editorsAddArgs(['C:\\A', 'C:\\B']), ['editors', 'add', 'C:\\A', 'C:\\B'], 'editors add args');
146
+ t.equal(licenceStatusArgs(), ['license', 'status', '--format', 'json'], 'licence status args');
147
+ t.section('MCP');
148
+ t.equal(unityCliMcpServer('C:\\u\\unity.exe', 'C:\\Workspace\\G'), {
149
+ type: 'stdio',
150
+ command: 'C:\\u\\unity.exe',
151
+ args: ['mcp', '--cli-tools'],
152
+ env: { ...UNITY_CLI_ENV, UNITY_PROJECT_PATH: 'C:\\Workspace\\G' },
153
+ }, 'with a project');
154
+ t.equal(unityCliMcpServer('u', null).env, { ...UNITY_CLI_ENV }, 'without a project');
155
+ t.section('Packages/manifest.json');
156
+ const manifestText = JSON.stringify({ dependencies: { 'com.a': '1.0.0', 'com.b': '2.0.0' }, scopedRegistries: [] }, null, 2) + '\n';
157
+ for (const bridge of ['pipeline', 'coplay']) {
158
+ const on = withBridge(manifestText, bridge, true);
159
+ t.equal(bridgeOf(on), bridge, bridge + ' added');
160
+ t.equal(Object.keys(JSON.parse(on).dependencies).slice(0, 2), ['com.a', 'com.b'], bridge + ': order kept');
161
+ t.equal(withBridge(on, bridge, false), manifestText, bridge + ': removed again, byte for byte');
162
+ }
163
+ t.equal(JSON.parse(withBridge(manifestText, 'pipeline', true, '0.11.0')).dependencies[PIPELINE_PACKAGE], '0.11.0', 'pipeline version');
164
+ t.equal(JSON.parse(withBridge(manifestText, 'coplay', true)).dependencies[COPLAY_PACKAGE], COPLAY_PACKAGE_URL, 'coplay url');
165
+ const crlf = withBridge(manifestText.replace(/\n/g, '\r\n'), 'pipeline', true);
166
+ t.check(crlf.includes('\r\n') && !/[^\r]\n/.test(crlf), 'CRLF kept');
167
+ t.equal(bridgeOf('not json'), null, 'garbage has no bridge');
168
+ t.equal(bridgeOf(manifestText), null, 'no bridge');
169
+ await t.throws(() => withBridge('[]', 'pipeline', true), 'not an object', 'refuses an array');
170
+ await t.throws(() => withBridge('{"a":"' + 'x'.repeat(1024 * 1024) + '"}', 'pipeline', true), 'over 1 MB', 'refuses a big file');
171
+ await t.throws(() => withBridge(manifestText, 'pipeline', true, '1.0; rm'), 'not a package version', 'bad version');
172
+ t.section('Asset Store cache');
173
+ const dirs = new Set(['C:\\Env\\Asset Store-5.x', 'C:\\Users\\u\\AppData\\Roaming\\Unity\\Asset Store-5.x']);
174
+ const isDir = (d) => dirs.has(d);
175
+ t.equal(pcAssetCacheDirs({ appData: 'C:\\Users\\u\\AppData\\Roaming', envValue: 'C:\\Env' }, isDir), ['C:\\Env', 'C:\\Users\\u\\AppData\\Roaming\\Unity'], 'the environment first');
176
+ t.equal(pcAssetCacheDirs({ appData: 'C:\\Users\\u\\AppData\\Roaming' }, isDir), ['C:\\Users\\u\\AppData\\Roaming\\Unity'], 'the default');
177
+ t.equal(pcAssetCacheDirs({ appData: 'C:\\X' }, isDir), [], 'none');
178
+ const tree = {
179
+ R: ['Pub', 'file.txt'],
180
+ 'R\\Pub': ['Cat'],
181
+ 'R\\Pub\\Cat': ['a.unitypackage', 'b.txt'],
182
+ };
183
+ const assetFs = {
184
+ list: (d) => tree[d] ?? [],
185
+ isDir: (d) => d in tree,
186
+ size: (f) => (f.endsWith('.unitypackage') ? 7 : null),
187
+ };
188
+ t.equal(assetPackages('R', assetFs), [{ publisher: 'Pub', category: 'Cat', name: 'a', sizeBytes: 7 }], 'packages');
189
+ const many = {
190
+ R: ['P'],
191
+ 'R\\P': ['C'],
192
+ 'R\\P\\C': Array.from({ length: 2500 }, (_, i) => 'p' + i + '.unitypackage'),
193
+ };
194
+ t.equal(assetPackages('R', { ...assetFs, list: (d) => many[d] ?? [], isDir: (d) => d in many }).length, 2000, 'at most 2000');
195
+ t.section('status');
196
+ const good = status();
197
+ t.check(UnityStatusSchema.safeParse(good).success, 'a status parses');
198
+ t.check(!UnityStatusSchema.safeParse({ ...good, editors: Array.from({ length: 33 }, () => good.editors[0]) }).success, '33 editors are refused');
199
+ t.section('guide');
200
+ const lines = (s) => unityGuideLines(s, [{ workspace: 'C:\\Workspace\\G' }]).join('\n');
201
+ const exe = '"C:\\PcInstalls\\unity\\6000.0.58f2\\Editor\\Unity.exe"';
202
+ t.equal(unityGuideLines(good, [{ workspace: 'C:\\Workspace\\G' }])[0], '- Unity: editor 6000.0.58f2 at `C:\\PcInstalls\\unity\\6000.0.58f2\\Editor\\Unity.exe` (' +
203
+ ORIGIN_LABELS.pc +
204
+ '; the project asks for 6000.0.58f2). Build or run code without opening it: `' +
205
+ exe +
206
+ ' -batchmode -nographics -quit -projectPath "C:\\Workspace\\G" -executeMethod <Class.Method> -logFile -`; tests: `-runTests -testPlatform EditMode -testResults <file>` (without -quit). Opening the project in another Unity version upgrades its files: never do that without asking the user. It is the user\'s PC copy, read-only: never add modules to it.', 'PC read-only line');
207
+ const onData = lines(status({
208
+ projects: [{ ...good.projects[0], editor: 'C:\\AgentData\\tools\\unity\\editors\\2022.3.40f1' }],
209
+ }));
210
+ t.includes(onData, '(' + ORIGIN_LABELS.data + ';', 'data origin label');
211
+ t.excludes(onData, 'read-only: never add modules', 'a data editor has no read-only note');
212
+ t.includes(lines(good), '- `unity` (Unity CLI 1.0.0-beta.16) is on PATH: `unity editors -i`, `unity build`, `unity test`, `unity open <project>` (opens it signed in).', 'CLI line');
213
+ t.includes(lines(good), "- To drive a running editor (scenes, GameObjects, play mode) the project needs an editor bridge package: ask the user to add it on the sandbox tool's Unity page. Never change Packages/manifest.json yourself unless asked.", 'no bridge');
214
+ const bridged = (bridge) => lines(status({ projects: [{ ...good.projects[0], bridge }] }));
215
+ t.includes(bridged('pipeline'), '- The open editor can be driven with `unity command` and `unity eval` (Unity Pipeline package); MCP clients use sbx-unity.', 'pipeline');
216
+ t.includes(bridged('coplay'), '- The open editor can be driven with MCP for Unity (sbx-unity-mcp in Apps).', 'coplay');
217
+ t.excludes(bridged('pipeline'), 'needs an editor bridge', 'no bridge line when there is one');
218
+ t.includes(lines(status({ pcAssets: ['C:\\PcInstalls\\unity\\AssetStore'] })), "- The user's Unity Asset Store downloads (read-only): `C:\\PcInstalls\\unity\\AssetStore`; import one with the editor closed:", 'assets');
219
+ t.includes(lines(status({ licence: { state: 'none', kind: '', detail: 'no licence', user: null } })), "- Unity licence: no licence. Batch mode fails without one: ask the user to sign in to Unity on the sandbox tool's Unity page.", 'no licence');
220
+ t.equal(unityGuideLines(good, [{ workspace: 'C:\\Elsewhere' }]), [], 'nothing for a project Unity has no word on');
221
+ t.section('licence files');
222
+ t.equal(unityUlfFile('C:\\ProgramData'), 'C:\\ProgramData\\Unity\\Unity_lic.ulf', 'ulf path');
223
+ t.equal(unityEntitlementFile('C:\\Users\\u\\AppData\\Local'), 'C:\\Users\\u\\AppData\\Local\\Unity\\licenses\\UnityEntitlementLicense.xml', 'entitlement path');
224
+ const ulf = '<?xml version="1.0" encoding="UTF-8"?>\r\n<root>\r\n <License id="Terms">\r\n <SerialMasked Value="U3-XXXX-XXXX-XXXX-XXXX-XXXX" />\r\n <UpdateDate Value="2026-10-01T10:00:00" />\r\n </License>\r\n</root>';
225
+ t.equal(ulfSummary(ulf), { serialMasked: 'U3-XXXX-XXXX-XXXX-XXXX-XXXX', updated: '2026-10-01T10:00:00', kind: 'personal' }, 'personal ulf');
226
+ t.equal(ulfSummary(ulf.replace('U3-', 'F4-'))?.kind, 'paid', 'paid ulf');
227
+ t.equal(ulfSummary('hello'), null, 'not a ulf');
228
+ t.equal(unitySummary({ email: 'me@x.com', password: 'hunter2hunter2' }), 'me@x.com', 'the email');
229
+ t.excludes(unitySummary({ email: 'me@x.com', password: 'hunter2hunter2', serial: 'SECRET' }), 'hunter2', 'no password in the summary');
230
+ t.equal(unitySummary({ ulf }), 'licence file (Personal)', 'a personal file');
231
+ t.equal(unitySummary({ ulf: ulf.replace('U3-', 'F4-') }), 'licence file (Plus or Pro)', 'a paid file');
232
+ t.check(!UnityPayloadSchema.safeParse({}).success, 'an empty sign-in is refused');
233
+ t.check(UnityPayloadSchema.safeParse({ entitlement: '<x/>' }).success, 'an entitlement alone is a sign-in');
234
+ t.equal(parseLicenceStatus({ weird: true }).state, 'unknown', 'an unknown shape is unknown');
235
+ t.equal(parseLicenceStatus('text').state, 'unknown', 'text is unknown');
236
+ t.equal(parseLicenceStatus(null).state, 'unknown', 'null is unknown');
237
+ t.equal(parseLicenceStatus({ licenses: [] }).state, 'none', 'no licences');
238
+ t.equal(parseLicenceStatus({ status: 'active', type: 'Personal' }).state, 'active', 'active');
239
+ t.section('readiness');
240
+ t.check(isToolchainReady('unity') && isKindReady('unity'), 'Unity is offered');
6
241
  },
7
242
  };
@@ -3,6 +3,7 @@ export * from './payloads.js';
3
3
  export { fingerprint } from './fingerprint.js';
4
4
  export { epicSettingsFile, epicSettingsFiles, MAX_EPIC_INI_BYTES, readEpicLogin, withEpicLogin, type EpicPayload, } from './kinds/epic.js';
5
5
  export * from './kinds/steam.js';
6
+ export * from './kinds/unity.js';
6
7
  export { PERPLEXITY_ENV, perplexityKeyFromText, perplexitySummary, type PerplexityPayload, } from './kinds/perplexity.js';
7
8
  export { cometPayloadFromCookies, cometSummary, cookiesToRestore, type CometPayload } from './kinds/comet.js';
8
9
  export * from './anthropic.js';
@@ -5,6 +5,7 @@ export * from './payloads.js';
5
5
  export { fingerprint } from './fingerprint.js';
6
6
  export { epicSettingsFile, epicSettingsFiles, MAX_EPIC_INI_BYTES, readEpicLogin, withEpicLogin, } from './kinds/epic.js';
7
7
  export * from './kinds/steam.js';
8
+ export * from './kinds/unity.js';
8
9
  export { PERPLEXITY_ENV, perplexityKeyFromText, perplexitySummary, } from './kinds/perplexity.js';
9
10
  export { cometPayloadFromCookies, cometSummary, cookiesToRestore } from './kinds/comet.js';
10
11
  export * from './anthropic.js';
@@ -0,0 +1,28 @@
1
+ import type { Payload } from '../payloads.js';
2
+ export type UnityPayload = Payload<'unity'>;
3
+ /** The licence files' size limit (the payload's). */
4
+ export declare const MAX_ULF_BYTES: number;
5
+ /** %PROGRAMDATA%\Unity\Unity_lic.ulf */
6
+ export declare function unityUlfFile(programData: string): string;
7
+ /** %LOCALAPPDATA%\Unity\licenses\UnityEntitlementLicense.xml */
8
+ export declare function unityEntitlementFile(localAppData: string): string;
9
+ export interface UlfSummary {
10
+ /** The serial as Unity masks it in the file (XXXX-...), never a full one. */
11
+ readonly serialMasked: string | null;
12
+ readonly updated: string | null;
13
+ readonly kind: 'personal' | 'paid' | 'unknown';
14
+ }
15
+ /** What a Unity_lic.ulf says (its SerialMasked and UpdateDate), or null when it holds neither. */
16
+ export declare function ulfSummary(text: string): UlfSummary | null;
17
+ /** One line for the status: the Unity ID, else what kind of licence file it is. Never a secret. */
18
+ export declare function unitySummary(p: UnityPayload): string;
19
+ export interface LicenceStatus {
20
+ readonly state: 'active' | 'none' | 'unknown';
21
+ readonly kind: string;
22
+ readonly detail: string;
23
+ }
24
+ /**
25
+ * What `unity license status --format json` printed. The shape is not documented (checked against the docs, not a live
26
+ * run), so anything unrecognised is 'unknown', never a guess.
27
+ */
28
+ export declare function parseLicenceStatus(json: unknown): LicenceStatus;
@@ -0,0 +1,88 @@
1
+ // Unity's licence files and what they say, without secrets: the serial licence (Unity_lic.ulf), the named-user licence
2
+ // (UnityEntitlementLicense.xml) and the answer of `unity license status`. Pure text handling, every scan bounded.
3
+ import path from 'node:path';
4
+ /** The licence files' size limit (the payload's). */
5
+ export const MAX_ULF_BYTES = 128 * 1024;
6
+ const MAX_LINES = 4000;
7
+ const MAX_FIELD = 200;
8
+ /** %PROGRAMDATA%\Unity\Unity_lic.ulf */
9
+ export function unityUlfFile(programData) {
10
+ return path.win32.join(programData, 'Unity', 'Unity_lic.ulf');
11
+ }
12
+ /** %LOCALAPPDATA%\Unity\licenses\UnityEntitlementLicense.xml */
13
+ export function unityEntitlementFile(localAppData) {
14
+ return path.win32.join(localAppData, 'Unity', 'licenses', 'UnityEntitlementLicense.xml');
15
+ }
16
+ /** The value of `<Name Value="..."/>` on one line, by index scan (no backtracking). */
17
+ function valueOf(line, name) {
18
+ const at = line.indexOf('<' + name);
19
+ if (at < 0)
20
+ return null;
21
+ const key = line.indexOf('Value="', at);
22
+ if (key < 0)
23
+ return null;
24
+ const from = key + 7;
25
+ const end = line.indexOf('"', from);
26
+ if (end < 0 || end - from > MAX_FIELD)
27
+ return null;
28
+ return line.slice(from, end);
29
+ }
30
+ /** What a Unity_lic.ulf says (its SerialMasked and UpdateDate), or null when it holds neither. */
31
+ export function ulfSummary(text) {
32
+ let serialMasked = null;
33
+ let updated = null;
34
+ const lines = text.slice(0, MAX_ULF_BYTES).split('\n', MAX_LINES);
35
+ for (const line of lines) {
36
+ serialMasked ??= valueOf(line, 'SerialMasked');
37
+ updated ??= valueOf(line, 'UpdateDate');
38
+ if (serialMasked !== null && updated !== null)
39
+ break;
40
+ }
41
+ if (serialMasked === null && updated === null)
42
+ return null;
43
+ const kind = serialMasked === null ? 'unknown' : serialMasked.startsWith('U3-') ? 'personal' : 'paid';
44
+ return { serialMasked, updated, kind };
45
+ }
46
+ /** One line for the status: the Unity ID, else what kind of licence file it is. Never a secret. */
47
+ export function unitySummary(p) {
48
+ if (p.email)
49
+ return p.email;
50
+ if (p.ulf) {
51
+ const kind = ulfSummary(p.ulf)?.kind;
52
+ return kind === 'personal' ? 'licence file (Personal)' : 'licence file (Plus or Pro)';
53
+ }
54
+ if (p.entitlement)
55
+ return 'licence file (Personal)';
56
+ return 'serial';
57
+ }
58
+ function text(v) {
59
+ return typeof v === 'string' ? v.slice(0, MAX_FIELD) : '';
60
+ }
61
+ /**
62
+ * What `unity license status --format json` printed. The shape is not documented (checked against the docs, not a live
63
+ * run), so anything unrecognised is 'unknown', never a guess.
64
+ */
65
+ export function parseLicenceStatus(json) {
66
+ const unknown = { state: 'unknown', kind: '', detail: 'not recognised' };
67
+ if (typeof json !== 'object' || json === null || Array.isArray(json))
68
+ return unknown;
69
+ const o = json;
70
+ const licences = Array.isArray(o.licenses)
71
+ ? o.licenses
72
+ : Array.isArray(o.licences)
73
+ ? o.licences
74
+ : null;
75
+ if (licences !== null) {
76
+ if (!licences.length)
77
+ return { state: 'none', kind: '', detail: 'no licence' };
78
+ const first = licences[0];
79
+ const f = typeof first === 'object' && first !== null ? first : {};
80
+ return { state: 'active', kind: text(f.type ?? f.kind), detail: text(f.detail ?? f.name ?? f.status) };
81
+ }
82
+ const status = text(o.status ?? o.state).toLowerCase();
83
+ if (status === 'active' || status === 'activated' || status === 'valid')
84
+ return { state: 'active', kind: text(o.type ?? o.kind), detail: text(o.detail ?? o.message) || status };
85
+ if (status === 'none' || status === 'inactive' || status === 'not activated' || status === 'unlicensed')
86
+ return { state: 'none', kind: '', detail: text(o.detail ?? o.message) || status };
87
+ return unknown;
88
+ }
@@ -1,9 +1,9 @@
1
- export declare const SERVICE_KINDS: readonly ["epic", "steam", "unity", "cfx", "keystore", "play", "expo", "appstore", "perplexity", "comet"];
1
+ export declare const SERVICE_KINDS: readonly ["epic", "steam", "unity", "cfx", "keystore", "play", "expo", "appstore", "perplexity", "comet", "perplexityapp"];
2
2
  export type ServiceKind = (typeof SERVICE_KINDS)[number];
3
3
  /** The toolchains a sign-in can serve (each is a key of `toolchains`). */
4
4
  export type ToolchainUser = 'unreal' | 'steam' | 'unity' | 'fivem' | 'android' | 'flutter' | 'reactNative' | 'godot' | 'dotnet';
5
5
  /** The apps a sign-in can serve: on when one of APP_USERS' apps is in apps.install. */
6
- export type AppUser = 'perplexity' | 'comet';
6
+ export type AppUser = 'perplexity' | 'comet' | 'perplexityapp';
7
7
  /** What a sign-in serves (it is handed over only when one of them is on for the boot). */
8
8
  export type UsedBy = ToolchainUser | AppUser;
9
9
  /** The toolchains a sign-in can serve. */
@@ -13,6 +13,7 @@ export const SERVICE_KINDS = [
13
13
  'appstore',
14
14
  'perplexity',
15
15
  'comet',
16
+ 'perplexityapp',
16
17
  ];
17
18
  /** The toolchains a sign-in can serve. */
18
19
  export const USED_BY = [
@@ -30,6 +31,7 @@ export const USED_BY = [
30
31
  export const APP_USERS = {
31
32
  perplexity: ['perplexity-mcp'],
32
33
  comet: ['comet'],
34
+ perplexityapp: ['perplexity-desktop'],
33
35
  };
34
36
  export const ACCOUNT_SOURCES = ['pc', 'file', 'typed', 'sandbox'];
35
37
  const AGENT_CAN_USE = 'The agent in the sandbox can use this sign-in while it is there.';
@@ -77,7 +79,9 @@ export const SERVICE_SPECS = {
77
79
  warnings: [
78
80
  AGENT_CAN_USE,
79
81
  ONE_PLACE,
80
- 'A Pro seat is handed back when the sandbox stops cleanly; a sandbox closed abruptly may keep it until you return it.',
82
+ '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).',
83
+ '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).',
84
+ 'A licence file taken from this PC may not work in a sandbox: Unity ties it to the computer.',
81
85
  ],
82
86
  fields: [
83
87
  { name: 'email', label: 'Unity ID email', secret: false, optional: true },
@@ -206,6 +210,23 @@ export const SERVICE_SPECS = {
206
210
  ],
207
211
  fields: [],
208
212
  },
213
+ perplexityapp: {
214
+ kind: 'perplexityapp',
215
+ label: 'Perplexity desktop app',
216
+ usedBy: ['perplexityapp'],
217
+ sources: ['sandbox'],
218
+ singleHolder: false,
219
+ returns: true,
220
+ maxBytes: 256 * 1024,
221
+ warnings: [
222
+ AGENT_CAN_USE +
223
+ ' 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.',
224
+ "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.",
225
+ 'Only the perplexity.ai sign-in cookies are kept: never saved passwords, other sites, localStorage or history.',
226
+ 'Several sandboxes can use it at once: the sign-in the last one hands back is kept.',
227
+ ],
228
+ fields: [],
229
+ },
209
230
  };
210
231
  /** Where a sandbox leaves a renewed sign-in, under the data folder: state\accounts\<kind>.json. */
211
232
  export const HANDOFF_PARTS = ['state', 'accounts'];
@@ -218,6 +239,7 @@ export function isServiceKind(value) {
218
239
  const APP_USER_TEXT = {
219
240
  perplexity: 'the Perplexity MCP app',
220
241
  comet: 'the Comet app',
242
+ perplexityapp: 'the Perplexity desktop app',
221
243
  };
222
244
  /** What a sign-in serves, for a sentence: "unreal", "reactNative or flutter", "the Comet app". */
223
245
  export function usedByText(usedBy) {
@@ -17,6 +17,7 @@ export declare const SteamPayloadSchema: z.ZodObject<{
17
17
  /** A Unity licence file (Unity_lic.ulf), and the Unity ID to sign in with when it has to be made again. */
18
18
  export declare const UnityPayloadSchema: z.ZodObject<{
19
19
  ulf: z.ZodOptional<z.ZodString>;
20
+ entitlement: z.ZodOptional<z.ZodString>;
20
21
  email: z.ZodOptional<z.ZodString>;
21
22
  password: z.ZodOptional<z.ZodString>;
22
23
  serial: z.ZodOptional<z.ZodString>;
@@ -91,6 +92,23 @@ export declare const CometPayloadSchema: z.ZodObject<{
91
92
  }>>;
92
93
  }, z.core.$strict>>;
93
94
  }, z.core.$strict>;
95
+ /** The Perplexity desktop app's sign-in: the same perplexity.ai cookies as Comet's. */
96
+ export declare const PerplexityAppPayloadSchema: z.ZodObject<{
97
+ cookies: z.ZodArray<z.ZodObject<{
98
+ name: z.ZodString;
99
+ value: z.ZodString;
100
+ domain: z.ZodString;
101
+ path: z.ZodString;
102
+ expires: z.ZodOptional<z.ZodNumber>;
103
+ secure: z.ZodBoolean;
104
+ httpOnly: z.ZodBoolean;
105
+ sameSite: z.ZodOptional<z.ZodEnum<{
106
+ Strict: "Strict";
107
+ Lax: "Lax";
108
+ None: "None";
109
+ }>>;
110
+ }, z.core.$strict>>;
111
+ }, z.core.$strict>;
94
112
  export declare const PAYLOADS: {
95
113
  readonly epic: z.ZodObject<{
96
114
  data: z.ZodString;
@@ -106,6 +124,7 @@ export declare const PAYLOADS: {
106
124
  }, z.core.$strict>;
107
125
  readonly unity: z.ZodObject<{
108
126
  ulf: z.ZodOptional<z.ZodString>;
127
+ entitlement: z.ZodOptional<z.ZodString>;
109
128
  email: z.ZodOptional<z.ZodString>;
110
129
  password: z.ZodOptional<z.ZodString>;
111
130
  serial: z.ZodOptional<z.ZodString>;
@@ -151,6 +170,22 @@ export declare const PAYLOADS: {
151
170
  }>>;
152
171
  }, z.core.$strict>>;
153
172
  }, z.core.$strict>;
173
+ readonly perplexityapp: z.ZodObject<{
174
+ cookies: z.ZodArray<z.ZodObject<{
175
+ name: z.ZodString;
176
+ value: z.ZodString;
177
+ domain: z.ZodString;
178
+ path: z.ZodString;
179
+ expires: z.ZodOptional<z.ZodNumber>;
180
+ secure: z.ZodBoolean;
181
+ httpOnly: z.ZodBoolean;
182
+ sameSite: z.ZodOptional<z.ZodEnum<{
183
+ Strict: "Strict";
184
+ Lax: "Lax";
185
+ None: "None";
186
+ }>>;
187
+ }, z.core.$strict>>;
188
+ }, z.core.$strict>;
154
189
  };
155
190
  export type Payload<K extends ServiceKind> = z.output<(typeof PAYLOADS)[K]>;
156
191
  export type AnyPayload = Payload<ServiceKind>;
@@ -173,6 +208,7 @@ export declare const ServiceAccountsSchema: z.ZodObject<{
173
208
  }, z.core.$strict>>;
174
209
  unity: z.ZodOptional<z.ZodObject<{
175
210
  ulf: z.ZodOptional<z.ZodString>;
211
+ entitlement: z.ZodOptional<z.ZodString>;
176
212
  email: z.ZodOptional<z.ZodString>;
177
213
  password: z.ZodOptional<z.ZodString>;
178
214
  serial: z.ZodOptional<z.ZodString>;
@@ -218,6 +254,22 @@ export declare const ServiceAccountsSchema: z.ZodObject<{
218
254
  }>>;
219
255
  }, z.core.$strict>>;
220
256
  }, z.core.$strict>>;
257
+ perplexityapp: z.ZodOptional<z.ZodObject<{
258
+ cookies: z.ZodArray<z.ZodObject<{
259
+ name: z.ZodString;
260
+ value: z.ZodString;
261
+ domain: z.ZodString;
262
+ path: z.ZodString;
263
+ expires: z.ZodOptional<z.ZodNumber>;
264
+ secure: z.ZodBoolean;
265
+ httpOnly: z.ZodBoolean;
266
+ sameSite: z.ZodOptional<z.ZodEnum<{
267
+ Strict: "Strict";
268
+ Lax: "Lax";
269
+ None: "None";
270
+ }>>;
271
+ }, z.core.$strict>>;
272
+ }, z.core.$strict>>;
221
273
  }, z.core.$strict>;
222
274
  export type ServiceAccounts = z.output<typeof ServiceAccountsSchema>;
223
275
  /** A launch's id (generate.ts: "<ms>-<6 hex>"). */
@@ -33,16 +33,24 @@ export const SteamPayloadSchema = z.strictObject({
33
33
  client: base64(256 * KB).optional(),
34
34
  });
35
35
  /** A Unity licence file (Unity_lic.ulf), and the Unity ID to sign in with when it has to be made again. */
36
- export const UnityPayloadSchema = z.strictObject({
36
+ export const UnityPayloadSchema = z
37
+ .strictObject({
37
38
  ulf: z
38
39
  .string()
39
40
  .min(1)
40
41
  .max(128 * KB)
41
42
  .optional(),
43
+ /** The named-user licence (UnityEntitlementLicense.xml) a Unity sign-in makes. */
44
+ entitlement: z
45
+ .string()
46
+ .min(1)
47
+ .max(128 * KB)
48
+ .optional(),
42
49
  email: z.string().email().max(256).optional(),
43
50
  password: secretText(1024).optional(),
44
51
  serial: secretText(64).optional(),
45
- });
52
+ })
53
+ .refine((p) => Object.keys(p).length > 0, 'empty Unity sign-in');
46
54
  /** A FiveM / RedM server licence key from the Cfx.re keymaster, and an optional Steam Web API key. */
47
55
  export const CfxPayloadSchema = z.strictObject({
48
56
  licenseKey: z
@@ -125,6 +133,8 @@ export const CometCookieSchema = z.strictObject({
125
133
  export const CometPayloadSchema = z.strictObject({
126
134
  cookies: z.array(CometCookieSchema).min(1).max(MAX_COMET_COOKIES),
127
135
  });
136
+ /** The Perplexity desktop app's sign-in: the same perplexity.ai cookies as Comet's. */
137
+ export const PerplexityAppPayloadSchema = CometPayloadSchema;
128
138
  export const PAYLOADS = {
129
139
  epic: EpicPayloadSchema,
130
140
  steam: SteamPayloadSchema,
@@ -136,6 +146,7 @@ export const PAYLOADS = {
136
146
  appstore: AppstorePayloadSchema,
137
147
  perplexity: PerplexityPayloadSchema,
138
148
  comet: CometPayloadSchema,
149
+ perplexityapp: PerplexityAppPayloadSchema,
139
150
  };
140
151
  /** The secrets-file field: the sign-ins this launch hands over, each optional. */
141
152
  export const ServiceAccountsSchema = z.strictObject({
@@ -149,6 +160,7 @@ export const ServiceAccountsSchema = z.strictObject({
149
160
  appstore: AppstorePayloadSchema.optional(),
150
161
  perplexity: PerplexityPayloadSchema.optional(),
151
162
  comet: CometPayloadSchema.optional(),
163
+ perplexityapp: PerplexityAppPayloadSchema.optional(),
152
164
  });
153
165
  /** A launch's id (generate.ts: "<ms>-<6 hex>"). */
154
166
  export const LaunchIdSchema = z.string().regex(/^[A-Za-z0-9-]{1,64}$/);
@@ -14,4 +14,11 @@ export declare const COMET_DOWNLOAD_URL = "https://www.perplexity.ai/rest/browse
14
14
  * subject with E=<contact address> first (E=..., CN="PERPLEXITY AI, INC.", O=...), so CN is not at the start.
15
15
  */
16
16
  export declare const COMET_SIGNER: AppSigner;
17
+ /**
18
+ * The Perplexity desktop app is Store-only (its old installer is gone): product 9P9XG917PWCJ, an MSIX of about 300 MB.
19
+ * The package is fetched on this PC with your own Store tooling and installed in the sandbox with Add-AppxPackage.
20
+ */
21
+ export declare const PERPLEXITY_DESKTOP_PRODUCT_ID = "9P9XG917PWCJ";
22
+ export declare const PERPLEXITY_DESKTOP_PACKAGE = "PerplexityAI.PerplexityApp";
23
+ export declare const PERPLEXITY_DESKTOP_FAMILY = "PerplexityAI.PerplexityApp_3jh4kjrg4dzr2";
17
24
  export declare const AGENTS_WEB_APPS: readonly AppEntry[];