@vexdo/cli 0.1.1 → 0.1.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.
Files changed (2) hide show
  1. package/dist/index.js +106 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -552,7 +552,11 @@ ${JSON.stringify(opts.reviewComments)}`
552
552
  if (!isRetryableError(error) || attempt === MAX_ATTEMPTS) {
553
553
  throw new ClaudeError(attempt, error);
554
554
  }
555
- await sleep(1e3 * 2 ** (attempt - 1));
555
+ const backoffMs = 1e3 * 2 ** (attempt - 1);
556
+ warn(
557
+ `Claude API error on attempt ${String(attempt)}/${String(MAX_ATTEMPTS)}. Retrying in ${String(Math.round(backoffMs / 1e3))}s...`
558
+ );
559
+ await sleep(backoffMs);
556
560
  }
557
561
  }
558
562
  throw new ClaudeError(MAX_ATTEMPTS, lastError);
@@ -673,6 +677,7 @@ function isRetryableError(error) {
673
677
  // src/lib/codex.ts
674
678
  import { execFile as execFileCb } from "child_process";
675
679
  var CODEX_TIMEOUT_MS = 6e5;
680
+ var VERBOSE_HEARTBEAT_MS = 15e3;
676
681
  var CodexError = class extends Error {
677
682
  stdout;
678
683
  stderr;
@@ -691,6 +696,33 @@ var CodexNotFoundError = class extends Error {
691
696
  this.name = "CodexNotFoundError";
692
697
  }
693
698
  };
699
+ function formatElapsed(startedAt) {
700
+ const seconds = Math.max(0, Math.round((Date.now() - startedAt) / 1e3));
701
+ return `${String(seconds)}s`;
702
+ }
703
+ function buildVerboseStreamHandler(label) {
704
+ let partialLine = "";
705
+ return {
706
+ onData(chunk) {
707
+ partialLine += chunk.toString();
708
+ const lines = partialLine.split(/\r?\n/);
709
+ partialLine = lines.pop() ?? "";
710
+ for (const line of lines) {
711
+ if (!line) {
712
+ continue;
713
+ }
714
+ debug(`[codex:${label}] ${line}`);
715
+ }
716
+ },
717
+ flush() {
718
+ if (!partialLine) {
719
+ return;
720
+ }
721
+ debug(`[codex:${label}] ${partialLine}`);
722
+ partialLine = "";
723
+ }
724
+ };
725
+ }
694
726
  async function checkCodexAvailable() {
695
727
  await new Promise((resolve, reject) => {
696
728
  execFileCb("codex", ["--version"], { timeout: CODEX_TIMEOUT_MS, encoding: "utf8" }, (error) => {
@@ -704,24 +736,43 @@ async function checkCodexAvailable() {
704
736
  }
705
737
  async function exec(opts) {
706
738
  const args = ["exec", "--model", opts.model, "--full-auto", "--", opts.spec];
739
+ const startedAt = Date.now();
740
+ if (opts.verbose) {
741
+ debug(`[codex] starting (model=${opts.model}, cwd=${opts.cwd})`);
742
+ }
707
743
  return await new Promise((resolve, reject) => {
708
- execFileCb(
744
+ let liveLogsAttached = false;
745
+ const stdoutHandler = buildVerboseStreamHandler("stdout");
746
+ const stderrHandler = buildVerboseStreamHandler("stderr");
747
+ const heartbeat = opts.verbose ? setInterval(() => {
748
+ debug(`[codex] still running (${formatElapsed(startedAt)})`);
749
+ }, VERBOSE_HEARTBEAT_MS) : null;
750
+ const child = execFileCb(
709
751
  "codex",
710
752
  args,
711
753
  { cwd: opts.cwd, timeout: CODEX_TIMEOUT_MS, encoding: "utf8", maxBuffer: 10 * 1024 * 1024 },
712
754
  (error, stdout, stderr) => {
755
+ if (heartbeat) {
756
+ clearInterval(heartbeat);
757
+ }
758
+ stdoutHandler.flush();
759
+ stderrHandler.flush();
713
760
  const normalizedStdout = stdout.trimEnd();
714
761
  const normalizedStderr = stderr.trimEnd();
715
762
  if (opts.verbose) {
716
- if (normalizedStdout) {
763
+ debug(`[codex] finished in ${formatElapsed(startedAt)}`);
764
+ if (!liveLogsAttached && normalizedStdout) {
717
765
  debug(normalizedStdout);
718
766
  }
719
- if (normalizedStderr) {
767
+ if (!liveLogsAttached && normalizedStderr) {
720
768
  debug(normalizedStderr);
721
769
  }
722
770
  }
723
771
  if (error) {
724
772
  const exitCode = typeof error.code === "number" ? error.code : 1;
773
+ if (opts.verbose) {
774
+ debug(`[codex] failed in ${formatElapsed(startedAt)} with exit ${String(exitCode)}`);
775
+ }
725
776
  reject(new CodexError(normalizedStdout, normalizedStderr || error.message, exitCode));
726
777
  return;
727
778
  }
@@ -732,6 +783,18 @@ async function exec(opts) {
732
783
  });
733
784
  }
734
785
  );
786
+ if (opts.verbose) {
787
+ const stdout = child.stdout;
788
+ const stderr = child.stderr;
789
+ if (stdout) {
790
+ liveLogsAttached = true;
791
+ stdout.on("data", stdoutHandler.onData);
792
+ }
793
+ if (stderr) {
794
+ liveLogsAttached = true;
795
+ stderr.on("data", stderrHandler.onData);
796
+ }
797
+ }
735
798
  });
736
799
  }
737
800
 
@@ -846,6 +909,10 @@ function getBranchName(taskId, service) {
846
909
  }
847
910
 
848
911
  // src/lib/review-loop.ts
912
+ function formatElapsed2(startedAt) {
913
+ const seconds = Math.max(0, Math.round((Date.now() - startedAt) / 1e3));
914
+ return `${String(seconds)}s`;
915
+ }
849
916
  async function runReviewLoop(opts) {
850
917
  if (opts.dryRun) {
851
918
  info(`[dry-run] Would run review loop for service ${opts.step.service}`);
@@ -868,7 +935,11 @@ async function runReviewLoop(opts) {
868
935
  let iteration2 = opts.stepState.iteration;
869
936
  for (; ; ) {
870
937
  iteration(iteration2 + 1, opts.config.review.max_iterations);
938
+ info(`Collecting git diff for service ${opts.step.service}`);
871
939
  const diff = await getDiff(serviceRoot);
940
+ if (opts.verbose) {
941
+ info(`Diff collected (${String(diff.length)} chars)`);
942
+ }
872
943
  if (!diff.trim()) {
873
944
  return {
874
945
  decision: "submit",
@@ -881,18 +952,39 @@ async function runReviewLoop(opts) {
881
952
  }
882
953
  };
883
954
  }
955
+ info(`Requesting reviewer analysis (model: ${opts.config.review.model})`);
956
+ const reviewerStartedAt = Date.now();
957
+ const reviewerHeartbeat = opts.verbose ? setInterval(() => {
958
+ info(`Waiting for reviewer response (${formatElapsed2(reviewerStartedAt)})`);
959
+ }, 15e3) : null;
884
960
  const review = await opts.claude.runReviewer({
885
961
  spec: opts.step.spec,
886
962
  diff,
887
963
  model: opts.config.review.model
964
+ }).finally(() => {
965
+ if (reviewerHeartbeat) {
966
+ clearInterval(reviewerHeartbeat);
967
+ }
888
968
  });
969
+ info(`Reviewer response received in ${formatElapsed2(reviewerStartedAt)}`);
889
970
  reviewSummary(review.comments);
971
+ info(`Requesting arbiter decision (model: ${opts.config.review.model})`);
972
+ const arbiterStartedAt = Date.now();
973
+ const arbiterHeartbeat = opts.verbose ? setInterval(() => {
974
+ info(`Waiting for arbiter response (${formatElapsed2(arbiterStartedAt)})`);
975
+ }, 15e3) : null;
890
976
  const arbiter = await opts.claude.runArbiter({
891
977
  spec: opts.step.spec,
892
978
  diff,
893
979
  reviewComments: review.comments,
894
980
  model: opts.config.review.model
981
+ }).finally(() => {
982
+ if (arbiterHeartbeat) {
983
+ clearInterval(arbiterHeartbeat);
984
+ }
895
985
  });
986
+ info(`Arbiter response received in ${formatElapsed2(arbiterStartedAt)}`);
987
+ info(`Arbiter decision: ${arbiter.decision} (${arbiter.summary})`);
896
988
  saveIterationLog(opts.projectRoot, opts.taskId, opts.step.service, iteration2, {
897
989
  diff,
898
990
  review,
@@ -940,6 +1032,7 @@ async function runReviewLoop(opts) {
940
1032
  }
941
1033
  };
942
1034
  }
1035
+ info("Applying arbiter feedback with codex");
943
1036
  await exec({
944
1037
  spec: arbiter.feedback_for_codex,
945
1038
  model: opts.config.codex.model,
@@ -992,6 +1085,7 @@ async function runFix(feedback, options) {
992
1085
  verbose: options.verbose
993
1086
  });
994
1087
  }
1088
+ info(`Running review loop for service ${step2.service}`);
995
1089
  const result = await runReviewLoop({
996
1090
  taskId: task.id,
997
1091
  task,
@@ -1023,7 +1117,7 @@ async function runFix(feedback, options) {
1023
1117
  }
1024
1118
  }
1025
1119
  function registerFixCommand(program2) {
1026
- program2.command("fix").description("Provide feedback to codex and rerun review").argument("<feedback>").action(async (feedback, options, command) => {
1120
+ program2.command("fix").description("Provide feedback to codex and rerun review").argument("<feedback>").option("--verbose", "Enable verbose logs").option("--dry-run", "Print plan without making changes").action(async (feedback, options, command) => {
1027
1121
  const merged = command.optsWithGlobals();
1028
1122
  await runFix(feedback, { ...options, ...merged });
1029
1123
  });
@@ -1223,6 +1317,7 @@ async function runReview(options) {
1223
1317
  if (!step2) {
1224
1318
  fatalAndExit4(`Could not locate task step for service '${currentStep.service}'.`);
1225
1319
  }
1320
+ info(`Running review loop for service ${step2.service}`);
1226
1321
  const result = await runReviewLoop({
1227
1322
  taskId: task.id,
1228
1323
  task,
@@ -1265,7 +1360,7 @@ async function runReview(options) {
1265
1360
  }
1266
1361
  }
1267
1362
  function registerReviewCommand(program2) {
1268
- program2.command("review").description("Run review loop for the current step").action(async (options, command) => {
1363
+ program2.command("review").description("Run review loop for the current step").option("--verbose", "Enable verbose logs").option("--dry-run", "Print plan without making changes").action(async (options, command) => {
1269
1364
  const merged = command.optsWithGlobals();
1270
1365
  await runReview({ ...options, ...merged });
1271
1366
  });
@@ -1382,6 +1477,7 @@ async function runStart(taskFile, options) {
1382
1477
  saveState(projectRoot, state);
1383
1478
  }
1384
1479
  if (!options.resume && !options.dryRun) {
1480
+ info(`Running codex implementation for service ${step2.service}`);
1385
1481
  await exec({
1386
1482
  spec: step2.spec,
1387
1483
  model: config.codex.model,
@@ -1391,6 +1487,7 @@ async function runStart(taskFile, options) {
1391
1487
  } else if (options.dryRun) {
1392
1488
  info(`[dry-run] Would run codex for service ${step2.service}`);
1393
1489
  }
1490
+ info(`Starting review loop for service ${step2.service}`);
1394
1491
  const result = await runReviewLoop({
1395
1492
  taskId: task.id,
1396
1493
  task,
@@ -1445,7 +1542,7 @@ async function runStart(taskFile, options) {
1445
1542
  }
1446
1543
  }
1447
1544
  function registerStartCommand(program2) {
1448
- program2.command("start").description("Start a task from a YAML file").argument("<task-file>").option("--resume", "Resume an existing active task").action(async (taskFile, options, command) => {
1545
+ program2.command("start").description("Start a task from a YAML file").argument("<task-file>").option("--verbose", "Enable verbose logs").option("--dry-run", "Print plan without making changes").option("--resume", "Resume an existing active task").action(async (taskFile, options, command) => {
1449
1546
  const merged = command.optsWithGlobals();
1450
1547
  await runStart(taskFile, { ...options, ...merged });
1451
1548
  });
@@ -1456,7 +1553,7 @@ function fatalAndExit6(message) {
1456
1553
  fatal(message);
1457
1554
  process.exit(1);
1458
1555
  }
1459
- function formatElapsed(startedAt) {
1556
+ function formatElapsed3(startedAt) {
1460
1557
  const elapsedMs = Date.now() - new Date(startedAt).getTime();
1461
1558
  const minutes = Math.floor(elapsedMs / 1e3 / 60);
1462
1559
  const hours = Math.floor(minutes / 60);
@@ -1484,7 +1581,7 @@ function runStatus() {
1484
1581
  if (inProgress?.lastArbiterResult?.summary) {
1485
1582
  info(`Last arbiter summary: ${inProgress.lastArbiterResult.summary}`);
1486
1583
  }
1487
- info(`Elapsed: ${formatElapsed(state.startedAt)}`);
1584
+ info(`Elapsed: ${formatElapsed3(state.startedAt)}`);
1488
1585
  }
1489
1586
  function registerStatusCommand(program2) {
1490
1587
  program2.command("status").description("Print active task status").action(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vexdo/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {