@tryarcanist/cli 0.1.222 → 0.1.224
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/dist/index.js +317 -325
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,11 +5,11 @@ import { createRequire as createRequire2 } from "module";
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/commands/agent-subscription.ts
|
|
8
|
-
import { execFileSync, spawn } from "child_process";
|
|
9
|
-
import { existsSync as existsSync2
|
|
10
|
-
import {
|
|
11
|
-
import { homedir as homedir2
|
|
12
|
-
import { join as
|
|
8
|
+
import { execFileSync, spawn as spawn2 } from "child_process";
|
|
9
|
+
import { existsSync as existsSync2 } from "fs";
|
|
10
|
+
import { readFile } from "fs/promises";
|
|
11
|
+
import { homedir as homedir2 } from "os";
|
|
12
|
+
import { join as join3 } from "path";
|
|
13
13
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
14
14
|
|
|
15
15
|
// src/api.ts
|
|
@@ -640,6 +640,71 @@ function isControlCharacter(char) {
|
|
|
640
640
|
return code < ASCII_FIRST_PRINTABLE || code === ASCII_DELETE;
|
|
641
641
|
}
|
|
642
642
|
|
|
643
|
+
// src/vendor-login.ts
|
|
644
|
+
import { spawn } from "child_process";
|
|
645
|
+
import { rmSync } from "fs";
|
|
646
|
+
import { mkdtemp, rm } from "fs/promises";
|
|
647
|
+
import { tmpdir } from "os";
|
|
648
|
+
import { join as join2 } from "path";
|
|
649
|
+
var VendorBinaryMissingError = class extends CliError {
|
|
650
|
+
constructor(binPath, hint) {
|
|
651
|
+
super("user", `Could not find the \`${binPath}\` executable.`, { hint });
|
|
652
|
+
this.name = "VendorBinaryMissingError";
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
function runVendorLoginProcess(spec) {
|
|
656
|
+
return new Promise((resolve2, reject) => {
|
|
657
|
+
const child = spawn(spec.binPath, spec.args, {
|
|
658
|
+
stdio: "inherit",
|
|
659
|
+
env: { ...process.env, ...spec.env }
|
|
660
|
+
});
|
|
661
|
+
child.on("error", (err) => {
|
|
662
|
+
if (err.code === "ENOENT") {
|
|
663
|
+
reject(new VendorBinaryMissingError(spec.binPath, spec.missingBinaryHint));
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
reject(new CliError("user", `Failed to launch \`${spec.binPath} ${spec.args.join(" ")}\`: ${err.message}`));
|
|
667
|
+
});
|
|
668
|
+
child.on("close", (code) => {
|
|
669
|
+
if (code === 0) {
|
|
670
|
+
resolve2();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
reject(
|
|
674
|
+
new CliError("user", `\`${spec.binPath} ${spec.args.join(" ")}\` exited with code ${code ?? "unknown"}.`, {
|
|
675
|
+
hint: `Complete the ${spec.displayName} login, then re-run \`${spec.retryCommand}\`.`
|
|
676
|
+
})
|
|
677
|
+
);
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
async function withIsolatedLoginDir(prefix, fn) {
|
|
682
|
+
let tempDir;
|
|
683
|
+
const handleSigint = () => {
|
|
684
|
+
if (tempDir) {
|
|
685
|
+
try {
|
|
686
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
687
|
+
} catch {
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
process.exit(EXIT_CODE_INTERRUPTED);
|
|
691
|
+
};
|
|
692
|
+
process.on("SIGINT", handleSigint);
|
|
693
|
+
try {
|
|
694
|
+
tempDir = await mkdtemp(join2(tmpdir(), prefix));
|
|
695
|
+
return await fn(tempDir);
|
|
696
|
+
} finally {
|
|
697
|
+
try {
|
|
698
|
+
if (tempDir) {
|
|
699
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
} finally {
|
|
703
|
+
process.off("SIGINT", handleSigint);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
643
708
|
// src/commands/agent-subscription.ts
|
|
644
709
|
var GROK_SUBSCRIPTION_CLI_CONFIG = {
|
|
645
710
|
harness: "grok",
|
|
@@ -650,7 +715,7 @@ var GROK_SUBSCRIPTION_CLI_CONFIG = {
|
|
|
650
715
|
// terminal without a local browser.
|
|
651
716
|
loginArgs: ["login", "--device-auth"],
|
|
652
717
|
loginEnv: () => ({}),
|
|
653
|
-
authJsonCandidates: [
|
|
718
|
+
authJsonCandidates: [join3(".grok", "auth.json")],
|
|
654
719
|
// Grok Build's auth.json is issuer-keyed (observed 2026-07-15 against the
|
|
655
720
|
// installed CLI's embedded docs): {"https://accounts.x.ai/sign-in": {"key":
|
|
656
721
|
// "…"}} — so the credential fields live one level down and the token field
|
|
@@ -663,8 +728,8 @@ var GROK_SUBSCRIPTION_CLI_CONFIG = {
|
|
|
663
728
|
installer: {
|
|
664
729
|
command: "curl -fsSL https://x.ai/cli/install.sh | bash",
|
|
665
730
|
binCandidates: (home) => [
|
|
666
|
-
|
|
667
|
-
|
|
731
|
+
join3(home, ".grok", "bin", "grok"),
|
|
732
|
+
join3(home, ".local", "bin", "grok"),
|
|
668
733
|
"/usr/local/bin/grok"
|
|
669
734
|
],
|
|
670
735
|
postInstallNote: "Grok CLI installed. Restart your shell if `grok` is not found on PATH later."
|
|
@@ -680,14 +745,14 @@ var CURSOR_SUBSCRIPTION_CLI_CONFIG = {
|
|
|
680
745
|
// formally documented, so pin every documented override at the throwaway
|
|
681
746
|
// HOME and search the known spots afterwards.
|
|
682
747
|
loginEnv: (tempHome) => ({
|
|
683
|
-
CURSOR_CONFIG_DIR:
|
|
684
|
-
XDG_CONFIG_HOME:
|
|
748
|
+
CURSOR_CONFIG_DIR: join3(tempHome, ".cursor"),
|
|
749
|
+
XDG_CONFIG_HOME: join3(tempHome, ".config")
|
|
685
750
|
}),
|
|
686
751
|
authJsonCandidates: [
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
752
|
+
join3(".cursor", "auth.json"),
|
|
753
|
+
join3(".cursor", "cli-config.json"),
|
|
754
|
+
join3(".config", "cursor", "auth.json"),
|
|
755
|
+
join3(".config", "cursor", "cli-config.json")
|
|
691
756
|
],
|
|
692
757
|
tokenFields: ["accessToken", "access_token", "refreshToken", "refresh_token", "token"],
|
|
693
758
|
installHint: "Install the Cursor CLI (https://cursor.com/docs/cli/installation), or point at it with --cursor-path <path> or ARCANIST_CURSOR_AGENT_BIN. If login-token capture keeps failing, use a Cursor API key in Settings instead.",
|
|
@@ -705,7 +770,7 @@ var CURSOR_SUBSCRIPTION_CLI_CONFIG = {
|
|
|
705
770
|
// name first, since `agent` is also Grok's alias.
|
|
706
771
|
installer: {
|
|
707
772
|
command: "curl https://cursor.com/install -fsS | bash",
|
|
708
|
-
binCandidates: (home) => [
|
|
773
|
+
binCandidates: (home) => [join3(home, ".local", "bin", "cursor-agent"), join3(home, ".local", "bin", "agent")],
|
|
709
774
|
postInstallNote: 'Cursor CLI installed to ~/.local/bin. Add it to your PATH for future shells: export PATH="$HOME/.local/bin:$PATH"'
|
|
710
775
|
}
|
|
711
776
|
};
|
|
@@ -715,15 +780,9 @@ function subscriptionBasePath(harness) {
|
|
|
715
780
|
function resolveVendorBin(config, optionPath) {
|
|
716
781
|
return optionPath?.trim() || process.env[config.binEnvVar]?.trim() || config.defaultBin;
|
|
717
782
|
}
|
|
718
|
-
var VendorBinaryMissingError = class extends CliError {
|
|
719
|
-
constructor(config, binPath) {
|
|
720
|
-
super("user", `Could not find the \`${binPath}\` executable.`, { hint: config.installHint });
|
|
721
|
-
this.name = "VendorBinaryMissingError";
|
|
722
|
-
}
|
|
723
|
-
};
|
|
724
783
|
function runVendorInstall(config, command) {
|
|
725
784
|
return new Promise((resolve2, reject) => {
|
|
726
|
-
const child =
|
|
785
|
+
const child = spawn2("bash", ["-c", command], { stdio: ["inherit", 2, "inherit"] });
|
|
727
786
|
child.on("error", (err) => {
|
|
728
787
|
reject(
|
|
729
788
|
new CliError("user", `Failed to run the ${config.displayName} CLI installer: ${err.message}`, {
|
|
@@ -779,29 +838,13 @@ async function runVendorLoginWithInstallOffer(config, binPath, tempHome, explici
|
|
|
779
838
|
}
|
|
780
839
|
}
|
|
781
840
|
function runVendorLogin(config, binPath, tempHome) {
|
|
782
|
-
return
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
reject(new VendorBinaryMissingError(config, binPath));
|
|
790
|
-
return;
|
|
791
|
-
}
|
|
792
|
-
reject(new CliError("user", `Failed to launch \`${binPath} ${config.loginArgs.join(" ")}\`: ${err.message}`));
|
|
793
|
-
});
|
|
794
|
-
child.on("close", (code) => {
|
|
795
|
-
if (code === 0) {
|
|
796
|
-
resolve2();
|
|
797
|
-
return;
|
|
798
|
-
}
|
|
799
|
-
reject(
|
|
800
|
-
new CliError("user", `\`${binPath} ${config.loginArgs.join(" ")}\` exited with code ${code ?? "unknown"}.`, {
|
|
801
|
-
hint: `Complete the ${config.displayName} login, then re-run \`arcanist ${config.harness} login\`.`
|
|
802
|
-
})
|
|
803
|
-
);
|
|
804
|
-
});
|
|
841
|
+
return runVendorLoginProcess({
|
|
842
|
+
displayName: config.displayName,
|
|
843
|
+
binPath,
|
|
844
|
+
args: config.loginArgs,
|
|
845
|
+
env: tempHome ? { HOME: tempHome, ...config.loginEnv(tempHome) } : {},
|
|
846
|
+
missingBinaryHint: config.installHint,
|
|
847
|
+
retryCommand: `arcanist ${config.harness} login`
|
|
805
848
|
});
|
|
806
849
|
}
|
|
807
850
|
function hasTokenField(value, tokenFields, depth = 0) {
|
|
@@ -838,7 +881,7 @@ function readDarwinKeychainAuthJson(config) {
|
|
|
838
881
|
}
|
|
839
882
|
async function readLoginAuthJson(config, tempHome) {
|
|
840
883
|
for (const candidate of config.authJsonCandidates) {
|
|
841
|
-
const path =
|
|
884
|
+
const path = join3(tempHome, candidate);
|
|
842
885
|
if (!existsSync2(path)) continue;
|
|
843
886
|
let raw;
|
|
844
887
|
try {
|
|
@@ -865,72 +908,51 @@ async function agentSubscriptionLoginCommand(config, options, command) {
|
|
|
865
908
|
const { config: apiConfig } = resolveBusinessContext(command, options);
|
|
866
909
|
const binPath = resolveVendorBin(config, options.binPath);
|
|
867
910
|
const explicitBinPath = Boolean(options.binPath?.trim() || process.env[config.binEnvVar]?.trim());
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
}
|
|
876
|
-
process.exit(EXIT_CODE_INTERRUPTED);
|
|
877
|
-
};
|
|
878
|
-
process.on("SIGINT", handleSigint);
|
|
879
|
-
try {
|
|
880
|
-
const captureViaKeychain = process.platform === "darwin" && config.darwinKeychain !== void 0;
|
|
881
|
-
let authJson;
|
|
882
|
-
if (captureViaKeychain) {
|
|
883
|
-
await runVendorLoginWithInstallOffer(config, binPath, null, explicitBinPath);
|
|
884
|
-
authJson = readDarwinKeychainAuthJson(config);
|
|
885
|
-
} else {
|
|
886
|
-
tempHome = await mkdtemp(join2(tmpdir(), `arcanist-${config.harness}-`));
|
|
911
|
+
const captureViaKeychain = process.platform === "darwin" && config.darwinKeychain !== void 0;
|
|
912
|
+
let authJson;
|
|
913
|
+
if (captureViaKeychain) {
|
|
914
|
+
await runVendorLoginWithInstallOffer(config, binPath, null, explicitBinPath);
|
|
915
|
+
authJson = readDarwinKeychainAuthJson(config);
|
|
916
|
+
} else {
|
|
917
|
+
authJson = await withIsolatedLoginDir(`arcanist-${config.harness}-`, async (tempHome) => {
|
|
887
918
|
await runVendorLoginWithInstallOffer(config, binPath, tempHome, explicitBinPath);
|
|
888
|
-
|
|
919
|
+
return readLoginAuthJson(config, tempHome);
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
const state = await apiFetch(
|
|
923
|
+
apiConfig,
|
|
924
|
+
`${subscriptionBasePath(config.harness)}/auth-json`,
|
|
925
|
+
{
|
|
926
|
+
method: "PUT",
|
|
927
|
+
body: JSON.stringify({ authJson })
|
|
889
928
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
929
|
+
);
|
|
930
|
+
let activated = false;
|
|
931
|
+
let activationError;
|
|
932
|
+
try {
|
|
933
|
+
await setSubscriptionEnabled(apiConfig, config.harness, true);
|
|
934
|
+
activated = true;
|
|
935
|
+
} catch (err) {
|
|
936
|
+
activationError = err instanceof Error ? err.message : String(err);
|
|
937
|
+
}
|
|
938
|
+
emit(command, options, { ...state, enabled: activated }, (payload) => {
|
|
939
|
+
console.log(
|
|
940
|
+
activated ? `${config.displayName} subscription auth saved and activated.` : `${config.displayName} subscription auth saved.`
|
|
897
941
|
);
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
activated = true;
|
|
903
|
-
} catch (err) {
|
|
904
|
-
activationError = err instanceof Error ? err.message : String(err);
|
|
942
|
+
if (payload.lastValidationStatus) console.log(`Status: ${payload.lastValidationStatus}`);
|
|
943
|
+
if (!activated) {
|
|
944
|
+
console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
|
|
945
|
+
console.log(`Run \`arcanist ${config.harness} use on\` to start using it.`);
|
|
905
946
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
if (payload.lastValidationStatus) console.log(`Status: ${payload.lastValidationStatus}`);
|
|
911
|
-
if (!activated) {
|
|
912
|
-
console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
|
|
913
|
-
console.log(`Run \`arcanist ${config.harness} use on\` to start using it.`);
|
|
914
|
-
}
|
|
947
|
+
console.log(
|
|
948
|
+
`${config.displayName} sessions will use your subscription auth while the selector is on; run \`arcanist ${config.harness} use off\` to switch back.`
|
|
949
|
+
);
|
|
950
|
+
if (captureViaKeychain) {
|
|
915
951
|
console.log(
|
|
916
|
-
|
|
952
|
+
`Your local ${config.defaultBin} login also remains active (Keychain-backed); run \`${config.defaultBin} logout\` to remove it.`
|
|
917
953
|
);
|
|
918
|
-
if (captureViaKeychain) {
|
|
919
|
-
console.log(
|
|
920
|
-
`Your local ${config.defaultBin} login also remains active (Keychain-backed); run \`${config.defaultBin} logout\` to remove it.`
|
|
921
|
-
);
|
|
922
|
-
}
|
|
923
|
-
});
|
|
924
|
-
} finally {
|
|
925
|
-
try {
|
|
926
|
-
if (tempHome) {
|
|
927
|
-
await rm(tempHome, { recursive: true, force: true }).catch(() => {
|
|
928
|
-
});
|
|
929
|
-
}
|
|
930
|
-
} finally {
|
|
931
|
-
process.off("SIGINT", handleSigint);
|
|
932
954
|
}
|
|
933
|
-
}
|
|
955
|
+
});
|
|
934
956
|
}
|
|
935
957
|
async function agentSubscriptionUseCommand(config, state, options, command) {
|
|
936
958
|
const normalized = state.trim().toLowerCase();
|
|
@@ -976,58 +998,97 @@ async function agentSubscriptionLogoutCommand(config, options, command) {
|
|
|
976
998
|
);
|
|
977
999
|
}
|
|
978
1000
|
|
|
979
|
-
//
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
// unset VAR) so the server rejects it as invalid instead of silently routing.
|
|
992
|
-
...options.model !== void 0 ? { model: options.model } : {}
|
|
993
|
-
})
|
|
994
|
-
});
|
|
995
|
-
if (!options.wait || trigger.cached) {
|
|
996
|
-
emit(command, options, trigger, (payload) => {
|
|
997
|
-
if (payload.cached) console.log(`Zeus review already completed: ${payload.verdict ?? "unknown"}.`);
|
|
998
|
-
else console.log(`Started Zeus review${payload.sessionId ? ` (${payload.sessionId})` : ""}.`);
|
|
999
|
-
});
|
|
1000
|
-
return;
|
|
1001
|
+
// ../../shared/utils/timing.ts
|
|
1002
|
+
function sleep(ms) {
|
|
1003
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// src/status-poll.ts
|
|
1007
|
+
async function pollUntilSettled(opts) {
|
|
1008
|
+
const deadline = Date.now() + opts.timeoutMs;
|
|
1009
|
+
while (Date.now() < deadline) {
|
|
1010
|
+
const settled = await opts.fetchStatus();
|
|
1011
|
+
if (settled !== null) return settled;
|
|
1012
|
+
await sleep(opts.pollIntervalMs);
|
|
1001
1013
|
}
|
|
1002
|
-
|
|
1003
|
-
emit(command, options, result, (payload) => {
|
|
1004
|
-
console.log(`Zeus review: ${sanitizeTerminalText(payload.verdict)}. ${payload.findings.length} finding(s).`);
|
|
1005
|
-
for (const finding of payload.findings) {
|
|
1006
|
-
console.log(
|
|
1007
|
-
`${sanitizeTerminalText(finding.severity)} ${sanitizeTerminalText(finding.file)}:${finding.line} - ${sanitizeTerminalText(finding.title)}`
|
|
1008
|
-
);
|
|
1009
|
-
}
|
|
1010
|
-
});
|
|
1014
|
+
throw new CliError("server", opts.timeoutMessage);
|
|
1011
1015
|
}
|
|
1012
|
-
function
|
|
1013
|
-
|
|
1016
|
+
function assertSameAttempt(triggeredSessionId, settledSessionId, label) {
|
|
1017
|
+
if (triggeredSessionId && settledSessionId !== triggeredSessionId) {
|
|
1018
|
+
throw new CliError("conflict", `${label} completed for a different attempt; retry the command.`);
|
|
1019
|
+
}
|
|
1014
1020
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1021
|
+
|
|
1022
|
+
// ../../shared/session/phase.ts
|
|
1023
|
+
var PHASES = [
|
|
1024
|
+
"idle",
|
|
1025
|
+
"running",
|
|
1026
|
+
"waiting_for_input",
|
|
1027
|
+
"finalizing",
|
|
1028
|
+
"review_listening",
|
|
1029
|
+
"completed",
|
|
1030
|
+
"superseded",
|
|
1031
|
+
"needs_you",
|
|
1032
|
+
"blocked",
|
|
1033
|
+
"failed",
|
|
1034
|
+
"stopped",
|
|
1035
|
+
"archived"
|
|
1036
|
+
];
|
|
1037
|
+
var TERMINAL_PHASES_ARRAY = [
|
|
1038
|
+
"completed",
|
|
1039
|
+
"superseded",
|
|
1040
|
+
"needs_you",
|
|
1041
|
+
"blocked",
|
|
1042
|
+
"failed",
|
|
1043
|
+
"stopped",
|
|
1044
|
+
"archived"
|
|
1045
|
+
];
|
|
1046
|
+
var TERMINAL_PHASES = new Set(TERMINAL_PHASES_ARRAY);
|
|
1047
|
+
var TERMINAL_FOR_FALLBACK_POLLING_PHASES = new Set(
|
|
1048
|
+
TERMINAL_PHASES_ARRAY.filter((phase) => phase !== "completed")
|
|
1049
|
+
);
|
|
1050
|
+
var CHILD_SLOT_RELEASE_PHASES = new Set(
|
|
1051
|
+
TERMINAL_PHASES_ARRAY.filter((phase) => phase !== "stopped")
|
|
1052
|
+
);
|
|
1053
|
+
var ARCHIVABLE_STALE_TERMINAL_PHASES_ARRAY = TERMINAL_PHASES_ARRAY.filter(
|
|
1054
|
+
(phase) => phase !== "archived" && phase !== "needs_you" && phase !== "blocked" && phase !== "stopped"
|
|
1055
|
+
);
|
|
1056
|
+
function isTerminalPhase(phase, _sessionKind) {
|
|
1057
|
+
return TERMINAL_PHASES.has(phase);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// src/constants/watch.ts
|
|
1061
|
+
var MIN_WATCH_POLL_INTERVAL_MS = 250;
|
|
1062
|
+
var DEFAULT_WATCH_POLL_INTERVAL_MS = 1e3;
|
|
1063
|
+
var MAX_WATCH_POLL_INTERVAL_MS = 6e4;
|
|
1064
|
+
var WATCH_REPLAY_PAGE_SIZE = 200;
|
|
1065
|
+
function isWatchTerminal(phase) {
|
|
1066
|
+
return isTerminalPhase(phase);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// src/utils/poll-interval.ts
|
|
1070
|
+
function clampPollInterval(ms, minimum) {
|
|
1071
|
+
return Math.max(ms, minimum);
|
|
1072
|
+
}
|
|
1073
|
+
function parsePollInterval(raw, opts = {}) {
|
|
1074
|
+
const defaultMs = opts.defaultMs ?? DEFAULT_WATCH_POLL_INTERVAL_MS;
|
|
1075
|
+
const minMs = opts.minMs ?? MIN_WATCH_POLL_INTERVAL_MS;
|
|
1076
|
+
const maxMs = opts.maxMs === void 0 ? MAX_WATCH_POLL_INTERVAL_MS : opts.maxMs;
|
|
1077
|
+
if (!raw) return defaultMs;
|
|
1078
|
+
if (!/^\d+$/.test(raw)) {
|
|
1079
|
+
throw new CliError("user", "Polling interval must be a non-negative integer.");
|
|
1080
|
+
}
|
|
1081
|
+
const value = Number(raw);
|
|
1082
|
+
if (value < minMs || maxMs !== null && value > maxMs) {
|
|
1083
|
+
const range = maxMs === null ? `at least ${minMs}` : `between ${minMs} and ${maxMs}`;
|
|
1084
|
+
throw new CliError("user", `Polling interval must be ${range} milliseconds.`);
|
|
1029
1085
|
}
|
|
1030
|
-
|
|
1086
|
+
return value;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// src/utils/terminal-text.ts
|
|
1090
|
+
function sanitizeTerminalText(value) {
|
|
1091
|
+
return value.replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g, "").replace(/\u001b\[[0-?]*[ -\/]*[@-~]/g, "").replace(/[\u0000-\u001f\u007f]/g, "");
|
|
1031
1092
|
}
|
|
1032
1093
|
|
|
1033
1094
|
// src/commands/anubis.ts
|
|
@@ -1035,6 +1096,7 @@ var ANUBIS_POLL_INTERVAL_MS = 15e3;
|
|
|
1035
1096
|
var ANUBIS_WAIT_TIMEOUT_MS = 45 * 60 * 1e3;
|
|
1036
1097
|
async function anubisCommand(prUrl, options = {}, command) {
|
|
1037
1098
|
assertArcanistSessionMutationAllowed("anubis");
|
|
1099
|
+
const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: ANUBIS_POLL_INTERVAL_MS, maxMs: null }) : null;
|
|
1038
1100
|
const { config } = resolveBusinessContext(command, options);
|
|
1039
1101
|
const trigger = await apiFetch(config, "/api/anubis-runs", {
|
|
1040
1102
|
method: "POST",
|
|
@@ -1046,29 +1108,25 @@ async function anubisCommand(prUrl, options = {}, command) {
|
|
|
1046
1108
|
});
|
|
1047
1109
|
return;
|
|
1048
1110
|
}
|
|
1049
|
-
const result = await
|
|
1111
|
+
const result = await pollUntilSettled({
|
|
1112
|
+
pollIntervalMs: pollIntervalMs ?? ANUBIS_POLL_INTERVAL_MS,
|
|
1113
|
+
timeoutMs: ANUBIS_WAIT_TIMEOUT_MS,
|
|
1114
|
+
timeoutMessage: "Timed out waiting for Anubis results.",
|
|
1115
|
+
fetchStatus: async () => {
|
|
1116
|
+
const status = await apiFetch(
|
|
1117
|
+
config,
|
|
1118
|
+
`/api/anubis-runs/status?prUrl=${encodeURIComponent(prUrl)}`
|
|
1119
|
+
);
|
|
1120
|
+
if (status.sessionStatus === "active") return null;
|
|
1121
|
+
assertSameAttempt(trigger.sessionId, status.sessionId, "Anubis");
|
|
1122
|
+
return status;
|
|
1123
|
+
}
|
|
1124
|
+
});
|
|
1050
1125
|
emit(command, options, result, (payload) => {
|
|
1051
1126
|
console.log(`Anubis: ${sanitizeTerminalText(payload.verdict ?? "could-not-verify")}.`);
|
|
1052
1127
|
if (payload.summary) console.log(sanitizeTerminalText(payload.summary));
|
|
1053
1128
|
});
|
|
1054
1129
|
}
|
|
1055
|
-
async function waitForAnubis(config, prUrl, sessionId) {
|
|
1056
|
-
const deadline = Date.now() + ANUBIS_WAIT_TIMEOUT_MS;
|
|
1057
|
-
while (Date.now() < deadline) {
|
|
1058
|
-
const status = await apiFetch(
|
|
1059
|
-
config,
|
|
1060
|
-
`/api/anubis-runs/status?prUrl=${encodeURIComponent(prUrl)}`
|
|
1061
|
-
);
|
|
1062
|
-
if (status.sessionStatus !== "active") {
|
|
1063
|
-
if (status.sessionId !== sessionId) {
|
|
1064
|
-
throw new CliError("conflict", "Anubis completed for a different attempt; retry the command.");
|
|
1065
|
-
}
|
|
1066
|
-
return status;
|
|
1067
|
-
}
|
|
1068
|
-
await new Promise((resolve2) => setTimeout(resolve2, ANUBIS_POLL_INTERVAL_MS));
|
|
1069
|
-
}
|
|
1070
|
-
throw new CliError("server", "Timed out waiting for Anubis results.");
|
|
1071
|
-
}
|
|
1072
1130
|
|
|
1073
1131
|
// src/commands/auth.ts
|
|
1074
1132
|
async function whoamiCommand(options, command) {
|
|
@@ -2072,11 +2130,8 @@ function formatTime(value) {
|
|
|
2072
2130
|
}
|
|
2073
2131
|
|
|
2074
2132
|
// src/commands/codex.ts
|
|
2075
|
-
import {
|
|
2076
|
-
import {
|
|
2077
|
-
import { mkdtemp as mkdtemp2, readFile as readFile2, rm as rm2 } from "fs/promises";
|
|
2078
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
2079
|
-
import { join as join3 } from "path";
|
|
2133
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
2134
|
+
import { join as join4 } from "path";
|
|
2080
2135
|
var CODEX_SUBSCRIPTION_PATH = "/api/settings/codex-subscription";
|
|
2081
2136
|
var CODEX_SUBSCRIPTION_AUTH_JSON_PATH = "/api/settings/codex-subscription/auth-json";
|
|
2082
2137
|
var CODEX_SUBSCRIPTION_ENABLED_PATH = "/api/settings/codex-subscription/enabled";
|
|
@@ -2092,33 +2147,13 @@ function resolveCodexPath(optionPath) {
|
|
|
2092
2147
|
return optionPath?.trim() || process.env.ARCANIST_CODEX_BIN?.trim() || "codex";
|
|
2093
2148
|
}
|
|
2094
2149
|
function runCodexDeviceLogin(codexPath, codexHome) {
|
|
2095
|
-
return
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
reject(
|
|
2103
|
-
new CliError("user", `Could not find the \`${codexPath}\` executable.`, {
|
|
2104
|
-
hint: "Install the Codex CLI, or point at it with --codex-path <path> or ARCANIST_CODEX_BIN."
|
|
2105
|
-
})
|
|
2106
|
-
);
|
|
2107
|
-
return;
|
|
2108
|
-
}
|
|
2109
|
-
reject(new CliError("user", `Failed to launch \`${codexPath} login --device-auth\`: ${err.message}`));
|
|
2110
|
-
});
|
|
2111
|
-
child.on("close", (code) => {
|
|
2112
|
-
if (code === 0) {
|
|
2113
|
-
resolve2();
|
|
2114
|
-
return;
|
|
2115
|
-
}
|
|
2116
|
-
reject(
|
|
2117
|
-
new CliError("user", `\`${codexPath} login --device-auth\` exited with code ${code ?? "unknown"}.`, {
|
|
2118
|
-
hint: "Complete the device approval in your browser, then re-run `arcanist codex login`."
|
|
2119
|
-
})
|
|
2120
|
-
);
|
|
2121
|
-
});
|
|
2150
|
+
return runVendorLoginProcess({
|
|
2151
|
+
displayName: "Codex",
|
|
2152
|
+
binPath: codexPath,
|
|
2153
|
+
args: ["login", "--device-auth"],
|
|
2154
|
+
env: { CODEX_HOME: codexHome },
|
|
2155
|
+
missingBinaryHint: "Install the Codex CLI, or point at it with --codex-path <path> or ARCANIST_CODEX_BIN.",
|
|
2156
|
+
retryCommand: "arcanist codex login"
|
|
2122
2157
|
});
|
|
2123
2158
|
}
|
|
2124
2159
|
async function setCodexSubscriptionEnabled(config, enabled) {
|
|
@@ -2130,64 +2165,44 @@ async function setCodexSubscriptionEnabled(config, enabled) {
|
|
|
2130
2165
|
async function codexLoginCommand(options, command) {
|
|
2131
2166
|
const { config } = resolveBusinessContext(command, options);
|
|
2132
2167
|
const codexPath = resolveCodexPath(options.codexPath);
|
|
2133
|
-
|
|
2134
|
-
const handleSigint = () => {
|
|
2135
|
-
if (codexHome) {
|
|
2136
|
-
try {
|
|
2137
|
-
rmSync2(codexHome, { recursive: true, force: true });
|
|
2138
|
-
} catch {
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
process.exit(EXIT_CODE_INTERRUPTED);
|
|
2142
|
-
};
|
|
2143
|
-
process.on("SIGINT", handleSigint);
|
|
2144
|
-
try {
|
|
2145
|
-
codexHome = await mkdtemp2(join3(tmpdir2(), "arcanist-codex-"));
|
|
2168
|
+
const authJson = await withIsolatedLoginDir("arcanist-codex-", async (codexHome) => {
|
|
2146
2169
|
await runCodexDeviceLogin(codexPath, codexHome);
|
|
2147
|
-
let
|
|
2170
|
+
let raw;
|
|
2148
2171
|
try {
|
|
2149
|
-
|
|
2172
|
+
raw = await readFile2(join4(codexHome, "auth.json"), "utf8");
|
|
2150
2173
|
} catch {
|
|
2151
2174
|
throw new CliError("user", "Codex login completed but no auth.json was written.", {
|
|
2152
2175
|
hint: "Verify `codex login --device-auth` succeeds on its own, then re-run `arcanist codex login`."
|
|
2153
2176
|
});
|
|
2154
2177
|
}
|
|
2155
|
-
if (!
|
|
2178
|
+
if (!raw.trim()) {
|
|
2156
2179
|
throw new CliError("user", "Codex login produced an empty auth.json.");
|
|
2157
2180
|
}
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
console.log(
|
|
2172
|
-
activated ? "Codex subscription auth saved and activated for OpenAI sessions." : "Codex subscription auth saved."
|
|
2173
|
-
);
|
|
2174
|
-
const status = describeCredentialStatus(payload);
|
|
2175
|
-
if (status) console.log(`Status: ${status}`);
|
|
2176
|
-
if (!activated) {
|
|
2177
|
-
console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
|
|
2178
|
-
console.log("Run `arcanist codex use on` to start using it.");
|
|
2179
|
-
}
|
|
2180
|
-
});
|
|
2181
|
-
} finally {
|
|
2182
|
-
try {
|
|
2183
|
-
if (codexHome) {
|
|
2184
|
-
await rm2(codexHome, { recursive: true, force: true }).catch(() => {
|
|
2185
|
-
});
|
|
2186
|
-
}
|
|
2187
|
-
} finally {
|
|
2188
|
-
process.off("SIGINT", handleSigint);
|
|
2189
|
-
}
|
|
2181
|
+
return raw;
|
|
2182
|
+
});
|
|
2183
|
+
const state = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
|
|
2184
|
+
method: "PUT",
|
|
2185
|
+
body: JSON.stringify({ authJson })
|
|
2186
|
+
});
|
|
2187
|
+
let activated = false;
|
|
2188
|
+
let activationError;
|
|
2189
|
+
try {
|
|
2190
|
+
await setCodexSubscriptionEnabled(config, true);
|
|
2191
|
+
activated = true;
|
|
2192
|
+
} catch (err) {
|
|
2193
|
+
activationError = err instanceof Error ? err.message : String(err);
|
|
2190
2194
|
}
|
|
2195
|
+
emit(command, options, { ...state, useCodexSubscription: activated }, (payload) => {
|
|
2196
|
+
console.log(
|
|
2197
|
+
activated ? "Codex subscription auth saved and activated for OpenAI sessions." : "Codex subscription auth saved."
|
|
2198
|
+
);
|
|
2199
|
+
const status = describeCredentialStatus(payload);
|
|
2200
|
+
if (status) console.log(`Status: ${status}`);
|
|
2201
|
+
if (!activated) {
|
|
2202
|
+
console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
|
|
2203
|
+
console.log("Run `arcanist codex use on` to start using it.");
|
|
2204
|
+
}
|
|
2205
|
+
});
|
|
2191
2206
|
}
|
|
2192
2207
|
async function codexUseCommand(state, options, command) {
|
|
2193
2208
|
const normalized = state.trim().toLowerCase();
|
|
@@ -2226,58 +2241,6 @@ async function codexLogoutCommand(options, command) {
|
|
|
2226
2241
|
emit(command, options, payload, () => console.log("Codex subscription auth deactivated and cleared."));
|
|
2227
2242
|
}
|
|
2228
2243
|
|
|
2229
|
-
// ../../shared/utils/timing.ts
|
|
2230
|
-
function sleep(ms) {
|
|
2231
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
2232
|
-
}
|
|
2233
|
-
|
|
2234
|
-
// ../../shared/session/phase.ts
|
|
2235
|
-
var PHASES = [
|
|
2236
|
-
"idle",
|
|
2237
|
-
"running",
|
|
2238
|
-
"waiting_for_input",
|
|
2239
|
-
"finalizing",
|
|
2240
|
-
"review_listening",
|
|
2241
|
-
"completed",
|
|
2242
|
-
"superseded",
|
|
2243
|
-
"needs_you",
|
|
2244
|
-
"blocked",
|
|
2245
|
-
"failed",
|
|
2246
|
-
"stopped",
|
|
2247
|
-
"archived"
|
|
2248
|
-
];
|
|
2249
|
-
var TERMINAL_PHASES_ARRAY = [
|
|
2250
|
-
"completed",
|
|
2251
|
-
"superseded",
|
|
2252
|
-
"needs_you",
|
|
2253
|
-
"blocked",
|
|
2254
|
-
"failed",
|
|
2255
|
-
"stopped",
|
|
2256
|
-
"archived"
|
|
2257
|
-
];
|
|
2258
|
-
var TERMINAL_PHASES = new Set(TERMINAL_PHASES_ARRAY);
|
|
2259
|
-
var TERMINAL_FOR_FALLBACK_POLLING_PHASES = new Set(
|
|
2260
|
-
TERMINAL_PHASES_ARRAY.filter((phase) => phase !== "completed")
|
|
2261
|
-
);
|
|
2262
|
-
var CHILD_SLOT_RELEASE_PHASES = new Set(
|
|
2263
|
-
TERMINAL_PHASES_ARRAY.filter((phase) => phase !== "stopped")
|
|
2264
|
-
);
|
|
2265
|
-
var ARCHIVABLE_STALE_TERMINAL_PHASES_ARRAY = TERMINAL_PHASES_ARRAY.filter(
|
|
2266
|
-
(phase) => phase !== "archived" && phase !== "needs_you" && phase !== "blocked" && phase !== "stopped"
|
|
2267
|
-
);
|
|
2268
|
-
function isTerminalPhase(phase, _sessionKind) {
|
|
2269
|
-
return TERMINAL_PHASES.has(phase);
|
|
2270
|
-
}
|
|
2271
|
-
|
|
2272
|
-
// src/constants/watch.ts
|
|
2273
|
-
var MIN_WATCH_POLL_INTERVAL_MS = 250;
|
|
2274
|
-
var DEFAULT_WATCH_POLL_INTERVAL_MS = 1e3;
|
|
2275
|
-
var MAX_WATCH_POLL_INTERVAL_MS = 6e4;
|
|
2276
|
-
var WATCH_REPLAY_PAGE_SIZE = 200;
|
|
2277
|
-
function isWatchTerminal(phase) {
|
|
2278
|
-
return isTerminalPhase(phase);
|
|
2279
|
-
}
|
|
2280
|
-
|
|
2281
2244
|
// src/uploads.ts
|
|
2282
2245
|
import { readFile as readFile3 } from "fs/promises";
|
|
2283
2246
|
import { basename, extname } from "path";
|
|
@@ -2423,26 +2386,6 @@ function normalizeUploadedFileOptions(files) {
|
|
|
2423
2386
|
return Array.isArray(files) ? files : [files];
|
|
2424
2387
|
}
|
|
2425
2388
|
|
|
2426
|
-
// src/utils/poll-interval.ts
|
|
2427
|
-
function clampPollInterval(ms, minimum) {
|
|
2428
|
-
return Math.max(ms, minimum);
|
|
2429
|
-
}
|
|
2430
|
-
function parsePollInterval(raw, opts = {}) {
|
|
2431
|
-
const defaultMs = opts.defaultMs ?? DEFAULT_WATCH_POLL_INTERVAL_MS;
|
|
2432
|
-
const minMs = opts.minMs ?? MIN_WATCH_POLL_INTERVAL_MS;
|
|
2433
|
-
const maxMs = opts.maxMs === void 0 ? MAX_WATCH_POLL_INTERVAL_MS : opts.maxMs;
|
|
2434
|
-
if (!raw) return defaultMs;
|
|
2435
|
-
if (!/^\d+$/.test(raw)) {
|
|
2436
|
-
throw new CliError("user", "Polling interval must be a non-negative integer.");
|
|
2437
|
-
}
|
|
2438
|
-
const value = Number(raw);
|
|
2439
|
-
if (value < minMs || maxMs !== null && value > maxMs) {
|
|
2440
|
-
const range = maxMs === null ? `at least ${minMs}` : `between ${minMs} and ${maxMs}`;
|
|
2441
|
-
throw new CliError("user", `Polling interval must be ${range} milliseconds.`);
|
|
2442
|
-
}
|
|
2443
|
-
return value;
|
|
2444
|
-
}
|
|
2445
|
-
|
|
2446
2389
|
// ../../shared/session/no-change-outcome.ts
|
|
2447
2390
|
function isNoChangesPromptResult(result) {
|
|
2448
2391
|
return typeof result === "object" && result !== null && "noChanges" in result && result.noChanges === true;
|
|
@@ -4783,6 +4726,55 @@ function parseRespondConflict(err) {
|
|
|
4783
4726
|
};
|
|
4784
4727
|
}
|
|
4785
4728
|
|
|
4729
|
+
// src/commands/review.ts
|
|
4730
|
+
var PR_REVIEW_POLL_INTERVAL_MS = 1e4;
|
|
4731
|
+
var PR_REVIEW_WAIT_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
4732
|
+
async function reviewCommand(prUrl, options = {}, command) {
|
|
4733
|
+
assertArcanistSessionMutationAllowed("review");
|
|
4734
|
+
const pollIntervalMs = options.wait ? parsePollInterval(options.pollInterval, { defaultMs: PR_REVIEW_POLL_INTERVAL_MS, maxMs: null }) : null;
|
|
4735
|
+
const { config } = resolveBusinessContext(command, options);
|
|
4736
|
+
const trigger = await apiFetch(config, "/api/pr-reviews", {
|
|
4737
|
+
method: "POST",
|
|
4738
|
+
body: JSON.stringify({
|
|
4739
|
+
prUrl,
|
|
4740
|
+
...options.focus ? { focus: options.focus } : {},
|
|
4741
|
+
// Send an explicitly passed model even when empty (e.g. --model "$VAR" with an
|
|
4742
|
+
// unset VAR) so the server rejects it as invalid instead of silently routing.
|
|
4743
|
+
...options.model !== void 0 ? { model: options.model } : {}
|
|
4744
|
+
})
|
|
4745
|
+
});
|
|
4746
|
+
if (!options.wait || trigger.cached) {
|
|
4747
|
+
emit(command, options, trigger, (payload) => {
|
|
4748
|
+
if (payload.cached) console.log(`Zeus review already completed: ${payload.verdict ?? "unknown"}.`);
|
|
4749
|
+
else console.log(`Started Zeus review${payload.sessionId ? ` (${payload.sessionId})` : ""}.`);
|
|
4750
|
+
});
|
|
4751
|
+
return;
|
|
4752
|
+
}
|
|
4753
|
+
const triggeredSessionId = trigger.sessionId ?? null;
|
|
4754
|
+
const result = await pollUntilSettled({
|
|
4755
|
+
pollIntervalMs: pollIntervalMs ?? PR_REVIEW_POLL_INTERVAL_MS,
|
|
4756
|
+
timeoutMs: PR_REVIEW_WAIT_TIMEOUT_MS,
|
|
4757
|
+
timeoutMessage: "Timed out waiting for Zeus review results.",
|
|
4758
|
+
fetchStatus: async () => {
|
|
4759
|
+
const status = await apiFetch(
|
|
4760
|
+
config,
|
|
4761
|
+
`/api/pr-reviews/status?prUrl=${encodeURIComponent(prUrl)}`
|
|
4762
|
+
);
|
|
4763
|
+
if (status.status !== "completed") return null;
|
|
4764
|
+
assertSameAttempt(triggeredSessionId, status.sessionId, "PR review");
|
|
4765
|
+
return { ...status, sessionId: status.sessionId ?? triggeredSessionId };
|
|
4766
|
+
}
|
|
4767
|
+
});
|
|
4768
|
+
emit(command, options, result, (payload) => {
|
|
4769
|
+
console.log(`Zeus review: ${sanitizeTerminalText(payload.verdict)}. ${payload.findings.length} finding(s).`);
|
|
4770
|
+
for (const finding of payload.findings) {
|
|
4771
|
+
console.log(
|
|
4772
|
+
`${sanitizeTerminalText(finding.severity)} ${sanitizeTerminalText(finding.file)}:${finding.line} - ${sanitizeTerminalText(finding.title)}`
|
|
4773
|
+
);
|
|
4774
|
+
}
|
|
4775
|
+
});
|
|
4776
|
+
}
|
|
4777
|
+
|
|
4786
4778
|
// src/commands/sandbox.ts
|
|
4787
4779
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4788
4780
|
import { dirname as dirname2 } from "path";
|
|
@@ -6391,7 +6383,7 @@ cursor.command("use <state>").description("Turn using your saved Cursor subscrip
|
|
|
6391
6383
|
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));
|
|
6392
6384
|
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));
|
|
6393
6385
|
var sessions = program.command("sessions").description("Session commands");
|
|
6394
|
-
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(
|
|
6386
|
+
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(
|
|
6395
6387
|
"after",
|
|
6396
6388
|
`
|
|
6397
6389
|
Examples:
|
|
@@ -6405,7 +6397,7 @@ a specific reviewer model instead (members only).
|
|
|
6405
6397
|
Zeus review is currently available to Arcanist members only.
|
|
6406
6398
|
`
|
|
6407
6399
|
).action((prUrl, options, command) => reviewCommand(prUrl, options, command));
|
|
6408
|
-
program.command("anubis <pr-url>").description("Run an Anubis QA verification for a GitHub pull request").option("--wait", "Wait for the verdict").addHelpText(
|
|
6400
|
+
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(
|
|
6409
6401
|
"after",
|
|
6410
6402
|
`
|
|
6411
6403
|
Examples:
|