@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
@@ -9,6 +9,29 @@ const http_1 = require("../http");
9
9
  const messages_1 = require("../messages");
10
10
  const devShared_1 = require("./devShared");
11
11
  const captureRuntime_1 = require("../captureRuntime");
12
+ function readUnsupportedPlaydropCaptureMessage(targetUrl) {
13
+ let parsed;
14
+ try {
15
+ parsed = new URL(targetUrl);
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ const hostname = parsed.hostname.toLowerCase();
21
+ const isPlaydropHost = hostname === 'www.playdrop.ai'
22
+ || hostname === 'playdrop.ai'
23
+ || hostname === 'localhost'
24
+ || hostname === '127.0.0.1';
25
+ if (!isPlaydropHost) {
26
+ return null;
27
+ }
28
+ const pathname = parsed.pathname.replace(/\/+$/, '') || '/';
29
+ const matchesCreatorAppRoute = /^\/(?:_auth\/)?creators\/[^/]+\/apps\/[^/]+\/[^/]+(?:\/.*)?$/i.test(pathname);
30
+ if (!matchesCreatorAppRoute || pathname.endsWith('/play')) {
31
+ return null;
32
+ }
33
+ return `Only Playdrop /play URLs are supported for remote capture. Use ${parsed.origin}${pathname}/play instead.`;
34
+ }
12
35
  function parseTimeout(raw) {
13
36
  if (raw === undefined) {
14
37
  return 10;
@@ -70,6 +93,10 @@ function parseOptions(url, options = {}) {
70
93
  if (expectedUrl) {
71
94
  assertValidUrl(expectedUrl, 'invalid_expected_url');
72
95
  }
96
+ const unsupportedPlaydropCaptureMessage = readUnsupportedPlaydropCaptureMessage(trimmedUrl);
97
+ if (unsupportedPlaydropCaptureMessage) {
98
+ throw new Error(`unsupported_playdrop_capture_target:${unsupportedPlaydropCaptureMessage}`);
99
+ }
73
100
  const { username, password } = parseCredentialPair(options);
74
101
  return {
75
102
  targetUrl: trimmedUrl,
@@ -92,6 +119,12 @@ async function captureRemote(url, options = {}) {
92
119
  parsed = parseOptions(url, options);
93
120
  }
94
121
  catch (error) {
122
+ if (error instanceof Error && error.message.startsWith('unsupported_playdrop_capture_target:')) {
123
+ const detail = error.message.slice('unsupported_playdrop_capture_target:'.length).trim();
124
+ (0, messages_1.printErrorWithHelp)(detail, [], { command: 'project capture remote' });
125
+ process.exitCode = 1;
126
+ return;
127
+ }
95
128
  if (error instanceof Error && error.message === 'invalid_expected_url') {
96
129
  (0, messages_1.printErrorWithHelp)('The --expected-url value must be a valid URL.', [], { command: 'project capture remote' });
97
130
  process.exitCode = 1;
@@ -2,5 +2,6 @@ type CreateOptions = {
2
2
  template?: string;
3
3
  remix?: string;
4
4
  };
5
+ export declare function createAssetSpecProject(name: string): Promise<void>;
5
6
  export declare function create(name: string, options?: CreateOptions): Promise<void>;
6
7
  export {};
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createAssetSpecProject = createAssetSpecProject;
6
7
  exports.create = create;
7
8
  const promises_1 = __importDefault(require("node:readline/promises"));
8
9
  const node_process_1 = require("node:process");
@@ -22,7 +23,7 @@ const catalogue_utils_1 = require("../catalogue-utils");
22
23
  const catalogue_1 = require("../catalogue");
23
24
  const CATALOGUE_FILENAME = 'catalogue.json';
24
25
  const LEGACY_CATALOGUE_VERSION_KEY = ['schema', 'Version'].join('');
25
- const ALLOWED_CATALOGUE_TOP_LEVEL_KEYS = new Set(['apps', 'assets', 'assetPacks']);
26
+ const ALLOWED_CATALOGUE_TOP_LEVEL_KEYS = new Set(['apps', 'assetSpecs', 'assets', 'assetPacks']);
26
27
  async function downloadArchive(url) {
27
28
  const response = await fetch(url);
28
29
  if (!response.ok) {
@@ -176,6 +177,7 @@ const DEFAULT_SURFACE_TARGETS = {
176
177
  mobilePortrait: app_1.DEFAULT_APP_SURFACE_TARGETS.includes('MOBILE_PORTRAIT'),
177
178
  };
178
179
  const DEFAULT_APP_VERSION = '1.0.0';
180
+ const DEFAULT_ASSET_SPEC_VERSION = '1.0.0';
179
181
  function makeDefaultAppEntry(name, filePath) {
180
182
  return {
181
183
  name,
@@ -187,6 +189,19 @@ function makeDefaultAppEntry(name, filePath) {
187
189
  surfaceTargets: { ...DEFAULT_SURFACE_TARGETS },
188
190
  };
189
191
  }
192
+ function makeDefaultAssetSpecEntry(name) {
193
+ const basePath = `asset-specs/${name}`;
194
+ return {
195
+ name,
196
+ version: DEFAULT_ASSET_SPEC_VERSION,
197
+ displayName: name,
198
+ description: '',
199
+ symbol: `${basePath}/symbol.svg`,
200
+ contract: `${basePath}/contract.json`,
201
+ documentation: `${basePath}/README.md`,
202
+ visibility: 'PUBLIC',
203
+ };
204
+ }
190
205
  function buildCatalogueEntry(name, metadata, sourceInfo, relativeFilePath) {
191
206
  const entry = makeDefaultAppEntry(name, relativeFilePath);
192
207
  if (!metadata) {
@@ -366,7 +381,7 @@ function logCatalogueOutcome(result, name) {
366
381
  return false;
367
382
  }
368
383
  if (result.error === 'unsupported_top_level_key') {
369
- (0, messages_1.printErrorWithHelp)(`${label} contains unsupported top-level key "${result.unsupportedKey || 'unknown'}".`, ['Keep catalogue.json limited to apps, assets, and assetPacks, then rerun `playdrop project create app`.'], { command: 'project create app' });
384
+ (0, messages_1.printErrorWithHelp)(`${label} contains unsupported top-level key "${result.unsupportedKey || 'unknown'}".`, ['Keep catalogue.json limited to apps, assetSpecs, assets, and assetPacks, then rerun `playdrop project create app`.'], { command: 'project create app' });
370
385
  process.exitCode = process.exitCode || 1;
371
386
  return false;
372
387
  }
@@ -388,6 +403,85 @@ function logCatalogueOutcome(result, name) {
388
403
  }
389
404
  return true;
390
405
  }
406
+ function ensureAssetSpecCatalogueEntry(name, cataloguePath) {
407
+ const path = (0, node_path_1.resolve)(cataloguePath);
408
+ const catalogueDir = (0, node_path_1.dirname)(path);
409
+ if (hasNestedCatalogueFiles(catalogueDir)) {
410
+ return { path, createdFile: false, addedEntry: false, error: 'workspace_root_marker_required' };
411
+ }
412
+ const entry = makeDefaultAssetSpecEntry(name);
413
+ if (!(0, node_fs_1.existsSync)(path)) {
414
+ const catalogue = { assetSpecs: [entry] };
415
+ (0, node_fs_1.writeFileSync)(path, `${JSON.stringify(catalogue, null, 2)}\n`);
416
+ return { path, createdFile: true, addedEntry: true };
417
+ }
418
+ const parsed = readCatalogue(path);
419
+ if (!parsed) {
420
+ return { path, createdFile: false, addedEntry: false, error: 'invalid_json' };
421
+ }
422
+ if (Object.prototype.hasOwnProperty.call(parsed, LEGACY_CATALOGUE_VERSION_KEY)) {
423
+ return { path, createdFile: false, addedEntry: false, error: 'invalid_schema_version' };
424
+ }
425
+ const unsupportedKey = findUnsupportedCatalogueTopLevelKey(parsed);
426
+ if (unsupportedKey) {
427
+ return { path, createdFile: false, addedEntry: false, error: 'unsupported_top_level_key', unsupportedKey };
428
+ }
429
+ if (!Array.isArray(parsed.apps) && Array.isArray(parsed.games)) {
430
+ return { path, createdFile: false, addedEntry: false, error: 'legacy_games_property' };
431
+ }
432
+ const assetSpecs = Array.isArray(parsed.assetSpecs) ? [...parsed.assetSpecs] : [];
433
+ if (assetSpecs.some((assetSpec) => typeof assetSpec?.name === 'string' && assetSpec.name === name)) {
434
+ return { path, createdFile: false, addedEntry: false, alreadyPresent: true };
435
+ }
436
+ assetSpecs.push(entry);
437
+ const updated = { ...parsed, assetSpecs };
438
+ (0, node_fs_1.writeFileSync)(path, `${JSON.stringify(updated, null, 2)}\n`);
439
+ return { path, createdFile: false, addedEntry: true };
440
+ }
441
+ function logAssetSpecCatalogueOutcome(result, name) {
442
+ const label = (0, node_path_1.relative)(process.cwd(), result.path) || CATALOGUE_FILENAME;
443
+ if (result.error === 'invalid_json') {
444
+ (0, messages_1.printErrorWithHelp)(`Could not update ${label} because it contains invalid JSON.`, [
445
+ `Fix the JSON in ${label} and add ${name} to the assetSpecs list, then rerun "playdrop project create asset-spec ${name}" if needed.`,
446
+ ], { command: 'project create asset-spec' });
447
+ process.exitCode = process.exitCode || 1;
448
+ return false;
449
+ }
450
+ if (result.error === 'legacy_games_property') {
451
+ (0, messages_1.printErrorWithHelp)(`${label} still uses the legacy "games" catalogue field.`, [
452
+ 'Rename the top-level "games" array to "apps" and rerun `playdrop project create asset-spec`.',
453
+ ], { command: 'project create asset-spec' });
454
+ process.exitCode = process.exitCode || 1;
455
+ return false;
456
+ }
457
+ if (result.error === 'invalid_schema_version') {
458
+ (0, messages_1.printErrorWithHelp)(`${label} must not define the legacy top-level version field.`, ['Remove the legacy top-level version field from the file and rerun `playdrop project create asset-spec`.'], { command: 'project create asset-spec' });
459
+ process.exitCode = process.exitCode || 1;
460
+ return false;
461
+ }
462
+ if (result.error === 'unsupported_top_level_key') {
463
+ (0, messages_1.printErrorWithHelp)(`${label} contains unsupported top-level key "${result.unsupportedKey || 'unknown'}".`, ['Keep catalogue.json limited to apps, assetSpecs, assets, and assetPacks, then rerun `playdrop project create asset-spec`.'], { command: 'project create asset-spec' });
464
+ process.exitCode = process.exitCode || 1;
465
+ return false;
466
+ }
467
+ if (result.error === 'workspace_root_marker_required') {
468
+ (0, messages_1.printErrorWithHelp)(`${label} must stay {} because nested catalogue.json files already exist below this directory.`, ['Create the asset spec inside its own project folder instead of writing entries into the workspace root catalogue.json.'], { command: 'project create asset-spec' });
469
+ process.exitCode = process.exitCode || 1;
470
+ return false;
471
+ }
472
+ if (result.createdFile) {
473
+ console.log(`Initialized ${label} and added asset spec ${name}.`);
474
+ return true;
475
+ }
476
+ if (result.addedEntry) {
477
+ console.log(`Added asset spec ${name} to ${label}.`);
478
+ return true;
479
+ }
480
+ if (result.alreadyPresent) {
481
+ console.log(`${label} already contains asset spec ${name}; keeping existing entry.`);
482
+ }
483
+ return true;
484
+ }
391
485
  function updateExtractedProjectCatalogue(name, metadata, projectCataloguePath, sourceInfo, htmlFilePath) {
392
486
  const path = (0, node_path_1.resolve)(projectCataloguePath);
393
487
  if (hasNestedCatalogueFiles((0, node_path_1.dirname)(path))) {
@@ -494,7 +588,7 @@ function logProjectCatalogueOutcome(result, name) {
494
588
  return false;
495
589
  }
496
590
  if (result.error === 'unsupported_top_level_key') {
497
- (0, messages_1.printErrorWithHelp)(`${label} contains unsupported top-level key "${result.unsupportedKey || 'unknown'}".`, ['Keep catalogue.json limited to apps, assets, and assetPacks, then rerun `playdrop project create app`.'], { command: 'project create app' });
591
+ (0, messages_1.printErrorWithHelp)(`${label} contains unsupported top-level key "${result.unsupportedKey || 'unknown'}".`, ['Keep catalogue.json limited to apps, assetSpecs, assets, and assetPacks, then rerun `playdrop project create app`.'], { command: 'project create app' });
498
592
  process.exitCode = process.exitCode || 1;
499
593
  return false;
500
594
  }
@@ -534,6 +628,8 @@ function buildPendingExternalRemixTask(name, metadata, sourceInfo, cataloguePath
534
628
  emoji: entry.emoji,
535
629
  color: entry.color,
536
630
  surfaceTargets,
631
+ assetSpecSupport: [],
632
+ tags: [],
537
633
  missingMetadata: [],
538
634
  version: entry.version,
539
635
  remix: typeof entry.remix === 'string' ? entry.remix : undefined,
@@ -626,6 +722,90 @@ function findWorkspaceRootDirectory(cataloguePath) {
626
722
  currentDir = parentDir;
627
723
  }
628
724
  }
725
+ function buildAssetSpecSymbolSvg() {
726
+ return [
727
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">',
728
+ ' <path d="M14 18h36v28H14z" stroke="#fff" stroke-width="4" stroke-linejoin="round"/>',
729
+ ' <path d="M24 28h16" stroke="#fff" stroke-width="4" stroke-linecap="round"/>',
730
+ ' <path d="M24 36h10" stroke="#fff" stroke-width="4" stroke-linecap="round"/>',
731
+ '</svg>',
732
+ '',
733
+ ].join('\n');
734
+ }
735
+ function buildAssetSpecContractTemplate() {
736
+ return `${JSON.stringify({
737
+ primaryRole: 'primary',
738
+ roles: [
739
+ {
740
+ name: 'primary',
741
+ required: true,
742
+ contentTypes: ['application/json'],
743
+ extensions: ['.json'],
744
+ mode: 'TEXT',
745
+ },
746
+ ],
747
+ }, null, 2)}\n`;
748
+ }
749
+ function buildAssetSpecReadmeTemplate(name) {
750
+ return [
751
+ `# ${name}`,
752
+ '',
753
+ 'Describe the custom asset format in plain English.',
754
+ '',
755
+ '## Files',
756
+ '',
757
+ '- `primary`: Main JSON payload for this asset format.',
758
+ '',
759
+ '## Notes',
760
+ '',
761
+ '- Update `contract.json` as the format evolves.',
762
+ '- Keep this documentation in sync with the published version.',
763
+ '',
764
+ ].join('\n');
765
+ }
766
+ async function createAssetSpecProject(name) {
767
+ const normalizedName = name.trim();
768
+ if (!normalizedName) {
769
+ (0, messages_1.printErrorWithHelp)('An asset spec name is required.', ['Example: playdrop project create asset-spec platformer-level'], {
770
+ command: 'project create asset-spec',
771
+ });
772
+ process.exitCode = process.exitCode || 1;
773
+ return;
774
+ }
775
+ if (!(0, types_1.isValidAssetSpecName)(normalizedName)) {
776
+ (0, messages_1.printErrorWithHelp)(`Asset spec name "${normalizedName}" is not valid.`, ['Use lowercase kebab-case, for example: playdrop project create asset-spec platformer-level'], { command: 'project create asset-spec' });
777
+ process.exitCode = process.exitCode || 1;
778
+ return;
779
+ }
780
+ const cataloguePath = (0, node_path_1.resolve)(process.cwd(), CATALOGUE_FILENAME);
781
+ const assetSpecDir = (0, node_path_1.resolve)(process.cwd(), 'asset-specs', normalizedName);
782
+ const contractPath = (0, node_path_1.resolve)(assetSpecDir, 'contract.json');
783
+ const symbolPath = (0, node_path_1.resolve)(assetSpecDir, 'symbol.svg');
784
+ const readmePath = (0, node_path_1.resolve)(assetSpecDir, 'README.md');
785
+ if ((0, node_fs_1.existsSync)(assetSpecDir)) {
786
+ (0, messages_1.printErrorWithHelp)(`Asset spec directory already exists at ${(0, node_path_1.relative)(process.cwd(), assetSpecDir) || assetSpecDir}.`, ['Choose a different asset spec name or remove the existing directory before rerunning the command.'], { command: 'project create asset-spec' });
787
+ process.exitCode = process.exitCode || 1;
788
+ return;
789
+ }
790
+ (0, node_fs_1.mkdirSync)(assetSpecDir, { recursive: true });
791
+ (0, node_fs_1.writeFileSync)(symbolPath, buildAssetSpecSymbolSvg());
792
+ (0, node_fs_1.writeFileSync)(contractPath, buildAssetSpecContractTemplate());
793
+ (0, node_fs_1.writeFileSync)(readmePath, buildAssetSpecReadmeTemplate(normalizedName));
794
+ const catalogueResult = ensureAssetSpecCatalogueEntry(normalizedName, cataloguePath);
795
+ const catalogueUpdated = logAssetSpecCatalogueOutcome(catalogueResult, normalizedName);
796
+ if (!catalogueUpdated) {
797
+ (0, node_fs_1.rmSync)(assetSpecDir, { recursive: true, force: true });
798
+ return;
799
+ }
800
+ const relativeAssetSpecDir = (0, node_path_1.relative)(process.cwd(), assetSpecDir) || assetSpecDir;
801
+ const catalogueLabel = (0, node_path_1.relative)(process.cwd(), cataloguePath) || CATALOGUE_FILENAME;
802
+ console.log('\nNext steps:');
803
+ console.log(` - Review ${catalogueLabel} and update the assetSpecs entry for ${normalizedName}.`);
804
+ console.log(` - Edit ${relativeAssetSpecDir}/contract.json to define the file contract.`);
805
+ console.log(` - Replace ${relativeAssetSpecDir}/symbol.svg with your final icon.`);
806
+ console.log(` - Expand ${relativeAssetSpecDir}/README.md with format documentation.`);
807
+ console.log(` - Validate locally with "playdrop project validate ." and publish with "playdrop project publish ${normalizedName}".`);
808
+ }
629
809
  function warnMissingWorkspaceReadme(cataloguePath) {
630
810
  const workspaceRoot = findWorkspaceRootDirectory(cataloguePath);
631
811
  const readmePath = (0, node_path_1.resolve)(workspaceRoot, 'README.md');
@@ -3,8 +3,14 @@ type TransactionsOptions = {
3
3
  offset?: string | number;
4
4
  json?: boolean;
5
5
  };
6
+ type EarningsOptions = {
7
+ days?: string | number;
8
+ appId?: string | number;
9
+ json?: boolean;
10
+ };
6
11
  export declare function showCreditBalance(options?: {
7
12
  json?: boolean;
8
13
  }): Promise<void>;
9
14
  export declare function browseCreditTransactions(options?: TransactionsOptions): Promise<void>;
15
+ export declare function showCreatorEarnings(options?: EarningsOptions): Promise<void>;
10
16
  export {};
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.showCreditBalance = showCreditBalance;
4
4
  exports.browseCreditTransactions = browseCreditTransactions;
5
+ exports.showCreatorEarnings = showCreatorEarnings;
5
6
  const commandContext_1 = require("../commandContext");
6
7
  const errors_1 = require("../errors");
7
8
  const messages_1 = require("../messages");
@@ -19,6 +20,19 @@ function parsePositiveInteger(raw, label, fallback) {
19
20
  }
20
21
  return parsed;
21
22
  }
23
+ function formatTransactionTitle(transaction) {
24
+ if (transaction.type === 'AD_PAYOUT') {
25
+ const appName = typeof transaction.metadata?.appName === 'string' ? transaction.metadata.appName : transaction.displayName;
26
+ const adFormat = typeof transaction.metadata?.adFormat === 'string' ? transaction.metadata.adFormat : null;
27
+ const dateUtc = typeof transaction.metadata?.dateUtc === 'string' ? transaction.metadata.dateUtc : null;
28
+ return [appName, adFormat, dateUtc].filter(Boolean).join(' | ');
29
+ }
30
+ if (transaction.type === 'BOOST_PURCHASE') {
31
+ const units = typeof transaction.metadata?.units === 'number' ? transaction.metadata.units : null;
32
+ return units ? `${transaction.displayName} | ${units} units` : transaction.displayName;
33
+ }
34
+ return transaction.displayName;
35
+ }
22
36
  async function showCreditBalance(options = {}) {
23
37
  await (0, commandContext_1.withEnvironment)('credits balance', 'Checking your credit balance', async ({ client }) => {
24
38
  try {
@@ -72,7 +86,7 @@ async function browseCreditTransactions(options = {}) {
72
86
  console.log('Credit transactions:\n');
73
87
  for (const transaction of transactions) {
74
88
  console.log(`#${transaction.id} | ${transaction.kind.toLowerCase()} | ${transaction.amount} | ${transaction.status.toLowerCase()} | ${(0, output_1.formatTimestamp)(transaction.createdAt)}`);
75
- console.log(` ${transaction.displayName}`);
89
+ console.log(` ${formatTransactionTitle(transaction)}`);
76
90
  }
77
91
  console.log('\nNext: run "playdrop credits balance" to confirm your latest balance.');
78
92
  }
@@ -89,3 +103,35 @@ async function browseCreditTransactions(options = {}) {
89
103
  }
90
104
  });
91
105
  }
106
+ async function showCreatorEarnings(options = {}) {
107
+ const days = parsePositiveInteger(options.days, 'days', 30);
108
+ if (days === null) {
109
+ (0, messages_1.printErrorWithHelp)('The --days value must be a positive integer.', ['Example: --days 30'], { command: 'credits earnings' });
110
+ process.exitCode = 1;
111
+ return;
112
+ }
113
+ const appId = options.appId === undefined ? undefined : parsePositiveInteger(options.appId, 'appId', 0);
114
+ if (options.appId !== undefined && appId === null) {
115
+ (0, messages_1.printErrorWithHelp)('The --app-id value must be a positive integer.', ['Example: --app-id 123'], { command: 'credits earnings' });
116
+ process.exitCode = 1;
117
+ return;
118
+ }
119
+ await (0, commandContext_1.withEnvironment)('credits earnings', 'Checking creator ad earnings', async ({ client }) => {
120
+ const response = await client.fetchCreatorEarnings({
121
+ days,
122
+ ...(appId && appId > 0 ? { appId } : {}),
123
+ });
124
+ if (options.json) {
125
+ (0, output_1.printJson)(response);
126
+ return;
127
+ }
128
+ console.log(`Today provisional: ${response.summary.provisionalToday}`);
129
+ console.log(`Last 7 days settled: ${response.summary.settledLast7Days}`);
130
+ console.log(`Last 30 days settled: ${response.summary.settledLast30Days}`);
131
+ console.log(`Last 30 days boost spend: ${response.summary.boostSpendLast30Days}`);
132
+ console.log('\nDaily rows:\n');
133
+ for (const row of response.days) {
134
+ console.log(`${row.dateUtc} | total ${row.totalAmount} | interstitial ${row.interstitialAmount} | rewarded ${row.rewardedAmount} | served ${row.servedCount} | credited ${row.creditedCount}${row.provisional ? ' | provisional' : ''}`);
135
+ }
136
+ });
137
+ }
@@ -10,7 +10,7 @@ const appUrls_1 = require("../appUrls");
10
10
  function printRefValidationError(raw) {
11
11
  (0, messages_1.printErrorWithHelp)('A canonical ref is required.', [
12
12
  'Use the format <creator>/<kind>/<name>.',
13
- 'Kinds must be exactly app, asset, or asset-pack.',
13
+ 'Kinds must be exactly app, asset, asset-spec, or asset-pack.',
14
14
  `Example: playdrop detail playdrop/app/hangingout`,
15
15
  `Example: playdrop detail playdrop/asset/astro`,
16
16
  ], { command: 'detail' });
@@ -73,6 +73,13 @@ function decorateAssetPack(apiBase, ref, pack) {
73
73
  },
74
74
  };
75
75
  }
76
+ function formatTagSummary(tags) {
77
+ const normalizedTags = Array.isArray(tags) ? tags : [];
78
+ if (normalizedTags.length === 0) {
79
+ return 'none';
80
+ }
81
+ return normalizedTags.map((tag) => `${tag.ref} (${tag.displayName})`).join(', ');
82
+ }
76
83
  function printAppText(ref, app) {
77
84
  console.log(`[app] ${ref}`);
78
85
  console.log(`Display name: ${(0, output_1.formatOptionalValue)(app.displayName)}`);
@@ -82,6 +89,7 @@ function printAppText(ref, app) {
82
89
  console.log(`Created: ${(0, output_1.formatTimestamp)(app.createdAt)}`);
83
90
  console.log(`Updated: ${(0, output_1.formatTimestamp)(app.updatedAt)}`);
84
91
  console.log(`Current version: ${(0, output_1.formatOptionalValue)(app.currentVersion)}`);
92
+ console.log(`Tags: ${formatTagSummary(app.tags)}`);
85
93
  console.log(`Play URL: ${(0, output_1.formatOptionalValue)(app.urls.play)}`);
86
94
  console.log(`Source URL: ${(0, output_1.formatOptionalValue)(app.urls.source)}`);
87
95
  console.log('\nNext: run "playdrop versions browse ' + ref + '" to inspect version history.');
@@ -96,6 +104,7 @@ function printAssetText(ref, asset) {
96
104
  console.log(`Updated: ${(0, output_1.formatTimestamp)(asset.updatedAt)}`);
97
105
  console.log(`Current revision: ${asset.currentVersion ? asset.currentVersion.revisionLabel : 'none'}`);
98
106
  console.log(`Current subcategory: ${(0, output_1.formatOptionalValue)(asset.currentVersion?.subcategory)}`);
107
+ console.log(`Tags: ${formatTagSummary(asset.tags)}`);
99
108
  console.log(`Source URL: ${(0, output_1.formatOptionalValue)(asset.urls.source)}`);
100
109
  if (asset.urls.files.length > 0) {
101
110
  for (const file of asset.urls.files) {
@@ -115,10 +124,25 @@ function printAssetPackText(ref, pack) {
115
124
  console.log(`Created: ${(0, output_1.formatTimestamp)(pack.createdAt)}`);
116
125
  console.log(`Updated: ${(0, output_1.formatTimestamp)(pack.updatedAt)}`);
117
126
  console.log(`Current version: ${(0, output_1.formatOptionalValue)(pack.currentVersion?.version)}`);
127
+ console.log(`Tags: ${formatTagSummary(pack.tags)}`);
118
128
  console.log(`Download URL: ${(0, output_1.formatOptionalValue)(pack.urls.download)}`);
119
129
  console.log(`Source URL: ${(0, output_1.formatOptionalValue)(pack.urls.source)}`);
120
130
  console.log('\nNext: run "playdrop versions browse ' + ref + '" to inspect other versions.');
121
131
  }
132
+ function printAssetSpecText(ref, assetSpec) {
133
+ console.log(`[asset-spec] ${ref}`);
134
+ console.log(`Display name: ${(0, output_1.formatOptionalValue)(assetSpec.displayName)}`);
135
+ console.log(`Creator: @${assetSpec.creatorUsername}`);
136
+ console.log(`Description: ${(0, output_1.formatOptionalValue)(assetSpec.description)}`);
137
+ console.log(`Current version: ${(0, output_1.formatOptionalValue)(assetSpec.currentVersion?.version)}`);
138
+ console.log(`Status: ${(0, output_1.formatOptionalValue)(assetSpec.status)}`);
139
+ console.log(`Public assets: ${assetSpec.publicAssetCount.toLocaleString('en-US')}`);
140
+ console.log(`Supporting apps: ${assetSpec.supportingAppCount.toLocaleString('en-US')}`);
141
+ console.log(`Documentation: ${assetSpec.documentationAvailable ? 'available' : 'none'}`);
142
+ console.log(`Created: ${(0, output_1.formatTimestamp)(assetSpec.createdAt)}`);
143
+ console.log(`Updated: ${(0, output_1.formatTimestamp)(assetSpec.updatedAt)}`);
144
+ console.log('\nNext: run "playdrop versions browse ' + ref + '" to inspect version history.');
145
+ }
122
146
  async function detail(rawRef, options = {}) {
123
147
  let ref;
124
148
  try {
@@ -150,13 +174,23 @@ async function detail(rawRef, options = {}) {
150
174
  printAssetText(ref.ref, item);
151
175
  return;
152
176
  }
153
- const response = await client.fetchAssetPackBySlug(ref.creator, ref.name);
154
- const item = decorateAssetPack(envConfig.apiBase, ref, response.pack);
177
+ if (ref.kind === 'asset-pack') {
178
+ const response = await client.fetchAssetPackBySlug(ref.creator, ref.name);
179
+ const item = decorateAssetPack(envConfig.apiBase, ref, response.pack);
180
+ if (options.json) {
181
+ (0, output_1.printJson)({ kind: ref.kind, ref: ref.ref, item });
182
+ return;
183
+ }
184
+ printAssetPackText(ref.ref, item);
185
+ return;
186
+ }
187
+ const response = await client.fetchAssetSpecBySlug(ref.creator, ref.name);
188
+ const item = response.assetSpec;
155
189
  if (options.json) {
156
190
  (0, output_1.printJson)({ kind: ref.kind, ref: ref.ref, item });
157
191
  return;
158
192
  }
159
- printAssetPackText(ref.ref, item);
193
+ printAssetSpecText(ref.ref, item);
160
194
  }
161
195
  catch (error) {
162
196
  const handled = (0, errors_1.handleCommandFailure)(error, 'detail', 'Detail lookup', {
@@ -69,17 +69,22 @@ function spawnDevScript(projectInfo) {
69
69
  stdio: 'inherit',
70
70
  env: { ...process.env },
71
71
  });
72
- child.on('exit', code => {
73
- if (code !== 0) {
74
- console.warn(`npm run dev exited with code ${code}.`);
75
- }
76
- });
77
72
  return child;
78
73
  }
79
74
  async function startDevServer(options) {
80
75
  const { appName, htmlPath, port, projectInfo } = options;
81
76
  let closing = false;
82
77
  const devProcess = spawnDevScript(projectInfo);
78
+ if (devProcess) {
79
+ devProcess.on('exit', code => {
80
+ if (closing) {
81
+ return;
82
+ }
83
+ if (code !== 0 && code !== null) {
84
+ console.warn(`npm run dev exited with code ${code}.`);
85
+ }
86
+ });
87
+ }
83
88
  const startedDevProcess = Boolean(devProcess);
84
89
  const staticRoot = (0, node_path_1.resolve)((0, node_path_1.dirname)(htmlPath));
85
90
  const server = node_http_1.default.createServer((req, res) => {
@@ -1,3 +1,4 @@
1
+ import type { ImageAttachment } from '@playdrop/ai-client';
1
2
  type AiGenerateOptions = {
2
3
  subcategory?: string;
3
4
  assetName?: string;
@@ -28,6 +29,7 @@ type AiListOptions = {
28
29
  offset?: string | number;
29
30
  format?: string;
30
31
  };
32
+ export declare function resolveImageAttachment(value: string | undefined, optionName: 'image1' | 'image2'): ImageAttachment | undefined;
31
33
  export declare function generate(typeInput: string, promptInput: string, options?: AiGenerateOptions): Promise<void>;
32
34
  type AiJobBrowseOptions = {
33
35
  type?: string;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveImageAttachment = resolveImageAttachment;
3
4
  exports.generate = generate;
4
5
  exports.browseGenerationJobs = browseGenerationJobs;
5
6
  exports.showGenerationJob = showGenerationJob;
@@ -3,10 +3,15 @@ type SearchOptions = {
3
3
  appType?: string;
4
4
  assetCategory?: string;
5
5
  assetSubcategory?: string;
6
+ assetSpec?: string;
7
+ assetSpecOwner?: string;
8
+ assetSpecName?: string;
6
9
  packContainsCategory?: string;
7
10
  packContainsSubcategory?: string;
11
+ sort?: string;
8
12
  limit?: string | number;
9
13
  offset?: string | number;
14
+ tag?: string[];
10
15
  json?: boolean;
11
16
  };
12
17
  export declare function search(query: string | undefined, options?: SearchOptions): Promise<void>;