@tryarcanist/cli 0.1.291 → 0.1.293

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 +12 -0
  2. package/dist/index.js +150 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -120,6 +120,18 @@ Mutation commands send an `Idempotency-Key` header. The CLI does not auto-retry
120
120
 
121
121
  ## Commands
122
122
 
123
+ ### `arcanist environments prepare|status`
124
+
125
+ Prepare the current repository for Zeus, or inspect its machine-readable setup state. Pass `owner/repo` outside a checkout. A write-scoped token is required to prepare; read-scoped tokens can inspect status.
126
+
127
+ ```bash
128
+ arcanist environments prepare --wait --json
129
+ arcanist environments status owner/repo --json
130
+ arcanist environments retry owner/repo --wait --json
131
+ ```
132
+
133
+ Agents should follow `repository.nextAction` and request a Zeus review only when it is `review`. The command waits through internal handoffs until setup is ready or there is a real action for the user or Arcanist.
134
+
123
135
  ### `arcanist auth login`
124
136
 
125
137
  ```bash
package/dist/index.js CHANGED
@@ -7368,8 +7368,9 @@ import { Command } from "commander";
7368
7368
  import { createRequire } from "module";
7369
7369
 
7370
7370
  // ../../shared/utils/errors.ts
7371
- function stringifyError(error) {
7372
- return error instanceof Error ? error.message : String(error);
7371
+ function stringifyError(error, fallback = "") {
7372
+ const message = error instanceof Error ? error.message : String(error);
7373
+ return message || fallback;
7373
7374
  }
7374
7375
 
7375
7376
  // ../../shared/utils/url.ts
@@ -7404,18 +7405,25 @@ function parseApiErrorBody(body) {
7404
7405
  try {
7405
7406
  const parsed = JSON.parse(body);
7406
7407
  if (!parsed || typeof parsed !== "object") return null;
7408
+ const details = parsed;
7409
+ const recovery = {
7410
+ ...typeof details.repository === "string" ? { repository: details.repository } : {},
7411
+ ...typeof details.environmentsUrl === "string" ? { environmentsUrl: details.environmentsUrl } : {}
7412
+ };
7407
7413
  const envelope = parseApiErrorEnvelope(parsed);
7408
7414
  if (!envelope) return null;
7409
7415
  if (typeof envelope.raw === "string" && envelope.raw) {
7410
7416
  return {
7411
7417
  ...envelope.code ? { serverCode: envelope.code } : {},
7412
7418
  rawError: envelope.raw,
7413
- ...envelope.message ? { message: envelope.message } : {}
7419
+ ...envelope.message ? { message: envelope.message } : {},
7420
+ ...recovery
7414
7421
  };
7415
7422
  }
7416
7423
  return envelope.message || envelope.code ? {
7417
7424
  ...envelope.message ? { message: envelope.message } : {},
7418
- ...envelope.code ? { serverCode: envelope.code, rawError: envelope.code } : {}
7425
+ ...envelope.code ? { serverCode: envelope.code, rawError: envelope.code } : {},
7426
+ ...recovery
7419
7427
  } : null;
7420
7428
  } catch {
7421
7429
  return null;
@@ -7462,9 +7470,13 @@ var ApiError = class extends CliError {
7462
7470
  const parsed = parseApiErrorBody(body);
7463
7471
  super(code, messageForApiError(status, body, parsed), {
7464
7472
  exitCode: exitCodeForHttpStatus(status),
7465
- hint: hintForHttpStatus(status, resourceNoun),
7473
+ hint: hintForHttpStatus(status, resourceNoun, parsed),
7466
7474
  requestId,
7467
- data: parsed?.serverCode ? { serverCode: parsed.serverCode } : void 0
7475
+ data: parsed?.serverCode || parsed?.repository || parsed?.environmentsUrl ? {
7476
+ ...parsed.serverCode ? { serverCode: parsed.serverCode } : {},
7477
+ ...parsed.repository ? { repository: parsed.repository } : {},
7478
+ ...parsed.environmentsUrl ? { environmentsUrl: parsed.environmentsUrl } : {}
7479
+ } : void 0
7468
7480
  });
7469
7481
  this.name = "ApiError";
7470
7482
  this.status = status;
@@ -7501,13 +7513,21 @@ function messageForApiError(status, body, parsed = parseApiErrorBody(body)) {
7501
7513
  if (parsed?.serverCode) return parsed.serverCode;
7502
7514
  return body ? `API error ${status}: ${body}` : `API error ${status}`;
7503
7515
  }
7504
- function hintForHttpStatus(status, resourceNoun) {
7516
+ function hintForHttpStatus(status, resourceNoun, parsed) {
7505
7517
  if (status === 401 || status === 403) return "Run `arcanist auth login` or set `ARCANIST_TOKEN`.";
7506
7518
  if (status === 404) {
7507
7519
  if (resourceNoun === "token") return "List tokens with `arcanist tokens list`.";
7508
7520
  if (resourceNoun === "repo") return "Check accessible repositories with `arcanist repos list`.";
7509
7521
  return "List sessions with `arcanist sessions list`.";
7510
7522
  }
7523
+ if (status === 409 && parsed?.repository) {
7524
+ if (parsed.serverCode === "environment_required") {
7525
+ return `Run \`arcanist environments prepare ${parsed.repository} --wait\`, then retry the review.`;
7526
+ }
7527
+ if (parsed.serverCode === "environment_preparing" || parsed.serverCode === "environment_needs_attention") {
7528
+ return `Run \`arcanist environments status ${parsed.repository} --wait\` for the latest setup state.`;
7529
+ }
7530
+ }
7511
7531
  if (status === 409) return "Check the current resource state and retry the command when it is ready.";
