@ricsam/r5d-worker 0.0.2 → 0.0.3

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/dist/mjs/main.mjs CHANGED
@@ -4,7 +4,7 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { createHash } from "node:crypto";
6
6
  import { hostname } from "node:os";
7
- import * as nodePty from "node-pty";
7
+ import { spawn as spawnChildProcess } from "node:child_process";
8
8
  const DEFAULT_BASE_URL = "https://r5d.dev";
9
9
  const LABEL_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,62}$/;
10
10
  const BRANCH_RE = /^[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9]$|^[A-Za-z0-9]$/;
@@ -17,20 +17,24 @@ const branchLocks = /* @__PURE__ */ new Map();
17
17
  function defaultConfigPath() {
18
18
  return path.join(os.homedir(), ".config", "r5d", "r5dctl", "config.json");
19
19
  }
20
- function defaultProjectRoot(projectId) {
21
- return path.join(os.homedir(), ".r5d", "projects", projectId);
20
+ function defaultProjectsRoot() {
21
+ return path.join(os.homedir(), ".r5d", "projects");
22
+ }
23
+ function defaultSyncRoot() {
24
+ return path.join(os.homedir(), ".r5d", "sync");
22
25
  }
23
26
  function printHelp() {
24
27
  process.stdout.write(`Usage:
25
- r5d-worker start <project-id> --label <label> [--base-url <url>] [--token <token>] [--api-key <key>]
28
+ r5d-worker start --label <label> [--base-url <url>] [--token <token>] [--api-key <key>]
26
29
 
27
30
  Options:
28
- --label <label> Required unique worker label for this project, e.g. macos or ec2-build
31
+ --label <label> Required unique worker label for this user, e.g. macos or ec2-build
29
32
  --base-url <url> r5d.dev base URL (default from r5d config, R5D_BASE_URL, or ${DEFAULT_BASE_URL})
30
33
  --token <token> r5d worker token
31
34
  --api-key <key> r5d API key
32
35
  --config <path> Config path (default ~/.config/r5d/r5dctl/config.json)
33
- --project-root <dir> Override checkout root (default ~/.r5d/projects/<project-id>)
36
+ --project-root <dir> Override projects checkout root (default ~/.r5d/projects)
37
+ --sync-root <dir> Override r5d internal sync git root (default ~/.r5d/sync)
34
38
  -h, --help Show this help
35
39
  `);
36
40
  }
@@ -93,6 +97,11 @@ function parseArgs(argv) {
93
97
  options.projectRoot = projectRoot;
94
98
  continue;
95
99
  }
100
+ const syncRoot = readOption("--sync-root");
101
+ if (syncRoot !== void 0) {
102
+ options.syncRoot = syncRoot;
103
+ continue;
104
+ }
96
105
  rest.push(arg);
97
106
  }
98
107
  if (options.help || rest.length === 0) {
@@ -104,7 +113,6 @@ function parseArgs(argv) {
104
113
  }
105
114
  return {
106
115
  command: "start",
107
- projectId: requireValue(rest[1], "Missing project id"),
108
116
  options
109
117
  };
110
118
  }
@@ -182,17 +190,14 @@ function validateBranchName(branchName) {
182
190
  throw new Error(`Invalid branch name from server: ${branchName}`);
183
191
  }
184
192
  }
185
- function gitRemoteUrl(baseUrl, projectId) {
186
- return `${baseUrl}/git/${projectId}.git`;
187
- }
188
- function gitExtraHeaderConfigKey(baseUrl) {
189
- return `http.${normalizeBaseUrl(baseUrl)}/.extraHeader`;
193
+ function gitExtraHeaderConfigKey(extraHeaderUrl) {
194
+ return `http.${normalizeBaseUrl(extraHeaderUrl)}/.extraHeader`;
190
195
  }
191
196
  function gitAuthArgs(auth) {
192
197
  if (!auth) {
193
198
  return [];
194
199
  }
195
- return ["-c", `${gitExtraHeaderConfigKey(auth.baseUrl)}=Authorization: Bearer ${auth.token}`];
200
+ return ["-c", `${gitExtraHeaderConfigKey(auth.extraHeaderUrl)}=${auth.header}`];
196
201
  }
197
202
  function runGit(args, options = {}) {
198
203
  const command = ["git", ...gitAuthArgs(options.auth), ...args];
@@ -220,8 +225,22 @@ function tryGit(args, options = {}) {
220
225
  return false;
221
226
  }
222
227
  }
223
- function getCurrentCommitHash(cwd) {
224
- return runGit(["rev-parse", "HEAD"], { cwd });
228
+ function runInternalGit(context, args) {
229
+ return runGit(["--git-dir", context.gitDir, "--work-tree", context.workTree, ...args], { auth: context.auth });
230
+ }
231
+ function runInternalGitDir(context, args) {
232
+ return runGit(["--git-dir", context.gitDir, ...args], { auth: context.auth });
233
+ }
234
+ function tryInternalGit(context, args) {
235
+ try {
236
+ runInternalGit(context, args);
237
+ return true;
238
+ } catch {
239
+ return false;
240
+ }
241
+ }
242
+ function getInternalCommitHash(context) {
243
+ return runInternalGit(context, ["rev-parse", "HEAD"]);
225
244
  }
226
245
  function getGitBlobHashForContent(content) {
227
246
  const buffer = typeof content === "string" ? Buffer.from(content, "utf8") : content;
@@ -230,6 +249,12 @@ function getGitBlobHashForContent(content) {
230
249
  function hasWorktreeChanges(cwd) {
231
250
  return runGit(["status", "--porcelain"], { cwd }).trim().length > 0;
232
251
  }
252
+ function getInternalStatus(context) {
253
+ return runInternalGit(context, ["status", "--porcelain", "--", ".", ":(exclude).git"]);
254
+ }
255
+ function hasInternalWorktreeChanges(context) {
256
+ return getInternalStatus(context).trim().length > 0;
257
+ }
233
258
  function assertInsideBranch(branchPath, filePath) {
234
259
  const relative = path.relative(branchPath, filePath);
235
260
  if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
@@ -252,11 +277,11 @@ function resolveProjectFilePath(branchPath, virtualPath) {
252
277
  assertInsideBranch(branchPath, absolutePath);
253
278
  return { absolutePath, virtualPath: `/${repoRelativePath}`, repoRelativePath };
254
279
  }
255
- function branchLockKey(projectRoot, branchName) {
256
- return `${projectRoot}:${branchName}`;
280
+ function branchLockKey(projectId, branchName) {
281
+ return `${projectId}:${branchName}`;
257
282
  }
258
- async function withBranchLock(projectRoot, branchName, fn) {
259
- const key = branchLockKey(projectRoot, branchName);
283
+ async function withBranchLock(projectId, branchName, fn) {
284
+ const key = branchLockKey(projectId, branchName);
260
285
  const previous = branchLocks.get(key) ?? Promise.resolve();
261
286
  let release = () => {
262
287
  };
@@ -277,60 +302,221 @@ async function withBranchLock(projectRoot, branchName, fn) {
277
302
  }
278
303
  }
279
304
  }
280
- function configureRepo(pathName, auth) {
281
- const authHeaderKey = gitExtraHeaderConfigKey(auth.baseUrl);
282
- tryGit(["config", "--unset-all", "http.extraHeader"], { cwd: pathName });
283
- tryGit(["config", "--unset-all", authHeaderKey], { cwd: pathName });
284
- runGit(["config", "--add", authHeaderKey, `Authorization: Bearer ${auth.token}`], { cwd: pathName });
285
- runGit(["config", "user.name", "r5d.dev Worker"], { cwd: pathName });
286
- runGit(["config", "user.email", "worker@r5d.dev"], { cwd: pathName });
305
+ function resolveRemoteUrl(baseUrl, remoteUrl) {
306
+ if (/^https?:\/\//i.test(remoteUrl)) {
307
+ return remoteUrl;
308
+ }
309
+ return new URL(remoteUrl, baseUrl).toString();
287
310
  }
288
- function ensureMainClone(input) {
289
- const mainPath = path.join(input.projectRoot, "main");
290
- const remoteUrl = gitRemoteUrl(input.baseUrl, input.projectId);
291
- fs.mkdirSync(input.projectRoot, { recursive: true });
292
- if (!fs.existsSync(path.join(mainPath, ".git"))) {
293
- fs.rmSync(mainPath, { recursive: true, force: true });
294
- runGit(["clone", "--origin", "build-it-now", remoteUrl, mainPath], { auth: input });
311
+ function fallbackInternalRemoteUrl(baseUrl, projectId) {
312
+ return new URL(`/git/${projectId}.git`, baseUrl).toString();
313
+ }
314
+ function internalGitAuth(baseUrl, token) {
315
+ return {
316
+ extraHeaderUrl: baseUrl,
317
+ header: `Authorization: Bearer ${token}`
318
+ };
319
+ }
320
+ function internalRemoteUrlFor(baseUrl, projectId, manifest) {
321
+ return resolveRemoteUrl(baseUrl, manifest?.internalRemoteUrl ?? fallbackInternalRemoteUrl(baseUrl, projectId));
322
+ }
323
+ function githubRemoteUrlFor(baseUrl, projectId, manifest) {
324
+ return manifest?.repoHttpUrl ?? internalRemoteUrlFor(baseUrl, projectId, manifest);
325
+ }
326
+ function gitExtraHeaderUrlForRemote(remoteUrl) {
327
+ try {
328
+ return new URL(remoteUrl).origin;
329
+ } catch {
330
+ return remoteUrl;
295
331
  }
296
- configureRepo(mainPath, input);
297
- runGit(["remote", "set-url", "build-it-now", remoteUrl], { cwd: mainPath });
298
- runGit(["fetch", "build-it-now", "--prune"], { cwd: mainPath });
299
- runGit(["checkout", "main"], { cwd: mainPath });
300
- if (!hasWorktreeChanges(mainPath)) {
301
- tryGit(["pull", "--ff-only", "build-it-now", "main"], { cwd: mainPath });
332
+ }
333
+ function gitAuthForRemote(remoteUrl, authHeader) {
334
+ if (!authHeader) {
335
+ return void 0;
302
336
  }
303
- return mainPath;
337
+ return {
338
+ extraHeaderUrl: gitExtraHeaderUrlForRemote(remoteUrl),
339
+ header: authHeader
340
+ };
304
341
  }
305
- function ensureBranchWorkspace(input) {
306
- validateBranchName(input.branchName);
307
- const mainPath = ensureMainClone(input);
308
- if (input.branchName === "main") {
309
- return mainPath;
342
+ function branchGitDirName(branchName) {
343
+ return `${encodeURIComponent(branchName)}.git`;
344
+ }
345
+ function internalGitDirFor(syncRoot, projectId, branchName) {
346
+ return path.join(syncRoot, projectId, branchGitDirName(branchName));
347
+ }
348
+ function hasNormalVisibleGitDir(branchPath) {
349
+ const gitPath = path.join(branchPath, ".git");
350
+ return fs.existsSync(gitPath) && fs.statSync(gitPath).isDirectory();
351
+ }
352
+ function ensureOriginRemote(branchPath, remoteUrl) {
353
+ if (tryGit(["remote", "get-url", "origin"], { cwd: branchPath })) {
354
+ runGit(["remote", "set-url", "origin", remoteUrl], { cwd: branchPath });
355
+ } else {
356
+ runGit(["remote", "add", "origin", remoteUrl], { cwd: branchPath });
310
357
  }
311
- const branchPath = path.join(input.projectRoot, input.branchName);
312
- runGit(["fetch", "build-it-now", "--prune"], { cwd: mainPath });
313
- const hasRemoteBranch = tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/build-it-now/${input.branchName}`], {
314
- cwd: mainPath
358
+ }
359
+ function shellQuote(value) {
360
+ return `'${value.replace(/'/g, "'\\''")}'`;
361
+ }
362
+ function githubCredentialsPath() {
363
+ return path.join(os.homedir(), ".r5d", "github-credentials");
364
+ }
365
+ function decodeBasicAuthHeader(authHeader) {
366
+ const match = /^Authorization:\s*Basic\s+(.+)$/i.exec(authHeader.trim());
367
+ if (!match) {
368
+ return null;
369
+ }
370
+ const decoded = Buffer.from(match[1], "base64").toString("utf8");
371
+ const separatorIndex = decoded.indexOf(":");
372
+ if (separatorIndex <= 0) {
373
+ return null;
374
+ }
375
+ return {
376
+ username: decoded.slice(0, separatorIndex),
377
+ password: decoded.slice(separatorIndex + 1)
378
+ };
379
+ }
380
+ function writeCredentialStoreEntry(remoteUrl, authHeader) {
381
+ const credentials = decodeBasicAuthHeader(authHeader);
382
+ if (!credentials) {
383
+ return null;
384
+ }
385
+ let remote;
386
+ try {
387
+ remote = new URL(remoteUrl);
388
+ } catch {
389
+ return null;
390
+ }
391
+ if (remote.protocol !== "https:") {
392
+ return null;
393
+ }
394
+ const storePath = githubCredentialsPath();
395
+ fs.mkdirSync(path.dirname(storePath), { recursive: true });
396
+ const hostKey = `${remote.protocol}//${remote.host}`;
397
+ const existing = fs.existsSync(storePath) ? fs.readFileSync(storePath, "utf8").split(/\r?\n/).filter(Boolean) : [];
398
+ const next = existing.filter((line) => {
399
+ try {
400
+ const parsed = new URL(line);
401
+ return `${parsed.protocol}//${parsed.host}` !== hostKey;
402
+ } catch {
403
+ return true;
404
+ }
405
+ });
406
+ const credentialUrl = new URL(hostKey);
407
+ credentialUrl.username = credentials.username;
408
+ credentialUrl.password = credentials.password;
409
+ next.push(credentialUrl.toString());
410
+ fs.writeFileSync(storePath, `${next.join("\n")}
411
+ `, { mode: 384 });
412
+ fs.chmodSync(storePath, 384);
413
+ return storePath;
414
+ }
415
+ function configureVisibleGitHubAuth(branchPath, remoteUrl, authHeader) {
416
+ if (!authHeader) {
417
+ return;
418
+ }
419
+ const credentialStore = writeCredentialStoreEntry(remoteUrl, authHeader);
420
+ if (credentialStore) {
421
+ runGit(["config", "credential.helper", `store --file=${shellQuote(credentialStore)}`], { cwd: branchPath });
422
+ runGit(["config", "credential.useHttpPath", "false"], { cwd: branchPath });
423
+ return;
424
+ }
425
+ runGit(["config", `${gitExtraHeaderConfigKey(gitExtraHeaderUrlForRemote(remoteUrl))}`, authHeader], { cwd: branchPath });
426
+ }
427
+ function checkoutVisibleBranch(input) {
428
+ const branchExists = tryGit(["show-ref", "--verify", "--quiet", `refs/heads/${input.branchName}`], { cwd: input.branchPath });
429
+ const remoteBranchExists = tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.branchName}`], {
430
+ cwd: input.branchPath
315
431
  });
316
- if (!fs.existsSync(path.join(branchPath, ".git"))) {
432
+ const defaultRemoteExists = tryGit(["show-ref", "--verify", "--quiet", `refs/remotes/origin/${input.defaultBranch}`], {
433
+ cwd: input.branchPath
434
+ });
435
+ const clean = !hasWorktreeChanges(input.branchPath);
436
+ if (remoteBranchExists && clean) {
437
+ runGit(["checkout", "-B", input.branchName, `origin/${input.branchName}`], { cwd: input.branchPath });
438
+ return;
439
+ }
440
+ if (branchExists) {
441
+ runGit(["checkout", input.branchName], { cwd: input.branchPath });
442
+ return;
443
+ }
444
+ if (defaultRemoteExists) {
445
+ runGit(["checkout", "-B", input.branchName, `origin/${input.defaultBranch}`], { cwd: input.branchPath });
446
+ return;
447
+ }
448
+ runGit(["checkout", "-B", input.branchName], { cwd: input.branchPath });
449
+ }
450
+ function ensureVisibleGitCheckout(input) {
451
+ validateBranchName(input.branchName);
452
+ fs.mkdirSync(input.projectRoot, { recursive: true });
453
+ const branchPath = path.join(input.projectRoot, input.branchName);
454
+ if (!hasNormalVisibleGitDir(branchPath)) {
317
455
  fs.rmSync(branchPath, { recursive: true, force: true });
318
- if (hasRemoteBranch) {
319
- runGit(["worktree", "add", "-B", input.branchName, branchPath, `build-it-now/${input.branchName}`], {
320
- cwd: mainPath
321
- });
322
- } else {
323
- runGit(["worktree", "add", "-b", input.branchName, branchPath, "build-it-now/main"], {
324
- cwd: mainPath
325
- });
456
+ fs.mkdirSync(path.dirname(branchPath), { recursive: true });
457
+ if (!tryGit(["clone", "--origin", "origin", input.githubRemoteUrl, branchPath], { auth: input.githubAuth })) {
458
+ fs.mkdirSync(branchPath, { recursive: true });
459
+ runGit(["init"], { cwd: branchPath });
326
460
  }
327
461
  }
328
- configureRepo(branchPath, input);
329
- if (hasRemoteBranch && !hasWorktreeChanges(branchPath)) {
330
- tryGit(["pull", "--ff-only", "build-it-now", input.branchName], { cwd: branchPath });
331
- }
462
+ ensureOriginRemote(branchPath, input.githubRemoteUrl);
463
+ configureVisibleGitHubAuth(branchPath, input.githubRemoteUrl, input.githubAuthHeader);
464
+ tryGit(["fetch", "origin", "--prune"], { cwd: branchPath, auth: input.githubAuth });
465
+ checkoutVisibleBranch({ branchPath, branchName: input.branchName, defaultBranch: input.defaultBranch });
332
466
  return branchPath;
333
467
  }
468
+ function configureInternalRepo(context, remoteUrl) {
469
+ if (tryInternalGit(context, ["remote", "get-url", "build-it-now"])) {
470
+ runInternalGit(context, ["remote", "set-url", "build-it-now", remoteUrl]);
471
+ } else {
472
+ runInternalGit(context, ["remote", "add", "build-it-now", remoteUrl]);
473
+ }
474
+ runInternalGitDir(context, ["config", "user.name", "r5d.dev Worker"]);
475
+ runInternalGitDir(context, ["config", "user.email", "worker@r5d.dev"]);
476
+ }
477
+ function ensureInternalSyncGit(input) {
478
+ validateBranchName(input.branchName);
479
+ const gitDir = internalGitDirFor(input.syncRoot, input.projectId, input.branchName);
480
+ fs.mkdirSync(path.dirname(gitDir), { recursive: true });
481
+ if (!fs.existsSync(gitDir)) {
482
+ runGit(["init", "--bare", gitDir]);
483
+ }
484
+ const auth = internalGitAuth(input.baseUrl, input.token);
485
+ const context = { gitDir, workTree: input.branchPath, auth };
486
+ const remoteUrl = internalRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
487
+ configureInternalRepo(context, remoteUrl);
488
+ runInternalGit(context, ["fetch", "build-it-now", "--prune", "+refs/heads/*:refs/remotes/build-it-now/*"]);
489
+ const hasRemoteBranch = tryInternalGit(context, ["show-ref", "--verify", "--quiet", `refs/remotes/build-it-now/${input.branchName}`]);
490
+ const target = hasRemoteBranch ? `refs/remotes/build-it-now/${input.branchName}` : "refs/remotes/build-it-now/main";
491
+ const hasHead = tryInternalGit(context, ["rev-parse", "--verify", "HEAD"]);
492
+ if (!hasHead || !hasInternalWorktreeChanges(context)) {
493
+ runInternalGit(context, ["checkout", "-f", "-B", input.branchName, target]);
494
+ }
495
+ return context;
496
+ }
497
+ function ensureBranchWorkspace(input) {
498
+ const defaultBranch = input.manifest?.defaultBranch || "main";
499
+ const githubRemoteUrl = githubRemoteUrlFor(input.baseUrl, input.projectId, input.manifest);
500
+ const githubAuth = gitAuthForRemote(githubRemoteUrl, input.manifest?.repoAuthHeader);
501
+ const branchPath = ensureVisibleGitCheckout({
502
+ projectRoot: input.projectRoot,
503
+ branchName: input.branchName,
504
+ githubRemoteUrl,
505
+ githubAuth,
506
+ githubAuthHeader: input.manifest?.repoAuthHeader ?? null,
507
+ defaultBranch
508
+ });
509
+ const internalGit = ensureInternalSyncGit({
510
+ projectId: input.projectId,
511
+ baseUrl: input.baseUrl,
512
+ token: input.token,
513
+ syncRoot: input.syncRoot,
514
+ branchPath,
515
+ branchName: input.branchName,
516
+ manifest: input.manifest
517
+ });
518
+ return { branchPath, internalGit };
519
+ }
334
520
  function resolveCommandCwd(branchPath, cwd) {
335
521
  if (!cwd) {
336
522
  return branchPath;
@@ -439,8 +625,8 @@ function readImageDimensions(bytes, mediaType) {
439
625
  return dimensions;
440
626
  }
441
627
  async function executeReadFileOperation(input) {
442
- const branchPath = ensureOperationBranch({ ...input, branchName: input.message.branchName });
443
- const resolved = resolveProjectFilePath(branchPath, input.message.filePath);
628
+ const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
629
+ const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
444
630
  if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isFile()) {
445
631
  throw new Error(`File not found: ${resolved.virtualPath}`);
446
632
  }
@@ -454,8 +640,8 @@ async function executeReadFileOperation(input) {
454
640
  };
455
641
  }
456
642
  async function executeCreateFileOperation(input) {
457
- const branchPath = ensureOperationBranch({ ...input, branchName: input.message.branchName });
458
- const resolved = resolveProjectFilePath(branchPath, input.message.filePath);
643
+ const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
644
+ const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
459
645
  if (fs.existsSync(resolved.absolutePath)) {
460
646
  throw new Error(`File "${resolved.virtualPath}" already exists. Use edit_file to modify existing files.`);
461
647
  }
@@ -468,8 +654,8 @@ async function executeCreateFileOperation(input) {
468
654
  };
469
655
  }
470
656
  async function executeEditFileOperation(input) {
471
- const branchPath = ensureOperationBranch({ ...input, branchName: input.message.branchName });
472
- const resolved = resolveProjectFilePath(branchPath, input.message.filePath);
657
+ const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
658
+ const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
473
659
  if (input.message.oldString === input.message.newString) {
474
660
  throw new Error("old_string and new_string must be different");
475
661
  }
@@ -498,8 +684,8 @@ async function executeEditFileOperation(input) {
498
684
  };
499
685
  }
500
686
  async function executeViewFileBytesOperation(input) {
501
- const branchPath = ensureOperationBranch({ ...input, branchName: input.message.branchName });
502
- const resolved = resolveProjectFilePath(branchPath, input.message.filePath);
687
+ const workspace = ensureOperationBranch({ ...input, branchName: input.message.branchName });
688
+ const resolved = resolveProjectFilePath(workspace.branchPath, input.message.filePath);
503
689
  if (!fs.existsSync(resolved.absolutePath) || !fs.statSync(resolved.absolutePath).isFile()) {
504
690
  throw new Error(`File not found: ${resolved.virtualPath}`);
505
691
  }
@@ -520,12 +706,12 @@ async function executeViewFileBytesOperation(input) {
520
706
  height: dimensions.height
521
707
  };
522
708
  }
523
- function stagedBlobSizeBytes(cwd) {
524
- const names = runGit(["diff", "--cached", "--name-only", "-z"], { cwd }).split("\0").filter(Boolean);
709
+ function stagedBlobSizeBytes(context) {
710
+ const names = runInternalGit(context, ["diff", "--cached", "--name-only", "-z", "--", ".", ":(exclude).git"]).split("\0").filter(Boolean);
525
711
  let total = 0;
526
712
  for (const name of names) {
527
713
  try {
528
- const sizeText = runGit(["cat-file", "-s", `:${name}`], { cwd });
714
+ const sizeText = runInternalGit(context, ["cat-file", "-s", `:${name}`]);
529
715
  total += Number.parseInt(sizeText, 10) || 0;
530
716
  } catch {
531
717
  }
@@ -536,11 +722,11 @@ function stagedBlobSizeBytes(cwd) {
536
722
  return total;
537
723
  }
538
724
  function syncBranch(input) {
539
- const branchPath = ensureOperationBranch(input);
540
- runGit(["add", "-A"], { cwd: branchPath });
541
- const hasStagedChanges = !tryGit(["diff", "--cached", "--quiet"], { cwd: branchPath });
542
- const status = runGit(["status", "--porcelain"], { cwd: branchPath });
543
- const currentCommit = getCurrentCommitHash(branchPath);
725
+ const workspace = ensureOperationBranch(input);
726
+ runInternalGit(workspace.internalGit, ["add", "-A", "--", ".", ":(exclude).git"]);
727
+ const hasStagedChanges = !tryInternalGit(workspace.internalGit, ["diff", "--cached", "--quiet", "--", ".", ":(exclude).git"]);
728
+ const status = getInternalStatus(workspace.internalGit);
729
+ const currentCommit = getInternalCommitHash(workspace.internalGit);
544
730
  if (!hasStagedChanges) {
545
731
  return {
546
732
  type: "sync",
@@ -550,7 +736,7 @@ function syncBranch(input) {
550
736
  status
551
737
  };
552
738
  }
553
- const diffSizeBytes = stagedBlobSizeBytes(branchPath);
739
+ const diffSizeBytes = stagedBlobSizeBytes(workspace.internalGit);
554
740
  if (diffSizeBytes > MAX_SYNC_DIFF_BYTES && !input.confirmedLargeDiff) {
555
741
  return {
556
742
  type: "sync",
@@ -568,15 +754,15 @@ function syncBranch(input) {
568
754
  parentNodeId: input.parentNodeId,
569
755
  ...input.confirmedLargeDiff ? { confirmedLargeDiff: true, confirmationReason: input.confirmationReason } : {}
570
756
  });
571
- runGit(["commit", "-m", message], { cwd: branchPath });
572
- const commitHash = getCurrentCommitHash(branchPath);
573
- runGit(["push", "build-it-now", `HEAD:refs/heads/${input.branchName}`], { cwd: branchPath });
757
+ runInternalGit(workspace.internalGit, ["commit", "-m", message]);
758
+ const commitHash = getInternalCommitHash(workspace.internalGit);
759
+ runInternalGit(workspace.internalGit, ["push", "build-it-now", `HEAD:refs/heads/${input.branchName}`]);
574
760
  return {
575
761
  type: "sync",
576
762
  changed: true,
577
763
  commitHash,
578
764
  diffSizeBytes,
579
- status: runGit(["status", "--porcelain"], { cwd: branchPath }),
765
+ status: getInternalStatus(workspace.internalGit),
580
766
  trigger: input.trigger
581
767
  };
582
768
  }
@@ -600,14 +786,14 @@ function confirmLargeDiff(input) {
600
786
  };
601
787
  }
602
788
  function pullBranch(input) {
603
- const branchPath = ensureOperationBranch(input);
604
- runGit(["fetch", "build-it-now", "--prune"], { cwd: branchPath });
789
+ const workspace = ensureOperationBranch(input);
790
+ runInternalGit(workspace.internalGit, ["fetch", "build-it-now", "--prune", "+refs/heads/*:refs/remotes/build-it-now/*"]);
605
791
  const target = input.commitHash ?? `build-it-now/${input.branchName}`;
606
- runGit(["reset", "--hard", target], { cwd: branchPath });
607
- runGit(["clean", "-fd"], { cwd: branchPath });
792
+ runInternalGit(workspace.internalGit, ["reset", "--hard", target]);
793
+ runInternalGit(workspace.internalGit, ["clean", "-fd", "--", ".", ":(exclude).git"]);
608
794
  return {
609
795
  type: "pull_branch",
610
- commitHash: getCurrentCommitHash(branchPath)
796
+ commitHash: getInternalCommitHash(workspace.internalGit)
611
797
  };
612
798
  }
613
799
  async function executeOperation(input) {
@@ -626,10 +812,12 @@ async function executeOperation(input) {
626
812
  baseUrl: input.baseUrl,
627
813
  token: input.token,
628
814
  projectRoot: input.projectRoot,
815
+ syncRoot: input.syncRoot,
629
816
  branchName: input.message.branchName,
630
817
  sessionId: input.message.sessionId,
631
818
  parentNodeId: input.message.parentNodeId,
632
- trigger: input.message.trigger
819
+ trigger: input.message.trigger,
820
+ manifest: input.manifest
633
821
  });
634
822
  case "confirm_large_diff":
635
823
  return confirmLargeDiff({
@@ -637,10 +825,12 @@ async function executeOperation(input) {
637
825
  baseUrl: input.baseUrl,
638
826
  token: input.token,
639
827
  projectRoot: input.projectRoot,
828
+ syncRoot: input.syncRoot,
640
829
  branchName: input.message.branchName,
641
830
  sessionId: input.message.sessionId,
642
831
  parentNodeId: input.message.parentNodeId,
643
- reason: input.message.reason
832
+ reason: input.message.reason,
833
+ manifest: input.manifest
644
834
  });
645
835
  case "pull_branch":
646
836
  return pullBranch({
@@ -648,8 +838,10 @@ async function executeOperation(input) {
648
838
  baseUrl: input.baseUrl,
649
839
  token: input.token,
650
840
  projectRoot: input.projectRoot,
841
+ syncRoot: input.syncRoot,
651
842
  branchName: input.message.branchName,
652
- commitHash: input.message.commitHash
843
+ commitHash: input.message.commitHash,
844
+ manifest: input.manifest
653
845
  });
654
846
  }
655
847
  }
@@ -662,9 +854,11 @@ async function executeCommand(input) {
662
854
  baseUrl: input.baseUrl,
663
855
  token: input.token,
664
856
  projectRoot: input.projectRoot,
665
- branchName: input.message.branchName
857
+ syncRoot: input.syncRoot,
858
+ branchName: input.message.branchName,
859
+ manifest: input.manifest
666
860
  });
667
- const cwd = resolveCommandCwd(branchPath, input.message.cwd);
861
+ const cwd = resolveCommandCwd(branchPath.branchPath, input.message.cwd);
668
862
  const subprocess = Bun.spawn(input.message.argv, {
669
863
  cwd,
670
864
  stdout: "pipe",
@@ -713,6 +907,182 @@ async function executeCommand(input) {
713
907
  function sendWorkerMessage(ws, message) {
714
908
  ws.send(JSON.stringify(message));
715
909
  }
910
+ const PTY_BRIDGE_SCRIPT = String.raw`
911
+ const readline = require("node:readline");
912
+ const nodePty = require("node-pty");
913
+
914
+ let ptyProcess = null;
915
+
916
+ function send(message, callback) {
917
+ process.stdout.write(JSON.stringify(message) + "\n", callback);
918
+ }
919
+
920
+ function decode(data) {
921
+ return Buffer.from(data, "base64").toString("utf8");
922
+ }
923
+
924
+ const rl = readline.createInterface({ input: process.stdin });
925
+
926
+ rl.on("line", (line) => {
927
+ let message;
928
+ try {
929
+ message = JSON.parse(line);
930
+ } catch (error) {
931
+ send({ type: "error", error: error instanceof Error ? error.message : String(error) });
932
+ return;
933
+ }
934
+
935
+ try {
936
+ if (message.type === "open") {
937
+ ptyProcess = nodePty.spawn(message.file, message.args, message.options);
938
+ ptyProcess.onData((data) => {
939
+ send({ type: "output", data: Buffer.from(data, "utf8").toString("base64") });
940
+ });
941
+ ptyProcess.onExit((event) => {
942
+ send({ type: "exit", exitCode: event.exitCode, signal: event.signal }, () => process.exit(0));
943
+ });
944
+ send({ type: "opened" });
945
+ return;
946
+ }
947
+
948
+ if (!ptyProcess) {
949
+ send({ type: "error", error: "PTY has not been opened" });
950
+ return;
951
+ }
952
+
953
+ if (message.type === "input") {
954
+ ptyProcess.write(decode(message.data));
955
+ return;
956
+ }
957
+
958
+ if (message.type === "resize") {
959
+ ptyProcess.resize(message.cols, message.rows);
960
+ return;
961
+ }
962
+
963
+ if (message.type === "close") {
964
+ ptyProcess.kill();
965
+ }
966
+ } catch (error) {
967
+ send({ type: "error", error: error instanceof Error ? error.message : String(error) });
968
+ }
969
+ });
970
+ `;
971
+ function nodePtyModulePaths() {
972
+ const candidates = [];
973
+ const entrypoint = process.argv[1];
974
+ if (entrypoint) {
975
+ try {
976
+ let current = path.dirname(fs.realpathSync(entrypoint));
977
+ for (let index = 0; index < 8; index += 1) {
978
+ candidates.push(path.join(current, "node_modules"));
979
+ const parent = path.dirname(current);
980
+ if (parent === current) {
981
+ break;
982
+ }
983
+ current = parent;
984
+ }
985
+ } catch {
986
+ }
987
+ }
988
+ candidates.push(path.join(process.cwd(), "node_modules"), "/usr/local/lib/node_modules", "/usr/lib/node_modules");
989
+ if (process.env.NODE_PATH) {
990
+ candidates.push(...process.env.NODE_PATH.split(path.delimiter));
991
+ }
992
+ return [...new Set(candidates.filter(Boolean))].join(path.delimiter);
993
+ }
994
+ function createNodePtyBridge(options) {
995
+ const child = spawnChildProcess("node", ["-e", PTY_BRIDGE_SCRIPT], {
996
+ env: {
997
+ ...process.env,
998
+ NODE_PATH: nodePtyModulePaths()
999
+ }
1000
+ });
1001
+ let lineBuffer = "";
1002
+ let emittedTerminalEvent = false;
1003
+ let stderr = "";
1004
+ const sendToChild = (message) => {
1005
+ if (child.stdin.writable) {
1006
+ child.stdin.write(`${JSON.stringify(message)}
1007
+ `);
1008
+ }
1009
+ };
1010
+ const handleEvent = (event) => {
1011
+ if (event.type === "opened") {
1012
+ options.onOpened();
1013
+ return;
1014
+ }
1015
+ if (event.type === "output") {
1016
+ options.onOutput(Buffer.from(event.data, "base64").toString("utf8"));
1017
+ return;
1018
+ }
1019
+ if (event.type === "exit") {
1020
+ emittedTerminalEvent = true;
1021
+ options.onExit({ exitCode: event.exitCode, signal: event.signal });
1022
+ return;
1023
+ }
1024
+ if (event.type === "error") {
1025
+ emittedTerminalEvent = true;
1026
+ options.onError(new Error(event.error));
1027
+ }
1028
+ };
1029
+ child.stdout.setEncoding("utf8");
1030
+ child.stdout.on("data", (chunk) => {
1031
+ lineBuffer += chunk;
1032
+ let newlineIndex = lineBuffer.indexOf("\n");
1033
+ while (newlineIndex !== -1) {
1034
+ const line = lineBuffer.slice(0, newlineIndex).trim();
1035
+ lineBuffer = lineBuffer.slice(newlineIndex + 1);
1036
+ if (line) {
1037
+ try {
1038
+ handleEvent(JSON.parse(line));
1039
+ } catch (error) {
1040
+ emittedTerminalEvent = true;
1041
+ options.onError(new Error(error instanceof Error ? error.message : String(error)));
1042
+ }
1043
+ }
1044
+ newlineIndex = lineBuffer.indexOf("\n");
1045
+ }
1046
+ });
1047
+ child.stderr.setEncoding("utf8");
1048
+ child.stderr.on("data", (chunk) => {
1049
+ stderr += chunk;
1050
+ process.stderr.write(`[r5d-worker] pty helper: ${chunk}`);
1051
+ });
1052
+ child.on("error", (error) => {
1053
+ emittedTerminalEvent = true;
1054
+ options.onError(error);
1055
+ });
1056
+ child.on("exit", (code, signal) => {
1057
+ if (emittedTerminalEvent) {
1058
+ return;
1059
+ }
1060
+ const detail = stderr.trim() || `node pty helper exited with code ${code ?? "unknown"}${signal ? ` signal ${signal}` : ""}`;
1061
+ options.onError(new Error(detail));
1062
+ });
1063
+ sendToChild({
1064
+ type: "open",
1065
+ file: options.file,
1066
+ args: options.args,
1067
+ options: options.ptyOptions
1068
+ });
1069
+ return {
1070
+ write(data) {
1071
+ sendToChild({ type: "input", data: Buffer.from(data, "utf8").toString("base64") });
1072
+ },
1073
+ resize(cols, rows) {
1074
+ sendToChild({ type: "resize", cols, rows });
1075
+ },
1076
+ kill() {
1077
+ sendToChild({ type: "close" });
1078
+ setTimeout(() => {
1079
+ if (!child.killed) {
1080
+ child.kill();
1081
+ }
1082
+ }, 500);
1083
+ }
1084
+ };
1085
+ }
716
1086
  function resolveHostShell() {
717
1087
  if (process.platform === "win32") {
718
1088
  return { file: process.env.COMSPEC || "powershell.exe", args: [] };
@@ -726,43 +1096,60 @@ function openPty(input) {
726
1096
  baseUrl: input.baseUrl,
727
1097
  token: input.token,
728
1098
  projectRoot: input.projectRoot,
729
- branchName: input.message.branchName
1099
+ syncRoot: input.syncRoot,
1100
+ branchName: input.message.branchName,
1101
+ manifest: input.manifest
730
1102
  });
731
1103
  const shell = resolveHostShell();
732
- const ptyProcess = nodePty.spawn(shell.file, shell.args, {
733
- name: "xterm-256color",
734
- cols: Math.max(1, Math.min(Math.floor(input.message.cols || 80), 500)),
735
- rows: Math.max(1, Math.min(Math.floor(input.message.rows || 24), 500)),
736
- cwd: branchPath,
737
- env: {
738
- ...process.env,
739
- ...input.message.env ?? {},
740
- BUILD_IT_NOW_PROJECT_ID: input.projectId,
741
- BUILD_IT_NOW_BRANCH_NAME: input.message.branchName
1104
+ const ptyProcess = createNodePtyBridge({
1105
+ file: shell.file,
1106
+ args: shell.args,
1107
+ ptyOptions: {
1108
+ name: "xterm-256color",
1109
+ cols: Math.max(1, Math.min(Math.floor(input.message.cols || 80), 500)),
1110
+ rows: Math.max(1, Math.min(Math.floor(input.message.rows || 24), 500)),
1111
+ cwd: branchPath.branchPath,
1112
+ env: {
1113
+ ...process.env,
1114
+ ...input.message.env ?? {},
1115
+ BUILD_IT_NOW_PROJECT_ID: input.projectId,
1116
+ BUILD_IT_NOW_BRANCH_NAME: input.message.branchName
1117
+ }
1118
+ },
1119
+ onOpened: () => {
1120
+ sendWorkerMessage(input.ws, {
1121
+ type: "pty_opened",
1122
+ requestId: input.message.requestId,
1123
+ ptyId: input.message.ptyId
1124
+ });
1125
+ },
1126
+ onOutput: (data) => {
1127
+ sendWorkerMessage(input.ws, {
1128
+ type: "pty_output",
1129
+ ptyId: input.message.ptyId,
1130
+ data
1131
+ });
1132
+ },
1133
+ onExit: (event) => {
1134
+ activePtys.delete(input.message.ptyId);
1135
+ sendWorkerMessage(input.ws, {
1136
+ type: "pty_exit",
1137
+ ptyId: input.message.ptyId,
1138
+ exitCode: event.exitCode,
1139
+ signal: event.signal
1140
+ });
1141
+ },
1142
+ onError: (error) => {
1143
+ activePtys.delete(input.message.ptyId);
1144
+ sendWorkerMessage(input.ws, {
1145
+ type: "pty_error",
1146
+ requestId: input.message.requestId,
1147
+ ptyId: input.message.ptyId,
1148
+ error: error.message
1149
+ });
742
1150
  }
743
1151
  });
744
1152
  activePtys.set(input.message.ptyId, ptyProcess);
745
- ptyProcess.onData((data) => {
746
- sendWorkerMessage(input.ws, {
747
- type: "pty_output",
748
- ptyId: input.message.ptyId,
749
- data
750
- });
751
- });
752
- ptyProcess.onExit((event) => {
753
- activePtys.delete(input.message.ptyId);
754
- sendWorkerMessage(input.ws, {
755
- type: "pty_exit",
756
- ptyId: input.message.ptyId,
757
- exitCode: event.exitCode,
758
- signal: event.signal
759
- });
760
- });
761
- sendWorkerMessage(input.ws, {
762
- type: "pty_opened",
763
- requestId: input.message.requestId,
764
- ptyId: input.message.ptyId
765
- });
766
1153
  } catch (error) {
767
1154
  sendWorkerMessage(input.ws, {
768
1155
  type: "pty_error",
@@ -808,32 +1195,36 @@ function closeAllPtys() {
808
1195
  }
809
1196
  activePtys.clear();
810
1197
  }
811
- function websocketUrl(baseUrl, projectId, label) {
1198
+ function websocketUrl(baseUrl, label) {
812
1199
  const url = new URL("/worker/ws", baseUrl);
813
1200
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
814
- url.searchParams.set("projectId", projectId);
815
1201
  url.searchParams.set("label", label);
816
1202
  return url.toString();
817
1203
  }
818
- async function startWorker(projectId, options) {
1204
+ function projectRootFor(projectsRoot, projectId, manifestByProjectId) {
1205
+ return path.join(projectsRoot, manifestByProjectId.get(projectId)?.repoSlug ?? projectId);
1206
+ }
1207
+ async function startWorker(options) {
819
1208
  const config = readConfig(options.configPath);
820
1209
  const { baseUrl, token } = resolveCredentials(options, config);
821
1210
  const label = requireValue(options.label, "Missing required --label <label>");
822
1211
  validateLabel(label);
823
- const projectRoot = path.resolve(options.projectRoot ?? defaultProjectRoot(projectId));
824
- process.stdout.write(`[r5d-worker] project: ${projectId}
825
- `);
1212
+ const projectsRoot = path.resolve(options.projectRoot ?? defaultProjectsRoot());
1213
+ const syncRoot = path.resolve(options.syncRoot ?? process.env.R5D_SYNC_ROOT ?? defaultSyncRoot());
826
1214
  process.stdout.write(`[r5d-worker] label: ${label}
827
1215
  `);
828
- process.stdout.write(`[r5d-worker] root: ${projectRoot}
1216
+ process.stdout.write(`[r5d-worker] projects root: ${projectsRoot}
1217
+ `);
1218
+ process.stdout.write(`[r5d-worker] sync root: ${syncRoot}
829
1219
  `);
830
1220
  process.stdout.write(`[r5d-worker] server: ${baseUrl}
831
1221
  `);
832
1222
  runGit(["--version"]);
833
1223
  await verifyBinctlAuth(baseUrl, token);
834
- process.stdout.write("[r5d-worker] preparing local checkout...\n");
835
- ensureMainClone({ projectId, baseUrl, token, projectRoot });
836
- const ws = new WebSocket(websocketUrl(baseUrl, projectId, label), {
1224
+ fs.mkdirSync(projectsRoot, { recursive: true });
1225
+ fs.mkdirSync(syncRoot, { recursive: true });
1226
+ const manifestByProjectId = /* @__PURE__ */ new Map();
1227
+ const ws = new WebSocket(websocketUrl(baseUrl, label), {
837
1228
  headers: {
838
1229
  Authorization: `Bearer ${token}`
839
1230
  }
@@ -847,7 +1238,7 @@ async function startWorker(projectId, options) {
847
1238
  arch: process.arch,
848
1239
  pid: process.pid,
849
1240
  version: "0.1.0",
850
- projectRoot
1241
+ projectRoot: projectsRoot
851
1242
  }
852
1243
  };
853
1244
  ws.send(JSON.stringify(hello));
@@ -859,6 +1250,39 @@ async function startWorker(projectId, options) {
859
1250
  if (message.type === "connected") {
860
1251
  return;
861
1252
  }
1253
+ if (message.type === "project_manifest") {
1254
+ manifestByProjectId.clear();
1255
+ for (const project of message.projects) {
1256
+ manifestByProjectId.set(project.projectId, project);
1257
+ }
1258
+ process.stdout.write(`[r5d-worker] project manifest: ${message.projects.length} projects
1259
+ `);
1260
+ for (const project of message.projects) {
1261
+ const projectRoot = projectRootFor(projectsRoot, project.projectId, manifestByProjectId);
1262
+ const branches = project.branches.length > 0 ? project.branches : [project.defaultBranch || "main"];
1263
+ for (const branchName of branches) {
1264
+ try {
1265
+ await withBranchLock(project.projectId, branchName, async () => {
1266
+ ensureBranchWorkspace({
1267
+ projectId: project.projectId,
1268
+ baseUrl,
1269
+ token,
1270
+ projectRoot,
1271
+ syncRoot,
1272
+ branchName,
1273
+ manifest: project
1274
+ });
1275
+ });
1276
+ } catch (error) {
1277
+ process.stderr.write(
1278
+ `[r5d-worker] failed to sync ${project.repoSlug}/${branchName}: ${error instanceof Error ? error.message : String(error)}
1279
+ `
1280
+ );
1281
+ }
1282
+ }
1283
+ }
1284
+ return;
1285
+ }
862
1286
  if (message.type === "ping") {
863
1287
  ws.send(JSON.stringify({ type: "pong" }));
864
1288
  return;
@@ -880,9 +1304,11 @@ async function startWorker(projectId, options) {
880
1304
  return;
881
1305
  }
882
1306
  if (message.type === "pty_open") {
883
- process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${message.branchName}
1307
+ const manifest = manifestByProjectId.get(message.projectId);
1308
+ const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
1309
+ process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${message.projectId}/${message.branchName}
884
1310
  `);
885
- openPty({ ws, message, projectId, baseUrl, token, projectRoot });
1311
+ openPty({ ws, message, projectId: message.projectId, baseUrl, token, projectRoot, syncRoot, manifest });
886
1312
  return;
887
1313
  }
888
1314
  if (message.type === "pty_input") {
@@ -898,22 +1324,26 @@ async function startWorker(projectId, options) {
898
1324
  return;
899
1325
  }
900
1326
  if (message.type === "exec") {
1327
+ const manifest = manifestByProjectId.get(message.projectId);
1328
+ const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
901
1329
  process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
902
1330
  `);
903
1331
  const result = await withBranchLock(
904
- projectRoot,
1332
+ message.projectId,
905
1333
  message.branchName,
906
- () => executeCommand({ message, projectId, baseUrl, token, projectRoot })
1334
+ () => executeCommand({ message, projectId: message.projectId, baseUrl, token, projectRoot, syncRoot, manifest })
907
1335
  );
908
1336
  ws.send(JSON.stringify(result));
909
1337
  return;
910
1338
  }
911
- if (message.type === "read_file" || message.type === "create_file" || message.type === "edit_file" || message.type === "view_file_bytes" || message.type === "sync" || message.type === "pull_branch") {
1339
+ if (message.type === "read_file" || message.type === "create_file" || message.type === "edit_file" || message.type === "view_file_bytes" || message.type === "sync" || message.type === "confirm_large_diff" || message.type === "pull_branch") {
912
1340
  try {
1341
+ const manifest = manifestByProjectId.get(message.projectId);
1342
+ const projectRoot = projectRootFor(projectsRoot, message.projectId, manifestByProjectId);
913
1343
  const result = await withBranchLock(
914
- projectRoot,
1344
+ message.projectId,
915
1345
  message.branchName,
916
- () => executeOperation({ message, projectId, baseUrl, token, projectRoot })
1346
+ () => executeOperation({ message, projectId: message.projectId, baseUrl, token, projectRoot, syncRoot, manifest })
917
1347
  );
918
1348
  ws.send(
919
1349
  JSON.stringify({
@@ -958,7 +1388,7 @@ async function main() {
958
1388
  printHelp();
959
1389
  return;
960
1390
  }
961
- await startWorker(parsed.projectId, parsed.options);
1391
+ await startWorker(parsed.options);
962
1392
  }
963
1393
  main().catch((error) => {
964
1394
  process.stderr.write(`[r5d-worker] ${error instanceof Error ? error.message : String(error)}