@playdrop/playdrop-cli 0.11.9 → 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.
- package/config/client-meta.json +2 -2
- package/dist/apps/build.js +2 -3
- package/dist/apps/index.d.ts +4 -5
- package/dist/apps/index.js +14 -16
- package/dist/apps/loadCheck.d.ts +76 -0
- package/dist/apps/loadCheck.js +874 -0
- package/dist/apps/upload.d.ts +3 -2
- package/dist/apps/upload.js +110 -6
- package/dist/catalogue-utils.d.ts +1 -0
- package/dist/catalogue-utils.js +1 -0
- package/dist/catalogue.d.ts +2 -0
- package/dist/catalogue.js +17 -0
- package/dist/commands/build.js +4 -1
- package/dist/commands/captureListing.d.ts +30 -1
- package/dist/commands/captureListing.js +305 -120
- package/dist/commands/check.d.ts +8 -0
- package/dist/commands/check.js +188 -0
- package/dist/commands/review.d.ts +20 -0
- package/dist/commands/review.js +112 -0
- package/dist/commands/upload-content.d.ts +1 -1
- package/dist/commands/upload.d.ts +2 -1
- package/dist/commands/upload.js +9 -52
- package/dist/commands/validate.js +0 -155
- package/dist/commands/worker/runtime.d.ts +1 -0
- package/dist/commands/worker/runtime.js +43 -3
- package/dist/commands/worker.d.ts +1 -1
- package/dist/commands/worker.js +22 -44
- package/dist/index.js +33 -49
- package/dist/playwright.d.ts +5 -0
- package/dist/playwright.js +60 -1
- package/node_modules/@playdrop/api-client/dist/client.d.ts +4 -2
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/client.js +0 -10
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +2 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/apps.js +11 -0
- package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts +3 -1
- package/node_modules/@playdrop/api-client/dist/domains/auth.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/auth.js +22 -0
- package/node_modules/@playdrop/api-client/dist/index.d.ts +4 -3
- package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/index.js +10 -5
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/config/dist/src/client-meta.d.ts +8 -0
- package/node_modules/@playdrop/config/dist/src/client-meta.d.ts.map +1 -0
- package/node_modules/@playdrop/config/dist/src/client-meta.js +51 -0
- package/node_modules/@playdrop/config/dist/src/deployment.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/deployment.js +2 -32
- package/node_modules/@playdrop/config/dist/src/index.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/index.js +2 -28
- package/node_modules/@playdrop/config/dist/src/runtime-service-origins.d.ts +0 -1
- package/node_modules/@playdrop/config/dist/src/runtime-service-origins.d.ts.map +1 -1
- package/node_modules/@playdrop/config/dist/src/runtime-service-origins.js +21 -5
- package/node_modules/@playdrop/config/dist/test/runtime-service-origins.test.js +8 -0
- package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +19 -14
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +5 -10
- package/node_modules/@playdrop/types/dist/version.d.ts +14 -0
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/apps/launchCheck.d.ts +0 -39
- package/dist/apps/launchCheck.js +0 -414
- package/dist/captureRuntime.d.ts +0 -73
- package/dist/captureRuntime.js +0 -1021
- package/dist/commands/capture.d.ts +0 -14
- package/dist/commands/capture.js +0 -548
- package/dist/commands/captureRemote.d.ts +0 -17
- package/dist/commands/captureRemote.js +0 -394
- package/node_modules/@playdrop/types/dist/entity.d.ts +0 -31
- package/node_modules/@playdrop/types/dist/entity.d.ts.map +0 -1
- package/node_modules/@playdrop/types/dist/entity.js +0 -67
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validate = validate;
|
|
4
4
|
const types_1 = require("@playdrop/types");
|
|
5
|
-
const node_fs_1 = require("node:fs");
|
|
6
|
-
const node_path_1 = require("node:path");
|
|
7
5
|
const apps_1 = require("../apps");
|
|
8
6
|
const assetSpecs_1 = require("../assetSpecs");
|
|
9
7
|
const commandContext_1 = require("../commandContext");
|
|
@@ -11,96 +9,7 @@ const externalAssetPackValidation_1 = require("../externalAssetPackValidation");
|
|
|
11
9
|
const taskSelection_1 = require("../taskSelection");
|
|
12
10
|
const taskUtils_1 = require("../taskUtils");
|
|
13
11
|
const uploadLog_1 = require("../uploadLog");
|
|
14
|
-
const dev_1 = require("./dev");
|
|
15
12
|
const upload_content_1 = require("./upload-content");
|
|
16
|
-
function normalizePositivePort(value) {
|
|
17
|
-
const parsed = typeof value === 'number' ? value : Number.parseInt(String(value ?? ''), 10);
|
|
18
|
-
return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : null;
|
|
19
|
-
}
|
|
20
|
-
function findWorkspaceMetadataFile(startPath) {
|
|
21
|
-
let current = (0, node_path_1.resolve)(startPath);
|
|
22
|
-
if ((0, node_fs_1.existsSync)(current) && (0, node_fs_1.statSync)(current).isFile()) {
|
|
23
|
-
current = (0, node_path_1.dirname)(current);
|
|
24
|
-
}
|
|
25
|
-
while (true) {
|
|
26
|
-
const workspacePinPath = (0, node_path_1.join)(current, '.playdrop.json');
|
|
27
|
-
if ((0, node_fs_1.existsSync)(workspacePinPath) && (0, node_fs_1.statSync)(workspacePinPath).isFile()) {
|
|
28
|
-
return workspacePinPath;
|
|
29
|
-
}
|
|
30
|
-
const taskContextPath = (0, node_path_1.join)(current, '.playdrop', 'task.json');
|
|
31
|
-
if ((0, node_fs_1.existsSync)(taskContextPath) && (0, node_fs_1.statSync)(taskContextPath).isFile()) {
|
|
32
|
-
return taskContextPath;
|
|
33
|
-
}
|
|
34
|
-
const parent = (0, node_path_1.dirname)(current);
|
|
35
|
-
if (parent === current) {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
current = parent;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function readTaskScopedDevRouterPort(workspacePath) {
|
|
42
|
-
const metadataPath = findWorkspaceMetadataFile(workspacePath);
|
|
43
|
-
if (!metadataPath) {
|
|
44
|
-
throw new Error('project_validate_task_dev_port_missing');
|
|
45
|
-
}
|
|
46
|
-
let parsed;
|
|
47
|
-
try {
|
|
48
|
-
parsed = JSON.parse((0, node_fs_1.readFileSync)(metadataPath, 'utf8'));
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
throw new Error(`project_validate_task_dev_port_invalid_json:${metadataPath}`);
|
|
52
|
-
}
|
|
53
|
-
const port = normalizePositivePort(parsed?.devPort);
|
|
54
|
-
if (!port) {
|
|
55
|
-
throw new Error(`project_validate_task_dev_port_missing:${metadataPath}`);
|
|
56
|
-
}
|
|
57
|
-
return { file: metadataPath, port };
|
|
58
|
-
}
|
|
59
|
-
function readWorkerTaskDevRouterPort() {
|
|
60
|
-
if (process.env.PLAYDROP_WORKER_CONTEXT !== '1') {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
const workerPortRaw = process.env.PLAYDROP_WORKER_TASK_DEV_PORT;
|
|
64
|
-
if (!workerPortRaw?.trim()) {
|
|
65
|
-
throw new Error('project_validate_worker_dev_port_missing');
|
|
66
|
-
}
|
|
67
|
-
const workerPort = normalizePositivePort(workerPortRaw);
|
|
68
|
-
if (!workerPort) {
|
|
69
|
-
throw new Error(`project_validate_worker_dev_port_invalid:${workerPortRaw}`);
|
|
70
|
-
}
|
|
71
|
-
const devRouterPortRaw = process.env.PLAYDROP_DEV_ROUTER_PORT;
|
|
72
|
-
if (devRouterPortRaw?.trim()) {
|
|
73
|
-
const devRouterPort = normalizePositivePort(devRouterPortRaw);
|
|
74
|
-
if (!devRouterPort) {
|
|
75
|
-
throw new Error(`project_validate_env_dev_port_invalid:${devRouterPortRaw}`);
|
|
76
|
-
}
|
|
77
|
-
if (devRouterPort !== workerPort) {
|
|
78
|
-
throw new Error(`project_validate_worker_dev_port_mismatch:env=${devRouterPort}:worker=${workerPort}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return workerPort;
|
|
82
|
-
}
|
|
83
|
-
function resolveValidationDevRouterPort(workspacePath, taskScoped) {
|
|
84
|
-
if (!taskScoped) {
|
|
85
|
-
return (0, dev_1.resolveCliDevRouterPort)();
|
|
86
|
-
}
|
|
87
|
-
const workerPort = readWorkerTaskDevRouterPort();
|
|
88
|
-
if (workerPort !== null) {
|
|
89
|
-
return workerPort;
|
|
90
|
-
}
|
|
91
|
-
const taskPort = readTaskScopedDevRouterPort(workspacePath);
|
|
92
|
-
const envPortRaw = process.env.PLAYDROP_DEV_ROUTER_PORT;
|
|
93
|
-
if (envPortRaw !== undefined && envPortRaw.trim()) {
|
|
94
|
-
const envPort = normalizePositivePort(envPortRaw);
|
|
95
|
-
if (!envPort) {
|
|
96
|
-
throw new Error(`project_validate_env_dev_port_invalid:${envPortRaw}`);
|
|
97
|
-
}
|
|
98
|
-
if (envPort !== taskPort.port) {
|
|
99
|
-
throw new Error(`project_validate_worker_dev_port_mismatch:env=${envPort}:task=${taskPort.port}:${taskPort.file}`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return taskPort.port;
|
|
103
|
-
}
|
|
104
13
|
function buildLocalAssetSpecLookups(tasks) {
|
|
105
14
|
const exactByRef = new Map();
|
|
106
15
|
const uniqueByNameVersion = new Map();
|
|
@@ -172,42 +81,6 @@ async function loadAuthenticatedValidationContext(workspacePath, envOverride) {
|
|
|
172
81
|
return null;
|
|
173
82
|
}
|
|
174
83
|
}
|
|
175
|
-
async function loadHostedLaunchValidationContext(workspacePath, envOverride) {
|
|
176
|
-
const ctx = await (0, commandContext_1.resolveAuthenticatedEnvironmentContext)('project validate', 'run hosted app launch validation', {
|
|
177
|
-
env: envOverride,
|
|
178
|
-
workspacePath,
|
|
179
|
-
});
|
|
180
|
-
if (!ctx) {
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
const username = typeof ctx.account?.username === 'string' ? ctx.account.username.trim() : '';
|
|
184
|
-
if (!username) {
|
|
185
|
-
process.exitCode = 1;
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
let currentUserRole = null;
|
|
189
|
-
let currentUser = null;
|
|
190
|
-
try {
|
|
191
|
-
const me = await ctx.client.me();
|
|
192
|
-
currentUserRole = me.user.role ?? null;
|
|
193
|
-
currentUser = me.user ?? null;
|
|
194
|
-
}
|
|
195
|
-
catch {
|
|
196
|
-
currentUserRole = null;
|
|
197
|
-
currentUser = null;
|
|
198
|
-
}
|
|
199
|
-
return {
|
|
200
|
-
client: ctx.client,
|
|
201
|
-
currentUserRole,
|
|
202
|
-
currentUser,
|
|
203
|
-
username,
|
|
204
|
-
apiBase: ctx.envConfig.apiBase,
|
|
205
|
-
webBase: ctx.envConfig.webBase ?? null,
|
|
206
|
-
token: ctx.token,
|
|
207
|
-
devRouterPort: resolveValidationDevRouterPort(workspacePath, Boolean(ctx.workspaceAuth?.config.taskToken)),
|
|
208
|
-
taskScoped: Boolean(ctx.workspaceAuth?.config.taskToken),
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
84
|
function printResolvedValidationAccount(ctx) {
|
|
212
85
|
const username = typeof ctx.account?.username === 'string' && ctx.account.username.trim().length > 0
|
|
213
86
|
? ctx.account.username.trim()
|
|
@@ -314,40 +187,12 @@ async function validate(pathOrName, options = {}) {
|
|
|
314
187
|
const assetSpecTasks = tasks.filter((task) => task.kind === 'asset-spec');
|
|
315
188
|
const assetSpecLookups = buildLocalAssetSpecLookups(assetSpecTasks);
|
|
316
189
|
const sortedTasks = (0, taskUtils_1.sortTasks)(tasks);
|
|
317
|
-
const hostedLaunchValidationRequired = sortedTasks.some((task) => task.kind === 'app' && task.hostingMode !== 'EXTERNAL' && !task.externalUrl);
|
|
318
|
-
const hostedLaunchValidationContext = hostedLaunchValidationRequired
|
|
319
|
-
? await loadHostedLaunchValidationContext(workspacePath, options.env)
|
|
320
|
-
: null;
|
|
321
|
-
if (hostedLaunchValidationRequired && !hostedLaunchValidationContext) {
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
190
|
for (const task of sortedTasks) {
|
|
325
191
|
const entityId = task.name;
|
|
326
192
|
try {
|
|
327
193
|
if (task.kind === 'app') {
|
|
328
194
|
await (0, apps_1.validateAppTask)(task);
|
|
329
195
|
(0, apps_1.collectAppValidationWarnings)(task, 'source').forEach((warning) => warnings.add(warning));
|
|
330
|
-
if (task.hostingMode !== 'EXTERNAL' && !task.externalUrl) {
|
|
331
|
-
const launchContext = hostedLaunchValidationContext;
|
|
332
|
-
const creatorResult = (0, upload_content_1.getTaskCreatorResult)(task, launchContext.username, launchContext.currentUserRole);
|
|
333
|
-
if (creatorResult.creatorTargetError) {
|
|
334
|
-
throw new Error(creatorResult.creatorTargetError);
|
|
335
|
-
}
|
|
336
|
-
const launchCheck = await (0, apps_1.runLocalHostedLaunchCheck)({
|
|
337
|
-
client: launchContext.client,
|
|
338
|
-
apiBase: launchContext.apiBase,
|
|
339
|
-
webBase: launchContext.webBase,
|
|
340
|
-
creatorUsername: creatorResult.taskCreator,
|
|
341
|
-
task,
|
|
342
|
-
token: launchContext.token,
|
|
343
|
-
currentUser: launchContext.currentUser,
|
|
344
|
-
devRouterPort: launchContext.devRouterPort,
|
|
345
|
-
allowUnregisteredViewerLaunch: launchContext.taskScoped,
|
|
346
|
-
});
|
|
347
|
-
if (launchCheck.status !== 'PASSED') {
|
|
348
|
-
throw new Error((0, apps_1.formatHostedLaunchCheckFailure)(task.name, launchCheck, 'local'));
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
196
|
}
|
|
352
197
|
else if (task.kind === 'asset-spec') {
|
|
353
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;
|
|
@@ -461,7 +461,13 @@ function buildClaudeExecArgs(input) {
|
|
|
461
461
|
throw new Error('agent_task_assignment_model_missing');
|
|
462
462
|
}
|
|
463
463
|
const permissionSettings = buildClaudePermissionSettings(input.denyReadRoots ?? []);
|
|
464
|
-
const
|
|
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;
|
|
465
471
|
const args = [
|
|
466
472
|
'-p',
|
|
467
473
|
'--model',
|
|
@@ -472,16 +478,19 @@ function buildClaudeExecArgs(input) {
|
|
|
472
478
|
'--include-partial-messages',
|
|
473
479
|
'--no-session-persistence',
|
|
474
480
|
'--permission-mode',
|
|
475
|
-
|
|
481
|
+
permissionMode,
|
|
476
482
|
'--tools',
|
|
477
483
|
tools,
|
|
478
484
|
'--allowedTools',
|
|
479
|
-
...
|
|
485
|
+
...allowedTools,
|
|
480
486
|
'--disallowedTools',
|
|
481
487
|
...buildClaudeDeniedPermissionRules(input.denyReadRoots ?? []),
|
|
482
488
|
'--settings',
|
|
483
489
|
JSON.stringify(permissionSettings),
|
|
484
490
|
];
|
|
491
|
+
if (input.chrome) {
|
|
492
|
+
args.unshift('--chrome');
|
|
493
|
+
}
|
|
485
494
|
const pluginDir = typeof input.pluginDir === 'string' ? input.pluginDir.trim() : '';
|
|
486
495
|
if (pluginDir) {
|
|
487
496
|
args.push('--plugin-dir', pluginDir);
|
|
@@ -492,6 +501,37 @@ function buildClaudeExecArgs(input) {
|
|
|
492
501
|
}
|
|
493
502
|
return args;
|
|
494
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
|
+
];
|
|
495
535
|
const CLAUDE_ALLOWED_PERMISSION_RULES = [
|
|
496
536
|
'Read',
|
|
497
537
|
'Edit',
|
|
@@ -5,7 +5,7 @@ import { type LoggedProcessResult, type LoggedProcessTranscriptChunk } from './w
|
|
|
5
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
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;
|
package/dist/commands/worker.js
CHANGED
|
@@ -120,11 +120,14 @@ const WORKER_CONTEXT_ALLOWED_COMMANDS = [
|
|
|
120
120
|
['task', 'submit-eval'],
|
|
121
121
|
['task', 'fail'],
|
|
122
122
|
['review', 'validate-result'],
|
|
123
|
+
['review', 'list-windows'],
|
|
124
|
+
['review', 'screenshot'],
|
|
123
125
|
['review', 'compose-evidence'],
|
|
124
126
|
['review', 'rating-card'],
|
|
125
127
|
['project', 'validate'],
|
|
126
128
|
['project', 'build'],
|
|
127
129
|
['project', 'dev'],
|
|
130
|
+
['project', 'check'],
|
|
128
131
|
['project', 'capture'],
|
|
129
132
|
['browse'],
|
|
130
133
|
['search'],
|
|
@@ -138,7 +141,7 @@ const WORKER_CONTEXT_ALLOWED_COMMANDS = [
|
|
|
138
141
|
['ai', 'jobs', 'detail'],
|
|
139
142
|
['ai', 'generations'],
|
|
140
143
|
];
|
|
141
|
-
exports.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.';
|
|
144
|
+
exports.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.';
|
|
142
145
|
function allocateWorkerDevPort(input) {
|
|
143
146
|
const basePort = input.basePort ?? DEFAULT_WORKER_DEV_PORT_BASE;
|
|
144
147
|
const maxParallelTasks = input.maxParallelTasks ?? DEFAULT_WORKER_MAX_PARALLEL_TASKS;
|
|
@@ -727,15 +730,6 @@ async function readTaskNextStepsFileForCommand(input) {
|
|
|
727
730
|
return NEXT_STEPS_READ_FAILED;
|
|
728
731
|
}
|
|
729
732
|
}
|
|
730
|
-
function isFinalOwnerRouteLaunchCheckFailure(error) {
|
|
731
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
732
|
-
return message.startsWith('hosted_launch_check_failed:')
|
|
733
|
-
&& message.includes('final owner play route');
|
|
734
|
-
}
|
|
735
|
-
function formatTerminalFinalOwnerRouteFailure(error) {
|
|
736
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
737
|
-
return `agent_task_final_owner_route_failed: final owner play route validation failed after the task version was created. Task uploads have a fixed outputVersion and cannot be retried by bumping catalogue.json. ${message}`;
|
|
738
|
-
}
|
|
739
733
|
function assertTaskUploadResultMatchesContext(input) {
|
|
740
734
|
if (input.uploadResult.taskId !== input.taskContext.taskId) {
|
|
741
735
|
throw new Error('task_upload_result_task_mismatch');
|
|
@@ -1851,6 +1845,7 @@ async function runClaude(input) {
|
|
|
1851
1845
|
workspaceDir: input.workspaceDir,
|
|
1852
1846
|
denyReadRoots,
|
|
1853
1847
|
pluginDir: input.playdropPluginRoot,
|
|
1848
|
+
chrome: input.enableChrome,
|
|
1854
1849
|
}),
|
|
1855
1850
|
cwd: input.workspaceDir,
|
|
1856
1851
|
env: (0, runtime_1.buildWorkerChildEnv)({
|
|
@@ -3529,6 +3524,7 @@ async function startWorker(options = {}) {
|
|
|
3529
3524
|
binDir,
|
|
3530
3525
|
eventDir,
|
|
3531
3526
|
prompt,
|
|
3527
|
+
enableChrome: task.kind === 'GAME_REVIEW' || task.kind === 'GAME_EVAL',
|
|
3532
3528
|
envName: env,
|
|
3533
3529
|
taskId: task.id,
|
|
3534
3530
|
attempt: taskContext.attempt,
|
|
@@ -3966,40 +3962,22 @@ async function uploadTask(options = {}) {
|
|
|
3966
3962
|
return;
|
|
3967
3963
|
}
|
|
3968
3964
|
const projectDir = discoverWorkerProjectRoot(node_process_1.default.cwd());
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
});
|
|
3986
|
-
}
|
|
3987
|
-
catch (error) {
|
|
3988
|
-
if (isFinalOwnerRouteLaunchCheckFailure(error)) {
|
|
3989
|
-
const terminalError = formatTerminalFinalOwnerRouteFailure(error);
|
|
3990
|
-
await ctx.client.workerFailAgentTask(taskContext.taskId, {
|
|
3991
|
-
taskToken: taskContext.taskToken,
|
|
3992
|
-
error: terminalError,
|
|
3993
|
-
result: {
|
|
3994
|
-
failedBy: 'worker_upload_final_owner_route',
|
|
3995
|
-
finalOwnerRouteFailure: true,
|
|
3996
|
-
originalError: error instanceof Error ? error.message : String(error),
|
|
3997
|
-
},
|
|
3998
|
-
});
|
|
3999
|
-
throw new Error(terminalError);
|
|
4000
|
-
}
|
|
4001
|
-
throw error;
|
|
4002
|
-
}
|
|
3965
|
+
const published = await (0, upload_1.publishWorkerAppProject)({
|
|
3966
|
+
client: ctx.client,
|
|
3967
|
+
taskId: taskContext.taskId,
|
|
3968
|
+
kind: taskContext.kind,
|
|
3969
|
+
executionTarget: taskContext.target,
|
|
3970
|
+
expectedAppName: taskContext.outputAppName ?? undefined,
|
|
3971
|
+
remixSourceRef: taskContext.remixSourceRef ?? null,
|
|
3972
|
+
playdropAssetRequirement: resolvePlaydropAssetRequirementFromTaskRequest(taskContext.creatorRequest),
|
|
3973
|
+
creatorRequest: taskContext.creatorRequest,
|
|
3974
|
+
projectDir,
|
|
3975
|
+
creatorUsername: taskContext.creatorUsername,
|
|
3976
|
+
apiBase: ctx.envConfig.apiBase,
|
|
3977
|
+
webBase: ctx.envConfig.webBase ?? null,
|
|
3978
|
+
token: ctx.token,
|
|
3979
|
+
user: ctx.user,
|
|
3980
|
+
});
|
|
4003
3981
|
const result = {
|
|
4004
3982
|
taskId: taskContext.taskId,
|
|
4005
3983
|
appId: published.appId,
|
package/dist/index.js
CHANGED
|
@@ -30,10 +30,9 @@ const create_1 = require("./commands/create");
|
|
|
30
30
|
const createRemixContent_1 = require("./commands/createRemixContent");
|
|
31
31
|
const dev_1 = require("./commands/dev");
|
|
32
32
|
const devBrowser_1 = require("./commands/devBrowser");
|
|
33
|
-
const
|
|
33
|
+
const check_1 = require("./commands/check");
|
|
34
34
|
const devServer_1 = require("./commands/devServer");
|
|
35
35
|
const captureListing_1 = require("./commands/captureListing");
|
|
36
|
-
const captureRemote_1 = require("./commands/captureRemote");
|
|
37
36
|
const marketing_1 = require("./commands/marketing");
|
|
38
37
|
const review_1 = require("./commands/review");
|
|
39
38
|
const validate_1 = require("./commands/validate");
|
|
@@ -290,6 +289,23 @@ task
|
|
|
290
289
|
await (0, worker_1.failTask)(opts);
|
|
291
290
|
});
|
|
292
291
|
const review = program.command('review').description('Review helper commands for PlayDrop worker tasks');
|
|
292
|
+
review
|
|
293
|
+
.command('list-windows')
|
|
294
|
+
.description('List native capture windows for review evidence')
|
|
295
|
+
.requiredOption('--pid <pid>', 'Browser process id')
|
|
296
|
+
.action(async (opts) => {
|
|
297
|
+
await (0, review_1.listReviewCaptureWindows)(opts);
|
|
298
|
+
});
|
|
299
|
+
review
|
|
300
|
+
.command('screenshot')
|
|
301
|
+
.description('Capture a native screenshot for review evidence')
|
|
302
|
+
.requiredOption('--pid <pid>', 'Browser process id')
|
|
303
|
+
.option('--window-id <id>', 'Native capture window id')
|
|
304
|
+
.requiredOption('--out <path>', 'Output PNG path')
|
|
305
|
+
.option('--metadata <path>', 'Output metadata JSON path')
|
|
306
|
+
.action(async (opts) => {
|
|
307
|
+
await (0, review_1.captureReviewScreenshot)(opts);
|
|
308
|
+
});
|
|
293
309
|
review
|
|
294
310
|
.command('validate-result')
|
|
295
311
|
.description('Validate a canonical game review result')
|
|
@@ -986,37 +1002,24 @@ project
|
|
|
986
1002
|
player: opts.player,
|
|
987
1003
|
});
|
|
988
1004
|
});
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
.
|
|
1005
|
+
project
|
|
1006
|
+
.command('check [target]')
|
|
1007
|
+
.description('Run the deterministic hosted game load-check in installed Chrome')
|
|
992
1008
|
.option('--app <name>', 'App name when the target is a workspace')
|
|
993
|
-
.option('--timeout <seconds>', '
|
|
994
|
-
.option('--log-level <level>', 'debug, info, warn, or error')
|
|
1009
|
+
.option('--timeout <seconds>', 'Load-check timeout in seconds')
|
|
995
1010
|
.option('--screenshot <path>', 'Screenshot output path')
|
|
996
|
-
.option('--
|
|
997
|
-
.
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
const timeoutSeconds = opts.timeout !== undefined ? Number.parseFloat(String(opts.timeout)) : undefined;
|
|
1004
|
-
await (0, capture_1.capture)(target, {
|
|
1005
|
-
timeoutSeconds,
|
|
1006
|
-
appName: opts.app,
|
|
1007
|
-
logLevel: opts.logLevel,
|
|
1008
|
-
screenshotPath: opts.screenshot,
|
|
1009
|
-
surfaceTarget: opts.surface,
|
|
1010
|
-
devAuth: opts.devAuth,
|
|
1011
|
-
player: opts.player,
|
|
1012
|
-
resetDevPlayer: opts.resetDevPlayer,
|
|
1013
|
-
username: opts.username,
|
|
1014
|
-
password: opts.password,
|
|
1011
|
+
.option('--actions <path>', 'JSON action script to dispatch after the game frame is ready')
|
|
1012
|
+
.action(async (target, opts = {}) => {
|
|
1013
|
+
await (0, check_1.check)(target, {
|
|
1014
|
+
app: opts.app,
|
|
1015
|
+
timeout: opts.timeout,
|
|
1016
|
+
screenshot: opts.screenshot,
|
|
1017
|
+
actions: opts.actions,
|
|
1015
1018
|
});
|
|
1016
1019
|
});
|
|
1020
|
+
const projectCapture = project.command('capture').description('Record game media with the native macOS recorder');
|
|
1017
1021
|
projectCapture
|
|
1018
|
-
.
|
|
1019
|
-
.description('Record private listing video for a hosted app')
|
|
1022
|
+
.argument('[target]')
|
|
1020
1023
|
.option('--app <name>', 'App name when the target is a workspace')
|
|
1021
1024
|
.option('--duration <seconds>', 'Recording duration in seconds')
|
|
1022
1025
|
.option('--width <pixels>', 'Final gameplay width in pixels')
|
|
@@ -1026,6 +1029,7 @@ projectCapture
|
|
|
1026
1029
|
.option('--audio', 'Capture gameplay audio')
|
|
1027
1030
|
.option('--output-dir <path>', 'Output directory for the capture artifacts')
|
|
1028
1031
|
.option('--keep-raw', 'Keep the raw intermediate recording')
|
|
1032
|
+
.option('--content-hash <sha256>', 'Uploaded build content hash to bind this capture to')
|
|
1029
1033
|
.action(async (target, opts) => {
|
|
1030
1034
|
await (0, captureListing_1.captureListing)(target, {
|
|
1031
1035
|
app: opts.app,
|
|
@@ -1037,27 +1041,7 @@ projectCapture
|
|
|
1037
1041
|
audio: opts.audio,
|
|
1038
1042
|
outputDir: opts.outputDir,
|
|
1039
1043
|
keepRaw: opts.keepRaw,
|
|
1040
|
-
|
|
1041
|
-
});
|
|
1042
|
-
projectCapture
|
|
1043
|
-
.command('remote <url>')
|
|
1044
|
-
.description('Run a Playwright capture against a published URL')
|
|
1045
|
-
.option('--timeout <seconds>', 'Readiness timeout in seconds')
|
|
1046
|
-
.option('--settle <seconds>', 'Post-ready settle delay before screenshot in seconds')
|
|
1047
|
-
.option('--screenshot <path>', 'Screenshot output path')
|
|
1048
|
-
.option('--log <path>', 'Log output path')
|
|
1049
|
-
.option('--expected-url <url>', 'Expected final URL after redirects')
|
|
1050
|
-
.option('--actions <path>', 'JSON action file with click, press, and wait steps before screenshot')
|
|
1051
|
-
.option('--viewport <size>', 'Viewport size as WIDTHxHEIGHT')
|
|
1052
|
-
.option('--surface <surface>', 'Browser surface preset: desktop, mobile-landscape, or mobile-portrait')
|
|
1053
|
-
.option('--fps-sample-ms <milliseconds>', 'Sample frame rate for this many milliseconds after capture')
|
|
1054
|
-
.option('--username <username>', 'Login username')
|
|
1055
|
-
.option('--password <password>', 'Login password')
|
|
1056
|
-
.option('--login-url <url>', 'Explicit login URL')
|
|
1057
|
-
.action(async (url, _opts, command) => {
|
|
1058
|
-
await (0, captureRemote_1.captureRemote)(url, {
|
|
1059
|
-
...(command.parent?.opts() ?? {}),
|
|
1060
|
-
...command.opts(),
|
|
1044
|
+
contentHash: opts.contentHash,
|
|
1061
1045
|
});
|
|
1062
1046
|
});
|
|
1063
1047
|
const projectMarketing = project.command('marketing').description('Prepare local marketing captures and assets');
|
package/dist/playwright.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Browser, BrowserContext, BrowserContextOptions, Page } from 'playwright-core';
|
|
2
2
|
export declare const HEADLESS_CHROMIUM_ARGS: readonly ["--no-sandbox", "--disable-web-security", "--use-gl=angle", "--use-angle=swiftshader", "--enable-unsafe-swiftshader", "--ignore-gpu-blocklist"];
|
|
3
3
|
export declare const INTERACTIVE_CHROMIUM_ARGS: readonly ["--no-sandbox", "--disable-web-security", "--ignore-gpu-blocklist"];
|
|
4
|
+
export declare const INSTALLED_CHROME_ARGS: readonly ["--no-sandbox", "--disable-web-security", "--ignore-gpu-blocklist"];
|
|
4
5
|
export declare const CHROMIUM_ARGS: readonly ["--no-sandbox", "--disable-web-security", "--use-gl=angle", "--use-angle=swiftshader", "--enable-unsafe-swiftshader", "--ignore-gpu-blocklist"];
|
|
5
6
|
type PlaywrightModule = {
|
|
6
7
|
chromium: {
|
|
@@ -8,11 +9,13 @@ type PlaywrightModule = {
|
|
|
8
9
|
headless?: boolean;
|
|
9
10
|
args?: readonly string[];
|
|
10
11
|
chromiumSandbox?: boolean;
|
|
12
|
+
channel?: string;
|
|
11
13
|
}): Promise<Browser>;
|
|
12
14
|
launchPersistentContext?(userDataDir: string, options: BrowserContextOptions & {
|
|
13
15
|
headless?: boolean;
|
|
14
16
|
args?: readonly string[];
|
|
15
17
|
chromiumSandbox?: boolean;
|
|
18
|
+
channel?: string;
|
|
16
19
|
}): Promise<BrowserContext>;
|
|
17
20
|
};
|
|
18
21
|
};
|
|
@@ -21,11 +24,13 @@ type PlaydropBrowserContextOptions = BrowserContextOptions & {
|
|
|
21
24
|
};
|
|
22
25
|
export declare function setPlaywrightLoader(loader: (() => Promise<PlaywrightModule>) | null): void;
|
|
23
26
|
export declare function createPlaywrightLaunchError(tool: string, error: unknown): Error;
|
|
27
|
+
export declare function createInstalledChromeLaunchError(tool: string, error: unknown): Error;
|
|
24
28
|
type PageCallback<T> = (context: {
|
|
25
29
|
browser: Browser;
|
|
26
30
|
context: BrowserContext;
|
|
27
31
|
page: Page;
|
|
28
32
|
}) => Promise<T>;
|
|
29
33
|
export declare function withChromiumPage<T>(callback: PageCallback<T>, options?: PlaydropBrowserContextOptions): Promise<T>;
|
|
34
|
+
export declare function withInstalledChromePage<T>(callback: PageCallback<T>, options?: PlaydropBrowserContextOptions): Promise<T>;
|
|
30
35
|
export declare function launchPersistentChromiumContext(userDataDir: string, options?: PlaydropBrowserContextOptions): Promise<BrowserContext>;
|
|
31
36
|
export {};
|
package/dist/playwright.js
CHANGED
|
@@ -33,13 +33,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.CHROMIUM_ARGS = exports.INTERACTIVE_CHROMIUM_ARGS = exports.HEADLESS_CHROMIUM_ARGS = void 0;
|
|
36
|
+
exports.CHROMIUM_ARGS = exports.INSTALLED_CHROME_ARGS = exports.INTERACTIVE_CHROMIUM_ARGS = exports.HEADLESS_CHROMIUM_ARGS = void 0;
|
|
37
37
|
exports.setPlaywrightLoader = setPlaywrightLoader;
|
|
38
38
|
exports.createPlaywrightLaunchError = createPlaywrightLaunchError;
|
|
39
|
+
exports.createInstalledChromeLaunchError = createInstalledChromeLaunchError;
|
|
39
40
|
exports.withChromiumPage = withChromiumPage;
|
|
41
|
+
exports.withInstalledChromePage = withInstalledChromePage;
|
|
40
42
|
exports.launchPersistentChromiumContext = launchPersistentChromiumContext;
|
|
41
43
|
const node_url_1 = require("node:url");
|
|
42
44
|
const TOOL_NAME = 'playdrop';
|
|
45
|
+
const INSTALLED_CHROME_CHANNEL = 'chrome';
|
|
43
46
|
const COMMON_CHROMIUM_ARGS = [
|
|
44
47
|
'--no-sandbox',
|
|
45
48
|
'--disable-web-security',
|
|
@@ -57,6 +60,8 @@ exports.INTERACTIVE_CHROMIUM_ARGS = [
|
|
|
57
60
|
...COMMON_CHROMIUM_ARGS,
|
|
58
61
|
'--ignore-gpu-blocklist',
|
|
59
62
|
];
|
|
63
|
+
// Deterministic project checks must exercise the installed Chrome GPU path.
|
|
64
|
+
exports.INSTALLED_CHROME_ARGS = exports.INTERACTIVE_CHROMIUM_ARGS;
|
|
60
65
|
// Backward-compatible alias for older imports.
|
|
61
66
|
exports.CHROMIUM_ARGS = exports.HEADLESS_CHROMIUM_ARGS;
|
|
62
67
|
let customLoader = null;
|
|
@@ -101,6 +106,19 @@ function createPlaywrightLaunchError(tool, error) {
|
|
|
101
106
|
}
|
|
102
107
|
return error instanceof Error ? error : new Error(message);
|
|
103
108
|
}
|
|
109
|
+
function createInstalledChromeLaunchError(tool, error) {
|
|
110
|
+
const message = error && typeof error === 'object' && 'message' in error ? String(error.message) : String(error);
|
|
111
|
+
return new Error([
|
|
112
|
+
`[${tool}] Failed to launch installed Google Chrome.`,
|
|
113
|
+
'',
|
|
114
|
+
'Required setup:',
|
|
115
|
+
' 1. Install Google Chrome for this macOS user.',
|
|
116
|
+
' 2. Run from a logged-in GUI session with WindowServer access.',
|
|
117
|
+
' 3. If invoked over SSH, keep keychain unlock and the command in the same SSH session.',
|
|
118
|
+
'',
|
|
119
|
+
`Original error: ${message}`,
|
|
120
|
+
].join('\n'));
|
|
121
|
+
}
|
|
104
122
|
async function grantAutomationLocalNetworkAccess(context, origin) {
|
|
105
123
|
if (!origin?.trim()) {
|
|
106
124
|
return;
|
|
@@ -153,6 +171,47 @@ async function withChromiumPage(callback, options = {}) {
|
|
|
153
171
|
}
|
|
154
172
|
}
|
|
155
173
|
}
|
|
174
|
+
async function withInstalledChromePage(callback, options = {}) {
|
|
175
|
+
const { chromium } = await loadPlaywright();
|
|
176
|
+
let browser;
|
|
177
|
+
try {
|
|
178
|
+
browser = await chromium.launch({
|
|
179
|
+
channel: INSTALLED_CHROME_CHANNEL,
|
|
180
|
+
headless: false,
|
|
181
|
+
args: [...exports.INSTALLED_CHROME_ARGS],
|
|
182
|
+
chromiumSandbox: false,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
throw createInstalledChromeLaunchError(TOOL_NAME, error);
|
|
187
|
+
}
|
|
188
|
+
let context = null;
|
|
189
|
+
try {
|
|
190
|
+
const { automationOrigin, ...contextOptions } = options;
|
|
191
|
+
context = await browser.newContext(contextOptions);
|
|
192
|
+
await grantAutomationLocalNetworkAccess(context, automationOrigin);
|
|
193
|
+
const page = await context.newPage();
|
|
194
|
+
return await callback({ browser, context, page });
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
if (context) {
|
|
198
|
+
try {
|
|
199
|
+
await context.close();
|
|
200
|
+
}
|
|
201
|
+
catch (closeError) {
|
|
202
|
+
const message = closeError instanceof Error ? closeError.message : String(closeError);
|
|
203
|
+
console.warn(`[${TOOL_NAME}] Failed to close Playwright context: ${message}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
await browser.close();
|
|
208
|
+
}
|
|
209
|
+
catch (closeError) {
|
|
210
|
+
const message = closeError instanceof Error ? closeError.message : String(closeError);
|
|
211
|
+
console.warn(`[${TOOL_NAME}] Failed to close Playwright browser: ${message}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
156
215
|
async function launchPersistentChromiumContext(userDataDir, options = {}) {
|
|
157
216
|
const { chromium } = await loadPlaywright();
|
|
158
217
|
if (typeof chromium.launchPersistentContext !== 'function') {
|