@tryarcanist/cli 0.1.230 → 0.1.232

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 +3 -3
  2. package/dist/index.js +108 -704
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -197,7 +197,7 @@ arcanist sessions create your-org/your-repo "review the trace" --uploaded-file t
197
197
  arcanist sessions create your-org/your-repo "retry-safe create" --idempotency-key 1f0e6f1a-...
198
198
  ```
199
199
 
200
- `--backend` picks the agent runtime backend: `codex` (default), `claude_code`, `opencode`, `cursor`, or `grok`. `--model` must be valid for the chosen backend and the CLI rejects a mismatch before any network call; run `arcanist models list` for the selectable models per backend. When `--model` is omitted the backend default is used. Non-codex backends require the matching provider credential configured in Settings.
200
+ `--backend` picks the agent runtime backend: `codex` (default) or `claude_code`. `--model` must be valid for the chosen backend and the CLI rejects a mismatch before any network call; run `arcanist models list` for the selectable models per backend. When `--model` is omitted the backend default is used. Non-codex backends require the matching provider credential configured in Settings.
201
201
 
202
202
  `--wait` blocks until the created prompt finishes, prints the resulting PR/branch line in human-readable mode when it is already available, and exits non-zero if the prompt finishes with `status: failed`, making it suitable for cron or other schedulers that alert on command failure. JSON mode waits quietly and prints the create payload after the prompt completes successfully. `--poll-interval <ms>` tunes the completion check frequency.
203
203
 
@@ -242,7 +242,7 @@ The PR URL must be `https://github.com/<owner>/<repo>/pull/<number>`.
242
242
  The CLI derives the repo from that URL, creates a QA session with `qa: true` and `targetPrUrl`, then enqueues the verifier prompt (skipped when the server signals `promptAlreadyEnqueued`).
243
243
  Inspect progress with the session URL or the session events stream.
244
244
 
245
- `--backend` picks the agent runtime backend: `codex` (default), `claude_code`, `opencode`, `cursor`, or `grok`.
245
+ `--backend` picks the agent runtime backend: `codex` (default) or `claude_code`.
246
246
  `--model` must be valid for the chosen backend, and the CLI rejects mismatches before any network call.
247
247
  When `--model` is omitted, the backend default is used.
248
248
 
@@ -435,7 +435,7 @@ printf "summarize new regressions" | arcanist automations create your-org/your-r
435
435
  arcanist automations create https://github.com/your-org/your-repo - --cron "0 14 * * 1-5" --name "Weekday summary"
436
436
  ```
437
437
 
438
- Use `--model <model>` to pin the model for sessions started by the automation. Backend is derived from the model; non-codex backends such as Claude or opencode are limited to Arcanist team businesses. When `--model` is omitted the codex backend default is used.
438
+ Use `--model <model>` to pin the model for sessions started by the automation. Backend is derived from the model; non-codex backends such as Claude are limited to Arcanist team businesses. When `--model` is omitted the codex backend default is used.
439
439
 
440
440
  Use `--slack-team-id <team-id>` together with `--slack-channel-id <channel-id>` to deliver each completed automation digest into a Slack channel. Both flags are required together; the target workspace must already be connected to Arcanist and the bot must already be in the channel.
441
441
 
package/dist/index.js CHANGED
@@ -7,36 +7,18 @@ import { Command } from "commander";
7
7
  // ../../shared/agent/agent-runtime-backend.ts
8
8
  var CODEX_AGENT_RUNTIME_BACKEND = "codex";
9
9
  var CLAUDE_CODE_AGENT_RUNTIME_BACKEND = "claude_code";
10
- var OPENCODE_AGENT_RUNTIME_BACKEND = "opencode";
11
- var CURSOR_AGENT_RUNTIME_BACKEND = "cursor";
12
- var GROK_AGENT_RUNTIME_BACKEND = "grok";
13
10
  var AGENT_RUNTIME_BACKENDS = [
14
11
  CODEX_AGENT_RUNTIME_BACKEND,
15
- CLAUDE_CODE_AGENT_RUNTIME_BACKEND,
16
- OPENCODE_AGENT_RUNTIME_BACKEND,
17
- CURSOR_AGENT_RUNTIME_BACKEND,
18
- GROK_AGENT_RUNTIME_BACKEND
12
+ CLAUDE_CODE_AGENT_RUNTIME_BACKEND
19
13
  ];
20
14
  var AGENT_RUNTIME_BACKEND_NAMES = {
21
15
  [CODEX_AGENT_RUNTIME_BACKEND]: "Codex",
22
- [CLAUDE_CODE_AGENT_RUNTIME_BACKEND]: "Claude Code",
23
- // "opencode" is intentionally lowercase to match the project's brand name.
24
- [OPENCODE_AGENT_RUNTIME_BACKEND]: "opencode",
25
- [CURSOR_AGENT_RUNTIME_BACKEND]: "Cursor",
26
- [GROK_AGENT_RUNTIME_BACKEND]: "Grok Build"
16
+ [CLAUDE_CODE_AGENT_RUNTIME_BACKEND]: "Claude Code"
27
17
  };
28
18
  function isAgentRuntimeBackend(value) {
29
19
  return AGENT_RUNTIME_BACKENDS.includes(value);
30
20
  }
31
21
 
32
- // src/commands/agent-subscription.ts
33
- import { execFileSync, spawn as spawn2 } from "child_process";
34
- import { existsSync as existsSync2 } from "fs";
35
- import { readFile } from "fs/promises";
36
- import { homedir as homedir2 } from "os";
37
- import { join as join3 } from "path";
38
- import { createInterface as createInterface2 } from "readline/promises";
39
-
40
22
  // src/api.ts
41
23
  import { createRequire } from "module";
42
24
 
@@ -668,364 +650,6 @@ function isControlCharacter(char) {
668
650
  return code < ASCII_FIRST_PRINTABLE || code === ASCII_DELETE;
669
651
  }
670
652
 
671
- // src/vendor-login.ts
672
- import { spawn } from "child_process";
673
- import { rmSync } from "fs";
674
- import { mkdtemp, rm } from "fs/promises";
675
- import { tmpdir } from "os";
676
- import { join as join2 } from "path";
677
- var VendorBinaryMissingError = class extends CliError {
678
- constructor(binPath, hint) {
679
- super("user", `Could not find the \`${binPath}\` executable.`, { hint });
680
- this.name = "VendorBinaryMissingError";
681
- }
682
- };
683
- function runVendorLoginProcess(spec) {
684
- return new Promise((resolve3, reject) => {
685
- const child = spawn(spec.binPath, spec.args, {
686
- stdio: "inherit",
687
- env: { ...process.env, ...spec.env }
688
- });
689
- child.on("error", (err) => {
690
- if (err.code === "ENOENT") {
691
- reject(new VendorBinaryMissingError(spec.binPath, spec.missingBinaryHint));
692
- return;
693
- }
694
- reject(new CliError("user", `Failed to launch \`${spec.binPath} ${spec.args.join(" ")}\`: ${err.message}`));
695
- });
696
- child.on("close", (code) => {
697
- if (code === 0) {
698
- resolve3();
699
- return;
700
- }
701
- reject(
702
- new CliError("user", `\`${spec.binPath} ${spec.args.join(" ")}\` exited with code ${code ?? "unknown"}.`, {
703
- hint: `Complete the ${spec.displayName} login, then re-run \`${spec.retryCommand}\`.`
704
- })
705
- );
706
- });
707
- });
708
- }
709
- async function withIsolatedLoginDir(prefix, fn) {
710
- let tempDir;
711
- const handleSigint = () => {
712
- if (tempDir) {
713
- try {
714
- rmSync(tempDir, { recursive: true, force: true });
715
- } catch {
716
- }
717
- }
718
- process.exit(EXIT_CODE_INTERRUPTED);
719
- };
720
- process.on("SIGINT", handleSigint);
721
- try {
722
- tempDir = await mkdtemp(join2(tmpdir(), prefix));
723
- return await fn(tempDir);
724
- } finally {
725
- try {
726
- if (tempDir) {
727
- await rm(tempDir, { recursive: true, force: true }).catch(() => {
728
- });
729
- }
730
- } finally {
731
- process.off("SIGINT", handleSigint);
732
- }
733
- }
734
- }
735
-
736
- // src/commands/agent-subscription.ts
737
- var GROK_SUBSCRIPTION_CLI_CONFIG = {
738
- harness: "grok",
739
- displayName: "Grok",
740
- defaultBin: "grok",
741
- binEnvVar: "ARCANIST_GROK_BIN",
742
- // RFC 8628 device flow: prints a verification URL + code, usable from a
743
- // terminal without a local browser.
744
- loginArgs: ["login", "--device-auth"],
745
- loginEnv: () => ({}),
746
- authJsonCandidates: [join3(".grok", "auth.json")],
747
- // Grok Build's auth.json is issuer-keyed (observed 2026-07-15 against the
748
- // installed CLI's embedded docs): {"https://accounts.x.ai/sign-in": {"key":
749
- // "…"}} — so the credential fields live one level down and the token field
750
- // is named `key`. Token detection scans nested objects.
751
- tokenFields: ["access_token", "refresh_token", "key"],
752
- installHint: "Install the Grok CLI (https://x.ai/cli), or point at it with --grok-path <path> or ARCANIST_GROK_BIN.",
753
- // Official Grok Build installer: installs `grok` (plus an `agent` alias)
754
- // under ~/.grok/bin, symlinks into ~/.local/bin or /usr/local/bin when
755
- // writable, and appends PATH exports to shell rc files itself.
756
- installer: {
757
- command: "curl -fsSL https://x.ai/cli/install.sh | bash",
758
- binCandidates: (home) => [
759
- join3(home, ".grok", "bin", "grok"),
760
- join3(home, ".local", "bin", "grok"),
761
- "/usr/local/bin/grok"
762
- ],
763
- postInstallNote: "Grok CLI installed. Restart your shell if `grok` is not found on PATH later."
764
- }
765
- };
766
- var CURSOR_SUBSCRIPTION_CLI_CONFIG = {
767
- harness: "cursor",
768
- displayName: "Cursor",
769
- defaultBin: "cursor-agent",
770
- binEnvVar: "ARCANIST_CURSOR_AGENT_BIN",
771
- loginArgs: ["login"],
772
- // Cursor's CLI stores login state under its config dir; the location is not
773
- // formally documented, so pin every documented override at the throwaway
774
- // HOME and search the known spots afterwards.
775
- loginEnv: (tempHome) => ({
776
- CURSOR_CONFIG_DIR: join3(tempHome, ".cursor"),
777
- XDG_CONFIG_HOME: join3(tempHome, ".config")
778
- }),
779
- authJsonCandidates: [
780
- join3(".cursor", "auth.json"),
781
- join3(".cursor", "cli-config.json"),
782
- join3(".config", "cursor", "auth.json"),
783
- join3(".config", "cursor", "cli-config.json")
784
- ],
785
- tokenFields: ["accessToken", "access_token", "refreshToken", "refresh_token", "token"],
786
- 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.",
787
- // Entry names observed 2026-07-15 after `cursor-agent login` on macOS
788
- // (login keychain, account "cursor-user").
789
- darwinKeychain: {
790
- entries: [
791
- { service: "cursor-access-token", field: "accessToken", required: true },
792
- { service: "cursor-refresh-token", field: "refreshToken", required: false }
793
- ]
794
- },
795
- // Official Cursor CLI installer: installs to ~/.local/bin. Installers have
796
- // shipped the binary as both `cursor-agent` and `agent` (the 2026-07
797
- // installer's success message says `agent`), so probe both — unambiguous
798
- // name first, since `agent` is also Grok's alias.
799
- installer: {
800
- command: "curl https://cursor.com/install -fsS | bash",
801
- binCandidates: (home) => [join3(home, ".local", "bin", "cursor-agent"), join3(home, ".local", "bin", "agent")],
802
- postInstallNote: 'Cursor CLI installed to ~/.local/bin. Add it to your PATH for future shells: export PATH="$HOME/.local/bin:$PATH"'
803
- }
804
- };
805
- function subscriptionBasePath(harness) {
806
- return `/api/settings/agent-subscriptions/${harness}`;
807
- }
808
- function resolveVendorBin(config, optionPath) {
809
- return optionPath?.trim() || process.env[config.binEnvVar]?.trim() || config.defaultBin;
810
- }
811
- function runVendorInstall(config, command) {
812
- return new Promise((resolve3, reject) => {
813
- const child = spawn2("bash", ["-c", command], { stdio: ["inherit", 2, "inherit"] });
814
- child.on("error", (err) => {
815
- reject(
816
- new CliError("user", `Failed to run the ${config.displayName} CLI installer: ${err.message}`, {
817
- hint: config.installHint
818
- })
819
- );
820
- });
821
- child.on("close", (code) => {
822
- if (code === 0) {
823
- resolve3();
824
- return;
825
- }
826
- reject(
827
- new CliError("user", `The ${config.displayName} CLI installer exited with code ${code ?? "unknown"}.`, {
828
- hint: config.installHint
829
- })
830
- );
831
- });
832
- });
833
- }
834
- async function offerVendorInstall(config, explicitBinPath) {
835
- const installer = config.installer;
836
- if (!installer || explicitBinPath) return null;
837
- if (process.platform !== "linux" && process.platform !== "darwin") return null;
838
- if (!process.stdin.isTTY || !process.stdout.isTTY) return null;
839
- console.error(`Could not find the \`${config.defaultBin}\` executable.`);
840
- console.error(`Install it now by running: ${installer.command}`);
841
- const rl = createInterface2({ input: process.stdin, output: process.stderr });
842
- let answer;
843
- try {
844
- answer = (await rl.question("Proceed? [y/N] ")).trim().toLowerCase();
845
- } finally {
846
- rl.close();
847
- }
848
- if (answer !== "y" && answer !== "yes") return null;
849
- await runVendorInstall(config, installer.command);
850
- for (const candidate of installer.binCandidates(homedir2())) {
851
- if (existsSync2(candidate)) {
852
- if (installer.postInstallNote) console.error(installer.postInstallNote);
853
- return candidate;
854
- }
855
- }
856
- return config.defaultBin;
857
- }
858
- async function runVendorLoginWithInstallOffer(config, binPath, tempHome, explicitBinPath) {
859
- try {
860
- await runVendorLogin(config, binPath, tempHome);
861
- } catch (err) {
862
- if (!(err instanceof VendorBinaryMissingError)) throw err;
863
- const installedBin = await offerVendorInstall(config, explicitBinPath);
864
- if (!installedBin) throw err;
865
- await runVendorLogin(config, installedBin, tempHome);
866
- }
867
- }
868
- function runVendorLogin(config, binPath, tempHome) {
869
- return runVendorLoginProcess({
870
- displayName: config.displayName,
871
- binPath,
872
- args: config.loginArgs,
873
- env: tempHome ? { HOME: tempHome, ...config.loginEnv(tempHome) } : {},
874
- missingBinaryHint: config.installHint,
875
- retryCommand: `arcanist ${config.harness} login`
876
- });
877
- }
878
- function hasTokenField(value, tokenFields, depth = 0) {
879
- if (!value || typeof value !== "object" || Array.isArray(value) || depth > 2) return false;
880
- const record = value;
881
- if (tokenFields.some((field) => typeof record[field] === "string" && record[field].trim().length > 0)) {
882
- return true;
883
- }
884
- return Object.values(record).some((nested) => hasTokenField(nested, tokenFields, depth + 1));
885
- }
886
- function readDarwinKeychainAuthJson(config) {
887
- const record = {};
888
- for (const entry of config.darwinKeychain?.entries ?? []) {
889
- let value = "";
890
- try {
891
- value = execFileSync("security", ["find-generic-password", "-s", entry.service, "-w"], {
892
- encoding: "utf8"
893
- }).trim();
894
- } catch {
895
- }
896
- if (value) {
897
- record[entry.field] = value;
898
- } else if (entry.required) {
899
- throw new CliError(
900
- "user",
901
- `${config.displayName} login completed but the '${entry.service}' Keychain item could not be read.`,
902
- {
903
- hint: `Approve the macOS Keychain access prompt if one appeared, then re-run \`arcanist ${config.harness} login\`.`
904
- }
905
- );
906
- }
907
- }
908
- return JSON.stringify(record);
909
- }
910
- async function readLoginAuthJson(config, tempHome) {
911
- for (const candidate of config.authJsonCandidates) {
912
- const path = join3(tempHome, candidate);
913
- if (!existsSync2(path)) continue;
914
- let raw;
915
- try {
916
- raw = await readFile(path, "utf8");
917
- } catch {
918
- continue;
919
- }
920
- try {
921
- if (hasTokenField(JSON.parse(raw), config.tokenFields)) return raw;
922
- } catch {
923
- }
924
- }
925
- throw new CliError("user", `${config.displayName} login completed but no auth credential file was found.`, {
926
- hint: `Verify \`${config.defaultBin} ${config.loginArgs.join(" ")}\` succeeds on its own, then re-run \`arcanist ${config.harness} login\`.`
927
- });
928
- }
929
- async function setSubscriptionEnabled(apiConfig, harness, enabled) {
930
- return apiFetch(apiConfig, `${subscriptionBasePath(harness)}/enabled`, {
931
- method: "PUT",
932
- body: JSON.stringify({ enabled })
933
- });
934
- }
935
- async function agentSubscriptionLoginCommand(config, options, command) {
936
- const { config: apiConfig } = resolveBusinessContext(command, options);
937
- const binPath = resolveVendorBin(config, options.binPath);
938
- const explicitBinPath = Boolean(options.binPath?.trim() || process.env[config.binEnvVar]?.trim());
939
- const captureViaKeychain = process.platform === "darwin" && config.darwinKeychain !== void 0;
940
- let authJson;
941
- if (captureViaKeychain) {
942
- await runVendorLoginWithInstallOffer(config, binPath, null, explicitBinPath);
943
- authJson = readDarwinKeychainAuthJson(config);
944
- } else {
945
- authJson = await withIsolatedLoginDir(`arcanist-${config.harness}-`, async (tempHome) => {
946
- await runVendorLoginWithInstallOffer(config, binPath, tempHome, explicitBinPath);
947
- return readLoginAuthJson(config, tempHome);
948
- });
949
- }
950
- const state = await apiFetch(
951
- apiConfig,
952
- `${subscriptionBasePath(config.harness)}/auth-json`,
953
- {
954
- method: "PUT",
955
- body: JSON.stringify({ authJson })
956
- }
957
- );
958
- let activated = false;
959
- let activationError;
960
- try {
961
- await setSubscriptionEnabled(apiConfig, config.harness, true);
962
- activated = true;
963
- } catch (err) {
964
- activationError = err instanceof Error ? err.message : String(err);
965
- }
966
- emit(command, options, { ...state, enabled: activated }, (payload) => {
967
- console.log(
968
- activated ? `${config.displayName} subscription auth saved and activated.` : `${config.displayName} subscription auth saved.`
969
- );
970
- if (payload.lastValidationStatus) console.log(`Status: ${payload.lastValidationStatus}`);
971
- if (!activated) {
972
- console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
973
- console.log(`Run \`arcanist ${config.harness} use on\` to start using it.`);
974
- }
975
- console.log(
976
- `${config.displayName} sessions will use your subscription auth while the selector is on; run \`arcanist ${config.harness} use off\` to switch back.`
977
- );
978
- if (captureViaKeychain) {
979
- console.log(
980
- `Your local ${config.defaultBin} login also remains active (Keychain-backed); run \`${config.defaultBin} logout\` to remove it.`
981
- );
982
- }
983
- });
984
- }
985
- async function agentSubscriptionUseCommand(config, state, options, command) {
986
- const normalized = state.trim().toLowerCase();
987
- if (normalized !== "on" && normalized !== "off") {
988
- throw new CliError("user", `Usage: arcanist ${config.harness} use <on|off>`);
989
- }
990
- const enabled = normalized === "on";
991
- const { config: apiConfig } = resolveBusinessContext(command, options);
992
- const result = await setSubscriptionEnabled(apiConfig, config.harness, enabled);
993
- emit(
994
- command,
995
- options,
996
- result,
997
- () => console.log(
998
- result.enabled ? `${config.displayName} subscription auth is now selected for ${config.displayName} sessions.` : `${config.displayName} subscription auth is no longer selected.`
999
- )
1000
- );
1001
- }
1002
- async function agentSubscriptionStatusCommand(config, options, command) {
1003
- const { config: apiConfig } = resolveBusinessContext(command, options);
1004
- const payload = await apiFetch(apiConfig, subscriptionBasePath(config.harness));
1005
- emit(command, options, payload, (state) => {
1006
- console.log(`Eligible: ${state.eligible ? "yes" : "no"}`);
1007
- console.log(`Auth saved: ${state.credential.isSet ? "yes" : "no"}`);
1008
- console.log(`Selected: ${state.enabled ? "yes" : "no"}`);
1009
- if (state.credential.lastValidationStatus) console.log(`Status: ${state.credential.lastValidationStatus}`);
1010
- });
1011
- }
1012
- async function agentSubscriptionLogoutCommand(config, options, command) {
1013
- const { config: apiConfig } = resolveBusinessContext(command, options);
1014
- await setSubscriptionEnabled(apiConfig, config.harness, false).catch(() => {
1015
- });
1016
- const payload = await apiFetch(
1017
- apiConfig,
1018
- `${subscriptionBasePath(config.harness)}/auth-json`,
1019
- { method: "DELETE" }
1020
- );
1021
- emit(
1022
- command,
1023
- options,
1024
- payload,
1025
- () => console.log(`${config.displayName} subscription auth deactivated and cleared.`)
1026
- );
1027
- }
1028
-
1029
653
  // ../../shared/utils/timing.ts
