@playdrop/playdrop-cli 0.5.2 → 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 (103) hide show
  1. package/config/client-meta.json +4 -4
  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 +126 -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/commands/ads.d.ts +8 -0
  13. package/dist/commands/ads.js +124 -0
  14. package/dist/commands/boosts.d.ts +25 -0
  15. package/dist/commands/boosts.js +209 -0
  16. package/dist/commands/browse.d.ts +6 -1
  17. package/dist/commands/browse.js +365 -124
  18. package/dist/commands/captureListing.d.ts +53 -0
  19. package/dist/commands/captureListing.js +804 -0
  20. package/dist/commands/create.d.ts +1 -0
  21. package/dist/commands/create.js +183 -3
  22. package/dist/commands/credits.d.ts +6 -0
  23. package/dist/commands/credits.js +47 -1
  24. package/dist/commands/detail.js +38 -4
  25. package/dist/commands/devServer.js +10 -5
  26. package/dist/commands/search.d.ts +5 -0
  27. package/dist/commands/search.js +139 -17
  28. package/dist/commands/tags.d.ts +7 -0
  29. package/dist/commands/tags.js +63 -0
  30. package/dist/commands/upload-content.d.ts +13 -3
  31. package/dist/commands/upload-content.js +86 -20
  32. package/dist/commands/upload.d.ts +2 -0
  33. package/dist/commands/upload.js +187 -11
  34. package/dist/commands/validate.js +163 -2
  35. package/dist/commands/versionsBrowse.js +128 -91
  36. package/dist/index.js +145 -3
  37. package/dist/refs.d.ts +2 -2
  38. package/dist/refs.js +13 -1
  39. package/dist/taskSelection.js +6 -3
  40. package/dist/taskUtils.d.ts +2 -2
  41. package/dist/taskUtils.js +1 -0
  42. package/dist/uploadLog.d.ts +1 -1
  43. package/dist/uploadLog.js +2 -2
  44. package/node_modules/@playdrop/ai-client/package.json +1 -1
  45. package/node_modules/@playdrop/api-client/dist/client.d.ts +131 -10
  46. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  47. package/node_modules/@playdrop/api-client/dist/client.js +6 -0
  48. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +9 -1
  49. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  50. package/node_modules/@playdrop/api-client/dist/domains/admin.js +45 -0
  51. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +3 -0
  52. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  53. package/node_modules/@playdrop/api-client/dist/domains/apps.js +27 -0
  54. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +2 -0
  55. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
  56. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +16 -0
  57. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +44 -2
  58. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  59. package/node_modules/@playdrop/api-client/dist/domains/assets.js +260 -3
  60. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +17 -1
  61. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  62. package/node_modules/@playdrop/api-client/dist/domains/payments.js +173 -0
  63. package/node_modules/@playdrop/api-client/dist/domains/search.d.ts.map +1 -1
  64. package/node_modules/@playdrop/api-client/dist/domains/search.js +39 -11
  65. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts +34 -0
  66. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts.map +1 -0
  67. package/node_modules/@playdrop/api-client/dist/domains/tags.js +111 -0
  68. package/node_modules/@playdrop/api-client/dist/index.d.ts +61 -1
  69. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  70. package/node_modules/@playdrop/api-client/dist/index.js +50 -0
  71. package/node_modules/@playdrop/api-client/package.json +1 -1
  72. package/node_modules/@playdrop/boxel-core/package.json +1 -1
  73. package/node_modules/@playdrop/boxel-three/package.json +1 -1
  74. package/node_modules/@playdrop/config/client-meta.json +4 -4
  75. package/node_modules/@playdrop/config/dist/src/constants.d.ts +11 -0
  76. package/node_modules/@playdrop/config/dist/src/constants.d.ts.map +1 -1
  77. package/node_modules/@playdrop/config/dist/src/constants.js +12 -1
  78. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  79. package/node_modules/@playdrop/config/package.json +1 -1
  80. package/node_modules/@playdrop/types/dist/api.d.ts +346 -6
  81. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  82. package/node_modules/@playdrop/types/dist/api.js +52 -1
  83. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +7 -1
  84. package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
  85. package/node_modules/@playdrop/types/dist/asset-spec-contract-meta-schema.json +86 -0
  86. package/node_modules/@playdrop/types/dist/asset-spec.d.ts +163 -0
  87. package/node_modules/@playdrop/types/dist/asset-spec.d.ts.map +1 -0
  88. package/node_modules/@playdrop/types/dist/asset-spec.js +101 -0
  89. package/node_modules/@playdrop/types/dist/asset.d.ts +23 -6
  90. package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
  91. package/node_modules/@playdrop/types/dist/asset.js +4 -1
  92. package/node_modules/@playdrop/types/dist/graph.d.ts +4 -2
  93. package/node_modules/@playdrop/types/dist/graph.d.ts.map +1 -1
  94. package/node_modules/@playdrop/types/dist/graph.js +9 -2
  95. package/node_modules/@playdrop/types/dist/index.d.ts +1 -0
  96. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  97. package/node_modules/@playdrop/types/dist/index.js +1 -0
  98. package/node_modules/@playdrop/types/dist/version.d.ts +13 -0
  99. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  100. package/node_modules/@playdrop/types/dist/version.js +21 -0
  101. package/node_modules/@playdrop/types/package.json +6 -1
  102. package/node_modules/@playdrop/vox-three/package.json +1 -1
  103. package/package.json +3 -1
