@playdrop/playdrop-cli 0.3.4-build.1 → 0.3.5-build.1

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 (141) hide show
  1. package/README.md +60 -23
  2. package/config/client-meta.json +5 -5
  3. package/dist/apps/upload.js +5 -3
  4. package/dist/assets/model-artifacts.js +1 -1
  5. package/dist/catalogue.d.ts +6 -0
  6. package/dist/catalogue.js +38 -1
  7. package/dist/commandContext.d.ts +1 -0
  8. package/dist/commandContext.js +45 -15
  9. package/dist/commands/browse.d.ts +16 -0
  10. package/dist/commands/browse.js +370 -0
  11. package/dist/commands/build.js +4 -9
  12. package/dist/commands/capture.js +24 -24
  13. package/dist/commands/captureRemote.d.ts +11 -0
  14. package/dist/commands/captureRemote.js +90 -0
  15. package/dist/commands/comments.d.ts +14 -0
  16. package/dist/commands/comments.js +189 -0
  17. package/dist/commands/create.js +112 -72
  18. package/dist/commands/creations.d.ts +49 -0
  19. package/dist/commands/creations.js +657 -0
  20. package/dist/commands/credits.d.ts +10 -0
  21. package/dist/commands/credits.js +91 -0
  22. package/dist/commands/detail.d.ts +2 -2
  23. package/dist/commands/detail.js +148 -290
  24. package/dist/commands/dev.js +24 -24
  25. package/dist/commands/devShared.js +2 -2
  26. package/dist/commands/documentation.d.ts +4 -1
  27. package/dist/commands/documentation.js +79 -104
  28. package/dist/commands/feedback.d.ts +12 -9
  29. package/dist/commands/feedback.js +125 -257
  30. package/dist/commands/format.js +6 -13
  31. package/dist/commands/generation.d.ts +11 -0
  32. package/dist/commands/generation.js +204 -42
  33. package/dist/commands/gettingStarted.d.ts +1 -0
  34. package/dist/commands/gettingStarted.js +26 -0
  35. package/dist/commands/init.js +26 -24
  36. package/dist/commands/login.js +9 -8
  37. package/dist/commands/logout.js +2 -1
  38. package/dist/commands/notifications.d.ts +14 -0
  39. package/dist/commands/notifications.js +179 -0
  40. package/dist/commands/search.d.ts +13 -0
  41. package/dist/commands/search.js +198 -0
  42. package/dist/commands/upload.js +20 -17
  43. package/dist/commands/validate.js +15 -1
  44. package/dist/commands/versionsBrowse.d.ts +7 -0
  45. package/dist/commands/versionsBrowse.js +209 -0
  46. package/dist/commands/whoami.js +9 -8
  47. package/dist/errors.d.ts +9 -0
  48. package/dist/errors.js +52 -0
  49. package/dist/externalAssetPackValidation.d.ts +2 -0
  50. package/dist/externalAssetPackValidation.js +115 -0
  51. package/dist/http.js +1 -1
  52. package/dist/index.js +570 -630
  53. package/dist/messages.js +11 -11
  54. package/dist/output.d.ts +5 -0
  55. package/dist/output.js +45 -0
  56. package/dist/playwright.js +1 -1
  57. package/dist/refs.d.ts +18 -0
  58. package/dist/refs.js +105 -0
  59. package/node_modules/@playdrop/ai-client/dist/index.d.ts +42 -15
  60. package/node_modules/@playdrop/ai-client/dist/index.d.ts.map +1 -1
  61. package/node_modules/@playdrop/ai-client/package.json +1 -0
  62. package/node_modules/@playdrop/api-client/dist/client.d.ts +39 -27
  63. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  64. package/node_modules/@playdrop/api-client/dist/client.js +280 -1669
  65. package/node_modules/@playdrop/api-client/dist/core/errors.d.ts +9 -0
  66. package/node_modules/@playdrop/api-client/dist/core/errors.d.ts.map +1 -0
  67. package/node_modules/@playdrop/api-client/dist/core/errors.js +46 -0
  68. package/node_modules/@playdrop/api-client/dist/core/request.d.ts +27 -0
  69. package/node_modules/@playdrop/api-client/dist/core/request.d.ts.map +1 -0
  70. package/node_modules/@playdrop/api-client/dist/core/request.js +122 -0
  71. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +75 -0
  72. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -0
  73. package/node_modules/@playdrop/api-client/dist/domains/admin.js +282 -0
  74. package/node_modules/@playdrop/api-client/dist/domains/ai.d.ts +22 -0
  75. package/node_modules/@playdrop/api-client/dist/domains/ai.d.ts.map +1 -0
  76. package/node_modules/@playdrop/api-client/dist/domains/ai.js +15 -0
  77. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +60 -0
  78. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -0
  79. package/node_modules/@playdrop/api-client/dist/domains/apps.js +301 -0
  80. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +59 -0
  81. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -0
  82. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +297 -0
  83. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +62 -0
  84. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -0
  85. package/node_modules/@playdrop/api-client/dist/domains/assets.js +297 -0
  86. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +28 -0
  87. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -0
  88. package/node_modules/@playdrop/api-client/dist/domains/auth.js +78 -0
  89. package/node_modules/@playdrop/api-client/dist/domains/comments.d.ts +29 -0
  90. package/node_modules/@playdrop/api-client/dist/domains/comments.d.ts.map +1 -0
  91. package/node_modules/@playdrop/api-client/dist/domains/comments.js +65 -0
  92. package/node_modules/@playdrop/api-client/dist/domains/me.d.ts +24 -0
  93. package/node_modules/@playdrop/api-client/dist/domains/me.d.ts.map +1 -0
  94. package/node_modules/@playdrop/api-client/dist/domains/me.js +35 -0
  95. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +37 -0
  96. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -0
  97. package/node_modules/@playdrop/api-client/dist/domains/payments.js +148 -0
  98. package/node_modules/@playdrop/api-client/dist/domains/search.d.ts +27 -0
  99. package/node_modules/@playdrop/api-client/dist/domains/search.d.ts.map +1 -0
  100. package/node_modules/@playdrop/api-client/dist/domains/search.js +65 -0
  101. package/node_modules/@playdrop/api-client/dist/index.d.ts +33 -56
  102. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  103. package/node_modules/@playdrop/api-client/dist/index.js +103 -44
  104. package/node_modules/@playdrop/api-client/package.json +3 -2
  105. package/node_modules/@playdrop/boxel-core/package.json +1 -1
  106. package/node_modules/@playdrop/boxel-three/dist/test/glb-skinned.test.js +1 -1
  107. package/node_modules/@playdrop/boxel-three/dist/test/instantiate.test.js +1 -1
  108. package/node_modules/@playdrop/boxel-three/dist/test/skinned-mesh.test.js +1 -1
  109. package/node_modules/@playdrop/boxel-three/package.json +2 -1
  110. package/node_modules/@playdrop/config/client-meta.json +5 -5
  111. package/node_modules/@playdrop/config/dist/src/constants.d.ts +5 -0
  112. package/node_modules/@playdrop/config/dist/src/constants.d.ts.map +1 -1
  113. package/node_modules/@playdrop/config/dist/src/constants.js +5 -1
  114. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  115. package/node_modules/@playdrop/config/package.json +1 -1
  116. package/node_modules/@playdrop/types/dist/api.d.ts +178 -17
  117. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  118. package/node_modules/@playdrop/types/dist/api.js +30 -1
  119. package/node_modules/@playdrop/types/dist/app.d.ts +0 -14
  120. package/node_modules/@playdrop/types/dist/app.d.ts.map +1 -1
  121. package/node_modules/@playdrop/types/dist/app.js +0 -10
  122. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +11 -1
  123. package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
  124. package/node_modules/@playdrop/types/dist/asset.d.ts +65 -0
  125. package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
  126. package/node_modules/@playdrop/types/dist/realtime.d.ts +26 -26
  127. package/node_modules/@playdrop/types/dist/realtime.d.ts.map +1 -1
  128. package/node_modules/@playdrop/types/dist/version.d.ts +5 -0
  129. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  130. package/package.json +2 -3
  131. package/bin/playdrop-cli +0 -2
  132. package/dist/commands/asset-packs.d.ts +0 -27
  133. package/dist/commands/asset-packs.js +0 -508
  134. package/dist/commands/assets.d.ts +0 -35
  135. package/dist/commands/assets.js +0 -668
  136. package/dist/commands/list.d.ts +0 -7
  137. package/dist/commands/list.js +0 -347
  138. package/dist/commands/migrateCatalogueV2.d.ts +0 -1
  139. package/dist/commands/migrateCatalogueV2.js +0 -142
  140. package/dist/commands/versions.d.ts +0 -17
  141. package/dist/commands/versions.js +0 -384