7512
7532
  if (status >= 500) return "Retry later or check the control-plane logs.";
7513
7533
  return void 0;
@@ -8549,6 +8569,105 @@ async function egressValidateCommand(path = EGRESS_ALLOWLIST_SOURCE_PATH, option
8549
8569
  });
8550
8570
  }
8551
8571
 
8572
+ // src/commands/environments.ts
8573
+ var SETUP_POLL_INTERVAL_MS = 5e3;
8574
+ var SETUP_WAIT_TIMEOUT_MS = 2 * 60 * 60 * 1e3;
8575
+ function requestedRepo(repoArg) {
8576
+ const repo = parseRepoArgOrCurrent(repoArg);
8577
+ return { ...repo, fullName: `${repo.owner}/${repo.repo}` };
8578
+ }
8579
+ async function fetchSetupState(config, fullName) {
8580
+ const response = await apiFetch(config, "/api/settings/repositories/onboarding");
8581
+ const state = response.repositories.find((item) => item.fullName.toLowerCase() === fullName.toLowerCase()) ?? {
8582
+ fullName,
8583
+ status: "not_started",
8584
+ stage: null
8585
+ };
8586
+ return { ...state, nextAction: nextAction(state) };
8587
+ }
8588
+ function isActive(state) {
8589
+ return state.nextAction === "wait";
8590
+ }
8591
+ function nextAction(state) {
8592
+ if (state.status === "not_started") return "prepare";
8593
+ if (state.status === "ready") return "review";
8594
+ if (state.secretRequest) return "add_credentials";
8595
+ if (state.question) return "answer_question";
8596
+ if (state.status === "failed") {
8597
+ return state.failureReason === "installation_missing" ? "authorize_github" : "retry";
8598
+ }
8599
+ if (state.status === "escalated" && state.stage !== "escalated_to_customer" && state.stage !== "question_for_customer") {
8600
+ return "wait_for_arcanist";
8601
+ }
8602
+ return "wait";
8603
+ }
8604
+ function printState(state) {
8605
+ const detail = state.failureReason ?? state.stage ?? state.buildStatus ?? state.nextAction;
8606
+ console.log(
8607
+ `${sanitizeTerminalText(state.fullName)}: ${sanitizeTerminalText(state.status)}${detail ? ` (${sanitizeTerminalText(detail)})` : ""}`
8608
+ );
8609
+ }
8610
+ async function waitForSetup(config, fullName, pollIntervalMs) {
8611
+ return pollUntilSettled({
8612
+ pollIntervalMs,
8613
+ timeoutMs: SETUP_WAIT_TIMEOUT_MS,
8614
+ timeoutMessage: `Timed out waiting for ${fullName} environment setup.`,
8615
+ fetchStatus: async () => {
8616
+ const state = await fetchSetupState(config, fullName);
8617
+ return isActive(state) ? null : state;
8618
+ }
8619
+ });
8620
+ }
8621
+ async function environmentStatusCommand(repoArg, options = {}, command) {
8622
+ const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: SETUP_POLL_INTERVAL_MS, maxMs: null }) : null;
8623
+ const repo = requestedRepo(repoArg);
8624
+ const { config } = resolveBusinessContext(command, options);
8625
+ await apiFetch(config, "/api/repos");
8626
+ const state = options.wait ? await waitForSetup(config, repo.fullName, pollIntervalMs ?? SETUP_POLL_INTERVAL_MS) : await fetchSetupState(config, repo.fullName);
8627
+ emit(command, options, { repository: state }, ({ repository }) => printState(repository));
8628
+ }
8629
+ async function environmentPrepareCommand(repoArg, options = {}, command) {
8630
+ const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: SETUP_POLL_INTERVAL_MS, maxMs: null }) : null;
8631
+ const repo = requestedRepo(repoArg);
8632
+ const { config } = resolveBusinessContext(command, options);
8633
+ const response = await apiFetch(config, "/api/settings/repositories/onboarding", {
8634
+ method: "POST",
8635
+ body: JSON.stringify({ repositories: [{ owner: repo.owner, name: repo.repo }] })
8636
+ });
8637
+ const result = response.repositories[0];
8638
+ if (!result) throw new CliError("server", "Environment setup returned no repository result.");
8639
+ if (result.status === "forbidden") {
8640
+ throw new CliError("auth", `You must have write access to ${repo.fullName} to prepare its environment.`);
8641
+ }
8642
+ if (result.status === "failed") {
8643
+ throw new CliError("server", `Environment setup could not be started for ${repo.fullName}.`);
8644
+ }
8645
+ if (options.wait && (result.status === "queued" || result.status === "already_queued")) {
8646
+ const state2 = await waitForSetup(config, repo.fullName, pollIntervalMs ?? SETUP_POLL_INTERVAL_MS);
8647
+ emit(command, options, { repository: state2 }, ({ repository }) => printState(repository));
8648
+ return;
8649
+ }
8650
+ const state = {
8651
+ fullName: result.fullName,
8652
+ status: result.status === "already_ready" ? "ready" : "pending",
8653
+ stage: null,
8654
+ nextAction: result.status === "already_ready" ? "review" : "wait"
8655
+ };
8656
+ emit(command, options, { repository: state }, ({ repository }) => printState(repository));
8657
+ }
8658
+ async function environmentRetryCommand(repoArg, options = {}, command) {
8659
+ const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: SETUP_POLL_INTERVAL_MS, maxMs: null }) : null;
8660
+ const repo = requestedRepo(repoArg);
8661
+ const { config } = resolveBusinessContext(command, options);
8662
+ await apiFetch(
8663
+ config,
8664
+ `/api/settings/repositories/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}/onboarding/retry`,
8665
+ { method: "POST" }
8666
+ );
8667
+ const state = options.wait ? await waitForSetup(config, repo.fullName, pollIntervalMs ?? SETUP_POLL_INTERVAL_MS) : await fetchSetupState(config, repo.fullName);
8668
+ emit(command, options, { repository: state }, ({ repository }) => printState(repository));
8669
+ }
8670
+
8552
8671
  // src/commands/hades.ts