1030
654
  function sleep(ms) {
1031
655
  return new Promise((resolve3) => setTimeout(resolve3, ms));
@@ -1157,8 +781,8 @@ async function anubisCommand(prUrl, options = {}, command) {
1157
781
  }
1158
782
 
1159
783
  // src/commands/artifacts.ts
1160
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
1161
- import { basename, join as join4, resolve as resolve2 } from "path";
784
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
785
+ import { basename, join as join2, resolve as resolve2 } from "path";
1162
786
  async function fetchArtifacts(config, sessionId) {
1163
787
  const payload = await apiFetch(
1164
788
  config,
@@ -1173,14 +797,14 @@ function artifactFilename(artifact) {
1173
797
  }
1174
798
  async function downloadArtifact(config, sessionId, artifact, outputDir, usedNames) {
1175
799
  const filename = artifactFilename(artifact);
1176
- const taken = usedNames.has(filename) || existsSync3(join4(outputDir, filename));
800
+ const taken = usedNames.has(filename) || existsSync2(join2(outputDir, filename));
1177
801
  const targetName = taken ? `${artifact.artifactId}-${filename}` : filename;
1178
802
  usedNames.add(targetName);
1179
803
  const bytes = await apiFetchBytes(
1180
804
  config,
1181
805
  `/api/sessions/${encodeURIComponent(sessionId)}/artifacts/${encodeURIComponent(artifact.artifactId)}/view?filename=${encodeURIComponent(filename)}`
1182
806
  );
1183
- const targetPath = join4(outputDir, targetName);
807
+ const targetPath = join2(outputDir, targetName);
1184
808
  writeFileSync2(targetPath, bytes);
1185
809
  return targetPath;
1186
810
  }
@@ -1266,7 +890,7 @@ async function whoamiCommand(options, command) {
1266
890
  var MAX_ALL_PAGES = 1e3;
1267
891
  async function fetchPages(label, all, initialCursor, fetchPage) {
1268
892
  const items = [];
1269
- let cursor2 = initialCursor;
893
+ let cursor = initialCursor;
1270
894
  let nextCursor = null;
1271
895
  let pageCount = 0;
1272
896
  do {
@@ -1274,10 +898,10 @@ async function fetchPages(label, all, initialCursor, fetchPage) {
1274
898
  if (all && pageCount > MAX_ALL_PAGES) {
1275
899
  throw new CliError("user", `${label} exceeded ${MAX_ALL_PAGES} pages without reaching the end.`);
1276
900
  }
1277
- const page = await fetchPage(cursor2);
901
+ const page = await fetchPage(cursor);
1278
902
  items.push(...page.items);
1279
903
  nextCursor = page.nextCursor;
1280
- cursor2 = nextCursor ?? void 0;
904
+ cursor = nextCursor ?? void 0;
1281
905
  } while (all && nextCursor);
1282
906
  return { items, nextCursor: all ? null : nextCursor };
1283
907
  }
@@ -1294,10 +918,10 @@ function parseGithubRepoFullName(value) {
1294
918
  }
1295
919
 
1296
920
  // src/git.ts
1297
- import { execFileSync as execFileSync2 } from "child_process";
921
+ import { execFileSync } from "child_process";
1298
922
  function git(args) {
1299
923
  try {
1300
- return execFileSync2("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
924
+ return execFileSync("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
1301
925
  } catch (err) {
1302
926
  throw new CliError("user", `git ${args.join(" ")} failed: ${stringifyError(err)}`);
1303
927
  }
@@ -1353,32 +977,7 @@ var AnthropicModel = {
1353
977
  Sonnet5: "claude-sonnet-5",
1354
978
  Fable5: "claude-fable-5"
1355
979
  };
1356
- var BasetenModel = {
1357
- KimiK27Code: "kimi-k2.7-code"
1358
- };
1359
- var XaiModel = {
1360
- Grok45: "grok-4.5",
1361
- Grok43: "grok-4.3",
1362
- Grok420Reasoning: "grok-4.20-0309-reasoning",
1363
- Grok420NonReasoning: "grok-4.20-0309-non-reasoning",
1364
- Grok420MultiAgent: "grok-4.20-multi-agent-0309",
1365
- GrokBuild01: "grok-build-0.1"
1366
- };
1367
- var CursorModel = {
1368
- Composer25: "composer-2.5"
1369
- };
1370
- var GrokBuildModel = {
1371
- Grok45Byos: "grok-4.5-byos",
1372
- GrokComposer25Fast: "grok-composer-2.5-fast"
1373
- };
1374
- var MODEL_PROVIDERS_SET = /* @__PURE__ */ new Set([
1375
- "openai",
1376
- "anthropic",
1377
- "baseten",
1378
- "xai",
1379
- "cursor",
1380
- "grok"
1381
- ]);
980
+ var MODEL_PROVIDERS_SET = /* @__PURE__ */ new Set(["openai", "anthropic"]);
1382
981
  var BACKEND_DESKTOP_IMAGE_FEEDBACK_CONFIGS = {
1383
982
  [CODEX_AGENT_RUNTIME_BACKEND]: {
1384
983
  backend: CODEX_AGENT_RUNTIME_BACKEND,
@@ -1623,7 +1222,7 @@ var MODEL_REGISTRY = [
1623
1222
  // billing. `anthropic/cost.ts` ANTHROPIC_MESSAGES_MODEL_PRICING owns that
1624
1223
  // and is intentionally ~3x divergent until Phase 5.
1625
1224
  pricing: { inputPerMillion: 5, outputPerMillion: 25, cacheReadPerMillion: 0.5, cacheWritePerMillion: 6.25 },
1626
- sessionStart: { eligible: true, isDefault: true }
1225
+ sessionStart: { eligible: true }
1627
1226
  },
1628
1227
  {
1629
1228
  id: AnthropicModel.Opus5,
@@ -1631,11 +1230,15 @@ var MODEL_REGISTRY = [
1631
1230
  provider: "anthropic",
1632
1231
  backends: [CLAUDE_CODE_AGENT_RUNTIME_BACKEND],
1633
1232
  contextWindow: 1e6,
1233
+ // Verified 2026-07-26 against @anthropic-ai/claude-agent-sdk 0.3.219
1234
+ // EffortLevel: Opus 5 supports low/medium/high/xhigh/max. "none" maps to
1235
+ // omitting SDK Options.effort so Claude keeps its adaptive default.
1634
1236
  reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "high" },
1635
1237
  // Bridge cost estimate; authoritative Anthropic Messages pricing lives in
1636
- // apps/control-plane-worker/src/anthropic/cost.ts.
1238
+ // apps/control-plane-worker/src/anthropic/cost.ts, where Opus 5 carries the
1239
+ // same rates as Opus 4.8, so this default move has no modeled cost delta.
1637
1240
  pricing: { inputPerMillion: 5, outputPerMillion: 25, cacheReadPerMillion: 0.5, cacheWritePerMillion: 6.25 },
1638
- sessionStart: { eligible: true }
1241
+ sessionStart: { eligible: true, isDefault: true }
1639
1242
  },
1640
1243
  {
1641
1244
  id: AnthropicModel.Sonnet46,
@@ -1683,212 +1286,11 @@ var MODEL_REGISTRY = [
1683
1286
  // verified Messages API table.
1684
1287
  pricing: { inputPerMillion: 10, outputPerMillion: 50, cacheReadPerMillion: 1, cacheWritePerMillion: 12.5 },
1685
1288
  sessionStart: { eligible: true }
1686
- },
1687
- {
1688
- id: BasetenModel.KimiK27Code,
1689
- name: "Kimi K2.7 Code",
1690
- provider: "baseten",
1691
- backends: [OPENCODE_AGENT_RUNTIME_BACKEND],
1692
- contextWindow: 262e3,
1693
- // Verified 2026-07-04 against Baseten Model APIs docs, model library, and
1694
- // changelog: wire id `moonshotai/Kimi-K2.7-Code`, 262k served
1695
- // context/output, OpenAI SDK compatible, MIT license, tool calling
1696
- // supported for all Model APIs.
1697
- providerModelId: "moonshotai/Kimi-K2.7-Code",
1698
- desktopImageFeedback: {
1699
- backend: OPENCODE_AGENT_RUNTIME_BACKEND,
1700
- fixture: "known_image_fixture",
1701
- deliveryPath: "native_mcp_tool_result_image",
1702
- verifiedAt: "2026-07-07"
1703
- },
1704
- pricing: { inputPerMillion: 0.95, outputPerMillion: 4, cacheReadPerMillion: 0.16 },
1705
- sessionStart: { eligible: true, isDefault: true }
1706
- },
1707
- // xAI models (all text/chat models from https://docs.x.ai/docs/models,
1708
- // verified 2026-07-15). All run on the opencode backend through the
1709
- // OpenAI-compatible Chat Completions API at api.x.ai. No reasoning config
1710
- // for any of them: xAI documents `reasoning_effort` for grok-4.3 only
1711
- // (https://docs.x.ai/docs/api-reference), AND the opencode backend
1712
- // intentionally passes no reasoning parameters to OSS providers
1713
- // (variantReasoning: intentional in backend-capabilities.ts) — grok-4.3's
1714
- // server-side default (`low`) applies. Pricing records both tiers: xAI
1715
- // bills the whole request at long-context rates once the prompt crosses
1716
- // 200k tokens, expressed via `longContext` (bridge cost estimates only).
1717
- {
1718
- id: XaiModel.Grok45,
1719
- name: "Grok 4.5",
1720
- provider: "xai",
1721
- backends: [OPENCODE_AGENT_RUNTIME_BACKEND],
1722
- contextWindow: 5e5,
1723
- // Verified 2026-07-15 against https://docs.x.ai/docs/pricing: $2/M in,
1724
- // $6/M out, $0.50/M cached input below 200k prompt tokens; $4/$12/$1.00
1725
- // at or above it. 500k context.
1726
- pricing: {
1727
- inputPerMillion: 2,
1728
- outputPerMillion: 6,
1729
- cacheReadPerMillion: 0.5,
1730
- longContext: { thresholdTokens: 2e5, inputPerMillion: 4, outputPerMillion: 12, cacheReadPerMillion: 1 }
1731
- },
1732
- sessionStart: { eligible: true }
1733
- },
1734
- {
1735
- id: XaiModel.Grok43,
1736
- name: "Grok 4.3",
1737
- provider: "xai",
1738
- backends: [OPENCODE_AGENT_RUNTIME_BACKEND],
1739
- contextWindow: 1e6,
1740
- // Verified 2026-07-15 against https://docs.x.ai/docs/pricing: $1.25/M in,
1741
- // $2.50/M out, $0.20/M cached input below 200k prompt tokens;
1742
- // $2.50/$5.00/$0.40 at or above it. 1M context.
1743
- pricing: {
1744
- inputPerMillion: 1.25,
1745
- outputPerMillion: 2.5,
1746
- cacheReadPerMillion: 0.2,
1747
- longContext: { thresholdTokens: 2e5, inputPerMillion: 2.5, outputPerMillion: 5, cacheReadPerMillion: 0.4 }
1748
- },
1749
- sessionStart: { eligible: true }
1750
- },
1751
- {
1752
- id: XaiModel.Grok420Reasoning,
1753
- name: "Grok 4.20 Reasoning",
1754
- provider: "xai",
1755
- backends: [OPENCODE_AGENT_RUNTIME_BACKEND],
1756
- contextWindow: 1e6,
1757
- // xAI encodes reasoning in distinct model ids for the 4.20 line (like
1758
- // Cursor), not a per-request parameter. Verified 2026-07-15 against
1759
- // https://docs.x.ai/docs/pricing: $1.25/M in, $2.50/M out, $0.20/M cached
1760
- // input below 200k prompt tokens; $2.50/$5.00/$0.40 at or above it. 1M context.
1761
- pricing: {
1762
- inputPerMillion: 1.25,
1763
- outputPerMillion: 2.5,
1764
- cacheReadPerMillion: 0.2,
1765
- longContext: { thresholdTokens: 2e5, inputPerMillion: 2.5, outputPerMillion: 5, cacheReadPerMillion: 0.4 }
1766
- },
1767
- sessionStart: { eligible: true }
1768
- },
1769
- {
1770
- id: XaiModel.Grok420NonReasoning,
1771
- name: "Grok 4.20 Non-Reasoning",
1772
- provider: "xai",
1773
- backends: [OPENCODE_AGENT_RUNTIME_BACKEND],
1774
- contextWindow: 1e6,
1775
- // Verified 2026-07-15 against https://docs.x.ai/docs/pricing: $1.25/M in,
1776
- // $2.50/M out, $0.20/M cached input below 200k prompt tokens;
1777
- // $2.50/$5.00/$0.40 at or above it. 1M context.
1778
- pricing: {
1779
- inputPerMillion: 1.25,
1780
- outputPerMillion: 2.5,
1781
- cacheReadPerMillion: 0.2,
1782
- longContext: { thresholdTokens: 2e5, inputPerMillion: 2.5, outputPerMillion: 5, cacheReadPerMillion: 0.4 }
1783
- },
1784
- sessionStart: { eligible: true }
1785
- },
1786
- {
1787
- id: XaiModel.Grok420MultiAgent,
1788
- name: "Grok 4.20 Multi-Agent",
1789
- provider: "xai",
1790
- backends: [OPENCODE_AGENT_RUNTIME_BACKEND],
1791
- contextWindow: 1e6,
1792
- // Verified 2026-07-15 against https://docs.x.ai/docs/pricing: $1.25/M in,
1793
- // $2.50/M out, $0.20/M cached input below 200k prompt tokens;
1794
- // $2.50/$5.00/$0.40 at or above it. 1M context.
1795
- pricing: {
1796
- inputPerMillion: 1.25,
1797
- outputPerMillion: 2.5,
1798
- cacheReadPerMillion: 0.2,
1799
- longContext: { thresholdTokens: 2e5, inputPerMillion: 2.5, outputPerMillion: 5, cacheReadPerMillion: 0.4 }
1800
- },
1801
- sessionStart: { eligible: true },
1802
- // Cannot run a coding-agent session on a standard xAI key (verified
1803
- // 2026-07-15 against api.x.ai with our production key): chat completions
1804
- // rejects the model outright ("Multi Agent requests are not allowed on
1805
- // chat completions") and the Responses API 400s any request carrying
1806
- // client-side tools ("Client-side tools for multi-agent models require
1807
- // beta access") — and the harness always sends tools. Hidden from the
1808
- // picker until xAI grants beta access; CLI/API selection stays open for
1809
- // retesting.
1810
- visibility: "internal_probe"
1811
- },
1812
- {
1813
- id: XaiModel.GrokBuild01,
1814
- name: "Grok Build 0.1",
1815
- provider: "xai",
1816
- backends: [OPENCODE_AGENT_RUNTIME_BACKEND],
1817
- contextWindow: 256e3,
1818
- // Verified 2026-07-15 against https://docs.x.ai/docs/pricing: $1/M in,
1819
- // $2/M out, $0.20/M cached input below 200k prompt tokens; $2/$4/$0.40 at
1820
- // or above it. 256k context.
1821
- pricing: {
1822
- inputPerMillion: 1,
1823
- outputPerMillion: 2,
1824
- cacheReadPerMillion: 0.2,
1825
- longContext: { thresholdTokens: 2e5, inputPerMillion: 2, outputPerMillion: 4, cacheReadPerMillion: 0.4 }
1826
- },
1827
- sessionStart: { eligible: true }
1828
- },
1829
- {
1830
- id: CursorModel.Composer25,
1831
- name: "Composer 2.5",
1832
- provider: "cursor",
1833
- backends: [CURSOR_AGENT_RUNTIME_BACKEND],
1834
- // Cursor bills Composer usage through the user's Cursor account (API key
1835
- // or subscription); Arcanist does not meter it, so no registry pricing is
1836
- // recorded (costTracked: false requires pricing to stay undefined). No
1837
- // reasoning config: Cursor encodes effort in distinct model ids
1838
- // (`--list-models`), not a per-request parameter. Context window is not
1839
- // published for Composer 2.5 (checked cursor.com/docs/models 2026-07-15),
1840
- // so none is recorded.
1841
- costTracked: false,
1842
- sessionStart: { eligible: true, isDefault: true }
1843
- },
1844
- {
1845
- id: GrokBuildModel.Grok45Byos,
1846
- name: "Grok 4.5 (Subscription)",
1847
- provider: "grok",
1848
- backends: [GROK_AGENT_RUNTIME_BACKEND],
1849
- // Wire id `grok-4.5` (the CLI's subscription default) collides with the
1850
- // xai/opencode registry id, so the Arcanist id is suffixed and the wire id
1851
- // rides providerModelId. Billed through the user's Grok subscription via
1852
- // the Grok Build CLI (cli-chat-proxy.grok.com); Arcanist does not meter it
1853
- // (costTracked: false requires pricing to stay undefined). Context window
1854
- // verified 2026-07-15 against https://docs.x.ai/docs/models (grok-4.5,
1855
- // 500k). No reasoning config: the CLI's --reasoning-effort is documented
1856
- // for reasoning models only and grok-4.5 is classified no-reasoning per
1857
- // the xai registry entries above.
1858
- providerModelId: "grok-4.5",
1859
- contextWindow: 5e5,
1860
- costTracked: false,
1861
- sessionStart: { eligible: true, isDefault: true },
1862
- // Internal probe (ARC-1704): hidden from the public model picker except
1863
- // for users with an active Grok subscription (Codex Spark precedent);
1864
- // CLI/API-selectable and session-startable by internal Arcanist
1865
- // businesses only.
1866
- visibility: "internal_probe",
1867
- requiresGrokSubscriptionAuth: true
1868
- },
1869
- {
1870
- id: GrokBuildModel.GrokComposer25Fast,
1871
- name: "Grok Composer 2.5 Fast",
1872
- provider: "grok",
1873
- backends: [GROK_AGENT_RUNTIME_BACKEND],
1874
- // Cursor's Composer 2.5 Fast served through Grok Build (verified in
1875
- // `grok models` 0.2.101 under a live subscription, 2026-07-15). Wire id
1876
- // matches the registry id, so no providerModelId. Context window is not
1877
- // published for Composer variants (cursor.com/docs/models), so none is
1878
- // recorded. Subscription-billed, not metered by Arcanist.
1879
- costTracked: false,
1880
- sessionStart: { eligible: true },
1881
- visibility: "internal_probe",
1882
- requiresGrokSubscriptionAuth: true
1883
1289
  }
1884
1290
  ];
1885
1291
  var MODEL_PROVIDER_NAMES = {
1886
1292
  openai: "OpenAI",
1887
- anthropic: "Anthropic",
1888
- baseten: "Baseten",
1889
- xai: "xAI",
1890
- cursor: "Cursor",
1891
- grok: "Grok Build"
1293
+ anthropic: "Anthropic"
1892
1294
  };
1893
1295
  function buildSessionStartModelIdsByBackend() {
1894
1296
  const byBackend = Object.fromEntries(AGENT_RUNTIME_BACKENDS.map((backend) => [backend, []]));
@@ -1926,10 +1328,7 @@ var VALID_SESSION_START_MODEL_IDS_BY_BACKEND = {
1926
1328
  [CODEX_AGENT_RUNTIME_BACKEND]: new Set(SESSION_START_MODEL_IDS_BY_BACKEND[CODEX_AGENT_RUNTIME_BACKEND]),
1927
1329
  [CLAUDE_CODE_AGENT_RUNTIME_BACKEND]: new Set(
1928
1330
  SESSION_START_MODEL_IDS_BY_BACKEND[CLAUDE_CODE_AGENT_RUNTIME_BACKEND]
1929
- ),
1930
- [OPENCODE_AGENT_RUNTIME_BACKEND]: new Set(SESSION_START_MODEL_IDS_BY_BACKEND[OPENCODE_AGENT_RUNTIME_BACKEND]),
1931
- [CURSOR_AGENT_RUNTIME_BACKEND]: new Set(SESSION_START_MODEL_IDS_BY_BACKEND[CURSOR_AGENT_RUNTIME_BACKEND]),
1932
- [GROK_AGENT_RUNTIME_BACKEND]: new Set(SESSION_START_MODEL_IDS_BY_BACKEND[GROK_AGENT_RUNTIME_BACKEND])
1331
+ )
1933
1332
  };
1934
1333
  var MODEL_CONTEXT_WINDOWS = {
1935
1334
  ...Object.fromEntries(
@@ -2036,6 +1435,12 @@ function buildModelProviderGroups(models2) {
2036
1435
  reasoning: model.reasoning
2037
1436
  });
2038
1437
  }
1438
+ const defaultModelIds = new Set(
1439
+ models2.filter((model) => model.sessionStart?.isDefault === true).map((model) => model.id)
1440
+ );
1441
+ for (const group of groups.values()) {
1442
+ group.models.sort((a, b) => Number(defaultModelIds.has(b.id)) - Number(defaultModelIds.has(a.id)));
1443
+ }
2039
1444
  return [...groups.values()];
2040
1445
  }
2041
1446
  var SESSION_START_MODEL_ID_SET_ANY_BACKEND = new Set(
@@ -2051,27 +1456,11 @@ var CODEX_SUBSCRIPTION_SESSION_START_MODEL_PROVIDER_GROUPS = buildModelProviderG
2051
1456
  (model) => SESSION_START_MODEL_ID_SET_ANY_BACKEND.has(model.id) && (model.visibility !== "internal_probe" || model.requiresCodexSubscriptionAuth === true)
2052
1457
  )
2053
1458
  );
2054
- function buildSubscriptionAwareGroups(include) {
2055
- return buildModelProviderGroups(
2056
- MODEL_REGISTRY.filter(
2057
- (model) => SESSION_START_MODEL_ID_SET_ANY_BACKEND.has(model.id) && (model.visibility !== "internal_probe" || include.codexSubscription && model.requiresCodexSubscriptionAuth === true || include.grokSubscription && model.requiresGrokSubscriptionAuth === true)
2058
- )
2059
- );
2060
- }
2061
- var SESSION_START_MODEL_PROVIDER_GROUPS_BY_SUBSCRIPTIONS = {
2062
- "false:false": PUBLIC_SESSION_START_MODEL_PROVIDER_GROUPS,
2063
- "false:true": buildSubscriptionAwareGroups({ codexSubscription: false, grokSubscription: true }),
2064
- "true:false": CODEX_SUBSCRIPTION_SESSION_START_MODEL_PROVIDER_GROUPS,
2065
- "true:true": buildSubscriptionAwareGroups({ codexSubscription: true, grokSubscription: true })
2066
- };
2067
1459
 
2068
1460
  // src/commands/model-options.ts
2069
1461
  function backendForProviderPrefix(rawModel) {
2070
1462
  const provider = rawModel.match(/^([a-z]+)[/:]/i)?.[1]?.toLowerCase();
2071
1463
  if (provider === "anthropic") return CLAUDE_CODE_AGENT_RUNTIME_BACKEND;
2072
- if (provider === "baseten" || provider === "xai") return OPENCODE_AGENT_RUNTIME_BACKEND;
2073
- if (provider === "cursor") return CURSOR_AGENT_RUNTIME_BACKEND;
2074
- if (provider === "grok") return GROK_AGENT_RUNTIME_BACKEND;
2075
1464
  return void 0;
2076
1465
  }
2077
1466
  function resolveModelAndBackend(options) {
@@ -2113,7 +1502,7 @@ var AUTOMATION_ERROR_HINTS = {
2113
1502
  slack_channel_unavailable: "Check the Slack channel ID and confirm the bot can see the channel.",
2114
1503
  slack_bot_not_in_channel: "Invite the Arcanist bot to the channel, then retry.",
2115
1504
  rule_cap_reached: "Delete or disable an existing automation before creating another one. The cap is 20 enabled rules.",
2116
- model_not_available: "That model's backend (Claude/opencode) is limited to Arcanist team businesses; use the codex default or a codex model.",
1505
+ model_not_available: "That model's backend (Claude) is limited to Arcanist team businesses; use the codex default or a codex model.",
2117
1506
  repo_skills_unavailable: "Arcanist could not verify the repo's skills right now. Retry once GitHub skill discovery is healthy.",
2118
1507
  unknown_skill: "That leading slash skill does not exist in the selected repository."
2119
1508
  };
@@ -2161,10 +1550,10 @@ async function listAutomationsCommand(options, command) {
2161
1550
  "automations list --all",
2162
1551
  options.all === true,
2163
1552
  options.cursor,
2164
- async (cursor2) => {
1553
+ async (cursor) => {
2165
1554
  const query = new URLSearchParams();
2166
1555
  if (options.limit) query.set("limit", options.limit);
2167
- if (cursor2) query.set("cursor", cursor2);
1556
+ if (cursor) query.set("cursor", cursor);
2168
1557
  const payload = await automationApiFetch(
2169
1558
  config,
2170
1559
  `/api/automation/schedules${query.size ? `?${query.toString()}` : ""}`
@@ -2245,8 +1634,73 @@ function formatTime(value) {
2245
1634
  }
2246
1635
 
2247
1636
  // src/commands/codex.ts
2248
- import { readFile as readFile2 } from "fs/promises";
2249
- import { join as join5 } from "path";
1637
+ import { readFile } from "fs/promises";
1638
+ import { join as join4 } from "path";
1639
+
1640
+ // src/vendor-login.ts
1641
+ import { spawn } from "child_process";
1642
+ import { rmSync } from "fs";
1643
+ import { mkdtemp, rm } from "fs/promises";
1644
+ import { tmpdir } from "os";
1645
+ import { join as join3 } from "path";
1646
+ function runVendorLoginProcess(spec) {
1647
+ return new Promise((resolve3, reject) => {
1648
+ const child = spawn(spec.binPath, spec.args, {
1649
+ stdio: "inherit",
1650
+ env: { ...process.env, ...spec.env }
1651
+ });
1652
+ child.on("error", (err) => {
1653
+ if (err.code === "ENOENT") {
1654
+ reject(
1655
+ new CliError("user", `Could not find the \`${spec.binPath}\` executable.`, {
1656
+ hint: spec.missingBinaryHint
1657
+ })
1658
+ );
1659
+ return;
1660
+ }
1661
+ reject(new CliError("user", `Failed to launch \`${spec.binPath} ${spec.args.join(" ")}\`: ${err.message}`));
1662
+ });
1663
+ child.on("close", (code) => {
1664
+ if (code === 0) {
1665
+ resolve3();
1666
+ return;
1667
+ }
1668
+ reject(
1669
+ new CliError("user", `\`${spec.binPath} ${spec.args.join(" ")}\` exited with code ${code ?? "unknown"}.`, {
1670
+ hint: `Complete the ${spec.displayName} login, then re-run \`${spec.retryCommand}\`.`
1671
+ })
1672
+ );
1673
+ });
1674
+ });
1675
+ }
1676
+ async function withIsolatedLoginDir(prefix, fn) {
1677
+ let tempDir;
1678
+ const handleSigint = () => {
1679
+ if (tempDir) {
1680
+ try {
1681
+ rmSync(tempDir, { recursive: true, force: true });
1682
+ } catch {
1683
+ }
1684
+ }
1685
+ process.exit(EXIT_CODE_INTERRUPTED);
1686
+ };
1687
+ process.on("SIGINT", handleSigint);
1688
+ try {
1689
+ tempDir = await mkdtemp(join3(tmpdir(), prefix));
1690
+ return await fn(tempDir);
1691
+ } finally {
1692
+ try {
1693
+ if (tempDir) {
1694
+ await rm(tempDir, { recursive: true, force: true }).catch(() => {
1695
+ });
1696
+ }
1697
+ } finally {
1698
+ process.off("SIGINT", handleSigint);
1699
+ }
1700
+ }
1701
+ }
1702
+
1703
+ // src/commands/codex.ts
2250
1704
  var CODEX_SUBSCRIPTION_PATH = "/api/settings/codex-subscription";
2251
1705
  var CODEX_SUBSCRIPTION_AUTH_JSON_PATH = "/api/settings/codex-subscription/auth-json";
2252
1706
  var CODEX_SUBSCRIPTION_ENABLED_PATH = "/api/settings/codex-subscription/enabled";
@@ -2284,7 +1738,7 @@ async function codexLoginCommand(options, command) {
2284
1738
  await runCodexDeviceLogin(codexPath, codexHome);
2285
1739
  let raw;
2286
1740
  try {
2287
- raw = await readFile2(join5(codexHome, "auth.json"), "utf8");
1741
+ raw = await readFile(join4(codexHome, "auth.json"), "utf8");
2288
1742
  } catch {
2289
1743
  throw new CliError("user", "Codex login completed but no auth.json was written.", {
2290
1744
  hint: "Verify `codex login --device-auth` succeeds on its own, then re-run `arcanist codex login`."
@@ -2357,7 +1811,7 @@ async function codexLogoutCommand(options, command) {
2357
1811
  }
2358
1812
 
2359
1813
  // src/uploads.ts
2360
- import { readFile as readFile3 } from "fs/promises";
1814
+ import { readFile as readFile2 } from "fs/promises";
2361
1815
  import { basename as basename2, extname } from "path";
2362
1816
 
2363
1817
  // ../../shared/constants/uploads.ts
@@ -2460,7 +1914,7 @@ async function resolveUploadedFileOptions(files) {
2460
1914
  paths.map(async (path) => {
2461
1915
  const name = basename2(path);
2462
1916
  try {
2463
- return { name, content: await readFile3(path, "utf8") };
1917
+ return { name, content: await readFile2(path, "utf8") };
2464
1918
  } catch (err) {
2465
1919
  const message = stringifyError(err);
2466
1920
  throw new CliError("user", `Failed to read uploaded file ${path}: ${message}`);
@@ -4863,7 +4317,7 @@ async function reviewCommand(prUrl, options = {}, command) {
4863
4317
  }
4864
4318
 
4865
4319
  // src/commands/sandbox.ts
4866
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
4320
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
4867
4321
  import { dirname as dirname2 } from "path";
4868
4322
 
4869
4323
  // ../../shared/sandbox-layer/parser.ts
@@ -5410,7 +4864,7 @@ function assertCleanAndPushed() {
5410
4864
  throw new CliError("user", "Current commit is not pushed; push before building the sandbox layer.");
5411
4865
  }
5412
4866
  function assertManifestExists(path) {
5413
- if (!existsSync4(path)) throw new CliError("user", `Missing sandbox manifest: ${path}`);
4867
+ if (!existsSync3(path)) throw new CliError("user", `Missing sandbox manifest: ${path}`);
5414
4868
  }
5415
4869
  function repoPath(repo) {
5416
4870
  return `${repo.owner}/${repo.repo}`;
@@ -5627,9 +5081,9 @@ function sourceBody(sourceRepo, manifestPath) {
5627
5081
  }
5628
5082
  async function sandboxInitCommand(options = {}, command) {
5629
5083
  const runtime = getRuntimeOptions(command, options);
5630
- if (existsSync4(DEFAULT_MANIFEST_PATH)) throw new CliError("conflict", `${DEFAULT_MANIFEST_PATH} already exists`);
5084
+ if (existsSync3(DEFAULT_MANIFEST_PATH)) throw new CliError("conflict", `${DEFAULT_MANIFEST_PATH} already exists`);
5631
5085
  const layerPath = ".arcanist/sandbox.layer.Dockerfile";
5632
- if (existsSync4(layerPath)) throw new CliError("conflict", `${layerPath} already exists`);
5086
+ if (existsSync3(layerPath)) throw new CliError("conflict", `${layerPath} already exists`);
5633
5087
  mkdirSync3(dirname2(DEFAULT_MANIFEST_PATH), { recursive: true });
5634
5088
  writeFileSync3(
5635
5089
  DEFAULT_MANIFEST_PATH,
@@ -5939,14 +5393,14 @@ async function listSessionsCommand(options, command) {
5939
5393
  "sessions list --all",
5940
5394
  options.all === true,
5941
5395
  options.cursor,
5942
- async (cursor2) => {
5396
+ async (cursor) => {
5943
5397
  const query = new URLSearchParams();
5944
5398
  if (options.status) query.set("status", options.status);
5945
5399
  if (options.scope) query.set("scope", options.scope);
5946
5400
  if (options.search) query.set("q", options.search);
5947
5401
  if (options.repo) query.set("repo", options.repo);
5948
5402
  if (options.limit) query.set("limit", options.limit);
5949
- if (cursor2) query.set("cursor", cursor2);
5403
+ if (cursor) query.set("cursor", cursor);
5950
5404
  const payload2 = await apiFetch(
5951
5405
  config,
5952
5406
  `/api/sessions${query.size ? `?${query.toString()}` : ""}`
@@ -6173,10 +5627,10 @@ async function listTokensCommand(options, command) {
6173
5627
  "tokens list --all",
6174
5628
  options.all === true,
6175
5629
  options.cursor,
6176
- async (cursor2) => {
5630
+ async (cursor) => {
6177
5631
  const query = new URLSearchParams();
6178
5632
  if (options.limit) query.set("limit", options.limit);
6179
- if (cursor2) query.set("cursor", cursor2);
5633
+ if (cursor) query.set("cursor", cursor);
6180
5634
  const payload2 = await apiFetch(
6181
5635
  config,
6182
5636
  `/api/cli-tokens${query.size ? `?${query.toString()}` : ""}`
@@ -6416,56 +5870,6 @@ Examples:
6416
5870
  codex.command("use <state>").description("Turn using your saved Codex subscription auth for OpenAI sessions on or off (state: on|off)").action((state, options, command) => codexUseCommand(state, options, command));
6417
5871
  codex.command("status").description("Show Codex subscription availability and whether auth is saved").action((options, command) => codexStatusCommand(options, command));
6418
5872
  codex.command("logout").description("Deactivate the selector and remove the stored Codex subscription auth for your user").action((options, command) => codexLogoutCommand(options, command));
6419
- var grok = program.command("grok").description("Grok (xAI) subscription (bring-your-own-subscription) commands");
6420
- grok.command("login").description("Authenticate a Grok (xAI) subscription and store it for future Grok BYOS sessions").option("--grok-path <path>", "Path to the grok executable (default: grok on PATH, or ARCANIST_GROK_BIN)").addHelpText(
6421
- "after",
6422
- `
6423
- Runs the Grok CLI device-authorization login locally under a temporary HOME, then uploads the
6424
- resulting auth.json to Arcanist (encrypted, per user) and activates the selector. Your workspace
6425
- must have Grok subscription auth enabled. The credential is never written to your default ~/.grok.
6426
- While the selector is on, Grok Build sessions run on your subscription auth.
6427
- If the grok CLI is not installed, you will be offered its official install script ([y/N] prompt,
6428
- interactive terminals only).
6429
-
6430
- Examples:
6431
- arcanist grok login
6432
- arcanist grok login --grok-path /usr/local/bin/grok
6433
- `
6434
- ).action(
6435
- (options, command) => agentSubscriptionLoginCommand(GROK_SUBSCRIPTION_CLI_CONFIG, { ...options, binPath: options.grokPath }, command)
6436
- );
6437
- grok.command("use <state>").description("Turn using your saved Grok subscription auth on or off (state: on|off)").action(
6438
- (state, options, command) => agentSubscriptionUseCommand(GROK_SUBSCRIPTION_CLI_CONFIG, state, options, command)
6439
- );
6440
- grok.command("status").description("Show whether your workspace is eligible and whether a Grok subscription auth is saved").action((options, command) => agentSubscriptionStatusCommand(GROK_SUBSCRIPTION_CLI_CONFIG, options, command));
6441
- grok.command("logout").description("Deactivate the selector and remove the stored Grok subscription auth for your user").action((options, command) => agentSubscriptionLogoutCommand(GROK_SUBSCRIPTION_CLI_CONFIG, options, command));
6442
- var cursor = program.command("cursor").description("Cursor subscription (bring-your-own-subscription) commands");
6443
- cursor.command("login").description("Authenticate a Cursor subscription and store it for future Cursor BYOS sessions").option(
6444
- "--cursor-path <path>",
6445
- "Path to the cursor-agent executable (default: cursor-agent on PATH, or ARCANIST_CURSOR_AGENT_BIN)"
6446
- ).addHelpText(
6447
- "after",
6448
- `
6449
- Runs the Cursor CLI login locally under a temporary HOME/CURSOR_CONFIG_DIR, then uploads the
6450
- captured auth credential to Arcanist (encrypted, per user) and activates the selector. Your
6451
- workspace must have Cursor subscription auth enabled. Cursor's login-token location is not
6452
- formally documented; if capture fails, use a Cursor API key in Settings instead. While the
6453
- selector is on, Cursor sessions run on your subscription auth instead of a Cursor API key.
6454
- If the cursor-agent CLI is not installed, you will be offered its official install script
6455
- ([y/N] prompt, interactive terminals only).
6456
-
6457
- Examples:
6458
- arcanist cursor login
6459
- arcanist cursor login --cursor-path /usr/local/bin/cursor-agent
6460
- `
6461
- ).action(
6462
- (options, command) => agentSubscriptionLoginCommand(CURSOR_SUBSCRIPTION_CLI_CONFIG, { ...options, binPath: options.cursorPath }, command)
6463
- );
6464
- cursor.command("use <state>").description("Turn using your saved Cursor subscription auth on or off (state: on|off)").action(
6465
- (state, options, command) => agentSubscriptionUseCommand(CURSOR_SUBSCRIPTION_CLI_CONFIG, state, options, command)
6466
- );
6467
- 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));
6468
- 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));
6469
5873
  var sessions = program.command("sessions").description("Session commands");
6470
5874
  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(
6471
5875
  "after",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.230",
3
+ "version": "0.1.232",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {