@tryarcanist/cli 0.1.346 → 0.1.348

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 (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +80 -33
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -266,9 +266,11 @@ arcanist sessions events abc123 --after-sequence 50 --limit 250 --json
266
266
  arcanist sessions events abc123 --before-sequence 100 --poll-interval 500 --json
267
267
  arcanist sessions events abc123 --prompt-id p-123 --json
268
268
  arcanist sessions events abc123 --follow --json
269
+ arcanist sessions events abc123 --follow --timeout 60000 --json
269
270
  ```
270
271
 
271
272
  `--after` and `--before` are aliases for `--after-sequence` and `--before-sequence`.
273
+ `--timeout` bounds `--follow`; omit it or pass `0` for unbounded follow. Process exit is a terminal phase, not `session_idle`.
272
274
 
273
275
  JSON mode without `--follow` returns one JSON object from `/events/history`: `{events: [...], ...}`. Canonical events carry `phase`.
274
276
 
package/dist/index.js CHANGED
@@ -7409,7 +7409,7 @@ function isTerminalPhase(phase, _sessionKind) {
7409
7409
  // package.json
7410
7410
  var package_default = {
7411
7411
  name: "@tryarcanist/cli",
7412
- version: "0.1.346",
7412
+ version: "0.1.348",
7413
7413
  description: "Command-line interface for Arcanist: inspect sessions, prepare Zeus environments, and trigger reviews",
7414
7414
  type: "module",
7415
7415
  bin: {
@@ -7659,17 +7659,27 @@ function timeoutError(timeoutMs) {
7659
7659
  hint: `Increase ${HTTP_TIMEOUT_ENV} for slow links, or check the API connection.`
7660
7660
  });
7661
7661
  }
7662
+ function resolveApiTimeoutMs(init) {
7663
+ const httpTimeout = parseHttpTimeoutMs();
7664
+ if (!init) return { fetchInit: void 0, timeoutMs: httpTimeout };
7665
+ const { timeoutMs: requestedTimeoutMs, ...fetchInit } = init;
7666
+ if (requestedTimeoutMs === void 0) return { fetchInit, timeoutMs: httpTimeout };
7667
+ if (!Number.isFinite(requestedTimeoutMs) || requestedTimeoutMs <= 0) {
7668
+ return { fetchInit, timeoutMs: 1 };
7669
+ }
7670
+ return { fetchInit, timeoutMs: Math.max(1, Math.min(httpTimeout, requestedTimeoutMs)) };
7671
+ }
7662
7672
  async function apiRequest(connection, path, init, read) {
7663
- const timeoutMs = parseHttpTimeoutMs();
7673
+ const { fetchInit, timeoutMs } = resolveApiTimeoutMs(init);
7664
7674
  const controller = new AbortController();
7665
7675
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
7666
7676
  try {
7667
- const headers = new Headers(init?.headers);
7677
+ const headers = new Headers(fetchInit?.headers);
7668
7678
  headers.set("Content-Type", "application/json");
7669
7679
  headers.set("User-Agent", CLI_USER_AGENT);
7670
7680
  if (connection.token) headers.set("Authorization", `Bearer ${connection.token}`);
7671
7681
  const res = await fetch(`${normalizeBaseUrl(connection.apiUrl)}${path}`, {
7672
- ...init,
7682
+ ...fetchInit,
7673
7683
  headers,
7674
7684
  signal: controller.signal
7675
7685
  });
@@ -23874,13 +23884,6 @@ async function reviewCommand(prUrl, options = {}, command) {
23874
23884
  ...sandboxRuntimeBackend !== null ? { sandboxRuntimeBackend } : {}
23875
23885
  })
23876
23886
  });
23877
- if (trigger.skipped && trigger.reason === "duplicate_diff") {
23878
- emit(command, options, trigger, (payload) => {
23879
- const source = payload.sourcePrUrl ? sanitizeTerminalText(payload.sourcePrUrl) : "a prior pull request";
23880
- console.log(`Zeus review skipped: this pull request has the same diff as ${source}.`);
23881
- });
23882
- return;
23883
- }
23884
23887
  if (!options.wait || trigger.cached) {
23885
23888
  emit(command, options, trigger, (payload) => {
23886
23889
  if (payload.cached) console.log(`Zeus review already completed: ${payload.verdict ?? "unknown"}.`);
@@ -24727,10 +24730,14 @@ async function drainTerminalBuildLogs(config2, businessId, buildId, options) {
24727
24730
  }
24728
24731
  }
24729
24732
  async function waitForBuild(config2, businessId, buildId, options) {
24730
- let afterSequence;
24733
+ let afterSequence = options.afterSequence;
24731
24734
  for (; ; ) {
24732
24735
  if (options.follow) {
24733
- const result = await fetchAndEmitBuildLogs(config2, businessId, buildId, { afterSequence, json: options.json });
24736
+ const result = await fetchAndEmitBuildLogs(config2, businessId, buildId, {
24737
+ afterSequence,
24738
+ limit: options.limit,
24739
+ json: options.json
24740
+ });
24734
24741
  afterSequence = result.afterSequence;
24735
24742
  }
24736
24743
  const status = sandboxLayerCliBuildRequestResponseSchema.parse(
@@ -25009,24 +25016,29 @@ async function sandboxLogsCommand(buildId, options = {}, command) {
25009
25016
  if (limit != null && (!Number.isInteger(limit) || limit < 1)) {
25010
25017
  throw new CliError("user", "--limit must be a positive integer.");
25011
25018
  }
25012
- for (; ; ) {
25013
- const payload = sandboxLayerCliBuildLogsResponseSchema.parse(
25014
- await apiFetch(config2, buildLogsPath(businessId, buildId, { afterSequence, limit }))
25015
- );
25016
- if (payload.logs.length > 0 || !options.follow) {
25017
- if (isJson(command, options)) writeJson(options.follow ? { type: "logs", logs: payload.logs } : payload);
25018
- else printLogs(payload.logs);
25019
- }
25020
- if (!options.follow) return;
25021
- if (payload.logs.length > 0) afterSequence = payload.logs[payload.logs.length - 1].sequence;
25022
- await sleep(
25023
- parsePollInterval(options.pollInterval, {
25019
+ const json2 = isJson(command, options);
25020
+ if (options.follow) {
25021
+ const finalBuild = await waitForBuild(config2, businessId, buildId, {
25022
+ follow: true,
25023
+ pollIntervalMs: parsePollInterval(options.pollInterval, {
25024
25024
  defaultMs: DEFAULT_POLL_INTERVAL_MS,
25025
25025
  minMs: MIN_POLL_INTERVAL_MS,
25026
25026
  maxMs: null
25027
- })
25028
- );
25027
+ }),
25028
+ json: json2,
25029
+ afterSequence,
25030
+ limit
25031
+ });
25032
+ if (json2) writeJson({ type: "build", buildRequest: finalBuild });
25033
+ else printTerminalBuildSummary(finalBuild);
25034
+ if (finalBuild.status !== "completed") process.exitCode = 1;
25035
+ return;
25029
25036
  }
25037
+ const payload = sandboxLayerCliBuildLogsResponseSchema.parse(
25038
+ await apiFetch(config2, buildLogsPath(businessId, buildId, { afterSequence, limit }))
25039
+ );
25040
+ if (json2) writeJson(payload);
25041
+ else printLogs(payload.logs);
25030
25042
  }
25031
25043
  async function sandboxAssignDefaultCommand(sourceRepoArg, options = {}, command) {
25032
25044
  const { config: config2 } = resolveBusinessContext(command, options);
@@ -26640,7 +26652,20 @@ function parseNonNegativeInteger(raw, name, defaultValue) {
26640
26652
  if (!/^\d+$/.test(raw)) {
26641
26653
  throw new CliError("user", `${name} must be a non-negative integer.`);
26642
26654
  }
26643
- return Number(raw);
26655
+ const value = Number(raw);
26656
+ if (!Number.isSafeInteger(value)) {
26657
+ throw new CliError("user", `${name} must be a non-negative integer.`);
26658
+ }
26659
+ return value;
26660
+ }
26661
+ function throwIfFollowTimedOut(deadline) {
26662
+ if (deadline !== null && Date.now() >= deadline) {
26663
+ throw new CliError("server", "Timed out waiting for session events.");
26664
+ }
26665
+ }
26666
+ function remainingFollowMs(deadline) {
26667
+ if (deadline === null) return void 0;
26668
+ return Math.max(0, deadline - Date.now());
26644
26669
  }
26645
26670
  function formatStatusLine(status) {
26646
26671
  const label = status.phase ?? "unknown";
@@ -26690,6 +26715,8 @@ async function watchCommand(sessionId, options, command) {
26690
26715
  const pollIntervalMs = parsePollInterval(options.pollInterval);
26691
26716
  const initialAfterSequence = parseNonNegativeInteger(options.afterSequence, "--after-sequence", 0);
26692
26717
  const pageSize = parseNonNegativeInteger(options.limit, "--limit", WATCH_REPLAY_PAGE_SIZE);
26718
+ const timeoutMs = parseNonNegativeInteger(options.timeout, "--timeout", 0);
26719
+ const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : null;
26693
26720
  if (pageSize === 0) {
26694
26721
  throw new CliError("user", "--limit must be greater than 0.");
26695
26722
  }
@@ -26714,11 +26741,23 @@ async function watchCommand(sessionId, options, command) {
26714
26741
  if (!json2) console.log(`Watching session ${sessionId}...`);
26715
26742
  try {
26716
26743
  while (true) {
26744
+ throwIfFollowTimedOut(deadline);
26717
26745
  const query = new URLSearchParams({
26718
26746
  afterSequence: String(afterSequence),
26719
26747
  limit: String(pageSize)
26720
26748
  });
26721
- const payload = await apiFetchText(config2, `/api/sessions/${sessionId}/events?${query}`);
26749
+ const remainingMs = remainingFollowMs(deadline);
26750
+ let payload;
26751
+ try {
26752
+ payload = await apiFetchText(
26753
+ config2,
26754
+ `/api/sessions/${sessionId}/events?${query}`,
26755
+ remainingMs === void 0 ? void 0 : { timeoutMs: remainingMs }
26756
+ );
26757
+ } catch (err) {
26758
+ throwIfFollowTimedOut(deadline);
26759
+ throw err;
26760
+ }
26722
26761
  const parsed = parseSsePayload(payload);
26723
26762
  const receivedFullPage = parsed.events.length >= pageSize;
26724
26763
  if (!json2 && parsed.status) {
@@ -26759,7 +26798,10 @@ async function watchCommand(sessionId, options, command) {
26759
26798
  }
26760
26799
  console.log(rendered.line);
26761
26800
  }
26762
- if (receivedFullPage) continue;
26801
+ if (receivedFullPage) {
26802
+ throwIfFollowTimedOut(deadline);
26803
+ continue;
26804
+ }
26763
26805
  if (parsed.status?.phase && isWatchTerminal(parsed.status.phase)) {
26764
26806
  if (!json2 && parsed.status.phase === "completed") {
26765
26807
  if (textOpen) {
@@ -26770,7 +26812,10 @@ async function watchCommand(sessionId, options, command) {
26770
26812
  }
26771
26813
  break;
26772
26814
  }
26773
- await sleep(pollIntervalMs);
26815
+ throwIfFollowTimedOut(deadline);
26816
+ const sleepMs = remainingFollowMs(deadline);
26817
+ await sleep(sleepMs === void 0 ? pollIntervalMs : Math.min(pollIntervalMs, sleepMs));
26818
+ throwIfFollowTimedOut(deadline);
26774
26819
  }
26775
26820
  } finally {
26776
26821
  if (textOpen) process.stdout.write("\n");
@@ -27565,7 +27610,7 @@ JSON:
27565
27610
  JSON mode returns {sessions, nextCursor}. --all follows cursors until nextCursor is null.
27566
27611
  `
27567
27612
  ).action((options, command) => listSessionsCommand(options, command));
27568
- sessions.command("events").description("Read or follow session replay events").argument("<session-id>", "Session ID").option("--after-sequence <n>", "Return events after this sequence").option("--after <n>", "Alias for --after-sequence").option("--before-sequence <n>", "Return events before this sequence").option("--before <n>", "Alias for --before-sequence").option("--prompt-id <id>", "Filter events by prompt ID").option("--limit <n>", "Maximum events to return").option("--follow", "Follow events until a terminal phase").option("--poll-interval <ms>", "Polling interval in milliseconds", String(DEFAULT_WATCH_POLL_INTERVAL_MS)).addHelpText(
27613
+ sessions.command("events").description("Read or follow session replay events").argument("<session-id>", "Session ID").option("--after-sequence <n>", "Return events after this sequence").option("--after <n>", "Alias for --after-sequence").option("--before-sequence <n>", "Return events before this sequence").option("--before <n>", "Alias for --before-sequence").option("--prompt-id <id>", "Filter events by prompt ID").option("--limit <n>", "Maximum events to return").option("--follow", "Follow events until a terminal phase").option("--poll-interval <ms>", "Polling interval in milliseconds", String(DEFAULT_WATCH_POLL_INTERVAL_MS)).option("--timeout <ms>", "Stop following after this many milliseconds; 0 or omitted means unbounded").addHelpText(
27569
27614
  "after",
27570
27615
  `
27571
27616
  Examples:
@@ -27573,10 +27618,12 @@ Examples:
27573
27618
  arcanist sessions events <session-id> --after-sequence 540 --limit 250 --json
27574
27619
  arcanist sessions events <session-id> --after 540 --limit 250 --json
27575
27620
  arcanist sessions events <session-id> --follow --json
27621
+ arcanist sessions events <session-id> --follow --timeout 60000 --json
27576
27622
 
27577
27623
  JSON:
27578
27624
  JSON mode without --follow returns one JSON object from /events/history: {events: [...], ...}; canonical events carry phase.
27579
27625
  --follow --json emits NDJSON, one {sequence, type, data} object per line, until a terminal phase: ${formatTerminalPhaseHelpList(TERMINAL_PHASES_ARRAY)}.
27626
+ --timeout bounds follow; 0 or omitted means unbounded. Process exit is a terminal phase, not session_idle.
27580
27627
  Actionable follow types are NDJSON signals, not process-exit conditions: pr_created/pr_updated use data.prUrl; question uses data.question; prompt_completed, prompt_failed, and session_idle do not exit the process.
27581
27628
  `
27582
27629
  ).action((sessionId, options, command) => sessionEventsCommand(sessionId, options, command));
@@ -27683,7 +27730,7 @@ sandbox.command("logs").description("Print sandbox layer build logs").argument("
27683
27730
  `
27684
27731
  JSON:
27685
27732
  Without --follow, --json prints one object: {ok, logs}
27686
- With --follow, --json emits NDJSON lines for non-empty batches: {type:"logs", logs:[...]}
27733
+ With --follow, --json emits {type:"logs"} lines, then a terminal {type:"build"} line, and exits.
27687
27734
  `
27688
27735
  ).action((buildId, options, command) => sandboxLogsCommand(buildId, options, command));
27689
27736
  sandbox.command("rebuild-stale").description("Create stale sandbox template rebuild campaigns").option("--business <id>", "Business ID to scan; defaults to authenticated user's business").option("--all", "Scan all businesses").option("--dry-run", "Preview stale rebuilds without queueing provider builds").option("--yes", "Start a rebuild campaign").option("--follow", "Follow campaign progress").action((options, command) => sandboxRebuildStaleCommand(options, command));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.346",
3
+ "version": "0.1.348",
4
4
  "description": "Command-line interface for Arcanist: inspect sessions, prepare Zeus environments, and trigger reviews",
5
5
  "type": "module",
6
6
  "bin": {