@playdrop/playdrop-cli 0.5.1 → 0.5.3

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 (155) hide show
  1. package/config/client-meta.json +7 -7
  2. package/dist/apps/build.js +49 -6
  3. package/dist/apps/index.d.ts +2 -0
  4. package/dist/apps/index.js +2 -0
  5. package/dist/apps/upload.d.ts +2 -0
  6. package/dist/apps/upload.js +132 -28
  7. package/dist/assetSpecs.d.ts +16 -0
  8. package/dist/assetSpecs.js +263 -0
  9. package/dist/assets/model-artifacts.js +3 -0
  10. package/dist/catalogue.d.ts +57 -3
  11. package/dist/catalogue.js +342 -16
  12. package/dist/clientInfo.js +19 -3
  13. package/dist/commands/ads.d.ts +8 -0
  14. package/dist/commands/ads.js +124 -0
  15. package/dist/commands/boosts.d.ts +25 -0
  16. package/dist/commands/boosts.js +209 -0
  17. package/dist/commands/browse.d.ts +6 -1
  18. package/dist/commands/browse.js +365 -124
  19. package/dist/commands/captureListing.d.ts +53 -0
  20. package/dist/commands/captureListing.js +804 -0
  21. package/dist/commands/captureRemote.js +33 -0
  22. package/dist/commands/create.d.ts +1 -0
  23. package/dist/commands/create.js +183 -3
  24. package/dist/commands/credits.d.ts +6 -0
  25. package/dist/commands/credits.js +47 -1
  26. package/dist/commands/detail.js +38 -4
  27. package/dist/commands/devServer.js +10 -5
  28. package/dist/commands/generation.d.ts +2 -0
  29. package/dist/commands/generation.js +1 -0
  30. package/dist/commands/search.d.ts +5 -0
  31. package/dist/commands/search.js +139 -17
  32. package/dist/commands/tags.d.ts +7 -0
  33. package/dist/commands/tags.js +63 -0
  34. package/dist/commands/upload-content.d.ts +13 -3
  35. package/dist/commands/upload-content.js +86 -20
  36. package/dist/commands/upload.d.ts +2 -0
  37. package/dist/commands/upload.js +187 -11
  38. package/dist/commands/validate.js +163 -2
  39. package/dist/commands/versionsBrowse.js +128 -91
  40. package/dist/index.js +145 -3
  41. package/dist/refs.d.ts +2 -2
  42. package/dist/refs.js +13 -1
  43. package/dist/taskSelection.js +6 -3
  44. package/dist/taskUtils.d.ts +2 -2
  45. package/dist/taskUtils.js +1 -0
  46. package/dist/uploadLog.d.ts +1 -1
  47. package/dist/uploadLog.js +2 -2
  48. package/node_modules/@playdrop/ai-client/package.json +1 -1
  49. package/node_modules/@playdrop/api-client/dist/client.d.ts +131 -10
  50. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  51. package/node_modules/@playdrop/api-client/dist/client.js +6 -0
  52. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +9 -1
  53. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  54. package/node_modules/@playdrop/api-client/dist/domains/admin.js +45 -0
  55. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +3 -0
  56. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  57. package/node_modules/@playdrop/api-client/dist/domains/apps.js +27 -0
  58. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +2 -0
  59. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
  60. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +16 -0
  61. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +44 -2
  62. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  63. package/node_modules/@playdrop/api-client/dist/domains/assets.js +260 -3
  64. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +17 -1
  65. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  66. package/node_modules/@playdrop/api-client/dist/domains/payments.js +173 -0
  67. package/node_modules/@playdrop/api-client/dist/domains/search.d.ts.map +1 -1
  68. package/node_modules/@playdrop/api-client/dist/domains/search.js +39 -11
  69. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts +34 -0
  70. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts.map +1 -0
  71. package/node_modules/@playdrop/api-client/dist/domains/tags.js +111 -0
  72. package/node_modules/@playdrop/api-client/dist/index.d.ts +61 -1
  73. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  74. package/node_modules/@playdrop/api-client/dist/index.js +50 -0
  75. package/node_modules/@playdrop/api-client/package.json +1 -1
  76. package/node_modules/@playdrop/boxel-core/dist/test/entity-utils.test.d.ts +1 -0
  77. package/node_modules/@playdrop/boxel-core/dist/test/entity-utils.test.js +92 -0
  78. package/node_modules/@playdrop/boxel-core/dist/test/entity-utils.test.js.map +1 -0
  79. package/node_modules/@playdrop/boxel-core/dist/test/greedy-mesher.test.d.ts +1 -0
  80. package/node_modules/@playdrop/boxel-core/dist/test/greedy-mesher.test.js +48 -0
  81. package/node_modules/@playdrop/boxel-core/dist/test/greedy-mesher.test.js.map +1 -0
  82. package/node_modules/@playdrop/boxel-core/dist/test/humanoid/humanoid-builders.test.d.ts +1 -0
  83. package/node_modules/@playdrop/boxel-core/dist/test/humanoid/humanoid-builders.test.js +270 -0
  84. package/node_modules/@playdrop/boxel-core/dist/test/humanoid/humanoid-builders.test.js.map +1 -0
  85. package/node_modules/@playdrop/boxel-core/dist/test/index.test.d.ts +1 -0
  86. package/node_modules/@playdrop/boxel-core/dist/test/index.test.js +48 -0
  87. package/node_modules/@playdrop/boxel-core/dist/test/index.test.js.map +1 -0
  88. package/node_modules/@playdrop/boxel-core/dist/test/layer-mode.test.d.ts +1 -0
  89. package/node_modules/@playdrop/boxel-core/dist/test/layer-mode.test.js +67 -0
  90. package/node_modules/@playdrop/boxel-core/dist/test/layer-mode.test.js.map +1 -0
  91. package/node_modules/@playdrop/boxel-core/dist/test/materials.test.d.ts +1 -0
  92. package/node_modules/@playdrop/boxel-core/dist/test/materials.test.js +55 -0
  93. package/node_modules/@playdrop/boxel-core/dist/test/materials.test.js.map +1 -0
  94. package/node_modules/@playdrop/boxel-core/dist/test/palette-tools.test.d.ts +1 -0
  95. package/node_modules/@playdrop/boxel-core/dist/test/palette-tools.test.js +124 -0
  96. package/node_modules/@playdrop/boxel-core/dist/test/palette-tools.test.js.map +1 -0
  97. package/node_modules/@playdrop/boxel-core/dist/test/serialization.test.d.ts +1 -0
  98. package/node_modules/@playdrop/boxel-core/dist/test/serialization.test.js +35 -0
  99. package/node_modules/@playdrop/boxel-core/dist/test/serialization.test.js.map +1 -0
  100. package/node_modules/@playdrop/boxel-core/dist/test/textures.test.d.ts +1 -0
  101. package/node_modules/@playdrop/boxel-core/dist/test/textures.test.js +120 -0
  102. package/node_modules/@playdrop/boxel-core/dist/test/textures.test.js.map +1 -0
  103. package/node_modules/@playdrop/boxel-core/dist/test/types.test.d.ts +1 -0
  104. package/node_modules/@playdrop/boxel-core/dist/test/types.test.js +32 -0
  105. package/node_modules/@playdrop/boxel-core/dist/test/types.test.js.map +1 -0
  106. package/node_modules/@playdrop/boxel-core/dist/test/upscale.test.d.ts +1 -0
  107. package/node_modules/@playdrop/boxel-core/dist/test/upscale.test.js +100 -0
  108. package/node_modules/@playdrop/boxel-core/dist/test/upscale.test.js.map +1 -0
  109. package/node_modules/@playdrop/boxel-core/dist/test/validation.test.d.ts +1 -0
  110. package/node_modules/@playdrop/boxel-core/dist/test/validation.test.js +61 -0
  111. package/node_modules/@playdrop/boxel-core/dist/test/validation.test.js.map +1 -0
  112. package/node_modules/@playdrop/boxel-core/dist/test/voxels.test.d.ts +1 -0
  113. package/node_modules/@playdrop/boxel-core/dist/test/voxels.test.js +51 -0
  114. package/node_modules/@playdrop/boxel-core/dist/test/voxels.test.js.map +1 -0
  115. package/node_modules/@playdrop/boxel-core/package.json +1 -1
  116. package/node_modules/@playdrop/boxel-three/package.json +1 -1
  117. package/node_modules/@playdrop/config/client-meta.json +7 -7
  118. package/node_modules/@playdrop/config/dist/src/constants.d.ts +11 -0
  119. package/node_modules/@playdrop/config/dist/src/constants.d.ts.map +1 -1
  120. package/node_modules/@playdrop/config/dist/src/constants.js +12 -1
  121. package/node_modules/@playdrop/config/dist/src/creator-docs.d.ts +24 -0
  122. package/node_modules/@playdrop/config/dist/src/creator-docs.d.ts.map +1 -0
  123. package/node_modules/@playdrop/config/dist/src/creator-docs.js +253 -0
  124. package/node_modules/@playdrop/config/dist/src/creator-faq.d.ts +17 -0
  125. package/node_modules/@playdrop/config/dist/src/creator-faq.d.ts.map +1 -0
  126. package/node_modules/@playdrop/config/dist/src/creator-faq.js +141 -0
  127. package/node_modules/@playdrop/config/dist/test/creator-docs.test.d.ts +2 -0
  128. package/node_modules/@playdrop/config/dist/test/creator-docs.test.d.ts.map +1 -0
  129. package/node_modules/@playdrop/config/dist/test/creator-docs.test.js +36 -0
  130. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  131. package/node_modules/@playdrop/config/package.json +1 -1
  132. package/node_modules/@playdrop/types/dist/api.d.ts +346 -6
  133. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  134. package/node_modules/@playdrop/types/dist/api.js +52 -1
  135. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +7 -1
  136. package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
  137. package/node_modules/@playdrop/types/dist/asset-spec-contract-meta-schema.json +86 -0
  138. package/node_modules/@playdrop/types/dist/asset-spec.d.ts +163 -0
  139. package/node_modules/@playdrop/types/dist/asset-spec.d.ts.map +1 -0
  140. package/node_modules/@playdrop/types/dist/asset-spec.js +101 -0
  141. package/node_modules/@playdrop/types/dist/asset.d.ts +23 -6
  142. package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
  143. package/node_modules/@playdrop/types/dist/asset.js +4 -1
  144. package/node_modules/@playdrop/types/dist/graph.d.ts +4 -2
  145. package/node_modules/@playdrop/types/dist/graph.d.ts.map +1 -1
  146. package/node_modules/@playdrop/types/dist/graph.js +9 -2
  147. package/node_modules/@playdrop/types/dist/index.d.ts +1 -0
  148. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  149. package/node_modules/@playdrop/types/dist/index.js +1 -0
  150. package/node_modules/@playdrop/types/dist/version.d.ts +13 -0
  151. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  152. package/node_modules/@playdrop/types/dist/version.js +21 -0
  153. package/node_modules/@playdrop/types/package.json +6 -1
  154. package/node_modules/@playdrop/vox-three/package.json +1 -1
  155. package/package.json +3 -1
@@ -0,0 +1,804 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.parseCaptureListingOptions = parseCaptureListingOptions;
37
+ exports.assertSupportedListingEnvironment = assertSupportedListingEnvironment;
38
+ exports.resolveListingRecorderPath = resolveListingRecorderPath;
39
+ exports.computeRecordedCrop = computeRecordedCrop;
40
+ exports.captureListing = captureListing;
41
+ const node_child_process_1 = require("node:child_process");
42
+ const promises_1 = require("node:fs/promises");
43
+ const node_path_1 = require("node:path");
44
+ const types_1 = require("@playdrop/types");
45
+ const catalogue_utils_1 = require("../catalogue-utils");
46
+ const commandContext_1 = require("../commandContext");
47
+ const http_1 = require("../http");
48
+ const messages_1 = require("../messages");
49
+ const playwright_1 = require("../playwright");
50
+ const sessionCookie_1 = require("../sessionCookie");
51
+ const devShared_1 = require("./devShared");
52
+ const devServer_1 = require("./devServer");
53
+ const appUrls_1 = require("../appUrls");
54
+ const CAPTURE_FRAME_SELECTOR = 'iframe[title="Game"]';
55
+ const DEFAULT_DURATION_SECONDS = 8;
56
+ const DEFAULT_WIDTH = 1280;
57
+ const DEFAULT_HEIGHT = 720;
58
+ const DEFAULT_FPS = 30;
59
+ const DEFAULT_POSTER_AT_SECONDS = 2;
60
+ const MAX_CAPTURE_SECONDS = 300;
61
+ const MIN_CAPTURE_SECONDS = 1;
62
+ const MIN_DIMENSION = 16;
63
+ const MAX_DIMENSION = 8192;
64
+ const MAX_FPS = 120;
65
+ const SUPPORTED_MACOS_PREFIX = '26.4';
66
+ const DEV_SERVER_PORT = 8888;
67
+ function parsePositiveNumber(raw, fallback, fieldName, { minimum = 0, maximum = Number.POSITIVE_INFINITY, integer = false, } = {}) {
68
+ if (raw === undefined) {
69
+ return fallback;
70
+ }
71
+ const parsed = typeof raw === 'number' ? raw : Number.parseFloat(String(raw));
72
+ if (!Number.isFinite(parsed)) {
73
+ throw new Error(`${fieldName}_invalid`);
74
+ }
75
+ if (parsed < minimum || parsed > maximum) {
76
+ throw new Error(`${fieldName}_out_of_range`);
77
+ }
78
+ if (integer && !Number.isInteger(parsed)) {
79
+ throw new Error(`${fieldName}_invalid`);
80
+ }
81
+ return parsed;
82
+ }
83
+ function parseCaptureListingOptions(targetArg, options = {}) {
84
+ const durationSeconds = parsePositiveNumber(options.duration, DEFAULT_DURATION_SECONDS, 'duration', {
85
+ minimum: MIN_CAPTURE_SECONDS,
86
+ maximum: MAX_CAPTURE_SECONDS,
87
+ });
88
+ const width = parsePositiveNumber(options.width, DEFAULT_WIDTH, 'width', {
89
+ minimum: MIN_DIMENSION,
90
+ maximum: MAX_DIMENSION,
91
+ integer: true,
92
+ });
93
+ const height = parsePositiveNumber(options.height, DEFAULT_HEIGHT, 'height', {
94
+ minimum: MIN_DIMENSION,
95
+ maximum: MAX_DIMENSION,
96
+ integer: true,
97
+ });
98
+ const fps = parsePositiveNumber(options.fps, DEFAULT_FPS, 'fps', {
99
+ minimum: 1,
100
+ maximum: MAX_FPS,
101
+ integer: true,
102
+ });
103
+ const posterAtSeconds = parsePositiveNumber(options.posterAt, DEFAULT_POSTER_AT_SECONDS, 'poster_at', {
104
+ minimum: 0,
105
+ maximum: durationSeconds,
106
+ });
107
+ const outputDir = options.outputDir?.trim() ? (0, node_path_1.resolve)(process.cwd(), options.outputDir.trim()) : null;
108
+ return {
109
+ targetArg,
110
+ appName: options.app?.trim() || undefined,
111
+ durationSeconds,
112
+ width,
113
+ height,
114
+ fps,
115
+ posterAtSeconds,
116
+ audio: Boolean(options.audio),
117
+ outputDir,
118
+ keepRaw: Boolean(options.keepRaw),
119
+ };
120
+ }
121
+ function readMacOsVersion() {
122
+ const result = (0, node_child_process_1.spawnSync)('sw_vers', ['-productVersion'], {
123
+ encoding: 'utf8',
124
+ });
125
+ if (result.status !== 0) {
126
+ throw new Error('macos_version_probe_failed');
127
+ }
128
+ return result.stdout.trim();
129
+ }
130
+ function assertSupportedListingEnvironment(platform = process.platform, macosVersion = platform === 'darwin' ? readMacOsVersion() : '') {
131
+ if (platform !== 'darwin') {
132
+ throw new Error('unsupported_platform');
133
+ }
134
+ if (!macosVersion.startsWith(SUPPORTED_MACOS_PREFIX)) {
135
+ throw new Error(`unsupported_macos_version:${macosVersion}`);
136
+ }
137
+ }
138
+ function commandExists(command) {
139
+ const result = (0, node_child_process_1.spawnSync)('which', [command], {
140
+ encoding: 'utf8',
141
+ });
142
+ return result.status === 0 && result.stdout.trim().length > 0;
143
+ }
144
+ async function ensureExecutableFile(filePath) {
145
+ await (0, promises_1.access)(filePath);
146
+ }
147
+ function resolveListingRecorderPath(baseDir = __dirname) {
148
+ return (0, node_path_1.resolve)(baseDir, '..', '..', '..', '..', 'clients', 'apple', 'playdrop-listing-recorder', '.build', 'release', 'playdrop-listing-recorder');
149
+ }
150
+ function buildTimestampFolder(date = new Date()) {
151
+ const iso = date.toISOString();
152
+ return iso.replace(/[:]/g, '-').replace(/\.\d{3}Z$/, 'Z');
153
+ }
154
+ async function ensureOutputPaths(appName, explicitOutputDir) {
155
+ const outputDir = explicitOutputDir
156
+ ? explicitOutputDir
157
+ : (0, node_path_1.resolve)(process.cwd(), 'output', 'listing-capture', appName, buildTimestampFolder());
158
+ await (0, promises_1.mkdir)(outputDir, { recursive: true });
159
+ return {
160
+ outputDir,
161
+ rawVideoPath: (0, node_path_1.join)(outputDir, 'listing-raw.mp4'),
162
+ metadataPath: (0, node_path_1.join)(outputDir, 'listing-raw.json'),
163
+ reportPath: (0, node_path_1.join)(outputDir, 'capture-report.json'),
164
+ posterPath: (0, node_path_1.join)(outputDir, 'poster.png'),
165
+ finalVideoPath: (0, node_path_1.join)(outputDir, 'listing.mp4'),
166
+ };
167
+ }
168
+ async function launchListingBrowser(initialViewport) {
169
+ const playwright = await Promise.resolve().then(() => __importStar(require('playwright-core')));
170
+ const args = [
171
+ '--app=about:blank',
172
+ `--window-size=${initialViewport.width},${initialViewport.height}`,
173
+ '--autoplay-policy=no-user-gesture-required',
174
+ '--disable-extensions',
175
+ '--disable-background-networking',
176
+ '--disable-component-update',
177
+ '--disable-sync',
178
+ '--metrics-recording-only',
179
+ '--no-first-run',
180
+ '--no-default-browser-check',
181
+ ];
182
+ try {
183
+ const server = await playwright.chromium.launchServer({
184
+ headless: false,
185
+ args,
186
+ });
187
+ const processId = server.process()?.pid ?? 0;
188
+ if (processId <= 0) {
189
+ await server.close();
190
+ throw new Error('browser_process_missing');
191
+ }
192
+ const browser = await playwright.chromium.connect(server.wsEndpoint());
193
+ const context = await browser.newContext({ viewport: null });
194
+ const page = await context.newPage();
195
+ return {
196
+ browser,
197
+ context,
198
+ page,
199
+ server,
200
+ processId,
201
+ close: async () => {
202
+ try {
203
+ await context.close();
204
+ }
205
+ catch {
206
+ // ignore close errors
207
+ }
208
+ try {
209
+ await browser.close();
210
+ }
211
+ catch {
212
+ // ignore close errors
213
+ }
214
+ try {
215
+ await server.close();
216
+ }
217
+ catch {
218
+ // ignore close errors
219
+ }
220
+ },
221
+ };
222
+ }
223
+ catch (error) {
224
+ throw (0, playwright_1.createPlaywrightLaunchError)('playdrop', error);
225
+ }
226
+ }
227
+ async function bootstrapAuthState(context, page, targetUrl, token, user) {
228
+ const cookies = (0, sessionCookie_1.buildCaptureAccessTokenCookies)(targetUrl, token);
229
+ await context.addCookies(cookies);
230
+ await page.addInitScript(({ bootstrapToken, bootstrapUser }) => {
231
+ try {
232
+ window.localStorage.setItem('playdrop.accessToken', bootstrapToken);
233
+ }
234
+ catch {
235
+ // ignore storage failures
236
+ }
237
+ try {
238
+ window.localStorage.setItem('playdrop.user', JSON.stringify(bootstrapUser));
239
+ }
240
+ catch {
241
+ // ignore storage failures
242
+ }
243
+ try {
244
+ window.sessionStorage.removeItem('playdrop.logoutReason');
245
+ }
246
+ catch {
247
+ // ignore storage failures
248
+ }
249
+ }, { bootstrapToken: token, bootstrapUser: user });
250
+ }
251
+ async function waitForHostedGameMeasurement(page, timeoutMs) {
252
+ const startedAt = Date.now();
253
+ let lastSignature = '';
254
+ let stableHits = 0;
255
+ while (Date.now() - startedAt < timeoutMs) {
256
+ try {
257
+ const measurement = await page.evaluate((selector) => {
258
+ const frame = document.querySelector(selector);
259
+ if (!(frame instanceof HTMLIFrameElement)) {
260
+ throw new Error('hosted_game_frame_missing');
261
+ }
262
+ const rect = frame.getBoundingClientRect();
263
+ if (rect.width <= 0 || rect.height <= 0) {
264
+ throw new Error('hosted_game_frame_hidden');
265
+ }
266
+ return {
267
+ outerWidth: window.outerWidth,
268
+ outerHeight: window.outerHeight,
269
+ innerWidth: window.innerWidth,
270
+ innerHeight: window.innerHeight,
271
+ devicePixelRatio: window.devicePixelRatio,
272
+ iframeRect: {
273
+ x: rect.x,
274
+ y: rect.y,
275
+ width: rect.width,
276
+ height: rect.height,
277
+ top: rect.top,
278
+ left: rect.left,
279
+ right: rect.right,
280
+ bottom: rect.bottom,
281
+ },
282
+ };
283
+ }, CAPTURE_FRAME_SELECTOR);
284
+ const signature = JSON.stringify([
285
+ Math.round(measurement.outerWidth),
286
+ Math.round(measurement.outerHeight),
287
+ Math.round(measurement.innerWidth),
288
+ Math.round(measurement.innerHeight),
289
+ Math.round(measurement.iframeRect.left),
290
+ Math.round(measurement.iframeRect.top),
291
+ Math.round(measurement.iframeRect.width),
292
+ Math.round(measurement.iframeRect.height),
293
+ ]);
294
+ if (signature === lastSignature) {
295
+ stableHits += 1;
296
+ }
297
+ else {
298
+ lastSignature = signature;
299
+ stableHits = 1;
300
+ }
301
+ if (stableHits >= 3) {
302
+ return measurement;
303
+ }
304
+ }
305
+ catch (error) {
306
+ if (error instanceof Error && error.message !== 'hosted_game_frame_missing' && error.message !== 'hosted_game_frame_hidden') {
307
+ throw error;
308
+ }
309
+ }
310
+ await page.waitForTimeout(250);
311
+ }
312
+ throw new Error('hosted_game_measurement_timeout');
313
+ }
314
+ async function fitWindowToRequestedGameplay(page, requestedWidth, requestedHeight) {
315
+ const cdpSession = await page.context().newCDPSession(page);
316
+ let measurement = await waitForHostedGameMeasurement(page, 15000);
317
+ for (let attempt = 0; attempt < 5; attempt += 1) {
318
+ const deltaWidth = requestedWidth - Math.round(measurement.iframeRect.width);
319
+ const deltaHeight = requestedHeight - Math.round(measurement.iframeRect.height);
320
+ if (Math.abs(deltaWidth) <= 1 && Math.abs(deltaHeight) <= 1) {
321
+ return measurement;
322
+ }
323
+ const windowState = await cdpSession.send('Browser.getWindowForTarget');
324
+ const currentWidth = typeof windowState.bounds.width === 'number'
325
+ ? windowState.bounds.width
326
+ : Math.round(measurement.outerWidth);
327
+ const currentHeight = typeof windowState.bounds.height === 'number'
328
+ ? windowState.bounds.height
329
+ : Math.round(measurement.outerHeight);
330
+ const nextWidth = Math.max(400, currentWidth + deltaWidth);
331
+ const nextHeight = Math.max(300, currentHeight + deltaHeight);
332
+ await cdpSession.send('Browser.setWindowBounds', {
333
+ windowId: windowState.windowId,
334
+ bounds: {
335
+ width: nextWidth,
336
+ height: nextHeight,
337
+ },
338
+ });
339
+ await page.waitForTimeout(800);
340
+ measurement = await waitForHostedGameMeasurement(page, 8000);
341
+ }
342
+ throw new Error('hosted_game_resize_failed');
343
+ }
344
+ function roundCropValue(value) {
345
+ return Math.round(value);
346
+ }
347
+ function computeRecordedCrop(measurement, rawWidth, rawHeight) {
348
+ if (measurement.outerWidth <= 0 || measurement.outerHeight <= 0) {
349
+ throw new Error('invalid_window_geometry');
350
+ }
351
+ if (measurement.innerWidth <= 0 || measurement.innerHeight <= 0) {
352
+ throw new Error('invalid_viewport_geometry');
353
+ }
354
+ if (rawWidth <= 0 || rawHeight <= 0) {
355
+ throw new Error('invalid_raw_dimensions');
356
+ }
357
+ const horizontalChrome = measurement.outerWidth - measurement.innerWidth;
358
+ const verticalChrome = measurement.outerHeight - measurement.innerHeight;
359
+ if (horizontalChrome < 0 || verticalChrome < 0) {
360
+ throw new Error('invalid_chrome_geometry');
361
+ }
362
+ const leftChrome = horizontalChrome / 2;
363
+ const bottomChrome = leftChrome;
364
+ const topChrome = verticalChrome - bottomChrome;
365
+ if (topChrome < 0) {
366
+ throw new Error('invalid_vertical_chrome_geometry');
367
+ }
368
+ const cropLeftCss = leftChrome + measurement.iframeRect.left;
369
+ const cropTopCss = topChrome + measurement.iframeRect.top;
370
+ const scaleX = rawWidth / measurement.outerWidth;
371
+ const scaleY = rawHeight / measurement.outerHeight;
372
+ const x = roundCropValue(cropLeftCss * scaleX);
373
+ const y = roundCropValue(cropTopCss * scaleY);
374
+ const width = roundCropValue(measurement.iframeRect.width * scaleX);
375
+ const height = roundCropValue(measurement.iframeRect.height * scaleY);
376
+ if (x < 0 || y < 0 || width <= 0 || height <= 0) {
377
+ throw new Error('invalid_crop_geometry');
378
+ }
379
+ if (x + width > rawWidth || y + height > rawHeight) {
380
+ throw new Error('crop_out_of_bounds');
381
+ }
382
+ return { x, y, width, height };
383
+ }
384
+ function runTool(command, args, failureCode) {
385
+ const result = (0, node_child_process_1.spawnSync)(command, args, {
386
+ encoding: 'utf8',
387
+ maxBuffer: 20 * 1024 * 1024,
388
+ });
389
+ if (result.status !== 0) {
390
+ const stderr = result.stderr?.trim() || '';
391
+ const stdout = result.stdout?.trim() || '';
392
+ const combined = [stdout, stderr].filter(Boolean).join('\n');
393
+ throw new Error(`${failureCode}:${combined}`);
394
+ }
395
+ return {
396
+ stdout: result.stdout || '',
397
+ stderr: result.stderr || '',
398
+ };
399
+ }
400
+ async function probeMediaFile(filePath) {
401
+ const { stdout } = runTool('ffprobe', [
402
+ '-v',
403
+ 'error',
404
+ '-show_streams',
405
+ '-show_format',
406
+ '-of',
407
+ 'json',
408
+ filePath,
409
+ ], 'ffprobe_failed');
410
+ return JSON.parse(stdout);
411
+ }
412
+ function parseFrameRate(raw) {
413
+ if (!raw) {
414
+ return null;
415
+ }
416
+ const parts = raw.split('/');
417
+ if (parts.length !== 2) {
418
+ const parsed = Number.parseFloat(raw);
419
+ return Number.isFinite(parsed) ? parsed : null;
420
+ }
421
+ const numerator = Number.parseFloat(parts[0]);
422
+ const denominator = Number.parseFloat(parts[1]);
423
+ if (!Number.isFinite(numerator) || !Number.isFinite(denominator) || denominator === 0) {
424
+ return null;
425
+ }
426
+ return numerator / denominator;
427
+ }
428
+ function extractVideoStream(probe) {
429
+ const videoStream = probe.streams?.find(stream => stream.codec_type === 'video');
430
+ if (!videoStream || typeof videoStream.width !== 'number' || typeof videoStream.height !== 'number') {
431
+ throw new Error('video_stream_missing');
432
+ }
433
+ return videoStream;
434
+ }
435
+ function countAudioStreams(probe) {
436
+ return probe.streams?.filter(stream => stream.codec_type === 'audio').length ?? 0;
437
+ }
438
+ function readDurationSeconds(probe) {
439
+ const raw = probe.format?.duration;
440
+ const parsed = raw ? Number.parseFloat(raw) : Number.NaN;
441
+ if (!Number.isFinite(parsed)) {
442
+ throw new Error('duration_missing');
443
+ }
444
+ return parsed;
445
+ }
446
+ async function runListingRecorder(recorderPath, pid, durationSeconds, rawOutputPath, metadataPath, audio) {
447
+ const args = [
448
+ '--pid',
449
+ String(pid),
450
+ '--duration',
451
+ String(durationSeconds),
452
+ '--output',
453
+ rawOutputPath,
454
+ '--metadata',
455
+ metadataPath,
456
+ ];
457
+ if (audio) {
458
+ args.push('--audio');
459
+ }
460
+ const result = (0, node_child_process_1.spawnSync)(recorderPath, args, {
461
+ encoding: 'utf8',
462
+ maxBuffer: 10 * 1024 * 1024,
463
+ });
464
+ if (result.status !== 0) {
465
+ const message = result.stderr?.trim() || result.stdout?.trim() || 'Native recorder failed.';
466
+ throw new Error(`listing_recorder_failed:${message}`);
467
+ }
468
+ const metadata = JSON.parse(await (0, promises_1.readFile)(metadataPath, 'utf8'));
469
+ return metadata;
470
+ }
471
+ function formatCommandError(error) {
472
+ if (error.message === 'unsupported_platform') {
473
+ return {
474
+ message: 'Listing capture only runs on macOS.',
475
+ suggestions: [],
476
+ };
477
+ }
478
+ if (error.message.startsWith('unsupported_macos_version:')) {
479
+ const version = error.message.split(':')[1] || 'unknown';
480
+ return {
481
+ message: `Listing capture only supports macOS ${SUPPORTED_MACOS_PREFIX} on this machine. Current version: ${version}.`,
482
+ suggestions: [],
483
+ };
484
+ }
485
+ if (error.message === 'macos_version_probe_failed') {
486
+ return {
487
+ message: 'Could not determine the local macOS version.',
488
+ suggestions: [],
489
+ };
490
+ }
491
+ if (error.message.startsWith('listing_recorder_failed:')) {
492
+ return {
493
+ message: error.message.slice('listing_recorder_failed:'.length),
494
+ suggestions: [],
495
+ };
496
+ }
497
+ if (error.message.startsWith('ffprobe_failed:') || error.message.startsWith('ffmpeg_failed:')) {
498
+ return {
499
+ message: error.message.slice(error.message.indexOf(':') + 1).trim() || 'ffmpeg validation failed.',
500
+ suggestions: [],
501
+ };
502
+ }
503
+ return {
504
+ message: error.message,
505
+ suggestions: [],
506
+ };
507
+ }
508
+ async function encodeListingVideo(rawVideoPath, finalVideoPath, posterPath, crop, width, height, fps, posterAtSeconds) {
509
+ const filter = `crop=${crop.width}:${crop.height}:${crop.x}:${crop.y},scale=${width}:${height}:flags=lanczos,fps=${fps}`;
510
+ runTool('ffmpeg', [
511
+ '-y',
512
+ '-i',
513
+ rawVideoPath,
514
+ '-vf',
515
+ filter,
516
+ '-c:v',
517
+ 'h264_videotoolbox',
518
+ '-pix_fmt',
519
+ 'yuv420p',
520
+ '-c:a',
521
+ 'aac',
522
+ finalVideoPath,
523
+ ], 'ffmpeg_failed');
524
+ runTool('ffmpeg', [
525
+ '-y',
526
+ '-ss',
527
+ String(posterAtSeconds),
528
+ '-i',
529
+ finalVideoPath,
530
+ '-frames:v',
531
+ '1',
532
+ posterPath,
533
+ ], 'ffmpeg_failed');
534
+ }
535
+ function ensureTolerance(actual, expected, tolerance, failureCode) {
536
+ if (Math.abs(actual - expected) > tolerance) {
537
+ throw new Error(`${failureCode}:${actual}:${expected}`);
538
+ }
539
+ }
540
+ function createWarnings(finalProbe, requestedAudio) {
541
+ const warnings = [];
542
+ const audioTrackCount = countAudioStreams(finalProbe);
543
+ if (!requestedAudio && audioTrackCount > 0) {
544
+ warnings.push('final output contains audio even though --audio was not requested');
545
+ }
546
+ return warnings;
547
+ }
548
+ async function captureListing(targetArg, options = {}) {
549
+ let parsedOptions;
550
+ try {
551
+ parsedOptions = parseCaptureListingOptions(targetArg, options);
552
+ assertSupportedListingEnvironment();
553
+ }
554
+ catch (error) {
555
+ const detail = formatCommandError(error instanceof Error ? error : new Error(String(error)));
556
+ (0, messages_1.printErrorWithHelp)(detail.message, detail.suggestions, { command: 'project capture listing' });
557
+ process.exitCode = 1;
558
+ return;
559
+ }
560
+ if (!commandExists('ffmpeg')) {
561
+ (0, messages_1.printErrorWithHelp)('ffmpeg is required for listing capture.', ['Install ffmpeg and make sure it is available on PATH.'], {
562
+ command: 'project capture listing',
563
+ });
564
+ process.exitCode = 1;
565
+ return;
566
+ }
567
+ if (!commandExists('ffprobe')) {
568
+ (0, messages_1.printErrorWithHelp)('ffprobe is required for listing capture.', ['Install ffprobe and make sure it is available on PATH.'], {
569
+ command: 'project capture listing',
570
+ });
571
+ process.exitCode = 1;
572
+ return;
573
+ }
574
+ const recorderPath = resolveListingRecorderPath();
575
+ try {
576
+ await ensureExecutableFile(recorderPath);
577
+ }
578
+ catch {
579
+ (0, messages_1.printErrorWithHelp)(`Native recorder binary was not found at ${recorderPath}.`, ['Build it with: (cd clients/apple/playdrop-listing-recorder && swift build -c release)'], { command: 'project capture listing' });
580
+ process.exitCode = 1;
581
+ return;
582
+ }
583
+ let resolvedTarget;
584
+ try {
585
+ resolvedTarget = (0, devShared_1.resolveDevTarget)(parsedOptions.targetArg, parsedOptions.appName);
586
+ }
587
+ catch (error) {
588
+ const code = error?.code;
589
+ const message = error?.message || 'Unable to resolve capture target.';
590
+ const suggestions = code === 'app_required'
591
+ ? ['Use "--app <name>" to choose which app to capture.']
592
+ : code === 'catalogue_missing'
593
+ ? ['Run the command inside a Playdrop workspace or pass the direct HTML file path.']
594
+ : ['Provide a valid app target or HTML file path.'];
595
+ (0, messages_1.printErrorWithHelp)(message, suggestions, { command: 'project capture listing' });
596
+ process.exitCode = 1;
597
+ return;
598
+ }
599
+ let appName = resolvedTarget.appName;
600
+ let appTypeSlug = (0, appUrls_1.getAppTypeSlug)(null);
601
+ try {
602
+ const match = (0, catalogue_utils_1.findAppDefinition)(resolvedTarget.htmlPath);
603
+ appName = match.name;
604
+ appTypeSlug = (0, appUrls_1.getAppTypeSlug)(match.type);
605
+ }
606
+ catch {
607
+ // keep defaults for direct-file targets outside a registered catalogue
608
+ }
609
+ const outputPaths = await ensureOutputPaths(appName, parsedOptions.outputDir);
610
+ const projectInfo = (0, devShared_1.findProjectInfo)(resolvedTarget.htmlPath);
611
+ const devScriptAvailable = Boolean(projectInfo.projectDir && projectInfo.packageJson && typeof projectInfo.packageJson.scripts?.dev === 'string');
612
+ // eslint-disable-next-line complexity
613
+ await (0, commandContext_1.withEnvironment)('project capture listing', 'Capturing listing media', async ({ client, env, envConfig, token }) => {
614
+ let currentUser = null;
615
+ let currentUsername = '';
616
+ try {
617
+ currentUser = await (0, devShared_1.fetchDevUser)(client);
618
+ currentUsername = currentUser.username.trim();
619
+ }
620
+ catch (error) {
621
+ if (error instanceof http_1.CLIUnsupportedClientError) {
622
+ return;
623
+ }
624
+ if (error instanceof types_1.UnsupportedClientError) {
625
+ (0, http_1.handleUnsupportedError)(error, 'Authentication');
626
+ process.exitCode = 1;
627
+ return;
628
+ }
629
+ if (error instanceof types_1.ApiError) {
630
+ (0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, [
631
+ 'Run "playdrop auth login" to refresh your session.',
632
+ 'Use "playdrop auth whoami" afterwards to verify the current account.',
633
+ ], { command: 'project capture listing' });
634
+ process.exitCode = 1;
635
+ return;
636
+ }
637
+ if ((0, devShared_1.isNetworkError)(error)) {
638
+ (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to resolve your account.', 'project capture listing');
639
+ process.exitCode = 1;
640
+ return;
641
+ }
642
+ throw error;
643
+ }
644
+ try {
645
+ await (0, devShared_1.assertAppRegistered)(client, currentUsername, appName);
646
+ }
647
+ catch (error) {
648
+ if (error instanceof http_1.CLIUnsupportedClientError) {
649
+ return;
650
+ }
651
+ if (error instanceof types_1.UnsupportedClientError) {
652
+ (0, http_1.handleUnsupportedError)(error, 'Listing capture');
653
+ process.exitCode = 1;
654
+ return;
655
+ }
656
+ if (error instanceof types_1.ApiError && error.status === 404) {
657
+ (0, messages_1.printErrorWithHelp)(`App ${currentUsername}/${appName} is not registered on ${env}.`, [`Run "playdrop project create app ${appName}" before using listing capture.`], { command: 'project capture listing' });
658
+ process.exitCode = 1;
659
+ return;
660
+ }
661
+ if ((0, devShared_1.isNetworkError)(error)) {
662
+ (0, messages_1.printNetworkIssue)('Could not verify the app registration.', 'project capture listing');
663
+ process.exitCode = 1;
664
+ return;
665
+ }
666
+ throw error;
667
+ }
668
+ const entryLabel = (0, node_path_1.relative)(process.cwd(), resolvedTarget.htmlPath) || resolvedTarget.htmlPath;
669
+ console.log(`[listing] Preparing ${entryLabel} for ${env} (${appTypeSlug}).`);
670
+ const serverAlreadyRunning = await (0, devServer_1.isDevServerAvailable)(appName, DEV_SERVER_PORT, 750);
671
+ const devServerStartedByCapture = !serverAlreadyRunning;
672
+ let serverHandle = null;
673
+ let browserHandle = null;
674
+ let cleanupRequested = false;
675
+ const cleanup = async () => {
676
+ if (cleanupRequested) {
677
+ return;
678
+ }
679
+ cleanupRequested = true;
680
+ if (browserHandle) {
681
+ await browserHandle.close();
682
+ }
683
+ if (serverHandle) {
684
+ await serverHandle.close();
685
+ }
686
+ };
687
+ try {
688
+ if (serverAlreadyRunning) {
689
+ console.log(`[listing] Reusing dev server at http://localhost:${DEV_SERVER_PORT}/apps/${appName}.html`);
690
+ }
691
+ else {
692
+ serverHandle = await (0, devServer_1.startDevServer)({
693
+ appName,
694
+ htmlPath: resolvedTarget.htmlPath,
695
+ port: DEV_SERVER_PORT,
696
+ projectInfo,
697
+ });
698
+ }
699
+ if (!serverAlreadyRunning && projectInfo.projectDir && !devScriptAvailable && projectInfo.packageJsonPath) {
700
+ const projectLabel = (0, devShared_1.formatProjectLabel)(projectInfo);
701
+ if (projectLabel) {
702
+ console.log(`[listing] package.json detected at ${projectLabel}, but no "dev" script was found. Run your app build manually if needed.`);
703
+ }
704
+ }
705
+ if (devServerStartedByCapture) {
706
+ await new Promise(resolve => setTimeout(resolve, 1000));
707
+ }
708
+ const webBase = envConfig.webBase ?? 'https://www.playdrop.ai';
709
+ const frameUrl = `${webBase}/creators/${encodeURIComponent(currentUsername)}/apps/${appTypeSlug}/${encodeURIComponent(appName)}/dev`;
710
+ browserHandle = await launchListingBrowser({
711
+ width: parsedOptions.width,
712
+ height: parsedOptions.height,
713
+ });
714
+ await bootstrapAuthState(browserHandle.context, browserHandle.page, frameUrl, token, currentUser);
715
+ await browserHandle.page.goto(frameUrl, { waitUntil: 'domcontentloaded' });
716
+ await browserHandle.page.waitForTimeout(1000);
717
+ const measurement = await fitWindowToRequestedGameplay(browserHandle.page, parsedOptions.width, parsedOptions.height);
718
+ console.log(`[listing] Gameplay frame ${Math.round(measurement.iframeRect.width)}x${Math.round(measurement.iframeRect.height)} in window ${Math.round(measurement.outerWidth)}x${Math.round(measurement.outerHeight)}.`);
719
+ await browserHandle.page.waitForTimeout(750);
720
+ const recorderMetadata = await runListingRecorder(recorderPath, browserHandle.processId, parsedOptions.durationSeconds, outputPaths.rawVideoPath, outputPaths.metadataPath, parsedOptions.audio);
721
+ const rawProbe = await probeMediaFile(outputPaths.rawVideoPath);
722
+ const rawVideoStream = extractVideoStream(rawProbe);
723
+ const rawDurationSeconds = readDurationSeconds(rawProbe);
724
+ const rawAudioTrackCount = countAudioStreams(rawProbe);
725
+ const crop = computeRecordedCrop(measurement, rawVideoStream.width, rawVideoStream.height);
726
+ console.log(`[listing] Cropping raw capture to gameplay at ${crop.width}x${crop.height}+${crop.x}+${crop.y}.`);
727
+ await encodeListingVideo(outputPaths.rawVideoPath, outputPaths.finalVideoPath, outputPaths.posterPath, crop, parsedOptions.width, parsedOptions.height, parsedOptions.fps, parsedOptions.posterAtSeconds);
728
+ const finalProbe = await probeMediaFile(outputPaths.finalVideoPath);
729
+ const finalVideoStream = extractVideoStream(finalProbe);
730
+ const finalDurationSeconds = readDurationSeconds(finalProbe);
731
+ const finalAudioTrackCount = countAudioStreams(finalProbe);
732
+ const finalFps = parseFrameRate(finalVideoStream.avg_frame_rate);
733
+ ensureTolerance(finalVideoStream.width, parsedOptions.width, 0, 'final_width_mismatch');
734
+ ensureTolerance(finalVideoStream.height, parsedOptions.height, 0, 'final_height_mismatch');
735
+ ensureTolerance(finalDurationSeconds, parsedOptions.durationSeconds, 1.5, 'final_duration_mismatch');
736
+ if (parsedOptions.audio && finalAudioTrackCount === 0) {
737
+ throw new Error('audio_track_missing');
738
+ }
739
+ const warnings = createWarnings(finalProbe, parsedOptions.audio);
740
+ const report = {
741
+ appName,
742
+ targetUrl: frameUrl,
743
+ outputDir: outputPaths.outputDir,
744
+ command: {
745
+ durationSeconds: parsedOptions.durationSeconds,
746
+ width: parsedOptions.width,
747
+ height: parsedOptions.height,
748
+ fps: parsedOptions.fps,
749
+ posterAtSeconds: parsedOptions.posterAtSeconds,
750
+ audio: parsedOptions.audio,
751
+ keepRaw: parsedOptions.keepRaw,
752
+ },
753
+ measurement,
754
+ recorder: recorderMetadata,
755
+ crop,
756
+ rawVideo: {
757
+ path: parsedOptions.keepRaw ? outputPaths.rawVideoPath : null,
758
+ width: rawVideoStream.width,
759
+ height: rawVideoStream.height,
760
+ durationSeconds: rawDurationSeconds,
761
+ audioTrackCount: rawAudioTrackCount,
762
+ },
763
+ finalVideo: {
764
+ path: outputPaths.finalVideoPath,
765
+ width: finalVideoStream.width,
766
+ height: finalVideoStream.height,
767
+ durationSeconds: finalDurationSeconds,
768
+ audioTrackCount: finalAudioTrackCount,
769
+ fps: finalFps,
770
+ },
771
+ posterPath: outputPaths.posterPath,
772
+ warnings,
773
+ };
774
+ await (0, promises_1.writeFile)(outputPaths.reportPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
775
+ if (!parsedOptions.keepRaw) {
776
+ await (0, promises_1.rm)(outputPaths.rawVideoPath, { force: true });
777
+ }
778
+ console.log(`[listing] Saved video to ${(0, node_path_1.relative)(process.cwd(), outputPaths.finalVideoPath) || outputPaths.finalVideoPath}`);
779
+ console.log(`[listing] Saved poster to ${(0, node_path_1.relative)(process.cwd(), outputPaths.posterPath) || outputPaths.posterPath}`);
780
+ console.log(`[listing] Saved report to ${(0, node_path_1.relative)(process.cwd(), outputPaths.reportPath) || outputPaths.reportPath}`);
781
+ if (warnings.length > 0) {
782
+ for (const warning of warnings) {
783
+ console.warn(`[listing][warn] ${warning}`);
784
+ }
785
+ }
786
+ }
787
+ catch (error) {
788
+ if (error instanceof http_1.CLIUnsupportedClientError) {
789
+ return;
790
+ }
791
+ if (error instanceof types_1.UnsupportedClientError) {
792
+ (0, http_1.handleUnsupportedError)(error, 'Listing capture');
793
+ process.exitCode = 1;
794
+ return;
795
+ }
796
+ const detail = formatCommandError(error instanceof Error ? error : new Error(String(error)));
797
+ (0, messages_1.printErrorWithHelp)(detail.message, detail.suggestions, { command: 'project capture listing' });
798
+ process.exitCode = 1;
799
+ }
800
+ finally {
801
+ await cleanup();
802
+ }
803
+ });
804
+ }