@tryarcanist/cli 0.1.345 → 0.1.347

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 -26
  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.345",
7412
+ version: "0.1.347",
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
  });
@@ -24727,10 +24737,14 @@ async function drainTerminalBuildLogs(config2, businessId, buildId, options) {
24727
24737
  }
24728
24738
  }
24729
24739
  async function waitForBuild(config2, businessId, buildId, options) {
24730
- let afterSequence;
24740
+ let afterSequence = options.afterSequence;
24731
24741
  for (; ; ) {
24732
24742
  if (options.follow) {
24733
- const result = await fetchAndEmitBuildLogs(config2, businessId, buildId, { afterSequence, json: options.json });
24743
+ const result = await fetchAndEmitBuildLogs(config2, businessId, buildId, {
24744
+ afterSequence,
24745
+ limit: options.limit,
24746
+ json: options.json
24747
+ });
24734
24748
  afterSequence = result.afterSequence;
24735
24749
  }
24736
24750
  const status = sandboxLayerCliBuildRequestResponseSchema.parse(
@@ -25009,24 +25023,29 @@ async function sandboxLogsCommand(buildId, options = {}, command) {
25009
25023
  if (limit != null && (!Number.isInteger(limit) || limit < 1)) {
25010
25024
  throw new CliError("user", "--limit must be a positive integer.");
25011
25025
  }
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, {
25026
+ const json2 = isJson(command, options);
25027
+ if (options.follow) {
25028
+ const finalBuild = await waitForBuild(config2, businessId, buildId, {
25029
+ follow: true,
25030
+ pollIntervalMs: parsePollInterval(options.pollInterval, {
25024
25031
  defaultMs: DEFAULT_POLL_INTERVAL_MS,
25025
25032
  minMs: MIN_POLL_INTERVAL_MS,
25026
25033
  maxMs: null
25027
- })
25028
- );
25034
+ }),
25035
+ json: json2,
25036
+ afterSequence,
25037
+ limit
25038
+ });
25039
+ if (json2) writeJson({ type: "build", buildRequest: finalBuild });
25040
+ else printTerminalBuildSummary(finalBuild);
25041
+ if (finalBuild.status !== "completed") process.exitCode = 1;
25042
+ return;
25029
25043
  }
25044
+ const payload = sandboxLayerCliBuildLogsResponseSchema.parse(
25045
+ await apiFetch(config2, buildLogsPath(businessId, buildId, { afterSequence, limit }))
25046
+ );
25047
+ if (json2) writeJson(payload);
25048
+ else printLogs(payload.logs);
25030
25049
  }
25031
25050
  async function sandboxAssignDefaultCommand(sourceRepoArg, options = {}, command) {
25032
25051
  const { config: config2 } = resolveBusinessContext(command, options);
@@ -26640,7 +26659,20 @@ function parseNonNegativeInteger(raw, name, defaultValue) {
26640
26659
  if (!/^\d+$/.test(raw)) {
26641
26660
  throw new CliError("user", `${name} must be a non-negative integer.`);
26642
26661
  }
26643
- return Number(raw);
26662
+ const value = Number(raw);
26663
+ if (!Number.isSafeInteger(value)) {
26664
+ throw new CliError("user", `${name} must be a non-negative integer.`);
26665
+ }
26666
+ return value;
26667
+ }
26668
+ function throwIfFollowTimedOut(deadline) {
26669
+ if (deadline !== null && Date.now() >= deadline) {
26670
+ throw new CliError("server", "Timed out waiting for session events.");
26671
+ }
26672
+ }
26673
+ function remainingFollowMs(deadline) {
26674
+ if (deadline === null) return void 0;
26675
+ return Math.max(0, deadline - Date.now());
26644
26676
  }
26645
26677
  function formatStatusLine(status) {
26646
26678
  const label = status.phase ?? "unknown";
@@ -26690,6 +26722,8 @@ async function watchCommand(sessionId, options, command) {
26690
26722
  const pollIntervalMs = parsePollInterval(options.pollInterval);
26691
26723
  const initialAfterSequence = parseNonNegativeInteger(options.afterSequence, "--after-sequence", 0);
26692
26724
  const pageSize = parseNonNegativeInteger(options.limit, "--limit", WATCH_REPLAY_PAGE_SIZE);
26725
+ const timeoutMs = parseNonNegativeInteger(options.timeout, "--timeout", 0);
26726
+ const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : null;
26693
26727
  if (pageSize === 0) {
26694
26728
  throw new CliError("user", "--limit must be greater than 0.");
26695
26729
  }
@@ -26714,11 +26748,23 @@ async function watchCommand(sessionId, options, command) {
26714
26748
  if (!json2) console.log(`Watching session ${sessionId}...`);
26715
26749
  try {
26716
26750
  while (true) {
26751
+ throwIfFollowTimedOut(deadline);
26717
26752
  const query = new URLSearchParams({
26718
26753
  afterSequence: String(afterSequence),
26719
26754
  limit: String(pageSize)
26720
26755
  });
26721
- const payload = await apiFetchText(config2, `/api/sessions/${sessionId}/events?${query}`);
26756
+ const remainingMs = remainingFollowMs(deadline);
26757
+ let payload;
26758
+ try {
26759
+ payload = await apiFetchText(
26760
+ config2,
26761
+ `/api/sessions/${sessionId}/events?${query}`,
26762
+ remainingMs === void 0 ? void 0 : { timeoutMs: remainingMs }
26763
+ );
26764
+ } catch (err) {
26765
+ throwIfFollowTimedOut(deadline);
26766
+ throw err;
26767
+ }
26722
26768
  const parsed = parseSsePayload(payload);
26723
26769
  const receivedFullPage = parsed.events.length >= pageSize;
26724
26770
  if (!json2 && parsed.status) {
@@ -26759,7 +26805,10 @@ async function watchCommand(sessionId, options, command) {
26759
26805
  }
26760
26806
  console.log(rendered.line);
26761
26807
  }
26762
- if (receivedFullPage) continue;
26808
+ if (receivedFullPage) {
26809
+ throwIfFollowTimedOut(deadline);
26810
+ continue;
26811
+ }
26763
26812
  if (parsed.status?.phase && isWatchTerminal(parsed.status.phase)) {
26764
26813
  if (!json2 && parsed.status.phase === "completed") {
26765
26814
  if (textOpen) {
@@ -26770,7 +26819,10 @@ async function watchCommand(sessionId, options, command) {
26770
26819
  }
26771
26820
  break;
26772
26821
  }
26773
- await sleep(pollIntervalMs);
26822
+ throwIfFollowTimedOut(deadline);
26823
+ const sleepMs = remainingFollowMs(deadline);
26824
+ await sleep(sleepMs === void 0 ? pollIntervalMs : Math.min(pollIntervalMs, sleepMs));
26825
+ throwIfFollowTimedOut(deadline);
26774
26826
  }
26775
26827
  } finally {
26776
26828
  if (textOpen) process.stdout.write("\n");
@@ -27565,7 +27617,7 @@ JSON:
27565
27617
  JSON mode returns {sessions, nextCursor}. --all follows cursors until nextCursor is null.
27566
27618
  `
27567
27619
  ).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(
27620
+ 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
27621
  "after",
27570
27622
  `
27571
27623
  Examples:
@@ -27573,10 +27625,12 @@ Examples:
27573
27625
  arcanist sessions events <session-id> --after-sequence 540 --limit 250 --json
27574
27626
  arcanist sessions events <session-id> --after 540 --limit 250 --json
27575
27627
  arcanist sessions events <session-id> --follow --json
27628
+ arcanist sessions events <session-id> --follow --timeout 60000 --json
27576
27629
 
27577
27630
  JSON:
27578
27631
  JSON mode without --follow returns one JSON object from /events/history: {events: [...], ...}; canonical events carry phase.
27579
27632
  --follow --json emits NDJSON, one {sequence, type, data} object per line, until a terminal phase: ${formatTerminalPhaseHelpList(TERMINAL_PHASES_ARRAY)}.
27633
+ --timeout bounds follow; 0 or omitted means unbounded. Process exit is a terminal phase, not session_idle.
27580
27634
  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
27635
  `
27582
27636
  ).action((sessionId, options, command) => sessionEventsCommand(sessionId, options, command));
@@ -27683,7 +27737,7 @@ sandbox.command("logs").description("Print sandbox layer build logs").argument("
27683
27737
  `
27684
27738
  JSON:
27685
27739
  Without --follow, --json prints one object: {ok, logs}
27686
- With --follow, --json emits NDJSON lines for non-empty batches: {type:"logs", logs:[...]}
27740
+ With --follow, --json emits {type:"logs"} lines, then a terminal {type:"build"} line, and exits.
27687
27741
  `
27688
27742
  ).action((buildId, options, command) => sandboxLogsCommand(buildId, options, command));
27689
27743
  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.345",
3
+ "version": "0.1.347",
4
4
  "description": "Command-line interface for Arcanist: inspect sessions, prepare Zeus environments, and trigger reviews",
5
5
  "type": "module",
6
6
  "bin": {