@tryarcanist/cli 0.1.221 → 0.1.223

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 +304 -311
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -69,6 +69,15 @@ function parseApiErrorBody(body) {
69
69
  return null;
70
70
  }
71
71
  }
72
+ function parseConflictBody(err) {
73
+ if (!(err instanceof ApiError) || err.status !== 409) return null;
74
+ try {
75
+ const parsed = JSON.parse(err.body);
76
+ return parsed && typeof parsed === "object" ? parsed : null;
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
72
81
 
73
82
  // src/errors.ts
74
83
  var EXIT_CODE_INTERRUPTED = 130;
@@ -967,58 +976,97 @@ async function agentSubscriptionLogoutCommand(config, options, command) {
967
976
  );
968
977
  }
969
978
 
970
- // src/commands/review.ts
971
- var PR_REVIEW_POLL_INTERVAL_MS = 1e4;
972
- var PR_REVIEW_WAIT_TIMEOUT_MS = 30 * 60 * 1e3;
973
- async function reviewCommand(prUrl, options = {}, command) {
974
- assertArcanistSessionMutationAllowed("review");
975
- const { config } = resolveBusinessContext(command, options);
976
- const trigger = await apiFetch(config, "/api/pr-reviews", {
977
- method: "POST",
978
- body: JSON.stringify({
979
- prUrl,
980
- ...options.focus ? { focus: options.focus } : {},
981
- // Send an explicitly passed model even when empty (e.g. --model "$VAR" with an
982
- // unset VAR) so the server rejects it as invalid instead of silently routing.
983
- ...options.model !== void 0 ? { model: options.model } : {}
984
- })
985
- });
986
- if (!options.wait || trigger.cached) {
987
- emit(command, options, trigger, (payload) => {
988
- if (payload.cached) console.log(`Zeus review already completed: ${payload.verdict ?? "unknown"}.`);
989
- else console.log(`Started Zeus review${payload.sessionId ? ` (${payload.sessionId})` : ""}.`);
990
- });
991
- return;
979
+ // ../../shared/utils/timing.ts
980
+ function sleep(ms) {
981
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
982
+ }
983
+
984
+ // src/status-poll.ts
985
+ async function pollUntilSettled(opts) {
986
+ const deadline = Date.now() + opts.timeoutMs;
987
+ while (Date.now() < deadline) {
988
+ const settled = await opts.fetchStatus();
989
+ if (settled !== null) return settled;
990
+ await sleep(opts.pollIntervalMs);
992
991
  }
993
- const result = await waitForReview(config, prUrl, trigger.sessionId ?? null);
994
- emit(command, options, result, (payload) => {
995
- console.log(`Zeus review: ${sanitizeTerminalText(payload.verdict)}. ${payload.findings.length} finding(s).`);
996
- for (const finding of payload.findings) {
997
- console.log(
998
- `${sanitizeTerminalText(finding.severity)} ${sanitizeTerminalText(finding.file)}:${finding.line} - ${sanitizeTerminalText(finding.title)}`
999
- );
1000
- }
1001
- });
992
+ throw new CliError("server", opts.timeoutMessage);
1002
993
  }
1003
- function sanitizeTerminalText(value) {
1004
- return value.replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g, "").replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u001f\u007f]/g, "");
994
+ function assertSameAttempt(triggeredSessionId, settledSessionId, label) {
995
+ if (triggeredSessionId && settledSessionId !== triggeredSessionId) {
996
+ throw new CliError("conflict", `${label} completed for a different attempt; retry the command.`);
997
+ }
1005
998
  }
1006
- async function waitForReview(config, prUrl, sessionId) {
1007
- const deadline = Date.now() + PR_REVIEW_WAIT_TIMEOUT_MS;
1008
- while (Date.now() < deadline) {
1009
- const status = await apiFetch(
1010
- config,
1011
- `/api/pr-reviews/status?prUrl=${encodeURIComponent(prUrl)}`
1012
- );
1013
- if (status.status === "completed") {
1014
- if (sessionId && status.sessionId !== sessionId) {
1015
- throw new CliError("conflict", "PR review completed for a different attempt; retry the command.");
1016
- }
1017
- return { ...status, sessionId: status.sessionId ?? sessionId };
1018
- }
1019
- await new Promise((resolve2) => setTimeout(resolve2, PR_REVIEW_POLL_INTERVAL_MS));
999
+
1000
+ // ../../shared/session/phase.ts
1001
+ var PHASES = [
1002
+ "idle",
1003
+ "running",
1004
+ "waiting_for_input",
1005
+ "finalizing",
1006
+ "review_listening",
1007
+ "completed",
1008
+ "superseded",
1009
+ "needs_you",
1010
+ "blocked",
1011
+ "failed",
1012
+ "stopped",
1013
+ "archived"
1014
+ ];
1015
+ var TERMINAL_PHASES_ARRAY = [
1016
+ "completed",
1017
+ "superseded",
1018
+ "needs_you",
1019
+ "blocked",
1020
+ "failed",
1021
+ "stopped",
1022
+ "archived"
1023
+ ];
1024
+ var TERMINAL_PHASES = new Set(TERMINAL_PHASES_ARRAY);
1025
+ var TERMINAL_FOR_FALLBACK_POLLING_PHASES = new Set(
1026
+ TERMINAL_PHASES_ARRAY.filter((phase) => phase !== "completed")
1027
+ );
1028
+ var CHILD_SLOT_RELEASE_PHASES = new Set(
1029
+ TERMINAL_PHASES_ARRAY.filter((phase) => phase !== "stopped")
1030
+ );
1031
+ var ARCHIVABLE_STALE_TERMINAL_PHASES_ARRAY = TERMINAL_PHASES_ARRAY.filter(
1032
+ (phase) => phase !== "archived" && phase !== "needs_you" && phase !== "blocked" && phase !== "stopped"
1033
+ );
1034
+ function isTerminalPhase(phase, _sessionKind) {
1035
+ return TERMINAL_PHASES.has(phase);
1036
+ }
1037
+
1038
+ // src/constants/watch.ts
1039
+ var MIN_WATCH_POLL_INTERVAL_MS = 250;
1040
+ var DEFAULT_WATCH_POLL_INTERVAL_MS = 1e3;
1041
+ var MAX_WATCH_POLL_INTERVAL_MS = 6e4;
1042
+ var WATCH_REPLAY_PAGE_SIZE = 200;
1043
+ function isWatchTerminal(phase) {
1044
+ return isTerminalPhase(phase);
1045
+ }
1046
+
1047
+ // src/utils/poll-interval.ts
1048
+ function clampPollInterval(ms, minimum) {
1049
+ return Math.max(ms, minimum);
1050
+ }
1051
+ function parsePollInterval(raw, opts = {}) {
1052
+ const defaultMs = opts.defaultMs ?? DEFAULT_WATCH_POLL_INTERVAL_MS;
1053
+ const minMs = opts.minMs ?? MIN_WATCH_POLL_INTERVAL_MS;
1054
+ const maxMs = opts.maxMs === void 0 ? MAX_WATCH_POLL_INTERVAL_MS : opts.maxMs;
1055
+ if (!raw) return defaultMs;
1056
+ if (!/^\d+$/.test(raw)) {
1057
+ throw new CliError("user", "Polling interval must be a non-negative integer.");
1058
+ }
1059
+ const value = Number(raw);
1060
+ if (value < minMs || maxMs !== null && value > maxMs) {
1061
+ const range = maxMs === null ? `at least ${minMs}` : `between ${minMs} and ${maxMs}`;
1062
+ throw new CliError("user", `Polling interval must be ${range} milliseconds.`);
1020
1063
  }
1021
- throw new CliError("server", "Timed out waiting for Zeus review results.");
1064
+ return value;
1065
+ }
1066
+
1067
+ // src/utils/terminal-text.ts
1068
+ function sanitizeTerminalText(value) {
1069
+ return value.replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g, "").replace(/\u001b\[[0-?]*[ -\/]*[@-~]/g, "").replace(/[\u0000-\u001f\u007f]/g, "");
1022
1070
  }
1023
1071
 
1024
1072
  // src/commands/anubis.ts
@@ -1026,6 +1074,7 @@ var ANUBIS_POLL_INTERVAL_MS = 15e3;
1026
1074
  var ANUBIS_WAIT_TIMEOUT_MS = 45 * 60 * 1e3;
1027
1075
  async function anubisCommand(prUrl, options = {}, command) {
1028
1076
  assertArcanistSessionMutationAllowed("anubis");
1077
+ const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: ANUBIS_POLL_INTERVAL_MS, maxMs: null }) : null;
1029
1078
  const { config } = resolveBusinessContext(command, options);
1030
1079
  const trigger = await apiFetch(config, "/api/anubis-runs", {
1031
1080
  method: "POST",
@@ -1037,29 +1086,25 @@ async function anubisCommand(prUrl, options = {}, command) {
1037
1086
  });
1038
1087
  return;
1039
1088
  }
1040
- const result = await waitForAnubis(config, prUrl, trigger.sessionId);
1089
+ const result = await pollUntilSettled({
1090
+ pollIntervalMs: pollIntervalMs ?? ANUBIS_POLL_INTERVAL_MS,
1091
+ timeoutMs: ANUBIS_WAIT_TIMEOUT_MS,
1092
+ timeoutMessage: "Timed out waiting for Anubis results.",
1093
+ fetchStatus: async () => {
1094
+ const status = await apiFetch(
1095
+ config,
1096
+ `/api/anubis-runs/status?prUrl=${encodeURIComponent(prUrl)}`
1097
+ );
1098
+ if (status.sessionStatus === "active") return null;
1099
+ assertSameAttempt(trigger.sessionId, status.sessionId, "Anubis");
1100
+ return status;
1101
+ }
1102
+ });
1041
1103
  emit(command, options, result, (payload) => {
1042
1104
  console.log(`Anubis: ${sanitizeTerminalText(payload.verdict ?? "could-not-verify")}.`);
1043
1105
  if (payload.summary) console.log(sanitizeTerminalText(payload.summary));
1044
1106
  });
1045
1107
  }
1046
- async function waitForAnubis(config, prUrl, sessionId) {
1047
- const deadline = Date.now() + ANUBIS_WAIT_TIMEOUT_MS;
1048
- while (Date.now() < deadline) {
1049
- const status = await apiFetch(
1050
- config,
1051
- `/api/anubis-runs/status?prUrl=${encodeURIComponent(prUrl)}`
1052
- );
1053
- if (status.sessionStatus !== "active") {
1054
- if (status.sessionId !== sessionId) {
1055
- throw new CliError("conflict", "Anubis completed for a different attempt; retry the command.");
1056
- }
1057
- return status;
1058
- }
1059
- await new Promise((resolve2) => setTimeout(resolve2, ANUBIS_POLL_INTERVAL_MS));
1060
- }
1061
- throw new CliError("server", "Timed out waiting for Anubis results.");
1062
- }
1063
1108
 
1064
1109
  // src/commands/auth.ts
1065
1110
  async function whoamiCommand(options, command) {
@@ -1842,6 +1887,26 @@ var SESSION_START_MODEL_PROVIDER_GROUPS_BY_SUBSCRIPTIONS = {
1842
1887
  "true:true": buildSubscriptionAwareGroups({ codexSubscription: true, grokSubscription: true })
1843
1888
  };
1844
1889
 
1890
+ // src/utils/pagination.ts
1891
+ var MAX_ALL_PAGES = 1e3;
1892
+ async function fetchPages(label, all, initialCursor, fetchPage) {
1893
+ const items = [];
1894
+ let cursor2 = initialCursor;
1895
+ let nextCursor = null;
1896
+ let pageCount = 0;
1897
+ do {
1898
+ pageCount += 1;
1899
+ if (all && pageCount > MAX_ALL_PAGES) {
1900
+ throw new CliError("user", `${label} exceeded ${MAX_ALL_PAGES} pages without reaching the end.`);
1901
+ }
1902
+ const page = await fetchPage(cursor2);
1903
+ items.push(...page.items);
1904
+ nextCursor = page.nextCursor;
1905
+ cursor2 = nextCursor ?? void 0;
1906
+ } while (all && nextCursor);
1907
+ return { items, nextCursor: all ? null : nextCursor };
1908
+ }
1909
+
1845
1910
  // ../../shared/github/repo-url.ts
1846
1911
  function parseGithubRepoFullName(value) {
1847
1912
  const shorthand = value.match(/^([a-zA-Z0-9_.-]+)\/([a-zA-Z0-9_.-]+)$/);
@@ -1853,6 +1918,32 @@ function parseGithubRepoFullName(value) {
1853
1918
  return null;
1854
1919
  }
1855
1920
 
1921
+ // src/git.ts
1922
+ import { execFileSync as execFileSync2 } from "child_process";
1923
+ function git(args) {
1924
+ try {
1925
+ return execFileSync2("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
1926
+ } catch (err) {
1927
+ throw new CliError("user", `git ${args.join(" ")} failed: ${stringifyError(err)}`);
1928
+ }
1929
+ }
1930
+ function currentRepo() {
1931
+ const parsed = parseGithubRepoFullName(git(["remote", "get-url", "origin"]));
1932
+ if (!parsed) throw new CliError("user", "origin remote must point at a GitHub repository");
1933
+ return { owner: parsed.owner, repo: parsed.repo };
1934
+ }
1935
+
1936
+ // src/utils/repo-arg.ts
1937
+ function parseRepoArg(value, argName = "repo") {
1938
+ const parsed = parseGithubRepoFullName(value);
1939
+ if (!parsed) throw new CliError("user", `${argName} must be owner/name or a GitHub URL`);
1940
+ return { owner: parsed.owner, repo: parsed.repo.replace(/\.git$/, "") };
1941
+ }
1942
+ function parseRepoArgOrCurrent(value, argName = "repo") {
1943
+ if (value === void 0) return currentRepo();
1944
+ return parseRepoArg(value, argName);
1945
+ }
1946
+
1856
1947
  // src/commands/automations.ts
1857
1948
  var AUTOMATION_ERROR_HINTS = {
1858
1949
  invalid_cron: "Use a standard five-field cron expression, for example: */15 * * * *",
@@ -1869,9 +1960,8 @@ var AUTOMATION_ERROR_HINTS = {
1869
1960
  repo_skills_unavailable: "Arcanist could not verify the repo's skills right now. Retry once GitHub skill discovery is healthy.",
1870
1961
  unknown_skill: "That leading slash skill does not exist in the selected repository."
1871
1962
  };
1872
- var MAX_ALL_PAGES = 1e3;
1873
1963
  async function createAutomationCommand(repoUrl, promptArg, options, command) {
1874
- const repo = parseAutomationRepo(repoUrl);
1964
+ const repo = parseRepoArg(repoUrl, "repo-url");
1875
1965
  const prompt = await resolvePromptInput(promptArg, options);
1876
1966
  const slackTeamId = options.slackTeamId?.trim();
1877
1967
  const slackChannelId = options.slackChannelId?.trim();
@@ -1918,27 +2008,22 @@ async function createAutomationCommand(repoUrl, promptArg, options, command) {
1918
2008
  async function listAutomationsCommand(options, command) {
1919
2009
  const runtime = getRuntimeOptions(command, options);
1920
2010
  const config = requireConfig(runtime);
1921
- const items = [];
1922
- let cursor2 = options.cursor;
1923
- let nextCursor = null;
1924
- let pageCount = 0;
1925
- do {
1926
- pageCount += 1;
1927
- if (options.all === true && pageCount > MAX_ALL_PAGES) {
1928
- throw new CliError("user", `automations list --all exceeded ${MAX_ALL_PAGES} pages without reaching the end.`);
2011
+ const { items, nextCursor } = await fetchPages(
2012
+ "automations list --all",
2013
+ options.all === true,
2014
+ options.cursor,
2015
+ async (cursor2) => {
2016
+ const query = new URLSearchParams();
2017
+ if (options.limit) query.set("limit", options.limit);
2018
+ if (cursor2) query.set("cursor", cursor2);
2019
+ const payload = await automationApiFetch(
2020
+ config,
2021
+ `/api/automation/schedules${query.size ? `?${query.toString()}` : ""}`
2022
+ );
2023
+ return { items: payload.data.items, nextCursor: payload.data.nextCursor };
1929
2024
  }
1930
- const query = new URLSearchParams();
1931
- if (options.limit) query.set("limit", options.limit);
1932
- if (cursor2) query.set("cursor", cursor2);
1933
- const payload = await automationApiFetch(
1934
- config,
1935
- `/api/automation/schedules${query.size ? `?${query.toString()}` : ""}`
1936
- );
1937
- items.push(...payload.data.items);
1938
- nextCursor = payload.data.nextCursor;
1939
- cursor2 = nextCursor ?? void 0;
1940
- } while (options.all === true && nextCursor);
1941
- const output = { items, nextCursor: options.all === true ? null : nextCursor };
2025
+ );
2026
+ const output = { items, nextCursor };
1942
2027
  if (isJson(command, options)) {
1943
2028
  writeJson(output);
1944
2029
  return;
@@ -1970,14 +2055,6 @@ async function deleteAutomationCommand(id, options, command) {
1970
2055
  }
1971
2056
  console.log(`Deleted automation ${id}.`);
1972
2057
  }
1973
- function parseAutomationRepo(value) {
1974
- if (/^git@/i.test(value) && !/^git@github\.com:/i.test(value)) {
1975
- throw new CliError("user", "repo-url must be owner/name or a GitHub URL.");
1976
- }
1977
- const parsed = parseGithubRepoFullName(value);
1978
- if (!parsed) throw new CliError("user", "repo-url must be owner/name or a GitHub URL.");
1979
- return { owner: parsed.owner, repo: parsed.repo };
1980
- }
1981
2058
  function resolveAutomationModelBackend(rawModel, normalizedModel) {
1982
2059
  if (normalizedModel) {
1983
2060
  const backend = getAgentRuntimeBackendForModel(normalizedModel);
@@ -2185,58 +2262,6 @@ async function codexLogoutCommand(options, command) {
2185
2262
  emit(command, options, payload, () => console.log("Codex subscription auth deactivated and cleared."));
2186
2263
  }
2187
2264
 
2188
- // ../../shared/utils/timing.ts
2189
- function sleep(ms) {
2190
- return new Promise((resolve2) => setTimeout(resolve2, ms));
2191
- }
2192
-
2193
- // ../../shared/session/phase.ts
2194
- var PHASES = [
2195
- "idle",
2196
- "running",
2197
- "waiting_for_input",
2198
- "finalizing",
2199
- "review_listening",
2200
- "completed",
2201
- "superseded",
2202
- "needs_you",
2203
- "blocked",
2204
- "failed",
2205
- "stopped",
2206
- "archived"
2207
- ];
2208
- var TERMINAL_PHASES_ARRAY = [
2209
- "completed",
2210
- "superseded",
2211
- "needs_you",
2212
- "blocked",
2213
- "failed",
2214
- "stopped",
2215
- "archived"
2216
- ];
2217
- var TERMINAL_PHASES = new Set(TERMINAL_PHASES_ARRAY);
2218
- var TERMINAL_FOR_FALLBACK_POLLING_PHASES = new Set(
2219
- TERMINAL_PHASES_ARRAY.filter((phase) => phase !== "completed")
2220
- );
2221
- var CHILD_SLOT_RELEASE_PHASES = new Set(
2222
- TERMINAL_PHASES_ARRAY.filter((phase) => phase !== "stopped")
2223
- );
2224
- var ARCHIVABLE_STALE_TERMINAL_PHASES_ARRAY = TERMINAL_PHASES_ARRAY.filter(
2225
- (phase) => phase !== "archived" && phase !== "needs_you" && phase !== "blocked" && phase !== "stopped"
2226
- );
2227
- function isTerminalPhase(phase, _sessionKind) {
2228
- return TERMINAL_PHASES.has(phase);
2229
- }
2230
-
2231
- // src/constants/watch.ts
2232
- var MIN_WATCH_POLL_INTERVAL_MS = 250;
2233
- var DEFAULT_WATCH_POLL_INTERVAL_MS = 1e3;
2234
- var MAX_WATCH_POLL_INTERVAL_MS = 6e4;
2235
- var WATCH_REPLAY_PAGE_SIZE = 200;
2236
- function isWatchTerminal(phase) {
2237
- return isTerminalPhase(phase);
2238
- }
2239
-
2240
2265
  // src/uploads.ts
2241
2266
  import { readFile as readFile3 } from "fs/promises";
2242
2267
  import { basename, extname } from "path";
@@ -2382,11 +2407,6 @@ function normalizeUploadedFileOptions(files) {
2382
2407
  return Array.isArray(files) ? files : [files];
2383
2408
  }
2384
2409
 
2385
- // src/utils/poll-interval.ts
2386
- function clampPollInterval(ms, minimum) {
2387
- return Math.max(ms, minimum);
2388
- }
2389
-
2390
2410
  // ../../shared/session/no-change-outcome.ts
2391
2411
  function isNoChangesPromptResult(result) {
2392
2412
  return typeof result === "object" && result !== null && "noChanges" in result && result.noChanges === true;
@@ -3889,20 +3909,6 @@ function applyDisconnectMask(view, mask, now) {
3889
3909
  }
3890
3910
 
3891
3911
  // src/commands/watch.ts
3892
- function parsePollInterval(raw) {
3893
- if (!raw) return DEFAULT_WATCH_POLL_INTERVAL_MS;
3894
- if (!/^\d+$/.test(raw)) {
3895
- throw new CliError("user", "Polling interval must be a non-negative integer.");
3896
- }
3897
- const value = Number(raw);
3898
- if (value < MIN_WATCH_POLL_INTERVAL_MS || value > MAX_WATCH_POLL_INTERVAL_MS) {
3899
- throw new CliError(
3900
- "user",
3901
- `Polling interval must be between ${MIN_WATCH_POLL_INTERVAL_MS} and ${MAX_WATCH_POLL_INTERVAL_MS} milliseconds.`
3902
- );
3903
- }
3904
- return value;
3905
- }
3906
3912
  function parseNonNegativeInteger(raw, name, defaultValue) {
3907
3913
  if (!raw) return defaultValue;
3908
3914
  if (!/^\d+$/.test(raw)) {
@@ -4327,16 +4333,6 @@ function parseEgressAllowlistSourceFile(content, key = EGRESS_ALLOWLIST_SOURCE_P
4327
4333
  }
4328
4334
 
4329
4335
  // src/commands/egress.ts
4330
- function parseRepoArg(value) {
4331
- const parsed = parseGithubRepoFullName(value);
4332
- if (!parsed) throw new CliError("user", "repo must be owner/name or a GitHub URL");
4333
- return {
4334
- ...parsed,
4335
- // Preserve the legacy shorthand contract while URLs and SSH remotes are
4336
- // normalized by the shared parser itself.
4337
- repo: /^[^/:]+\/[^/]+\.git$/.test(value.trim()) ? parsed.repo.slice(0, -4) : parsed.repo
4338
- };
4339
- }
4340
4336
  async function egressSourceSetCommand(sourceRepoArg, options = {}, command) {
4341
4337
  const { config } = resolveBusinessContext(command, options);
4342
4338
  const businessId = await resolveBusinessId(config, options);
@@ -4569,13 +4565,13 @@ function parseCreateConflict(error) {
4569
4565
  let message = error.message;
4570
4566
  let sessionId;
4571
4567
  let sessionUrl;
4572
- try {
4573
- const parsed = JSON.parse(error.body);
4574
- const parsedMessage = typeof parsed.error === "string" ? parsed.error : typeof parsed.error?.message === "string" ? parsed.error.message : void 0;
4568
+ const parsed = parseConflictBody(error);
4569
+ if (parsed) {
4570
+ const rawError = parsed.error;
4571
+ const parsedMessage = typeof rawError === "string" ? rawError : rawError && typeof rawError === "object" && typeof rawError.message === "string" ? rawError.message : void 0;
4575
4572
  if (parsedMessage) message = parsedMessage;
4576
4573
  if (typeof parsed.sessionId === "string") sessionId = parsed.sessionId;
4577
4574
  if (typeof parsed.sessionUrl === "string") sessionUrl = parsed.sessionUrl;
4578
- } catch {
4579
4575
  }
4580
4576
  const location = sessionUrl ? ` (${sessionUrl})` : "";
4581
4577
  const existing = sessionId ? ` Existing verifier session: ${sessionId}${location}.` : "";
@@ -4667,28 +4663,7 @@ async function qaCommand(prUrl, options, command) {
4667
4663
  console.log(`Follow with: arcanist sessions events ${sessionId} --follow --json`);
4668
4664
  }
4669
4665
 
4670
- // src/git.ts
4671
- import { execFileSync as execFileSync2 } from "child_process";
4672
- function git(args) {
4673
- try {
4674
- return execFileSync2("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
4675
- } catch (err) {
4676
- throw new CliError("user", `git ${args.join(" ")} failed: ${stringifyError(err)}`);
4677
- }
4678
- }
4679
- function currentRepo() {
4680
- const parsed = parseGithubRepoFullName(git(["remote", "get-url", "origin"]));
4681
- if (!parsed) throw new CliError("user", "origin remote must point at a GitHub repository");
4682
- return { owner: parsed.owner, repo: parsed.repo };
4683
- }
4684
-
4685
4666
  // src/commands/repos.ts
4686
- function parseRepoArg2(value) {
4687
- if (value === void 0) return currentRepo();
4688
- const parsed = parseGithubRepoFullName(value);
4689
- if (!parsed) throw new CliError("user", "repo must be owner/name or a GitHub URL");
4690
- return { owner: parsed.owner, repo: parsed.repo };
4691
- }
4692
4667
  async function reposListCommand(options = {}, command) {
4693
4668
  const { config } = resolveBusinessContext(command, options);
4694
4669
  const payload = await apiFetch(
@@ -4704,8 +4679,7 @@ async function reposListCommand(options = {}, command) {
4704
4679
  }
4705
4680
  async function repoBranchesCommand(repoArg, options = {}, command) {
4706
4681
  const { config } = resolveBusinessContext(command, options);
4707
- const repo = parseRepoArg2(repoArg);
4708
- await apiFetch(config, "/api/repos");
4682
+ const repo = parseRepoArgOrCurrent(repoArg);
4709
4683
  const payload = await apiFetch(
4710
4684
  config,
4711
4685
  `/api/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}/branches`
@@ -4716,7 +4690,7 @@ async function repoBranchesCommand(repoArg, options = {}, command) {
4716
4690
  }
4717
4691
  async function repoSkillsCommand(repoArg, options = {}, command) {
4718
4692
  const { config } = resolveBusinessContext(command, options);
4719
- const repo = parseRepoArg2(repoArg);
4693
+ const repo = parseRepoArgOrCurrent(repoArg);
4720
4694
  const payload = await apiFetch(
4721
4695
  config,
4722
4696
  `/api/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}/skills`
@@ -4764,17 +4738,62 @@ async function respondCommand(sessionId, answerArg, options = {}, command) {
4764
4738
  }
4765
4739
  }
4766
4740
  function parseRespondConflict(err) {
4767
- if (!(err instanceof ApiError) || err.status !== 409) return null;
4768
- try {
4769
- const body = JSON.parse(err.body);
4770
- if (body.error !== "session_not_respondable") return null;
4771
- return {
4772
- message: body.reason ? `Session cannot be answered from phase=${body.reason}.` : "Session does not have a pending question.",
4773
- reason: body.reason ?? "session_not_respondable"
4774
- };
4775
- } catch {
4776
- return null;
4741
+ const body = parseConflictBody(err);
4742
+ if (!body || body.error !== "session_not_respondable") return null;
4743
+ const reason = typeof body.reason === "string" ? body.reason : void 0;
4744
+ return {
4745
+ message: reason ? `Session cannot be answered from phase=${reason}.` : "Session does not have a pending question.",
4746
+ reason: reason ?? "session_not_respondable"
4747
+ };
4748
+ }
4749
+
4750
+ // src/commands/review.ts
4751
+ var PR_REVIEW_POLL_INTERVAL_MS = 1e4;
4752
+ var PR_REVIEW_WAIT_TIMEOUT_MS = 30 * 60 * 1e3;
4753
+ async function reviewCommand(prUrl, options = {}, command) {
4754
+ assertArcanistSessionMutationAllowed("review");
4755
+ const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: PR_REVIEW_POLL_INTERVAL_MS, maxMs: null }) : null;
4756
+ const { config } = resolveBusinessContext(command, options);
4757
+ const trigger = await apiFetch(config, "/api/pr-reviews", {
4758
+ method: "POST",
4759
+ body: JSON.stringify({
4760
+ prUrl,
4761
+ ...options.focus ? { focus: options.focus } : {},
4762
+ // Send an explicitly passed model even when empty (e.g. --model "$VAR" with an
4763
+ // unset VAR) so the server rejects it as invalid instead of silently routing.
4764
+ ...options.model !== void 0 ? { model: options.model } : {}
4765
+ })
4766
+ });
4767
+ if (!options.wait || trigger.cached) {
4768
+ emit(command, options, trigger, (payload) => {
4769
+ if (payload.cached) console.log(`Zeus review already completed: ${payload.verdict ?? "unknown"}.`);
4770
+ else console.log(`Started Zeus review${payload.sessionId ? ` (${payload.sessionId})` : ""}.`);
4771
+ });
4772
+ return;
4777
4773
  }
4774
+ const triggeredSessionId = trigger.sessionId ?? null;
4775
+ const result = await pollUntilSettled({
4776
+ pollIntervalMs: pollIntervalMs ?? PR_REVIEW_POLL_INTERVAL_MS,
4777
+ timeoutMs: PR_REVIEW_WAIT_TIMEOUT_MS,
4778
+ timeoutMessage: "Timed out waiting for Zeus review results.",
4779
+ fetchStatus: async () => {
4780
+ const status = await apiFetch(
4781
+ config,
4782
+ `/api/pr-reviews/status?prUrl=${encodeURIComponent(prUrl)}`
4783
+ );
4784
+ if (status.status !== "completed") return null;
4785
+ assertSameAttempt(triggeredSessionId, status.sessionId, "PR review");
4786
+ return { ...status, sessionId: status.sessionId ?? triggeredSessionId };
4787
+ }
4788
+ });
4789
+ emit(command, options, result, (payload) => {
4790
+ console.log(`Zeus review: ${sanitizeTerminalText(payload.verdict)}. ${payload.findings.length} finding(s).`);
4791
+ for (const finding of payload.findings) {
4792
+ console.log(
4793
+ `${sanitizeTerminalText(finding.severity)} ${sanitizeTerminalText(finding.file)}:${finding.line} - ${sanitizeTerminalText(finding.title)}`
4794
+ );
4795
+ }
4796
+ });
4778
4797
  }
4779
4798
 
4780
4799
  // src/commands/sandbox.ts
@@ -5327,23 +5346,9 @@ function assertCleanAndPushed() {
5327
5346
  function assertManifestExists(path) {
5328
5347
  if (!existsSync3(path)) throw new CliError("user", `Missing sandbox manifest: ${path}`);
5329
5348
  }
5330
- function parseRepoArg3(value, fallback) {
5331
- if (!value) return fallback();
5332
- const parsed = parseGithubRepoFullName(value);
5333
- if (!parsed) throw new CliError("user", "repo must be owner/name or a GitHub URL");
5334
- return { owner: parsed.owner, repo: parsed.repo };
5335
- }
5336
5349
  function repoPath(repo) {
5337
5350
  return `${repo.owner}/${repo.repo}`;
5338
5351
  }
5339
- function parsePollInterval2(value) {
5340
- if (!value) return DEFAULT_POLL_INTERVAL_MS;
5341
- const parsed = Number(value);
5342
- if (!Number.isInteger(parsed) || parsed < MIN_POLL_INTERVAL_MS) {
5343
- throw new CliError("user", `--poll-interval must be an integer >= ${MIN_POLL_INTERVAL_MS}.`);
5344
- }
5345
- return parsed;
5346
- }
5347
5352
  function printBuildRequest(buildRequest) {
5348
5353
  console.log(
5349
5354
  [
@@ -5615,8 +5620,8 @@ async function sandboxBuildCommand(sourceRepoArg, options = {}, command) {
5615
5620
  const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
5616
5621
  assertManifestExists(manifestPath);
5617
5622
  if (!options.ref) assertCleanAndPushed();
5618
- const sourceRepo = parseRepoArg3(sourceRepoArg, currentRepo);
5619
- const targetRepo = options.targetRepo ? parseRepoArg3(options.targetRepo, currentRepo) : null;
5623
+ const sourceRepo = parseRepoArgOrCurrent(sourceRepoArg);
5624
+ const targetRepo = options.targetRepo ? parseRepoArgOrCurrent(options.targetRepo) : null;
5620
5625
  const businessId = await resolveBusinessId(config, options);
5621
5626
  const ref = options.ref ?? currentRef();
5622
5627
  let payload;
@@ -5654,7 +5659,11 @@ async function sandboxBuildCommand(sourceRepoArg, options = {}, command) {
5654
5659
  if (!options.wait && !options.follow) return;
5655
5660
  const finalBuild = await waitForBuild(config, businessId, payload.buildRequest.id, {
5656
5661
  follow: options.follow,
5657
- pollIntervalMs: parsePollInterval2(options.pollInterval),
5662
+ pollIntervalMs: parsePollInterval(options.pollInterval, {
5663
+ defaultMs: DEFAULT_POLL_INTERVAL_MS,
5664
+ minMs: MIN_POLL_INTERVAL_MS,
5665
+ maxMs: null
5666
+ }),
5658
5667
  json
5659
5668
  });
5660
5669
  if (json)
@@ -5667,7 +5676,7 @@ async function sandboxBuildCommand(sourceRepoArg, options = {}, command) {
5667
5676
  async function sandboxStatusCommand(repoArg, options = {}, command) {
5668
5677
  const { config } = resolveBusinessContext(command, options);
5669
5678
  const businessId = await resolveBusinessId(config, options);
5670
- const repo = parseRepoArg3(repoArg, currentRepo);
5679
+ const repo = parseRepoArgOrCurrent(repoArg);
5671
5680
  const payload = await apiFetch(
5672
5681
  config,
5673
5682
  `/api/businesses/${encodeURIComponent(businessId)}/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(
@@ -5679,9 +5688,9 @@ async function sandboxStatusCommand(repoArg, options = {}, command) {
5679
5688
  async function sandboxHistoryCommand(sourceRepoArg, options = {}, command) {
5680
5689
  const { config } = resolveBusinessContext(command, options);
5681
5690
  const businessId = await resolveBusinessId(config, options);
5682
- const sourceRepo = parseRepoArg3(sourceRepoArg, currentRepo);
5691
+ const sourceRepo = parseRepoArgOrCurrent(sourceRepoArg);
5683
5692
  const params = new URLSearchParams({ sourceRepo: repoPath(sourceRepo) });
5684
- if (options.targetRepo) params.set("targetRepo", repoPath(parseRepoArg3(options.targetRepo, currentRepo)));
5693
+ if (options.targetRepo) params.set("targetRepo", repoPath(parseRepoArgOrCurrent(options.targetRepo)));
5685
5694
  if (options.status) params.set("status", options.status);
5686
5695
  if (options.limit) {
5687
5696
  const limit = Number(options.limit);
@@ -5776,13 +5785,19 @@ async function sandboxLogsCommand(buildId, options = {}, command) {
5776
5785
  }
5777
5786
  if (!options.follow) return;
5778
5787
  if (payload.logs.length > 0) afterSequence = payload.logs[payload.logs.length - 1].sequence;
5779
- await sleep(parsePollInterval2(options.pollInterval));
5788
+ await sleep(
5789
+ parsePollInterval(options.pollInterval, {
5790
+ defaultMs: DEFAULT_POLL_INTERVAL_MS,
5791
+ minMs: MIN_POLL_INTERVAL_MS,
5792
+ maxMs: null
5793
+ })
5794
+ );
5780
5795
  }
5781
5796
  }
5782
5797
  async function sandboxAssignDefaultCommand(sourceRepoArg, options = {}, command) {
5783
5798
  const { config } = resolveBusinessContext(command, options);
5784
5799
  const businessId = await resolveBusinessId(config, options);
5785
- const sourceRepo = parseRepoArg3(sourceRepoArg, currentRepo);
5800
+ const sourceRepo = parseRepoArgOrCurrent(sourceRepoArg);
5786
5801
  const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
5787
5802
  const payload = await apiFetch(
5788
5803
  config,
@@ -5799,8 +5814,8 @@ async function sandboxAssignDefaultCommand(sourceRepoArg, options = {}, command)
5799
5814
  async function sandboxAssignRepoCommand(targetRepoArg, sourceRepoArg, options = {}, command) {
5800
5815
  const { config } = resolveBusinessContext(command, options);
5801
5816
  const businessId = await resolveBusinessId(config, options);
5802
- const targetRepo = parseRepoArg3(targetRepoArg, currentRepo);
5803
- const sourceRepo = parseRepoArg3(sourceRepoArg, currentRepo);
5817
+ const targetRepo = parseRepoArgOrCurrent(targetRepoArg);
5818
+ const sourceRepo = parseRepoArgOrCurrent(sourceRepoArg);
5804
5819
  const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
5805
5820
  const payload = await apiFetch(
5806
5821
  config,
@@ -5838,7 +5853,7 @@ async function sandboxUnassignRepoCommand(targetRepoArg, options = {}, command)
5838
5853
  }
5839
5854
  const { config } = resolveBusinessContext(command, options);
5840
5855
  const businessId = await resolveBusinessId(config, options);
5841
- const targetRepo = parseRepoArg3(targetRepoArg, currentRepo);
5856
+ const targetRepo = parseRepoArgOrCurrent(targetRepoArg);
5842
5857
  if (!options.yes) await confirmOrThrow(`Clear sandbox layer assignment for ${repoPath(targetRepo)}?`);
5843
5858
  const payload = await apiFetch(
5844
5859
  config,
@@ -5851,35 +5866,29 @@ async function sandboxUnassignRepoCommand(targetRepoArg, options = {}, command)
5851
5866
  }
5852
5867
 
5853
5868
  // src/commands/sessions.ts
5854
- var MAX_ALL_PAGES2 = 1e3;
5855
5869
  async function listSessionsCommand(options, command) {
5856
5870
  const runtime = getRuntimeOptions(command, options);
5857
5871
  const config = requireConfig(runtime);
5858
- const sessions2 = [];
5859
- let cursor2 = options.cursor;
5860
- let nextCursor = null;
5861
- let pageCount = 0;
5862
- do {
5863
- pageCount += 1;
5864
- if (options.all === true && pageCount > MAX_ALL_PAGES2) {
5865
- throw new CliError("user", `sessions list --all exceeded ${MAX_ALL_PAGES2} pages without reaching the end.`);
5866
- }
5867
- const query = new URLSearchParams();
5868
- if (options.status) query.set("status", options.status);
5869
- if (options.scope) query.set("scope", options.scope);
5870
- if (options.search) query.set("q", options.search);
5871
- if (options.repo) query.set("repo", options.repo);
5872
- if (options.limit) query.set("limit", options.limit);
5873
- if (cursor2) query.set("cursor", cursor2);
5874
- const payload2 = await apiFetch(
5875
- config,
5876
- `/api/sessions${query.size ? `?${query.toString()}` : ""}`
5877
- );
5878
- sessions2.push(...payload2.sessions);
5879
- nextCursor = payload2.nextCursor;
5880
- cursor2 = nextCursor ?? void 0;
5881
- } while (options.all === true && nextCursor);
5882
- const payload = { sessions: sessions2, nextCursor: options.all === true ? null : nextCursor };
5872
+ const { items: sessions2, nextCursor } = await fetchPages(
5873
+ "sessions list --all",
5874
+ options.all === true,
5875
+ options.cursor,
5876
+ async (cursor2) => {
5877
+ const query = new URLSearchParams();
5878
+ if (options.status) query.set("status", options.status);
5879
+ if (options.scope) query.set("scope", options.scope);
5880
+ if (options.search) query.set("q", options.search);
5881
+ if (options.repo) query.set("repo", options.repo);
5882
+ if (options.limit) query.set("limit", options.limit);
5883
+ if (cursor2) query.set("cursor", cursor2);
5884
+ const payload2 = await apiFetch(
5885
+ config,
5886
+ `/api/sessions${query.size ? `?${query.toString()}` : ""}`
5887
+ );
5888
+ return { items: payload2.sessions, nextCursor: payload2.nextCursor };
5889
+ }
5890
+ );
5891
+ const payload = { sessions: sessions2, nextCursor };
5883
5892
  if (isJson(command, options)) {
5884
5893
  writeJson(payload);
5885
5894
  return;
@@ -6021,25 +6030,15 @@ async function stopCommand(sessionId, options = {}, command) {
6021
6030
  });
6022
6031
  }
6023
6032
  function parseStopBlocked(err) {
6024
- if (!(err instanceof ApiError) || err.status !== 409) return null;
6025
- try {
6026
- const body = JSON.parse(err.body);
6027
- if (body.error !== "session_not_stoppable") return null;
6028
- return { reason: body.reason ?? "not_stoppable" };
6029
- } catch {
6030
- return null;
6031
- }
6033
+ const body = parseConflictBody(err);
6034
+ if (!body || body.error !== "session_not_stoppable") return null;
6035
+ return { reason: typeof body.reason === "string" ? body.reason : "not_stoppable" };
6032
6036
  }
6033
6037
 
6034
6038
  // src/commands/test-creds.ts
6035
6039
  import { readFileSync as readFileSync4 } from "fs";
6036
- function parseRepoArg4(value) {
6037
- const parsed = parseGithubRepoFullName(value);
6038
- if (!parsed) throw new CliError("user", "repo must be owner/name or a GitHub URL");
6039
- return { owner: parsed.owner, name: parsed.repo };
6040
- }
6041
6040
  function basePath(businessId, repo) {
6042
- return `/api/businesses/${encodeURIComponent(businessId)}/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.name)}/test-credentials`;
6041
+ return `/api/businesses/${encodeURIComponent(businessId)}/repos/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}/test-credentials`;
6043
6042
  }
6044
6043
  function readValue(options) {
6045
6044
  const sources = [options.value, options.valueFile, options.valueStdin].filter((v) => v !== void 0 && v !== false);
@@ -6052,11 +6051,11 @@ function readValue(options) {
6052
6051
  }
6053
6052
  async function listTestCredentialsCommand(repo, options, command) {
6054
6053
  const { config } = resolveBusinessContext(command, options);
6055
- const repoArg = parseRepoArg4(repo);
6054
+ const repoArg = parseRepoArg(repo);
6056
6055
  const payload = await apiFetch(config, basePath(options.business, repoArg));
6057
6056
  emit(command, options, payload, (listPayload) => {
6058
6057
  if (listPayload.credentials.length === 0) {
6059
- console.log(`No test credentials configured for ${repoArg.owner}/${repoArg.name}.`);
6058
+ console.log(`No test credentials configured for ${repoArg.owner}/${repoArg.repo}.`);
6060
6059
  return;
6061
6060
  }
6062
6061
  for (const cred of listPayload.credentials) {
@@ -6067,7 +6066,7 @@ async function listTestCredentialsCommand(repo, options, command) {
6067
6066
  }
6068
6067
  async function setTestCredentialCommand(repo, name, options, command) {
6069
6068
  const { config } = resolveBusinessContext(command, options);
6070
- const repoArg = parseRepoArg4(repo);
6069
+ const repoArg = parseRepoArg(repo);
6071
6070
  const value = readValue(options);
6072
6071
  const payload = await apiFetch(
6073
6072
  config,
@@ -6078,7 +6077,7 @@ async function setTestCredentialCommand(repo, name, options, command) {
6078
6077
  command,
6079
6078
  options,
6080
6079
  payload,
6081
- () => console.log(`Set test credential '${name}' for ${repoArg.owner}/${repoArg.name}.`)
6080
+ () => console.log(`Set test credential '${name}' for ${repoArg.owner}/${repoArg.repo}.`)
6082
6081
  );
6083
6082
  }
6084
6083
  async function deleteTestCredentialCommand(repo, name, options, command) {
@@ -6086,11 +6085,11 @@ async function deleteTestCredentialCommand(repo, name, options, command) {
6086
6085
  throw new CliError("user", "`test-creds delete --json` requires --yes.");
6087
6086
  }
6088
6087
  if (options.yes !== true) {
6089
- const repoArg2 = parseRepoArg4(repo);
6090
- await confirmOrThrow(`Delete test credential '${name}' for ${repoArg2.owner}/${repoArg2.name}?`);
6088
+ const repoArg2 = parseRepoArg(repo);
6089
+ await confirmOrThrow(`Delete test credential '${name}' for ${repoArg2.owner}/${repoArg2.repo}?`);
6091
6090
  }
6092
6091
  const { config } = resolveBusinessContext(command, options);
6093
- const repoArg = parseRepoArg4(repo);
6092
+ const repoArg = parseRepoArg(repo);
6094
6093
  const payload = await apiFetch(
6095
6094
  config,
6096
6095
  `${basePath(options.business, repoArg)}/${encodeURIComponent(name)}`,
@@ -6100,35 +6099,29 @@ async function deleteTestCredentialCommand(repo, name, options, command) {
6100
6099
  command,
6101
6100
  options,
6102
6101
  payload,
6103
- () => console.log(`Deleted test credential '${name}' for ${repoArg.owner}/${repoArg.name}.`)
6102
+ () => console.log(`Deleted test credential '${name}' for ${repoArg.owner}/${repoArg.repo}.`)
6104
6103
  );
6105
6104
  }
6106
6105
 
6107
6106
  // src/commands/tokens.ts
6108
- var MAX_ALL_PAGES3 = 1e3;
6109
6107
  async function listTokensCommand(options, command) {
6110
6108
  const { config } = resolveBusinessContext(command, options);
6111
- const data = [];
6112
- let cursor2 = options.cursor;
6113
- let nextCursor = null;
6114
- let pageCount = 0;
6115
- do {
6116
- pageCount += 1;
6117
- if (options.all === true && pageCount > MAX_ALL_PAGES3) {
6118
- throw new CliError("user", `tokens list --all exceeded ${MAX_ALL_PAGES3} pages without reaching the end.`);
6109
+ const { items: data, nextCursor } = await fetchPages(
6110
+ "tokens list --all",
6111
+ options.all === true,
6112
+ options.cursor,
6113
+ async (cursor2) => {
6114
+ const query = new URLSearchParams();
6115
+ if (options.limit) query.set("limit", options.limit);
6116
+ if (cursor2) query.set("cursor", cursor2);
6117
+ const payload2 = await apiFetch(
6118
+ config,
6119
+ `/api/cli-tokens${query.size ? `?${query.toString()}` : ""}`
6120
+ );
6121
+ return { items: payload2.data, nextCursor: payload2.nextCursor };
6119
6122
  }
6120
- const query = new URLSearchParams();
6121
- if (options.limit) query.set("limit", options.limit);
6122
- if (cursor2) query.set("cursor", cursor2);
6123
- const payload2 = await apiFetch(
6124
- config,
6125
- `/api/cli-tokens${query.size ? `?${query.toString()}` : ""}`
6126
- );
6127
- data.push(...payload2.data);
6128
- nextCursor = payload2.nextCursor;
6129
- cursor2 = nextCursor ?? void 0;
6130
- } while (options.all === true && nextCursor);
6131
- const payload = { data, nextCursor: options.all === true ? null : nextCursor };
6123
+ );
6124
+ const payload = { data, nextCursor };
6132
6125
  emit(command, options, payload, (listPayload) => {
6133
6126
  if (listPayload.data.length === 0) {
6134
6127
  console.log("No CLI tokens found.");
@@ -6411,7 +6404,7 @@ cursor.command("use <state>").description("Turn using your saved Cursor subscrip
6411
6404
  cursor.command("status").description("Show whether your workspace is eligible and whether a Cursor subscription auth is saved").action((options, command) => agentSubscriptionStatusCommand(CURSOR_SUBSCRIPTION_CLI_CONFIG, options, command));
6412
6405
  cursor.command("logout").description("Deactivate the selector and remove the stored Cursor subscription auth for your user").action((options, command) => agentSubscriptionLogoutCommand(CURSOR_SUBSCRIPTION_CLI_CONFIG, options, command));
6413
6406
  var sessions = program.command("sessions").description("Session commands");
6414
- program.command("review <pr-url>").description("Run a Zeus review for a GitHub pull request").option("--focus <text>", "Focus the review on a specific concern").option("--model <model>", "Pin the reviewer model (Arcanist members only); defaults to automatic routing").option("--wait", "Wait for the review and print its verdict and findings").addHelpText(
6407
+ program.command("review <pr-url>").description("Run a Zeus review for a GitHub pull request").option("--focus <text>", "Focus the review on a specific concern").option("--model <model>", "Pin the reviewer model (Arcanist members only); defaults to automatic routing").option("--wait", "Wait for the review and print its verdict and findings").option("--poll-interval <ms>", "Polling interval in milliseconds while waiting (default 10000)").addHelpText(
6415
6408
  "after",
6416
6409
  `
6417
6410
  Examples:
@@ -6425,7 +6418,7 @@ a specific reviewer model instead (members only).
6425
6418
  Zeus review is currently available to Arcanist members only.
6426
6419
  `
6427
6420
  ).action((prUrl, options, command) => reviewCommand(prUrl, options, command));
6428
- program.command("anubis <pr-url>").description("Run an Anubis QA verification for a GitHub pull request").option("--wait", "Wait for the verdict").addHelpText(
6421
+ program.command("anubis <pr-url>").description("Run an Anubis QA verification for a GitHub pull request").option("--wait", "Wait for the verdict").option("--poll-interval <ms>", "Polling interval in milliseconds while waiting (default 15000)").addHelpText(
6429
6422
  "after",
6430
6423
  `
6431
6424
  Examples:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.221",
3
+ "version": "0.1.223",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {