@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.
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "0.13.0",
2
+ "version": "0.13.1",
3
3
  "build": 1,
4
- "runtimeSdkVersion": "0.13.0",
4
+ "runtimeSdkVersion": "0.13.1",
5
5
  "runtimeSdkBuild": 1,
6
6
  "clients": {
7
7
  "all": {
@@ -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(baseDir?: string): string;
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(baseDir = __dirname) {
208
- return (0, node_path_1.resolve)(baseDir, '..', '..', '..', '..', 'clients', 'apple', 'playdrop-listing-recorder', '.build', 'release', 'playdrop-listing-recorder');
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}.`, ['Build it with: (cd clients/apple/playdrop-listing-recorder && swift build -c release)'], { command: 'project capture' });
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: 'npx',
719
+ command: process.execPath,
698
720
  args: [
699
- '-y',
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?: {
@@ -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
- let npmVersion = null;
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 infrastructureReady = Boolean(npmVersion && playwright?.chromiumInstalled);
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.npmVersion && local.playwright?.chromiumInstalled);
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 npmVersion = probeNpmVersion();
4086
- const playwright = probePlaywrightChromium();
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 = probeReadyWorkerCapabilities({
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
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "0.13.0",
2
+ "version": "0.13.1",
3
3
  "build": 1,
4
- "runtimeSdkVersion": "0.13.0",
4
+ "runtimeSdkVersion": "0.13.1",
5
5
  "runtimeSdkBuild": 1,
6
6
  "clients": {
7
7
  "all": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playdrop/playdrop-cli",
3
- "version": "0.13.0",
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",