@playdrop/playdrop-cli 0.10.0 → 0.10.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.
Files changed (34) hide show
  1. package/config/client-meta.json +2 -1
  2. package/dist/apiClient.d.ts +8 -0
  3. package/dist/apiClient.js +29 -1
  4. package/dist/captureRuntime.d.ts +13 -0
  5. package/dist/captureRuntime.js +21 -0
  6. package/dist/commandContext.js +21 -3
  7. package/dist/commands/captureRemote.d.ts +2 -0
  8. package/dist/commands/captureRemote.js +90 -0
  9. package/dist/commands/review.d.ts +46 -0
  10. package/dist/commands/review.js +353 -0
  11. package/dist/commands/validate.js +63 -1
  12. package/dist/commands/worker/runtime.d.ts +12 -0
  13. package/dist/commands/worker/runtime.js +79 -35
  14. package/dist/commands/worker.d.ts +17 -3
  15. package/dist/commands/worker.js +431 -24
  16. package/dist/index.js +45 -0
  17. package/dist/workspaceAuth.d.ts +2 -0
  18. package/dist/workspaceAuth.js +6 -0
  19. package/node_modules/@playdrop/api-client/dist/client.d.ts +6 -2
  20. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  21. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +3 -2
  22. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  23. package/node_modules/@playdrop/api-client/dist/domains/admin.js +6 -3
  24. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +4 -1
  25. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -1
  26. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +36 -0
  27. package/node_modules/@playdrop/api-client/dist/index.d.ts +6 -2
  28. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  29. package/node_modules/@playdrop/api-client/dist/index.js +21 -6
  30. package/node_modules/@playdrop/config/client-meta.json +2 -1
  31. package/node_modules/@playdrop/types/dist/api.d.ts +163 -3
  32. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  33. package/node_modules/@playdrop/types/dist/api.js +11 -1
  34. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  {
2
- "version": "0.10.0",
2
+ "version": "0.10.2",
3
3
  "build": 1,
4
+ "runtimeSdkVersion": "0.10.0",
4
5
  "clients": {
5
6
  "all": {
6
7
  "minimumVersion": "0.7.4"
@@ -5,7 +5,15 @@ type ClientOptions = {
5
5
  token?: string | null;
6
6
  onBehalfCreatorUsername?: string | null;
7
7
  agentTaskToken?: string | null;
8
+ agentTaskId?: number | null;
9
+ agentTaskAttempt?: number | null;
8
10
  };
11
+ export declare function createCliClientHeaders(input: {
12
+ onBehalfCreatorUsername?: string | null;
13
+ agentTaskToken?: string | null;
14
+ agentTaskId?: number | null;
15
+ agentTaskAttempt?: number | null;
16
+ }): Record<string, string>;
9
17
  export declare function createCliApiClient(options: ClientOptions): ApiClient;
10
18
  export declare function createCliAiClient(options: ClientOptions): AiClient;
11
19
  export {};
package/dist/apiClient.js CHANGED
@@ -1,10 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCliClientHeaders = createCliClientHeaders;
3
4
  exports.createCliApiClient = createCliApiClient;
4
5
  exports.createCliAiClient = createCliAiClient;
5
6
  const api_client_1 = require("@playdrop/api-client");
6
7
  const ai_client_1 = require("@playdrop/ai-client");
7
8
  const clientInfo_1 = require("./clientInfo");
9
+ function readPositiveIntEnv(name) {
10
+ const raw = process.env[name]?.trim();
11
+ if (!raw) {
12
+ return null;
13
+ }
14
+ const parsed = Number(raw);
15
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
16
+ }
8
17
  function createCliClientHeaders(input) {
9
18
  const onBehalfCreatorUsername = input.onBehalfCreatorUsername;
10
19
  const creatorUsername = typeof onBehalfCreatorUsername === 'string'
@@ -13,6 +22,15 @@ function createCliClientHeaders(input) {
13
22
  const agentTaskToken = typeof input.agentTaskToken === 'string'
14
23
  ? input.agentTaskToken.trim()
15
24
  : '';
25
+ const agentTaskId = Number.isInteger(input.agentTaskId) && Number(input.agentTaskId) > 0
26
+ ? Number(input.agentTaskId)
27
+ : readPositiveIntEnv('PLAYDROP_WORKER_TASK_ID');
28
+ const agentTaskAttempt = Number.isInteger(input.agentTaskAttempt) && Number(input.agentTaskAttempt) > 0
29
+ ? Number(input.agentTaskAttempt)
30
+ : readPositiveIntEnv('PLAYDROP_WORKER_TASK_ATTEMPT');
31
+ if (agentTaskToken && process.env.PLAYDROP_WORKER_CONTEXT === '1' && (!agentTaskId || !agentTaskAttempt)) {
32
+ throw new Error('worker_ai_generation_task_context_missing');
33
+ }
16
34
  return {
17
35
  ...(0, clientInfo_1.createClientHeaders)(),
18
36
  ...(creatorUsername
@@ -21,7 +39,13 @@ function createCliClientHeaders(input) {
21
39
  'x-playdrop-ai-paid-by': 'creator',
22
40
  }
23
41
  : {}),
24
- ...(agentTaskToken ? { 'x-playdrop-agent-task-token': agentTaskToken } : {}),
42
+ ...(agentTaskToken
43
+ ? {
44
+ 'x-playdrop-agent-task-token': agentTaskToken,
45
+ ...(agentTaskId ? { 'x-playdrop-agent-task-id': String(agentTaskId) } : {}),
46
+ ...(agentTaskAttempt ? { 'x-playdrop-agent-task-attempt': String(agentTaskAttempt) } : {}),
47
+ }
48
+ : {}),
25
49
  };
26
50
  }
27
51
  function createCliApiClient(options) {
@@ -30,6 +54,8 @@ function createCliApiClient(options) {
30
54
  clientHeaders: () => createCliClientHeaders({
31
55
  onBehalfCreatorUsername: options.onBehalfCreatorUsername,
32
56
  agentTaskToken: options.agentTaskToken,
57
+ agentTaskId: options.agentTaskId,
58
+ agentTaskAttempt: options.agentTaskAttempt,
33
59
  }),
34
60
  tokenProvider: () => options.token ?? null,
35
61
  });
@@ -40,6 +66,8 @@ function createCliAiClient(options) {
40
66
  clientHeaders: () => createCliClientHeaders({
41
67
  onBehalfCreatorUsername: options.onBehalfCreatorUsername,
42
68
  agentTaskToken: options.agentTaskToken,
69
+ agentTaskId: options.agentTaskId,
70
+ agentTaskAttempt: options.agentTaskAttempt,
43
71
  }),
44
72
  tokenProvider: () => options.token ?? null,
45
73
  });
@@ -24,8 +24,21 @@ export type RunCaptureOptions = {
24
24
  enableCaptureBridge?: boolean;
25
25
  requireHostedLaunchReady?: boolean;
26
26
  expectedHostedLaunchState?: HostedLaunchExpectedState;
27
+ actions?: CaptureAction[];
27
28
  };
28
29
  export type HostedLaunchExpectedState = 'ready' | 'login_required' | 'controller_required' | 'surface_unsupported';
30
+ export type CaptureAction = {
31
+ type: 'click';
32
+ x: number;
33
+ y: number;
34
+ button?: 'left' | 'right' | 'middle';
35
+ } | {
36
+ type: 'press';
37
+ key: string;
38
+ } | {
39
+ type: 'wait';
40
+ ms: number;
41
+ };
29
42
  export type CaptureHostedLaunchState = {
30
43
  state: 'ready';
31
44
  } | {
@@ -506,6 +506,26 @@ async function summarizeCanvasScreenshots(page, frames, summaries) {
506
506
  }
507
507
  return screenshotSummaries;
508
508
  }
509
+ async function runCaptureActions(page, actions) {
510
+ if (!actions?.length) {
511
+ return;
512
+ }
513
+ for (const action of actions) {
514
+ if (action.type === 'click') {
515
+ await page.mouse.click(action.x, action.y, { button: action.button ?? 'left' });
516
+ continue;
517
+ }
518
+ if (action.type === 'press') {
519
+ await page.keyboard.press(action.key);
520
+ continue;
521
+ }
522
+ if (action.type === 'wait') {
523
+ await page.waitForTimeout(action.ms);
524
+ continue;
525
+ }
526
+ throw new Error('capture_action_type_unknown');
527
+ }
528
+ }
509
529
  async function assertHostedFrameVisualReadiness(page) {
510
530
  const childFrames = page.frames().filter(frame => frame.parentFrame() !== null);
511
531
  if (childFrames.length === 0) {
@@ -922,6 +942,7 @@ async function runCapture(options) {
922
942
  }
923
943
  }
924
944
  finalUrl = await assertPageState();
945
+ await runCaptureActions(page, options.actions);
925
946
  await page.waitForTimeout(options.settleAfterReadyMs ?? options.timeoutMs);
926
947
  finalUrl = await assertPageState();
927
948
  if (shouldAssertHostedVisualReadiness({
@@ -40,8 +40,24 @@ function buildContext(cfg, envConfig, account, workspaceAuth = null) {
40
40
  const token = account?.token ?? cfg.token ?? '';
41
41
  const onBehalfCreatorUsername = workspaceAuth?.config.ownerUsername ?? null;
42
42
  const agentTaskToken = workspaceAuth?.config.taskToken ?? null;
43
- const client = (0, apiClient_1.createCliApiClient)({ baseUrl: envConfig.apiBase, token, onBehalfCreatorUsername, agentTaskToken });
44
- const aiClient = (0, apiClient_1.createCliAiClient)({ baseUrl: envConfig.aiBase, token, onBehalfCreatorUsername, agentTaskToken });
43
+ const agentTaskId = workspaceAuth?.config.taskId ?? null;
44
+ const agentTaskAttempt = workspaceAuth?.config.taskAttempt ?? null;
45
+ const client = (0, apiClient_1.createCliApiClient)({
46
+ baseUrl: envConfig.apiBase,
47
+ token,
48
+ onBehalfCreatorUsername,
49
+ agentTaskToken,
50
+ agentTaskId,
51
+ agentTaskAttempt,
52
+ });
53
+ const aiClient = (0, apiClient_1.createCliAiClient)({
54
+ baseUrl: envConfig.aiBase,
55
+ token,
56
+ onBehalfCreatorUsername,
57
+ agentTaskToken,
58
+ agentTaskId,
59
+ agentTaskAttempt,
60
+ });
45
61
  return {
46
62
  client,
47
63
  aiClient,
@@ -92,8 +108,10 @@ async function migrateLegacyConfigIfNeeded(cfg, envConfig, command) {
92
108
  async function loadWorkspaceAwareConfig(command, options) {
93
109
  let cfg = (0, config_1.loadConfig)();
94
110
  let workspaceAuth = null;
111
+ const workspacePath = options.workspacePath
112
+ ?? (process.env.PLAYDROP_WORKER_CONTEXT === '1' ? process.cwd() : undefined);
95
113
  try {
96
- workspaceAuth = options.workspacePath ? (0, workspaceAuth_1.findWorkspaceAuthConfig)(options.workspacePath) : null;
114
+ workspaceAuth = workspacePath ? (0, workspaceAuth_1.findWorkspaceAuthConfig)(workspacePath) : null;
97
115
  }
98
116
  catch (error) {
99
117
  if (error instanceof workspaceAuth_1.WorkspaceAuthConfigError) {
@@ -6,6 +6,8 @@ type CaptureRemoteOptions = {
6
6
  username?: string;
7
7
  password?: string;
8
8
  loginUrl?: string;
9
+ actions?: string;
10
+ viewport?: string;
9
11
  };
10
12
  export declare function buildCaptureRemoteCommandArgs(url: string | undefined, options?: CaptureRemoteOptions): string[] | null;
11
13
  export declare function captureRemote(url: string | undefined, options?: CaptureRemoteOptions): Promise<void>;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildCaptureRemoteCommandArgs = buildCaptureRemoteCommandArgs;
4
4
  exports.captureRemote = captureRemote;
5
+ const promises_1 = require("node:fs/promises");
5
6
  const node_path_1 = require("node:path");
6
7
  const types_1 = require("@playdrop/types");
7
8
  const commandContext_1 = require("../commandContext");
@@ -42,6 +43,75 @@ function parseTimeout(raw) {
42
43
  }
43
44
  return parsed;
44
45
  }
46
+ function parseViewport(raw) {
47
+ const value = raw?.trim();
48
+ if (!value) {
49
+ return null;
50
+ }
51
+ const match = /^(\d{2,5})x(\d{2,5})$/i.exec(value);
52
+ if (!match) {
53
+ throw new Error('invalid_viewport');
54
+ }
55
+ const width = Number.parseInt(match[1], 10);
56
+ const height = Number.parseInt(match[2], 10);
57
+ if (width < 320 || width > 4096 || height < 320 || height > 4096) {
58
+ throw new Error('invalid_viewport');
59
+ }
60
+ return { width, height };
61
+ }
62
+ function normalizeCaptureAction(raw, index) {
63
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
64
+ throw new Error(`invalid_action:${index}`);
65
+ }
66
+ const action = raw;
67
+ const type = typeof action.type === 'string' ? action.type.trim() : '';
68
+ if (type === 'click') {
69
+ const x = typeof action.x === 'number' ? action.x : Number.NaN;
70
+ const y = typeof action.y === 'number' ? action.y : Number.NaN;
71
+ const button = typeof action.button === 'string' ? action.button.trim() : undefined;
72
+ if (!Number.isFinite(x) || !Number.isFinite(y) || x < 0 || y < 0) {
73
+ throw new Error(`invalid_action:${index}:click_coordinates`);
74
+ }
75
+ if (button !== undefined && button !== 'left' && button !== 'right' && button !== 'middle') {
76
+ throw new Error(`invalid_action:${index}:click_button`);
77
+ }
78
+ return button ? { type: 'click', x, y, button } : { type: 'click', x, y };
79
+ }
80
+ if (type === 'press') {
81
+ const key = typeof action.key === 'string' ? action.key.trim() : '';
82
+ if (!key) {
83
+ throw new Error(`invalid_action:${index}:press_key`);
84
+ }
85
+ return { type: 'press', key };
86
+ }
87
+ if (type === 'wait') {
88
+ const ms = typeof action.ms === 'number' ? action.ms : Number.NaN;
89
+ if (!Number.isInteger(ms) || ms < 0 || ms > 60000) {
90
+ throw new Error(`invalid_action:${index}:wait_ms`);
91
+ }
92
+ return { type: 'wait', ms };
93
+ }
94
+ throw new Error(`invalid_action:${index}:type`);
95
+ }
96
+ async function readCaptureActions(actionsPath) {
97
+ if (!actionsPath) {
98
+ return null;
99
+ }
100
+ let parsed;
101
+ try {
102
+ parsed = JSON.parse(await (0, promises_1.readFile)(actionsPath, 'utf8'));
103
+ }
104
+ catch (error) {
105
+ throw new Error(`invalid_actions_file:${error instanceof Error ? error.message : String(error)}`);
106
+ }
107
+ if (!Array.isArray(parsed)) {
108
+ throw new Error('invalid_actions_file:expected_array');
109
+ }
110
+ if (parsed.length > 100) {
111
+ throw new Error('invalid_actions_file:too_many_actions');
112
+ }
113
+ return parsed.map((entry, index) => normalizeCaptureAction(entry, index));
114
+ }
45
115
  function assertValidUrl(value, errorCode) {
46
116
  try {
47
117
  new URL(value);
@@ -77,6 +147,8 @@ function buildCaptureRemoteCommandArgs(url, options = {}) {
77
147
  pushOptionalArg(args, '--screenshot', options.screenshot);
78
148
  pushOptionalArg(args, '--log', options.log);
79
149
  pushOptionalArg(args, '--expected-url', options.expectedUrl);
150
+ pushOptionalArg(args, '--actions', options.actions);
151
+ pushOptionalArg(args, '--viewport', options.viewport);
80
152
  if (username) {
81
153
  args.push('--username', username, '--password', password);
82
154
  }
@@ -104,6 +176,8 @@ function parseOptions(url, options = {}) {
104
176
  timeoutSeconds: parseTimeout(options.timeout),
105
177
  screenshotPath: (0, node_path_1.resolve)(process.cwd(), options.screenshot?.trim() || 'output/playwright/capture-url.png'),
106
178
  logPath: options.log?.trim() ? (0, node_path_1.resolve)(process.cwd(), options.log.trim()) : null,
179
+ actionsPath: options.actions?.trim() ? (0, node_path_1.resolve)(process.cwd(), options.actions.trim()) : null,
180
+ viewport: parseViewport(options.viewport),
107
181
  login: username
108
182
  ? {
109
183
  username,
@@ -135,6 +209,11 @@ async function captureRemote(url, options = {}) {
135
209
  process.exitCode = 1;
136
210
  return;
137
211
  }
212
+ if (error instanceof Error && error.message === 'invalid_viewport') {
213
+ (0, messages_1.printErrorWithHelp)('The --viewport value must be WIDTHxHEIGHT with each side between 320 and 4096 pixels.', [], { command: 'project capture remote' });
214
+ process.exitCode = 1;
215
+ return;
216
+ }
138
217
  if (error instanceof Error && error.message === 'invalid_credentials_pair') {
139
218
  (0, messages_1.printErrorWithHelp)('Use --username and --password together.', [], { command: 'project capture remote' });
140
219
  process.exitCode = 1;
@@ -149,6 +228,15 @@ async function captureRemote(url, options = {}) {
149
228
  process.exitCode = 1;
150
229
  return;
151
230
  }
231
+ let actions = null;
232
+ try {
233
+ actions = await readCaptureActions(parsed.actionsPath);
234
+ }
235
+ catch (error) {
236
+ (0, messages_1.printErrorWithHelp)(error instanceof Error ? error.message : String(error), [], { command: 'project capture remote' });
237
+ process.exitCode = 1;
238
+ return;
239
+ }
152
240
  await (0, commandContext_1.withEnvironment)('project capture remote', 'Capturing remote app logs', async ({ client, token }) => {
153
241
  let currentUser = null;
154
242
  try {
@@ -190,6 +278,8 @@ async function captureRemote(url, options = {}) {
190
278
  token: parsed.login ? undefined : token,
191
279
  user: parsed.login ? undefined : currentUser,
192
280
  login: parsed.login,
281
+ actions: actions ?? undefined,
282
+ contextOptions: parsed.viewport ? { viewport: parsed.viewport } : undefined,
193
283
  });
194
284
  if (result.errorCount > 0) {
195
285
  console.error(`[capture] Completed with ${result.errorCount} error(s) after ${parsed.timeoutSeconds} seconds.`);
@@ -0,0 +1,46 @@
1
+ export declare const REVIEW_CRITERIA: readonly ["Gameplay / Core Loop", "Depth / Replayability", "Controls / Input", "UX / Usability", "First Time User Experience", "Visuals / Art Direction", "Audio / Feedback", "Store Listing & Metadata Accuracy", "Safety / Age Rating / Compliance", "Performance / Stability"];
2
+ export declare const REQUIRED_REVIEW_EVIDENCE_FILES: string[];
3
+ export type ValidateGameReviewResultInput = {
4
+ creatorFeedback?: string;
5
+ evidenceDir: string;
6
+ reviewMessage: string;
7
+ reviewState: string;
8
+ };
9
+ export type ReviewValidationResult = {
10
+ average?: number;
11
+ evidenceDir?: string;
12
+ outcome?: string;
13
+ primarySurface?: string;
14
+ reason?: string;
15
+ scoreCount?: number;
16
+ skipped: boolean;
17
+ };
18
+ export type ReviewValidateResultOptions = {
19
+ creatorFeedbackFile?: string;
20
+ evidenceDir: string;
21
+ messageFile: string;
22
+ state: string;
23
+ };
24
+ export type ReviewComposeEvidenceOptions = {
25
+ core?: string;
26
+ win?: string;
27
+ loss?: string;
28
+ out: string;
29
+ };
30
+ export type ReviewRatingCardOptions = {
31
+ reviewMessageFile: string;
32
+ out: string;
33
+ punchline?: string;
34
+ title?: string;
35
+ };
36
+ export declare function validateGameReviewResult(input: ValidateGameReviewResultInput): Promise<ReviewValidationResult>;
37
+ export declare function validateReviewResultCommand(options: ReviewValidateResultOptions): Promise<void>;
38
+ export declare function composeReviewEvidence(options: ReviewComposeEvidenceOptions): Promise<{
39
+ outputPath: string;
40
+ imageCount: number;
41
+ }>;
42
+ export declare function createReviewRatingCard(options: ReviewRatingCardOptions): Promise<{
43
+ outputPath: string;
44
+ width: number;
45
+ height: number;
46
+ }>;