@playdrop/playdrop-cli 0.11.8 → 0.12.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 (116) hide show
  1. package/config/client-meta.json +2 -2
  2. package/dist/apps/build.d.ts +1 -1
  3. package/dist/apps/build.js +4 -6
  4. package/dist/apps/index.d.ts +4 -5
  5. package/dist/apps/index.js +14 -16
  6. package/dist/apps/loadCheck.d.ts +76 -0
  7. package/dist/apps/loadCheck.js +874 -0
  8. package/dist/apps/upload.d.ts +3 -2
  9. package/dist/apps/upload.js +112 -9
  10. package/dist/assetSpecs.js +2 -5
  11. package/dist/catalogue-utils.d.ts +1 -0
  12. package/dist/catalogue-utils.js +1 -0
  13. package/dist/catalogue.d.ts +3 -1
  14. package/dist/catalogue.js +19 -3
  15. package/dist/commands/browse.js +2 -2
  16. package/dist/commands/build.js +4 -1
  17. package/dist/commands/captureListing.d.ts +30 -1
  18. package/dist/commands/captureListing.js +308 -125
  19. package/dist/commands/check.d.ts +8 -0
  20. package/dist/commands/check.js +188 -0
  21. package/dist/commands/create.js +17 -15
  22. package/dist/commands/createRemixContent.js +2 -2
  23. package/dist/commands/detail.js +2 -2
  24. package/dist/commands/dev.js +9 -12
  25. package/dist/commands/devServer.d.ts +2 -2
  26. package/dist/commands/devServer.js +3 -4
  27. package/dist/commands/marketing.js +10 -11
  28. package/dist/commands/play.js +2 -2
  29. package/dist/commands/review.d.ts +20 -0
  30. package/dist/commands/review.js +112 -0
  31. package/dist/commands/upload-content.d.ts +1 -1
  32. package/dist/commands/upload-content.js +1 -1
  33. package/dist/commands/upload.d.ts +2 -1
  34. package/dist/commands/upload.js +9 -52
  35. package/dist/commands/validate.js +4 -161
  36. package/dist/commands/worker/runtime.d.ts +1 -0
  37. package/dist/commands/worker/runtime.js +48 -5
  38. package/dist/commands/worker.d.ts +5 -6
  39. package/dist/commands/worker.js +42 -92
  40. package/dist/config.js +1 -2
  41. package/dist/index.js +33 -49
  42. package/dist/playwright.d.ts +5 -0
  43. package/dist/playwright.js +60 -1
  44. package/node_modules/@playdrop/api-client/dist/client.d.ts +5 -3
  45. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  46. package/node_modules/@playdrop/api-client/dist/client.js +10 -27
  47. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +1 -1
  48. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  49. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +2 -1
  50. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  51. package/node_modules/@playdrop/api-client/dist/domains/apps.js +11 -0
  52. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +1 -1
  53. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  54. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +3 -1
  55. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -1
  56. package/node_modules/@playdrop/api-client/dist/domains/auth.js +22 -0
  57. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +1 -1
  58. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  59. package/node_modules/@playdrop/api-client/dist/index.d.ts +7 -6
  60. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  61. package/node_modules/@playdrop/api-client/dist/index.js +11 -6
  62. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js +0 -7
  63. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js.map +1 -1
  64. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js +2 -46
  65. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js.map +1 -1
  66. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js +2 -22
  67. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js.map +1 -1
  68. package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.js.map +1 -1
  69. package/node_modules/@playdrop/boxel-core/dist/src/serialization.js +1 -1
  70. package/node_modules/@playdrop/boxel-core/dist/src/serialization.js.map +1 -1
  71. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/infer-face.js.map +1 -1
  72. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/layer-mode.js +2 -2
  73. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/layer-mode.js.map +1 -1
  74. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.js +0 -11
  75. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.js.map +1 -1
  76. package/node_modules/@playdrop/boxel-core/dist/src/transforms/upscale.js.map +1 -1
  77. package/node_modules/@playdrop/boxel-core/dist/src/validation.d.ts +1 -1
  78. package/node_modules/@playdrop/boxel-core/dist/src/validation.js +0 -20
  79. package/node_modules/@playdrop/boxel-core/dist/src/validation.js.map +1 -1
  80. package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.d.ts +2 -2
  81. package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.js +2 -12
  82. package/node_modules/@playdrop/boxel-three/dist/src/voxels/faces.js +1 -125
  83. package/node_modules/@playdrop/config/client-meta.json +2 -2
  84. package/node_modules/@playdrop/config/dist/src/client-meta.d.ts +8 -0
  85. package/node_modules/@playdrop/config/dist/src/client-meta.d.ts.map +1 -0
  86. package/node_modules/@playdrop/config/dist/src/client-meta.js +51 -0
  87. package/node_modules/@playdrop/config/dist/src/deployment.d.ts.map +1 -1
  88. package/node_modules/@playdrop/config/dist/src/deployment.js +2 -32
  89. package/node_modules/@playdrop/config/dist/src/index.d.ts +2 -2
  90. package/node_modules/@playdrop/config/dist/src/index.d.ts.map +1 -1
  91. package/node_modules/@playdrop/config/dist/src/index.js +3 -29
  92. package/node_modules/@playdrop/config/dist/src/runtime-service-origins.d.ts +0 -1
  93. package/node_modules/@playdrop/config/dist/src/runtime-service-origins.d.ts.map +1 -1
  94. package/node_modules/@playdrop/config/dist/src/runtime-service-origins.js +21 -5
  95. package/node_modules/@playdrop/config/dist/src/server/fastify.d.ts +2 -2
  96. package/node_modules/@playdrop/config/dist/src/server/fastify.d.ts.map +1 -1
  97. package/node_modules/@playdrop/config/dist/src/server/fastify.js +0 -1
  98. package/node_modules/@playdrop/config/dist/test/runtime-service-origins.test.js +8 -0
  99. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  100. package/node_modules/@playdrop/types/dist/api.d.ts +25 -24
  101. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  102. package/node_modules/@playdrop/types/dist/api.js +5 -10
  103. package/node_modules/@playdrop/types/dist/version.d.ts +14 -0
  104. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  105. package/package.json +1 -1
  106. package/dist/apps/launchCheck.d.ts +0 -39
  107. package/dist/apps/launchCheck.js +0 -414
  108. package/dist/captureRuntime.d.ts +0 -73
  109. package/dist/captureRuntime.js +0 -1021
  110. package/dist/commands/capture.d.ts +0 -14
  111. package/dist/commands/capture.js +0 -550
  112. package/dist/commands/captureRemote.d.ts +0 -17
  113. package/dist/commands/captureRemote.js +0 -394
  114. package/node_modules/@playdrop/types/dist/entity.d.ts +0 -31
  115. package/node_modules/@playdrop/types/dist/entity.d.ts.map +0 -1
  116. package/node_modules/@playdrop/types/dist/entity.js +0 -67
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
- /* eslint-disable max-lines */
3
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
3
  if (k2 === undefined) k2 = k;
