@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
@@ -14,7 +14,7 @@ function parseBrowseKind(raw) {
14
14
  return 'all';
15
15
  }
16
16
  const normalized = raw.trim().toLowerCase();
17
- if (normalized === 'app' || normalized === 'asset' || normalized === 'asset-pack' || normalized === 'all') {
17
+ if (normalized === 'app' || normalized === 'asset' || normalized === 'asset-pack' || normalized === 'asset-spec' || normalized === 'all') {
18
18
  return normalized;
19
19
  }
20
20
  return null;
@@ -66,6 +66,19 @@ function parseAssetSubcategory(raw) {
66
66
  const normalized = (0, types_1.normalizeAssetSubcategory)(raw);
67
67
  return normalized || null;
68
68
  }
69
+ function parseBrowseSort(raw) {
70
+ if (!raw || raw.trim().length === 0) {
71
+ return undefined;
72
+ }
73
+ const normalized = raw.trim().toLowerCase();
74
+ if (types_1.ASSET_LIST_SORT_VALUES.includes(normalized)) {
75
+ return normalized;
76
+ }
77
+ if (types_1.ASSET_SPEC_LIST_SORT_VALUES.includes(normalized)) {
78
+ return normalized;
79
+ }
80
+ return null;
81
+ }
69
82
  function buildItemRef(kind, creator, name) {
70
83
  return `${creator}/${kind}/${name}`;
71
84
  }
@@ -84,6 +97,9 @@ function itemSummary(item) {
84
97
  const subcategory = item.item.currentVersion?.subcategory;
85
98
  return subcategory ? `${item.item.category.toLowerCase()}/${subcategory}` : item.item.category.toLowerCase();
86
99
  }
100
+ if (item.kind === 'asset-spec') {
101
+ return item.item.currentVersionRef ? `current ${item.item.currentVersionRef.split('@')[1] ?? 'none'}` : 'no current version';
102
+ }
87
103
  return 'asset-pack';
88
104
  }
89
105
  function formatCountValue(value) {
@@ -97,15 +113,28 @@ function buildCountsSuffix(item) {
97
113
  }
98
114
  parts.push(`launch ${formatCountValue(item.item.playCount)}`);
99
115
  }
100
- if (item.kind === 'asset' && typeof item.item.playCount === 'number') {
116
+ if (item.kind === 'asset') {
101
117
  if (typeof item.item.viewCount === 'number') {
102
118
  parts.push(`view ${formatCountValue(item.item.viewCount)}`);
103
119
  }
104
- parts.push(`play ${formatCountValue(item.item.playCount)}`);
120
+ if (typeof item.item.downloadCount === 'number') {
121
+ parts.push(`download ${formatCountValue(item.item.downloadCount)}`);
122
+ }
123
+ if (typeof item.item.playCount === 'number') {
124
+ parts.push(`play ${formatCountValue(item.item.playCount)}`);
125
+ }
105
126
  }
106
127
  if (item.kind === 'asset-pack' && typeof item.item.downloadCount === 'number') {
107
128
  parts.push(`download ${formatCountValue(item.item.downloadCount)}`);
108
129
  }
130
+ if (item.kind === 'asset-spec') {
131
+ parts.push(`assets ${formatCountValue(item.item.publicAssetCount)}`);
132
+ parts.push(`apps ${formatCountValue(item.item.supportingAppCount)}`);
133
+ if (item.item.documentationAvailable) {
134
+ parts.push('docs yes');
135
+ }
136
+ return ` | ${parts.join(' | ')}`;
137
+ }
109
138
  if (typeof item.item.likeCount === 'number') {
110
139
  parts.push(`like ${formatCountValue(item.item.likeCount)}`);
111
140
  }
@@ -175,50 +204,342 @@ async function resolveCreatorUsername(creator, command, resolveMe) {
175
204
  }
176
205
  return creator;
177
206
  }
178
- async function browse(options = {}) {
179
- const kind = parseBrowseKind(options.kind);
180
- if (!kind) {
181
- (0, messages_1.printErrorWithHelp)(`Kind "${options.kind ?? ''}" is not supported.`, ['Use one of: app, asset, asset-pack, all.'], { command: 'browse' });
207
+ function normalizeOptionalOption(value) {
208
+ if (typeof value !== 'string') {
209
+ return undefined;
210
+ }
211
+ const normalized = value.trim();
212
+ return normalized.length > 0 ? normalized : undefined;
213
+ }
214
+ function normalizeTagFilters(rawTags) {
215
+ if (!Array.isArray(rawTags) || rawTags.length === 0) {
216
+ return [];
217
+ }
218
+ const normalizedRefs = [];
219
+ const seenRefs = new Set();
220
+ for (const rawTag of rawTags) {
221
+ if (typeof rawTag !== 'string' || rawTag.trim().length === 0) {
222
+ return null;
223
+ }
224
+ let normalizedRef = '';
225
+ try {
226
+ normalizedRef = (0, types_1.normalizeTagRef)(rawTag);
227
+ }
228
+ catch {
229
+ return null;
230
+ }
231
+ if (seenRefs.has(normalizedRef)) {
232
+ return null;
233
+ }
234
+ seenRefs.add(normalizedRef);
235
+ normalizedRefs.push(normalizedRef);
236
+ }
237
+ if (normalizedRefs.length > 5) {
238
+ return null;
239
+ }
240
+ return normalizedRefs;
241
+ }
242
+ function buildTagSuffix(tags) {
243
+ const normalizedTags = Array.isArray(tags) ? tags : [];
244
+ if (normalizedTags.length === 0) {
245
+ return '';
246
+ }
247
+ return ` | tags ${normalizedTags.slice(0, 3).map((tag) => tag.ref).join(', ')}`;
248
+ }
249
+ function parseBrowseSortOption(rawSort, kind) {
250
+ const sort = parseBrowseSort(rawSort);
251
+ if (sort === null) {
252
+ (0, messages_1.printErrorWithHelp)(`Sort "${rawSort ?? ''}" is not supported.`, ['Use one of: recent, likes, downloads, remixes, comments, assets, apps, alpha.'], { command: 'browse' });
182
253
  process.exitCode = 1;
183
- return;
254
+ return null;
184
255
  }
185
- const creator = parseBrowseCreator(options.creator);
186
- if (!creator) {
187
- (0, messages_1.printErrorWithHelp)('The --creator value is invalid.', ['Use all, me, or a creator slug without path separators.'], { command: 'browse' });
256
+ if (sort && kind === 'all') {
257
+ (0, messages_1.printErrorWithHelp)('The --sort option is not supported with --kind all.', ['Choose --kind asset or --kind asset-spec when sorting browse results.'], { command: 'browse' });
188
258
  process.exitCode = 1;
189
- return;
259
+ return null;
190
260
  }
191
- const parsedAppType = options.appType ? (0, types_1.parseAppType)(options.appType.trim().toUpperCase()) : undefined;
192
- if (options.appType !== undefined && !parsedAppType) {
193
- (0, messages_1.printErrorWithHelp)(`App type "${options.appType}" is not supported.`, ['Use one of: game, demo, tool, template.'], { command: 'browse' });
261
+ if (sort && kind === 'asset-spec' && !types_1.ASSET_SPEC_LIST_SORT_VALUES.includes(sort)) {
262
+ (0, messages_1.printErrorWithHelp)(`Sort "${sort}" is not supported for asset specs.`, ['Use one of: recent, assets, apps, alpha.'], { command: 'browse' });
194
263
  process.exitCode = 1;
195
- return;
264
+ return null;
196
265
  }
197
- const appType = parsedAppType ?? undefined;
266
+ if (sort && kind === 'asset' && !types_1.ASSET_LIST_SORT_VALUES.includes(sort)) {
267
+ (0, messages_1.printErrorWithHelp)(`Sort "${sort}" is not supported for assets.`, ['Use one of: recent, likes, downloads, remixes, comments.'], { command: 'browse' });
268
+ process.exitCode = 1;
269
+ return null;
270
+ }
271
+ return sort ?? undefined;
272
+ }
273
+ function parseBrowseLimitsAndFilters(options) {
198
274
  const assetCategory = parseAssetCategory(options.assetCategory);
199
275
  if (assetCategory === null) {
200
276
  (0, messages_1.printErrorWithHelp)(`Asset category "${options.assetCategory}" is not supported.`, ['Use a canonical asset category like IMAGE, VIDEO, AUDIO, SPRITESHEET, or MODEL_3D.'], { command: 'browse' });
201
277
  process.exitCode = 1;
202
- return;
278
+ return null;
203
279
  }
204
280
  const assetSubcategory = parseAssetSubcategory(options.assetSubcategory);
205
281
  if (assetSubcategory === null) {
206
282
  (0, messages_1.printErrorWithHelp)(`Asset subcategory "${options.assetSubcategory}" is invalid.`, ['Use lowercase slug values like generic, avatar, music, or sfx.'], { command: 'browse' });
207
283
  process.exitCode = 1;
208
- return;
284
+ return null;
209
285
  }
210
286
  const limit = parsePositiveInteger(options.limit, 'limit', 10);
211
287
  if (limit === null) {
212
288
  (0, messages_1.printErrorWithHelp)('The --limit value must be a positive integer.', ['Example: --limit 10'], { command: 'browse' });
213
289
  process.exitCode = 1;
214
- return;
290
+ return null;
215
291
  }
216
292
  const offset = parsePositiveInteger(options.offset, 'offset', 0);
217
293
  if (offset === null) {
218
294
  (0, messages_1.printErrorWithHelp)('The --offset value must be zero or a positive integer.', ['Example: --offset 20'], { command: 'browse' });
219
295
  process.exitCode = 1;
296
+ return null;
297
+ }
298
+ return {
299
+ assetCategory: assetCategory ?? undefined,
300
+ assetSubcategory: assetSubcategory ?? undefined,
301
+ limit,
302
+ offset,
303
+ };
304
+ }
305
+ function parseBrowseCommandOptions(options) {
306
+ const kind = parseBrowseKind(options.kind);
307
+ if (!kind) {
308
+ (0, messages_1.printErrorWithHelp)(`Kind "${options.kind ?? ''}" is not supported.`, ['Use one of: app, asset, asset-pack, asset-spec, all.'], { command: 'browse' });
309
+ process.exitCode = 1;
310
+ return { ok: false };
311
+ }
312
+ const creator = parseBrowseCreator(options.creator);
313
+ if (!creator) {
314
+ (0, messages_1.printErrorWithHelp)('The --creator value is invalid.', ['Use all, me, or a creator slug without path separators.'], { command: 'browse' });
315
+ process.exitCode = 1;
316
+ return { ok: false };
317
+ }
318
+ const appType = options.appType ? (0, types_1.parseAppType)(options.appType.trim().toUpperCase()) : undefined;
319
+ if (options.appType !== undefined && !appType) {
320
+ (0, messages_1.printErrorWithHelp)(`App type "${options.appType}" is not supported.`, ['Use one of: game, demo, tool, template.'], { command: 'browse' });
321
+ process.exitCode = 1;
322
+ return { ok: false };
323
+ }
324
+ const browseFilters = parseBrowseLimitsAndFilters(options);
325
+ if (!browseFilters) {
326
+ return { ok: false };
327
+ }
328
+ const sort = parseBrowseSortOption(options.sort, kind);
329
+ if (sort === null) {
330
+ return { ok: false };
331
+ }
332
+ const tags = normalizeTagFilters(options.tag);
333
+ if (tags === null) {
334
+ (0, messages_1.printErrorWithHelp)('The --tag value is invalid.', ['Use canonical refs like theme/pirate or visual-style/pixel-art.', 'Pass at most 5 unique tag refs.'], { command: 'browse' });
335
+ process.exitCode = 1;
336
+ return { ok: false };
337
+ }
338
+ return {
339
+ ok: true,
340
+ input: {
341
+ kind,
342
+ creator,
343
+ appType: appType ?? undefined,
344
+ ...browseFilters,
345
+ assetSpec: normalizeOptionalOption(options.assetSpec),
346
+ assetSpecOwner: normalizeOptionalOption(options.assetSpecOwner),
347
+ assetSpecName: normalizeOptionalOption(options.assetSpecName),
348
+ sort,
349
+ tags,
350
+ },
351
+ };
352
+ }
353
+ async function loadMergedBrowseResults(input) {
354
+ const { client, creator, creatorUsername, appType, assetCategory, assetSubcategory, assetSpec, assetSpecOwner, assetSpecName, limit, offset, tags, } = input;
355
+ const appResultsPromise = creatorUsername
356
+ ? collectPaginatedItems((pageLimit, pageOffset) => client.fetchAppsByCreator(creatorUsername, { limit: pageLimit, offset: pageOffset, type: appType, ...(tags.length > 0 ? { tags } : {}) })
357
+ .then((response) => ({
358
+ items: response.apps ?? [],
359
+ pagination: requirePagination(response.pagination, 'browse_all_creator_apps'),
360
+ })))
361
+ : (appType
362
+ ? collectPaginatedItems((pageLimit, pageOffset) => client.fetchAppsByType(appType, { limit: pageLimit, offset: pageOffset, ...(tags.length > 0 ? { tags } : {}) }).then((response) => ({
363
+ items: response.apps ?? [],
364
+ pagination: requirePagination(response.pagination, 'browse_all_type_apps'),
365
+ })))
366
+ : collectPaginatedItems((pageLimit, pageOffset) => client.fetchApps({ limit: pageLimit, offset: pageOffset, ...(tags.length > 0 ? { tags } : {}) }).then((response) => ({
367
+ items: response.apps ?? [],
368
+ pagination: requirePagination(response.pagination, 'browse_all_apps'),
369
+ }))));
370
+ const assetResultsPromise = creatorUsername
371
+ ? collectPaginatedItems((pageLimit, pageOffset) => client.listAssetsForCreator(creatorUsername, {
372
+ limit: pageLimit,
373
+ offset: pageOffset,
374
+ category: assetCategory,
375
+ subcategory: assetSubcategory,
376
+ assetSpec,
377
+ assetSpecOwner,
378
+ assetSpecName,
379
+ ...(tags.length > 0 ? { tags } : {}),
380
+ }).then((response) => ({
381
+ items: response.assets ?? [],
382
+ pagination: requirePagination(response.pagination, 'browse_all_creator_assets'),
383
+ })))
384
+ : collectPaginatedItems((pageLimit, pageOffset) => client.listAssets({
385
+ limit: pageLimit,
386
+ offset: pageOffset,
387
+ category: assetCategory,
388
+ subcategory: assetSubcategory,
389
+ assetSpec,
390
+ assetSpecOwner,
391
+ assetSpecName,
392
+ ...(tags.length > 0 ? { tags } : {}),
393
+ }).then((response) => ({
394
+ items: response.assets ?? [],
395
+ pagination: requirePagination(response.pagination, 'browse_all_assets'),
396
+ })));
397
+ const specResultsPromise = creatorUsername
398
+ ? collectPaginatedItems((pageLimit, pageOffset) => client.listAssetSpecsForCreator(creatorUsername, { limit: pageLimit, offset: pageOffset }).then((response) => ({
399
+ items: response.assetSpecs ?? [],
400
+ pagination: requirePagination(response.pagination, 'browse_all_creator_specs'),
401
+ })))
402
+ : collectPaginatedItems((pageLimit, pageOffset) => client.listAssetSpecs({ limit: pageLimit, offset: pageOffset }).then((response) => ({
403
+ items: response.assetSpecs ?? [],
404
+ pagination: requirePagination(response.pagination, 'browse_all_specs'),
405
+ })));
406
+ const packResultsPromise = creatorUsername
407
+ ? collectPaginatedItems((pageLimit, pageOffset) => client.listAssetPacksForCreator(creatorUsername, {
408
+ limit: pageLimit,
409
+ offset: pageOffset,
410
+ containsCategory: assetCategory,
411
+ containsSubcategory: assetSubcategory,
412
+ ...(tags.length > 0 ? { tags } : {}),
413
+ }).then((response) => ({
414
+ items: response.packs ?? [],
415
+ pagination: requirePagination(response.pagination, 'browse_all_creator_packs'),
416
+ })))
417
+ : collectPaginatedItems((pageLimit, pageOffset) => client.listAssetPacks({
418
+ limit: pageLimit,
419
+ offset: pageOffset,
420
+ containsCategory: assetCategory,
421
+ containsSubcategory: assetSubcategory,
422
+ ...(tags.length > 0 ? { tags } : {}),
423
+ }).then((response) => ({
424
+ items: response.packs ?? [],
425
+ pagination: requirePagination(response.pagination, 'browse_all_packs'),
426
+ })));
427
+ const [apps, assets, specs, packs] = await Promise.all([
428
+ appResultsPromise,
429
+ assetResultsPromise,
430
+ specResultsPromise,
431
+ packResultsPromise,
432
+ ]);
433
+ const combined = [
434
+ ...apps.items.map((item) => toBrowseItem('app', item)),
435
+ ...assets.items.map((item) => toBrowseItem('asset', item)),
436
+ ...specs.items.map((item) => toBrowseItem('asset-spec', item)),
437
+ ...packs.items.map((item) => toBrowseItem('asset-pack', item)),
438
+ ];
439
+ return sliceMergedItems(combined, limit, offset);
440
+ }
441
+ async function runAssetBrowseCommand(input) {
442
+ const { client, creatorUsername, assetCategory, assetSubcategory, assetSpec, assetSpecOwner, assetSpecName, sort, limit, offset, tags } = input;
443
+ const response = creatorUsername
444
+ ? await client.listAssetsForCreator(creatorUsername, {
445
+ limit,
446
+ offset,
447
+ category: assetCategory,
448
+ subcategory: assetSubcategory,
449
+ assetSpec,
450
+ assetSpecOwner,
451
+ assetSpecName,
452
+ ...(tags.length > 0 ? { tags } : {}),
453
+ ...(sort ? { sort: sort } : {}),
454
+ })
455
+ : await client.listAssets({
456
+ limit,
457
+ offset,
458
+ category: assetCategory,
459
+ subcategory: assetSubcategory,
460
+ assetSpec,
461
+ assetSpecOwner,
462
+ assetSpecName,
463
+ ...(tags.length > 0 ? { tags } : {}),
464
+ ...(sort ? { sort: sort } : {}),
465
+ });
466
+ return {
467
+ items: (response.assets ?? []).map((item) => toBrowseItem('asset', item)),
468
+ pagination: requirePagination(response.pagination, 'browse_assets'),
469
+ };
470
+ }
471
+ async function runAssetSpecBrowseCommand(input) {
472
+ const { client, creatorUsername, sort, limit, offset } = input;
473
+ const response = creatorUsername
474
+ ? await client.listAssetSpecsForCreator(creatorUsername, { limit, offset, ...(sort ? { sort: sort } : {}) })
475
+ : await client.listAssetSpecs({ limit, offset, ...(sort ? { sort: sort } : {}) });
476
+ return {
477
+ items: (response.assetSpecs ?? []).map((item) => toBrowseItem('asset-spec', item)),
478
+ pagination: requirePagination(response.pagination, 'browse_asset_specs'),
479
+ };
480
+ }
481
+ async function runAssetPackBrowseCommand(input) {
482
+ const { client, creatorUsername, assetCategory, assetSubcategory, limit, offset, tags } = input;
483
+ const response = creatorUsername
484
+ ? await client.listAssetPacksForCreator(creatorUsername, {
485
+ limit,
486
+ offset,
487
+ containsCategory: assetCategory,
488
+ containsSubcategory: assetSubcategory,
489
+ ...(tags.length > 0 ? { tags } : {}),
490
+ })
491
+ : await client.listAssetPacks({
492
+ limit,
493
+ offset,
494
+ containsCategory: assetCategory,
495
+ containsSubcategory: assetSubcategory,
496
+ ...(tags.length > 0 ? { tags } : {}),
497
+ });
498
+ return {
499
+ items: (response.packs ?? []).map((item) => toBrowseItem('asset-pack', item)),
500
+ pagination: requirePagination(response.pagination, 'browse_asset_packs'),
501
+ };
502
+ }
503
+ async function runBrowseCommand(input) {
504
+ const { client, kind, creator, creatorUsername, appType, limit, offset, tags, } = input;
505
+ if (kind === 'app') {
506
+ if (creatorUsername) {
507
+ const response = await client.fetchAppsByCreator(creatorUsername, { limit, offset, type: appType, ...(tags.length > 0 ? { tags } : {}) });
508
+ return {
509
+ items: (response.apps ?? []).map((item) => toBrowseItem('app', item)),
510
+ pagination: requirePagination(response.pagination, 'browse_app_creator'),
511
+ };
512
+ }
513
+ if (appType) {
514
+ const response = await client.fetchAppsByType(appType, { limit, offset, ...(tags.length > 0 ? { tags } : {}) });
515
+ return {
516
+ items: (response.apps ?? []).map((item) => toBrowseItem('app', item)),
517
+ pagination: requirePagination(response.pagination, 'browse_app_type'),
518
+ };
519
+ }
520
+ const response = await client.fetchApps({ limit, offset, ...(tags.length > 0 ? { tags } : {}) });
521
+ return {
522
+ items: (response.apps ?? []).map((item) => toBrowseItem('app', item)),
523
+ pagination: requirePagination(response.pagination, 'browse_app_all'),
524
+ };
525
+ }
526
+ if (kind === 'asset') {
527
+ return await runAssetBrowseCommand(input);
528
+ }
529
+ if (kind === 'asset-spec') {
530
+ return await runAssetSpecBrowseCommand(input);
531
+ }
532
+ if (kind === 'asset-pack') {
533
+ return await runAssetPackBrowseCommand(input);
534
+ }
535
+ return await loadMergedBrowseResults(input);
536
+ }
537
+ async function browse(options = {}) {
538
+ const parsed = parseBrowseCommandOptions(options);
539
+ if (!parsed.ok) {
220
540
  return;
221
541
  }
542
+ const { kind, creator, appType, assetCategory, assetSubcategory, assetSpec, assetSpecOwner, assetSpecName, sort, limit, offset, tags } = parsed.input;
222
543
  await (0, commandContext_1.withPublicEnvironment)('browse', async ({ client }) => {
223
544
  try {
224
545
  const creatorUsername = await resolveCreatorUsername(creator, 'browse', async () => {
@@ -232,108 +553,22 @@ async function browse(options = {}) {
232
553
  if (process.exitCode) {
233
554
  return;
234
555
  }
235
- const items = [];
236
- let pagination;
237
- if (kind === 'app') {
238
- if (creatorUsername) {
239
- const response = creator === 'me'
240
- ? await client.fetchMyApps({ limit, offset, type: appType })
241
- : await client.fetchAppsByCreator(creatorUsername, { limit, offset, type: appType });
242
- items.push(...(response.apps ?? []).map((item) => toBrowseItem('app', item)));
243
- pagination = requirePagination(response.pagination, 'browse_app_creator');
244
- }
245
- else if (appType) {
246
- const response = await client.fetchAppsByType(appType, { limit, offset });
247
- items.push(...(response.apps ?? []).map((item) => toBrowseItem('app', item)));
248
- pagination = requirePagination(response.pagination, 'browse_app_type');
249
- }
250
- else {
251
- const response = await client.fetchApps({ limit, offset });
252
- items.push(...(response.apps ?? []).map((item) => toBrowseItem('app', item)));
253
- pagination = requirePagination(response.pagination, 'browse_app_all');
254
- }
255
- }
256
- else if (kind === 'asset') {
257
- const response = creatorUsername
258
- ? await client.listAssetsForCreator(creatorUsername, { limit, offset, category: assetCategory, subcategory: assetSubcategory })
259
- : await client.listAssets({ limit, offset, category: assetCategory, subcategory: assetSubcategory });
260
- items.push(...(response.assets ?? []).map((item) => toBrowseItem('asset', item)));
261
- pagination = response.pagination;
262
- }
263
- else if (kind === 'asset-pack') {
264
- const response = creatorUsername
265
- ? await client.listAssetPacksForCreator(creatorUsername, { limit, offset, containsCategory: assetCategory, containsSubcategory: assetSubcategory })
266
- : await client.listAssetPacks({ limit, offset, containsCategory: assetCategory, containsSubcategory: assetSubcategory });
267
- items.push(...(response.packs ?? []).map((item) => toBrowseItem('asset-pack', item)));
268
- pagination = response.pagination;
269
- }
270
- else {
271
- const [apps, assets, packs] = await Promise.all([
272
- creatorUsername
273
- ? collectPaginatedItems((pageLimit, pageOffset) => (creator === 'me'
274
- ? client.fetchMyApps({ limit: pageLimit, offset: pageOffset, type: appType })
275
- : client.fetchAppsByCreator(creatorUsername, { limit: pageLimit, offset: pageOffset, type: appType }))
276
- .then((response) => ({
277
- items: response.apps ?? [],
278
- pagination: requirePagination(response.pagination, 'browse_all_creator_apps'),
279
- })))
280
- : (appType
281
- ? collectPaginatedItems((pageLimit, pageOffset) => client.fetchAppsByType(appType, { limit: pageLimit, offset: pageOffset }).then((response) => ({
282
- items: response.apps ?? [],
283
- pagination: requirePagination(response.pagination, 'browse_all_type_apps'),
284
- })))
285
- : collectPaginatedItems((pageLimit, pageOffset) => client.fetchApps({ limit: pageLimit, offset: pageOffset }).then((response) => ({
286
- items: response.apps ?? [],
287
- pagination: requirePagination(response.pagination, 'browse_all_apps'),
288
- })))),
289
- creatorUsername
290
- ? collectPaginatedItems((pageLimit, pageOffset) => client.listAssetsForCreator(creatorUsername, {
291
- limit: pageLimit,
292
- offset: pageOffset,
293
- category: assetCategory,
294
- subcategory: assetSubcategory,
295
- }).then((response) => ({
296
- items: response.assets ?? [],
297
- pagination: response.pagination,
298
- })))
299
- : collectPaginatedItems((pageLimit, pageOffset) => client.listAssets({
300
- limit: pageLimit,
301
- offset: pageOffset,
302
- category: assetCategory,
303
- subcategory: assetSubcategory,
304
- }).then((response) => ({
305
- items: response.assets ?? [],
306
- pagination: response.pagination,
307
- }))),
308
- creatorUsername
309
- ? collectPaginatedItems((pageLimit, pageOffset) => client.listAssetPacksForCreator(creatorUsername, {
310
- limit: pageLimit,
311
- offset: pageOffset,
312
- containsCategory: assetCategory,
313
- containsSubcategory: assetSubcategory,
314
- }).then((response) => ({
315
- items: response.packs ?? [],
316
- pagination: response.pagination,
317
- })))
318
- : collectPaginatedItems((pageLimit, pageOffset) => client.listAssetPacks({
319
- limit: pageLimit,
320
- offset: pageOffset,
321
- containsCategory: assetCategory,
322
- containsSubcategory: assetSubcategory,
323
- }).then((response) => ({
324
- items: response.packs ?? [],
325
- pagination: response.pagination,
326
- }))),
327
- ]);
328
- const combined = [
329
- ...apps.items.map((item) => toBrowseItem('app', item)),
330
- ...assets.items.map((item) => toBrowseItem('asset', item)),
331
- ...packs.items.map((item) => toBrowseItem('asset-pack', item)),
332
- ];
333
- const sliced = sliceMergedItems(combined, limit, offset);
334
- items.push(...sliced.items);
335
- pagination = sliced.pagination;
336
- }
556
+ const { items, pagination } = await runBrowseCommand({
557
+ client,
558
+ kind,
559
+ creator,
560
+ creatorUsername,
561
+ appType,
562
+ assetCategory,
563
+ assetSubcategory,
564
+ assetSpec,
565
+ assetSpecOwner,
566
+ assetSpecName,
567
+ sort,
568
+ limit,
569
+ offset,
570
+ tags,
571
+ });
337
572
  if (options.json) {
338
573
  (0, output_1.printJson)({
339
574
  items,
@@ -348,20 +583,26 @@ async function browse(options = {}) {
348
583
  return;
349
584
  }
350
585
  if (kind === 'all') {
351
- console.log('Browsing content (apps, then assets, then asset packs):\n');
586
+ console.log('Browsing content (apps, assets, asset specs, then asset packs):\n');
352
587
  }
353
588
  else {
354
589
  console.log(`Browsing ${(0, refs_1.contentKindPluralLabel)(kind)}:\n`);
355
590
  }
356
591
  for (const [index, item] of items.entries()) {
357
592
  const displayName = item.item.displayName || item.item.name;
358
- console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${displayName} | @${item.item.creatorUsername} | ${itemSummary(item)}${buildCountsSuffix(item)}`);
593
+ console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${displayName} | @${item.item.creatorUsername} | ${itemSummary(item)}${buildCountsSuffix(item)}${buildTagSuffix(item.item.tags)}`);
359
594
  }
360
595
  console.log('\nNext: run "playdrop detail <creator>/<kind>/<name>" to inspect one item.');
361
596
  }
362
597
  catch (error) {
363
598
  const handled = (0, errors_1.handleCommandFailure)(error, 'browse', 'Browse', {
364
599
  apiMessage: (apiError) => {
600
+ if (apiError.code === 'invalid_tag_ref' || apiError.code === 'duplicate_tag_ref' || apiError.code === 'tag_filter_limit_exceeded') {
601
+ return {
602
+ problem: 'One or more tag filters are invalid.',
603
+ suggestions: ['Use canonical refs like theme/pirate.', 'Pass at most 5 unique --tag values.'],
604
+ };
605
+ }
365
606
  if (apiError.status === 401 || apiError.status === 403) {
366
607
  return {
367
608
  problem: 'Browsing this content requires you to be logged in.',
@@ -0,0 +1,53 @@
1
+ type CaptureListingOptions = {
2
+ app?: string;
3
+ duration?: string | number;
4
+ width?: string | number;
5
+ height?: string | number;
6
+ fps?: string | number;
7
+ posterAt?: string | number;
8
+ audio?: boolean;
9
+ outputDir?: string;
10
+ keepRaw?: boolean;
11
+ };
12
+ type ParsedCaptureListingOptions = {
13
+ targetArg?: string;
14
+ appName?: string;
15
+ durationSeconds: number;
16
+ width: number;
17
+ height: number;
18
+ fps: number;
19
+ posterAtSeconds: number;
20
+ audio: boolean;
21
+ outputDir: string | null;
22
+ keepRaw: boolean;
23
+ };
24
+ type CaptureRect = {
25
+ x: number;
26
+ y: number;
27
+ width: number;
28
+ height: number;
29
+ top: number;
30
+ left: number;
31
+ right: number;
32
+ bottom: number;
33
+ };
34
+ type HostedGameMeasurement = {
35
+ outerWidth: number;
36
+ outerHeight: number;
37
+ innerWidth: number;
38
+ innerHeight: number;
39
+ devicePixelRatio: number;
40
+ iframeRect: CaptureRect;
41
+ };
42
+ type CropRect = {
43
+ x: number;
44
+ y: number;
45
+ width: number;
46
+ height: number;
47
+ };
48
+ export declare function parseCaptureListingOptions(targetArg: string | undefined, options?: CaptureListingOptions): ParsedCaptureListingOptions;
49
+ export declare function assertSupportedListingEnvironment(platform?: NodeJS.Platform, macosVersion?: string): void;
50
+ export declare function resolveListingRecorderPath(baseDir?: string): string;
51
+ export declare function computeRecordedCrop(measurement: HostedGameMeasurement, rawWidth: number, rawHeight: number): CropRect;
52
+ export declare function captureListing(targetArg: string | undefined, options?: CaptureListingOptions): Promise<void>;
53
+ export {};