@playdrop/playdrop-cli 0.9.6 → 0.10.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.
Files changed (73) hide show
  1. package/config/client-meta.json +2 -2
  2. package/dist/apiClient.d.ts +10 -0
  3. package/dist/apiClient.js +55 -2
  4. package/dist/appUrls.d.ts +1 -0
  5. package/dist/appUrls.js +9 -0
  6. package/dist/apps/build.js +39 -28
  7. package/dist/apps/index.d.ts +1 -0
  8. package/dist/apps/index.js +2 -0
  9. package/dist/apps/launchCheck.d.ts +2 -0
  10. package/dist/apps/launchCheck.js +31 -6
  11. package/dist/apps/registration.d.ts +1 -0
  12. package/dist/apps/registration.js +1 -0
  13. package/dist/apps/upload.d.ts +1 -0
  14. package/dist/apps/upload.js +4 -17
  15. package/dist/captureRuntime.d.ts +14 -0
  16. package/dist/captureRuntime.js +329 -0
  17. package/dist/catalogue.d.ts +4 -2
  18. package/dist/catalogue.js +50 -7
  19. package/dist/commandContext.js +61 -4
  20. package/dist/commands/capture.d.ts +1 -0
  21. package/dist/commands/capture.js +30 -13
  22. package/dist/commands/captureRemote.d.ts +2 -0
  23. package/dist/commands/captureRemote.js +90 -0
  24. package/dist/commands/create.d.ts +0 -1
  25. package/dist/commands/create.js +2 -151
  26. package/dist/commands/creations.d.ts +0 -13
  27. package/dist/commands/creations.js +0 -141
  28. package/dist/commands/dev.d.ts +2 -1
  29. package/dist/commands/dev.js +23 -6
  30. package/dist/commands/devServer.js +3 -1
  31. package/dist/commands/generation.d.ts +1 -0
  32. package/dist/commands/generation.js +274 -0
  33. package/dist/commands/review.d.ts +46 -0
  34. package/dist/commands/review.js +353 -0
  35. package/dist/commands/upload.d.ts +27 -1
  36. package/dist/commands/upload.js +962 -21
  37. package/dist/commands/validate.js +5 -0
  38. package/dist/commands/worker/runtime.d.ts +81 -0
  39. package/dist/commands/worker/runtime.js +458 -0
  40. package/dist/commands/worker.d.ts +158 -0
  41. package/dist/commands/worker.js +2626 -0
  42. package/dist/config.d.ts +2 -0
  43. package/dist/config.js +23 -0
  44. package/dist/index.js +116 -30
  45. package/dist/shellProbe.d.ts +1 -1
  46. package/dist/shellProbe.js +3 -3
  47. package/dist/workspaceAuth.d.ts +3 -0
  48. package/dist/workspaceAuth.js +14 -0
  49. package/node_modules/@playdrop/api-client/dist/client.d.ts +36 -15
  50. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  51. package/node_modules/@playdrop/api-client/dist/client.js +2 -2
  52. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +5 -2
  53. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  54. package/node_modules/@playdrop/api-client/dist/domains/admin.js +51 -3
  55. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +75 -0
  56. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -0
  57. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +478 -0
  58. package/node_modules/@playdrop/api-client/dist/index.d.ts +36 -15
  59. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  60. package/node_modules/@playdrop/api-client/dist/index.js +153 -42
  61. package/node_modules/@playdrop/config/client-meta.json +2 -2
  62. package/node_modules/@playdrop/types/dist/api.d.ts +662 -75
  63. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  64. package/node_modules/@playdrop/types/dist/api.js +100 -9
  65. package/node_modules/@playdrop/types/dist/app.d.ts +2 -0
  66. package/node_modules/@playdrop/types/dist/app.d.ts.map +1 -1
  67. package/node_modules/@playdrop/types/dist/app.js +3 -0
  68. package/node_modules/@playdrop/types/dist/version.d.ts +1 -0
  69. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  70. package/package.json +2 -1
  71. package/node_modules/@playdrop/api-client/dist/domains/game-ideas.d.ts +0 -46
  72. package/node_modules/@playdrop/api-client/dist/domains/game-ideas.d.ts.map +0 -1
  73. package/node_modules/@playdrop/api-client/dist/domains/game-ideas.js +0 -177
@@ -10,6 +10,7 @@ const uploadLog_1 = require("../uploadLog");
10
10
  const apps_1 = require("../apps");
11
11
  const assetSpecs_1 = require("../assetSpecs");
12
12
  const externalAssetPackValidation_1 = require("../externalAssetPackValidation");
13
+ const dev_1 = require("./dev");
13
14
  const upload_content_1 = require("./upload-content");
14
15
  function buildLocalAssetSpecLookups(tasks) {
15
16
  const exactByRef = new Map();
@@ -114,6 +115,8 @@ async function loadHostedLaunchValidationContext(workspacePath, envOverride) {
114
115
  apiBase: ctx.envConfig.apiBase,
115
116
  webBase: ctx.envConfig.webBase ?? null,
116
117
  token: ctx.token,
118
+ devRouterPort: (0, dev_1.resolveDevRouterPort)(process.env.PLAYDROP_DEV_ROUTER_PORT),
119
+ taskScoped: Boolean(ctx.workspaceAuth?.config.taskToken),
117
120
  };
118
121
  }
119
122
  function printResolvedValidationAccount(ctx) {
@@ -250,6 +253,8 @@ async function validate(pathOrName, options = {}) {
250
253
  task,
251
254
  token: launchContext.token,
252
255
  currentUser: launchContext.currentUser,
256
+ devRouterPort: launchContext.devRouterPort,
257
+ allowUnregisteredViewerLaunch: launchContext.taskScoped,
253
258
  });