5
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -35,24 +34,34 @@ var __importStar = (this && this.__importStar) || (function () {
35
34
  })();
36
35
  Object.defineProperty(exports, "__esModule", { value: true });
37
36
  exports.resolveListingCaptureRouteSegment = resolveListingCaptureRouteSegment;
37
+ exports.resolveCaptureListingDevRouterPort = resolveCaptureListingDevRouterPort;
38
+ exports.resolveListingCaptureLocalDevPort = resolveListingCaptureLocalDevPort;
39
+ exports.buildListingCaptureFrameUrl = buildListingCaptureFrameUrl;
38
40
  exports.resolveListingCaptureSceneId = resolveListingCaptureSceneId;
39
41
  exports.parseCaptureListingOptions = parseCaptureListingOptions;
40
42
  exports.assertSupportedListingEnvironment = assertSupportedListingEnvironment;
41
43
  exports.resolveListingRecorderPath = resolveListingRecorderPath;
44
+ exports.buildSurfaceOutputPaths = buildSurfaceOutputPaths;
45
+ exports.resolveCaptureDimensions = resolveCaptureDimensions;
46
+ exports.resolveListingCaptureBrowserContextOptions = resolveListingCaptureBrowserContextOptions;
42
47
  exports.computeRecordedCrop = computeRecordedCrop;
43
48
  exports.captureListing = captureListing;
49
+ const types_1 = require("@playdrop/types");
44
50
  const node_child_process_1 = require("node:child_process");
51
+ const node_crypto_1 = require("node:crypto");
45
52
  const promises_1 = require("node:fs/promises");
46
53
  const node_path_1 = require("node:path");
47
- const types_1 = require("@playdrop/types");
54
+ const appUrls_1 = require("../appUrls");
55
+ const catalogue_1 = require("../catalogue");
48
56
  const catalogue_utils_1 = require("../catalogue-utils");
49
57
  const commandContext_1 = require("../commandContext");
50
58
  const http_1 = require("../http");
51
59
  const messages_1 = require("../messages");
52
60
  const playwright_1 = require("../playwright");
53
- const devShared_1 = require("./devShared");
61
+ const dev_1 = require("./dev");
62
+ const devRuntimeAssets_1 = require("./devRuntimeAssets");
54
63
  const devServer_1 = require("./devServer");
55
- const appUrls_1 = require("../appUrls");
64
+ const devShared_1 = require("./devShared");
56
65
  const CAPTURE_FRAME_SELECTOR = 'iframe[title="Game"]';
57
66
  const DEFAULT_DURATION_SECONDS = 8;
58
67
  const DEFAULT_WIDTH = 1280;
@@ -64,10 +73,43 @@ const MIN_CAPTURE_SECONDS = 1;
64
73
  const MIN_DIMENSION = 16;
65
74
  const MAX_DIMENSION = 8192;
66
75
  const MAX_FPS = 120;
67
- const SUPPORTED_MACOS_PREFIX = '26.4';
76
+ const MIN_SUPPORTED_MACOS_MAJOR = 26;
77
+ const MIN_SUPPORTED_MACOS_MINOR = 4;
68
78
  const LISTING_CAPTURE_HOOK_TIMEOUT_MS = 60000;
79
+ const SHA256_HEX_REGEX = /^[a-f0-9]{64}$/i;
80
+ const LISTING_CAPTURE_MOBILE_USER_AGENT = 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1';
81
+ const SURFACE_CAPTURE_DIMENSIONS = {
82
+ DESKTOP: { width: 1280, height: 720 },
83
+ MOBILE_LANDSCAPE: { width: 844, height: 390 },
84
+ MOBILE_PORTRAIT: { width: 390, height: 844 },
85
+ };
86
+ const SURFACE_OUTPUT_SLUG = {
87
+ DESKTOP: 'desktop',
88
+ MOBILE_LANDSCAPE: 'mobile-landscape',
89
+ MOBILE_PORTRAIT: 'mobile-portrait',
90
+ };
69
91
  function resolveListingCaptureRouteSegment(previewable) {
70
- return previewable ? 'dev-preview' : 'dev';
92
+ if (!previewable) {
93
+ throw new Error('listing_preview_support_missing');
94
+ }
95
+ return 'dev-preview';
96
+ }
97
+ function resolveCaptureListingDevRouterPort(env = process.env) {
98
+ return (0, dev_1.resolveCliDevRouterPort)(env);
99
+ }
100
+ function resolveListingCaptureLocalDevPort(devRouterPort) {
101
+ return devRouterPort === devServer_1.DEV_ROUTER_PORT ? null : devRouterPort;
102
+ }
103
+ function buildListingCaptureFrameUrl({ webBase, currentUsername, appTypeSlug, appName, routeSegment, localDevPort, }) {
104
+ const frameUrlObject = new URL(`${(0, appUrls_1.normalizePlaydropWebBase)(webBase)}/creators/${encodeURIComponent(currentUsername)}/apps/${appTypeSlug}/${encodeURIComponent(appName)}/${routeSegment}`);
105
+ frameUrlObject.searchParams.set('devAuth', 'anonymous');
106
+ frameUrlObject.searchParams.set('launchCheck', '1');
107
+ frameUrlObject.searchParams.set('captureShell', 'listing');
108
+ frameUrlObject.searchParams.set('mobileFrameHost', 'admin');
109
+ if (localDevPort !== null) {
110
+ frameUrlObject.searchParams.set('localDevPort', String(localDevPort));
111
+ }
112
+ return frameUrlObject.toString();
71
113
  }