8553
8672
  async function hadesCommand(repo, options = {}, command) {
8554
8673
  assertArcanistSessionMutationAllowed("hades");
@@ -9199,7 +9318,12 @@ async function reviewCommand(prUrl, options = {}, command) {
9199
9318
  );
9200
9319
  if (status.status !== "completed") return null;
9201
9320
  assertSameAttempt(triggeredSessionId, status.sessionId, "PR review");
9202
- return { ...status, sessionId: status.sessionId ?? triggeredSessionId };
9321
+ return {
9322
+ ...status,
9323
+ status: "completed",
9324
+ sessionId: status.sessionId ?? triggeredSessionId,
9325
+ verdict: status.verdict ?? "unknown"
9326
+ };
9203
9327
  }
9204
9328
  });
9205
9329
  emit(command, options, result, (payload) => {
@@ -12556,6 +12680,24 @@ JSON:
12556
12680
  JSON mode returns {skills}
12557
12681
  `
12558
12682
  ).action((repo, options, command) => repoSkillsCommand(repo, options, command));
12683
+ var environments = program.command("environments").description("Zeus environment setup commands");
12684
+ environments.command("prepare").description("Prepare a repository for Zeus reviews").argument("[repo]", "Repository owner/name; defaults to current git remote").option("--wait", "Wait until setup is ready or needs attention").option("--poll-interval <ms>", "Polling interval while waiting (default 5000)").addHelpText(
12685
+ "after",
12686
+ `
12687
+ Examples:
12688
+ arcanist environments prepare
12689
+ arcanist environments prepare owner/repo --wait --json
12690
+ `
12691
+ ).action((repo, options, command) => environmentPrepareCommand(repo, options, command));
12692
+ environments.command("status").description("Show repository environment setup status").argument("[repo]", "Repository owner/name; defaults to current git remote").option("--wait", "Wait until setup is ready or needs attention").option("--poll-interval <ms>", "Polling interval while waiting (default 5000)").addHelpText(
12693
+ "after",
12694
+ `
12695
+ JSON:
12696
+ Returns {repository: {fullName, status, stage, nextAction, buildStatus?, failureReason?, secretRequest?, question?}}.
12697
+ The stable status values are not_started, pending, running, escalated, succeeded, failed, and ready.
12698
+ `
12699
+ ).action((repo, options, command) => environmentStatusCommand(repo, options, command));
12700
+ environments.command("retry").description("Retry a failed repository environment setup").argument("[repo]", "Repository owner/name; defaults to current git remote").option("--wait", "Wait until setup is ready or needs attention").option("--poll-interval <ms>", "Polling interval while waiting (default 5000)").action((repo, options, command) => environmentRetryCommand(repo, options, command));
12559
12701
  var models = program.command("models").description("Model discovery commands");
12560
12702
  models.command("list").description("List session-start models").addHelpText(
12561
12703
  "after",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.291",
3
+ "version": "0.1.293",
4
4
  "description": "CLI for Arcanist - create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {