@tryarcanist/cli 0.1.291 → 0.1.292

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 +141 -5
  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
@@ -7404,18 +7404,25 @@ function parseApiErrorBody(body) {
7404
7404
  try {
7405
7405
  const parsed = JSON.parse(body);
7406
7406
  if (!parsed || typeof parsed !== "object") return null;
7407
+ const details = parsed;
7408
+ const recovery = {
7409
+ ...typeof details.repository === "string" ? { repository: details.repository } : {},
7410
+ ...typeof details.environmentsUrl === "string" ? { environmentsUrl: details.environmentsUrl } : {}
7411
+ };
7407
7412
  const envelope = parseApiErrorEnvelope(parsed);
7408
7413
  if (!envelope) return null;
7409
7414
  if (typeof envelope.raw === "string" && envelope.raw) {
7410
7415
  return {
7411
7416
  ...envelope.code ? { serverCode: envelope.code } : {},
7412
7417
  rawError: envelope.raw,
7413
- ...envelope.message ? { message: envelope.message } : {}
7418
+ ...envelope.message ? { message: envelope.message } : {},
7419
+ ...recovery
7414
7420
  };
7415
7421
  }
7416
7422
  return envelope.message || envelope.code ? {
7417
7423
  ...envelope.message ? { message: envelope.message } : {},
7418
- ...envelope.code ? { serverCode: envelope.code, rawError: envelope.code } : {}
7424
+ ...envelope.code ? { serverCode: envelope.code, rawError: envelope.code } : {},
7425
+ ...recovery
7419
7426
  } : null;
7420
7427
  } catch {
7421
7428
  return null;
@@ -7462,9 +7469,13 @@ var ApiError = class extends CliError {
7462
7469
  const parsed = parseApiErrorBody(body);
7463
7470
  super(code, messageForApiError(status, body, parsed), {
7464
7471
  exitCode: exitCodeForHttpStatus(status),
7465
- hint: hintForHttpStatus(status, resourceNoun),
7472
+ hint: hintForHttpStatus(status, resourceNoun, parsed),
7466
7473
  requestId,
7467
- data: parsed?.serverCode ? { serverCode: parsed.serverCode } : void 0
7474
+ data: parsed?.serverCode || parsed?.repository || parsed?.environmentsUrl ? {
7475
+ ...parsed.serverCode ? { serverCode: parsed.serverCode } : {},
7476
+ ...parsed.repository ? { repository: parsed.repository } : {},
7477
+ ...parsed.environmentsUrl ? { environmentsUrl: parsed.environmentsUrl } : {}
7478
+ } : void 0
7468
7479
  });
7469
7480
  this.name = "ApiError";
7470
7481
  this.status = status;
@@ -7501,13 +7512,21 @@ function messageForApiError(status, body, parsed = parseApiErrorBody(body)) {
7501
7512
  if (parsed?.serverCode) return parsed.serverCode;
7502
7513
  return body ? `API error ${status}: ${body}` : `API error ${status}`;
7503
7514
  }
7504
- function hintForHttpStatus(status, resourceNoun) {
7515
+ function hintForHttpStatus(status, resourceNoun, parsed) {
7505
7516
  if (status === 401 || status === 403) return "Run `arcanist auth login` or set `ARCANIST_TOKEN`.";
7506
7517
  if (status === 404) {
7507
7518
  if (resourceNoun === "token") return "List tokens with `arcanist tokens list`.";
7508
7519
  if (resourceNoun === "repo") return "Check accessible repositories with `arcanist repos list`.";
7509
7520
  return "List sessions with `arcanist sessions list`.";
7510
7521
  }
7522
+ if (status === 409 && parsed?.repository) {
7523
+ if (parsed.serverCode === "environment_required") {
7524
+ return `Run \`arcanist environments prepare ${parsed.repository} --wait\`, then retry the review.`;
7525
+ }
7526
+ if (parsed.serverCode === "environment_preparing" || parsed.serverCode === "environment_needs_attention") {
7527
+ return `Run \`arcanist environments status ${parsed.repository} --wait\` for the latest setup state.`;
7528
+ }
7529
+ }
7511
7530
  if (status === 409) return "Check the current resource state and retry the command when it is ready.";
7512
7531
  if (status >= 500) return "Retry later or check the control-plane logs.";
7513
7532
  return void 0;
@@ -8549,6 +8568,105 @@ async function egressValidateCommand(path = EGRESS_ALLOWLIST_SOURCE_PATH, option
8549
8568
  });
8550
8569
  }
8551
8570
 
8571
+ // src/commands/environments.ts
8572
+ var SETUP_POLL_INTERVAL_MS = 5e3;
8573
+ var SETUP_WAIT_TIMEOUT_MS = 2 * 60 * 60 * 1e3;
8574
+ function requestedRepo(repoArg) {
8575
+ const repo = parseRepoArgOrCurrent(repoArg);
8576
+ return { ...repo, fullName: `${repo.owner}/${repo.repo}` };
8577
+ }
8578
+ async function fetchSetupState(config, fullName) {
8579
+ const response = await apiFetch(config, "/api/settings/repositories/onboarding");
8580
+ const state = response.repositories.find((item) => item.fullName.toLowerCase() === fullName.toLowerCase()) ?? {
8581
+ fullName,
8582
+ status: "not_started",
8583
+ stage: null
8584
+ };
8585
+ return { ...state, nextAction: nextAction(state) };
8586
+ }
8587
+ function isActive(state) {
8588
+ return state.nextAction === "wait";
8589
+ }
8590
+ function nextAction(state) {
8591
+ if (state.status === "not_started") return "prepare";
8592
+ if (state.status === "ready") return "review";
8593
+ if (state.secretRequest) return "add_credentials";
8594
+ if (state.question) return "answer_question";
8595
+ if (state.status === "failed") {
8596
+ return state.failureReason === "installation_missing" ? "authorize_github" : "retry";
8597
+ }
8598
+ if (state.status === "escalated" && state.stage !== "escalated_to_customer" && state.stage !== "question_for_customer") {
8599
+ return "wait_for_arcanist";
8600
+ }
8601
+ return "wait";
8602
+ }
8603
+ function printState(state) {
8604
+ const detail = state.failureReason ?? state.stage ?? state.buildStatus ?? state.nextAction;
8605
+ console.log(
8606
+ `${sanitizeTerminalText(state.fullName)}: ${sanitizeTerminalText(state.status)}${detail ? ` (${sanitizeTerminalText(detail)})` : ""}`
8607
+ );
8608
+ }
8609
+ async function waitForSetup(config, fullName, pollIntervalMs) {
8610
+ return pollUntilSettled({
8611
+ pollIntervalMs,
8612
+ timeoutMs: SETUP_WAIT_TIMEOUT_MS,
8613
+ timeoutMessage: `Timed out waiting for ${fullName} environment setup.`,
8614
+ fetchStatus: async () => {
8615
+ const state = await fetchSetupState(config, fullName);
8616
+ return isActive(state) ? null : state;
8617
+ }
8618
+ });
8619
+ }
8620
+ async function environmentStatusCommand(repoArg, options = {}, command) {
8621
+ const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: SETUP_POLL_INTERVAL_MS, maxMs: null }) : null;
8622
+ const repo = requestedRepo(repoArg);
8623
+ const { config } = resolveBusinessContext(command, options);
8624
+ await apiFetch(config, "/api/repos");
8625
+ const state = options.wait ? await waitForSetup(config, repo.fullName, pollIntervalMs ?? SETUP_POLL_INTERVAL_MS) : await fetchSetupState(config, repo.fullName);
8626
+ emit(command, options, { repository: state }, ({ repository }) => printState(repository));
8627
+ }
8628
+ async function environmentPrepareCommand(repoArg, options = {}, command) {
8629
+ const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: SETUP_POLL_INTERVAL_MS, maxMs: null }) : null;
8630
+ const repo = requestedRepo(repoArg);
8631
+ const { config } = resolveBusinessContext(command, options);
8632
+ const response = await apiFetch(config, "/api/settings/repositories/onboarding", {
8633
+ method: "POST",
8634
+ body: JSON.stringify({ repositories: [{ owner: repo.owner, name: repo.repo }] })
8635
+ });
8636
+ const result = response.repositories[0];
8637
+ if (!result) throw new CliError("server", "Environment setup returned no repository result.");
8638
+ if (result.status === "forbidden") {
8639
+ throw new CliError("auth", `You must have write access to ${repo.fullName} to prepare its environment.`);
8640
+ }
8641
+ if (result.status === "failed") {
8642
+ throw new CliError("server", `Environment setup could not be started for ${repo.fullName}.`);
8643
+ }
8644
+ if (options.wait && (result.status === "queued" || result.status === "already_queued")) {
8645
+ const state2 = await waitForSetup(config, repo.fullName, pollIntervalMs ?? SETUP_POLL_INTERVAL_MS);
8646
+ emit(command, options, { repository: state2 }, ({ repository }) => printState(repository));
8647
+ return;
8648
+ }
8649
+ const state = {
8650
+ fullName: result.fullName,
8651
+ status: result.status === "already_ready" ? "ready" : "pending",
8652
+ stage: null,
8653
+ nextAction: result.status === "already_ready" ? "review" : "wait"
8654
+ };
8655
+ emit(command, options, { repository: state }, ({ repository }) => printState(repository));
8656
+ }
8657
+ async function environmentRetryCommand(repoArg, options = {}, command) {
8658
+ const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: SETUP_POLL_INTERVAL_MS, maxMs: null }) : null;
8659
+ const repo = requestedRepo(repoArg);
8660
+ const { config } = resolveBusinessContext(command, options);
8661
+ await apiFetch(
8662
+ config,
8663
+ `/api/settings/repositories/${encodeURIComponent(repo.owner)}/${encodeURIComponent(repo.repo)}/onboarding/retry`,
8664
+ { method: "POST" }
8665
+ );
8666
+ const state = options.wait ? await waitForSetup(config, repo.fullName, pollIntervalMs ?? SETUP_POLL_INTERVAL_MS) : await fetchSetupState(config, repo.fullName);
8667
+ emit(command, options, { repository: state }, ({ repository }) => printState(repository));
8668
+ }
8669
+
8552
8670
  // src/commands/hades.ts
