@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.
- package/README.md +12 -0
- package/dist/index.js +141 -5
- 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
|
|
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",
|