@playdrop/playdrop-cli 0.5.4 → 0.5.6

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 (99) hide show
  1. package/README.md +2 -1
  2. package/config/client-meta.json +4 -4
  3. package/dist/apps/upload.js +226 -80
  4. package/dist/assetSpecs.d.ts +1 -0
  5. package/dist/assetSpecs.js +22 -1
  6. package/dist/assets/model-artifacts.d.ts +2 -2
  7. package/dist/assets/model-artifacts.js +1 -1
  8. package/dist/captureRuntime.d.ts +1 -0
  9. package/dist/captureRuntime.js +3 -2
  10. package/dist/catalogue.d.ts +33 -8
  11. package/dist/catalogue.js +364 -46
  12. package/dist/commandContext.d.ts +5 -1
  13. package/dist/commandContext.js +90 -29
  14. package/dist/commands/browse.d.ts +3 -0
  15. package/dist/commands/browse.js +90 -17
  16. package/dist/commands/build.js +1 -1
  17. package/dist/commands/capture.d.ts +3 -0
  18. package/dist/commands/capture.js +121 -9
  19. package/dist/commands/captureListing.d.ts +2 -0
  20. package/dist/commands/captureListing.js +157 -61
  21. package/dist/commands/create.js +6 -28
  22. package/dist/commands/createRemixContent.js +4 -26
  23. package/dist/commands/creations.js +2 -3
  24. package/dist/commands/detail.js +24 -2
  25. package/dist/commands/dev.d.ts +8 -1
  26. package/dist/commands/dev.js +180 -2
  27. package/dist/commands/devRuntimeAssets.d.ts +34 -0
  28. package/dist/commands/devRuntimeAssets.js +308 -0
  29. package/dist/commands/devServer.d.ts +11 -0
  30. package/dist/commands/devServer.js +196 -13
  31. package/dist/commands/init.js +6 -24
  32. package/dist/commands/search.d.ts +4 -0
  33. package/dist/commands/search.js +68 -11
  34. package/dist/commands/upload-content.d.ts +3 -3
  35. package/dist/commands/upload-content.js +19 -38
  36. package/dist/commands/upload.js +67 -77
  37. package/dist/commands/validate.js +13 -20
  38. package/dist/commands/whoami.js +23 -26
  39. package/dist/devAuth.d.ts +16 -0
  40. package/dist/devAuth.js +60 -0
  41. package/dist/index.js +22 -4
  42. package/dist/taskSelection.js +4 -3
  43. package/dist/taskUtils.d.ts +2 -2
  44. package/dist/taskUtils.js +1 -1
  45. package/dist/uploadLog.d.ts +1 -1
  46. package/node_modules/@playdrop/ai-client/package.json +1 -1
  47. package/node_modules/@playdrop/api-client/dist/client.d.ts +44 -114
  48. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  49. package/node_modules/@playdrop/api-client/dist/client.js +22 -0
  50. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +2 -1
  51. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  52. package/node_modules/@playdrop/api-client/dist/domains/admin.js +11 -0
  53. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +11 -19
  54. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  55. package/node_modules/@playdrop/api-client/dist/domains/apps.js +116 -106
  56. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +2 -1
  57. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  58. package/node_modules/@playdrop/api-client/dist/domains/assets.js +13 -0
  59. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +5 -5
  60. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  61. package/node_modules/@playdrop/api-client/dist/domains/payments.js +8 -8
  62. package/node_modules/@playdrop/api-client/dist/domains/search.d.ts.map +1 -1
  63. package/node_modules/@playdrop/api-client/dist/domains/search.js +24 -2
  64. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts +13 -1
  65. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts.map +1 -1
  66. package/node_modules/@playdrop/api-client/dist/domains/tags.js +52 -0
  67. package/node_modules/@playdrop/api-client/dist/index.d.ts +28 -29
  68. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  69. package/node_modules/@playdrop/api-client/dist/index.js +26 -8
  70. package/node_modules/@playdrop/api-client/package.json +1 -1
  71. package/node_modules/@playdrop/boxel-core/package.json +1 -1
  72. package/node_modules/@playdrop/boxel-three/package.json +1 -1
  73. package/node_modules/@playdrop/config/client-meta.json +4 -4
  74. package/node_modules/@playdrop/config/package.json +1 -1
  75. package/node_modules/@playdrop/types/dist/api.d.ts +130 -3
  76. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  77. package/node_modules/@playdrop/types/dist/api.js +23 -0
  78. package/node_modules/@playdrop/types/dist/app-capability-filters.d.ts +24 -0
  79. package/node_modules/@playdrop/types/dist/app-capability-filters.d.ts.map +1 -0
  80. package/node_modules/@playdrop/types/dist/app-capability-filters.js +72 -0
  81. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +3 -2
  82. package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
  83. package/node_modules/@playdrop/types/dist/asset.d.ts +2 -3
  84. package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
  85. package/node_modules/@playdrop/types/dist/asset.js +1 -1
  86. package/node_modules/@playdrop/types/dist/index.d.ts +2 -0
  87. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  88. package/node_modules/@playdrop/types/dist/index.js +2 -0
  89. package/node_modules/@playdrop/types/dist/owned-assets.d.ts +21 -0
  90. package/node_modules/@playdrop/types/dist/owned-assets.d.ts.map +1 -0
  91. package/node_modules/@playdrop/types/dist/owned-assets.js +35 -0
  92. package/node_modules/@playdrop/types/dist/player-meta.d.ts +28 -0
  93. package/node_modules/@playdrop/types/dist/player-meta.d.ts.map +1 -1
  94. package/node_modules/@playdrop/types/dist/version.d.ts +111 -1
  95. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  96. package/node_modules/@playdrop/types/dist/version.js +4 -0
  97. package/node_modules/@playdrop/types/package.json +1 -1
  98. package/node_modules/@playdrop/vox-three/package.json +1 -1
  99. package/package.json +1 -1
