@playdrop/playdrop-cli 0.3.8-build.2 → 0.3.9

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 (106) hide show
  1. package/config/client-meta.json +5 -5
  2. package/dist/apps/build.js +45 -39
  3. package/dist/catalogue-utils.js +23 -18
  4. package/dist/catalogue.d.ts +0 -2
  5. package/dist/catalogue.js +54 -41
  6. package/dist/clientInfo.js +16 -2
  7. package/dist/commands/browse.js +66 -23
  8. package/dist/commands/capture.js +3 -2
  9. package/dist/commands/create.js +49 -46
  10. package/dist/commands/createRemixContent.js +29 -44
  11. package/dist/commands/creations.js +51 -13
  12. package/dist/commands/devServer.d.ts +1 -1
  13. package/dist/commands/devShared.js +1 -1
  14. package/dist/commands/generation.js +91 -74
  15. package/dist/commands/gettingStarted.js +1 -1
  16. package/dist/commands/search.js +29 -1
  17. package/dist/commands/upload-content.d.ts +70 -0
  18. package/dist/commands/upload-content.js +627 -0
  19. package/dist/commands/upload-graph.d.ts +23 -0
  20. package/dist/commands/upload-graph.js +108 -0
  21. package/dist/commands/upload.js +264 -543
  22. package/dist/http.d.ts +1 -1
  23. package/dist/playwright.d.ts +12 -4
  24. package/dist/proxyFetch.js +3 -2
  25. package/node_modules/@playdrop/ai-client/dist/index.d.ts.map +1 -1
  26. package/node_modules/@playdrop/ai-client/dist/index.js +74 -54
  27. package/node_modules/@playdrop/api-client/dist/client.d.ts +34 -14
  28. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  29. package/node_modules/@playdrop/api-client/dist/client.js +6 -8
  30. package/node_modules/@playdrop/api-client/dist/core/errors.js +11 -11
  31. package/node_modules/@playdrop/api-client/dist/core/request.d.ts +2 -0
  32. package/node_modules/@playdrop/api-client/dist/core/request.d.ts.map +1 -1
  33. package/node_modules/@playdrop/api-client/dist/core/request.js +10 -3
  34. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +12 -10
  35. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  36. package/node_modules/@playdrop/api-client/dist/domains/admin.js +33 -30
  37. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +8 -1
  38. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  39. package/node_modules/@playdrop/api-client/dist/domains/apps.js +140 -124
  40. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +9 -5
  41. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
  42. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +151 -88
  43. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +4 -1
  44. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  45. package/node_modules/@playdrop/api-client/dist/domains/assets.js +153 -112
  46. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +3 -1
  47. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -1
  48. package/node_modules/@playdrop/api-client/dist/domains/auth.js +21 -0
  49. package/node_modules/@playdrop/api-client/dist/domains/me.d.ts +6 -2
  50. package/node_modules/@playdrop/api-client/dist/domains/me.d.ts.map +1 -1
  51. package/node_modules/@playdrop/api-client/dist/domains/me.js +13 -2
  52. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +1 -0
  53. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  54. package/node_modules/@playdrop/api-client/dist/domains/payments.js +10 -0
  55. package/node_modules/@playdrop/api-client/dist/index.d.ts +65 -24
  56. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  57. package/node_modules/@playdrop/api-client/dist/index.js +38 -13
  58. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js +2 -0
  59. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js.map +1 -1
  60. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js +1 -1
  61. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js.map +1 -1
  62. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js +1 -1
  63. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js.map +1 -1
  64. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js +95 -75
  65. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/builder.js.map +1 -1
  66. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.d.ts +2 -3
  67. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/scanner.js.map +1 -1
  68. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js +4 -4
  69. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r6/textures/face-map.js.map +1 -1
  70. package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.d.ts +2 -2
  71. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.d.ts +5 -5
  72. package/node_modules/@playdrop/boxel-core/dist/src/transforms/voxels/textured-to-voxel.d.ts +3 -3
  73. package/node_modules/@playdrop/boxel-core/dist/src/types.d.ts +25 -25
  74. package/node_modules/@playdrop/boxel-core/dist/src/validation.js +2 -1
  75. package/node_modules/@playdrop/boxel-core/dist/src/validation.js.map +1 -1
  76. package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.js +5 -0
  77. package/node_modules/@playdrop/boxel-three/package.json +3 -3
  78. package/node_modules/@playdrop/config/client-meta.json +5 -5
  79. package/node_modules/@playdrop/config/dist/src/index.js +6 -6
  80. package/node_modules/@playdrop/config/dist/test/validateClientEnvironment.test.js +2 -2
  81. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  82. package/node_modules/@playdrop/types/dist/api.d.ts +33 -8
  83. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  84. package/node_modules/@playdrop/types/dist/api.js +16 -8
  85. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +107 -11
  86. package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
  87. package/node_modules/@playdrop/types/dist/asset-pack.js +2 -0
  88. package/node_modules/@playdrop/types/dist/asset.d.ts +15 -0
  89. package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
  90. package/node_modules/@playdrop/types/dist/asset.js +1 -0
  91. package/node_modules/@playdrop/types/dist/ecs.d.ts.map +1 -1
  92. package/node_modules/@playdrop/types/dist/ecs.js +10 -6
  93. package/node_modules/@playdrop/types/dist/entity.d.ts +5 -10
  94. package/node_modules/@playdrop/types/dist/entity.d.ts.map +1 -1
  95. package/node_modules/@playdrop/types/dist/entity.js +40 -23
  96. package/node_modules/@playdrop/types/dist/graph.d.ts.map +1 -1
  97. package/node_modules/@playdrop/types/dist/graph.js +13 -5
  98. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  99. package/node_modules/@playdrop/types/dist/version.js +7 -3
  100. package/node_modules/@playdrop/vox-three/dist/src/vox.d.ts +1 -0
  101. package/node_modules/@playdrop/vox-three/dist/src/vox.js +15 -6
  102. package/node_modules/@playdrop/vox-three/dist/src/vox.js.map +1 -1
  103. package/node_modules/@playdrop/vox-three/dist/test/vox.test.js +16 -0
  104. package/node_modules/@playdrop/vox-three/dist/test/vox.test.js.map +1 -1
  105. package/node_modules/@playdrop/vox-three/package.json +3 -3
  106. package/package.json +1 -1