8553
8671
  async function hadesCommand(repo, options = {}, command) {
8554
8672
  assertArcanistSessionMutationAllowed("hades");
@@ -12556,6 +12674,24 @@ JSON:
12556
12674
  JSON mode returns {skills}
12557
12675
  `
12558
12676
  ).action((repo, options, command) => repoSkillsCommand(repo, options, command));
12677
+ var environments = program.command("environments").description("Zeus environment setup commands");
12678
+ 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(
12679
+ "after",
12680
+ `
12681
+ Examples:
12682
+ arcanist environments prepare
12683
+ arcanist environments prepare owner/repo --wait --json
12684
+ `
12685
+ ).action((repo, options, command) => environmentPrepareCommand(repo, options, command));
12686
+ 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(
12687
+ "after",
12688
+ `
12689
+ JSON:
12690
+ Returns {repository: {fullName, status, stage, nextAction, buildStatus?, failureReason?, secretRequest?, question?}}.
12691
+ The stable status values are not_started, pending, running, escalated, succeeded, failed, and ready.
12692
+ `
12693
+ ).action((repo, options, command) => environmentStatusCommand(repo, options, command));
12694
+ 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
12695
  var models = program.command("models").description("Model discovery commands");
12560
12696
  models.command("list").description("List session-start models").addHelpText(
12561
12697
  "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.292",
4
4
  "description": "CLI for Arcanist - create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {