@playdrop/playdrop-cli 0.5.2 → 0.5.4

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 (118) 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/commandContext.d.ts +6 -2
  13. package/dist/commandContext.js +144 -20
  14. package/dist/commands/accounts.d.ts +2 -0
  15. package/dist/commands/accounts.js +48 -0
  16. package/dist/commands/ads.d.ts +8 -0
  17. package/dist/commands/ads.js +124 -0
  18. package/dist/commands/boosts.d.ts +25 -0
  19. package/dist/commands/boosts.js +209 -0
  20. package/dist/commands/browse.d.ts +6 -1
  21. package/dist/commands/browse.js +365 -124
  22. package/dist/commands/capture.js +30 -9
  23. package/dist/commands/captureListing.d.ts +53 -0
  24. package/dist/commands/captureListing.js +815 -0
  25. package/dist/commands/create.d.ts +1 -0
  26. package/dist/commands/create.js +183 -3
  27. package/dist/commands/credits.d.ts +6 -0
  28. package/dist/commands/credits.js +47 -1
  29. package/dist/commands/detail.js +38 -4
  30. package/dist/commands/dev.js +169 -192
  31. package/dist/commands/devServer.d.ts +26 -3
  32. package/dist/commands/devServer.js +415 -72
  33. package/dist/commands/login.js +10 -2
  34. package/dist/commands/logout.d.ts +6 -1
  35. package/dist/commands/logout.js +25 -3
  36. package/dist/commands/search.d.ts +5 -0
  37. package/dist/commands/search.js +139 -17
  38. package/dist/commands/tags.d.ts +7 -0
  39. package/dist/commands/tags.js +63 -0
  40. package/dist/commands/upload-content.d.ts +13 -3
  41. package/dist/commands/upload-content.js +86 -20
  42. package/dist/commands/upload.d.ts +2 -0
  43. package/dist/commands/upload.js +187 -11
  44. package/dist/commands/validate.js +163 -2
  45. package/dist/commands/versionsBrowse.js +128 -91
  46. package/dist/commands/whoami.js +10 -2
  47. package/dist/config.d.ts +37 -0
  48. package/dist/config.js +205 -3
  49. package/dist/index.js +177 -5
  50. package/dist/refs.d.ts +2 -2
  51. package/dist/refs.js +13 -1
  52. package/dist/taskSelection.js +6 -3
  53. package/dist/taskUtils.d.ts +2 -2
  54. package/dist/taskUtils.js +1 -0
  55. package/dist/uploadLog.d.ts +1 -1
  56. package/dist/uploadLog.js +2 -2
  57. package/dist/workspaceAuth.d.ts +14 -0
  58. package/dist/workspaceAuth.js +75 -0
  59. package/node_modules/@playdrop/ai-client/package.json +1 -1
  60. package/node_modules/@playdrop/api-client/dist/client.d.ts +139 -10
  61. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  62. package/node_modules/@playdrop/api-client/dist/client.js +6 -0
  63. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +9 -1
  64. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  65. package/node_modules/@playdrop/api-client/dist/domains/admin.js +45 -0
  66. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +7 -1
  67. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  68. package/node_modules/@playdrop/api-client/dist/domains/apps.js +58 -0
  69. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +2 -0
  70. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
  71. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +16 -0
  72. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +44 -2
  73. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  74. package/node_modules/@playdrop/api-client/dist/domains/assets.js +260 -3
  75. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +22 -1
  76. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  77. package/node_modules/@playdrop/api-client/dist/domains/payments.js +228 -0
  78. package/node_modules/@playdrop/api-client/dist/domains/search.d.ts.map +1 -1
  79. package/node_modules/@playdrop/api-client/dist/domains/search.js +39 -11
  80. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts +34 -0
  81. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts.map +1 -0
  82. package/node_modules/@playdrop/api-client/dist/domains/tags.js +111 -0
  83. package/node_modules/@playdrop/api-client/dist/index.d.ts +69 -1
  84. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  85. package/node_modules/@playdrop/api-client/dist/index.js +74 -0
  86. package/node_modules/@playdrop/api-client/package.json +1 -1
  87. package/node_modules/@playdrop/boxel-core/package.json +1 -1
  88. package/node_modules/@playdrop/boxel-three/package.json +1 -1
  89. package/node_modules/@playdrop/config/client-meta.json +4 -4
  90. package/node_modules/@playdrop/config/dist/src/constants.d.ts +11 -0
  91. package/node_modules/@playdrop/config/dist/src/constants.d.ts.map +1 -1
  92. package/node_modules/@playdrop/config/dist/src/constants.js +12 -1
  93. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  94. package/node_modules/@playdrop/config/package.json +1 -1
  95. package/node_modules/@playdrop/types/dist/api.d.ts +366 -6
  96. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  97. package/node_modules/@playdrop/types/dist/api.js +52 -1
  98. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +7 -1
  99. package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
  100. package/node_modules/@playdrop/types/dist/asset-spec-contract-meta-schema.json +86 -0
  101. package/node_modules/@playdrop/types/dist/asset-spec.d.ts +163 -0
  102. package/node_modules/@playdrop/types/dist/asset-spec.d.ts.map +1 -0
  103. package/node_modules/@playdrop/types/dist/asset-spec.js +101 -0
  104. package/node_modules/@playdrop/types/dist/asset.d.ts +23 -6
  105. package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
  106. package/node_modules/@playdrop/types/dist/asset.js +4 -1
  107. package/node_modules/@playdrop/types/dist/graph.d.ts +4 -2
  108. package/node_modules/@playdrop/types/dist/graph.d.ts.map +1 -1
  109. package/node_modules/@playdrop/types/dist/graph.js +9 -2
  110. package/node_modules/@playdrop/types/dist/index.d.ts +1 -0
  111. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  112. package/node_modules/@playdrop/types/dist/index.js +1 -0
  113. package/node_modules/@playdrop/types/dist/version.d.ts +13 -0
  114. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  115. package/node_modules/@playdrop/types/dist/version.js +21 -0
  116. package/node_modules/@playdrop/types/package.json +6 -1
  117. package/node_modules/@playdrop/vox-three/package.json +1 -1
  118. package/package.json +3 -1