@@ -13,6 +13,31 @@ const http_1 = require("../http");
13
13
  const messages_1 = require("../messages");
14
14
  const init_1 = require("./init");
15
15
  const CATALOGUE_FILENAME = 'catalogue.json';
16
+ const MIME_TYPE_TO_EXTENSION = {
17
+ 'image/png': '.png',
18
+ 'image/jpeg': '.jpg',
19
+ 'image/webp': '.webp',
20
+ 'image/gif': '.gif',
21
+ 'audio/mpeg': '.mp3',
22
+ 'audio/wav': '.wav',
23
+ 'audio/ogg': '.ogg',
24
+ 'video/mp4': '.mp4',
25
+ 'video/webm': '.webm',
26
+ 'model/gltf+json': '.gltf',
27
+ 'model/gltf-binary': '.glb',
28
+ 'application/json': '.json',
29
+ };
30
+ function inferExtensionFromCandidate(candidate) {
31
+ try {
32
+ if (candidate.startsWith('http://') || candidate.startsWith('https://')) {
33
+ return (0, node_path_1.extname)(new URL(candidate).pathname);
34
+ }
35
+ }
36
+ catch {
37
+ return (0, node_path_1.extname)(candidate);
38
+ }
39
+ return (0, node_path_1.extname)(candidate);
40
+ }
16
41
  async function ensureWorkspaceCataloguePath() {
17
42
  const path = (0, node_path_1.resolve)(process.cwd(), CATALOGUE_FILENAME);
18
43
  if ((0, node_fs_1.existsSync)(path) && (0, node_fs_1.statSync)(path).isFile()) {
@@ -100,53 +125,13 @@ function parseAssetManifestFiles(value) {
100
125
  function inferExtensionFromManifestEntry(entry) {
101
126
  const candidates = [entry.key, entry.url].filter((value) => typeof value === 'string' && value.length > 0);
102
127
  for (const candidate of candidates) {
103
- try {
104
- if (candidate.startsWith('http://') || candidate.startsWith('https://')) {
105
- const extension = (0, node_path_1.extname)(new URL(candidate).pathname);
106
- if (extension) {
107
- return extension;
108
- }
109
- }
110
- else {
111
- const extension = (0, node_path_1.extname)(candidate);
112
- if (extension) {
113
- return extension;
114
- }
115
- }
116
- }
117
- catch {
118
- const extension = (0, node_path_1.extname)(candidate);
119
- if (extension) {
120
- return extension;
121
- }
128
+ const extension = inferExtensionFromCandidate(candidate);
129
+ if (extension) {
130
+ return extension;
122
131
  }
123
132
  }
124
133
  const mimeType = typeof entry.contentType === 'string' ? entry.contentType.trim().toLowerCase() : '';
125
- if (mimeType === 'image/png')
126
- return '.png';
127
- if (mimeType === 'image/jpeg')
128
- return '.jpg';
129
- if (mimeType === 'image/webp')
130
- return '.webp';
131
- if (mimeType === 'image/gif')
132
- return '.gif';
133
- if (mimeType === 'audio/mpeg')
134
- return '.mp3';
135
- if (mimeType === 'audio/wav')
136
- return '.wav';
137
- if (mimeType === 'audio/ogg')
138
- return '.ogg';
139
- if (mimeType === 'video/mp4')
140
- return '.mp4';
141
- if (mimeType === 'video/webm')
142
- return '.webm';
143
- if (mimeType === 'model/gltf+json')
144
- return '.gltf';
145
- if (mimeType === 'model/gltf-binary')
146
- return '.glb';
147
- if (mimeType === 'application/json')
148
- return '.json';
149
- return '';
134
+ return MIME_TYPE_TO_EXTENSION[mimeType] ?? '';
150
135
  }
151
136
  function buildLocalFilename(entry, usedNames) {
152
137
  const sourceName = typeof entry.key === 'string' && entry.key.length > 0
@@ -94,6 +94,34 @@ function parseVersion(raw, command) {
94
94
  function kindLabel(kind) {
95
95
  return kind === 'all' ? 'content' : kind === 'asset-pack' ? 'asset packs' : `${kind}s`;
96
96
  }
97
+ function formatCountValue(value) {
98
+ return value.toLocaleString('en-US');
99
+ }
100
+ function buildCountsSuffix(item) {
101
+ const parts = [];
102
+ if (item.kind === 'app' && 'playCount' in item.item && typeof item.item.playCount === 'number') {
103
+ if (typeof item.item.viewCount === 'number') {
104
+ parts.push(`view ${formatCountValue(item.item.viewCount)}`);
105
+ }
106
+ parts.push(`launch ${formatCountValue(item.item.playCount)}`);
107
+ }
108
+ if (item.kind === 'asset' && 'playCount' in item.item && typeof item.item.playCount === 'number') {
109
+ if (typeof item.item.viewCount === 'number') {
110
+ parts.push(`view ${formatCountValue(item.item.viewCount)}`);
111
+ }
112
+ parts.push(`play ${formatCountValue(item.item.playCount)}`);
113
+ }
114
+ if (typeof item.item.likeCount === 'number') {
115
+ parts.push(`like ${formatCountValue(item.item.likeCount)}`);
116
+ }
117
+ if (typeof item.item.remixCount === 'number') {
118
+ parts.push(`remix ${formatCountValue(item.item.remixCount)}`);
119
+ }
120
+ if (typeof item.item.commentCount === 'number') {
121
+ parts.push(`comment ${formatCountValue(item.item.commentCount)}`);
122
+ }
123
+ return parts.length > 0 ? ` | ${parts.join(' | ')}` : '';
124
+ }
97
125
  async function collectPaginatedItems(fetchPage) {
98
126
  const items = [];
99
127
  let offset = 0;
@@ -127,6 +155,12 @@ function sliceMergedItems(items, limit, offset) {
127
155
  },
128
156
  };
129
157
  }
158
+ function requirePagination(pagination, context) {
159
+ if (!pagination) {
160
+ throw new Error(`missing_pagination:${context}`);
161
+ }
162
+ return pagination;
163
+ }
130
164
  function parseManagedName(raw, command) {
131
165
  try {
132
166
  return (0, refs_1.parseScopedName)(raw ?? '', 'name');
@@ -179,12 +213,11 @@ async function browseCreations(options = {}) {
179
213
  const items = [];
180
214
  let pagination;
181
215
  if (kind === 'app') {
182
- const apps = !usingCurrentCreator
183
- ? (await client.fetchAppsByCreator(creator)).apps ?? []
184
- : (await client.fetchMyApps()).apps ?? [];
185
- const sliced = sliceMergedItems(apps, limit, offset);
186
- items.push(...sliced.items.map((item) => ({ kind: 'app', ref: `${item.creatorUsername}/app/${item.name}`, item })));
187
- pagination = sliced.pagination;
216
+ const response = !usingCurrentCreator
217
+ ? await client.fetchAppsByCreator(creator, { limit, offset })
218
+ : await client.fetchMyApps({ limit, offset });
219
+ items.push(...(response.apps ?? []).map((item) => ({ kind: 'app', ref: `${item.creatorUsername}/app/${item.name}`, item })));
220
+ pagination = requirePagination(response.pagination, 'creations_browse_app');
188
221
  }
189
222
  else if (kind === 'asset') {
190
223
  const response = await client.listAssetsForCreator(creator, { limit, offset });
@@ -198,9 +231,13 @@ async function browseCreations(options = {}) {
198
231
  }
199
232
  else {
200
233
  const [appsResponse, assetsResponse, packsResponse] = await Promise.all([
201
- !usingCurrentCreator
202
- ? client.fetchAppsByCreator(creator)
203
- : client.fetchMyApps(),
234
+ collectPaginatedItems((pageLimit, pageOffset) => (!usingCurrentCreator
235
+ ? client.fetchAppsByCreator(creator, { limit: pageLimit, offset: pageOffset })
236
+ : client.fetchMyApps({ limit: pageLimit, offset: pageOffset }))
237
+ .then((response) => ({
238
+ items: response.apps ?? [],
239
+ pagination: requirePagination(response.pagination, 'creations_browse_all_apps'),
240
+ }))),
204
241
  collectPaginatedItems((pageLimit, pageOffset) => client.listAssetsForCreator(creator, { limit: pageLimit, offset: pageOffset }).then((response) => ({
205
242
  items: response.assets ?? [],
206
243
  pagination: response.pagination,
@@ -211,7 +248,7 @@ async function browseCreations(options = {}) {
211
248
  }))),
212
249
  ]);
213
250
  const combined = [
214
- ...((appsResponse.apps ?? []).map((item) => ({ kind: 'app', ref: `${item.creatorUsername}/app/${item.name}`, item }))),
251
+ ...(appsResponse.items.map((item) => ({ kind: 'app', ref: `${item.creatorUsername}/app/${item.name}`, item }))),
215
252
  ...(assetsResponse.items.map((item) => ({ kind: 'asset', ref: `${item.creatorUsername}/asset/${item.name}`, item }))),
216
253
  ...(packsResponse.items.map((item) => ({ kind: 'asset-pack', ref: `${item.creatorUsername}/asset-pack/${item.name}`, item }))),
217
254
  ];
@@ -238,7 +275,7 @@ async function browseCreations(options = {}) {
238
275
  console.log(`Creations for @${creator}:\n`);
239
276
  }
240
277
  for (const [index, item] of items.entries()) {
241
- console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${item.item.displayName || item.item.name}`);
278
+ console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${item.item.displayName || item.item.name}${buildCountsSuffix(item)}`);
242
279
  }
243
280
  console.log('\nNext: run "playdrop detail <creator>/<kind>/<name>" or a "playdrop creations ..." command to manage one item.');
244
281
  }
@@ -260,12 +297,13 @@ async function updateCreationApp(nameArg, options = {}) {
260
297
  if (!name) {
261
298
  return;
262
299
  }
263
- const appType = options.type ? (0, types_1.parseAppType)(options.type.trim().toUpperCase()) : undefined;
264
- if (options.type !== undefined && !appType) {
300
+ const parsedAppType = options.type ? (0, types_1.parseAppType)(options.type.trim().toUpperCase()) : undefined;
301
+ if (options.type !== undefined && !parsedAppType) {
265
302
  (0, messages_1.printErrorWithHelp)('The --type value is invalid.', ['Use one of: game, demo, tool, template.'], { command: 'creations apps update' });
266
303
  process.exitCode = 1;
267
304
  return;
268
305
  }
306
+ const appType = parsedAppType ?? undefined;
269
307
  if (!options.displayName && !appType) {
270
308
  (0, messages_1.printErrorWithHelp)('At least one update field is required.', ['Use --display-name and/or --type.'], { command: 'creations apps update' });
271
309
  process.exitCode = 1;
@@ -1,6 +1,6 @@
1
1
  import http from 'node:http';
2
2
  import { type ChildProcess } from 'node:child_process';
3
- import { ProjectInfo } from './devShared';
3
+ import type { ProjectInfo } from './devShared';
4
4
  export interface StartDevServerOptions {
5
5
  appName: string;
6
6
  htmlPath: string;
@@ -63,7 +63,7 @@ async function assertAppRegistered(client, creatorUsername, appName) {
63
63
  }
64
64
  function findNearestCatalogue(startDir) {
65
65
  let current = (0, node_path_1.resolve)(startDir);
66
- while (true) { // eslint-disable-line no-constant-condition
66
+ while (true) {
67
67
  const candidate = (0, node_path_1.join)(current, 'catalogue.json');
68
68
  if ((0, node_fs_1.existsSync)(candidate)) {
69
69
  return candidate;
@@ -36,6 +36,95 @@ const LOCAL_IMAGE_MIME_BY_EXTENSION = {
36
36
  '.jpeg': 'image/jpeg',
37
37
  '.webp': 'image/webp',
38
38
  };
39
+ function handleGenerateBuildRequestError(message) {
40
+ const errorByCode = {
41
+ missing_type: {
42
+ title: 'A generation type is required.',
43
+ details: ['Use one of: image, music, sfx, video, model_3d.'],
44
+ },
45
+ missing_prompt: {
46
+ title: 'A prompt is required.',
47
+ details: ['Example: playdrop ai create image "A pixel art hero portrait"'],
48
+ },
49
+ invalid_type: {
50
+ title: 'The generation type is invalid.',
51
+ details: ['Use one of: image, music, sfx, video, model_3d.'],
52
+ },
53
+ invalid_duration: {
54
+ title: 'The --duration value is invalid.',
55
+ details: ['Use a positive value like 20000, 20s, or 20000ms.'],
56
+ },
57
+ invalid_video_duration: {
58
+ title: 'The --duration value for video is invalid.',
59
+ details: ['Use 4s or 8s for video generation.'],
60
+ },
61
+ invalid_aspect_ratio: {
62
+ title: 'The --ratio value is invalid.',
63
+ details: ['Image: 1:1, 3:4, 9:16, 4:3, 16:9. Video: 16:9, 9:16.'],
64
+ },
65
+ missing_model3d_images: {
66
+ title: 'Model 3D IMAGE source mode requires at least one reference image.',
67
+ details: ['Provide --image1 <url> or --image2 <url>, or switch to --source-mode TEXT.'],
68
+ },
69
+ model3d_text_mode_disallows_images: {
70
+ title: 'Model 3D TEXT source mode cannot include reference images.',
71
+ details: ['Remove --image1/--image2 or switch to --source-mode IMAGE.'],
72
+ },
73
+ invalid_asset_subcategory: {
74
+ title: 'The --subcategory value is invalid.',
75
+ details: ['Use a lowercase slug such as generic, music, sfx, or avatar.'],
76
+ },
77
+ };
78
+ const entry = errorByCode[message];
79
+ if (!entry) {
80
+ return false;
81
+ }
82
+ (0, messages_1.printErrorWithHelp)(entry.title, entry.details, { command: 'ai create' });
83
+ process.exitCode = 1;
84
+ return true;
85
+ }
86
+ function handleGenerateImageReferenceError(message) {
87
+ const imageRefHandlers = [
88
+ {
89
+ prefix: 'image_ref_not_found:',
90
+ title: (optionName) => `The ${optionName || 'image'} reference file was not found.`,
91
+ details: (path) => [path ? `Missing file: ${path}` : 'Provide a valid local file path or HTTPS URL.'],
92
+ },
93
+ {
94
+ prefix: 'image_ref_not_file:',
95
+ title: (optionName) => `The ${optionName || 'image'} reference is not a file.`,
96
+ details: (path) => [path ? `Path must point to a file: ${path}` : 'Provide a valid local file path.'],
97
+ },
98
+ {
99
+ prefix: 'image_ref_invalid_scheme:',
100
+ title: (optionName) => `The ${optionName || 'image'} reference URL uses an unsupported scheme.`,
101
+ details: (scheme) => [`Use an HTTPS URL or a local file path. Received: ${scheme || 'unknown'}`],
102
+ },
103
+ {
104
+ prefix: 'image_ref_signature_mismatch:',
105
+ title: (optionName) => `The ${optionName || 'image'} reference file extension does not match its image signature.`,
106
+ details: (path) => [path ? `Fix file type mismatch for: ${path}` : 'Ensure extension and file signature match (.png, .jpg, .jpeg, .webp).'],
107
+ },
108
+ {
109
+ prefix: 'image_ref_unsupported_type:',
110
+ title: (optionName) => `The ${optionName || 'image'} reference file type is unsupported.`,
111
+ details: (path) => [
112
+ path ? `Unsupported file: ${path}` : 'Provide a supported local file.',
113
+ 'Supported local image types: .png, .jpg, .jpeg, .webp.',
114
+ ],
115
+ },
116
+ ];
117
+ for (const handler of imageRefHandlers) {
118
+ if (!message.startsWith(handler.prefix)) {
119
+ continue;
120
+ }
121
+ const [, optionName, value] = message.split(':');
122
+ (0, messages_1.printErrorWithHelp)(handler.title(optionName), handler.details(value), { command: 'ai create' });
123
+ process.exitCode = 1;
124
+ return true;
125
+ }
126
+ return false;
127
+ }
39
128
  function sleep(ms) {
40
129
  return new Promise((resolve) => {
41
130
  setTimeout(resolve, ms);
@@ -442,82 +531,10 @@ async function generate(typeInput, promptInput, options = {}) {
442
531
  }
443
532
  catch (error) {
444
533
  const message = typeof error?.message === 'string' ? error.message : 'invalid_input';
445
- if (message === 'missing_type') {
446
- (0, messages_1.printErrorWithHelp)('A generation type is required.', ['Use one of: image, music, sfx, video, model_3d.'], { command: 'ai create' });
447
- process.exitCode = 1;
448
- return;
449
- }
450
- if (message === 'missing_prompt') {
451
- (0, messages_1.printErrorWithHelp)('A prompt is required.', ['Example: playdrop ai create image "A pixel art hero portrait"'], { command: 'ai create' });
452
- process.exitCode = 1;
453
- return;
454
- }
455
- if (message === 'invalid_type') {
456
- (0, messages_1.printErrorWithHelp)('The generation type is invalid.', ['Use one of: image, music, sfx, video, model_3d.'], { command: 'ai create' });
457
- process.exitCode = 1;
534
+ if (handleGenerateBuildRequestError(message)) {
458
535
  return;
459
536
  }
460
- if (message === 'invalid_duration') {
461
- (0, messages_1.printErrorWithHelp)('The --duration value is invalid.', ['Use a positive value like 20000, 20s, or 20000ms.'], { command: 'ai create' });
462
- process.exitCode = 1;
463
- return;
464
- }
465
- if (message === 'invalid_video_duration') {
466
- (0, messages_1.printErrorWithHelp)('The --duration value for video is invalid.', ['Use 4s or 8s for video generation.'], { command: 'ai create' });
467
- process.exitCode = 1;
468
- return;
469
- }
470
- if (message === 'invalid_aspect_ratio') {
471
- (0, messages_1.printErrorWithHelp)('The --ratio value is invalid.', ['Image: 1:1, 3:4, 9:16, 4:3, 16:9. Video: 16:9, 9:16.'], { command: 'ai create' });
472
- process.exitCode = 1;
473
- return;
474
- }
475
- if (message === 'missing_model3d_images') {
476
- (0, messages_1.printErrorWithHelp)('Model 3D IMAGE source mode requires at least one reference image.', ['Provide --image1 <url> or --image2 <url>, or switch to --source-mode TEXT.'], { command: 'ai create' });
477
- process.exitCode = 1;
478
- return;
479
- }
480
- if (message === 'model3d_text_mode_disallows_images') {
481
- (0, messages_1.printErrorWithHelp)('Model 3D TEXT source mode cannot include reference images.', ['Remove --image1/--image2 or switch to --source-mode IMAGE.'], { command: 'ai create' });
482
- process.exitCode = 1;
483
- return;
484
- }
485
- if (message === 'invalid_asset_subcategory') {
486
- (0, messages_1.printErrorWithHelp)('The --subcategory value is invalid.', ['Use a lowercase slug such as generic, music, sfx, or avatar.'], { command: 'ai create' });
487
- process.exitCode = 1;
488
- return;
489
- }
490
- if (message.startsWith('image_ref_not_found:')) {
491
- const [, optionName, path] = message.split(':');
492
- (0, messages_1.printErrorWithHelp)(`The ${optionName || 'image'} reference file was not found.`, [path ? `Missing file: ${path}` : 'Provide a valid local file path or HTTPS URL.'], { command: 'ai create' });
493
- process.exitCode = 1;
494
- return;
495
- }
496
- if (message.startsWith('image_ref_not_file:')) {
497
- const [, optionName, path] = message.split(':');
498
- (0, messages_1.printErrorWithHelp)(`The ${optionName || 'image'} reference is not a file.`, [path ? `Path must point to a file: ${path}` : 'Provide a valid local file path.'], { command: 'ai create' });
499
- process.exitCode = 1;
500
- return;
501
- }
502
- if (message.startsWith('image_ref_invalid_scheme:')) {
503
- const [, optionName, scheme] = message.split(':');
504
- (0, messages_1.printErrorWithHelp)(`The ${optionName || 'image'} reference URL uses an unsupported scheme.`, [`Use an HTTPS URL or a local file path. Received: ${scheme || 'unknown'}`], { command: 'ai create' });
505
- process.exitCode = 1;
506
- return;
507
- }
508
- if (message.startsWith('image_ref_signature_mismatch:')) {
509
- const [, optionName, path] = message.split(':');
510
- (0, messages_1.printErrorWithHelp)(`The ${optionName || 'image'} reference file extension does not match its image signature.`, [path ? `Fix file type mismatch for: ${path}` : 'Ensure extension and file signature match (.png, .jpg, .jpeg, .webp).'], { command: 'ai create' });
511
- process.exitCode = 1;
512
- return;
513
- }
514
- if (message.startsWith('image_ref_unsupported_type:')) {
515
- const [, optionName, path] = message.split(':');
516
- (0, messages_1.printErrorWithHelp)(`The ${optionName || 'image'} reference file type is unsupported.`, [
517
- path ? `Unsupported file: ${path}` : 'Provide a supported local file.',
518
- 'Supported local image types: .png, .jpg, .jpeg, .webp.',
519
- ], { command: 'ai create' });
520
- process.exitCode = 1;
537
+ if (handleGenerateImageReferenceError(message)) {
521
538
  return;
522
539
  }
523
540
  (0, messages_1.printErrorWithHelp)(`Invalid AI create input: ${message}.`, ['Run "playdrop help ai create" for valid options.'], {
@@ -10,7 +10,7 @@ function printGettingStarted() {
10
10
  console.log(' playdrop project init .');
11
11
  console.log('');
12
12
  console.log('3. Create an app');
13
- console.log(' playdrop project create app my-app --template playdrop/template/html_template');
13
+ console.log(' playdrop project create app my-app --template playdrop/template/typescript_template');
14
14
  console.log('');
15
15
  console.log('4. Preview locally');
16
16
  console.log(' playdrop project dev my-app');
@@ -87,6 +87,34 @@ function itemSummary(item) {
87
87
  }
88
88
  return 'asset-pack';
89
89
  }
90
+ function formatCountValue(value) {
91
+ return value.toLocaleString('en-US');
92
+ }
93
+ function buildCountsSuffix(item) {
94
+ const parts = [];
95
+ if (item.kind === 'app' && typeof item.item.playCount === 'number') {
96
+ if (typeof item.item.viewCount === 'number') {
97
+ parts.push(`view ${formatCountValue(item.item.viewCount)}`);
98
+ }
99
+ parts.push(`launch ${formatCountValue(item.item.playCount)}`);
100
+ }
101
+ if (item.kind === 'asset' && typeof item.item.playCount === 'number') {
102
+ if (typeof item.item.viewCount === 'number') {
103
+ parts.push(`view ${formatCountValue(item.item.viewCount)}`);
104
+ }
105
+ parts.push(`play ${formatCountValue(item.item.playCount)}`);
106
+ }
107
+ if (typeof item.item.likeCount === 'number') {
108
+ parts.push(`like ${formatCountValue(item.item.likeCount)}`);
109
+ }
110
+ if (typeof item.item.remixCount === 'number') {
111
+ parts.push(`remix ${formatCountValue(item.item.remixCount)}`);
112
+ }
113
+ if (typeof item.item.commentCount === 'number') {
114
+ parts.push(`comment ${formatCountValue(item.item.commentCount)}`);
115
+ }
116
+ return parts.length > 0 ? ` | ${parts.join(' | ')}` : '';
117
+ }
90
118
  async function search(query, options = {}) {
91
119
  const trimmedQuery = typeof query === 'string' ? query.trim() : '';
92
120
  if (!trimmedQuery) {
@@ -179,7 +207,7 @@ async function search(query, options = {}) {
179
207
  console.log(`Search results for "${trimmedQuery}":\n`);
180
208
  for (const [index, item] of items.entries()) {
181
209
  const displayName = item.item.displayName || item.item.name;
182
- console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${displayName} | @${item.item.creatorUsername} | ${itemSummary(item)}`);
210
+ console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${displayName} | @${item.item.creatorUsername} | ${itemSummary(item)}${buildCountsSuffix(item)}`);
183
211
  }
184
212
  console.log('\nNext: run "playdrop detail <creator>/<kind>/<name>" to inspect one result.');
185
213
  }
@@ -0,0 +1,70 @@
1
+ import type { ApiClient } from '@playdrop/api-client';
2
+ import { type AssetPackUploadedLocalAsset } from '@playdrop/types';
3
+ import type { AssetPackTask, AssetTask, EmbeddedAssetTask } from '../catalogue';
4
+ import type { CliTask } from '../taskUtils';
5
+ export type CurrentUserRole = 'USER' | 'CREATOR' | 'ADMIN' | null;
6
+ export type UploadedAssetInfo = {
7
+ creatorUsername: string;
8
+ name: string;
9
+ revision: number;
10
+ ref: string;
11
+ versionNodeId: string;
12
+ };
13
+ export type UploadedAppInfo = {
14
+ creatorUsername: string;
15
+ name: string;
16
+ version: string;
17
+ versionId: number;
18
+ versionNodeId: string;
19
+ ref: string;
20
+ };
21
+ export type UploadedPackInfo = {
22
+ creatorUsername: string;
23
+ name: string;
24
+ version: string;
25
+ versionNodeId: string;
26
+ ref: string;
27
+ assetRefs: string[];
28
+ uploadedLocalAssets: AssetPackUploadedLocalAsset[];
29
+ };
30
+ export type PreparedUploadFile = {
31
+ fieldName: string;
32
+ filename: string;
33
+ filePath: string;
34
+ file: File;
35
+ contentType: string;
36
+ sizeBytes: number;
37
+ sha256: string;
38
+ };
39
+ export type PackUploadPlan = {
40
+ packKey: string;
41
+ packTask: AssetPackTask;
42
+ packCreator: string;
43
+ ownedAssetKeys: string[];
44
+ uploadKeyByAssetKey: Map<string, string>;
45
+ };
46
+ export type PackUploadPlanningResult = {
47
+ ok: true;
48
+ packPlansByKey: Map<string, PackUploadPlan>;
49
+ ownedPackByAssetKey: Map<string, PackUploadPlan>;
50
+ } | {
51
+ ok: false;
52
+ task: CliTask;
53
+ message: string;
54
+ };
55
+ type TaskCreatorResult = {
56
+ requestedTaskOwner: string;
57
+ taskCreator: string;
58
+ creatorTargetError: string | null;
59
+ };
60
+ export declare function buildAssetKey(creatorUsername: string, name: string): string;
61
+ export declare function buildPackKey(creatorUsername: string, name: string, version: string): string;
62
+ export declare function getTaskCreatorResult(task: CliTask, defaultCreator: string, currentUserRole: CurrentUserRole): TaskCreatorResult;
63
+ export declare function parseUnversionedAssetTaskRef(rawRef: string, fallbackCreator: string): {
64
+ creatorUsername: string;
65
+ name: string;
66
+ } | null;
67
+ export declare function buildAssetPackUploadPlans(tasks: CliTask[], defaultCreator: string, currentUserRole: CurrentUserRole): PackUploadPlanningResult;
68
+ export declare function uploadAssetTask(client: ApiClient, task: AssetTask | EmbeddedAssetTask, sourceAppVersionId?: number, creatorUsername?: string): Promise<UploadedAssetInfo>;
69
+ export declare function uploadAssetPackTask(client: ApiClient, task: AssetPackTask, creatorUsername: string, uploadedAssets: Map<string, UploadedAssetInfo>, localAssetTasks: AssetTask[], uploadKeyByAssetKey: Map<string, string>, targetCreatorUsername?: string): Promise<UploadedPackInfo>;
70
+ export {};