@playdrop/playdrop-cli 0.13.0 → 0.13.1
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 +1 -0
- package/dist/commands/worker.js +155 -24
- 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,7 @@ 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
|
+
export declare function describeWorkerPreflightReason(reason: string): string;
|
|
307
308
|
export declare function buildWorkerSetupActions(input: {
|
|
308
309
|
degradedReasons: string[];
|
|
309
310
|
playwright?: {
|
package/dist/commands/worker.js
CHANGED
|
@@ -56,6 +56,7 @@ exports.parseCodexModelCatalog = parseCodexModelCatalog;
|
|
|
56
56
|
exports.parseClaudeModelCatalog = parseClaudeModelCatalog;
|
|
57
57
|
exports.claudeModelProbeReportsSelection = claudeModelProbeReportsSelection;
|
|
58
58
|
exports.probeWorkerProviderIndependently = probeWorkerProviderIndependently;
|
|
59
|
+
exports.describeWorkerPreflightReason = describeWorkerPreflightReason;
|
|
59
60
|
exports.buildWorkerSetupActions = buildWorkerSetupActions;
|
|
60
61
|
exports.resolveWorkerStateDir = resolveWorkerStateDir;
|
|
61
62
|
exports.resolveWorkerSupervisorLockFile = resolveWorkerSupervisorLockFile;
|
|
@@ -108,6 +109,7 @@ const commandContext_1 = require("../commandContext");
|
|
|
108
109
|
const config_1 = require("../config");
|
|
109
110
|
const messages_1 = require("../messages");
|
|
110
111
|
const output_1 = require("../output");
|
|
112
|
+
const recorderRelease_1 = require("../recorderRelease");
|
|
111
113
|
const shellProbe_1 = require("../shellProbe");
|
|
112
114
|
const review_1 = require("./review");
|
|
113
115
|
const upload_1 = require("./upload");
|
|
@@ -1548,14 +1550,6 @@ function readCodexAuthenticated(homeDir = node_os_1.default.homedir()) {
|
|
|
1548
1550
|
return false;
|
|
1549
1551
|
}
|
|
1550
1552
|
}
|
|
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
1553
|
function probePlaywrightChromium() {
|
|
1560
1554
|
let playwrightCore;
|
|
1561
1555
|
let packageJson;
|
|
@@ -2833,6 +2827,111 @@ function errorMessage(error) {
|
|
|
2833
2827
|
function errorCode(error) {
|
|
2834
2828
|
return errorMessage(error).split(':')[0]?.trim() || 'unknown_error';
|
|
2835
2829
|
}
|
|
2830
|
+
function workerCommandExists(command) {
|
|
2831
|
+
const probe = (0, shellProbe_1.runShell)((0, shellProbe_1.buildCommandPathProbe)(command));
|
|
2832
|
+
return probe.exitCode === 0 && Boolean(probe.output.trim());
|
|
2833
|
+
}
|
|
2834
|
+
function resolveInstalledChromeExecutable() {
|
|
2835
|
+
if (node_process_1.default.platform === 'darwin') {
|
|
2836
|
+
const executable = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
2837
|
+
return (0, node_fs_1.existsSync)(executable) ? executable : null;
|
|
2838
|
+
}
|
|
2839
|
+
if (node_process_1.default.platform === 'win32') {
|
|
2840
|
+
const roots = [node_process_1.default.env.PROGRAMFILES, node_process_1.default.env['PROGRAMFILES(X86)'], node_process_1.default.env.LOCALAPPDATA]
|
|
2841
|
+
.map((value) => value?.trim())
|
|
2842
|
+
.filter((value) => Boolean(value));
|
|
2843
|
+
for (const root of roots) {
|
|
2844
|
+
const executable = node_path_1.default.join(root, 'Google', 'Chrome', 'Application', 'chrome.exe');
|
|
2845
|
+
if ((0, node_fs_1.existsSync)(executable)) {
|
|
2846
|
+
return executable;
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
return null;
|
|
2850
|
+
}
|
|
2851
|
+
return workerCommandExists('google-chrome') ? 'google-chrome' : null;
|
|
2852
|
+
}
|
|
2853
|
+
function collectWorkerInfrastructureFailures() {
|
|
2854
|
+
const failures = [];
|
|
2855
|
+
if (!resolveInstalledChromeExecutable()) {
|
|
2856
|
+
failures.push('worker_preflight_google_chrome_missing');
|
|
2857
|
+
}
|
|
2858
|
+
try {
|
|
2859
|
+
(0, runtime_1.resolvePlaywrightMcpCliPath)();
|
|
2860
|
+
}
|
|
2861
|
+
catch (error) {
|
|
2862
|
+
failures.push(errorCode(error));
|
|
2863
|
+
}
|
|
2864
|
+
if (node_process_1.default.platform !== 'darwin') {
|
|
2865
|
+
return failures;
|
|
2866
|
+
}
|
|
2867
|
+
if (node_process_1.default.arch !== recorderRelease_1.MAC_WORKER_RECORDER_RELEASE.architecture) {
|
|
2868
|
+
failures.push('worker_preflight_recorder_architecture_unsupported');
|
|
2869
|
+
return failures;
|
|
2870
|
+
}
|
|
2871
|
+
if (!workerCommandExists('ffmpeg')) {
|
|
2872
|
+
failures.push('worker_preflight_ffmpeg_missing');
|
|
2873
|
+
}
|
|
2874
|
+
if (!workerCommandExists('ffprobe')) {
|
|
2875
|
+
failures.push('worker_preflight_ffprobe_missing');
|
|
2876
|
+
}
|
|
2877
|
+
const guiSession = (0, shellProbe_1.runShell)('/bin/launchctl print "gui/$(/usr/bin/id -u)"');
|
|
2878
|
+
if (guiSession.exitCode !== 0) {
|
|
2879
|
+
failures.push('worker_preflight_gui_session_missing');
|
|
2880
|
+
}
|
|
2881
|
+
const recorderPath = (0, recorderRelease_1.resolveMacWorkerRecorderPath)();
|
|
2882
|
+
if (!(0, node_fs_1.existsSync)(recorderPath)) {
|
|
2883
|
+
failures.push('worker_preflight_recorder_missing');
|
|
2884
|
+
return failures;
|
|
2885
|
+
}
|
|
2886
|
+
const recorderStat = (0, node_fs_1.statSync)(recorderPath);
|
|
2887
|
+
if (!recorderStat.isFile() || (recorderStat.mode & 0o111) === 0) {
|
|
2888
|
+
failures.push('worker_preflight_recorder_not_executable');
|
|
2889
|
+
return failures;
|
|
2890
|
+
}
|
|
2891
|
+
const installedSha256 = (0, recorderRelease_1.recorderSha256)((0, node_fs_1.readFileSync)(recorderPath));
|
|
2892
|
+
if (installedSha256 !== recorderRelease_1.MAC_WORKER_RECORDER_RELEASE.sha256) {
|
|
2893
|
+
failures.push('worker_preflight_recorder_hash_mismatch');
|
|
2894
|
+
return failures;
|
|
2895
|
+
}
|
|
2896
|
+
const permission = (0, node_child_process_1.spawnSync)(recorderPath, ['--check-permission'], {
|
|
2897
|
+
encoding: 'utf8',
|
|
2898
|
+
timeout: 10000,
|
|
2899
|
+
});
|
|
2900
|
+
if (permission.status !== 0) {
|
|
2901
|
+
failures.push('worker_preflight_screen_recording_permission_missing');
|
|
2902
|
+
}
|
|
2903
|
+
return failures;
|
|
2904
|
+
}
|
|
2905
|
+
function describeWorkerPreflightReason(reason) {
|
|
2906
|
+
if (reason.startsWith('worker_preflight_recorder_download_failed:')) {
|
|
2907
|
+
return `The PlayDrop macOS recorder could not be downloaded: ${reason.slice('worker_preflight_recorder_download_failed:'.length).trim()}`;
|
|
2908
|
+
}
|
|
2909
|
+
const descriptions = {
|
|
2910
|
+
worker_preflight_google_chrome_missing: 'Google Chrome is missing from its standard installation path.',
|
|
2911
|
+
worker_preflight_playwright_missing: 'The PlayDrop CLI cannot load Playwright.',
|
|
2912
|
+
worker_preflight_chromium_missing: 'The Chromium executable required by Playwright is missing.',
|
|
2913
|
+
worker_preflight_playwright_mcp_missing: 'The pinned Playwright MCP package is missing from the PlayDrop CLI installation.',
|
|
2914
|
+
worker_preflight_playwright_mcp_version: 'The installed Playwright MCP package does not match the PlayDrop CLI release.',
|
|
2915
|
+
worker_preflight_ffmpeg_missing: 'ffmpeg is missing from PATH.',
|
|
2916
|
+
worker_preflight_ffprobe_missing: 'ffprobe is missing from PATH.',
|
|
2917
|
+
worker_preflight_gui_session_missing: 'No active macOS GUI session is available.',
|
|
2918
|
+
worker_preflight_recorder_architecture_unsupported: 'The native recorder is currently available only for Apple silicon Macs.',
|
|
2919
|
+
worker_preflight_recorder_missing: 'The PlayDrop macOS recorder is missing.',
|
|
2920
|
+
worker_preflight_recorder_not_executable: 'The PlayDrop macOS recorder is not executable.',
|
|
2921
|
+
worker_preflight_recorder_hash_mismatch: 'The PlayDrop macOS recorder does not match this CLI release.',
|
|
2922
|
+
worker_preflight_screen_recording_permission_missing: 'Screen Recording permission is not granted to the PlayDrop recorder.',
|
|
2923
|
+
codex_not_authenticated: 'Codex is installed but not authenticated.',
|
|
2924
|
+
codex_models_unavailable: 'Codex is authenticated but reported no usable models.',
|
|
2925
|
+
claude_not_authenticated: 'Claude Code is installed but not authenticated.',
|
|
2926
|
+
claude_models_unavailable: 'Claude Code is authenticated but reported no usable models.',
|
|
2927
|
+
agent_cli_not_found: 'No supported authenticated agent CLI is installed.',
|
|
2928
|
+
};
|
|
2929
|
+
return descriptions[reason] ?? reason;
|
|
2930
|
+
}
|
|
2931
|
+
function formatWorkerPreflightFailure(reasons) {
|
|
2932
|
+
const uniqueReasons = Array.from(new Set(reasons));
|
|
2933
|
+
return new Error(`worker_preflight_failed:\n${uniqueReasons.map((reason) => `- ${describeWorkerPreflightReason(reason)}`).join('\n')}`);
|
|
2934
|
+
}
|
|
2836
2935
|
function readWorkerTargetUpdateFailure() {
|
|
2837
2936
|
const failureFile = node_process_1.default.env.PLAYDROP_WORKER_TARGET_UPDATE_FAILURE_FILE?.trim() ?? '';
|
|
2838
2937
|
if (!failureFile || !(0, node_fs_1.existsSync)(failureFile)) {
|
|
@@ -2858,7 +2957,7 @@ function collectWorkerLocalStatus() {
|
|
|
2858
2957
|
let claudeVersion = null;
|
|
2859
2958
|
let claudeModels = [];
|
|
2860
2959
|
let cursorVersion = null;
|
|
2861
|
-
|
|
2960
|
+
const npmVersion = null;
|
|
2862
2961
|
let playwright = null;
|
|
2863
2962
|
let targetUpdateFailure = null;
|
|
2864
2963
|
try {
|
|
@@ -2881,12 +2980,6 @@ function collectWorkerLocalStatus() {
|
|
|
2881
2980
|
catch (error) {
|
|
2882
2981
|
degradedReasons.push(errorCode(error));
|
|
2883
2982
|
}
|
|
2884
|
-
try {
|
|
2885
|
-
npmVersion = probeNpmVersion();
|
|
2886
|
-
}
|
|
2887
|
-
catch (error) {
|
|
2888
|
-
degradedReasons.push(errorCode(error));
|
|
2889
|
-
}
|
|
2890
2983
|
try {
|
|
2891
2984
|
playwright = probePlaywrightChromium();
|
|
2892
2985
|
if (!playwright.chromiumInstalled) {
|
|
@@ -2896,6 +2989,7 @@ function collectWorkerLocalStatus() {
|
|
|
2896
2989
|
catch (error) {
|
|
2897
2990
|
degradedReasons.push(errorCode(error));
|
|
2898
2991
|
}
|
|
2992
|
+
degradedReasons.push(...collectWorkerInfrastructureFailures());
|
|
2899
2993
|
try {
|
|
2900
2994
|
targetUpdateFailure = readWorkerTargetUpdateFailure();
|
|
2901
2995
|
}
|
|
@@ -2929,7 +3023,8 @@ function collectWorkerLocalStatus() {
|
|
|
2929
3023
|
degradedReasons.push(errorCode(error));
|
|
2930
3024
|
}
|
|
2931
3025
|
const uniqueReasons = Array.from(new Set(degradedReasons));
|
|
2932
|
-
const
|
|
3026
|
+
const infrastructureFailures = degradedReasons.filter((reason) => reason.startsWith('worker_preflight_'));
|
|
3027
|
+
const infrastructureReady = Boolean(playwright?.chromiumInstalled) && infrastructureFailures.length === 0;
|
|
2933
3028
|
const ready = Boolean(capabilities?.ready) && infrastructureReady;
|
|
2934
3029
|
return {
|
|
2935
3030
|
ready,
|
|
@@ -2949,6 +3044,11 @@ function collectWorkerLocalStatus() {
|
|
|
2949
3044
|
function buildWorkerSetupActions(input) {
|
|
2950
3045
|
const reasons = new Set(input.degradedReasons);
|
|
2951
3046
|
const actions = [];
|
|
3047
|
+
const addManualAction = (reason, label) => {
|
|
3048
|
+
if (reasons.has(reason)) {
|
|
3049
|
+
actions.push({ reason, label, message: describeWorkerPreflightReason(reason) });
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
2952
3052
|
if (reasons.has('worker_preflight_chromium_missing')) {
|
|
2953
3053
|
const version = input.playwright?.version && input.playwright.version !== 'unknown'
|
|
2954
3054
|
? `@${input.playwright.version}`
|
|
@@ -2977,6 +3077,17 @@ function buildWorkerSetupActions(input) {
|
|
|
2977
3077
|
message: 'Install and authenticate Codex or Claude Code, then rerun playdrop agents status --json.',
|
|
2978
3078
|
});
|
|
2979
3079
|
}
|
|
3080
|
+
addManualAction('worker_preflight_google_chrome_missing', 'Install Google Chrome');
|
|
3081
|
+
addManualAction('worker_preflight_playwright_mcp_missing', 'Repair the PlayDrop CLI installation');
|
|
3082
|
+
addManualAction('worker_preflight_playwright_mcp_version', 'Repair the PlayDrop CLI installation');
|
|
3083
|
+
addManualAction('worker_preflight_ffmpeg_missing', 'Install ffmpeg');
|
|
3084
|
+
addManualAction('worker_preflight_ffprobe_missing', 'Install ffprobe');
|
|
3085
|
+
addManualAction('worker_preflight_gui_session_missing', 'Sign in to the Mac desktop');
|
|
3086
|
+
addManualAction('worker_preflight_recorder_architecture_unsupported', 'Use an Apple silicon Mac');
|
|
3087
|
+
addManualAction('worker_preflight_recorder_missing', 'Restart the worker to download the recorder');
|
|
3088
|
+
addManualAction('worker_preflight_recorder_not_executable', 'Restart the worker to repair the recorder');
|
|
3089
|
+
addManualAction('worker_preflight_recorder_hash_mismatch', 'Restart the worker to repair the recorder');
|
|
3090
|
+
addManualAction('worker_preflight_screen_recording_permission_missing', 'Grant Screen Recording permission');
|
|
2980
3091
|
return actions;
|
|
2981
3092
|
}
|
|
2982
3093
|
function resolveWorkerStateDir() {
|
|
@@ -3799,7 +3910,7 @@ async function setupWorker(options = {}) {
|
|
|
3799
3910
|
const workerName = explicitWorkerName || `${username} - ${node_os_1.default.hostname()}`;
|
|
3800
3911
|
const local = collectWorkerLocalStatus();
|
|
3801
3912
|
const agentReady = local.agents.some((agent) => agent.ready === true);
|
|
3802
|
-
const baseRuntimeReady = Boolean(agentReady && local.
|
|
3913
|
+
const baseRuntimeReady = Boolean(agentReady && local.playwright?.chromiumInstalled);
|
|
3803
3914
|
if (!baseRuntimeReady) {
|
|
3804
3915
|
throw new Error(`worker_setup_preflight_failed: ${local.degradedReasons.join(', ') || 'local worker runtime is not ready'}`);
|
|
3805
3916
|
}
|
|
@@ -4082,15 +4193,35 @@ async function startWorker(options = {}) {
|
|
|
4082
4193
|
const supervisorLock = acquireWorkerSupervisorLock({ supervisorType, workerName });
|
|
4083
4194
|
try {
|
|
4084
4195
|
await removeStaleAgentRunCredentials();
|
|
4085
|
-
const
|
|
4086
|
-
|
|
4196
|
+
const startupFailures = [];
|
|
4197
|
+
if (node_process_1.default.platform === 'darwin') {
|
|
4198
|
+
try {
|
|
4199
|
+
const recorder = await (0, recorderRelease_1.ensureMacWorkerRecorderInstalled)();
|
|
4200
|
+
if (recorder.downloaded) {
|
|
4201
|
+
console.log(`Installed PlayDrop macOS recorder ${recorderRelease_1.MAC_WORKER_RECORDER_RELEASE.version} at ${recorder.path}.`);
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
catch (error) {
|
|
4205
|
+
startupFailures.push(`worker_preflight_recorder_download_failed:${errorMessage(error)}`);
|
|
4206
|
+
}
|
|
4207
|
+
}
|
|
4208
|
+
else if (node_process_1.default.platform === 'win32') {
|
|
4209
|
+
console.warn('Native listing recording is not available on Windows yet.');
|
|
4210
|
+
}
|
|
4211
|
+
const local = collectWorkerLocalStatus();
|
|
4212
|
+
startupFailures.push(...local.degradedReasons);
|
|
4213
|
+
if (startupFailures.length > 0 || !local.ready || !local.capabilities || !local.playwright?.chromiumInstalled) {
|
|
4214
|
+
throw formatWorkerPreflightFailure(startupFailures);
|
|
4215
|
+
}
|
|
4216
|
+
const npmVersion = local.npmVersion;
|
|
4217
|
+
const playwright = {
|
|
4218
|
+
version: local.playwright.version ?? 'unknown',
|
|
4219
|
+
chromiumInstalled: true,
|
|
4220
|
+
...(local.playwright.executablePath ? { executablePath: local.playwright.executablePath } : {}),
|
|
4221
|
+
};
|
|
4087
4222
|
const workerKey = (0, config_1.getOrCreateWorkerKey)();
|
|
4088
4223
|
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
|
-
});
|
|
4224
|
+
let capabilities = local.capabilities;
|
|
4094
4225
|
let lastCapabilityRefreshAt = Date.now();
|
|
4095
4226
|
writeWorkerRuntimeState({
|
|
4096
4227
|
env,
|
|
@@ -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.1",
|
|
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",
|