@playdrop/playdrop-cli 0.11.8 → 0.12.0

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 (116) hide show
  1. package/config/client-meta.json +2 -2
  2. package/dist/apps/build.d.ts +1 -1
  3. package/dist/apps/build.js +4 -6
  4. package/dist/apps/index.d.ts +4 -5
  5. package/dist/apps/index.js +14 -16
  6. package/dist/apps/loadCheck.d.ts +76 -0
  7. package/dist/apps/loadCheck.js +874 -0
  8. package/dist/apps/upload.d.ts +3 -2
  9. package/dist/apps/upload.js +112 -9
  10. package/dist/assetSpecs.js +2 -5
  11. package/dist/catalogue-utils.d.ts +1 -0
  12. package/dist/catalogue-utils.js +1 -0
  13. package/dist/catalogue.d.ts +3 -1
  14. package/dist/catalogue.js +19 -3
  15. package/dist/commands/browse.js +2 -2
  16. package/dist/commands/build.js +4 -1
  17. package/dist/commands/captureListing.d.ts +30 -1
  18. package/dist/commands/captureListing.js +308 -125
  19. package/dist/commands/check.d.ts +8 -0
  20. package/dist/commands/check.js +188 -0
  21. package/dist/commands/create.js +17 -15
  22. package/dist/commands/createRemixContent.js +2 -2
  23. package/dist/commands/detail.js +2 -2
  24. package/dist/commands/dev.js +9 -12
  25. package/dist/commands/devServer.d.ts +2 -2
  26. package/dist/commands/devServer.js +3 -4
  27. package/dist/commands/marketing.js +10 -11
  28. package/dist/commands/play.js +2 -2
  29. package/dist/commands/review.d.ts +20 -0
  30. package/dist/commands/review.js +112 -0
  31. package/dist/commands/upload-content.d.ts +1 -1
  32. package/dist/commands/upload-content.js +1 -1
  33. package/dist/commands/upload.d.ts +2 -1
  34. package/dist/commands/upload.js +9 -52
  35. package/dist/commands/validate.js +4 -161
  36. package/dist/commands/worker/runtime.d.ts +1 -0
  37. package/dist/commands/worker/runtime.js +48 -5
  38. package/dist/commands/worker.d.ts +5 -6
  39. package/dist/commands/worker.js +42 -92
  40. package/dist/config.js +1 -2
  41. package/dist/index.js +33 -49
  42. package/dist/playwright.d.ts +5 -0
  43. package/dist/playwright.js +60 -1
  44. package/node_modules/@playdrop/api-client/dist/client.d.ts +5 -3
  45. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  46. package/node_modules/@playdrop/api-client/dist/client.js +10 -27
  47. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +1 -1
  48. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  49. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +2 -1
  50. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  51. package/node_modules/@playdrop/api-client/dist/domains/apps.js +11 -0
  52. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +1 -1
  53. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  54. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +3 -1
  55. package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -1
  56. package/node_modules/@playdrop/api-client/dist/domains/auth.js +22 -0
  57. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +1 -1
  58. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  59. package/node_modules/@playdrop/api-client/dist/index.d.ts +7 -6
  60. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  61. package/node_modules/@playdrop/api-client/dist/index.js +11 -6
  62. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js +0 -7
  63. package/node_modules/@playdrop/boxel-core/dist/src/entity-cleaner.js.map +1 -1
  64. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js +2 -46
  65. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/textured-builder.js.map +1 -1
  66. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js +2 -22
  67. package/node_modules/@playdrop/boxel-core/dist/src/humanoid/r15/voxel-builder.js.map +1 -1
  68. package/node_modules/@playdrop/boxel-core/dist/src/palette_tools.js.map +1 -1
  69. package/node_modules/@playdrop/boxel-core/dist/src/serialization.js +1 -1
  70. package/node_modules/@playdrop/boxel-core/dist/src/serialization.js.map +1 -1
  71. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/infer-face.js.map +1 -1
  72. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/layer-mode.js +2 -2
  73. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/layer-mode.js.map +1 -1
  74. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.js +0 -11
  75. package/node_modules/@playdrop/boxel-core/dist/src/transforms/textured-boxes/slice.js.map +1 -1
  76. package/node_modules/@playdrop/boxel-core/dist/src/transforms/upscale.js.map +1 -1
  77. package/node_modules/@playdrop/boxel-core/dist/src/validation.d.ts +1 -1
  78. package/node_modules/@playdrop/boxel-core/dist/src/validation.js +0 -20
  79. package/node_modules/@playdrop/boxel-core/dist/src/validation.js.map +1 -1
  80. package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.d.ts +2 -2
  81. package/node_modules/@playdrop/boxel-three/dist/src/exporters/glb.js +2 -12
  82. package/node_modules/@playdrop/boxel-three/dist/src/voxels/faces.js +1 -125
  83. package/node_modules/@playdrop/config/client-meta.json +2 -2
  84. package/node_modules/@playdrop/config/dist/src/client-meta.d.ts +8 -0
  85. package/node_modules/@playdrop/config/dist/src/client-meta.d.ts.map +1 -0
  86. package/node_modules/@playdrop/config/dist/src/client-meta.js +51 -0
  87. package/node_modules/@playdrop/config/dist/src/deployment.d.ts.map +1 -1
  88. package/node_modules/@playdrop/config/dist/src/deployment.js +2 -32
  89. package/node_modules/@playdrop/config/dist/src/index.d.ts +2 -2
  90. package/node_modules/@playdrop/config/dist/src/index.d.ts.map +1 -1
  91. package/node_modules/@playdrop/config/dist/src/index.js +3 -29
  92. package/node_modules/@playdrop/config/dist/src/runtime-service-origins.d.ts +0 -1
  93. package/node_modules/@playdrop/config/dist/src/runtime-service-origins.d.ts.map +1 -1
  94. package/node_modules/@playdrop/config/dist/src/runtime-service-origins.js +21 -5
  95. package/node_modules/@playdrop/config/dist/src/server/fastify.d.ts +2 -2
  96. package/node_modules/@playdrop/config/dist/src/server/fastify.d.ts.map +1 -1
  97. package/node_modules/@playdrop/config/dist/src/server/fastify.js +0 -1
  98. package/node_modules/@playdrop/config/dist/test/runtime-service-origins.test.js +8 -0
  99. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  100. package/node_modules/@playdrop/types/dist/api.d.ts +25 -24
  101. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  102. package/node_modules/@playdrop/types/dist/api.js +5 -10
  103. package/node_modules/@playdrop/types/dist/version.d.ts +14 -0
  104. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  105. package/package.json +1 -1
  106. package/dist/apps/launchCheck.d.ts +0 -39
  107. package/dist/apps/launchCheck.js +0 -414
  108. package/dist/captureRuntime.d.ts +0 -73
  109. package/dist/captureRuntime.js +0 -1021
  110. package/dist/commands/capture.d.ts +0 -14
  111. package/dist/commands/capture.js +0 -550
  112. package/dist/commands/captureRemote.d.ts +0 -17
  113. package/dist/commands/captureRemote.js +0 -394
  114. package/node_modules/@playdrop/types/dist/entity.d.ts +0 -31
  115. package/node_modules/@playdrop/types/dist/entity.d.ts.map +0 -1
  116. package/node_modules/@playdrop/types/dist/entity.js +0 -67