@@ -57,18 +57,144 @@ function printEntry(entry) {
57
57
  }
58
58
  return;
59
59
  }
60
+ if (entry.kind === 'asset-spec') {
61
+ const currentLabel = entry.isCurrent ? ' | current' : '';
62
+ console.log(`${entry.version} | ${entry.visibility.toLowerCase()} | ${entry.status.toLowerCase()}${currentLabel} | ${(0, output_1.formatTimestamp)(entry.createdAt)}`);
63
+ console.log(` Documentation URL: ${(0, output_1.formatOptionalValue)(entry.documentationUrl)}`);
64
+ return;
65
+ }
60
66
  const currentLabel = entry.isCurrent ? ' | current' : '';
61
67
  console.log(`${entry.version} | ${entry.visibility.toLowerCase()}${currentLabel} | ${(0, output_1.formatTimestamp)(entry.createdAt)}`);
62
68
  console.log(` Download URL: ${(0, output_1.formatOptionalValue)(entry.urls.download)}`);
63
69
  console.log(` Source URL: ${entry.urls.source}`);
64
70
  }
71
+ async function loadVersionEntries(input) {
72
+ const { client, envConfig, ref, limit, offset } = input;
73
+ if (ref.kind === 'app') {
74
+ const [appDetail, response] = await Promise.all([
75
+ client.fetchAppBySlug(ref.creator, ref.name),
76
+ client.listAppVersions(ref.creator, ref.name),
77
+ ]);
78
+ const listed = response.versions ?? [];
79
+ const selected = listed.slice(offset, offset + limit);
80
+ const entries = await Promise.all(selected.map(async (version) => {
81
+ const detail = await client.getAppVersion(ref.creator, ref.name, version.version);
82
+ return {
83
+ kind: 'app',
84
+ version: version.version,
85
+ visibility: version.visibility,
86
+ createdAt: version.createdAt,
87
+ updatedAt: detail.version.updatedAt,
88
+ releaseNotes: version.releaseNotes ?? null,
89
+ isCurrent: Boolean(version.isCurrent),
90
+ urls: {
91
+ play: (0, appUrls_1.buildPlatformPlayUrl)(envConfig.webBase, {
92
+ creatorUsername: ref.creator,
93
+ appName: ref.name,
94
+ appType: appDetail.app.type,
95
+ }),
96
+ source: typeof detail.version.sourceUrl === 'string' ? detail.version.sourceUrl : null,
97
+ },
98
+ };
99
+ }));
100
+ return {
101
+ entries,
102
+ pagination: {
103
+ limit,
104
+ offset,
105
+ count: entries.length,
106
+ hasMore: listed.length > offset + limit,
107
+ },
108
+ };
109
+ }
110
+ if (ref.kind === 'asset') {
111
+ const [detail, response] = await Promise.all([
112
+ client.fetchAssetBySlug(ref.creator, ref.name),
113
+ client.listAssetVersions(ref.creator, ref.name, { limit, offset }),
114
+ ]);
115
+ const currentRevision = detail.asset.currentVersion?.revision ?? null;
116
+ return {
117
+ entries: (response.versions ?? []).map((version) => ({
118
+ kind: 'asset',
119
+ revision: version.revision,
120
+ revisionLabel: version.revisionLabel,
121
+ visibility: version.visibility,
122
+ createdAt: version.createdAt,
123
+ updatedAt: version.updatedAt,
124
+ subcategory: version.subcategory,
125
+ format: version.format,
126
+ isCurrent: currentRevision === version.revision,
127
+ urls: {
128
+ source: buildAssetSourceUrl(envConfig.apiBase, ref, version.revision),
129
+ files: assetFileUrls(version),
130
+ },
131
+ })),
132
+ pagination: response.pagination ?? {
133
+ limit,
134
+ offset,
135
+ count: (response.versions ?? []).length,
136
+ hasMore: false,
137
+ },
138
+ };
139
+ }
140
+ if (ref.kind === 'asset-pack') {
141
+ const [detail, response] = await Promise.all([
142
+ client.fetchAssetPackBySlug(ref.creator, ref.name),
143
+ client.listAssetPackVersions(ref.creator, ref.name, { limit, offset }),
144
+ ]);
145
+ const currentVersion = detail.pack.currentVersion?.version ?? null;
146
+ return {
147
+ entries: (response.versions ?? []).map((version) => ({
148
+ kind: 'asset-pack',
149
+ version: version.version,
150
+ visibility: version.visibility,
151
+ createdAt: version.createdAt,
152
+ updatedAt: version.updatedAt,
153
+ isCurrent: currentVersion === version.version,
154
+ urls: {
155
+ download: buildAssetPackDownloadUrl(envConfig.apiBase, ref, version.version),
156
+ source: buildAssetPackSourceUrl(envConfig.apiBase, ref, version.version),
157
+ },
158
+ })),
159
+ pagination: response.pagination ?? {
160
+ limit,
161
+ offset,
162
+ count: (response.versions ?? []).length,
163
+ hasMore: false,
164
+ },
165
+ };
166
+ }
167
+ const [detail, response] = await Promise.all([
168
+ client.fetchAssetSpecBySlug(ref.creator, ref.name),
169
+ client.listAssetSpecVersions(ref.creator, ref.name, { limit, offset }),
170
+ ]);
171
+ const currentVersion = detail.assetSpec.currentVersion?.version ?? null;
172
+ return {
173
+ entries: (response.versions ?? []).map((version) => ({
174
+ kind: 'asset-spec',
175
+ version: version.version,
176
+ visibility: version.visibility,
177
+ status: version.status,
178
+ createdAt: version.createdAt,
179
+ updatedAt: version.updatedAt,
180
+ isCurrent: currentVersion === version.version,
181
+ documentationUrl: version.documentationUrl ?? null,
182
+ })),
183
+ pagination: response.pagination ?? {
184
+ limit,
185
+ offset,
186
+ count: (response.versions ?? []).length,
187
+ hasMore: false,
188
+ },
189
+ };
190
+ }
65
191
  async function browseVersions(rawRef, options = {}) {
66
192
  let ref;
67
193
  try {
68
194
  ref = (0, refs_1.parseContentRef)(rawRef ?? '');
69
195
  }
70
196
  catch {
71
- (0, messages_1.printErrorWithHelp)('A canonical ref is required.', ['Use the format <creator>/<kind>/<name>.', 'Example: playdrop versions browse playdrop/app/hangingout'], { command: 'versions browse' });
197
+ (0, messages_1.printErrorWithHelp)('A canonical ref is required.', ['Use the format <creator>/<kind>/<name>.', 'Example: playdrop versions browse playdrop/app/hangingout', 'Example: playdrop versions browse playdrop/asset-spec/platformer-level'], { command: 'versions browse' });
72
198
  process.exitCode = 1;
73
199
  return;
74
200
  }
@@ -86,96 +212,7 @@ async function browseVersions(rawRef, options = {}) {
86
212
  }
87
213
  await (0, commandContext_1.withPublicEnvironment)('versions browse', async ({ client, envConfig }) => {
88
214
  try {
89
- let entries = [];
90
- let pagination;
91
- if (ref.kind === 'app') {
92
- const [appDetail, response] = await Promise.all([
93
- client.fetchAppBySlug(ref.creator, ref.name),
94
- client.listAppVersions(ref.creator, ref.name),
95
- ]);
96
- const listed = response.versions ?? [];
97
- const selected = listed.slice(offset, offset + limit);
98
- const details = await Promise.all(selected.map(async (version) => {
99
- const detail = await client.getAppVersion(ref.creator, ref.name, version.version);
100
- return {
101
- kind: 'app',
102
- version: version.version,
103
- visibility: version.visibility,
104
- createdAt: version.createdAt,
105
- updatedAt: detail.version.updatedAt,
106
- releaseNotes: version.releaseNotes ?? null,
107
- isCurrent: Boolean(version.isCurrent),
108
- urls: {
109
- play: (0, appUrls_1.buildPlatformPlayUrl)(envConfig.webBase, {
110
- creatorUsername: ref.creator,
111
- appName: ref.name,
112
- appType: appDetail.app.type,
113
- }),
114
- source: typeof detail.version.sourceUrl === 'string' ? detail.version.sourceUrl : null,
115
- },
116
- };
117
- }));
118
- entries = details;
119
- pagination = {
120
- limit,
121
- offset,
122
- count: entries.length,
123
- hasMore: listed.length > offset + limit,
124
- };
125
- }
126
- else if (ref.kind === 'asset') {
127
- const [detail, response] = await Promise.all([
128
- client.fetchAssetBySlug(ref.creator, ref.name),
129
- client.listAssetVersions(ref.creator, ref.name, { limit, offset }),
130
- ]);
131
- const currentRevision = detail.asset.currentVersion?.revision ?? null;
132
- entries = (response.versions ?? []).map((version) => ({
133
- kind: 'asset',
134
- revision: version.revision,
135
- revisionLabel: version.revisionLabel,
136
- visibility: version.visibility,
137
- createdAt: version.createdAt,
138
- updatedAt: version.updatedAt,
139
- subcategory: version.subcategory,
140
- format: version.format,
141
- isCurrent: currentRevision === version.revision,
142
- urls: {
143
- source: buildAssetSourceUrl(envConfig.apiBase, ref, version.revision),
144
- files: assetFileUrls(version),
145
- },
146
- }));
147
- pagination = response.pagination ?? {
148
- limit,
149
- offset,
150
- count: entries.length,
151
- hasMore: false,
152
- };
153
- }
154
- else {
155
- const [detail, response] = await Promise.all([
156
- client.fetchAssetPackBySlug(ref.creator, ref.name),
157
- client.listAssetPackVersions(ref.creator, ref.name, { limit, offset }),
158
- ]);
159
- const currentVersion = detail.pack.currentVersion?.version ?? null;
160
- entries = (response.versions ?? []).map((version) => ({
161
- kind: 'asset-pack',
162
- version: version.version,
163
- visibility: version.visibility,
164
- createdAt: version.createdAt,
165
- updatedAt: version.updatedAt,
166
- isCurrent: currentVersion === version.version,
167
- urls: {
168
- download: buildAssetPackDownloadUrl(envConfig.apiBase, ref, version.version),
169
- source: buildAssetPackSourceUrl(envConfig.apiBase, ref, version.version),
170
- },
171
- }));
172
- pagination = response.pagination ?? {
173
- limit,
174
- offset,
175
- count: entries.length,
176
- hasMore: false,
177
- };
178
- }
215
+ const { entries, pagination } = await loadVersionEntries({ client, envConfig, ref, limit, offset });
179
216
  if (options.json) {
180
217
  (0, output_1.printJson)({ kind: ref.kind, ref: ref.ref, entries, pagination });
181
218
  return;
package/dist/index.js CHANGED
@@ -10,11 +10,14 @@ const whoami_1 = require("./commands/whoami");
10
10
  const logout_1 = require("./commands/logout");
11
11
  const browse_1 = require("./commands/browse");
12
12
  const search_1 = require("./commands/search");
13
+ const tags_1 = require("./commands/tags");
13
14
  const detail_1 = require("./commands/detail");
14
15
  const play_1 = require("./commands/play");
16
+ const ads_1 = require("./commands/ads");
15
17
  const comments_1 = require("./commands/comments");
16
18
  const versionsBrowse_1 = require("./commands/versionsBrowse");
17
19
  const credits_1 = require("./commands/credits");
20
+ const boosts_1 = require("./commands/boosts");
18
21
  const notifications_1 = require("./commands/notifications");
19
22
  const creations_1 = require("./commands/creations");
20
23
  const generation_1 = require("./commands/generation");
@@ -23,6 +26,7 @@ const create_1 = require("./commands/create");
23
26
  const createRemixContent_1 = require("./commands/createRemixContent");
24
27
  const dev_1 = require("./commands/dev");
25
28
  const capture_1 = require("./commands/capture");
29
+ const captureListing_1 = require("./commands/captureListing");
26
30
  const captureRemote_1 = require("./commands/captureRemote");
27
31
  const validate_1 = require("./commands/validate");
28
32
  const build_1 = require("./commands/build");
@@ -95,6 +99,9 @@ program.on('command:*', (unknownCommands) => {
95
99
  function createEnvOption() {
96
100
  return new commander_1.Option('--env <env>', 'Environment (dev|local|prod)').default('prod');
97
101
  }
102
+ function collectRepeatedOption(value, previous = []) {
103
+ return [...previous, value];
104
+ }
98
105
  function registerLoginCommand(parent) {
99
106
  parent
100
107
  .command('login')
@@ -136,13 +143,18 @@ registerLogoutCommand(auth);
136
143
  registerWhoamiCommand(auth);
137
144
  const browseCommand = program.command('browse').description('Browse public content');
138
145
  browseCommand
139
- .option('--kind <kind>', 'app, asset, asset-pack, or all')
146
+ .option('--kind <kind>', 'app, asset, asset-pack, asset-spec, or all')
140
147
  .option('--creator <creator>', 'all, me, or a creator slug')
141
148
  .option('--app-type <type>', 'game, demo, tool, or template')
142
149
  .option('--asset-category <category>', 'Asset category filter')
143
150
  .option('--asset-subcategory <subcategory>', 'Asset subcategory filter')
151
+ .option('--asset-spec <ref>', 'Asset spec family or version filter for assets')
152
+ .option('--asset-spec-owner <creator>', 'Asset spec owner filter for assets')
153
+ .option('--asset-spec-name <name>', 'Asset spec name filter for assets')
154
+ .option('--sort <sort>', 'Sort asset or asset-spec browse results')
144
155
  .option('--limit <number>', 'Maximum number of results to return')
145
156
  .option('--offset <number>', 'Result offset')
157
+ .option('--tag <ref>', 'Filter by canonical tag ref', collectRepeatedOption, [])
146
158
  .option('--json', 'Output JSON')
147
159
  .action(async (opts) => {
148
160
  await (0, browse_1.browse)(opts);
@@ -157,18 +169,33 @@ browseCommand
157
169
  program
158
170
  .command('search <query>')
159
171
  .description('Search public content')
160
- .option('--kind <kind>', 'app, asset, asset-pack, or all')
172
+ .option('--kind <kind>', 'app, asset, asset-pack, asset-spec, or all')
161
173
  .option('--app-type <type>', 'game, demo, tool, or template')
162
174
  .option('--asset-category <category>', 'Asset category filter')
163
175
  .option('--asset-subcategory <subcategory>', 'Asset subcategory filter')
176
+ .option('--asset-spec <ref>', 'Asset spec family or version filter for assets')
177
+ .option('--asset-spec-owner <creator>', 'Asset spec owner filter for assets')
178
+ .option('--asset-spec-name <name>', 'Asset spec name filter for assets')
179
+ .option('--sort <sort>', 'Sort search results')
164
180
  .option('--pack-contains-category <category>', 'Asset pack category filter')
165
181
  .option('--pack-contains-subcategory <subcategory>', 'Asset pack subcategory filter')
166
182
  .option('--limit <number>', 'Maximum number of results to return')
167
183
  .option('--offset <number>', 'Result offset')
184
+ .option('--tag <ref>', 'Filter by canonical tag ref', collectRepeatedOption, [])
168
185
  .option('--json', 'Output JSON')
169
186
  .action(async (query, opts) => {
170
187
  await (0, search_1.search)(query, opts);
171
188
  });
189
+ const tags = program.command('tags').description('Browse and inspect tags');
190
+ tags
191
+ .command('browse')
192
+ .description('Browse public tags')
193
+ .option('--group <slug>', 'Filter to one tag group')
194
+ .option('--kind <kind>', 'Filter to one target kind')
195
+ .option('--json', 'Output JSON')
196
+ .action(async (opts) => {
197
+ await (0, tags_1.browseTags)(opts);
198
+ });
172
199
  program
173
200
  .command('detail <ref>')
174
201
  .description('Show one app, asset, or asset pack')
@@ -234,6 +261,88 @@ credits
234
261
  .action(async (opts) => {
235
262
  await (0, credits_1.browseCreditTransactions)(opts);
236
263
  });
264
+ credits
265
+ .command('earnings')
266
+ .description('Show creator ad earnings and boost spend summary')
267
+ .option('--days <number>', 'Number of days to include')
268
+ .option('--app-id <number>', 'Filter to one app id')
269
+ .option('--json', 'Output JSON')
270
+ .action(async (opts) => {
271
+ await (0, credits_1.showCreatorEarnings)(opts);
272
+ });
273
+ const ads = program.command('ads').description('Creator ad earnings and reporting');
274
+ ads
275
+ .command('earnings')
276
+ .description('Show ad earnings grouped by app and format')
277
+ .option('--app <ref>', 'Filter to one app ref')
278
+ .option('--days <number>', 'Number of days to include')
279
+ .option('--json', 'Output JSON')
280
+ .action(async (opts) => {
281
+ await (0, ads_1.showAdsEarnings)(opts);
282
+ });
283
+ ads
284
+ .command('report')
285
+ .description('Show daily ad reporting buckets')
286
+ .option('--app <ref>', 'Filter to one app ref')
287
+ .option('--days <number>', 'Number of days to include')
288
+ .option('--json', 'Output JSON')
289
+ .action(async (opts) => {
290
+ await (0, ads_1.showAdsReport)(opts);
291
+ });
292
+ const boosts = program.command('boosts').description('Buy, activate, and inspect game boosts');
293
+ boosts
294
+ .command('balance')
295
+ .description('Show your available boost inventory')
296
+ .option('--json', 'Output JSON')
297
+ .action(async (opts) => {
298
+ await (0, boosts_1.showBoostBalance)(opts);
299
+ });
300
+ boosts
301
+ .command('offers')
302
+ .description('List available boost packs')
303
+ .option('--json', 'Output JSON')
304
+ .action(async (opts) => {
305
+ await (0, boosts_1.browseBoostOffers)(opts);
306
+ });
307
+ boosts
308
+ .command('buy <sku>')
309
+ .description('Buy one boost pack')
310
+ .option('--quantity <number>', 'Number of packs to buy')
311
+ .option('--json', 'Output JSON')
312
+ .action(async (sku, opts) => {
313
+ await (0, boosts_1.buyBoostPack)(sku, opts);
314
+ });
315
+ boosts
316
+ .command('activate <ref>')
317
+ .description('Activate one or more boosts for an app')
318
+ .option('--units <number>', 'Number of boosts to consume')
319
+ .option('--json', 'Output JSON')
320
+ .action(async (ref, opts) => {
321
+ await (0, boosts_1.activateBoost)(ref, opts);
322
+ });
323
+ boosts
324
+ .command('status <ref>')
325
+ .description('Show the current boost status for an app')
326
+ .option('--json', 'Output JSON')
327
+ .action(async (ref, opts) => {
328
+ await (0, boosts_1.showBoostStatus)(ref, opts);
329
+ });
330
+ boosts
331
+ .command('history <ref>')
332
+ .description('Show past boost runs for an app')
333
+ .option('--limit <number>', 'Maximum number of boost runs to include')
334
+ .option('--json', 'Output JSON')
335
+ .action(async (ref, opts) => {
336
+ await (0, boosts_1.showBoostHistory)(ref, opts);
337
+ });
338
+ boosts
339
+ .command('report <ref>')
340
+ .description('Show daily boost performance for an app')
341
+ .option('--days <number>', 'Number of days to include')
342
+ .option('--json', 'Output JSON')
343
+ .action(async (ref, opts) => {
344
+ await (0, boosts_1.showBoostReport)(ref, opts);
345
+ });
237
346
  const notifications = program.command('notifications').description('Notification inbox');
238
347
  notifications
239
348
  .command('browse')
@@ -519,6 +628,12 @@ projectCreate
519
628
  .action(async (name, opts) => {
520
629
  await (0, create_1.create)(name, opts);
521
630
  });
631
+ projectCreate
632
+ .command('asset-spec <name>')
633
+ .description('Create a new custom asset spec scaffold')
634
+ .action(async (name) => {
635
+ await (0, create_1.createAssetSpecProject)(name);
636
+ });
522
637
  projectCreate
523
638
  .command('asset <name>')
524
639
  .description('Create a new asset remix from an exact asset version')
@@ -562,6 +677,31 @@ projectCapture
562
677
  password: opts.password,
563
678
  });
564
679
  });
680
+ projectCapture
681
+ .command('listing [target]', { hidden: true })
682
+ .description('Record private listing video for a hosted app')
683
+ .option('--app <name>', 'App name when the target is a workspace')
684
+ .option('--duration <seconds>', 'Recording duration in seconds')
685
+ .option('--width <pixels>', 'Final gameplay width in pixels')
686
+ .option('--height <pixels>', 'Final gameplay height in pixels')
687
+ .option('--fps <number>', 'Final output frame rate')
688
+ .option('--poster-at <seconds>', 'Poster frame offset in seconds')
689
+ .option('--audio', 'Capture gameplay audio')
690
+ .option('--output-dir <path>', 'Output directory for the capture artifacts')
691
+ .option('--keep-raw', 'Keep the raw intermediate recording')
692
+ .action(async (target, opts) => {
693
+ await (0, captureListing_1.captureListing)(target, {
694
+ app: opts.app,
695
+ duration: opts.duration,
696
+ width: opts.width,
697
+ height: opts.height,
698
+ fps: opts.fps,
699
+ posterAt: opts.posterAt,
700
+ audio: opts.audio,
701
+ outputDir: opts.outputDir,
702
+ keepRaw: opts.keepRaw,
703
+ });
704
+ });
565
705
  projectCapture
566
706
  .command('remote <url>')
567
707
  .description('Run a Playwright capture against a published URL')
@@ -600,8 +740,10 @@ project
600
740
  .command('publish [target]')
601
741
  .description('Publish local content to Playdrop')
602
742
  .option('--skip-ecs', 'Skip ecs.json and server.js during publish')
743
+ .option('--clear-tags', 'Confirm clearing existing live tags when this publish removes them')
744
+ .addOption(new commander_1.Option('--skip-review', 'Mark uploaded app versions as NOT_REQUIRED review. Admin-only.').hideHelp())
603
745
  .action(async (target, opts) => {
604
- await (0, upload_1.upload)(target ?? '.', { skipEcs: opts.skipEcs });
746
+ await (0, upload_1.upload)(target ?? '.', { skipEcs: opts.skipEcs, skipReview: opts.skipReview, clearTags: opts.clearTags });
605
747
  });
606
748
  const documentation = program.command('documentation').description('Public Playdrop documentation');
607
749
  documentation
package/dist/refs.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { ApiClient } from '@playdrop/api-client';
2
2
  import type { SavedItemKind } from '@playdrop/types';
3
- export type ContentKind = 'app' | 'asset' | 'asset-pack';
3
+ export type ContentKind = 'app' | 'asset' | 'asset-pack' | 'asset-spec';
4
4
  export type ContentRef = {
5
5
  creator: string;
6
6
  kind: ContentKind;
@@ -13,6 +13,6 @@ export declare function parseCreatorOption(raw: string | undefined): string | 'm
13
13
  export declare function resolveCreatorOption(client: ApiClient, raw: string | undefined): Promise<string>;
14
14
  export declare function buildScopedRef(creator: string, kind: ContentKind, name: string): ContentRef;
15
15
  export declare function contentKindToSavedItemKind(kind: ContentKind): SavedItemKind;
16
- export declare function contentKindToSearchKind(kind: ContentKind | 'all'): 'all' | 'app' | 'asset' | 'pack';
16
+ export declare function contentKindToSearchKind(kind: ContentKind | 'all'): 'all' | 'app' | 'asset' | 'asset-spec' | 'pack';
17
17
  export declare function contentKindLabel(kind: ContentKind): string;
18
18
  export declare function contentKindPluralLabel(kind: ContentKind): string;
package/dist/refs.js CHANGED
@@ -31,7 +31,7 @@ function parseContentRef(raw) {
31
31
  const creator = sanitizeSegment(segments[0], 'creator');
32
32
  const kind = sanitizeSegment(segments[1], 'kind');
33
33
  const name = sanitizeSegment(segments[2], 'name');
34
- if (kind !== 'app' && kind !== 'asset' && kind !== 'asset-pack') {
34
+ if (kind !== 'app' && kind !== 'asset' && kind !== 'asset-pack' && kind !== 'asset-spec') {
35
35
  throw new Error('invalid_kind');
36
36
  }
37
37
  return {
@@ -83,9 +83,15 @@ function contentKindToSavedItemKind(kind) {
83
83
  if (kind === 'asset') {
84
84
  return 'ASSET';
85
85
  }
86
+ if (kind === 'asset-spec') {
87
+ throw new Error('asset_specs_not_saveable');
88
+ }
86
89
  return 'PACK';
87
90
  }
88
91
  function contentKindToSearchKind(kind) {
92
+ if (kind === 'asset-spec') {
93
+ return 'asset-spec';
94
+ }
89
95
  if (kind === 'asset-pack') {
90
96
  return 'pack';
91
97
  }
@@ -95,11 +101,17 @@ function contentKindLabel(kind) {
95
101
  if (kind === 'asset-pack') {
96
102
  return 'asset pack';
97
103
  }
104
+ if (kind === 'asset-spec') {
105
+ return 'asset spec';
106
+ }
98
107
  return kind;
99
108
  }
100
109
  function contentKindPluralLabel(kind) {
101
110
  if (kind === 'asset-pack') {
102
111
  return 'asset packs';
103
112
  }
113
+ if (kind === 'asset-spec') {
114
+ return 'asset specs';
115
+ }
104
116
  return `${kind}s`;
105
117
  }
@@ -29,6 +29,8 @@ function createDirectAppTask(htmlPath) {
29
29
  emoji: undefined,
30
30
  color: undefined,
31
31
  surfaceTargets: [],
32
+ assetSpecSupport: [],
33
+ tags: [],
32
34
  missingMetadata: [],
33
35
  achievements: [],
34
36
  leaderboards: [],
@@ -53,7 +55,7 @@ function selectTasks(pathOrName) {
53
55
  });
54
56
  return { tasks: [], warnings, errors, resolution: 'directory' };
55
57
  }
56
- const { apps, assets, embeddedAssets, assetPacks, warnings: rawWarnings, errors: catalogueErrors } = (0, catalogue_1.resolveCatalogueEntries)(absolute);
58
+ const { apps, assetSpecs, assets, embeddedAssets, assetPacks, warnings: rawWarnings, errors: catalogueErrors } = (0, catalogue_1.resolveCatalogueEntries)(absolute);
57
59
  if (catalogueErrors.length > 0) {
58
60
  catalogueErrors.forEach(message => {
59
61
  errors.push({
@@ -64,12 +66,12 @@ function selectTasks(pathOrName) {
64
66
  });
65
67
  return { tasks: [], warnings, errors, resolution: 'directory' };
66
68
  }
67
- const taskList = [...assets, ...apps, ...embeddedAssets, ...assetPacks];
69
+ const taskList = [...assetSpecs, ...assets, ...apps, ...embeddedAssets, ...assetPacks];
68
70
  if (taskList.length === 0) {
69
71
  errors.push({
70
72
  type: 'no-tasks',
71
73
  message: `No publishable entries were found under ${absolute}.`,
72
- help: ['Add child catalogue.json files or add apps, assets, or assetPacks to the root catalogue.json, then rerun the command.'],
74
+ help: ['Add child catalogue.json files or add apps, assetSpecs, assets, or assetPacks to the root catalogue.json, then rerun the command.'],
73
75
  directory: absolute,
74
76
  });
75
77
  return { tasks: [], warnings, errors, resolution: 'directory' };
@@ -126,6 +128,7 @@ function selectTasks(pathOrName) {
126
128
  }
127
129
  const candidates = [
128
130
  ...lookup.apps.filter(task => task.name === pathOrName),
131
+ ...lookup.assetSpecs.filter(task => task.name === pathOrName),
129
132
  ...lookup.assets.filter(task => task.name === pathOrName),
130
133
  ...lookup.assetPacks.filter(task => task.name === pathOrName),
131
134
  ];
@@ -1,4 +1,4 @@
1
- import type { AppTask, AssetTask, EmbeddedAssetTask, AssetPackTask } from './catalogue';
2
- export type CliTask = AppTask | AssetTask | EmbeddedAssetTask | AssetPackTask;
1
+ import type { AppTask, AssetSpecTask, AssetTask, EmbeddedAssetTask, AssetPackTask } from './catalogue';
2
+ export type CliTask = AppTask | AssetSpecTask | AssetTask | EmbeddedAssetTask | AssetPackTask;
3
3
  export declare function sortTasks(tasks: CliTask[]): CliTask[];
4
4
  export declare function addTaskWarnings(warnings: Set<string>, tasks: CliTask[]): void;
package/dist/taskUtils.js CHANGED
@@ -5,6 +5,7 @@ exports.addTaskWarnings = addTaskWarnings;
5
5
  const catalogue_1 = require("./catalogue");
6
6
  function sortTasks(tasks) {
7
7
  const order = {
8
+ 'asset-spec': -1,
8
9
  asset: 0,
9
10
  app: 1,
10
11
  'embedded-asset': 2,
@@ -3,7 +3,7 @@ export type TaskStatus = 'success' | 'skipped' | 'error';
3
3
  export type TaskLogEntry = {
4
4
  action: TaskAction;
5
5
  status: TaskStatus;
6
- entityType: 'app' | 'asset' | 'embedded-asset' | 'asset-pack';
6
+ entityType: 'app' | 'asset-spec' | 'asset' | 'embedded-asset' | 'asset-pack' | 'tag-group' | 'tag';
7
7
  entityId: string;
8
8
  catalogue: string;
9
9
  detail?: string;
package/dist/uploadLog.js CHANGED
@@ -39,7 +39,7 @@ function formatLabel(entry) {
39
39
  function formatTaskLogLine(entry) {
40
40
  const prefix = `${STATUS_EMOJI[entry.status]} ${formatLabel(entry)}`;
41
41
  if (entry.detail) {
42
- return `${prefix} ${entry.detail}`;
42
+ return `${prefix} - ${entry.detail}`;
43
43
  }
44
44
  return prefix;
45
45
  }
@@ -108,7 +108,7 @@ function printTaskSummary(entries, warnings, options) {
108
108
  summaryText = labels.complete;
109
109
  }
110
110
  const parts = [
111
- `${summaryEmoji} ${summaryText} ${success} ${labels.unit}`,
111
+ `${summaryEmoji} ${summaryText} - ${success} ${labels.unit}`,
112
112
  `${skipped} skipped`,
113
113
  `${failed} failed`,
114
114
  ];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playdrop/ai-client",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Client SDK for the Playdrop AI server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",