@playdrop/playdrop-cli 0.13.0 → 0.13.2
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/commands/captureListing.d.ts +1 -1
- package/dist/commands/captureListing.js +4 -3
- package/dist/commands/worker/runtime.d.ts +1 -0
- package/dist/commands/worker/runtime.js +24 -3
- package/dist/commands/worker.d.ts +21 -0
- package/dist/commands/worker.js +229 -36
- package/dist/recorderRelease.d.ts +18 -0
- package/dist/recorderRelease.js +90 -0
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/package.json +2 -1
package/config/client-meta.json
CHANGED
|
@@ -85,7 +85,7 @@ export declare function buildListingCaptureFrameUrl({ webBase, currentUsername,
|
|
|
85
85
|
export declare function resolveListingCaptureSceneId(width: number, height: number): 'listing-landscape' | 'listing-portrait';
|
|
86
86
|
export declare function parseCaptureListingOptions(targetArg: string | undefined, options?: CaptureListingOptions): ParsedCaptureListingOptions;
|
|
87
87
|
export declare function assertSupportedListingEnvironment(platform?: NodeJS.Platform, macosVersion?: string): void;
|
|
88
|
-
export declare function resolveListingRecorderPath(
|
|
88
|
+
export declare function resolveListingRecorderPath(workerHome?: string): string;
|
|
89
89
|
export declare function buildSurfaceOutputPaths(outputDir: string, surface: AppSurface, useSurfacePrefix: boolean): {
|
|
90
90
|
rawVideoPath: string;
|
|
91
91
|
metadataPath: string;
|
|
@@ -59,6 +59,7 @@ const commandContext_1 = require("../commandContext");
|
|
|
59
59
|
const http_1 = require("../http");
|
|
60
60
|
const messages_1 = require("../messages");
|
|
61
61
|
const playwright_1 = require("../playwright");
|
|
62
|
+
const recorderRelease_1 = require("../recorderRelease");
|
|
62
63
|
const listingCaptureLease_1 = require("../listingCaptureLease");
|
|
63
64
|
const dev_1 = require("./dev");
|
|
64
65
|
const devRuntimeAssets_1 = require("./devRuntimeAssets");
|
|
@@ -204,8 +205,8 @@ function commandExists(command) {
|
|
|
204
205
|
async function ensureExecutableFile(filePath) {
|
|
205
206
|
await (0, promises_1.access)(filePath);
|
|
206
207
|
}
|
|
207
|
-
function resolveListingRecorderPath(
|
|
208
|
-
return (0,
|
|
208
|
+
function resolveListingRecorderPath(workerHome) {
|
|
209
|
+
return (0, recorderRelease_1.resolveMacWorkerRecorderPath)(workerHome);
|
|
209
210
|
}
|
|
210
211
|
function buildTimestampFolder(date = new Date()) {
|
|
211
212
|
const iso = date.toISOString();
|
|
@@ -755,7 +756,7 @@ async function ensureListingRecorderAvailable(recorderPath) {
|
|
|
755
756
|
return true;
|
|
756
757
|
}
|
|
757
758
|
catch {
|
|
758
|
-
(0, messages_1.printErrorWithHelp)(`Native recorder binary was not found at ${recorderPath}.`, ['
|
|
759
|
+
(0, messages_1.printErrorWithHelp)(`Native recorder binary was not found at ${recorderPath}.`, ['Start the PlayDrop worker once on this Mac to install the recorder from the PlayDrop CDN.'], { command: 'project capture' });
|
|
759
760
|
process.exitCode = 1;
|
|
760
761
|
return false;
|
|
761
762
|
}
|
|
@@ -85,6 +85,7 @@ export declare function buildClaudeExecArgs(input: {
|
|
|
85
85
|
}): string[];
|
|
86
86
|
export declare const PLAYWRIGHT_MCP_VERSION = "0.0.78";
|
|
87
87
|
export type WorkerInstrumentSurface = 'DESKTOP' | 'MOBILE_LANDSCAPE' | 'MOBILE_PORTRAIT';
|
|
88
|
+
export declare function resolvePlaywrightMcpCliPath(): string;
|
|
88
89
|
export declare function buildPlaywrightMcpCommand(surface?: WorkerInstrumentSurface | null): {
|
|
89
90
|
command: string;
|
|
90
91
|
args: string[];
|
|
@@ -18,6 +18,7 @@ exports.runLoggedProcess = runLoggedProcess;
|
|
|
18
18
|
exports.buildCodexExecArgs = buildCodexExecArgs;
|
|
19
19
|
exports.readCodexSandboxMode = readCodexSandboxMode;
|
|
20
20
|
exports.buildClaudeExecArgs = buildClaudeExecArgs;
|
|
21
|
+
exports.resolvePlaywrightMcpCliPath = resolvePlaywrightMcpCliPath;
|
|
21
22
|
exports.buildPlaywrightMcpCommand = buildPlaywrightMcpCommand;
|
|
22
23
|
exports.buildPlaywrightMcpServerConfig = buildPlaywrightMcpServerConfig;
|
|
23
24
|
exports.buildClaudePermissionSettings = buildClaudePermissionSettings;
|
|
@@ -27,6 +28,7 @@ exports.buildCleanRoomChildEnv = buildCleanRoomChildEnv;
|
|
|
27
28
|
const node_child_process_1 = require("node:child_process");
|
|
28
29
|
const node_fs_1 = require("node:fs");
|
|
29
30
|
const promises_1 = require("node:fs/promises");
|
|
31
|
+
const node_module_1 = require("node:module");
|
|
30
32
|
const node_os_1 = __importDefault(require("node:os"));
|
|
31
33
|
const node_path_1 = __importDefault(require("node:path"));
|
|
32
34
|
const surfaceProfiles_1 = require("../../apps/surfaceProfiles");
|
|
@@ -691,13 +693,32 @@ const CLAUDE_BASE_TOOL_NAMES = [
|
|
|
691
693
|
// never through @latest drift (see the 2026-07-13 auto-update incident).
|
|
692
694
|
exports.PLAYWRIGHT_MCP_VERSION = '0.0.78';
|
|
693
695
|
const PLAYWRIGHT_MCP_ALLOWED_TOOL_RULE = 'mcp__playwright';
|
|
696
|
+
const requireFromWorkerRuntime = (0, node_module_1.createRequire)(__filename);
|
|
697
|
+
function resolvePlaywrightMcpCliPath() {
|
|
698
|
+
let packageJsonPath;
|
|
699
|
+
let packageJson;
|
|
700
|
+
try {
|
|
701
|
+
packageJsonPath = requireFromWorkerRuntime.resolve('@playwright/mcp/package.json');
|
|
702
|
+
packageJson = requireFromWorkerRuntime('@playwright/mcp/package.json');
|
|
703
|
+
}
|
|
704
|
+
catch {
|
|
705
|
+
throw new Error(`worker_preflight_playwright_mcp_missing:@playwright/mcp@${exports.PLAYWRIGHT_MCP_VERSION}`);
|
|
706
|
+
}
|
|
707
|
+
if (packageJson.version !== exports.PLAYWRIGHT_MCP_VERSION) {
|
|
708
|
+
throw new Error(`worker_preflight_playwright_mcp_version:expected=${exports.PLAYWRIGHT_MCP_VERSION}:actual=${packageJson.version ?? 'missing'}`);
|
|
709
|
+
}
|
|
710
|
+
const cliPath = node_path_1.default.join(node_path_1.default.dirname(packageJsonPath), 'cli.js');
|
|
711
|
+
if (!(0, node_fs_1.existsSync)(cliPath)) {
|
|
712
|
+
throw new Error(`worker_preflight_playwright_mcp_missing:${cliPath}`);
|
|
713
|
+
}
|
|
714
|
+
return cliPath;
|
|
715
|
+
}
|
|
694
716
|
function buildPlaywrightMcpCommand(surface) {
|
|
695
717
|
const surfaceArgs = surface ? (0, surfaceProfiles_1.buildPlaywrightMcpSurfaceArgs)(surface) : [];
|
|
696
718
|
return {
|
|
697
|
-
command:
|
|
719
|
+
command: process.execPath,
|
|
698
720
|
args: [
|
|
699
|
-
|
|
700
|
-
`@playwright/mcp@${exports.PLAYWRIGHT_MCP_VERSION}`,
|
|
721
|
+
resolvePlaywrightMcpCliPath(),
|
|
701
722
|
'--browser',
|
|
702
723
|
'chrome',
|
|
703
724
|
'--isolated',
|
|
@@ -304,6 +304,16 @@ export declare function parseCodexModelCatalog(output: string): string[];
|
|
|
304
304
|
export declare function parseClaudeModelCatalog(output: string): string[];
|
|
305
305
|
export declare function claudeModelProbeReportsSelection(output: string, expectedDisplayName: string): boolean;
|
|
306
306
|
export declare function probeWorkerProviderIndependently<T>(probe: () => T, unavailable: T): T;
|
|
307
|
+
type MacChromeAutomationProbeRunner = (command: string, args: string[]) => {
|
|
308
|
+
status: number | null;
|
|
309
|
+
stdout: string;
|
|
310
|
+
stderr: string;
|
|
311
|
+
};
|
|
312
|
+
export declare function probeMacChromeAutomationPermission(options?: {
|
|
313
|
+
platform?: NodeJS.Platform;
|
|
314
|
+
runner?: MacChromeAutomationProbeRunner;
|
|
315
|
+
}): 'granted' | 'not_running' | 'denied' | 'not_applicable';
|
|
316
|
+
export declare function describeWorkerPreflightReason(reason: string): string;
|
|
307
317
|
export declare function buildWorkerSetupActions(input: {
|
|
308
318
|
degradedReasons: string[];
|
|
309
319
|
playwright?: {
|
|
@@ -392,6 +402,17 @@ export declare function enforceInstrumentBrowserHygiene(assignment: WorkerTaskAs
|
|
|
392
402
|
remainingTaskTabs: number;
|
|
393
403
|
blankWindows: number;
|
|
394
404
|
}>;
|
|
405
|
+
export declare function finalizeInstrumentBrowserHygiene(assignment: WorkerTaskAssignmentV2, options?: {
|
|
406
|
+
platform?: NodeJS.Platform;
|
|
407
|
+
runner?: InstrumentBrowserHygieneRunner;
|
|
408
|
+
}): Promise<{
|
|
409
|
+
ok: true;
|
|
410
|
+
remainingTaskTabs: number;
|
|
411
|
+
blankWindows: number;
|
|
412
|
+
} | {
|
|
413
|
+
ok: false;
|
|
414
|
+
error: string;
|
|
415
|
+
}>;
|
|
395
416
|
export declare function assignmentRequiresRegisteredBundle(assignment: WorkerTaskAssignmentV2): boolean;
|
|
396
417
|
export declare function startWorker(options?: WorkerStartOptions): Promise<void>;
|
|
397
418
|
export declare function reportTask(options: TaskReportOptions): Promise<void>;
|
package/dist/commands/worker.js
CHANGED
|
@@ -56,6 +56,8 @@ exports.parseCodexModelCatalog = parseCodexModelCatalog;
|
|
|
56
56
|
exports.parseClaudeModelCatalog = parseClaudeModelCatalog;
|
|
57
57
|
exports.claudeModelProbeReportsSelection = claudeModelProbeReportsSelection;
|
|
58
58
|
exports.probeWorkerProviderIndependently = probeWorkerProviderIndependently;
|
|
59
|
+
exports.probeMacChromeAutomationPermission = probeMacChromeAutomationPermission;
|
|
60
|
+
exports.describeWorkerPreflightReason = describeWorkerPreflightReason;
|
|
59
61
|
exports.buildWorkerSetupActions = buildWorkerSetupActions;
|
|
60
62
|
exports.resolveWorkerStateDir = resolveWorkerStateDir;
|
|
61
63
|
exports.resolveWorkerSupervisorLockFile = resolveWorkerSupervisorLockFile;
|
|
@@ -77,6 +79,7 @@ exports.showWorkerStatus = showWorkerStatus;
|
|
|
77
79
|
exports.setupWorker = setupWorker;
|
|
78
80
|
exports.instrumentTaskChromeTitleMatchers = instrumentTaskChromeTitleMatchers;
|
|
79
81
|
exports.enforceInstrumentBrowserHygiene = enforceInstrumentBrowserHygiene;
|
|
82
|
+
exports.finalizeInstrumentBrowserHygiene = finalizeInstrumentBrowserHygiene;
|
|
80
83
|
exports.assignmentRequiresRegisteredBundle = assignmentRequiresRegisteredBundle;
|
|
81
84
|
exports.startWorker = startWorker;
|
|
82
85
|
exports.reportTask = reportTask;
|
|
@@ -108,6 +111,7 @@ const commandContext_1 = require("../commandContext");
|
|
|
108
111
|
const config_1 = require("../config");
|
|
109
112
|
const messages_1 = require("../messages");
|
|
110
113
|
const output_1 = require("../output");
|
|
114
|
+
const recorderRelease_1 = require("../recorderRelease");
|
|
111
115
|
const shellProbe_1 = require("../shellProbe");
|
|
112
116
|
const review_1 = require("./review");
|
|
113
117
|
const upload_1 = require("./upload");
|
|
@@ -1548,14 +1552,6 @@ function readCodexAuthenticated(homeDir = node_os_1.default.homedir()) {
|
|
|
1548
1552
|
return false;
|
|
1549
1553
|
}
|
|
1550
1554
|
}
|
|
1551
|
-
function probeNpmVersion() {
|
|
1552
|
-
const result = (0, shellProbe_1.runShell)('npm --version');
|
|
1553
|
-
const version = result.output.trim().split('\n')[0]?.trim() ?? '';
|
|
1554
|
-
if (result.exitCode !== 0 || !version) {
|
|
1555
|
-
throw new Error(`worker_preflight_npm_missing: "npm --version" exited with code ${result.exitCode}.`);
|
|
1556
|
-
}
|
|
1557
|
-
return version;
|
|
1558
|
-
}
|
|
1559
1555
|
function probePlaywrightChromium() {
|
|
1560
1556
|
let playwrightCore;
|
|
1561
1557
|
let packageJson;
|
|
@@ -2833,6 +2829,146 @@ function errorMessage(error) {
|
|
|
2833
2829
|
function errorCode(error) {
|
|
2834
2830
|
return errorMessage(error).split(':')[0]?.trim() || 'unknown_error';
|
|
2835
2831
|
}
|
|
2832
|
+
function workerCommandExists(command) {
|
|
2833
|
+
const probe = (0, shellProbe_1.runShell)((0, shellProbe_1.buildCommandPathProbe)(command));
|
|
2834
|
+
return probe.exitCode === 0 && Boolean(probe.output.trim());
|
|
2835
|
+
}
|
|
2836
|
+
function resolveInstalledChromeExecutable() {
|
|
2837
|
+
if (node_process_1.default.platform === 'darwin') {
|
|
2838
|
+
const executable = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
2839
|
+
return (0, node_fs_1.existsSync)(executable) ? executable : null;
|
|
2840
|
+
}
|
|
2841
|
+
if (node_process_1.default.platform === 'win32') {
|
|
2842
|
+
const roots = [node_process_1.default.env.PROGRAMFILES, node_process_1.default.env['PROGRAMFILES(X86)'], node_process_1.default.env.LOCALAPPDATA]
|
|
2843
|
+
.map((value) => value?.trim())
|
|
2844
|
+
.filter((value) => Boolean(value));
|
|
2845
|
+
for (const root of roots) {
|
|
2846
|
+
const executable = node_path_1.default.join(root, 'Google', 'Chrome', 'Application', 'chrome.exe');
|
|
2847
|
+
if ((0, node_fs_1.existsSync)(executable)) {
|
|
2848
|
+
return executable;
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
return null;
|
|
2852
|
+
}
|
|
2853
|
+
return workerCommandExists('google-chrome') ? 'google-chrome' : null;
|
|
2854
|
+
}
|
|
2855
|
+
const MAC_CHROME_AUTOMATION_PREFLIGHT_APPLESCRIPT = `
|
|
2856
|
+
if application "Google Chrome" is not running then
|
|
2857
|
+
return "not_running"
|
|
2858
|
+
end if
|
|
2859
|
+
tell application "Google Chrome"
|
|
2860
|
+
count of windows
|
|
2861
|
+
end tell
|
|
2862
|
+
return "granted"
|
|
2863
|
+
`;
|
|
2864
|
+
function probeMacChromeAutomationPermission(options = {}) {
|
|
2865
|
+
if ((options.platform ?? node_process_1.default.platform) !== 'darwin') {
|
|
2866
|
+
return 'not_applicable';
|
|
2867
|
+
}
|
|
2868
|
+
const runner = options.runner ?? ((command, args) => {
|
|
2869
|
+
const result = (0, node_child_process_1.spawnSync)(command, args, { encoding: 'utf8', timeout: 10000 });
|
|
2870
|
+
return {
|
|
2871
|
+
status: result.status,
|
|
2872
|
+
stdout: result.stdout ?? '',
|
|
2873
|
+
stderr: result.stderr ?? '',
|
|
2874
|
+
};
|
|
2875
|
+
});
|
|
2876
|
+
const result = runner('osascript', ['-e', MAC_CHROME_AUTOMATION_PREFLIGHT_APPLESCRIPT]);
|
|
2877
|
+
const output = result.stdout.trim();
|
|
2878
|
+
if (result.status === 0 && output === 'granted') {
|
|
2879
|
+
return 'granted';
|
|
2880
|
+
}
|
|
2881
|
+
if (result.status === 0 && output === 'not_running') {
|
|
2882
|
+
return 'not_running';
|
|
2883
|
+
}
|
|
2884
|
+
return 'denied';
|
|
2885
|
+
}
|
|
2886
|
+
function collectWorkerInfrastructureFailures() {
|
|
2887
|
+
const failures = [];
|
|
2888
|
+
if (!resolveInstalledChromeExecutable()) {
|
|
2889
|
+
failures.push('worker_preflight_google_chrome_missing');
|
|
2890
|
+
}
|
|
2891
|
+
try {
|
|
2892
|
+
(0, runtime_1.resolvePlaywrightMcpCliPath)();
|
|
2893
|
+
}
|
|
2894
|
+
catch (error) {
|
|
2895
|
+
failures.push(errorCode(error));
|
|
2896
|
+
}
|
|
2897
|
+
if (node_process_1.default.platform !== 'darwin') {
|
|
2898
|
+
return failures;
|
|
2899
|
+
}
|
|
2900
|
+
if (node_process_1.default.arch !== recorderRelease_1.MAC_WORKER_RECORDER_RELEASE.architecture) {
|
|
2901
|
+
failures.push('worker_preflight_recorder_architecture_unsupported');
|
|
2902
|
+
return failures;
|
|
2903
|
+
}
|
|
2904
|
+
if (!workerCommandExists('ffmpeg')) {
|
|
2905
|
+
failures.push('worker_preflight_ffmpeg_missing');
|
|
2906
|
+
}
|
|
2907
|
+
if (!workerCommandExists('ffprobe')) {
|
|
2908
|
+
failures.push('worker_preflight_ffprobe_missing');
|
|
2909
|
+
}
|
|
2910
|
+
const guiSession = (0, shellProbe_1.runShell)('/bin/launchctl print "gui/$(/usr/bin/id -u)"');
|
|
2911
|
+
if (guiSession.exitCode !== 0) {
|
|
2912
|
+
failures.push('worker_preflight_gui_session_missing');
|
|
2913
|
+
}
|
|
2914
|
+
if (probeMacChromeAutomationPermission() === 'denied') {
|
|
2915
|
+
failures.push('worker_preflight_chrome_automation_permission_missing');
|
|
2916
|
+
}
|
|
2917
|
+
const recorderPath = (0, recorderRelease_1.resolveMacWorkerRecorderPath)();
|
|
2918
|
+
if (!(0, node_fs_1.existsSync)(recorderPath)) {
|
|
2919
|
+
failures.push('worker_preflight_recorder_missing');
|
|
2920
|
+
return failures;
|
|
2921
|
+
}
|
|
2922
|
+
const recorderStat = (0, node_fs_1.statSync)(recorderPath);
|
|
2923
|
+
if (!recorderStat.isFile() || (recorderStat.mode & 0o111) === 0) {
|
|
2924
|
+
failures.push('worker_preflight_recorder_not_executable');
|
|
2925
|
+
return failures;
|
|
2926
|
+
}
|
|
2927
|
+
const installedSha256 = (0, recorderRelease_1.recorderSha256)((0, node_fs_1.readFileSync)(recorderPath));
|
|
2928
|
+
if (installedSha256 !== recorderRelease_1.MAC_WORKER_RECORDER_RELEASE.sha256) {
|
|
2929
|
+
failures.push('worker_preflight_recorder_hash_mismatch');
|
|
2930
|
+
return failures;
|
|
2931
|
+
}
|
|
2932
|
+
const permission = (0, node_child_process_1.spawnSync)(recorderPath, ['--check-permission'], {
|
|
2933
|
+
encoding: 'utf8',
|
|
2934
|
+
timeout: 10000,
|
|
2935
|
+
});
|
|
2936
|
+
if (permission.status !== 0) {
|
|
2937
|
+
failures.push('worker_preflight_screen_recording_permission_missing');
|
|
2938
|
+
}
|
|
2939
|
+
return failures;
|
|
2940
|
+
}
|
|
2941
|
+
function describeWorkerPreflightReason(reason) {
|
|
2942
|
+
if (reason.startsWith('worker_preflight_recorder_download_failed:')) {
|
|
2943
|
+
return `The PlayDrop macOS recorder could not be downloaded: ${reason.slice('worker_preflight_recorder_download_failed:'.length).trim()}`;
|
|
2944
|
+
}
|
|
2945
|
+
const descriptions = {
|
|
2946
|
+
worker_preflight_google_chrome_missing: 'Google Chrome is missing from its standard installation path.',
|
|
2947
|
+
worker_preflight_playwright_missing: 'The PlayDrop CLI cannot load Playwright.',
|
|
2948
|
+
worker_preflight_chromium_missing: 'The Chromium executable required by Playwright is missing.',
|
|
2949
|
+
worker_preflight_playwright_mcp_missing: 'The pinned Playwright MCP package is missing from the PlayDrop CLI installation.',
|
|
2950
|
+
worker_preflight_playwright_mcp_version: 'The installed Playwright MCP package does not match the PlayDrop CLI release.',
|
|
2951
|
+
worker_preflight_ffmpeg_missing: 'ffmpeg is missing from PATH.',
|
|
2952
|
+
worker_preflight_ffprobe_missing: 'ffprobe is missing from PATH.',
|
|
2953
|
+
worker_preflight_gui_session_missing: 'No active macOS GUI session is available.',
|
|
2954
|
+
worker_preflight_chrome_automation_permission_missing: 'Google Chrome Automation permission is missing. In System Settings > Privacy & Security > Automation, allow the PlayDrop worker process to control Google Chrome, then restart the worker.',
|
|
2955
|
+
worker_preflight_recorder_architecture_unsupported: 'The native recorder is currently available only for Apple silicon Macs.',
|
|
2956
|
+
worker_preflight_recorder_missing: 'The PlayDrop macOS recorder is missing.',
|
|
2957
|
+
worker_preflight_recorder_not_executable: 'The PlayDrop macOS recorder is not executable.',
|
|
2958
|
+
worker_preflight_recorder_hash_mismatch: 'The PlayDrop macOS recorder does not match this CLI release.',
|
|
2959
|
+
worker_preflight_screen_recording_permission_missing: 'Screen Recording permission is not granted to the PlayDrop recorder.',
|
|
2960
|
+
codex_not_authenticated: 'Codex is installed but not authenticated.',
|
|
2961
|
+
codex_models_unavailable: 'Codex is authenticated but reported no usable models.',
|
|
2962
|
+
claude_not_authenticated: 'Claude Code is installed but not authenticated.',
|
|
2963
|
+
claude_models_unavailable: 'Claude Code is authenticated but reported no usable models.',
|
|
2964
|
+
agent_cli_not_found: 'No supported authenticated agent CLI is installed.',
|
|
2965
|
+
};
|
|
2966
|
+
return descriptions[reason] ?? reason;
|
|
2967
|
+
}
|
|
2968
|
+
function formatWorkerPreflightFailure(reasons) {
|
|
2969
|
+
const uniqueReasons = Array.from(new Set(reasons));
|
|
2970
|
+
return new Error(`worker_preflight_failed:\n${uniqueReasons.map((reason) => `- ${describeWorkerPreflightReason(reason)}`).join('\n')}`);
|
|
2971
|
+
}
|
|
2836
2972
|
function readWorkerTargetUpdateFailure() {
|
|
2837
2973
|
const failureFile = node_process_1.default.env.PLAYDROP_WORKER_TARGET_UPDATE_FAILURE_FILE?.trim() ?? '';
|
|
2838
2974
|
if (!failureFile || !(0, node_fs_1.existsSync)(failureFile)) {
|
|
@@ -2858,7 +2994,7 @@ function collectWorkerLocalStatus() {
|
|
|
2858
2994
|
let claudeVersion = null;
|
|
2859
2995
|
let claudeModels = [];
|
|
2860
2996
|
let cursorVersion = null;
|
|
2861
|
-
|
|
2997
|
+
const npmVersion = null;
|
|
2862
2998
|
let playwright = null;
|
|
2863
2999
|
let targetUpdateFailure = null;
|
|
2864
3000
|
try {
|
|
@@ -2881,12 +3017,6 @@ function collectWorkerLocalStatus() {
|
|
|
2881
3017
|
catch (error) {
|
|
2882
3018
|
degradedReasons.push(errorCode(error));
|
|
2883
3019
|
}
|
|
2884
|
-
try {
|
|
2885
|
-
npmVersion = probeNpmVersion();
|
|
2886
|
-
}
|
|
2887
|
-
catch (error) {
|
|
2888
|
-
degradedReasons.push(errorCode(error));
|
|
2889
|
-
}
|
|
2890
3020
|
try {
|
|
2891
3021
|
playwright = probePlaywrightChromium();
|
|
2892
3022
|
if (!playwright.chromiumInstalled) {
|
|
@@ -2896,6 +3026,7 @@ function collectWorkerLocalStatus() {
|
|
|
2896
3026
|
catch (error) {
|
|
2897
3027
|
degradedReasons.push(errorCode(error));
|
|
2898
3028
|
}
|
|
3029
|
+
degradedReasons.push(...collectWorkerInfrastructureFailures());
|
|
2899
3030
|
try {
|
|
2900
3031
|
targetUpdateFailure = readWorkerTargetUpdateFailure();
|
|
2901
3032
|
}
|
|
@@ -2929,7 +3060,8 @@ function collectWorkerLocalStatus() {
|
|
|
2929
3060
|
degradedReasons.push(errorCode(error));
|
|
2930
3061
|
}
|
|
2931
3062
|
const uniqueReasons = Array.from(new Set(degradedReasons));
|
|
2932
|
-
const
|
|
3063
|
+
const infrastructureFailures = degradedReasons.filter((reason) => reason.startsWith('worker_preflight_'));
|
|
3064
|
+
const infrastructureReady = Boolean(playwright?.chromiumInstalled) && infrastructureFailures.length === 0;
|
|
2933
3065
|
const ready = Boolean(capabilities?.ready) && infrastructureReady;
|
|
2934
3066
|
return {
|
|
2935
3067
|
ready,
|
|
@@ -2949,6 +3081,11 @@ function collectWorkerLocalStatus() {
|
|
|
2949
3081
|
function buildWorkerSetupActions(input) {
|
|
2950
3082
|
const reasons = new Set(input.degradedReasons);
|
|
2951
3083
|
const actions = [];
|
|
3084
|
+
const addManualAction = (reason, label) => {
|
|
3085
|
+
if (reasons.has(reason)) {
|
|
3086
|
+
actions.push({ reason, label, message: describeWorkerPreflightReason(reason) });
|
|
3087
|
+
}
|
|
3088
|
+
};
|
|
2952
3089
|
if (reasons.has('worker_preflight_chromium_missing')) {
|
|
2953
3090
|
const version = input.playwright?.version && input.playwright.version !== 'unknown'
|
|
2954
3091
|
? `@${input.playwright.version}`
|
|
@@ -2977,6 +3114,18 @@ function buildWorkerSetupActions(input) {
|
|
|
2977
3114
|
message: 'Install and authenticate Codex or Claude Code, then rerun playdrop agents status --json.',
|
|
2978
3115
|
});
|
|
2979
3116
|
}
|
|
3117
|
+
addManualAction('worker_preflight_google_chrome_missing', 'Install Google Chrome');
|
|
3118
|
+
addManualAction('worker_preflight_playwright_mcp_missing', 'Repair the PlayDrop CLI installation');
|
|
3119
|
+
addManualAction('worker_preflight_playwright_mcp_version', 'Repair the PlayDrop CLI installation');
|
|
3120
|
+
addManualAction('worker_preflight_ffmpeg_missing', 'Install ffmpeg');
|
|
3121
|
+
addManualAction('worker_preflight_ffprobe_missing', 'Install ffprobe');
|
|
3122
|
+
addManualAction('worker_preflight_gui_session_missing', 'Sign in to the Mac desktop');
|
|
3123
|
+
addManualAction('worker_preflight_chrome_automation_permission_missing', 'Grant Google Chrome Automation permission');
|
|
3124
|
+
addManualAction('worker_preflight_recorder_architecture_unsupported', 'Use an Apple silicon Mac');
|
|
3125
|
+
addManualAction('worker_preflight_recorder_missing', 'Restart the worker to download the recorder');
|
|
3126
|
+
addManualAction('worker_preflight_recorder_not_executable', 'Restart the worker to repair the recorder');
|
|
3127
|
+
addManualAction('worker_preflight_recorder_hash_mismatch', 'Restart the worker to repair the recorder');
|
|
3128
|
+
addManualAction('worker_preflight_screen_recording_permission_missing', 'Grant Screen Recording permission');
|
|
2980
3129
|
return actions;
|
|
2981
3130
|
}
|
|
2982
3131
|
function resolveWorkerStateDir() {
|
|
@@ -3595,13 +3744,30 @@ TARGET_CLI_VERSION="$(read_policy_value targetCliVersion)"
|
|
|
3595
3744
|
MINIMUM_CLI_VERSION="$(read_policy_value minimumCliVersion)"
|
|
3596
3745
|
|
|
3597
3746
|
if [ -n "$TARGET_CLI_VERSION" ]; then
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3747
|
+
CURRENT_CLI_VERSION="$(current_cli_version)" || CURRENT_CLI_VERSION=""
|
|
3748
|
+
if [ "$CURRENT_CLI_VERSION" != "$TARGET_CLI_VERSION" ]; then
|
|
3749
|
+
GLOBAL_NPM_ROOT="$(npm root -g)" || GLOBAL_NPM_ROOT=""
|
|
3750
|
+
BROKEN_FSEVENTS_PATH="$GLOBAL_NPM_ROOT/@playdrop/playdrop-cli/node_modules/fsevents"
|
|
3751
|
+
if [ -n "$GLOBAL_NPM_ROOT" ] && [ -f "$BROKEN_FSEVENTS_PATH/package.json" ]; then
|
|
3752
|
+
node - "$BROKEN_FSEVENTS_PATH" <<'NODE' || record_target_failure "worker_update_fsevents_cleanup_failed"
|
|
3753
|
+
const fs = require("fs");
|
|
3754
|
+
const path = process.argv[2];
|
|
3755
|
+
const packageJson = JSON.parse(fs.readFileSync(path + "/package.json", "utf8"));
|
|
3756
|
+
if (packageJson.version === "2.3.2") {
|
|
3757
|
+
fs.rmSync(path, { recursive: true, force: true });
|
|
3758
|
+
}
|
|
3759
|
+
NODE
|
|
3760
|
+
fi
|
|
3761
|
+
if [ "$TARGET_UPDATE_FAILED" -eq 0 ]; then
|
|
3762
|
+
if npm install -g --no-audit --no-fund "@playdrop/playdrop-cli@$TARGET_CLI_VERSION"; then
|
|
3763
|
+
CURRENT_CLI_VERSION="$(current_cli_version)" || CURRENT_CLI_VERSION=""
|
|
3764
|
+
if [ "$CURRENT_CLI_VERSION" != "$TARGET_CLI_VERSION" ]; then
|
|
3765
|
+
record_target_failure "worker_update_cli_version_mismatch"
|
|
3766
|
+
fi
|
|
3767
|
+
else
|
|
3768
|
+
record_target_failure "worker_update_cli_install_failed"
|
|
3769
|
+
fi
|
|
3602
3770
|
fi
|
|
3603
|
-
else
|
|
3604
|
-
record_target_failure "worker_update_cli_install_failed"
|
|
3605
3771
|
fi
|
|
3606
3772
|
fi
|
|
3607
3773
|
|
|
@@ -3799,7 +3965,7 @@ async function setupWorker(options = {}) {
|
|
|
3799
3965
|
const workerName = explicitWorkerName || `${username} - ${node_os_1.default.hostname()}`;
|
|
3800
3966
|
const local = collectWorkerLocalStatus();
|
|
3801
3967
|
const agentReady = local.agents.some((agent) => agent.ready === true);
|
|
3802
|
-
const baseRuntimeReady = Boolean(agentReady && local.
|
|
3968
|
+
const baseRuntimeReady = Boolean(agentReady && local.playwright?.chromiumInstalled);
|
|
3803
3969
|
if (!baseRuntimeReady) {
|
|
3804
3970
|
throw new Error(`worker_setup_preflight_failed: ${local.degradedReasons.join(', ') || 'local worker runtime is not ready'}`);
|
|
3805
3971
|
}
|
|
@@ -4037,6 +4203,15 @@ async function enforceInstrumentBrowserHygiene(assignment, options = {}) {
|
|
|
4037
4203
|
instrumentBrowserHygieneTail = result.then(() => undefined, () => undefined);
|
|
4038
4204
|
return result;
|
|
4039
4205
|
}
|
|
4206
|
+
async function finalizeInstrumentBrowserHygiene(assignment, options = {}) {
|
|
4207
|
+
try {
|
|
4208
|
+
const result = await enforceInstrumentBrowserHygiene(assignment, options);
|
|
4209
|
+
return { ok: true, ...result };
|
|
4210
|
+
}
|
|
4211
|
+
catch (error) {
|
|
4212
|
+
return { ok: false, error: errorMessage(error) };
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4040
4215
|
function assignmentRequiresRegisteredBundle(assignment) {
|
|
4041
4216
|
const playdropMetadata = assignment.task.metadata.playdrop;
|
|
4042
4217
|
return Boolean(playdropMetadata
|
|
@@ -4082,15 +4257,35 @@ async function startWorker(options = {}) {
|
|
|
4082
4257
|
const supervisorLock = acquireWorkerSupervisorLock({ supervisorType, workerName });
|
|
4083
4258
|
try {
|
|
4084
4259
|
await removeStaleAgentRunCredentials();
|
|
4085
|
-
const
|
|
4086
|
-
|
|
4260
|
+
const startupFailures = [];
|
|
4261
|
+
if (node_process_1.default.platform === 'darwin') {
|
|
4262
|
+
try {
|
|
4263
|
+
const recorder = await (0, recorderRelease_1.ensureMacWorkerRecorderInstalled)();
|
|
4264
|
+
if (recorder.downloaded) {
|
|
4265
|
+
console.log(`Installed PlayDrop macOS recorder ${recorderRelease_1.MAC_WORKER_RECORDER_RELEASE.version} at ${recorder.path}.`);
|
|
4266
|
+
}
|
|
4267
|
+
}
|
|
4268
|
+
catch (error) {
|
|
4269
|
+
startupFailures.push(`worker_preflight_recorder_download_failed:${errorMessage(error)}`);
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
else if (node_process_1.default.platform === 'win32') {
|
|
4273
|
+
console.warn('Native listing recording is not available on Windows yet.');
|
|
4274
|
+
}
|
|
4275
|
+
const local = collectWorkerLocalStatus();
|
|
4276
|
+
startupFailures.push(...local.degradedReasons);
|
|
4277
|
+
if (startupFailures.length > 0 || !local.ready || !local.capabilities || !local.playwright?.chromiumInstalled) {
|
|
4278
|
+
throw formatWorkerPreflightFailure(startupFailures);
|
|
4279
|
+
}
|
|
4280
|
+
const npmVersion = local.npmVersion;
|
|
4281
|
+
const playwright = {
|
|
4282
|
+
version: local.playwright.version ?? 'unknown',
|
|
4283
|
+
chromiumInstalled: true,
|
|
4284
|
+
...(local.playwright.executablePath ? { executablePath: local.playwright.executablePath } : {}),
|
|
4285
|
+
};
|
|
4087
4286
|
const workerKey = (0, config_1.getOrCreateWorkerKey)();
|
|
4088
4287
|
const maxParallelTasks = (0, runtime_1.readPositiveEnvInt)('PLAYDROP_WORKER_MAX_PARALLEL_TASKS', DEFAULT_WORKER_MAX_PARALLEL_TASKS);
|
|
4089
|
-
let capabilities =
|
|
4090
|
-
npmVersion,
|
|
4091
|
-
playwright,
|
|
4092
|
-
maxParallelTasks,
|
|
4093
|
-
});
|
|
4288
|
+
let capabilities = local.capabilities;
|
|
4094
4289
|
let lastCapabilityRefreshAt = Date.now();
|
|
4095
4290
|
writeWorkerRuntimeState({
|
|
4096
4291
|
env,
|
|
@@ -4962,15 +5157,13 @@ async function startWorker(options = {}) {
|
|
|
4962
5157
|
}
|
|
4963
5158
|
if (agentStartedAt
|
|
4964
5159
|
&& assignment.agent.browser !== 'NONE') {
|
|
4965
|
-
|
|
4966
|
-
|
|
5160
|
+
const hygiene = await finalizeInstrumentBrowserHygiene(assignment);
|
|
5161
|
+
if (hygiene.ok) {
|
|
4967
5162
|
console.log(`Agent task ${task.id} browser hygiene passed: ${hygiene.remainingTaskTabs} task tabs, ${hygiene.blankWindows} blank window(s).`);
|
|
4968
5163
|
}
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
console.error(`Agent task ${task.id} browser hygiene failed: ${error.message}`);
|
|
5164
|
+
else {
|
|
5165
|
+
console.error(`Agent task ${task.id} browser hygiene failed; worker continuing: ${hygiene.error}`);
|
|
4972
5166
|
retainWorkspace = true;
|
|
4973
|
-
recordFatalTaskError(error);
|
|
4974
5167
|
}
|
|
4975
5168
|
}
|
|
4976
5169
|
activeTerminators.delete(task.id);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const MAC_WORKER_RECORDER_RELEASE: {
|
|
2
|
+
readonly version: 1;
|
|
3
|
+
readonly architecture: "arm64";
|
|
4
|
+
readonly url: "https://assets.playdrop.ai/cli/recorder/1/darwin-arm64/playdrop-listing-recorder";
|
|
5
|
+
readonly sha256: "0a19f0613b8b69945b532d9bc75a52803cd84c5977c78da76a446ecd2d29f715";
|
|
6
|
+
};
|
|
7
|
+
export declare function resolveWorkerHomeDirectory(): string;
|
|
8
|
+
export declare function resolveMacWorkerRecorderPath(workerHome?: string): string;
|
|
9
|
+
export declare function recorderSha256(bytes: Uint8Array): string;
|
|
10
|
+
export declare function ensureMacWorkerRecorderInstalled(input?: {
|
|
11
|
+
workerHome?: string;
|
|
12
|
+
download?: (url: string) => Promise<Buffer>;
|
|
13
|
+
platform?: NodeJS.Platform;
|
|
14
|
+
architecture?: string;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
path: string;
|
|
17
|
+
downloaded: boolean;
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.MAC_WORKER_RECORDER_RELEASE = void 0;
|
|
7
|
+
exports.resolveWorkerHomeDirectory = resolveWorkerHomeDirectory;
|
|
8
|
+
exports.resolveMacWorkerRecorderPath = resolveMacWorkerRecorderPath;
|
|
9
|
+
exports.recorderSha256 = recorderSha256;
|
|
10
|
+
exports.ensureMacWorkerRecorderInstalled = ensureMacWorkerRecorderInstalled;
|
|
11
|
+
const node_crypto_1 = require("node:crypto");
|
|
12
|
+
const promises_1 = require("node:fs/promises");
|
|
13
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
14
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
exports.MAC_WORKER_RECORDER_RELEASE = {
|
|
16
|
+
version: 1,
|
|
17
|
+
architecture: 'arm64',
|
|
18
|
+
url: 'https://assets.playdrop.ai/cli/recorder/1/darwin-arm64/playdrop-listing-recorder',
|
|
19
|
+
sha256: '0a19f0613b8b69945b532d9bc75a52803cd84c5977c78da76a446ecd2d29f715',
|
|
20
|
+
};
|
|
21
|
+
const MAX_RECORDER_DOWNLOAD_BYTES = 10 * 1024 * 1024;
|
|
22
|
+
const RECORDER_DOWNLOAD_TIMEOUT_MS = 30000;
|
|
23
|
+
function resolveWorkerHomeDirectory() {
|
|
24
|
+
const configured = process.env.PLAYDROP_WORKER_HOME?.trim();
|
|
25
|
+
return configured ? node_path_1.default.resolve(configured) : node_path_1.default.join(node_os_1.default.homedir(), 'PlayDropWorker');
|
|
26
|
+
}
|
|
27
|
+
function resolveMacWorkerRecorderPath(workerHome = resolveWorkerHomeDirectory()) {
|
|
28
|
+
return node_path_1.default.join(node_path_1.default.resolve(workerHome), 'tools', 'playdrop-listing-recorder');
|
|
29
|
+
}
|
|
30
|
+
function recorderSha256(bytes) {
|
|
31
|
+
return (0, node_crypto_1.createHash)('sha256').update(bytes).digest('hex');
|
|
32
|
+
}
|
|
33
|
+
async function downloadRecorder(url) {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const timeout = setTimeout(() => controller.abort(), RECORDER_DOWNLOAD_TIMEOUT_MS);
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
throw new Error(`worker_recorder_download_http_${response.status}`);
|
|
40
|
+
}
|
|
41
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
42
|
+
if (bytes.length === 0 || bytes.length > MAX_RECORDER_DOWNLOAD_BYTES) {
|
|
43
|
+
throw new Error(`worker_recorder_download_size_invalid:${bytes.length}`);
|
|
44
|
+
}
|
|
45
|
+
return bytes;
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
clearTimeout(timeout);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function ensureMacWorkerRecorderInstalled(input = {}) {
|
|
52
|
+
const platform = input.platform ?? process.platform;
|
|
53
|
+
const architecture = input.architecture ?? process.arch;
|
|
54
|
+
if (platform !== 'darwin') {
|
|
55
|
+
throw new Error(`worker_recorder_platform_unsupported:${platform}`);
|
|
56
|
+
}
|
|
57
|
+
if (architecture !== exports.MAC_WORKER_RECORDER_RELEASE.architecture) {
|
|
58
|
+
throw new Error(`worker_recorder_architecture_unsupported:${architecture}`);
|
|
59
|
+
}
|
|
60
|
+
const recorderPath = resolveMacWorkerRecorderPath(input.workerHome);
|
|
61
|
+
let installedBytes = null;
|
|
62
|
+
try {
|
|
63
|
+
installedBytes = await (0, promises_1.readFile)(recorderPath);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error.code !== 'ENOENT') {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (installedBytes && recorderSha256(installedBytes) === exports.MAC_WORKER_RECORDER_RELEASE.sha256) {
|
|
71
|
+
await (0, promises_1.chmod)(recorderPath, 0o755);
|
|
72
|
+
return { path: recorderPath, downloaded: false };
|
|
73
|
+
}
|
|
74
|
+
const bytes = await (input.download ?? downloadRecorder)(exports.MAC_WORKER_RECORDER_RELEASE.url);
|
|
75
|
+
const actualSha256 = recorderSha256(bytes);
|
|
76
|
+
if (actualSha256 !== exports.MAC_WORKER_RECORDER_RELEASE.sha256) {
|
|
77
|
+
throw new Error(`worker_recorder_hash_mismatch:expected=${exports.MAC_WORKER_RECORDER_RELEASE.sha256}:actual=${actualSha256}`);
|
|
78
|
+
}
|
|
79
|
+
await (0, promises_1.mkdir)(node_path_1.default.dirname(recorderPath), { recursive: true });
|
|
80
|
+
const temporaryPath = `${recorderPath}.${process.pid}.${Date.now()}.tmp`;
|
|
81
|
+
try {
|
|
82
|
+
await (0, promises_1.writeFile)(temporaryPath, bytes, { mode: 0o755 });
|
|
83
|
+
await (0, promises_1.chmod)(temporaryPath, 0o755);
|
|
84
|
+
await (0, promises_1.rename)(temporaryPath, recorderPath);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await (0, promises_1.rm)(temporaryPath, { force: true });
|
|
88
|
+
}
|
|
89
|
+
return { path: recorderPath, downloaded: true };
|
|
90
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playdrop/playdrop-cli",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"description": "Official Playdrop CLI for publishing browser games, creator apps, and AI-generated game assets on playdrop.ai",
|
|
5
5
|
"homepage": "https://www.playdrop.ai",
|
|
6
6
|
"repository": {
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
"@playdrop/config": "file:../config",
|
|
79
79
|
"@playdrop/types": "file:../types",
|
|
80
80
|
"@playdrop/vox-three": "file:../vox-three",
|
|
81
|
+
"@playwright/mcp": "0.0.78",
|
|
81
82
|
"ajv": "^8.17.1",
|
|
82
83
|
"commander": "^11.1.0",
|
|
83
84
|
"entities": "^6.0.0",
|