@@ -0,0 +1,370 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.browse = browse;
4
+ exports.browseCategories = browseCategories;
5
+ const types_1 = require("@playdrop/types");
6
+ const commandContext_1 = require("../commandContext");
7
+ const errors_1 = require("../errors");
8
+ const messages_1 = require("../messages");
9
+ const output_1 = require("../output");
10
+ const refs_1 = require("../refs");
11
+ const MERGED_PAGE_SIZE = 100;
12
+ function parseBrowseKind(raw) {
13
+ if (!raw || raw.trim().length === 0) {
14
+ return 'all';
15
+ }
16
+ const normalized = raw.trim().toLowerCase();
17
+ if (normalized === 'app' || normalized === 'asset' || normalized === 'asset-pack' || normalized === 'all') {
18
+ return normalized;
19
+ }
20
+ return null;
21
+ }
22
+ function parseBrowseCreator(raw) {
23
+ if (!raw || raw.trim().length === 0) {
24
+ return 'all';
25
+ }
26
+ const normalized = raw.trim();
27
+ const lowered = normalized.toLowerCase();
28
+ if (lowered === 'all' || lowered === 'me') {
29
+ return lowered;
30
+ }
31
+ if (normalized.includes('/') || normalized.includes('\\') || normalized.includes('..')) {
32
+ return null;
33
+ }
34
+ return normalized;
35
+ }
36
+ function parsePositiveInteger(raw, label, fallback) {
37
+ if (raw === undefined) {
38
+ return fallback;
39
+ }
40
+ const parsed = typeof raw === 'number' ? raw : Number.parseInt(String(raw), 10);
41
+ if (!Number.isInteger(parsed) || parsed < 0) {
42
+ return null;
43
+ }
44
+ if (label === 'limit' && parsed <= 0) {
45
+ return null;
46
+ }
47
+ return parsed;
48
+ }
49
+ function parseAssetCategory(raw) {
50
+ if (!raw || raw.trim().length === 0) {
51
+ return undefined;
52
+ }
53
+ const normalized = raw.trim().toUpperCase().replace(/[-\s]/g, '_');
54
+ if (normalized === '3D' || normalized === 'MODEL3D') {
55
+ return 'MODEL_3D';
56
+ }
57
+ if (types_1.ASSET_CATEGORY_VALUES.includes(normalized)) {
58
+ return normalized;
59
+ }
60
+ return null;
61
+ }
62
+ function parseAssetSubcategory(raw) {
63
+ if (!raw || raw.trim().length === 0) {
64
+ return undefined;
65
+ }
66
+ const normalized = (0, types_1.normalizeAssetSubcategory)(raw);
67
+ return normalized || null;
68
+ }
69
+ function buildItemRef(kind, creator, name) {
70
+ return `${creator}/${kind}/${name}`;
71
+ }
72
+ function toBrowseItem(kind, item) {
73
+ return {
74
+ kind,
75
+ ref: buildItemRef(kind, item.creatorUsername, item.name),
76
+ item: item,
77
+ };
78
+ }
79
+ function itemSummary(item) {
80
+ if (item.kind === 'app') {
81
+ return item.item.type.toLowerCase();
82
+ }
83
+ if (item.kind === 'asset') {
84
+ const subcategory = item.item.currentVersion?.subcategory;
85
+ return subcategory ? `${item.item.category.toLowerCase()}/${subcategory}` : item.item.category.toLowerCase();
86
+ }
87
+ return 'asset-pack';
88
+ }
89
+ async function collectPaginatedItems(fetchPage) {
90
+ const items = [];
91
+ let offset = 0;
92
+ let lastPagination = null;
93
+ while (true) {
94
+ const page = await fetchPage(MERGED_PAGE_SIZE, offset);
95
+ items.push(...page.items);
96
+ lastPagination = page.pagination;
97
+ if (!page.pagination.hasMore) {
98
+ break;
99
+ }
100
+ if (page.pagination.limit <= 0) {
101
+ throw new Error('invalid_pagination_limit');
102
+ }
103
+ offset += page.pagination.limit;
104
+ }
105
+ return {
106
+ items,
107
+ pagination: lastPagination ?? { limit: MERGED_PAGE_SIZE, offset: 0, count: 0, hasMore: false },
108
+ };
109
+ }
110
+ function sliceMergedItems(items, limit, offset) {
111
+ const count = items.length;
112
+ return {
113
+ items: items.slice(offset, offset + limit),
114
+ pagination: {
115
+ limit,
116
+ offset,
117
+ count,
118
+ hasMore: offset + limit < count,
119
+ },
120
+ };
121
+ }
122
+ async function resolveCreatorUsername(creator, command, resolveMe) {
123
+ if (creator === 'all') {
124
+ return null;
125
+ }
126
+ if (creator === 'me') {
127
+ try {
128
+ return await resolveMe();
129
+ }
130
+ catch (error) {
131
+ if (error instanceof types_1.ApiError && (error.status === 401 || error.status === 403)) {
132
+ (0, messages_1.printLoginRequired)('Browsing your content', command);
133
+ process.exitCode = 1;
134
+ return null;
135
+ }
136
+ throw error;
137
+ }
138
+ }
139
+ return creator;
140
+ }
141
+ async function browse(options = {}) {
142
+ const kind = parseBrowseKind(options.kind);
143
+ if (!kind) {
144
+ (0, messages_1.printErrorWithHelp)(`Kind "${options.kind ?? ''}" is not supported.`, ['Use one of: app, asset, asset-pack, all.'], { command: 'browse' });
145
+ process.exitCode = 1;
146
+ return;
147
+ }
148
+ const creator = parseBrowseCreator(options.creator);
149
+ if (!creator) {
150
+ (0, messages_1.printErrorWithHelp)('The --creator value is invalid.', ['Use all, me, or a creator slug without path separators.'], { command: 'browse' });
151
+ process.exitCode = 1;
152
+ return;
153
+ }
154
+ const appType = options.appType ? (0, types_1.parseAppType)(options.appType.trim().toUpperCase()) : undefined;
155
+ if (options.appType !== undefined && !appType) {
156
+ (0, messages_1.printErrorWithHelp)(`App type "${options.appType}" is not supported.`, ['Use one of: game, demo, tool, template.'], { command: 'browse' });
157
+ process.exitCode = 1;
158
+ return;
159
+ }
160
+ const assetCategory = parseAssetCategory(options.assetCategory);
161
+ if (assetCategory === null) {
162
+ (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' });
163
+ process.exitCode = 1;
164
+ return;
165
+ }
166
+ const assetSubcategory = parseAssetSubcategory(options.assetSubcategory);
167
+ if (assetSubcategory === null) {
168
+ (0, messages_1.printErrorWithHelp)(`Asset subcategory "${options.assetSubcategory}" is invalid.`, ['Use lowercase slug values like generic, avatar, music, or sfx.'], { command: 'browse' });
169
+ process.exitCode = 1;
170
+ return;
171
+ }
172
+ const limit = parsePositiveInteger(options.limit, 'limit', 10);
173
+ if (limit === null) {
174
+ (0, messages_1.printErrorWithHelp)('The --limit value must be a positive integer.', ['Example: --limit 10'], { command: 'browse' });
175
+ process.exitCode = 1;
176
+ return;
177
+ }
178
+ const offset = parsePositiveInteger(options.offset, 'offset', 0);
179
+ if (offset === null) {
180
+ (0, messages_1.printErrorWithHelp)('The --offset value must be zero or a positive integer.', ['Example: --offset 20'], { command: 'browse' });
181
+ process.exitCode = 1;
182
+ return;
183
+ }
184
+ await (0, commandContext_1.withPublicEnvironment)('browse', async ({ client }) => {
185
+ try {
186
+ const creatorUsername = await resolveCreatorUsername(creator, 'browse', async () => {
187
+ const response = await client.me();
188
+ const username = response.user?.username?.trim();
189
+ if (!username) {
190
+ throw new Error('missing_current_user');
191
+ }
192
+ return username;
193
+ });
194
+ if (process.exitCode) {
195
+ return;
196
+ }
197
+ const items = [];
198
+ let pagination;
199
+ if (kind === 'app') {
200
+ let apps = [];
201
+ if (creatorUsername) {
202
+ if (creator === 'me') {
203
+ const response = await client.fetchMyApps();
204
+ apps = response.apps ?? [];
205
+ }
206
+ else {
207
+ const response = await client.fetchAppsByCreator(creatorUsername);
208
+ apps = response.apps ?? [];
209
+ }
210
+ }
211
+ else if (appType) {
212
+ const response = await client.fetchAppsByType(appType);
213
+ apps = response.apps ?? [];
214
+ }
215
+ else {
216
+ const response = await client.fetchApps();
217
+ apps = response.apps ?? [];
218
+ }
219
+ const filtered = appType ? apps.filter((app) => app.type === appType) : apps;
220
+ const sliced = sliceMergedItems(filtered, limit, offset);
221
+ items.push(...sliced.items.map((item) => toBrowseItem('app', item)));
222
+ pagination = sliced.pagination;
223
+ }
224
+ else if (kind === 'asset') {
225
+ const response = creatorUsername
226
+ ? await client.listAssetsForCreator(creatorUsername, { limit, offset, category: assetCategory, subcategory: assetSubcategory })
227
+ : await client.listAssets({ limit, offset, category: assetCategory, subcategory: assetSubcategory });
228
+ items.push(...(response.assets ?? []).map((item) => toBrowseItem('asset', item)));
229
+ pagination = response.pagination;
230
+ }
231
+ else if (kind === 'asset-pack') {
232
+ const response = creatorUsername
233
+ ? await client.listAssetPacksForCreator(creatorUsername, { limit, offset, containsCategory: assetCategory, containsSubcategory: assetSubcategory })
234
+ : await client.listAssetPacks({ limit, offset, containsCategory: assetCategory, containsSubcategory: assetSubcategory });
235
+ items.push(...(response.packs ?? []).map((item) => toBrowseItem('asset-pack', item)));
236
+ pagination = response.pagination;
237
+ }
238
+ else {
239
+ const [apps, assets, packs] = await Promise.all([
240
+ creatorUsername
241
+ ? (creator === 'me' ? client.fetchMyApps().then((response) => response.apps ?? []) : client.fetchAppsByCreator(creatorUsername).then((response) => response.apps ?? []))
242
+ : (appType ? client.fetchAppsByType(appType).then((response) => response.apps ?? []) : client.fetchApps().then((response) => response.apps ?? [])),
243
+ creatorUsername
244
+ ? collectPaginatedItems((pageLimit, pageOffset) => client.listAssetsForCreator(creatorUsername, {
245
+ limit: pageLimit,
246
+ offset: pageOffset,
247
+ category: assetCategory,
248
+ subcategory: assetSubcategory,
249
+ }).then((response) => ({
250
+ items: response.assets ?? [],
251
+ pagination: response.pagination,
252
+ })))
253
+ : collectPaginatedItems((pageLimit, pageOffset) => client.listAssets({
254
+ limit: pageLimit,
255
+ offset: pageOffset,
256
+ category: assetCategory,
257
+ subcategory: assetSubcategory,
258
+ }).then((response) => ({
259
+ items: response.assets ?? [],
260
+ pagination: response.pagination,
261
+ }))),
262
+ creatorUsername
263
+ ? collectPaginatedItems((pageLimit, pageOffset) => client.listAssetPacksForCreator(creatorUsername, {
264
+ limit: pageLimit,
265
+ offset: pageOffset,
266
+ containsCategory: assetCategory,
267
+ containsSubcategory: assetSubcategory,
268
+ }).then((response) => ({
269
+ items: response.packs ?? [],
270
+ pagination: response.pagination,
271
+ })))
272
+ : collectPaginatedItems((pageLimit, pageOffset) => client.listAssetPacks({
273
+ limit: pageLimit,
274
+ offset: pageOffset,
275
+ containsCategory: assetCategory,
276
+ containsSubcategory: assetSubcategory,
277
+ }).then((response) => ({
278
+ items: response.packs ?? [],
279
+ pagination: response.pagination,
280
+ }))),
281
+ ]);
282
+ const combined = [
283
+ ...(appType ? apps.filter((app) => app.type === appType) : apps).map((item) => toBrowseItem('app', item)),
284
+ ...assets.items.map((item) => toBrowseItem('asset', item)),
285
+ ...packs.items.map((item) => toBrowseItem('asset-pack', item)),
286
+ ];
287
+ const sliced = sliceMergedItems(combined, limit, offset);
288
+ items.push(...sliced.items);
289
+ pagination = sliced.pagination;
290
+ }
291
+ if (options.json) {
292
+ (0, output_1.printJson)({
293
+ items,
294
+ ...(pagination ? { pagination } : {}),
295
+ });
296
+ return;
297
+ }
298
+ if (items.length === 0) {
299
+ const label = kind === 'all' ? 'content' : (0, refs_1.contentKindPluralLabel)(kind);
300
+ console.log(`No ${label} found.`);
301
+ console.log('Next: adjust your filters or run "playdrop search <query>" to look for something specific.');
302
+ return;
303
+ }
304
+ if (kind === 'all') {
305
+ console.log('Browsing content (apps, then assets, then asset packs):\n');
306
+ }
307
+ else {
308
+ console.log(`Browsing ${(0, refs_1.contentKindPluralLabel)(kind)}:\n`);
309
+ }
310
+ for (const [index, item] of items.entries()) {
311
+ const displayName = item.item.displayName || item.item.name;
312
+ console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${displayName} | @${item.item.creatorUsername} | ${itemSummary(item)}`);
313
+ }
314
+ console.log('\nNext: run "playdrop detail <creator>/<kind>/<name>" to inspect one item.');
315
+ }
316
+ catch (error) {
317
+ const handled = (0, errors_1.handleCommandFailure)(error, 'browse', 'Browse', {
318
+ apiMessage: (apiError) => {
319
+ if (apiError.status === 401 || apiError.status === 403) {
320
+ return {
321
+ problem: 'Browsing this content requires you to be logged in.',
322
+ suggestions: ['Run "playdrop auth login" and retry.'],
323
+ };
324
+ }
325
+ return {
326
+ problem: `Browse failed with status ${apiError.status}.`,
327
+ suggestions: ['Retry in a moment.', 'Use "playdrop help browse" for usage details.'],
328
+ };
329
+ },
330
+ });
331
+ if (!handled) {
332
+ throw error;
333
+ }
334
+ }
335
+ });
336
+ }
337
+ async function browseCategories(options = {}) {
338
+ await (0, commandContext_1.withPublicEnvironment)('browse categories', async ({ client }) => {
339
+ try {
340
+ const response = await client.listAssetCategories();
341
+ const items = response.categories ?? [];
342
+ if (options.json) {
343
+ (0, output_1.printJson)({ items });
344
+ return;
345
+ }
346
+ if (items.length === 0) {
347
+ console.log('No asset categories were returned.');
348
+ return;
349
+ }
350
+ console.log('Asset categories:\n');
351
+ for (const item of items) {
352
+ const formats = Array.isArray(item.formats) ? item.formats.join(', ') : 'none';
353
+ console.log(`${item.category} / ${item.subcategory}`);
354
+ console.log(` Formats: ${formats}`);
355
+ }
356
+ console.log('\nNext: use these values with "playdrop browse" or "playdrop search".');
357
+ }
358
+ catch (error) {
359
+ const handled = (0, errors_1.handleCommandFailure)(error, 'browse categories', 'Browse categories', {
360
+ apiMessage: (apiError) => ({
361
+ problem: `Category lookup failed with status ${apiError.status}.`,
362
+ suggestions: ['Retry in a moment.'],
363
+ }),
364
+ });
365
+ if (!handled) {
366
+ throw error;
367
+ }
368
+ }
369
+ });
370
+ }
@@ -9,7 +9,7 @@ const model_artifacts_1 = require("../assets/model-artifacts");
9
9
  async function build(pathOrName) {
10
10
  const selection = (0, taskSelection_1.selectTasks)(pathOrName);
11
11
  if (selection.errors.length > 0) {
12
- (0, taskSelection_1.reportTaskErrors)(selection.errors, 'build');
12
+ (0, taskSelection_1.reportTaskErrors)(selection.errors, 'project build');
13
13
  process.exitCode = 1;
14
14
  return;
15
15
  }
@@ -23,8 +23,6 @@ async function build(pathOrName) {
23
23
  for (const task of (0, taskUtils_1.sortTasks)(tasks)) {
24
24
  const entityId = task.name;
25
25
  try {
26
- let skipped = false;
27
- let detail;
28
26
  if (task.kind === 'app') {
29
27
  await (0, apps_1.validateAppTask)(task);
30
28
  await (0, apps_1.buildApp)(task);
@@ -32,21 +30,18 @@ async function build(pathOrName) {
32
30
  else if (task.kind === 'asset' || task.kind === 'embedded-asset') {
33
31
  const generated = await (0, model_artifacts_1.prepareModel3DAssetArtifacts)(task);
34
32
  if (!generated) {
35
- skipped = true;
36
- detail = `no build step for ${task.kind}`;
33
+ throw new Error(`project build does not support ${task.kind} "${entityId}" because it has no local build step.`);
37
34
  }
38
35
  }
39
36
  else {
40
- skipped = true;
41
- detail = `no build step for ${task.kind}`;
37
+ throw new Error(`project build does not support ${task.kind} "${entityId}".`);
42
38
  }
43
39
  const entry = {
44
40
  action: 'build',
45
- status: skipped ? 'skipped' : 'success',
41
+ status: 'success',
46
42
  entityType: task.kind,
47
43
  entityId,
48
44
  catalogue: task.cataloguePath,
49
- detail,
50
45
  };
51
46
  results.push(entry);
52
47
  console.log((0, uploadLog_1.formatTaskLogLine)(entry));
@@ -193,7 +193,7 @@ async function capture(targetArg, options = {}) {
193
193
  minimumLogLevel = resolveLogLevel(options.logLevel);
194
194
  }
195
195
  catch (error) {
196
- (0, messages_1.printErrorWithHelp)(error?.message || 'Invalid log level.', [`Valid values: ${LOG_LEVEL_VALUES.join(', ')}`], { command: 'capture' });
196
+ (0, messages_1.printErrorWithHelp)(error?.message || 'Invalid log level.', [`Valid values: ${LOG_LEVEL_VALUES.join(', ')}`], { command: 'project capture' });
197
197
  process.exitCode = 1;
198
198
  return;
199
199
  }
@@ -202,19 +202,19 @@ async function capture(targetArg, options = {}) {
202
202
  : null;
203
203
  const cfg = (0, config_1.loadConfig)();
204
204
  if (!cfg.env) {
205
- (0, messages_1.printConfigEnvironmentMissing)('capture');
205
+ (0, messages_1.printConfigEnvironmentMissing)('project capture');
206
206
  process.exitCode = 1;
207
207
  return;
208
208
  }
209
209
  if (!cfg.token) {
210
- (0, messages_1.printLoginRequired)('Capturing app logs', 'capture');
210
+ (0, messages_1.printLoginRequired)('Capturing app logs', 'project capture');
211
211
  process.exitCode = 1;
212
212
  return;
213
213
  }
214
214
  const envConfig = (0, environment_1.resolveEnvironmentConfig)(cfg.env);
215
215
  if (!envConfig) {
216
216
  const choices = (0, environment_1.formatEnvironmentList)();
217
- (0, messages_1.printUnknownEnvironment)(cfg.env, choices, 'capture');
217
+ (0, messages_1.printUnknownEnvironment)(cfg.env, choices, 'project capture');
218
218
  process.exitCode = 1;
219
219
  return;
220
220
  }
@@ -239,7 +239,7 @@ async function capture(targetArg, options = {}) {
239
239
  suggestions = ['Confirm the app exists in catalogue.json, use "--app <name>", or pass the direct HTML file path.'];
240
240
  break;
241
241
  case 'catalogue_missing':
242
- suggestions = ['Run "playdrop-cli capture" from a workspace with catalogue.json or provide the HTML file path.'];
242
+ suggestions = ['Run "playdrop project capture" from a workspace with catalogue.json or provide the HTML file path.'];
243
243
  break;
244
244
  case 'invalid_target':
245
245
  suggestions = ['Provide a valid HTML file path or app name.'];
@@ -248,13 +248,13 @@ async function capture(targetArg, options = {}) {
248
248
  suggestions = ['Add at least one app entry to catalogue.json before running capture.'];
249
249
  break;
250
250
  case 'invalid_catalogue':
251
- suggestions = ['Fix the catalogue.json file and rerun "playdrop-cli capture".'];
251
+ suggestions = ['Fix the catalogue.json file and rerun "playdrop project capture".'];
252
252
  break;
253
253
  default:
254
254
  suggestions = ['Provide an app name, HTML file path, or run inside a Playdrop workspace.'];
255
255
  break;
256
256
  }
257
- (0, messages_1.printErrorWithHelp)(message, suggestions, { command: 'capture' });
257
+ (0, messages_1.printErrorWithHelp)(message, suggestions, { command: 'project capture' });
258
258
  process.exitCode = 1;
259
259
  return;
260
260
  }
@@ -263,19 +263,19 @@ async function capture(targetArg, options = {}) {
263
263
  if (!(0, node_fs_1.existsSync)(filePath) || !(0, node_fs_1.statSync)(filePath).isFile()) {
264
264
  (0, messages_1.printErrorWithHelp)(`App HTML was not found at ${filePath}.`, [
265
265
  'Ensure the catalogue entry points to a compiled HTML file or provide the correct file path.'
266
- ], { command: 'capture' });
266
+ ], { command: 'project capture' });
267
267
  process.exitCode = 1;
268
268
  return;
269
269
  }
270
270
  const availableSurfaces = resolvedTarget.surfaceTargets;
271
271
  const overrideSurface = normalizeSurfaceOverride(options.surfaceTarget);
272
272
  if (options.surfaceTarget && !overrideSurface) {
273
- (0, messages_1.printErrorWithHelp)(`Unsupported surface target "${options.surfaceTarget}".`, ['Valid values: desktop, mobile-landscape, mobile-portrait'], { command: 'capture' });
273
+ (0, messages_1.printErrorWithHelp)(`Unsupported surface target "${options.surfaceTarget}".`, ['Valid values: desktop, mobile-landscape, mobile-portrait'], { command: 'project capture' });
274
274
  process.exitCode = 1;
275
275
  return;
276
276
  }
277
277
  if (overrideSurface && !availableSurfaces.map[overrideSurface]) {
278
- (0, messages_1.printErrorWithHelp)(`Surface target "${options.surfaceTarget}" is not enabled for this app.`, [`Catalogue surfaces: ${formatSurfaceList(availableSurfaces)}`], { command: 'capture' });
278
+ (0, messages_1.printErrorWithHelp)(`Surface target "${options.surfaceTarget}" is not enabled for this app.`, [`Catalogue surfaces: ${formatSurfaceList(availableSurfaces)}`], { command: 'project capture' });
279
279
  process.exitCode = 1;
280
280
  return;
281
281
  }
@@ -301,8 +301,8 @@ async function capture(targetArg, options = {}) {
301
301
  if (resolvedTarget.cataloguePath) {
302
302
  (0, messages_1.printErrorWithHelp)(error?.message || 'Catalogue lookup failed.', [
303
303
  'Ensure the HTML file is listed exactly once in catalogue.json with a valid type.',
304
- 'Run "playdrop-cli create <name>" to scaffold a new entry if needed.',
305
- ], { command: 'capture' });
304
+ 'Run "playdrop project create app <name>" to scaffold a new entry if needed.',
305
+ ], { command: 'project capture' });
306
306
  process.exitCode = 1;
307
307
  return;
308
308
  }
@@ -324,22 +324,22 @@ async function capture(targetArg, options = {}) {
324
324
  }
325
325
  if (error instanceof types_1.ApiError) {
326
326
  (0, messages_1.printErrorWithHelp)(`Could not fetch your account (status ${error.status}).`, [
327
- 'Run "playdrop-cli login" to refresh your session and ensure the API is reachable.',
328
- 'Use "playdrop-cli whoami" afterwards to confirm your status.'
329
- ], { command: 'capture' });
327
+ 'Run "playdrop auth login" to refresh your session and ensure the API is reachable.',
328
+ 'Use "playdrop auth whoami" afterwards to confirm your status.'
329
+ ], { command: 'project capture' });
330
330
  process.exitCode = 1;
331
331
  return;
332
332
  }
333
333
  if ((0, devShared_1.isNetworkError)(error)) {
334
- (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to resolve your account.', 'capture');
334
+ (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to resolve your account.', 'project capture');
335
335
  process.exitCode = 1;
336
336
  return;
337
337
  }
338
338
  if (error instanceof Error && error.message === 'DEV_USERNAME_EMPTY') {
339
339
  (0, messages_1.printErrorWithHelp)('Could not determine your Playdrop creator username.', [
340
- 'Retry "playdrop-cli capture" in a moment.',
340
+ 'Retry "playdrop project capture" in a moment.',
341
341
  'If the problem persists, contact the Playdrop team.'
342
- ], { command: 'capture' });
342
+ ], { command: 'project capture' });
343
343
  process.exitCode = 1;
344
344
  return;
345
345
  }
@@ -360,21 +360,21 @@ async function capture(targetArg, options = {}) {
360
360
  if (error instanceof types_1.ApiError) {
361
361
  if (error.status === 404) {
362
362
  (0, messages_1.printErrorWithHelp)(`App ${currentUsername}/${appName} is not registered on ${cfg.env}.`, [
363
- `Run "playdrop-cli create ${appName}" to register the app before running capture.`,
363
+ `Run "playdrop project create app ${appName}" to register the app before running capture.`,
364
364
  'If you expected it to exist, ensure you are logged into the correct environment.'
365
- ], { command: 'capture' });
365
+ ], { command: 'project capture' });
366
366
  }
367
367
  else {
368
368
  (0, messages_1.printErrorWithHelp)(`Failed to verify app registration (status ${error.status}).`, [
369
369
  'Retry in a moment.',
370
370
  'If the issue persists, contact the Playdrop team.'
371
- ], { command: 'capture' });
371
+ ], { command: 'project capture' });
372
372
  }
373
373
  process.exitCode = 1;
374
374
  return;
375
375
  }
376
376
  if ((0, devShared_1.isNetworkError)(error)) {
377
- (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to verify the app registration.', 'capture');
377
+ (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to verify the app registration.', 'project capture');
378
378
  process.exitCode = 1;
379
379
  return;
380
380
  }
@@ -424,7 +424,7 @@ async function capture(targetArg, options = {}) {
424
424
  (0, messages_1.printErrorWithHelp)(error?.message || `Failed to start dev server on port ${portToUse}.`, [
425
425
  'Check if another process is already using the port.',
426
426
  'Ensure the HTML file exists and is readable.',
427
- ], { command: 'capture' });
427
+ ], { command: 'project capture' });
428
428
  process.exitCode = 1;
429
429
  return;
430
430
  }
@@ -652,7 +652,7 @@ async function capture(targetArg, options = {}) {
652
652
  }
653
653
  const message = error instanceof Error ? error.message : String(error);
654
654
  if (/browserType\.launch/i.test(message) || /MachPortRendezvousServer/i.test(message) || /Chromium/i.test(message)) {
655
- const launchError = (0, playwright_1.createPlaywrightLaunchError)('playdrop-cli capture', error);
655
+ const launchError = (0, playwright_1.createPlaywrightLaunchError)('playdrop project capture', error);
656
656
  console.error(launchError.message);
657
657
  }
658
658
  else if (error instanceof Error && error.stack) {
@@ -0,0 +1,11 @@
1
+ type CaptureRemoteOptions = {
2
+ timeout?: string | number;
3
+ screenshot?: string;
4
+ log?: string;
5
+ username?: string;
6
+ password?: string;
7
+ loginUrl?: string;
8
+ };
9
+ export declare function buildCaptureRemoteCommandArgs(url: string, options?: CaptureRemoteOptions): string[] | null;
10
+ export declare function captureRemote(url: string | undefined, options?: CaptureRemoteOptions): Promise<void>;
11
+ export {};
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildCaptureRemoteCommandArgs = buildCaptureRemoteCommandArgs;
7
+ exports.captureRemote = captureRemote;
8
+ const node_child_process_1 = require("node:child_process");
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const messages_1 = require("../messages");
11
+ function parseTimeout(raw) {
12
+ if (raw === undefined) {
13
+ return null;
14
+ }
15
+ const parsed = typeof raw === 'number' ? raw : Number.parseFloat(String(raw));
16
+ if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 600) {
17
+ return null;
18
+ }
19
+ return String(parsed);
20
+ }
21
+ function buildCaptureRemoteCommandArgs(url, options = {}) {
22
+ const trimmedUrl = typeof url === 'string' ? url.trim() : '';
23
+ if (!trimmedUrl) {
24
+ return null;
25
+ }
26
+ const args = [
27
+ node_path_1.default.resolve(__dirname, '../../scripts/capture-url.mjs'),
28
+ '--url',
29
+ trimmedUrl,
30
+ ];
31
+ const timeout = parseTimeout(options.timeout);
32
+ if (options.timeout !== undefined && timeout === null) {
33
+ throw new Error('invalid_timeout');
34
+ }
35
+ if (timeout) {
36
+ args.push('--timeout', timeout);
37
+ }
38
+ if (options.screenshot) {
39
+ args.push('--screenshot', options.screenshot);
40
+ }
41
+ if (options.log) {
42
+ args.push('--log', options.log);
43
+ }
44
+ const username = options.username?.trim() || '';
45
+ const password = options.password || '';
46
+ if ((username && !password) || (!username && password)) {
47
+ throw new Error('invalid_credentials_pair');
48
+ }
49
+ if (username) {
50
+ args.push('--username', username, '--password', password);
51
+ }
52
+ if (options.loginUrl) {
53
+ args.push('--login-url', options.loginUrl);
54
+ }
55
+ return args;
56
+ }
57
+ async function captureRemote(url, options = {}) {
58
+ let args;
59
+ try {
60
+ args = buildCaptureRemoteCommandArgs(url ?? '', options);
61
+ }
62
+ catch (error) {
63
+ if (error instanceof Error && error.message === 'invalid_timeout') {
64
+ (0, messages_1.printErrorWithHelp)('The --timeout value must be a number between 0 and 600 seconds.', [], { command: 'project capture remote' });
65
+ process.exitCode = 1;
66
+ return;
67
+ }
68
+ if (error instanceof Error && error.message === 'invalid_credentials_pair') {
69
+ (0, messages_1.printErrorWithHelp)('Use --username and --password together.', [], { command: 'project capture remote' });
70
+ process.exitCode = 1;
71
+ return;
72
+ }
73
+ throw error;
74
+ }
75
+ if (!args) {
76
+ (0, messages_1.printErrorWithHelp)('A URL is required.', ['Example: playdrop project capture remote https://www.playdrop.ai/creators/playdrop/apps/game/hangingout'], { command: 'project capture remote' });
77
+ process.exitCode = 1;
78
+ return;
79
+ }
80
+ const result = (0, node_child_process_1.spawnSync)(process.execPath, args, {
81
+ stdio: 'inherit',
82
+ });
83
+ if (typeof result.status === 'number' && result.status !== 0) {
84
+ process.exitCode = result.status;
85
+ return;
86
+ }
87
+ if (result.error) {
88
+ throw result.error;
89
+ }
90
+ }