@@ -12,7 +12,7 @@ function parseSearchKind(raw) {
12
12
  return 'all';
13
13
  }
14
14
  const normalized = raw.trim().toLowerCase();
15
- if (normalized === 'app' || normalized === 'asset' || normalized === 'asset-pack' || normalized === 'all') {
15
+ if (normalized === 'app' || normalized === 'asset' || normalized === 'asset-pack' || normalized === 'asset-spec' || normalized === 'all') {
16
16
  return normalized;
17
17
  }
18
18
  return null;
@@ -50,6 +50,16 @@ function parseAssetSubcategory(raw) {
50
50
  const normalized = (0, types_1.normalizeAssetSubcategory)(raw);
51
51
  return normalized.length > 0 ? normalized : null;
52
52
  }
53
+ function parseSearchSort(raw) {
54
+ if (!raw || raw.trim().length === 0) {
55
+ return undefined;
56
+ }
57
+ const normalized = raw.trim().toLowerCase();
58
+ if (types_1.SEARCH_SORT_VALUES.includes(normalized)) {
59
+ return normalized;
60
+ }
61
+ return null;
62
+ }
53
63
  function toSearchItem(result) {
54
64
  if (result.kind === 'app') {
55
65
  return {
@@ -67,6 +77,14 @@ function toSearchItem(result) {
67
77
  item: result.asset,
68
78
  };
69
79
  }
80
+ if (result.kind === 'asset-spec') {
81
+ return {
82
+ kind: 'asset-spec',
83
+ ref: `${result.assetSpec.creatorUsername}/asset-spec/${result.assetSpec.name}`,
84
+ targetPath: result.targetPath,
85
+ item: result.assetSpec,
86
+ };
87
+ }
70
88
  if (result.kind === 'pack') {
71
89
  return {
72
90
  kind: 'asset-pack',
@@ -85,6 +103,9 @@ function itemSummary(item) {
85
103
  const subcategory = item.item.currentVersion?.subcategory;
86
104
  return subcategory ? `${item.item.category.toLowerCase()}/${subcategory}` : item.item.category.toLowerCase();
87
105
  }
106
+ if (item.kind === 'asset-spec') {
107
+ return item.item.currentVersionRef ? `current ${item.item.currentVersionRef.split('@')[1] ?? 'none'}` : 'no current version';
108
+ }
88
109
  return 'asset-pack';
89
110
  }
90
111
  function formatCountValue(value) {
@@ -98,15 +119,28 @@ function buildCountsSuffix(item) {
98
119
  }
99
120
  parts.push(`launch ${formatCountValue(item.item.playCount)}`);
100
121
  }
101
- if (item.kind === 'asset' && typeof item.item.playCount === 'number') {
122
+ if (item.kind === 'asset') {
102
123
  if (typeof item.item.viewCount === 'number') {
103
124
  parts.push(`view ${formatCountValue(item.item.viewCount)}`);
104
125
  }
105
- parts.push(`play ${formatCountValue(item.item.playCount)}`);
126
+ if (typeof item.item.downloadCount === 'number') {
127
+ parts.push(`download ${formatCountValue(item.item.downloadCount)}`);
128
+ }
129
+ if (typeof item.item.playCount === 'number') {
130
+ parts.push(`play ${formatCountValue(item.item.playCount)}`);
131
+ }
106
132
  }
107
133
  if (item.kind === 'asset-pack' && typeof item.item.downloadCount === 'number') {
108
134
  parts.push(`download ${formatCountValue(item.item.downloadCount)}`);
109
135
  }
136
+ if (item.kind === 'asset-spec') {
137
+ parts.push(`assets ${formatCountValue(item.item.publicAssetCount)}`);
138
+ parts.push(`apps ${formatCountValue(item.item.supportingAppCount)}`);
139
+ if (item.item.documentationAvailable) {
140
+ parts.push('docs yes');
141
+ }
142
+ return ` | ${parts.join(' | ')}`;
143
+ }
110
144
  if (typeof item.item.likeCount === 'number') {
111
145
  parts.push(`like ${formatCountValue(item.item.likeCount)}`);
112
146
  }
@@ -118,6 +152,71 @@ function buildCountsSuffix(item) {
118
152
  }
119
153
  return parts.length > 0 ? ` | ${parts.join(' | ')}` : '';
120
154
  }
155
+ function normalizeOptionalString(value) {
156
+ if (typeof value !== 'string') {
157
+ return undefined;
158
+ }
159
+ const normalized = value.trim();
160
+ return normalized.length > 0 ? normalized : undefined;
161
+ }
162
+ function normalizeTagFilters(rawTags) {
163
+ if (!Array.isArray(rawTags) || rawTags.length === 0) {
164
+ return [];
165
+ }
166
+ const normalizedRefs = [];
167
+ const seenRefs = new Set();
168
+ for (const rawTag of rawTags) {
169
+ if (typeof rawTag !== 'string' || rawTag.trim().length === 0) {
170
+ return null;
171
+ }
172
+ let normalizedRef = '';
173
+ try {
174
+ normalizedRef = (0, types_1.normalizeTagRef)(rawTag);
175
+ }
176
+ catch {
177
+ return null;
178
+ }
179
+ if (seenRefs.has(normalizedRef)) {
180
+ return null;
181
+ }
182
+ seenRefs.add(normalizedRef);
183
+ normalizedRefs.push(normalizedRef);
184
+ }
185
+ if (normalizedRefs.length > 5) {
186
+ return null;
187
+ }
188
+ return normalizedRefs;
189
+ }
190
+ function buildTagSuffix(tags) {
191
+ const normalizedTags = Array.isArray(tags) ? tags : [];
192
+ if (normalizedTags.length === 0) {
193
+ return '';
194
+ }
195
+ const label = normalizedTags
196
+ .slice(0, 3)
197
+ .map((tag) => tag.ref)
198
+ .join(', ');
199
+ return ` | tags ${label}`;
200
+ }
201
+ function buildSearchRequest(input) {
202
+ return {
203
+ q: input.trimmedQuery,
204
+ mode: 'flat',
205
+ kind: (0, refs_1.contentKindToSearchKind)(input.kind),
206
+ page: 1,
207
+ pageSize: input.limit + input.offset,
208
+ ...(input.sort ? { sort: input.sort } : {}),
209
+ appType: input.appType,
210
+ assetCategory: input.assetCategory,
211
+ assetSubcategory: input.assetSubcategory,
212
+ assetSpec: input.assetSpec,
213
+ assetSpecOwner: input.assetSpecOwner,
214
+ assetSpecName: input.assetSpecName,
215
+ packContainsCategory: input.packContainsCategory,
216
+ packContainsSubcategory: input.packContainsSubcategory,
217
+ tags: input.tags,
218
+ };
219
+ }
121
220
  async function search(query, options = {}) {
122
221
  const trimmedQuery = typeof query === 'string' ? query.trim() : '';
123
222
  if (!trimmedQuery) {
@@ -127,7 +226,7 @@ async function search(query, options = {}) {
127
226
  }
128
227
  const kind = parseSearchKind(options.kind);
129
228
  if (!kind) {
130
- (0, messages_1.printErrorWithHelp)(`Kind "${options.kind ?? ''}" is not supported.`, ['Use one of: app, asset, asset-pack, all.'], { command: 'search' });
229
+ (0, messages_1.printErrorWithHelp)(`Kind "${options.kind ?? ''}" is not supported.`, ['Use one of: app, asset, asset-pack, asset-spec, all.'], { command: 'search' });
131
230
  process.exitCode = 1;
132
231
  return;
133
232
  }
@@ -143,6 +242,12 @@ async function search(query, options = {}) {
143
242
  process.exitCode = 1;
144
243
  return;
145
244
  }
245
+ const sort = parseSearchSort(options.sort);
246
+ if (sort === null) {
247
+ (0, messages_1.printErrorWithHelp)(`Sort "${options.sort ?? ''}" is not supported.`, ['Use one of: relevance, recent, likes, downloads, remixes, comments, assets, apps, alpha.'], { command: 'search' });
248
+ process.exitCode = 1;
249
+ return;
250
+ }
146
251
  const appType = options.appType ? (0, types_1.parseAppType)(options.appType.trim().toUpperCase()) : undefined;
147
252
  if (options.appType !== undefined && !appType) {
148
253
  (0, messages_1.printErrorWithHelp)(`App type "${options.appType}" is not supported.`, ['Use one of: game, demo, tool, template.'], { command: 'search' });
@@ -173,21 +278,30 @@ async function search(query, options = {}) {
173
278
  process.exitCode = 1;
174
279
  return;
175
280
  }
281
+ const tags = normalizeTagFilters(options.tag);
282
+ if (tags === null) {
283
+ (0, messages_1.printErrorWithHelp)('The --tag value is invalid.', ['Use canonical refs like theme/pirate or visual-style/pixel-art.', 'Pass at most 5 unique tag refs.'], { command: 'search' });
284
+ process.exitCode = 1;
285
+ return;
286
+ }
176
287
  await (0, commandContext_1.withPublicEnvironment)('search', async ({ client }) => {
177
288
  try {
178
- const requestLimit = limit + offset;
179
- const response = await client.search({
180
- q: trimmedQuery,
181
- mode: 'flat',
182
- kind: (0, refs_1.contentKindToSearchKind)(kind),
183
- page: 1,
184
- pageSize: requestLimit,
289
+ const response = await client.search(buildSearchRequest({
290
+ trimmedQuery,
291
+ kind,
292
+ limit,
293
+ offset,
294
+ sort: sort ?? undefined,
185
295
  appType: appType,
186
296
  assetCategory: assetCategory ?? undefined,
187
297
  assetSubcategory: assetSubcategory ?? undefined,
298
+ assetSpec: normalizeOptionalString(options.assetSpec),
299
+ assetSpecOwner: normalizeOptionalString(options.assetSpecOwner),
300
+ assetSpecName: normalizeOptionalString(options.assetSpecName),
188
301
  packContainsCategory: packContainsCategory ?? undefined,
189
302
  packContainsSubcategory: packContainsSubcategory ?? undefined,
190
- });
303
+ tags,
304
+ }));
191
305
  const normalizedItems = (response.results ?? [])
192
306
  .map((result) => toSearchItem(result))
193
307
  .filter((item) => Boolean(item));
@@ -210,16 +324,24 @@ async function search(query, options = {}) {
210
324
  console.log(`Search results for "${trimmedQuery}":\n`);
211
325
  for (const [index, item] of items.entries()) {
212
326
  const displayName = item.item.displayName || item.item.name;
213
- console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${displayName} | @${item.item.creatorUsername} | ${itemSummary(item)}${buildCountsSuffix(item)}`);
327
+ console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${displayName} | @${item.item.creatorUsername} | ${itemSummary(item)}${buildCountsSuffix(item)}${buildTagSuffix(item.item.tags)}`);
214
328
  }
215
329
  console.log('\nNext: run "playdrop detail <creator>/<kind>/<name>" to inspect one result.');
216
330
  }
217
331
  catch (error) {
218
332
  const handled = (0, errors_1.handleCommandFailure)(error, 'search', 'Search', {
219
- apiMessage: (apiError) => ({
220
- problem: `Search failed with status ${apiError.status}.`,
221
- suggestions: ['Retry in a moment.', 'Use "playdrop help search" for usage details.'],
222
- }),
333
+ apiMessage: (apiError) => {
334
+ if (apiError.code === 'invalid_tag_ref' || apiError.code === 'duplicate_tag_ref' || apiError.code === 'tag_filter_limit_exceeded') {
335
+ return {
336
+ problem: 'One or more tag filters are invalid.',
337
+ suggestions: ['Use canonical refs like theme/pirate.', 'Pass at most 5 unique --tag values.'],
338
+ };
339
+ }
340
+ return {
341
+ problem: `Search failed with status ${apiError.status}.`,
342
+ suggestions: ['Retry in a moment.', 'Use "playdrop help search" for usage details.'],
343
+ };
344
+ },
223
345
  });
224
346
  if (!handled) {
225
347
  throw error;
@@ -0,0 +1,7 @@
1
+ type BrowseTagsOptions = {
2
+ group?: string;
3
+ kind?: string;
4
+ json?: boolean;
5
+ };
6
+ export declare function browseTags(options: BrowseTagsOptions): Promise<void>;
7
+ export {};
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.browseTags = browseTags;
4
+ const types_1 = require("@playdrop/types");
5
+ const commandContext_1 = require("../commandContext");
6
+ const messages_1 = require("../messages");
7
+ function normalizeGroupSlug(value) {
8
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
9
+ return normalized.length > 0 ? normalized : null;
10
+ }
11
+ function normalizeTargetKind(value) {
12
+ const normalized = typeof value === 'string' ? value.trim().toUpperCase() : '';
13
+ if (!normalized) {
14
+ return null;
15
+ }
16
+ return types_1.SAVED_ITEM_KIND_VALUES.includes(normalized)
17
+ ? normalized
18
+ : null;
19
+ }
20
+ function formatAllowedTargetKinds(kinds) {
21
+ if (!Array.isArray(kinds) || kinds.length === 0) {
22
+ return 'all';
23
+ }
24
+ return kinds.join(', ');
25
+ }
26
+ async function browseTags(options) {
27
+ const groupSlug = normalizeGroupSlug(options.group);
28
+ const targetKind = normalizeTargetKind(options.kind);
29
+ if (options.group && (!groupSlug || !(0, types_1.isValidTagSlug)(groupSlug))) {
30
+ (0, messages_1.printErrorWithHelp)(`Invalid group slug "${options.group}".`, ['Use lowercase letters, numbers, and hyphens only.'], { command: 'tags browse' });
31
+ process.exitCode = 1;
32
+ return;
33
+ }
34
+ if (options.kind && !targetKind) {
35
+ (0, messages_1.printErrorWithHelp)(`Unsupported target kind "${options.kind}".`, [`Use one of: ${types_1.SAVED_ITEM_KIND_VALUES.join(', ')}.`], { command: 'tags browse' });
36
+ process.exitCode = 1;
37
+ return;
38
+ }
39
+ await (0, commandContext_1.withPublicEnvironment)('tags browse', async ({ client }) => {
40
+ const response = await client.fetchTags({
41
+ ...(groupSlug ? { groupSlug } : {}),
42
+ ...(targetKind ? { targetKind } : {}),
43
+ });
44
+ if (options.json) {
45
+ console.log(JSON.stringify(response, null, 2));
46
+ return;
47
+ }
48
+ if (response.groups.length === 0) {
49
+ console.log('No tags found.');
50
+ return;
51
+ }
52
+ for (const [groupIndex, group] of response.groups.entries()) {
53
+ if (groupIndex > 0) {
54
+ console.log('');
55
+ }
56
+ console.log(`${group.displayName} (${group.slug})`);
57
+ console.log(`Allowed targets: ${formatAllowedTargetKinds(group.allowedTargetKinds)}`);
58
+ for (const tag of group.tags) {
59
+ console.log(`${tag.ref} ${tag.displayName}`);
60
+ }
61
+ }
62
+ });
63
+ }
@@ -1,6 +1,6 @@
1
1
  import type { ApiClient } from '@playdrop/api-client';
2
2
  import { type AssetPackUploadedLocalAsset } from '@playdrop/types';
3
- import type { AssetPackTask, AssetTask, EmbeddedAssetTask } from '../catalogue';
3
+ import type { AssetPackTask, AssetSpecTask, AssetTask, EmbeddedAssetTask } from '../catalogue';
4
4
  import type { CliTask } from '../taskUtils';
5
5
  export type CurrentUserRole = 'USER' | 'CREATOR' | 'ADMIN' | null;
6
6
  export type UploadedAssetInfo = {
@@ -10,6 +10,11 @@ export type UploadedAssetInfo = {
10
10
  ref: string;
11
11
  versionNodeId: string;
12
12
  };
13
+ export type UploadedAssetSpecInfo = {
14
+ creatorUsername: string;
15
+ name: string;
16
+ version: string;
17
+ };
13
18
  export type UploadedAppInfo = {
14
19
  creatorUsername: string;
15
20
  name: string;
@@ -65,6 +70,11 @@ export declare function parseUnversionedAssetTaskRef(rawRef: string, fallbackCre
65
70
  name: string;
66
71
  } | null;
67
72
  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>;
73
+ export declare function uploadAssetTask(client: ApiClient, task: AssetTask | EmbeddedAssetTask, sourceAppVersionId?: number, creatorUsername?: string, options?: {
74
+ clearTags?: boolean;
75
+ }): Promise<UploadedAssetInfo>;
76
+ export declare function uploadAssetSpecTask(client: ApiClient, task: AssetSpecTask, creatorUsername?: string): Promise<UploadedAssetSpecInfo>;
77
+ export declare function uploadAssetPackTask(client: ApiClient, task: AssetPackTask, creatorUsername: string, uploadedAssets: Map<string, UploadedAssetInfo>, localAssetTasks: AssetTask[], uploadKeyByAssetKey: Map<string, string>, targetCreatorUsername?: string, options?: {
78
+ clearTags?: boolean;
79
+ }): Promise<UploadedPackInfo>;
70
80
  export {};
@@ -6,6 +6,7 @@ exports.getTaskCreatorResult = getTaskCreatorResult;
6
6
  exports.parseUnversionedAssetTaskRef = parseUnversionedAssetTaskRef;
7
7
  exports.buildAssetPackUploadPlans = buildAssetPackUploadPlans;
8
8
  exports.uploadAssetTask = uploadAssetTask;
9
+ exports.uploadAssetSpecTask = uploadAssetSpecTask;
9
10
  exports.uploadAssetPackTask = uploadAssetPackTask;
10
11
  const node_crypto_1 = require("node:crypto");
11
12
  const node_fs_1 = require("node:fs");
@@ -316,6 +317,22 @@ function validateAssetSubcategoryCompatibility(task, format) {
316
317
  return subcategory;
317
318
  }
318
319
  async function prepareAssetTaskUploadData(task) {
320
+ if ('assetSpec' in task && typeof task.assetSpec === 'string' && task.assetSpec.trim().length > 0) {
321
+ const uploadFiles = Object.entries(task.filePaths)
322
+ .sort(([leftRole], [rightRole]) => leftRole.localeCompare(rightRole))
323
+ .map(([role, filePath]) => prepareUploadFile(filePath, role));
324
+ return {
325
+ assetSpec: task.assetSpec.trim(),
326
+ uploadFiles,
327
+ manifestFiles: uploadFiles.map((entry) => ({
328
+ role: entry.fieldName,
329
+ filename: entry.filename,
330
+ contentType: entry.contentType,
331
+ sizeBytes: entry.sizeBytes,
332
+ sha256: entry.sha256,
333
+ })),
334
+ };
335
+ }
319
336
  const format = resolveAssetFormat(task);
320
337
  const subcategory = validateAssetSubcategoryCompatibility(task, format);
321
338
  await (0, model_artifacts_1.prepareModel3DAssetArtifacts)(task);
@@ -335,7 +352,7 @@ async function prepareAssetTaskUploadData(task) {
335
352
  })),
336
353
  };
337
354
  }
338
- async function uploadAssetTask(client, task, sourceAppVersionId, creatorUsername) {
355
+ async function uploadAssetTask(client, task, sourceAppVersionId, creatorUsername, options) {
339
356
  const prepared = await prepareAssetTaskUploadData(task);
340
357
  const visibility = task.visibility;
341
358
  const files = prepared.uploadFiles.map((entry) => ({
@@ -347,19 +364,35 @@ async function uploadAssetTask(client, task, sourceAppVersionId, creatorUsername
347
364
  if (!targetCreatorUsername) {
348
365
  throw new Error(`Asset "${task.name}" upload is missing creator username.`);
349
366
  }
350
- const response = await client.createAssetVersion(targetCreatorUsername, task.name, {
351
- displayName: task.kind === 'asset' ? task.displayName : undefined,
352
- category: task.category,
353
- subcategory: prepared.subcategory,
354
- format: prepared.format,
355
- remixRef: task.kind === 'asset' ? task.remix : undefined,
356
- visibility: visibility,
357
- sourceKind: sourceAppVersionId ? 'APP_EMBEDDED' : 'UPLOAD',
358
- sourceAppVersionId,
359
- shopListed: task.shopListed,
360
- shopPriceCredits: task.shopPriceCredits,
361
- files,
362
- });
367
+ const response = await client.createAssetVersion(targetCreatorUsername, task.name, prepared.assetSpec
368
+ ? {
369
+ displayName: task.kind === 'asset' ? task.displayName : undefined,
370
+ assetSpec: prepared.assetSpec,
371
+ remixRef: task.kind === 'asset' ? task.remix : undefined,
372
+ visibility: visibility,
373
+ sourceKind: sourceAppVersionId ? 'APP_EMBEDDED' : 'UPLOAD',
374
+ sourceAppVersionId,
375
+ tags: task.kind === 'asset' ? task.tags : undefined,
376
+ clearTags: task.kind === 'asset' ? options?.clearTags : undefined,
377
+ shopListed: task.shopListed,
378
+ shopPriceCredits: task.shopPriceCredits,
379
+ files,
380
+ }
381
+ : {
382
+ displayName: task.kind === 'asset' ? task.displayName : undefined,
383
+ category: task.category,
384
+ subcategory: prepared.subcategory,
385
+ format: prepared.format,
386
+ remixRef: task.kind === 'asset' ? task.remix : undefined,
387
+ visibility: visibility,
388
+ sourceKind: sourceAppVersionId ? 'APP_EMBEDDED' : 'UPLOAD',
389
+ sourceAppVersionId,
390
+ tags: task.kind === 'asset' ? task.tags : undefined,
391
+ clearTags: task.kind === 'asset' ? options?.clearTags : undefined,
392
+ shopListed: task.shopListed,
393
+ shopPriceCredits: task.shopPriceCredits,
394
+ files,
395
+ });
363
396
  const uploadedCreatorUsername = response.asset.creatorUsername;
364
397
  const name = response.asset.name;
365
398
  const revision = response.version.revision;
@@ -378,6 +411,35 @@ async function uploadAssetTask(client, task, sourceAppVersionId, creatorUsername
378
411
  versionNodeId,
379
412
  };
380
413
  }
414
+ async function uploadAssetSpecTask(client, task, creatorUsername) {
415
+ const targetCreatorUsername = typeof creatorUsername === 'string' ? creatorUsername.trim() : '';
416
+ if (!targetCreatorUsername) {
417
+ throw new Error(`Asset spec "${task.name}@${task.version}" upload is missing creator username.`);
418
+ }
419
+ const symbol = prepareUploadFile(task.symbolPath, 'symbol', 'image/svg+xml');
420
+ const contract = prepareUploadFile(task.contractPath, 'contract', 'application/json');
421
+ const documentation = task.documentationPath
422
+ ? prepareUploadFile(task.documentationPath, 'documentation', 'text/markdown')
423
+ : null;
424
+ await client.createAssetSpecVersion(targetCreatorUsername, task.name, {
425
+ version: task.version,
426
+ displayName: task.displayName,
427
+ description: task.description,
428
+ visibility: task.visibility,
429
+ validationKind: task.validationKind,
430
+ status: task.status,
431
+ releaseNotes: task.releaseNotes,
432
+ successorVersion: task.successorVersion,
433
+ symbol: symbol.file,
434
+ contract: contract.file,
435
+ ...(documentation ? { documentation: documentation.file } : {}),
436
+ });
437
+ return {
438
+ creatorUsername: targetCreatorUsername,
439
+ name: task.name,
440
+ version: task.version,
441
+ };
442
+ }
381
443
  function parseAssetRef(raw) {
382
444
  const trimmed = raw.trim();
383
445
  const canonicalMatch = /^asset:([^/]+)\/([^@]+)@r(\d+)$/i.exec(trimmed);
@@ -459,11 +521,13 @@ function buildLocalAssetManifestEntries(preparedLocalAssets) {
459
521
  uploadKey,
460
522
  name: localAsset.task.name,
461
523
  displayName: localAsset.task.displayName,
462
- category: localAsset.task.category,
463
- subcategory: localAsset.prepared.subcategory,
464
- format: localAsset.prepared.format,
524
+ category: (localAsset.task.assetSpec ? 'CUSTOM' : localAsset.task.category),
525
+ subcategory: localAsset.prepared.subcategory ?? null,
526
+ format: (localAsset.prepared.format ?? 'CUSTOM'),
527
+ ...(localAsset.task.assetSpec ? { assetSpec: localAsset.task.assetSpec } : {}),
465
528
  visibility: localAsset.task.visibility,
466
529
  remixRef: localAsset.task.remix,
530
+ tags: localAsset.task.tags,
467
531
  shopListed: localAsset.task.shopListed,
468
532
  shopPriceCredits: localAsset.task.shopPriceCredits,
469
533
  files: localAsset.prepared.manifestFiles,
@@ -534,12 +598,14 @@ function buildPackListingPayload(listingUploads) {
534
598
  videosLandscape: listingUploads.filter((entry) => entry.mediaKey.startsWith('videosLandscape:')).map((entry) => entry.entry),
535
599
  };
536
600
  }
537
- function buildPackInitializeRequest(task, localAssets, existingAssets, members, listingUploads) {
601
+ function buildPackInitializeRequest(task, localAssets, existingAssets, members, listingUploads, options) {
538
602
  return {
539
603
  version: task.version,
540
604
  displayName: task.displayName,
541
605
  remixRef: task.remix,
542
606
  visibility: task.visibility,
607
+ tags: task.tags,
608
+ clearTags: options?.clearTags,
543
609
  hostingMode: task.hostingMode,
544
610
  externalUrl: task.externalUrl,
545
611
  downloadUrl: task.downloadUrl,
@@ -586,13 +652,13 @@ function buildUploadedPackInfo(creatorUsername, task, versionNodeId, versionAsse
586
652
  uploadedLocalAssets: uploadedLocalAssets ?? [],
587
653
  };
588
654
  }
589
- async function uploadAssetPackTask(client, task, creatorUsername, uploadedAssets, localAssetTasks, uploadKeyByAssetKey, targetCreatorUsername) {
655
+ async function uploadAssetPackTask(client, task, creatorUsername, uploadedAssets, localAssetTasks, uploadKeyByAssetKey, targetCreatorUsername, options) {
590
656
  const mutationCreatorUsername = resolveMutationCreatorUsername(task, creatorUsername, targetCreatorUsername);
591
657
  const preparedLocalAssets = await prepareLocalPackAssets(task, mutationCreatorUsername, localAssetTasks, uploadKeyByAssetKey);
592
658
  const localAssets = buildLocalAssetManifestEntries(preparedLocalAssets);
593
659
  const { existingAssets, members } = await buildPackMembersAndExistingAssets(client, task, mutationCreatorUsername, uploadedAssets, uploadKeyByAssetKey);
594
660
  const listingUploads = buildPackListingUploads(task);
595
- const initializeBody = buildPackInitializeRequest(task, localAssets, existingAssets, members, listingUploads);
661
+ const initializeBody = buildPackInitializeRequest(task, localAssets, existingAssets, members, listingUploads, options);
596
662
  const initializeResponse = await client.initializeAssetPackUpload(mutationCreatorUsername, task.name, initializeBody);
597
663
  if (initializeResponse.status === 'finalized') {
598
664
  if (!initializeResponse.versionNodeId || !initializeResponse.version) {
@@ -1,4 +1,6 @@
1
1
  export type UploadCommandOptions = {
2
2
  skipEcs?: boolean;
3
+ skipReview?: boolean;
4
+ clearTags?: boolean;
3
5
  };
4
6
  export declare function upload(pathOrName: string, options?: UploadCommandOptions): Promise<void>;