254
259
  if (launchCheck.status !== 'PASSED') {
255
260
  throw new Error((0, apps_1.formatHostedLaunchCheckFailure)(task.name, launchCheck, 'local'));
@@ -0,0 +1,81 @@
1
+ export declare const DEFAULT_CODEX_TIMEOUT_MS: number;
2
+ export declare const DEFAULT_WORKER_TOKEN_CAP = 20000000;
3
+ export declare const DEFAULT_CODEX_LOG_TAIL_CHARS = 12000;
4
+ export declare const DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS = 10000;
5
+ export type LoggedProcessResult = {
6
+ exitCode: number | null;
7
+ signal: NodeJS.Signals | null;
8
+ stdout: string;
9
+ stderr: string;
10
+ outputTail: string;
11
+ timedOut: boolean;
12
+ tokenUsage: AgentTokenUsage;
13
+ tokensUsed: number | null;
14
+ };
15
+ export type AgentTokenUsage = {
16
+ inputTokens: number | null;
17
+ outputTokens: number | null;
18
+ cacheCreationInputTokens: number | null;
19
+ cacheReadInputTokens: number | null;
20
+ totalTokens: number | null;
21
+ rawProviderUsage: Record<string, unknown> | null;
22
+ usageParseError: string | null;
23
+ };
24
+ export type LoggedProcessTranscriptChunk = {
25
+ stream: 'stdout' | 'stderr';
26
+ content: string;
27
+ };
28
+ export declare function readPositiveEnvInt(name: string, fallback: number): number;
29
+ export declare function readEnvBoolean(name: string, fallback: boolean): boolean;
30
+ export declare function extractAgentTokenUsage(output: string): AgentTokenUsage;
31
+ export declare function extractCodexTokensUsed(output: string): number | null;
32
+ export declare function assertWorkerTokenUsageWithinCap(input: {
33
+ tokensUsed: number | null;
34
+ tokenCap: number;
35
+ }): boolean;
36
+ export declare function runLoggedProcess(input: {
37
+ command: string;
38
+ args: string[];
39
+ cwd: string;
40
+ env: NodeJS.ProcessEnv;
41
+ stdin?: string;
42
+ timeoutMs: number;
43
+ maxOutputChars: number;
44
+ transcriptFlushIntervalMs?: number;
45
+ onTranscriptChunks?: (chunks: LoggedProcessTranscriptChunk[]) => Promise<void> | void;
46
+ onChild?: (controls: {
47
+ terminate: () => void;
48
+ }) => void;
49
+ }): Promise<LoggedProcessResult>;
50
+ export declare function buildCodexExecArgs(input: {
51
+ workspaceDir: string;
52
+ model: string;
53
+ reasoningEffort: string;
54
+ networkAccess: boolean;
55
+ sandboxMode?: CodexSandboxMode;
56
+ }): string[];
57
+ export type CodexSandboxMode = 'danger-full-access' | 'workspace-write' | 'read-only';
58
+ export declare function readCodexSandboxMode(env?: NodeJS.ProcessEnv): CodexSandboxMode;
59
+ export declare function buildClaudeExecArgs(input: {
60
+ model: string;
61
+ effort?: string | null;
62
+ workspaceDir?: string | null;
63
+ denyReadRoots?: string[] | null;
64
+ pluginDir?: string | null;
65
+ }): string[];
66
+ export declare function buildClaudePermissionSettings(denyReadRoots?: string[]): {
67
+ permissions: {
68
+ allow: string[];
69
+ deny: string[];
70
+ };
71
+ };
72
+ export declare function buildClaudeDeniedPermissionRules(denyReadRoots?: string[]): string[];
73
+ export declare function buildWorkerChildEnv(input: {
74
+ binDir: string;
75
+ taskId: number;
76
+ attempt: number;
77
+ envName: string;
78
+ eventDir?: string;
79
+ devPort?: number;
80
+ baseEnv?: NodeJS.ProcessEnv;
81
+ }): NodeJS.ProcessEnv;
@@ -0,0 +1,458 @@
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.DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS = exports.DEFAULT_CODEX_LOG_TAIL_CHARS = exports.DEFAULT_WORKER_TOKEN_CAP = exports.DEFAULT_CODEX_TIMEOUT_MS = void 0;
7
+ exports.readPositiveEnvInt = readPositiveEnvInt;
8
+ exports.readEnvBoolean = readEnvBoolean;
9
+ exports.extractAgentTokenUsage = extractAgentTokenUsage;
10
+ exports.extractCodexTokensUsed = extractCodexTokensUsed;
11
+ exports.assertWorkerTokenUsageWithinCap = assertWorkerTokenUsageWithinCap;
12
+ exports.runLoggedProcess = runLoggedProcess;
13
+ exports.buildCodexExecArgs = buildCodexExecArgs;
14
+ exports.readCodexSandboxMode = readCodexSandboxMode;
15
+ exports.buildClaudeExecArgs = buildClaudeExecArgs;
16
+ exports.buildClaudePermissionSettings = buildClaudePermissionSettings;
17
+ exports.buildClaudeDeniedPermissionRules = buildClaudeDeniedPermissionRules;
18
+ exports.buildWorkerChildEnv = buildWorkerChildEnv;
19
+ const node_child_process_1 = require("node:child_process");
20
+ const node_path_1 = __importDefault(require("node:path"));
21
+ const CHILD_KILL_GRACE_MS = 10000;
22
+ exports.DEFAULT_CODEX_TIMEOUT_MS = 2 * 60 * 60 * 1000;
23
+ exports.DEFAULT_WORKER_TOKEN_CAP = 20000000;
24
+ exports.DEFAULT_CODEX_LOG_TAIL_CHARS = 12000;
25
+ exports.DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS = 10000;
26
+ function tailText(value, maxChars) {
27
+ if (value.length <= maxChars) {
28
+ return value;
29
+ }
30
+ return value.slice(value.length - maxChars);
31
+ }
32
+ function readPositiveEnvInt(name, fallback) {
33
+ const raw = process.env[name]?.trim();
34
+ if (!raw) {
35
+ return fallback;
36
+ }
37
+ const parsed = Number.parseInt(raw, 10);
38
+ if (!Number.isInteger(parsed) || parsed <= 0) {
39
+ throw new Error(`invalid_${name.toLowerCase()}`);
40
+ }
41
+ return parsed;
42
+ }
43
+ function readEnvBoolean(name, fallback) {
44
+ const raw = process.env[name]?.trim().toLowerCase();
45
+ if (!raw) {
46
+ return fallback;
47
+ }
48
+ if (raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on') {
49
+ return true;
50
+ }
51
+ if (raw === '0' || raw === 'false' || raw === 'no' || raw === 'off') {
52
+ return false;
53
+ }
54
+ throw new Error(`invalid_${name.toLowerCase()}`);
55
+ }
56
+ function normalizeTokenCount(value) {
57
+ if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
58
+ return null;
59
+ }
60
+ return Math.ceil(value);
61
+ }
62
+ function coerceRecord(value) {
63
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
64
+ return null;
65
+ }
66
+ return value;
67
+ }
68
+ function readStructuredUsage(parsed) {
69
+ const root = coerceRecord(parsed);
70
+ if (!root) {
71
+ return null;
72
+ }
73
+ const directUsage = coerceRecord(root.usage);
74
+ if (directUsage) {
75
+ return directUsage;
76
+ }
77
+ const message = coerceRecord(root.message);
78
+ const messageUsage = coerceRecord(message?.usage);
79
+ if (messageUsage) {
80
+ return messageUsage;
81
+ }
82
+ const event = coerceRecord(root.event);
83
+ const eventUsage = coerceRecord(event?.usage);
84
+ if (eventUsage) {
85
+ return eventUsage;
86
+ }
87
+ const eventMessage = coerceRecord(event?.message);
88
+ const eventMessageUsage = coerceRecord(eventMessage?.usage);
89
+ if (eventMessageUsage) {
90
+ return eventMessageUsage;
91
+ }
92
+ return null;
93
+ }
94
+ function extractAgentTokenUsage(output) {
95
+ for (const line of output.split(/\r?\n/).reverse()) {
96
+ const trimmed = line.trim();
97
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
98
+ continue;
99
+ }
100
+ try {
101
+ const parsed = JSON.parse(trimmed);
102
+ const usage = readStructuredUsage(parsed);
103
+ if (!usage) {
104
+ continue;
105
+ }
106
+ const inputTokens = normalizeTokenCount(usage.input_tokens);
107
+ const outputTokens = normalizeTokenCount(usage.output_tokens);
108
+ const cacheCreationInputTokens = normalizeTokenCount(usage.cache_creation_input_tokens);
109
+ const cacheReadInputTokens = normalizeTokenCount(usage.cache_read_input_tokens);
110
+ const tokenParts = [
111
+ inputTokens,
112
+ outputTokens,
113
+ cacheCreationInputTokens,
114
+ cacheReadInputTokens,
115
+ ];
116
+ const sawTokenValue = tokenParts.some((value) => value !== null);
117
+ if (sawTokenValue) {
118
+ return {
119
+ inputTokens,
120
+ outputTokens,
121
+ cacheCreationInputTokens,
122
+ cacheReadInputTokens,
123
+ totalTokens: tokenParts.reduce((sum, value) => sum + (value ?? 0), 0),
124
+ rawProviderUsage: usage,
125
+ usageParseError: null,
126
+ };
127
+ }
128
+ }
129
+ catch {
130
+ continue;
131
+ }
132
+ }
133
+ return {
134
+ inputTokens: null,
135
+ outputTokens: null,
136
+ cacheCreationInputTokens: null,
137
+ cacheReadInputTokens: null,
138
+ totalTokens: null,
139
+ rawProviderUsage: null,
140
+ usageParseError: 'agent_usage_not_reported',
141
+ };
142
+ }
143
+ function extractCodexTokensUsed(output) {
144
+ return extractAgentTokenUsage(output).totalTokens;
145
+ }
146
+ function assertWorkerTokenUsageWithinCap(input) {
147
+ if (input.tokensUsed === null) {
148
+ return false;
149
+ }
150
+ if (input.tokensUsed > input.tokenCap) {
151
+ throw new Error(`token_cap_exceeded:${input.tokensUsed}:${input.tokenCap}`);
152
+ }
153
+ return true;
154
+ }
155
+ async function runLoggedProcess(input) {
156
+ if (!input.command.trim()) {
157
+ throw new Error('invalid_process_command');
158
+ }
159
+ if (!Number.isInteger(input.timeoutMs) || input.timeoutMs <= 0) {
160
+ throw new Error('invalid_process_timeout');
161
+ }
162
+ if (!Number.isInteger(input.maxOutputChars) || input.maxOutputChars <= 0) {
163
+ throw new Error('invalid_process_log_tail');
164
+ }
165
+ const transcriptFlushIntervalMs = input.transcriptFlushIntervalMs ?? exports.DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS;
166
+ if (input.onTranscriptChunks && (!Number.isInteger(transcriptFlushIntervalMs) || transcriptFlushIntervalMs <= 0)) {
167
+ throw new Error('invalid_process_transcript_flush_interval');
168
+ }
169
+ return await new Promise((resolve, reject) => {
170
+ let stdout = '';
171
+ let stderr = '';
172
+ let combined = '';
173
+ let timedOut = false;
174
+ let closed = false;
175
+ let killTimer = null;
176
+ let terminateKillTimer = null;
177
+ let transcriptFlushTimer = null;
178
+ let transcriptFlushError = null;
179
+ let transcriptFlushPromise = Promise.resolve();
180
+ const pendingTranscriptChunks = [];
181
+ const child = (0, node_child_process_1.spawn)(input.command, input.args, {
182
+ cwd: input.cwd,
183
+ stdio: ['pipe', 'pipe', 'pipe'],
184
+ env: input.env,
185
+ });
186
+ child.stdin?.end(input.stdin ?? '');
187
+ if (input.onChild) {
188
+ input.onChild({
189
+ terminate: () => {
190
+ if (closed) {
191
+ return;
192
+ }
193
+ child.kill('SIGTERM');
194
+ terminateKillTimer = setTimeout(() => {
195
+ if (!closed) {
196
+ child.kill('SIGKILL');
197
+ }
198
+ }, CHILD_KILL_GRACE_MS);
199
+ },
200
+ });
201
+ }
202
+ const flushTranscriptChunks = async () => {
203
+ if (!input.onTranscriptChunks || pendingTranscriptChunks.length <= 0) {
204
+ return;
205
+ }
206
+ const chunks = pendingTranscriptChunks.splice(0, pendingTranscriptChunks.length);
207
+ await input.onTranscriptChunks(chunks);
208
+ };
209
+ const queueTranscriptFlush = () => {
210
+ transcriptFlushPromise = transcriptFlushPromise.then(flushTranscriptChunks, flushTranscriptChunks);
211
+ return transcriptFlushPromise;
212
+ };
213
+ const handleTranscriptFlushError = (error) => {
214
+ transcriptFlushError = transcriptFlushError ?? error;
215
+ if (!closed) {
216
+ child.kill('SIGTERM');
217
+ }
218
+ };
219
+ if (input.onTranscriptChunks) {
220
+ transcriptFlushTimer = setInterval(() => {
221
+ queueTranscriptFlush().catch(handleTranscriptFlushError);
222
+ }, transcriptFlushIntervalMs);
223
+ }
224
+ const append = (stream, chunk) => {
225
+ const text = chunk.toString('utf8');
226
+ if (stream === 'stdout') {
227
+ process.stdout.write(text);
228
+ stdout = tailText(stdout + text, input.maxOutputChars);
229
+ }
230
+ else {
231
+ process.stderr.write(text);
232
+ stderr = tailText(stderr + text, input.maxOutputChars);
233
+ }
234
+ if (input.onTranscriptChunks) {
235
+ pendingTranscriptChunks.push({ stream, content: text });
236
+ }
237
+ combined = tailText(combined + text, input.maxOutputChars);
238
+ };
239
+ const timeout = setTimeout(() => {
240
+ timedOut = true;
241
+ child.kill('SIGTERM');
242
+ killTimer = setTimeout(() => {
243
+ if (!closed) {
244
+ child.kill('SIGKILL');
245
+ }
246
+ }, 5000);
247
+ }, input.timeoutMs);
248
+ child.stdout?.on('data', (chunk) => append('stdout', chunk));
249
+ child.stderr?.on('data', (chunk) => append('stderr', chunk));
250
+ child.on('error', reject);
251
+ child.on('close', (code, signal) => {
252
+ closed = true;
253
+ clearTimeout(timeout);
254
+ if (killTimer) {
255
+ clearTimeout(killTimer);
256
+ }
257
+ if (terminateKillTimer) {
258
+ clearTimeout(terminateKillTimer);
259
+ }
260
+ if (transcriptFlushTimer) {
261
+ clearInterval(transcriptFlushTimer);
262
+ }
263
+ void (async () => {
264
+ await queueTranscriptFlush();
265
+ if (transcriptFlushError) {
266
+ throw transcriptFlushError;
267
+ }
268
+ const outputTail = tailText(combined, input.maxOutputChars);
269
+ const tokenUsage = extractAgentTokenUsage(`${stdout}\n${stderr}\n${outputTail}`);
270
+ resolve({
271
+ exitCode: code,
272
+ signal,
273
+ stdout,
274
+ stderr,
275
+ outputTail,
276
+ timedOut,
277
+ tokenUsage,
278
+ tokensUsed: tokenUsage.totalTokens,
279
+ });
280
+ })().catch(reject);
281
+ });
282
+ });
283
+ }
284
+ function buildCodexExecArgs(input) {
285
+ const sandboxMode = input.sandboxMode ?? 'danger-full-access';
286
+ const args = [
287
+ 'exec',
288
+ '--sandbox',
289
+ sandboxMode,
290
+ '--cd',
291
+ input.workspaceDir,
292
+ '--skip-git-repo-check',
293
+ '--ephemeral',
294
+ '--model',
295
+ input.model,
296
+ '-c',
297
+ `model_reasoning_effort="${input.reasoningEffort}"`,
298
+ '-',
299
+ ];
300
+ if (sandboxMode === 'workspace-write') {
301
+ args.splice(args.length - 3, 0, '-c', `sandbox_workspace_write.network_access=${input.networkAccess ? 'true' : 'false'}`);
302
+ }
303
+ return args;
304
+ }
305
+ function readCodexSandboxMode(env = process.env) {
306
+ const raw = env.PLAYDROP_WORKER_CODEX_SANDBOX?.trim();
307
+ if (!raw) {
308
+ return 'danger-full-access';
309
+ }
310
+ if (raw === 'danger-full-access' || raw === 'workspace-write' || raw === 'read-only') {
311
+ return raw;
312
+ }
313
+ throw new Error(`invalid_playdrop_worker_codex_sandbox:${raw}`);
314
+ }
315
+ function buildClaudeExecArgs(input) {
316
+ const model = input.model.trim();
317
+ if (!model) {
318
+ throw new Error('agent_task_assignment_model_missing');
319
+ }
320
+ const permissionSettings = buildClaudePermissionSettings(input.denyReadRoots ?? []);
321
+ const tools = 'Bash,Edit,Write,Read,Glob,Grep';
322
+ const args = [
323
+ '-p',
324
+ '--model',
325
+ model,
326
+ '--output-format',
327
+ 'stream-json',
328
+ '--verbose',
329
+ '--include-partial-messages',
330
+ '--no-session-persistence',
331
+ '--permission-mode',
332
+ 'acceptEdits',
333
+ '--tools',
334
+ tools,
335
+ '--allowedTools',
336
+ ...CLAUDE_ALLOWED_PERMISSION_RULES,
337
+ '--disallowedTools',
338
+ ...buildClaudeDeniedPermissionRules(input.denyReadRoots ?? []),
339
+ '--settings',
340
+ JSON.stringify(permissionSettings),
341
+ ];
342
+ const pluginDir = typeof input.pluginDir === 'string' ? input.pluginDir.trim() : '';
343
+ if (pluginDir) {
344
+ args.push('--plugin-dir', pluginDir);
345
+ }
346
+ const effort = typeof input.effort === 'string' ? input.effort.trim() : '';
347
+ if (effort) {
348
+ args.push('--effort', effort);
349
+ }
350
+ return args;
351
+ }
352
+ const CLAUDE_ALLOWED_PERMISSION_RULES = [
353
+ 'Read',
354
+ 'Edit',
355
+ 'Write',
356
+ 'Glob',
357
+ 'Grep',
358
+ 'Bash',
359
+ 'Bash(playdrop *)',
360
+ 'Bash(./bin/playdrop *)',
361
+ 'Bash(bin/playdrop *)',
362
+ 'Bash(/Users/*/PlayDropWorker/tasks/*/bin/playdrop *)',
363
+ 'Bash(node *)',
364
+ 'Bash(npm *)',
365
+ 'Bash(mkdir *)',
366
+ 'Bash(cp *)',
367
+ 'Bash(mv *)',
368
+ 'Bash(file *)',
369
+ 'Bash(sips *)',
370
+ 'Bash(ls *)',
371
+ 'Bash(pwd)',
372
+ 'Bash(cat *)',
373
+ 'Bash(head *)',
374
+ 'Bash(tail *)',
375
+ 'Bash(rg *)',
376
+ 'Bash(grep *)',
377
+ 'Bash(find *)',
378
+ 'Bash(which *)',
379
+ 'Bash(wc *)',
380
+ 'Bash(stat *)',
381
+ 'Bash(du *)',
382
+ ];
383
+ function normalizeClaudePermissionPath(value) {
384
+ const trimmed = value.trim();
385
+ if (!trimmed) {
386
+ return null;
387
+ }
388
+ return node_path_1.default.resolve(trimmed).replace(/\\/g, '/').replace(/\/+$/, '');
389
+ }
390
+ function buildClaudePermissionSettings(denyReadRoots = []) {
391
+ return {
392
+ permissions: {
393
+ allow: [...CLAUDE_ALLOWED_PERMISSION_RULES],
394
+ deny: buildClaudeDeniedPermissionRules(denyReadRoots),
395
+ },
396
+ };
397
+ }
398
+ function buildClaudeDeniedPermissionRules(denyReadRoots = []) {
399
+ const deny = new Set([
400
+ 'Bash(rm *)',
401
+ 'Bash(git *)',
402
+ ]);
403
+ for (const rawRoot of denyReadRoots) {
404
+ const root = normalizeClaudePermissionPath(rawRoot);
405
+ if (!root) {
406
+ continue;
407
+ }
408
+ deny.add(`Read(//${root.replace(/^\/+/, '')}/**)`);
409
+ deny.add(`Bash(* ${root}*)`);
410
+ }
411
+ return [...deny];
412
+ }
413
+ function buildWorkerChildEnv(input) {
414
+ const base = input.baseEnv ?? process.env;
415
+ const child = {};
416
+ for (const key of ['HOME', 'USER', 'SHELL', 'TMPDIR', 'LANG', 'TERM']) {
417
+ const value = base[key];
418
+ if (typeof value === 'string') {
419
+ child[key] = value;
420
+ }
421
+ }
422
+ for (const [key, value] of Object.entries(base)) {
423
+ if (key.startsWith('LC_') && typeof value === 'string') {
424
+ child[key] = value;
425
+ }
426
+ }
427
+ child.PATH = typeof base.PATH === 'string' && base.PATH
428
+ ? `${input.binDir}${node_path_1.default.delimiter}${base.PATH}`
429
+ : input.binDir;
430
+ for (const key of [
431
+ 'PLAYDROP_API_BASE',
432
+ `PLAYDROP_API_BASE_${input.envName.toUpperCase()}`,
433
+ 'PLAYDROP_AI_BASE',
434
+ `PLAYDROP_AI_BASE_${input.envName.toUpperCase()}`,
435
+ ]) {
436
+ const value = base[key];
437
+ if (typeof value === 'string') {
438
+ child[key] = value;
439
+ }
440
+ }
441
+ child.PLAYDROP_WORKER_CONTEXT = '1';
442
+ if (!Number.isInteger(input.attempt) || input.attempt <= 0) {
443
+ throw new Error('invalid_worker_task_attempt');
444
+ }
445
+ child.PLAYDROP_WORKER_TASK_ID = String(input.taskId);
446
+ child.PLAYDROP_WORKER_TASK_ATTEMPT = String(input.attempt);
447
+ if (input.devPort !== undefined) {
448
+ if (!Number.isInteger(input.devPort) || input.devPort <= 0 || input.devPort > 65535) {
449
+ throw new Error('invalid_worker_task_dev_port');
450
+ }
451
+ child.PLAYDROP_WORKER_TASK_DEV_PORT = String(input.devPort);
452
+ child.PLAYDROP_DEV_ROUTER_PORT = String(input.devPort);
453
+ }
454
+ if (input.eventDir?.trim()) {
455
+ child.PLAYDROP_WORKER_EVENT_DIR = input.eventDir.trim();
456
+ }
457
+ return child;
458
+ }