@@ -7,8 +7,11 @@ exports.DEV_ROUTER_PORT = void 0;
7
7
  exports.parseMountConflictError = parseMountConflictError;
8
8
  exports.buildLocalDevAppUrl = buildLocalDevAppUrl;
9
9
  exports.ensureDevRouterRunning = ensureDevRouterRunning;
10
+ exports.updateMountedDevRuntimeAssetManifest = updateMountedDevRuntimeAssetManifest;
10
11
  exports.startDevServer = startDevServer;
11
12
  exports.isDevServerAvailable = isDevServerAvailable;
13
+ exports.createDevRouterServer = createDevRouterServer;
14
+ exports.listenDevRouterServer = listenDevRouterServer;
12
15
  exports.runDevRouterServer = runDevRouterServer;
13
16
  const node_http_1 = __importDefault(require("node:http"));
14
17
  const node_fs_1 = require("node:fs");
@@ -18,6 +21,7 @@ const node_crypto_1 = require("node:crypto");
18
21
  exports.DEV_ROUTER_PORT = 8888;
19
22
  const DEV_ROUTER_HOST = '127.0.0.1';
20
23
  const CONTROL_PREFIX = '/_playdrop';
24
+ const DEV_ROUTER_PROTOCOL_VERSION = 3;
21
25
  const CONTENT_TYPE_BY_EXTENSION = {
22
26
  '.html': 'text/html; charset=utf-8',
23
27
  '.js': 'application/javascript; charset=utf-8',
@@ -128,6 +132,19 @@ function cleanupStaleRouterMounts() {
128
132
  }
129
133
  }
130
134
  }
135
+ function findRouterMountByCreatorAndApp(creatorUsername, appName) {
136
+ let match = null;
137
+ for (const mount of routerMountsById.values()) {
138
+ if (mount.creatorUsername !== creatorUsername || mount.appName !== appName) {
139
+ continue;
140
+ }
141
+ if (match) {
142
+ throw new Error(`ambiguous_mount_lookup:${creatorUsername}/${appName}`);
143
+ }
144
+ match = mount;
145
+ }
146
+ return match;
147
+ }
131
148
  function resolveStaticPath(staticRoot, rawRelativePath) {
132
149
  const normalizedRelativePath = (0, node_path_1.normalize)(rawRelativePath.replace(/^\/+/, ''));
133
150
  if (!normalizedRelativePath || normalizedRelativePath === '.' || normalizedRelativePath.startsWith('..')) {
@@ -159,6 +176,13 @@ function parseMountPath(pathname) {
159
176
  assetPath: rest.map((entry) => decodeURIComponent(entry)).join('/'),
160
177
  };
161
178
  }
179
+ function getOwnedRuntimeAssetFile(mount, ownedAssetName, role) {
180
+ const files = mount.runtimeAssetManifest?.ownedAssetFiles?.[ownedAssetName];
181
+ if (!files) {
182
+ return null;
183
+ }
184
+ return files[role.trim().toLowerCase()] ?? null;
185
+ }
162
186
  async function readJsonBody(req) {
163
187
  const chunks = [];
164
188
  for await (const chunk of req) {
@@ -192,7 +216,7 @@ async function fetchRouterJson(path, init = {}, port = exports.DEV_ROUTER_PORT)
192
216
  }
193
217
  return await response.json();
194
218
  }
195
- async function isRouterHealthy(port = exports.DEV_ROUTER_PORT, timeoutMs = 400) {
219
+ async function fetchRouterHealth(port = exports.DEV_ROUTER_PORT, timeoutMs = 400) {
196
220
  const controller = new AbortController();
197
221
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
198
222
  timeout.unref?.();
@@ -201,18 +225,34 @@ async function isRouterHealthy(port = exports.DEV_ROUTER_PORT, timeoutMs = 400)
201
225
  method: 'GET',
202
226
  signal: controller.signal,
203
227
  });
204
- return response.ok;
228
+ if (!response.ok) {
229
+ return null;
230
+ }
231
+ const payload = await response.json().catch(() => null);
232
+ if (!payload || typeof payload !== 'object' || payload.status !== 'ok') {
233
+ return null;
234
+ }
235
+ return payload;
205
236
  }
206
237
  catch {
207
- return false;
238
+ return null;
208
239
  }
209
240
  finally {
210
241
  clearTimeout(timeout);
211
242
  }
212
243
  }
244
+ function isCompatibleRouterHealth(health) {
245
+ return health.protocolVersion === DEV_ROUTER_PROTOCOL_VERSION
246
+ && health.capabilities?.runtimeAssetManifest === true
247
+ && health.capabilities?.runtimeAssetManifestLookup === true;
248
+ }
213
249
  async function ensureDevRouterRunning(port = exports.DEV_ROUTER_PORT) {
214
- if (await isRouterHealthy(port)) {
215
- return;
250
+ const existingHealth = await fetchRouterHealth(port);
251
+ if (existingHealth) {
252
+ if (isCompatibleRouterHealth(existingHealth)) {
253
+ return;
254
+ }
255
+ throw new Error(`dev_router_incompatible:port_${port}`);
216
256
  }
217
257
  const cliEntrypoint = getCliEntrypointPath();
218
258
  const child = (0, node_child_process_1.spawn)(process.execPath, [cliEntrypoint, 'project', '_dev-router', 'serve'], {
@@ -226,7 +266,8 @@ async function ensureDevRouterRunning(port = exports.DEV_ROUTER_PORT) {
226
266
  child.unref();
227
267
  const deadline = Date.now() + 5000;
228
268
  while (Date.now() < deadline) {
229
- if (await isRouterHealthy(port, 250)) {
269
+ const health = await fetchRouterHealth(port, 250);
270
+ if (health && isCompatibleRouterHealth(health)) {
230
271
  return;
231
272
  }
232
273
  await new Promise((resolveTimeout) => setTimeout(resolveTimeout, 150));
@@ -271,8 +312,24 @@ async function unregisterDevMount(mountId, port = exports.DEV_ROUTER_PORT) {
271
312
  throw new Error(`mount_unregister_failed:${response.status}`);
272
313
  }
273
314
  }
315
+ async function updateMountedDevRuntimeAssetManifest(input) {
316
+ const port = input.port ?? exports.DEV_ROUTER_PORT;
317
+ const response = await fetch(`http://${DEV_ROUTER_HOST}:${port}${CONTROL_PREFIX}/apps/dev/${encodeURIComponent(input.creatorUsername)}/${encodeURIComponent(input.appName)}/runtime-assets`, {
318
+ method: 'POST',
319
+ headers: { 'Content-Type': 'application/json' },
320
+ body: JSON.stringify({
321
+ runtimeAssetManifest: input.runtimeAssetManifest ?? null,
322
+ }),
323
+ });
324
+ if (!response.ok) {
325
+ const payload = await response.json().catch(() => null);
326
+ throw new Error(typeof payload?.error === 'string'
327
+ ? payload.error
328
+ : `runtime_asset_manifest_update_failed:${response.status}`);
329
+ }
330
+ }
274
331
  async function startDevServer(options) {
275
- const { appName, appType, creatorUsername, htmlPath, port, projectInfo } = options;
332
+ const { appName, appType, creatorUsername, htmlPath, port, projectInfo, runtimeAssetManifest } = options;
276
333
  const ownerPid = process.pid;
277
334
  let closing = false;
278
335
  const devProcess = spawnDevScript(projectInfo);
@@ -301,6 +358,7 @@ async function startDevServer(options) {
301
358
  staticRoot,
302
359
  repoRoot,
303
360
  ownerPid,
361
+ runtimeAssetManifest: runtimeAssetManifest ?? null,
304
362
  }, port);
305
363
  mountId = registeredMount.mountId;
306
364
  appUrl = registeredMount.appUrl;
@@ -355,7 +413,8 @@ async function isDevServerAvailable(input, timeoutMs = 1000) {
355
413
  clearTimeout(timeout);
356
414
  }
357
415
  }
358
- async function runDevRouterServer(port = Number(process.env.PLAYDROP_DEV_ROUTER_PORT) || exports.DEV_ROUTER_PORT) {
416
+ function createDevRouterServer(initialPort = exports.DEV_ROUTER_PORT) {
417
+ let activePort = initialPort;
359
418
  // eslint-disable-next-line complexity
360
419
  const handleRequest = async (req, res) => {
361
420
  const method = req.method || 'GET';
@@ -369,16 +428,86 @@ async function runDevRouterServer(port = Number(process.env.PLAYDROP_DEV_ROUTER_
369
428
  res.end();
370
429
  return;
371
430
  }
372
- let pathname = '/';
431
+ let requestUrl;
373
432
  try {
374
- pathname = decodeURIComponent(new URL(req.url || '/', `http://${DEV_ROUTER_HOST}:${port}`).pathname);
433
+ requestUrl = new URL(req.url || '/', `http://${DEV_ROUTER_HOST}:${activePort}`);
375
434
  }
376
435
  catch {
377
436
  respondWithText(res, method, 400, 'Invalid URL path');
378
437
  return;
379
438
  }
439
+ const pathname = decodeURIComponent(requestUrl.pathname);
440
+ const searchParams = requestUrl.searchParams;
380
441
  if (pathname === `${CONTROL_PREFIX}/health` && method === 'GET') {
381
- sendJson(res, 200, { status: 'ok' });
442
+ sendJson(res, 200, {
443
+ status: 'ok',
444
+ protocolVersion: DEV_ROUTER_PROTOCOL_VERSION,
445
+ capabilities: {
446
+ runtimeAssetManifest: true,
447
+ runtimeAssetManifestLookup: true,
448
+ },
449
+ });
450
+ return;
451
+ }
452
+ if (pathname.startsWith(`${CONTROL_PREFIX}/apps/dev/`) && pathname.endsWith('/runtime-assets') && method === 'GET') {
453
+ cleanupStaleRouterMounts();
454
+ const segments = pathname.split('/').filter(Boolean);
455
+ if (segments.length !== 6) {
456
+ sendJson(res, 404, { error: 'runtime_asset_manifest_not_found' });
457
+ return;
458
+ }
459
+ const creatorUsername = decodeURIComponent(segments[3] ?? '');
460
+ const appName = decodeURIComponent(segments[4] ?? '');
461
+ if (!creatorUsername || !appName) {
462
+ sendJson(res, 400, { error: 'invalid_runtime_asset_manifest_lookup' });
463
+ return;
464
+ }
465
+ let mount;
466
+ try {
467
+ mount = findRouterMountByCreatorAndApp(creatorUsername, appName);
468
+ }
469
+ catch (error) {
470
+ const message = error instanceof Error ? error.message : 'ambiguous_mount_lookup';
471
+ sendJson(res, 409, { error: message });
472
+ return;
473
+ }
474
+ if (!mount?.runtimeAssetManifest) {
475
+ sendJson(res, 404, { error: 'runtime_asset_manifest_not_found' });
476
+ return;
477
+ }
478
+ sendJson(res, 200, mount.runtimeAssetManifest.response);
479
+ return;
480
+ }
481
+ if (pathname.startsWith(`${CONTROL_PREFIX}/apps/dev/`) && pathname.endsWith('/runtime-assets') && method === 'POST') {
482
+ cleanupStaleRouterMounts();
483
+ const segments = pathname.split('/').filter(Boolean);
484
+ if (segments.length !== 6) {
485
+ sendJson(res, 404, { error: 'runtime_asset_manifest_not_found' });
486
+ return;
487
+ }
488
+ const creatorUsername = decodeURIComponent(segments[3] ?? '');
489
+ const appName = decodeURIComponent(segments[4] ?? '');
490
+ if (!creatorUsername || !appName) {
491
+ sendJson(res, 400, { error: 'invalid_runtime_asset_manifest_lookup' });
492
+ return;
493
+ }
494
+ let mount;
495
+ try {
496
+ mount = findRouterMountByCreatorAndApp(creatorUsername, appName);
497
+ }
498
+ catch (error) {
499
+ const message = error instanceof Error ? error.message : 'ambiguous_mount_lookup';
500
+ sendJson(res, 409, { error: message });
501
+ return;
502
+ }
503
+ if (!mount) {
504
+ sendJson(res, 404, { error: 'runtime_asset_manifest_not_found' });
505
+ return;
506
+ }
507
+ const body = await readJsonBody(req);
508
+ mount.runtimeAssetManifest = body.runtimeAssetManifest ?? null;
509
+ mount.updatedAt = Date.now();
510
+ sendJson(res, 200, { ok: true });
382
511
  return;
383
512
  }
384
513
  if (pathname === `${CONTROL_PREFIX}/mounts` && method === 'POST') {
@@ -414,9 +543,10 @@ async function runDevRouterServer(port = Number(process.env.PLAYDROP_DEV_ROUTER_
414
543
  }
415
544
  existingMount.ownerPid = ownerPid;
416
545
  existingMount.updatedAt = Date.now();
546
+ existingMount.runtimeAssetManifest = body.runtimeAssetManifest ?? null;
417
547
  sendJson(res, 200, {
418
548
  mountId: existingMount.id,
419
- appUrl: buildLocalDevAppUrl({ creatorUsername, appType, appName, port }),
549
+ appUrl: buildLocalDevAppUrl({ creatorUsername, appType, appName, port: activePort }),
420
550
  creatorUsername,
421
551
  appType,
422
552
  appName,
@@ -436,12 +566,13 @@ async function runDevRouterServer(port = Number(process.env.PLAYDROP_DEV_ROUTER_
436
566
  repoRoot,
437
567
  ownerPid,
438
568
  updatedAt: Date.now(),
569
+ runtimeAssetManifest: body.runtimeAssetManifest ?? null,
439
570
  };
440
571
  routerMountsById.set(mount.id, mount);
441
572
  routerMountIdsByKey.set(key, mount.id);
442
573
  sendJson(res, 200, {
443
574
  mountId: mount.id,
444
- appUrl: buildLocalDevAppUrl({ creatorUsername, appType, appName, port }),
575
+ appUrl: buildLocalDevAppUrl({ creatorUsername, appType, appName, port: activePort }),
445
576
  creatorUsername,
446
577
  appType,
447
578
  appName,
@@ -494,6 +625,40 @@ async function runDevRouterServer(port = Number(process.env.PLAYDROP_DEV_ROUTER_
494
625
  respondWithText(res, method, 404, 'Not found');
495
626
  return;
496
627
  }
628
+ if (parsedMountPath.assetPath === '_playdrop/runtime-assets') {
629
+ if (!mount.runtimeAssetManifest) {
630
+ sendJson(res, 500, { error: 'runtime_asset_manifest_not_registered' });
631
+ return;
632
+ }
633
+ sendJson(res, 200, mount.runtimeAssetManifest.response);
634
+ return;
635
+ }
636
+ if (parsedMountPath.assetPath.startsWith('_playdrop/runtime-assets/files/')) {
637
+ const ownedAssetName = parsedMountPath.assetPath.slice('_playdrop/runtime-assets/files/'.length).trim();
638
+ const role = (searchParams.get('role') || 'primary').trim().toLowerCase();
639
+ if (!ownedAssetName || !role) {
640
+ respondWithText(res, method, 400, 'Invalid runtime asset file request');
641
+ return;
642
+ }
643
+ const ownedFile = getOwnedRuntimeAssetFile(mount, ownedAssetName, role);
644
+ if (!ownedFile) {
645
+ respondWithText(res, method, 404, 'Not found');
646
+ return;
647
+ }
648
+ try {
649
+ const stats = (0, node_fs_1.statSync)(ownedFile.absolutePath);
650
+ if (!stats.isFile()) {
651
+ respondWithText(res, method, 404, 'Not found');
652
+ return;
653
+ }
654
+ const content = (0, node_fs_1.readFileSync)(ownedFile.absolutePath);
655
+ respondWithBuffer(res, method, 200, ownedFile.contentType || resolveContentType(ownedFile.absolutePath), content);
656
+ }
657
+ catch {
658
+ respondWithText(res, method, 404, 'Not found');
659
+ }
660
+ return;
661
+ }
497
662
  if (parsedMountPath.assetPath === 'index.html') {
498
663
  try {
499
664
  const freshHtml = (0, node_fs_1.readFileSync)(mount.htmlPath);
@@ -525,6 +690,15 @@ async function runDevRouterServer(port = Number(process.env.PLAYDROP_DEV_ROUTER_
525
690
  const server = node_http_1.default.createServer((req, res) => {
526
691
  void handleRequest(req, res);
527
692
  });
693
+ server.on('listening', () => {
694
+ const address = server.address();
695
+ if (address && typeof address === 'object' && typeof address.port === 'number') {
696
+ activePort = address.port;
697
+ }
698
+ });
699
+ return server;
700
+ }
701
+ async function listenDevRouterServer(server, port = exports.DEV_ROUTER_PORT) {
528
702
  await new Promise((resolveListen, rejectListen) => {
529
703
  const onError = (error) => {
530
704
  server.off('listening', onListening);
@@ -538,6 +712,15 @@ async function runDevRouterServer(port = Number(process.env.PLAYDROP_DEV_ROUTER_
538
712
  server.once('listening', onListening);
539
713
  server.listen(port, DEV_ROUTER_HOST);
540
714
  });
715
+ const address = server.address();
716
+ if (!address || typeof address !== 'object' || typeof address.port !== 'number') {
717
+ throw new Error('dev_router_port_unavailable');
718
+ }
719
+ return address.port;
720
+ }
721
+ async function runDevRouterServer(port = Number(process.env.PLAYDROP_DEV_ROUTER_PORT) || exports.DEV_ROUTER_PORT) {
722
+ const server = createDevRouterServer(port);
723
+ await listenDevRouterServer(server, port);
541
724
  await new Promise(() => {
542
725
  // Keep the router alive until the process exits.
543
726
  });
@@ -10,10 +10,9 @@ const promises_1 = __importDefault(require("node:readline/promises"));
10
10
  const node_process_1 = require("node:process");
11
11
  const node_child_process_1 = require("node:child_process");
12
12
  const types_1 = require("@playdrop/types");
13
- const config_1 = require("../config");
14
13
  const apiClient_1 = require("../apiClient");
14
+ const commandContext_1 = require("../commandContext");
15
15
  const http_1 = require("../http");
16
- const environment_1 = require("../environment");
17
16
  const messages_1 = require("../messages");
18
17
  const clientInfo_1 = require("../clientInfo");
19
18
  const DEFAULT_CATALOGUE = '{}\n';
@@ -197,31 +196,14 @@ async function init(targetPath, options = {}) {
197
196
  (0, node_fs_1.mkdirSync)(resolved, { recursive: true });
198
197
  const cataloguePath = (0, node_path_1.join)(resolved, 'catalogue.json');
199
198
  const catalogueExisted = (0, node_fs_1.existsSync)(cataloguePath);
200
- const cfg = (0, config_1.loadConfig)();
201
- if (!cfg.token) {
202
- (0, messages_1.printLoginRequired)('Bootstrapping a Playdrop project', 'project init');
203
- process.exitCode = 1;
204
- return null;
205
- }
206
- if (!cfg.env) {
207
- (0, messages_1.printConfigEnvironmentMissing)('project init');
208
- process.exitCode = 1;
199
+ const ctx = await (0, commandContext_1.resolveAuthenticatedEnvironmentContext)('project init', 'Bootstrapping a Playdrop project', { workspacePath: resolved });
200
+ if (!ctx) {
209
201
  return null;
210
202
  }
211
- const preferredEnv = cfg.env;
212
- const envConfig = (0, environment_1.resolveEnvironmentConfig)(preferredEnv);
213
- if (!envConfig) {
214
- const choices = (0, environment_1.formatEnvironmentList)();
215
- (0, messages_1.printUnknownEnvironment)(preferredEnv || '', choices, 'project init');
216
- process.exitCode = 1;
217
- return null;
218
- }
219
- if (envConfig.allowInsecureRequests) {
220
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
221
- }
203
+ const { envConfig, token } = ctx;
222
204
  let bootstrap = null;
223
205
  try {
224
- bootstrap = await fetchBootstrap(envConfig.apiBase, cfg.token);
206
+ bootstrap = await fetchBootstrap(envConfig.apiBase, token);
225
207
  }
226
208
  catch (error) {
227
209
  if (error instanceof http_1.CLIUnsupportedClientError) {
@@ -240,7 +222,7 @@ async function init(targetPath, options = {}) {
240
222
  const agentsExisted = (0, node_fs_1.existsSync)(agentsPath);
241
223
  let username = null;
242
224
  try {
243
- username = await resolveUsername(envConfig.apiBase, cfg.token);
225
+ username = await resolveUsername(envConfig.apiBase, token);
244
226
  }
245
227
  catch (error) {
246
228
  if (error instanceof http_1.CLIUnsupportedClientError) {
@@ -1,6 +1,10 @@
1
1
  type SearchOptions = {
2
2
  kind?: string;
3
+ creator?: string;
3
4
  appType?: string;
5
+ auth?: string;
6
+ controller?: string;
7
+ surface?: string;
4
8
  assetCategory?: string;
5
9
  assetSubcategory?: string;
6
10
  assetSpec?: string;
@@ -7,6 +7,7 @@ const errors_1 = require("../errors");
7
7
  const messages_1 = require("../messages");
8
8
  const output_1 = require("../output");
9
9
  const refs_1 = require("../refs");
10
+ const CREATOR_USERNAME_FILTER_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
10
11
  function parseSearchKind(raw) {
11
12
  if (!raw || raw.trim().length === 0) {
12
13
  return 'all';
@@ -123,12 +124,6 @@ function buildCountsSuffix(item) {
123
124
  if (typeof item.item.viewCount === 'number') {
124
125
  parts.push(`view ${formatCountValue(item.item.viewCount)}`);
125
126
  }
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
- }
132
127
  }
133
128
  if (item.kind === 'asset-pack' && typeof item.item.downloadCount === 'number') {
134
129
  parts.push(`download ${formatCountValue(item.item.downloadCount)}`);
@@ -206,7 +201,11 @@ function buildSearchRequest(input) {
206
201
  page: 1,
207
202
  pageSize: input.limit + input.offset,
208
203
  ...(input.sort ? { sort: input.sort } : {}),
204
+ ...(input.creatorUsername ? { creatorUsername: input.creatorUsername } : {}),
209
205
  appType: input.appType,
206
+ auth: input.auth,
207
+ controller: input.controller,
208
+ surface: input.surface,
210
209
  assetCategory: input.assetCategory,
211
210
  assetSubcategory: input.assetSubcategory,
212
211
  assetSpec: input.assetSpec,
@@ -219,8 +218,27 @@ function buildSearchRequest(input) {
219
218
  }
220
219
  async function search(query, options = {}) {
221
220
  const trimmedQuery = typeof query === 'string' ? query.trim() : '';
222
- if (!trimmedQuery) {
223
- (0, messages_1.printErrorWithHelp)('A search query is required.', ['Example: playdrop search "city builder"'], { command: 'search' });
221
+ const creatorUsername = normalizeOptionalString(options.creator)?.toLowerCase();
222
+ if (creatorUsername && !CREATOR_USERNAME_FILTER_REGEX.test(creatorUsername)) {
223
+ (0, messages_1.printErrorWithHelp)('The --creator value is invalid.', ['Use a creator username like playdrop or olivier-dev.'], { command: 'search' });
224
+ process.exitCode = 1;
225
+ return;
226
+ }
227
+ const hasScopeFilters = Boolean(creatorUsername)
228
+ || Boolean(options.appType)
229
+ || Boolean(options.auth)
230
+ || Boolean(options.controller)
231
+ || Boolean(options.surface)
232
+ || Boolean(options.assetCategory)
233
+ || Boolean(options.assetSubcategory)
234
+ || Boolean(options.assetSpec)
235
+ || Boolean(options.assetSpecOwner)
236
+ || Boolean(options.assetSpecName)
237
+ || Boolean(options.packContainsCategory)
238
+ || Boolean(options.packContainsSubcategory)
239
+ || Array.isArray(options.tag) && options.tag.length > 0;
240
+ if (!trimmedQuery && !hasScopeFilters) {
241
+ (0, messages_1.printErrorWithHelp)('A search query or filter is required.', ['Example: playdrop search "city builder"', 'Example: playdrop search --creator playdrop --kind app'], { command: 'search' });
224
242
  process.exitCode = 1;
225
243
  return;
226
244
  }
@@ -244,7 +262,7 @@ async function search(query, options = {}) {
244
262
  }
245
263
  const sort = parseSearchSort(options.sort);
246
264
  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' });
265
+ (0, messages_1.printErrorWithHelp)(`Sort "${options.sort ?? ''}" is not supported.`, ['Use one of: relevance, views, likes, used, newest, updated, downloads, remixes, comments, assets, apps, alpha.'], { command: 'search' });
248
266
  process.exitCode = 1;
249
267
  return;
250
268
  }
@@ -254,6 +272,30 @@ async function search(query, options = {}) {
254
272
  process.exitCode = 1;
255
273
  return;
256
274
  }
275
+ const auth = options.auth === undefined ? undefined : (0, types_1.parseAppAuthFilter)(options.auth);
276
+ if (options.auth !== undefined && !auth) {
277
+ (0, messages_1.printErrorWithHelp)(`Auth filter "${options.auth}" is not supported.`, ['Use one of: required, optional, none.'], { command: 'search' });
278
+ process.exitCode = 1;
279
+ return;
280
+ }
281
+ const controller = options.controller === undefined ? undefined : (0, types_1.parseAppControllerFilter)(options.controller);
282
+ if (options.controller !== undefined && !controller) {
283
+ (0, messages_1.printErrorWithHelp)(`Controller filter "${options.controller}" is not supported.`, ['Use one of: required, optional, none.'], { command: 'search' });
284
+ process.exitCode = 1;
285
+ return;
286
+ }
287
+ const surface = options.surface === undefined ? undefined : (0, types_1.parseAppSurfaceFilter)(options.surface);
288
+ if (options.surface !== undefined && !surface) {
289
+ (0, messages_1.printErrorWithHelp)(`Surface filter "${options.surface}" is not supported.`, ['Use one of: desktop, mobile-portrait, mobile-landscape.'], { command: 'search' });
290
+ process.exitCode = 1;
291
+ return;
292
+ }
293
+ const hasCapabilityFilters = Boolean(auth || controller || surface);
294
+ if (hasCapabilityFilters && kind !== 'all' && kind !== 'app') {
295
+ (0, messages_1.printErrorWithHelp)('Capability filters only support app search.', ['Use --kind app, or omit --kind to search across app results only.'], { command: 'search' });
296
+ process.exitCode = 1;
297
+ return;
298
+ }
257
299
  const assetCategory = parseAssetCategory(options.assetCategory);
258
300
  if (assetCategory === null) {
259
301
  (0, messages_1.printErrorWithHelp)(`Asset category "${options.assetCategory}" is not supported.`, ['Use a canonical asset category like IMAGE, VIDEO, AUDIO, SPRITESHEET, or MODEL_3D.'], { command: 'search' });
@@ -278,6 +320,17 @@ async function search(query, options = {}) {
278
320
  process.exitCode = 1;
279
321
  return;
280
322
  }
323
+ if (hasCapabilityFilters && (assetCategory
324
+ || assetSubcategory
325
+ || normalizeOptionalString(options.assetSpec)
326
+ || normalizeOptionalString(options.assetSpecOwner)
327
+ || normalizeOptionalString(options.assetSpecName)
328
+ || packContainsCategory
329
+ || packContainsSubcategory)) {
330
+ (0, messages_1.printErrorWithHelp)('Capability filters cannot be combined with asset or pack search filters.', ['Use app search filters only when passing --auth, --surface, or --controller.'], { command: 'search' });
331
+ process.exitCode = 1;
332
+ return;
333
+ }
281
334
  const tags = normalizeTagFilters(options.tag);
282
335
  if (tags === null) {
283
336
  (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' });
@@ -289,10 +342,14 @@ async function search(query, options = {}) {
289
342
  const response = await client.search(buildSearchRequest({
290
343
  trimmedQuery,
291
344
  kind,
345
+ ...(creatorUsername ? { creatorUsername } : {}),
292
346
  limit,
293
347
  offset,
294
348
  sort: sort ?? undefined,
295
349
  appType: appType,
350
+ auth: auth ?? undefined,
351
+ controller: controller ?? undefined,
352
+ surface: surface ?? undefined,
296
353
  assetCategory: assetCategory ?? undefined,
297
354
  assetSubcategory: assetSubcategory ?? undefined,
298
355
  assetSpec: normalizeOptionalString(options.assetSpec),
@@ -317,11 +374,11 @@ async function search(query, options = {}) {
317
374
  return;
318
375
  }
319
376
  if (items.length === 0) {
320
- console.log(`No results found for "${trimmedQuery}".`);
377
+ console.log(trimmedQuery ? `No results found for "${trimmedQuery}".` : 'No results found.');
321
378
  console.log('Next: adjust your filters or run "playdrop browse" to explore available content.');
322
379
  return;
323
380
  }
324
- console.log(`Search results for "${trimmedQuery}":\n`);
381
+ console.log(trimmedQuery ? `Search results for "${trimmedQuery}":\n` : 'Search results:\n');
325
382
  for (const [index, item] of items.entries()) {
326
383
  const displayName = item.item.displayName || item.item.name;
327
384
  console.log(`${index + 1}. [${item.kind}] ${item.ref} | ${displayName} | @${item.item.creatorUsername} | ${itemSummary(item)}${buildCountsSuffix(item)}${buildTagSuffix(item.item.tags)}`);
@@ -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, AssetSpecTask, AssetTask, EmbeddedAssetTask } from '../catalogue';
3
+ import type { AssetPackTask, AssetSpecTask, AssetTask, OwnedAssetTask, PackOwnedAssetTask } from '../catalogue';
4
4
  import type { CliTask } from '../taskUtils';
5
5
  export type CurrentUserRole = 'USER' | 'CREATOR' | 'ADMIN' | null;
6
6
  export type UploadedAssetInfo = {
@@ -70,11 +70,11 @@ export declare function parseUnversionedAssetTaskRef(rawRef: string, fallbackCre
70
70
  name: string;
71
71
  } | null;
72
72
  export declare function buildAssetPackUploadPlans(tasks: CliTask[], defaultCreator: string, currentUserRole: CurrentUserRole): PackUploadPlanningResult;
73
- export declare function uploadAssetTask(client: ApiClient, task: AssetTask | EmbeddedAssetTask, sourceAppVersionId?: number, creatorUsername?: string, options?: {
73
+ export declare function uploadAssetTask(client: ApiClient, task: AssetTask | OwnedAssetTask, sourceAppVersionId?: number, creatorUsername?: string, options?: {
74
74
  clearTags?: boolean;
75
75
  }): Promise<UploadedAssetInfo>;
76
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?: {
77
+ export declare function uploadAssetPackTask(client: ApiClient, task: AssetPackTask, creatorUsername: string, uploadedAssets: Map<string, UploadedAssetInfo>, localAssetTasks: PackOwnedAssetTask[], uploadKeyByAssetKey: Map<string, string>, targetCreatorUsername?: string, options?: {
78
78
  clearTags?: boolean;
79
79
  }): Promise<UploadedPackInfo>;
80
80
  export {};