72
114
  function resolveListingCaptureSceneId(width, height) {
73
115
  return height > width ? 'listing-portrait' : 'listing-landscape';
@@ -93,6 +135,7 @@ function parseCaptureListingOptions(targetArg, options = {}) {
93
135
  minimum: MIN_CAPTURE_SECONDS,
94
136
  maximum: MAX_CAPTURE_SECONDS,
95
137
  });
138
+ const explicitDimensions = options.width !== undefined || options.height !== undefined;
96
139
  const width = parsePositiveNumber(options.width, DEFAULT_WIDTH, 'width', {
97
140
  minimum: MIN_DIMENSION,
98
141
  maximum: MAX_DIMENSION,
@@ -113,6 +156,12 @@ function parseCaptureListingOptions(targetArg, options = {}) {
113
156
  maximum: durationSeconds,
114
157
  });
115
158
  const outputDir = options.outputDir?.trim() ? (0, node_path_1.resolve)(process.cwd(), options.outputDir.trim()) : null;
159
+ const contentHash = typeof options.contentHash === 'string' && options.contentHash.trim().length > 0
160
+ ? options.contentHash.trim().toLowerCase()
161
+ : null;
162
+ if (contentHash && !SHA256_HEX_REGEX.test(contentHash)) {
163
+ throw new Error('content_hash_invalid');
164
+ }
116
165
  return {
117
166
  targetArg,
118
167
  appName: options.app?.trim() || undefined,
@@ -124,6 +173,8 @@ function parseCaptureListingOptions(targetArg, options = {}) {
124
173
  audio: Boolean(options.audio),
125
174
  outputDir,
126
175
  keepRaw: Boolean(options.keepRaw),
176
+ contentHash,
177
+ explicitDimensions,
127
178
  };
128
179
  }
129
180
  function readMacOsVersion() {
@@ -139,7 +190,13 @@ function assertSupportedListingEnvironment(platform = process.platform, macosVer
139
190
  if (platform !== 'darwin') {
140
191
  throw new Error('unsupported_platform');
141
192
  }
142
- if (!macosVersion.startsWith(SUPPORTED_MACOS_PREFIX)) {
193
+ const [majorRaw, minorRaw] = macosVersion.split('.');
194
+ const major = Number.parseInt(majorRaw ?? '', 10);
195
+ const minor = Number.parseInt(minorRaw ?? '', 10);
196
+ if (!Number.isInteger(major)
197
+ || !Number.isInteger(minor)
198
+ || major !== MIN_SUPPORTED_MACOS_MAJOR
199
+ || minor < MIN_SUPPORTED_MACOS_MINOR) {
143
200
  throw new Error(`unsupported_macos_version:${macosVersion}`);
144
201
  }
145
202
  }
@@ -166,11 +223,40 @@ async function ensureOutputPaths(appName, explicitOutputDir) {
166
223
  await (0, promises_1.mkdir)(outputDir, { recursive: true });
167
224
  return {
168
225
  outputDir,
169
- rawVideoPath: (0, node_path_1.join)(outputDir, 'listing-raw.mp4'),
170
- metadataPath: (0, node_path_1.join)(outputDir, 'listing-raw.json'),
171
226
  reportPath: (0, node_path_1.join)(outputDir, 'capture-report.json'),
172
- posterPath: (0, node_path_1.join)(outputDir, 'poster.png'),
173
- finalVideoPath: (0, node_path_1.join)(outputDir, 'listing.mp4'),
227
+ };
228
+ }
229
+ function buildSurfaceOutputPaths(outputDir, surface, useSurfacePrefix) {
230
+ const prefix = useSurfacePrefix ? `${SURFACE_OUTPUT_SLUG[surface]}-` : '';
231
+ return {
232
+ rawVideoPath: (0, node_path_1.join)(outputDir, `${prefix}listing-raw.mp4`),
233
+ metadataPath: (0, node_path_1.join)(outputDir, `${prefix}listing-raw.json`),
234
+ posterPath: (0, node_path_1.join)(outputDir, `${prefix}poster.png`),
235
+ finalVideoPath: (0, node_path_1.join)(outputDir, `${prefix}listing.mp4`),
236
+ };
237
+ }
238
+ function resolveCaptureDimensions(surface, parsedOptions) {
239
+ if (parsedOptions.explicitDimensions) {
240
+ return {
241
+ width: parsedOptions.width,
242
+ height: parsedOptions.height,
243
+ };
244
+ }
245
+ return SURFACE_CAPTURE_DIMENSIONS[surface];
246
+ }
247
+ function resolveListingCaptureBrowserContextOptions(surface, dimensions) {
248
+ if (surface === 'DESKTOP') {
249
+ return { viewport: null };
250
+ }
251
+ return {
252
+ viewport: {
253
+ width: dimensions.width,
254
+ height: dimensions.height,
255
+ },
256
+ deviceScaleFactor: 2,
257
+ isMobile: true,
258
+ hasTouch: true,
259
+ userAgent: LISTING_CAPTURE_MOBILE_USER_AGENT,
174
260
  };
175
261
  }
176
262
  async function grantListingBrowserLocalNetworkAccess(context, origin) {
@@ -180,7 +266,7 @@ async function grantListingBrowserLocalNetworkAccess(context, origin) {
180
266
  }
181
267
  await grantPermissions.call(context, ['local-network-access'], { origin });
182
268
  }
183
- async function launchListingBrowser(initialViewport, automationOrigin) {
269
+ async function launchListingBrowser(initialViewport, automationOrigin, contextOptions) {
184
270
  const playwright = await Promise.resolve().then(() => __importStar(require('playwright-core')));
185
271
  const args = [
186
272
  '--no-sandbox',
@@ -207,7 +293,7 @@ async function launchListingBrowser(initialViewport, automationOrigin) {
207
293
  throw new Error('browser_process_missing');
208
294
  }
209
295
  const browser = await playwright.chromium.connect(server.wsEndpoint());
210
- const context = await browser.newContext({ viewport: null });
296
+ const context = await browser.newContext(contextOptions);
211
297
  await grantListingBrowserLocalNetworkAccess(context, automationOrigin);
212
298
  const page = await context.newPage();
213
299
  return {
@@ -471,6 +557,9 @@ async function probeMediaFile(filePath) {
471
557
  ], 'ffprobe_failed');
472
558
  return JSON.parse(stdout);
473
559
  }
560
+ async function computeFileSha256(filePath) {
561
+ return (0, node_crypto_1.createHash)('sha256').update(await (0, promises_1.readFile)(filePath)).digest('hex');
562
+ }
474
563
  function parseFrameRate(raw) {
475
564
  if (!raw) {
476
565
  return null;
@@ -533,14 +622,14 @@ async function runListingRecorder(recorderPath, pid, durationSeconds, rawOutputP
533
622
  function formatCommandError(error) {
534
623
  if (error.message === 'unsupported_platform') {
535
624
  return {
536
- message: 'Listing capture only runs on macOS.',
625
+ message: 'Game media capture only runs on macOS.',
537
626
  suggestions: [],
538
627
  };
539
628
  }
540
629
  if (error.message.startsWith('unsupported_macos_version:')) {
541
630
  const version = error.message.split(':')[1] || 'unknown';
542
631
  return {
543
- message: `Listing capture only supports macOS ${SUPPORTED_MACOS_PREFIX} on this machine. Current version: ${version}.`,
632
+ message: `Game media capture requires macOS ${MIN_SUPPORTED_MACOS_MAJOR}.${MIN_SUPPORTED_MACOS_MINOR} or newer on this machine. Current version: ${version}.`,
544
633
  suggestions: [],
545
634
  };
546
635
  }
@@ -550,6 +639,18 @@ function formatCommandError(error) {
550
639
  suggestions: [],
551
640
  };
552
641
  }
642
+ if (error.message === 'content_hash_invalid') {
643
+ return {
644
+ message: 'The --content-hash value must be a 64-character SHA-256 hex string.',
645
+ suggestions: [],
646
+ };
647
+ }
648
+ if (error.message === 'listing_preview_support_missing') {
649
+ return {
650
+ message: 'This game does not declare preview support, so listing capture cannot run.',
651
+ suggestions: ['Set previewable: true in catalogue.json, implement the preview scene and __listingCapture hooks, then rerun "playdrop project capture".'],
652
+ };
653
+ }
553
654
  if (error.message.startsWith('listing_recorder_failed:')) {
554
655
  return {
555
656
  message: error.message.slice('listing_recorder_failed:'.length),
@@ -629,7 +730,7 @@ function ensureCaptureListingToolAvailable(command) {
629
730
  return true;
630
731
  }
631
732
  (0, messages_1.printErrorWithHelp)(`${command} is required for listing capture.`, [`Install ${command} and make sure it is available on PATH.`], {
632
- command: 'project capture listing',
733
+ command: 'project capture',
633
734
  });
634
735
  process.exitCode = 1;
635
736
  return false;
@@ -640,7 +741,7 @@ async function ensureListingRecorderAvailable(recorderPath) {
640
741
  return true;
641
742
  }
642
743
  catch {
643
- (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' });
744
+ (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' });
644
745
  process.exitCode = 1;
645
746
  return false;
646
747
  }
@@ -657,20 +758,22 @@ function resolveCaptureListingTarget(parsedOptions) {
657
758
  : code === 'catalogue_missing'
658
759
  ? ['Run the command inside a Playdrop workspace or pass the direct HTML file path.']
659
760
  : ['Provide a valid app target or HTML file path.'];
660
- (0, messages_1.printErrorWithHelp)(message, suggestions, { command: 'project capture listing' });
761
+ (0, messages_1.printErrorWithHelp)(message, suggestions, { command: 'project capture' });
661
762
  process.exitCode = 1;
662
763
  return null;
663
764
  }
664
765
  }
665
766
  async function captureListing(targetArg, options = {}) {
666
767
  let parsedOptions;
768
+ let devRouterPort;
667
769
  try {
668
770
  parsedOptions = parseCaptureListingOptions(targetArg, options);
771
+ devRouterPort = resolveCaptureListingDevRouterPort();
669
772
  assertSupportedListingEnvironment();
670
773
  }
671
774
  catch (error) {
672
775
  const detail = formatCommandError(error instanceof Error ? error : new Error(String(error)));
673
- (0, messages_1.printErrorWithHelp)(detail.message, detail.suggestions, { command: 'project capture listing' });
776
+ (0, messages_1.printErrorWithHelp)(detail.message, detail.suggestions, { command: 'project capture' });
674
777
  process.exitCode = 1;
675
778
  return;
676
779
  }
@@ -687,21 +790,30 @@ async function captureListing(targetArg, options = {}) {
687
790
  }
688
791
  let appName = resolvedTarget.appName;
689
792
  let appTypeSlug = (0, appUrls_1.getAppTypeSlug)(null);
793
+ let declaresPreviewSupport = false;
690
794
  try {
691
- const match = (0, catalogue_utils_1.findAppDefinition)(resolvedTarget.htmlPath);
795
+ const match = (0, catalogue_utils_1.findAppCatalogueInfo)(resolvedTarget.htmlPath);
692
796
  appName = match.name;
693
797
  appTypeSlug = (0, appUrls_1.getAppTypeSlug)(match.type);
798
+ declaresPreviewSupport = match.previewable;
694
799
  }
695
800
  catch {
696
- // keep defaults for direct-file targets outside a registered catalogue
801
+ declaresPreviewSupport = false;
802
+ }
803
+ try {
804
+ resolveListingCaptureRouteSegment(declaresPreviewSupport);
805
+ }
806
+ catch (error) {
807
+ const detail = formatCommandError(error instanceof Error ? error : new Error(String(error)));
808
+ (0, messages_1.printErrorWithHelp)(detail.message, detail.suggestions, { command: 'project capture' });
809
+ process.exitCode = 1;
810
+ return;
697
811
  }
698
812
  const outputPaths = await ensureOutputPaths(appName, parsedOptions.outputDir);
699
813
  const projectInfo = (0, devShared_1.findProjectInfo)(resolvedTarget.htmlPath);
700
814
  const devScriptAvailable = Boolean(projectInfo.projectDir && projectInfo.packageJson && typeof projectInfo.packageJson.scripts?.dev === 'string');
701
- // eslint-disable-next-line complexity
702
- await (0, commandContext_1.withEnvironment)('project capture listing', 'Capturing listing media', async ({ client, env, envConfig }) => {
815
+ await (0, commandContext_1.withEnvironment)('project capture', 'Capturing game media', async ({ client, env, envConfig }) => {
703
816
  let currentUsername = '';
704
- let registeredApp = null;
705
817
  try {
706
818
  const currentUser = await (0, devShared_1.fetchDevUser)(client);
707
819
  currentUsername = currentUser.username.trim();
@@ -719,36 +831,36 @@ async function captureListing(targetArg, options = {}) {
719
831
  (0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, [
720
832
  'Run "playdrop auth login" to refresh your session.',
721
833
  'Use "playdrop auth whoami" afterwards to verify the current account.',
722
- ], { command: 'project capture listing' });
834
+ ], { command: 'project capture' });
723
835
  process.exitCode = 1;
724
836
  return;
725
837
  }
726
838
  if ((0, devShared_1.isNetworkError)(error)) {
727
- (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to resolve your account.', 'project capture listing');
839
+ (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to resolve your account.', 'project capture');
728
840
  process.exitCode = 1;
729
841
  return;
730
842
  }
731
843
  throw error;
732
844
  }
733
845
  try {
734
- registeredApp = await (0, devShared_1.assertAppRegistered)(client, currentUsername, appName);
846
+ await (0, devShared_1.assertAppRegistered)(client, currentUsername, appName);
735
847
  }
736
848
  catch (error) {
737
849
  if (error instanceof http_1.CLIUnsupportedClientError) {
738
850
  return;
739
851
  }
740
852
  if (error instanceof types_1.UnsupportedClientError) {
741
- (0, http_1.handleUnsupportedError)(error, 'Listing capture');
853
+ (0, http_1.handleUnsupportedError)(error, 'Game media capture');
742
854
  process.exitCode = 1;
743
855
  return;
744
856
  }
745
857
  if (error instanceof types_1.ApiError && error.status === 404) {
746
- (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' });
858
+ (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' });
747
859
  process.exitCode = 1;
748
860
  return;
749
861
  }
750
862
  if ((0, devShared_1.isNetworkError)(error)) {
751
- (0, messages_1.printNetworkIssue)('Could not verify the app registration.', 'project capture listing');
863
+ (0, messages_1.printNetworkIssue)('Could not verify the app registration.', 'project capture');
752
864
  process.exitCode = 1;
753
865
  return;
754
866
  }
@@ -756,11 +868,41 @@ async function captureListing(targetArg, options = {}) {
756
868
  }
757
869
  const entryLabel = (0, node_path_1.relative)(process.cwd(), resolvedTarget.htmlPath) || resolvedTarget.htmlPath;
758
870
  console.log(`[listing] Preparing ${entryLabel} for ${env} (${appTypeSlug}).`);
871
+ const taskLookup = (0, catalogue_1.findAppTaskByFile)(resolvedTarget.htmlPath);
872
+ if (taskLookup.errors.length > 0) {
873
+ (0, messages_1.printErrorWithHelp)(taskLookup.errors[0] || 'Failed to resolve the app task from catalogue.json.', taskLookup.errors.slice(1), { command: 'project capture' });
874
+ process.exitCode = 1;
875
+ return;
876
+ }
877
+ const localDevAppUrl = (0, devServer_1.buildLocalDevAppUrl)({
878
+ creatorUsername: currentUsername,
879
+ appType: appTypeSlug,
880
+ appName,
881
+ port: devRouterPort,
882
+ });
883
+ let runtimeAssetManifest = (0, devRuntimeAssets_1.createEmptyDevRuntimeAssetManifest)();
884
+ if (taskLookup.task) {
885
+ try {
886
+ runtimeAssetManifest = await (0, devRuntimeAssets_1.buildDevRuntimeAssetManifest)({
887
+ client,
888
+ apiBase: envConfig.apiBase,
889
+ task: taskLookup.task,
890
+ creatorUsername: currentUsername,
891
+ appBaseUrl: new URL('.', localDevAppUrl).toString(),
892
+ });
893
+ }
894
+ catch (error) {
895
+ const formatted = (0, dev_1.formatDevRuntimeAssetManifestFailure)(error);
896
+ (0, messages_1.printErrorWithHelp)(formatted.message, formatted.suggestions, { command: 'project capture' });
897
+ process.exitCode = 1;
898
+ return;
899
+ }
900
+ }
759
901
  const serverAlreadyRunning = await (0, devServer_1.isDevServerAvailable)({
760
902
  creatorUsername: currentUsername,
761
903
  appType: appTypeSlug,
762
904
  appName,
763
- port: devServer_1.DEV_ROUTER_PORT,
905
+ port: devRouterPort,
764
906
  }, 750);
765
907
  const devServerStartedByCapture = !serverAlreadyRunning;
766
908
  let serverHandle = null;
@@ -780,12 +922,13 @@ async function captureListing(targetArg, options = {}) {
780
922
  };
781
923
  try {
782
924
  if (serverAlreadyRunning) {
783
- console.log(`[listing] Reusing dev server at ${(0, devServer_1.buildLocalDevAppUrl)({
925
+ await (0, devServer_1.updateMountedDevRuntimeAssetManifest)({
784
926
  creatorUsername: currentUsername,
785
- appType: appTypeSlug,
786
927
  appName,
787
- port: devServer_1.DEV_ROUTER_PORT,
788
- })}`);
928
+ runtimeAssetManifest,
929
+ port: devRouterPort,
930
+ });
931
+ console.log(`[listing] Reusing dev server at ${localDevAppUrl}`);
789
932
  }
790
933
  else {
791
934
  serverHandle = await (0, devServer_1.startDevServer)({
@@ -793,8 +936,9 @@ async function captureListing(targetArg, options = {}) {
793
936
  appType: appTypeSlug,
794
937
  creatorUsername: currentUsername,
795
938
  htmlPath: resolvedTarget.htmlPath,
796
- port: devServer_1.DEV_ROUTER_PORT,
939
+ port: devRouterPort,
797
940
  projectInfo,
941
+ runtimeAssetManifest,
798
942
  });
799
943
  }
800
944
  if (!serverAlreadyRunning && projectInfo.projectDir && !devScriptAvailable && projectInfo.packageJsonPath) {
@@ -807,114 +951,153 @@ async function captureListing(targetArg, options = {}) {
807
951
  await new Promise(resolve => setTimeout(resolve, 1000));
808
952
  }
809
953
  const webBase = envConfig.webBase ?? 'https://www.playdrop.ai';
810
- const routeSegment = resolveListingCaptureRouteSegment(Boolean(registeredApp?.previewable));
811
- const frameUrlObject = new URL(`${(0, appUrls_1.normalizePlaydropWebBase)(webBase)}/creators/${encodeURIComponent(currentUsername)}/apps/${appTypeSlug}/${encodeURIComponent(appName)}/${routeSegment}`);
812
- frameUrlObject.searchParams.set('launchCheck', '1');
813
- const frameUrl = frameUrlObject.toString();
814
- const captureSceneId = resolveListingCaptureSceneId(parsedOptions.width, parsedOptions.height);
954
+ const routeSegment = resolveListingCaptureRouteSegment(declaresPreviewSupport);
955
+ const localDevPort = resolveListingCaptureLocalDevPort(devRouterPort);
956
+ const frameUrl = buildListingCaptureFrameUrl({
957
+ webBase,
958
+ currentUsername,
959
+ appTypeSlug,
960
+ appName,
961
+ routeSegment,
962
+ localDevPort,
963
+ });
964
+ const frameUrlObject = new URL(frameUrl);
965
+ console.log(`[listing] Opening ${frameUrl}`);
815
966
  const shouldExportPreviewAudio = parsedOptions.audio && routeSegment === 'dev-preview';
816
- browserHandle = await launchListingBrowser({
817
- width: parsedOptions.width,
818
- height: parsedOptions.height,
819
- }, frameUrlObject.origin);
820
- await browserHandle.page.goto(frameUrl, { waitUntil: 'domcontentloaded' });
821
- await browserHandle.page.waitForTimeout(1000);
822
- if (routeSegment === 'dev-preview') {
823
- console.log(`[listing] Preparing preview scene ${captureSceneId}.`);
824
- await prepareHostedListingScene(browserHandle.page, captureSceneId);
825
- await browserHandle.page.waitForTimeout(1000);
826
- }
827
- if (shouldExportPreviewAudio) {
828
- console.log('[listing] Starting in-app preview audio capture.');
829
- await startHostedListingAudioCapture(browserHandle.page);
830
- }
831
- const measurement = await fitWindowToRequestedGameplay(browserHandle.page, parsedOptions.width, parsedOptions.height);
832
- 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)}.`);
833
- await browserHandle.page.waitForTimeout(750);
834
- const recorderMetadata = await runListingRecorder(recorderPath, browserHandle.processId, parsedOptions.durationSeconds, outputPaths.rawVideoPath, outputPaths.metadataPath, shouldExportPreviewAudio ? false : parsedOptions.audio);
835
- const exportedAudioPath = shouldExportPreviewAudio
836
- ? await stopHostedListingAudioCapture(browserHandle.page, outputPaths.outputDir)
837
- : null;
838
- const rawProbe = await probeMediaFile(outputPaths.rawVideoPath);
839
- const rawVideoStream = extractVideoStream(rawProbe);
840
- const rawDurationSeconds = readDurationSeconds(rawProbe);
841
- const rawAudioTrackCount = countAudioStreams(rawProbe);
842
- const crop = computeRecordedCrop(measurement, rawVideoStream.width, rawVideoStream.height);
843
- console.log(`[listing] Cropping raw capture to gameplay at ${crop.width}x${crop.height}+${crop.x}+${crop.y}.`);
844
- await encodeListingVideo(outputPaths.rawVideoPath, outputPaths.finalVideoPath, outputPaths.posterPath, crop, parsedOptions.width, parsedOptions.height, parsedOptions.fps, parsedOptions.posterAtSeconds);
845
- if (exportedAudioPath) {
846
- await muxListingAudio(outputPaths.finalVideoPath, exportedAudioPath);
847
- }
848
- const finalProbe = await probeMediaFile(outputPaths.finalVideoPath);
849
- const finalVideoStream = extractVideoStream(finalProbe);
850
- const finalDurationSeconds = readDurationSeconds(finalProbe);
851
- const finalAudioTrackCount = countAudioStreams(finalProbe);
852
- const finalFps = parseFrameRate(finalVideoStream.avg_frame_rate);
853
- ensureTolerance(finalVideoStream.width, parsedOptions.width, 0, 'final_width_mismatch');
854
- ensureTolerance(finalVideoStream.height, parsedOptions.height, 0, 'final_height_mismatch');
855
- ensureTolerance(finalDurationSeconds, parsedOptions.durationSeconds, 1.5, 'final_duration_mismatch');
856
- if (parsedOptions.audio && finalAudioTrackCount === 0) {
857
- throw new Error('audio_track_missing');
967
+ const surfaces = resolvedTarget.surfaceTargets.list;
968
+ const useSurfacePrefix = surfaces.length > 1;
969
+ const captures = [];
970
+ const artifactHashes = {};
971
+ const capturedSurfaces = [];
972
+ const allWarnings = [];
973
+ for (const surface of surfaces) {
974
+ const dimensions = resolveCaptureDimensions(surface, parsedOptions);
975
+ const captureSceneId = resolveListingCaptureSceneId(dimensions.width, dimensions.height);
976
+ const surfaceOutputPaths = buildSurfaceOutputPaths(outputPaths.outputDir, surface, useSurfacePrefix);
977
+ console.log(`[listing] Capturing ${surface} at ${dimensions.width}x${dimensions.height}.`);
978
+ const contextOptions = resolveListingCaptureBrowserContextOptions(surface, dimensions);
979
+ browserHandle = await launchListingBrowser(dimensions, frameUrlObject.origin, contextOptions);
980
+ try {
981
+ await browserHandle.page.goto(frameUrl, { waitUntil: 'domcontentloaded' });
982
+ await browserHandle.page.waitForTimeout(1000);
983
+ if (routeSegment === 'dev-preview') {
984
+ console.log(`[listing] Preparing preview scene ${captureSceneId}.`);
985
+ await prepareHostedListingScene(browserHandle.page, captureSceneId);
986
+ await browserHandle.page.waitForTimeout(1000);
987
+ }
988
+ if (shouldExportPreviewAudio) {
989
+ console.log('[listing] Starting in-app preview audio capture.');
990
+ await startHostedListingAudioCapture(browserHandle.page);
991
+ }
992
+ const measurement = await fitWindowToRequestedGameplay(browserHandle.page, dimensions.width, dimensions.height);
993
+ 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)}.`);
994
+ await browserHandle.page.waitForTimeout(750);
995
+ const recorderMetadata = await runListingRecorder(recorderPath, browserHandle.processId, parsedOptions.durationSeconds, surfaceOutputPaths.rawVideoPath, surfaceOutputPaths.metadataPath, shouldExportPreviewAudio ? false : parsedOptions.audio);
996
+ const exportedAudioPath = shouldExportPreviewAudio
997
+ ? await stopHostedListingAudioCapture(browserHandle.page, outputPaths.outputDir)
998
+ : null;
999
+ const rawProbe = await probeMediaFile(surfaceOutputPaths.rawVideoPath);
1000
+ const rawVideoStream = extractVideoStream(rawProbe);
1001
+ const rawDurationSeconds = readDurationSeconds(rawProbe);
1002
+ const rawAudioTrackCount = countAudioStreams(rawProbe);
1003
+ const crop = computeRecordedCrop(measurement, rawVideoStream.width, rawVideoStream.height);
1004
+ console.log(`[listing] Cropping raw capture to gameplay at ${crop.width}x${crop.height}+${crop.x}+${crop.y}.`);
1005
+ await encodeListingVideo(surfaceOutputPaths.rawVideoPath, surfaceOutputPaths.finalVideoPath, surfaceOutputPaths.posterPath, crop, dimensions.width, dimensions.height, parsedOptions.fps, parsedOptions.posterAtSeconds);
1006
+ if (exportedAudioPath) {
1007
+ await muxListingAudio(surfaceOutputPaths.finalVideoPath, exportedAudioPath);
1008
+ }
1009
+ const finalProbe = await probeMediaFile(surfaceOutputPaths.finalVideoPath);
1010
+ const finalVideoStream = extractVideoStream(finalProbe);
1011
+ const finalDurationSeconds = readDurationSeconds(finalProbe);
1012
+ const finalAudioTrackCount = countAudioStreams(finalProbe);
1013
+ const finalFps = parseFrameRate(finalVideoStream.avg_frame_rate);
1014
+ ensureTolerance(finalVideoStream.width, dimensions.width, 0, 'final_width_mismatch');
1015
+ ensureTolerance(finalVideoStream.height, dimensions.height, 0, 'final_height_mismatch');
1016
+ ensureTolerance(finalDurationSeconds, parsedOptions.durationSeconds, 1.5, 'final_duration_mismatch');
1017
+ if (parsedOptions.audio && finalAudioTrackCount === 0) {
1018
+ throw new Error('audio_track_missing');
1019
+ }
1020
+ const warnings = createWarnings(finalProbe, parsedOptions.audio);
1021
+ for (const warning of warnings) {
1022
+ allWarnings.push(`${surface}: ${warning}`);
1023
+ }
1024
+ artifactHashes[(0, node_path_1.relative)(outputPaths.outputDir, surfaceOutputPaths.finalVideoPath) || 'listing.mp4'] = await computeFileSha256(surfaceOutputPaths.finalVideoPath);
1025
+ artifactHashes[(0, node_path_1.relative)(outputPaths.outputDir, surfaceOutputPaths.posterPath) || 'poster.png'] = await computeFileSha256(surfaceOutputPaths.posterPath);
1026
+ capturedSurfaces.push(surface);
1027
+ captures.push({
1028
+ surface,
1029
+ targetUrl: frameUrl,
1030
+ command: {
1031
+ durationSeconds: parsedOptions.durationSeconds,
1032
+ width: dimensions.width,
1033
+ height: dimensions.height,
1034
+ fps: parsedOptions.fps,
1035
+ posterAtSeconds: parsedOptions.posterAtSeconds,
1036
+ audio: parsedOptions.audio,
1037
+ keepRaw: parsedOptions.keepRaw,
1038
+ },
1039
+ measurement,
1040
+ recorder: recorderMetadata,
1041
+ crop,
1042
+ rawVideo: {
1043
+ path: parsedOptions.keepRaw ? surfaceOutputPaths.rawVideoPath : null,
1044
+ width: rawVideoStream.width,
1045
+ height: rawVideoStream.height,
1046
+ durationSeconds: rawDurationSeconds,
1047
+ audioTrackCount: rawAudioTrackCount,
1048
+ },
1049
+ finalVideo: {
1050
+ path: surfaceOutputPaths.finalVideoPath,
1051
+ width: finalVideoStream.width,
1052
+ height: finalVideoStream.height,
1053
+ durationSeconds: finalDurationSeconds,
1054
+ audioTrackCount: finalAudioTrackCount,
1055
+ fps: finalFps,
1056
+ },
1057
+ posterPath: surfaceOutputPaths.posterPath,
1058
+ warnings,
1059
+ });
1060
+ if (!parsedOptions.keepRaw) {
1061
+ await (0, promises_1.rm)(surfaceOutputPaths.rawVideoPath, { force: true });
1062
+ }
1063
+ console.log(`[listing] Saved ${surface} video to ${(0, node_path_1.relative)(process.cwd(), surfaceOutputPaths.finalVideoPath) || surfaceOutputPaths.finalVideoPath}`);
1064
+ console.log(`[listing] Saved ${surface} poster to ${(0, node_path_1.relative)(process.cwd(), surfaceOutputPaths.posterPath) || surfaceOutputPaths.posterPath}`);
1065
+ if (warnings.length > 0) {
1066
+ for (const warning of warnings) {
1067
+ console.warn(`[listing][warn][${surface}] ${warning}`);
1068
+ }
1069
+ }
1070
+ }
1071
+ finally {
1072
+ await browserHandle.close();
1073
+ browserHandle = null;
1074
+ }
858
1075
  }
859
- const warnings = createWarnings(finalProbe, parsedOptions.audio);
860
1076
  const report = {
861
1077
  appName,
862
- targetUrl: frameUrl,
863
1078
  outputDir: outputPaths.outputDir,
864
- command: {
865
- durationSeconds: parsedOptions.durationSeconds,
866
- width: parsedOptions.width,
867
- height: parsedOptions.height,
868
- fps: parsedOptions.fps,
869
- posterAtSeconds: parsedOptions.posterAtSeconds,
870
- audio: parsedOptions.audio,
871
- keepRaw: parsedOptions.keepRaw,
872
- },
873
- measurement,
874
- recorder: recorderMetadata,
875
- crop,
876
- rawVideo: {
877
- path: parsedOptions.keepRaw ? outputPaths.rawVideoPath : null,
878
- width: rawVideoStream.width,
879
- height: rawVideoStream.height,
880
- durationSeconds: rawDurationSeconds,
881
- audioTrackCount: rawAudioTrackCount,
1079
+ captures,
1080
+ warnings: allWarnings,
1081
+ binding: {
1082
+ contentHash: parsedOptions.contentHash,
1083
+ artifactHashes,
1084
+ capturedSurfaces,
882
1085
  },
883
- finalVideo: {
884
- path: outputPaths.finalVideoPath,
885
- width: finalVideoStream.width,
886
- height: finalVideoStream.height,
887
- durationSeconds: finalDurationSeconds,
888
- audioTrackCount: finalAudioTrackCount,
889
- fps: finalFps,
890
- },
891
- posterPath: outputPaths.posterPath,
892
- warnings,
893
1086
  };
894
1087
  await (0, promises_1.writeFile)(outputPaths.reportPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
895
- if (!parsedOptions.keepRaw) {
896
- await (0, promises_1.rm)(outputPaths.rawVideoPath, { force: true });
897
- }
898
- console.log(`[listing] Saved video to ${(0, node_path_1.relative)(process.cwd(), outputPaths.finalVideoPath) || outputPaths.finalVideoPath}`);
899
- console.log(`[listing] Saved poster to ${(0, node_path_1.relative)(process.cwd(), outputPaths.posterPath) || outputPaths.posterPath}`);
900
1088
  console.log(`[listing] Saved report to ${(0, node_path_1.relative)(process.cwd(), outputPaths.reportPath) || outputPaths.reportPath}`);
901
- if (warnings.length > 0) {
902
- for (const warning of warnings) {
903
- console.warn(`[listing][warn] ${warning}`);
904
- }
905
- }
906
1089
  }
907
1090
  catch (error) {
908
1091
  if (error instanceof http_1.CLIUnsupportedClientError) {
909
1092
  return;
910
1093
  }
911
1094
  if (error instanceof types_1.UnsupportedClientError) {
912
- (0, http_1.handleUnsupportedError)(error, 'Listing capture');
1095
+ (0, http_1.handleUnsupportedError)(error, 'Game media capture');
913
1096
  process.exitCode = 1;
914
1097
  return;
915
1098
  }
916
1099
  const detail = formatCommandError(error instanceof Error ? error : new Error(String(error)));
917
- (0, messages_1.printErrorWithHelp)(detail.message, detail.suggestions, { command: 'project capture listing' });
1100
+ (0, messages_1.printErrorWithHelp)(detail.message, detail.suggestions, { command: 'project capture' });
918
1101
  process.exitCode = 1;
919
1102
  }
920
1103
  finally {