@@ -8,10 +8,10 @@ exports.buildAssetPackUploadPlans = buildAssetPackUploadPlans;
8
8
  exports.uploadAssetTask = uploadAssetTask;
9
9
  exports.uploadAssetSpecTask = uploadAssetSpecTask;
10
10
  exports.uploadAssetPackTask = uploadAssetPackTask;
11
+ const types_1 = require("@playdrop/types");
11
12
  const node_crypto_1 = require("node:crypto");
12
13
  const node_fs_1 = require("node:fs");
13
14
  const node_path_1 = require("node:path");
14
- const types_1 = require("@playdrop/types");
15
15
  const model_artifacts_1 = require("../assets/model-artifacts");
16
16
  const assetSpecs_1 = require("../assetSpecs");
17
17
  const EXTENSION_TO_FORMAT = {
@@ -1,5 +1,5 @@
1
1
  import type { ApiClient } from '@playdrop/api-client';
2
- import type { UserResponse } from '@playdrop/types';
2
+ import type { AgentExecutionTarget, UserResponse } from '@playdrop/types';
3
3
  export type UploadCommandOptions = {
4
4
  env?: string;
5
5
  skipReview?: boolean;
@@ -10,6 +10,7 @@ export type WorkerAppPublishInput = {
10
10
  client: ApiClient;
11
11
  taskId: number;
12
12
  kind: 'NEW_GAME' | 'REMIX_GAME' | 'GAME_UPDATE';
13
+ executionTarget?: AgentExecutionTarget;
13
14
  expectedAppName?: string | null;
14
15
  remixSourceRef?: string | null;
15
16
  playdropAssetRequirement?: WorkerPlaydropAssetRequirement | null;
@@ -50,7 +50,6 @@ const taskUtils_1 = require("../taskUtils");
50
50
  const uploadLog_1 = require("../uploadLog");
51
51
  const upload_content_1 = require("./upload-content");
52
52
  const appUrls_1 = require("../appUrls");
53
- const launchCheck_1 = require("../apps/launchCheck");
54
53
  const devRuntimeAssets_1 = require("./devRuntimeAssets");
55
54
  const upload_graph_1 = require("./upload-graph");
56
55
  function normalizeSavedItemKinds(kinds) {
@@ -856,8 +855,6 @@ async function uploadAppTask(state, task, taskCreator, options) {
856
855
  webBase: state.portalBase,
857
856
  token: state.token,
858
857
  user: state.currentUser,
859
- runLocalLaunchCheck: true,
860
- runStagedUploadLaunchCheck: true,
861
858
  ensureRegisteredAppShell: true,
862
859
  });
863
860
  if (!upload.versionCreated || !upload.version) {
@@ -2137,7 +2134,7 @@ function assertNewGameListingScreenshots(task) {
2137
2134
  function readPlaytestEvidenceManifest(task) {
2138
2135
  const manifestPath = (0, node_path_1.join)(task.projectDir, 'playtest-evidence.json');
2139
2136
  if (!(0, node_fs_1.existsSync)(manifestPath)) {
2140
- throw new Error(formatMissingPlaytestEvidenceManifestError('new game tasks must include playtest-evidence.json before upload.'));
2137
+ throw new Error(formatMissingPlaytestEvidenceManifestError('task uploads must include playtest-evidence.json before upload.'));
2141
2138
  }
2142
2139
  try {
2143
2140
  const value = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, 'utf8'));
@@ -2183,7 +2180,7 @@ function assertPlaytestCapturePaths(task, captures) {
2183
2180
  }
2184
2181
  }
2185
2182
  }
2186
- function assertNewGamePlaytestEvidenceManifest(task) {
2183
+ function assertTaskPlaytestEvidenceManifest(task) {
2187
2184
  const manifest = readPlaytestEvidenceManifest(task);
2188
2185
  const entries = Array.isArray(manifest.entries) ? manifest.entries : [];
2189
2186
  if (entries.length === 0) {
@@ -2222,28 +2219,6 @@ function assertNewGamePlaytestEvidenceManifest(task) {
2222
2219
  throw new Error(formatPlaytestEvidenceManifestError(`playtest-evidence.json must include a non-final self-playtest checkedAt after the last source change${sourceLastModifiedAt ? ` (${sourceLastModifiedAt})` : ''}.`));
2223
2220
  }
2224
2221
  }
2225
- function writeFinalPublishedRouteEvidence(input) {
2226
- const manifest = readPlaytestEvidenceManifest(input.task);
2227
- const entries = Array.isArray(manifest.entries) ? manifest.entries : [];
2228
- const finalEntry = {
2229
- environment: 'final',
2230
- url: input.url,
2231
- surface: 'DESKTOP',
2232
- captures: [],
2233
- actions: ['open owner draft play URL'],
2234
- statesObserved: input.status === 'PASSED' ? ['ready', 'nonblank-first-frame'] : [],
2235
- consoleErrors: input.message ? [input.message] : [],
2236
- checkedAt: input.checkedAt,
2237
- sourceLastModifiedAt: readRuntimeSourceLastModifiedAt(input.task.projectDir),
2238
- };
2239
- const nextManifest = {
2240
- ...manifest,
2241
- version: 1,
2242
- entries: [...entries, finalEntry],
2243
- finalPublishedRoute: finalEntry,
2244
- };
2245
- (0, node_fs_1.writeFileSync)((0, node_path_1.join)(input.task.projectDir, 'playtest-evidence.json'), `${JSON.stringify(nextManifest, null, 2)}\n`, 'utf8');
2246
- }
2247
2222
  function readRuntimeSourceLastModifiedAt(rootDir) {
2248
2223
  const root = (0, node_path_1.resolve)(rootDir);
2249
2224
  let latest = 0;
@@ -2313,6 +2288,9 @@ async function publishWorkerAppProject(input) {
2313
2288
  throw new Error(`agent_task_remix_ref_mismatch:${catalogueRemixRef}:${expectedRemixRef}`);
2314
2289
  }
2315
2290
  }
2291
+ if (input.kind === 'GAME_UPDATE') {
2292
+ assertTaskPlaytestEvidenceManifest(task);
2293
+ }
2316
2294
  if (input.kind === 'NEW_GAME' || input.kind === 'REMIX_GAME') {
2317
2295
  assertNewGameHeroListingArt(task);
2318
2296
  }
@@ -2355,7 +2333,7 @@ async function publishWorkerAppProject(input) {
2355
2333
  }
2356
2334
  if (input.kind === 'NEW_GAME' || input.kind === 'REMIX_GAME') {
2357
2335
  assertNewGameListingScreenshots(task);
2358
- assertNewGamePlaytestEvidenceManifest(task);
2336
+ assertTaskPlaytestEvidenceManifest(task);
2359
2337
  }
2360
2338
  const { upload: uploadResult, warnings } = await (0, apps_1.runAppPipeline)(input.client, task, {
2361
2339
  creatorUsername,
@@ -2363,10 +2341,11 @@ async function publishWorkerAppProject(input) {
2363
2341
  webBase: normalizePortalBase(input.webBase ?? undefined),
2364
2342
  token: input.token,
2365
2343
  user: input.user,
2366
- runLocalLaunchCheck: true,
2367
- runStagedUploadLaunchCheck: true,
2344
+ runLocalLoadCheck: true,
2345
+ runStagedUploadLoadCheck: true,
2368
2346
  ensureRegisteredAppShell: true,
2369
2347
  agentTaskId: input.taskId,
2348
+ mediaCaptureRequired: input.executionTarget === 'FIRST_PARTY',
2370
2349
  });
2371
2350
  if (!uploadResult.versionCreated || !uploadResult.version) {
2372
2351
  throw new Error(`App "${task.name}" upload did not return a created version.`);
@@ -2377,28 +2356,6 @@ async function publishWorkerAppProject(input) {
2377
2356
  if (typeof uploadResult.versionNodeId !== 'string' || uploadResult.versionNodeId.trim().length === 0) {
2378
2357
  throw new Error(`App "${task.name}" upload did not return versionNodeId.`);
2379
2358
  }
2380
- const finalPlayUrl = (0, appUrls_1.buildPlatformPrivateDraftPlayUrl)(input.webBase ?? null, {
2381
- creatorUsername,
2382
- appName: task.name,
2383
- appType: task.type ?? 'GAME',
2384
- version: uploadResult.version,
2385
- });
2386
- const finalPlayCheck = await (0, launchCheck_1.runPublishedHostedPlayCheck)({
2387
- targetUrl: finalPlayUrl,
2388
- surfaceTargets: task.surfaceTargets,
2389
- token: input.token,
2390
- currentUser: input.user,
2391
- });
2392
- writeFinalPublishedRouteEvidence({
2393
- task,
2394
- url: finalPlayUrl,
2395
- checkedAt: finalPlayCheck.checkedAt,
2396
- status: finalPlayCheck.status,
2397
- message: finalPlayCheck.message,
2398
- });
2399
- if (finalPlayCheck.status !== 'PASSED') {
2400
- throw new Error((0, launchCheck_1.formatHostedLaunchCheckFailure)(task.name, finalPlayCheck, 'final'));
2401
- }
2402
2359
  return {
2403
2360
  appId: app.id,
2404
2361
  appVersionId: uploadResult.versionId,
@@ -1,107 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validate = validate;
4
- const node_fs_1 = require("node:fs");
5
- const node_path_1 = require("node:path");
6
4
  const types_1 = require("@playdrop/types");
7
- const types_2 = require("@playdrop/types");
5
+ const apps_1 = require("../apps");
6
+ const assetSpecs_1 = require("../assetSpecs");
8
7
  const commandContext_1 = require("../commandContext");
8
+ const externalAssetPackValidation_1 = require("../externalAssetPackValidation");
9
9
  const taskSelection_1 = require("../taskSelection");
10
10
  const taskUtils_1 = require("../taskUtils");
11
11
  const uploadLog_1 = require("../uploadLog");
12
- const apps_1 = require("../apps");
13
- const assetSpecs_1 = require("../assetSpecs");
14
- const externalAssetPackValidation_1 = require("../externalAssetPackValidation");
15
- const dev_1 = require("./dev");
16
12
  const upload_content_1 = require("./upload-content");
17
- function normalizePositivePort(value) {
18
- const parsed = typeof value === 'number' ? value : Number.parseInt(String(value ?? ''), 10);
19
- return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : null;
20
- }
21
- function findWorkspaceMetadataFile(startPath) {
22
- let current = (0, node_path_1.resolve)(startPath);
23
- if ((0, node_fs_1.existsSync)(current) && (0, node_fs_1.statSync)(current).isFile()) {
24
- current = (0, node_path_1.dirname)(current);
25
- }
26
- while (true) {
27
- const workspacePinPath = (0, node_path_1.join)(current, '.playdrop.json');
28
- if ((0, node_fs_1.existsSync)(workspacePinPath) && (0, node_fs_1.statSync)(workspacePinPath).isFile()) {
29
- return workspacePinPath;
30
- }
31
- const taskContextPath = (0, node_path_1.join)(current, '.playdrop', 'task.json');
32
- if ((0, node_fs_1.existsSync)(taskContextPath) && (0, node_fs_1.statSync)(taskContextPath).isFile()) {
33
- return taskContextPath;
34
- }
35
- const parent = (0, node_path_1.dirname)(current);
36
- if (parent === current) {
37
- return null;
38
- }
39
- current = parent;
40
- }
41
- }
42
- function readTaskScopedDevRouterPort(workspacePath) {
43
- const metadataPath = findWorkspaceMetadataFile(workspacePath);
44
- if (!metadataPath) {
45
- throw new Error('project_validate_task_dev_port_missing');
46
- }
47
- let parsed;
48
- try {
49
- parsed = JSON.parse((0, node_fs_1.readFileSync)(metadataPath, 'utf8'));
50
- }
51
- catch {
52
- throw new Error(`project_validate_task_dev_port_invalid_json:${metadataPath}`);
53
- }
54
- const port = normalizePositivePort(parsed?.devPort);
55
- if (!port) {
56
- throw new Error(`project_validate_task_dev_port_missing:${metadataPath}`);
57
- }
58
- return { file: metadataPath, port };
59
- }
60
- function readWorkerTaskDevRouterPort() {
61
- if (process.env.PLAYDROP_WORKER_CONTEXT !== '1') {
62
- return null;
63
- }
64
- const workerPortRaw = process.env.PLAYDROP_WORKER_TASK_DEV_PORT;
65
- if (!workerPortRaw?.trim()) {
66
- throw new Error('project_validate_worker_dev_port_missing');
67
- }
68
- const workerPort = normalizePositivePort(workerPortRaw);
69
- if (!workerPort) {
70
- throw new Error(`project_validate_worker_dev_port_invalid:${workerPortRaw}`);
71
- }
72
- const devRouterPortRaw = process.env.PLAYDROP_DEV_ROUTER_PORT;
73
- if (devRouterPortRaw?.trim()) {
74
- const devRouterPort = normalizePositivePort(devRouterPortRaw);
75
- if (!devRouterPort) {
76
- throw new Error(`project_validate_env_dev_port_invalid:${devRouterPortRaw}`);
77
- }
78
- if (devRouterPort !== workerPort) {
79
- throw new Error(`project_validate_worker_dev_port_mismatch:env=${devRouterPort}:worker=${workerPort}`);
80
- }
81
- }
82
- return workerPort;
83
- }
84
- function resolveValidationDevRouterPort(workspacePath, taskScoped) {
85
- if (!taskScoped) {
86
- return (0, dev_1.resolveCliDevRouterPort)();
87
- }
88
- const workerPort = readWorkerTaskDevRouterPort();
89
- if (workerPort !== null) {
90
- return workerPort;
91
- }
92
- const taskPort = readTaskScopedDevRouterPort(workspacePath);
93
- const envPortRaw = process.env.PLAYDROP_DEV_ROUTER_PORT;
94
- if (envPortRaw !== undefined && envPortRaw.trim()) {
95
- const envPort = normalizePositivePort(envPortRaw);
96
- if (!envPort) {
97
- throw new Error(`project_validate_env_dev_port_invalid:${envPortRaw}`);
98
- }
99
- if (envPort !== taskPort.port) {
100
- throw new Error(`project_validate_worker_dev_port_mismatch:env=${envPort}:task=${taskPort.port}:${taskPort.file}`);
101
- }
102
- }
103
- return taskPort.port;
104
- }
105
13
  function buildLocalAssetSpecLookups(tasks) {
106
14
  const exactByRef = new Map();
107
15
  const uniqueByNameVersion = new Map();
@@ -127,7 +35,7 @@ function resolveLocalAssetSpecTask(task, lookups) {
127
35
  if (direct) {
128
36
  return direct;
129
37
  }
130
- const parsed = (0, types_2.parseAssetSpecVersionRef)(task.assetSpec);
38
+ const parsed = (0, types_1.parseAssetSpecVersionRef)(task.assetSpec);
131
39
  if (!parsed) {
132
40
  return null;
133
41
  }
@@ -173,42 +81,6 @@ async function loadAuthenticatedValidationContext(workspacePath, envOverride) {
173
81
  return null;
174
82
  }
175
83
  }
176
- async function loadHostedLaunchValidationContext(workspacePath, envOverride) {
177
- const ctx = await (0, commandContext_1.resolveAuthenticatedEnvironmentContext)('project validate', 'run hosted app launch validation', {
178
- env: envOverride,
179
- workspacePath,
180
- });
181
- if (!ctx) {
182
- return null;
183
- }
184
- const username = typeof ctx.account?.username === 'string' ? ctx.account.username.trim() : '';
185
- if (!username) {
186
- process.exitCode = 1;
187
- return null;
188
- }
189
- let currentUserRole = null;
190
- let currentUser = null;
191
- try {
192
- const me = await ctx.client.me();
193
- currentUserRole = me.user.role ?? null;
194
- currentUser = me.user ?? null;
195
- }
196
- catch {
197
- currentUserRole = null;
198
- currentUser = null;
199
- }
200
- return {
201
- client: ctx.client,
202
- currentUserRole,
203
- currentUser,
204
- username,
205
- apiBase: ctx.envConfig.apiBase,
206
- webBase: ctx.envConfig.webBase ?? null,
207
- token: ctx.token,
208
- devRouterPort: resolveValidationDevRouterPort(workspacePath, Boolean(ctx.workspaceAuth?.config.taskToken)),
209
- taskScoped: Boolean(ctx.workspaceAuth?.config.taskToken),
210
- };
211
- }
212
84
  function printResolvedValidationAccount(ctx) {
213
85
  const username = typeof ctx.account?.username === 'string' && ctx.account.username.trim().length > 0
214
86
  ? ctx.account.username.trim()
@@ -222,7 +94,6 @@ function formatLiveTagClearWarning(input) {
222
94
  const countLabel = input.liveTagCount === 1 ? '1 existing live tag' : `${input.liveTagCount} existing live tags`;
223
95
  return `${input.entityLabel} "${input.displayName}" would remove ${countLabel} on publish. Add a tags field or pass --clear-tags to confirm.`;
224
96
  }
225
- // eslint-disable-next-line complexity
226
97
  async function collectLiveTagClearWarnings(tasks, workspacePath, envOverride) {
227
98
  const context = await loadAuthenticatedValidationContext(workspacePath, envOverride);
228
99
  if (!context) {
@@ -316,40 +187,12 @@ async function validate(pathOrName, options = {}) {
316
187
  const assetSpecTasks = tasks.filter((task) => task.kind === 'asset-spec');
317
188
  const assetSpecLookups = buildLocalAssetSpecLookups(assetSpecTasks);
318
189
  const sortedTasks = (0, taskUtils_1.sortTasks)(tasks);
319
- const hostedLaunchValidationRequired = sortedTasks.some((task) => task.kind === 'app' && task.hostingMode !== 'EXTERNAL' && !task.externalUrl);
320
- const hostedLaunchValidationContext = hostedLaunchValidationRequired
321
- ? await loadHostedLaunchValidationContext(workspacePath, options.env)
322
- : null;
323
- if (hostedLaunchValidationRequired && !hostedLaunchValidationContext) {
324
- return;
325
- }
326
190
  for (const task of sortedTasks) {
327
191
  const entityId = task.name;
328
192
  try {
329
193
  if (task.kind === 'app') {
330
194
  await (0, apps_1.validateAppTask)(task);
331
195
  (0, apps_1.collectAppValidationWarnings)(task, 'source').forEach((warning) => warnings.add(warning));
332
- if (task.hostingMode !== 'EXTERNAL' && !task.externalUrl) {
333
- const launchContext = hostedLaunchValidationContext;
334
- const creatorResult = (0, upload_content_1.getTaskCreatorResult)(task, launchContext.username, launchContext.currentUserRole);
335
- if (creatorResult.creatorTargetError) {
336
- throw new Error(creatorResult.creatorTargetError);
337
- }
338
- const launchCheck = await (0, apps_1.runLocalHostedLaunchCheck)({
339
- client: launchContext.client,
340
- apiBase: launchContext.apiBase,
341
- webBase: launchContext.webBase,
342
- creatorUsername: creatorResult.taskCreator,
343
- task,
344
- token: launchContext.token,
345
- currentUser: launchContext.currentUser,
346
- devRouterPort: launchContext.devRouterPort,
347
- allowUnregisteredViewerLaunch: launchContext.taskScoped,
348
- });
349
- if (launchCheck.status !== 'PASSED') {
350
- throw new Error((0, apps_1.formatHostedLaunchCheckFailure)(task.name, launchCheck, 'local'));
351
- }
352
- }
353
196
  }
354
197
  else if (task.kind === 'asset-spec') {
355
198
  // Parsing already validates the contract, symbol, and documentation files.
@@ -66,6 +66,7 @@ export declare function readCodexSandboxMode(env?: NodeJS.ProcessEnv): CodexSand
66
66
  export declare function buildClaudeExecArgs(input: {
67
67
  model: string;
68
68
  effort?: string | null;
69
+ chrome?: boolean;
69
70
  workspaceDir?: string | null;
70
71
  denyReadRoots?: string[] | null;
71
72
  pluginDir?: string | null;
@@ -34,6 +34,9 @@ function tailText(value, maxChars) {
34
34
  }
35
35
  return value.slice(value.length - maxChars);
36
36
  }
37
+ function throwLoggedProcessError(error) {
38
+ throw error;
39
+ }
37
40
  function readPositiveEnvInt(name, fallback) {
38
41
  const raw = process.env[name]?.trim();
39
42
  if (!raw) {
@@ -351,7 +354,7 @@ async function runLoggedProcess(input) {
351
354
  return transcriptFlushPromise;
352
355
  };
353
356
  const handleTranscriptFlushError = (error) => {
354
- transcriptFlushError = transcriptFlushError ?? error;
357
+ transcriptFlushError = transcriptFlushError ?? (error instanceof Error ? error : new Error(String(error)));
355
358
  if (!closed) {
356
359
  child.kill('SIGTERM');
357
360
  }
@@ -403,7 +406,7 @@ async function runLoggedProcess(input) {
403
406
  void (async () => {
404
407
  await queueTranscriptFlush();
405
408
  if (transcriptFlushError) {
406
- throw transcriptFlushError;
409
+ throwLoggedProcessError(transcriptFlushError);
407
410
  }
408
411
  const outputTail = tailText(combined, input.maxOutputChars);
409
412
  const tokenUsage = extractAgentTokenUsage(`${stdout}\n${stderr}\n${outputTail}`);
@@ -458,7 +461,13 @@ function buildClaudeExecArgs(input) {
458
461
  throw new Error('agent_task_assignment_model_missing');
459
462
  }
460
463
  const permissionSettings = buildClaudePermissionSettings(input.denyReadRoots ?? []);
461
- const tools = 'Bash,Edit,Write,Read,Glob,Grep';
464
+ const permissionMode = input.chrome ? 'bypassPermissions' : 'acceptEdits';
465
+ const tools = input.chrome
466
+ ? [...CLAUDE_BASE_TOOL_NAMES, ...CLAUDE_CHROME_TOOL_NAMES].join(',')
467
+ : CLAUDE_BASE_TOOL_NAMES.join(',');
468
+ const allowedTools = input.chrome
469
+ ? [...CLAUDE_ALLOWED_PERMISSION_RULES, ...CLAUDE_CHROME_TOOL_NAMES]
470
+ : CLAUDE_ALLOWED_PERMISSION_RULES;
462
471
  const args = [
463
472
  '-p',
464
473
  '--model',
@@ -469,16 +478,19 @@ function buildClaudeExecArgs(input) {
469
478
  '--include-partial-messages',
470
479
  '--no-session-persistence',
471
480
  '--permission-mode',
472
- 'acceptEdits',
481
+ permissionMode,
473
482
  '--tools',
474
483
  tools,
475
484
  '--allowedTools',
476
- ...CLAUDE_ALLOWED_PERMISSION_RULES,
485
+ ...allowedTools,
477
486
  '--disallowedTools',
478
487
  ...buildClaudeDeniedPermissionRules(input.denyReadRoots ?? []),
479
488
  '--settings',
480
489
  JSON.stringify(permissionSettings),
481
490
  ];
491
+ if (input.chrome) {
492
+ args.unshift('--chrome');
493
+ }
482
494
  const pluginDir = typeof input.pluginDir === 'string' ? input.pluginDir.trim() : '';
483
495
  if (pluginDir) {
484
496
  args.push('--plugin-dir', pluginDir);
@@ -489,6 +501,37 @@ function buildClaudeExecArgs(input) {
489
501
  }
490
502
  return args;
491
503
  }
504
+ const CLAUDE_BASE_TOOL_NAMES = [
505
+ 'Bash',
506
+ 'Edit',
507
+ 'Write',
508
+ 'Read',
509
+ 'Glob',
510
+ 'Grep',
511
+ ];
512
+ const CLAUDE_CHROME_TOOL_NAMES = [
513
+ 'mcp__claude-in-chrome__browser_batch',
514
+ 'mcp__claude-in-chrome__computer',
515
+ 'mcp__claude-in-chrome__file_upload',
516
+ 'mcp__claude-in-chrome__find',
517
+ 'mcp__claude-in-chrome__form_input',
518
+ 'mcp__claude-in-chrome__get_page_text',
519
+ 'mcp__claude-in-chrome__javascript_tool',
520
+ 'mcp__claude-in-chrome__list_connected_browsers',
521
+ 'mcp__claude-in-chrome__navigate',
522
+ 'mcp__claude-in-chrome__read_console_messages',
523
+ 'mcp__claude-in-chrome__read_network_requests',
524
+ 'mcp__claude-in-chrome__read_page',
525
+ 'mcp__claude-in-chrome__resize_window',
526
+ 'mcp__claude-in-chrome__select_browser',
527
+ 'mcp__claude-in-chrome__shortcuts_execute',
528
+ 'mcp__claude-in-chrome__shortcuts_list',
529
+ 'mcp__claude-in-chrome__switch_browser',
530
+ 'mcp__claude-in-chrome__tabs_close_mcp',
531
+ 'mcp__claude-in-chrome__tabs_context_mcp',
532
+ 'mcp__claude-in-chrome__tabs_create_mcp',
533
+ 'mcp__claude-in-chrome__upload_image',
534
+ ];
492
535
  const CLAUDE_ALLOWED_PERMISSION_RULES = [
493
536
  'Read',
494
537
  'Edit',
@@ -1,11 +1,11 @@
1
1
  import type { ApiClient } from '@playdrop/api-client';
2
- import { type AgentTaskNextStepSuggestion, type AgentExecutionTarget, type AgentRuntime, type AgentTaskKind, type AgentTaskResponse, type AgentWorkerCapabilities, type WorkerAgentTaskAssignmentResponse, type WorkerAgentTaskBaseSourceResponse, type WorkerAgentTaskWorkspaceFileResponse, type WorkerClaimAgentTaskResponse } from '@playdrop/types';
2
+ import { type AgentExecutionTarget, type AgentRuntime, type AgentTaskKind, type AgentTaskNextStepSuggestion, type AgentTaskResponse, type AgentWorkerCapabilities, type AgentWorkerResponse, type WorkerAgentTaskAssignmentResponse, type WorkerAgentTaskBaseSourceResponse, type WorkerAgentTaskWorkspaceFileResponse, type WorkerClaimAgentTaskResponse } from '@playdrop/types';
3
3
  import { type WorkerPlaydropAssetRequirement } from './upload';
4
4
  import { type LoggedProcessResult, type LoggedProcessTranscriptChunk } from './worker/runtime';
5
- export { DEFAULT_CODEX_TIMEOUT_MS, DEFAULT_WORKER_TOKEN_CAP, assertWorkerTokenUsageWithinCap, buildCodexExecArgs, buildClaudeExecArgs, buildClaudePermissionSettings, buildWorkerChildEnv, extractAgentTokenUsage, extractCodexRolloutTokenUsage, extractCodexThreadId, extractCodexTokensUsed, readEnvBoolean, readCodexSandboxMode, readCodexRolloutTokenUsageForThread, runLoggedProcess, } from './worker/runtime';
6
- export type { AgentTokenUsage, LoggedProcessResult, LoggedProcessTranscriptChunk, } from './worker/runtime';
5
+ export { assertWorkerTokenUsageWithinCap, buildClaudeExecArgs, buildClaudePermissionSettings, buildCodexExecArgs, buildWorkerChildEnv, DEFAULT_CODEX_TIMEOUT_MS, DEFAULT_WORKER_TOKEN_CAP, extractAgentTokenUsage, extractCodexRolloutTokenUsage, extractCodexThreadId, extractCodexTokensUsed, readCodexRolloutTokenUsageForThread, readCodexSandboxMode, readEnvBoolean, runLoggedProcess } from './worker/runtime';
6
+ export type { AgentTokenUsage, LoggedProcessResult, LoggedProcessTranscriptChunk } from './worker/runtime';
7
7
  export declare const WORKER_SESSION_EXPIRED_MESSAGE = "worker session expired: run \"playdrop auth login\" and start the worker again";
8
- export declare const WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = "worker_context_command_not_allowed: inside a PlayDrop worker task only task progress, task claim-slug, task upload/done/fail, assigned app scaffolding, project validation/build/dev/capture, read-only help/catalogue/documentation lookup, and PlayDrop AI generation are permitted.";
8
+ export declare const WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = "worker_context_command_not_allowed: inside a PlayDrop worker task only task progress, task claim-slug, task upload/done/fail, assigned app scaffolding, project validation/build/dev/check/capture, read-only help/catalogue/documentation lookup, and PlayDrop AI generation are permitted.";
9
9
  type WorkerStartOptions = {
10
10
  env?: string;
11
11
  once?: boolean;
@@ -56,7 +56,7 @@ type WorkerStatusPayload = {
56
56
  server: {
57
57
  reachable: boolean;
58
58
  authenticated: boolean;
59
- worker: import('@playdrop/types').AgentWorkerResponse | null;
59
+ worker: AgentWorkerResponse | null;
60
60
  error?: string;
61
61
  };
62
62
  };
@@ -190,7 +190,6 @@ export declare function buildWorkerCapabilities(input: string | {
190
190
  } | null;
191
191
  maxParallelTasks?: number | null;
192
192
  runningTaskCount?: number | null;
193
- providerAccountLabel?: string | null;
194
193
  }): AgentWorkerCapabilities;
195
194
  export declare function buildWorkerClaimBody(input: {
196
195
  workerKey: string;