@tryarcanist/cli 0.1.223 → 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.
Files changed (2) hide show
  1. package/dist/index.js +164 -185
  2. 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, rmSync } from "fs";
10
- import { mkdtemp, readFile, rm } from "fs/promises";
11
- import { homedir as homedir2, tmpdir } from "os";
12
- import { join as join2 } from "path";
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: [join2(".grok", "auth.json")],
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
- join2(home, ".grok", "bin", "grok"),
667
- join2(home, ".local", "bin", "grok"),
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: join2(tempHome, ".cursor"),
684
- XDG_CONFIG_HOME: join2(tempHome, ".config")
748
+ CURSOR_CONFIG_DIR: join3(tempHome, ".cursor"),
749
+ XDG_CONFIG_HOME: join3(tempHome, ".config")
685
750
  }),
686
751
  authJsonCandidates: [
687
- join2(".cursor", "auth.json"),
688
- join2(".cursor", "cli-config.json"),
689
- join2(".config", "cursor", "auth.json"),
690
- join2(".config", "cursor", "cli-config.json")
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) => [join2(home, ".local", "bin", "cursor-agent"), join2(home, ".local", "bin", "agent")],
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 = spawn("bash", ["-c", command], { stdio: ["inherit", 2, "inherit"] });
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 new Promise((resolve2, reject) => {
783
- const child = spawn(binPath, config.loginArgs, {
784
- stdio: "inherit",
785
- env: tempHome ? { ...process.env, HOME: tempHome, ...config.loginEnv(tempHome) } : process.env
786
- });
787
- child.on("error", (err) => {
788
- if (err.code === "ENOENT") {
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 = join2(tempHome, candidate);
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
- let tempHome;
869
- const handleSigint = () => {
870
- if (tempHome) {
871
- try {
872
- rmSync(tempHome, { recursive: true, force: true });
873
- } catch {
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
- authJson = await readLoginAuthJson(config, tempHome);
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
- const state = await apiFetch(
891
- apiConfig,
892
- `${subscriptionBasePath(config.harness)}/auth-json`,
893
- {
894
- method: "PUT",
895
- body: JSON.stringify({ authJson })
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
- let activated = false;
899
- let activationError;
900
- try {
901
- await setSubscriptionEnabled(apiConfig, config.harness, true);
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
- emit(command, options, { ...state, enabled: activated }, (payload) => {
907
- console.log(
908
- activated ? `${config.displayName} subscription auth saved and activated.` : `${config.displayName} subscription auth saved.`
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
- `${config.displayName} sessions will use your subscription auth while the selector is on; run \`arcanist ${config.harness} use off\` to switch back.`
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();
@@ -2108,11 +2130,8 @@ function formatTime(value) {
2108
2130
  }
2109
2131
 
2110
2132
  // src/commands/codex.ts
2111
- import { spawn as spawn2 } from "child_process";
2112
- import { rmSync as rmSync2 } from "fs";
2113
- import { mkdtemp as mkdtemp2, readFile as readFile2, rm as rm2 } from "fs/promises";
2114
- import { tmpdir as tmpdir2 } from "os";
2115
- import { join as join3 } from "path";
2133
+ import { readFile as readFile2 } from "fs/promises";
2134
+ import { join as join4 } from "path";
2116
2135
  var CODEX_SUBSCRIPTION_PATH = "/api/settings/codex-subscription";
2117
2136
  var CODEX_SUBSCRIPTION_AUTH_JSON_PATH = "/api/settings/codex-subscription/auth-json";
2118
2137
  var CODEX_SUBSCRIPTION_ENABLED_PATH = "/api/settings/codex-subscription/enabled";
@@ -2128,33 +2147,13 @@ function resolveCodexPath(optionPath) {
2128
2147
  return optionPath?.trim() || process.env.ARCANIST_CODEX_BIN?.trim() || "codex";
2129
2148
  }
2130
2149
  function runCodexDeviceLogin(codexPath, codexHome) {
2131
- return new Promise((resolve2, reject) => {
2132
- const child = spawn2(codexPath, ["login", "--device-auth"], {
2133
- stdio: "inherit",
2134
- env: { ...process.env, CODEX_HOME: codexHome }
2135
- });
2136
- child.on("error", (err) => {
2137
- if (err.code === "ENOENT") {
2138
- reject(
2139
- new CliError("user", `Could not find the \`${codexPath}\` executable.`, {
2140
- hint: "Install the Codex CLI, or point at it with --codex-path <path> or ARCANIST_CODEX_BIN."
2141
- })
2142
- );
2143
- return;
2144
- }
2145
- reject(new CliError("user", `Failed to launch \`${codexPath} login --device-auth\`: ${err.message}`));
2146
- });
2147
- child.on("close", (code) => {
2148
- if (code === 0) {
2149
- resolve2();
2150
- return;
2151
- }
2152
- reject(
2153
- new CliError("user", `\`${codexPath} login --device-auth\` exited with code ${code ?? "unknown"}.`, {
2154
- hint: "Complete the device approval in your browser, then re-run `arcanist codex login`."
2155
- })
2156
- );
2157
- });
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"
2158
2157
  });
2159
2158
  }
2160
2159
  async function setCodexSubscriptionEnabled(config, enabled) {
@@ -2166,64 +2165,44 @@ async function setCodexSubscriptionEnabled(config, enabled) {
2166
2165
  async function codexLoginCommand(options, command) {
2167
2166
  const { config } = resolveBusinessContext(command, options);
2168
2167
  const codexPath = resolveCodexPath(options.codexPath);
2169
- let codexHome;
2170
- const handleSigint = () => {
2171
- if (codexHome) {
2172
- try {
2173
- rmSync2(codexHome, { recursive: true, force: true });
2174
- } catch {
2175
- }
2176
- }
2177
- process.exit(EXIT_CODE_INTERRUPTED);
2178
- };
2179
- process.on("SIGINT", handleSigint);
2180
- try {
2181
- codexHome = await mkdtemp2(join3(tmpdir2(), "arcanist-codex-"));
2168
+ const authJson = await withIsolatedLoginDir("arcanist-codex-", async (codexHome) => {
2182
2169
  await runCodexDeviceLogin(codexPath, codexHome);
2183
- let authJson;
2170
+ let raw;
2184
2171
  try {
2185
- authJson = await readFile2(join3(codexHome, "auth.json"), "utf8");
2172
+ raw = await readFile2(join4(codexHome, "auth.json"), "utf8");
2186
2173
  } catch {
2187
2174
  throw new CliError("user", "Codex login completed but no auth.json was written.", {
2188
2175
  hint: "Verify `codex login --device-auth` succeeds on its own, then re-run `arcanist codex login`."
2189
2176
  });
2190
2177
  }
2191
- if (!authJson.trim()) {
2178
+ if (!raw.trim()) {
2192
2179
  throw new CliError("user", "Codex login produced an empty auth.json.");
2193
2180
  }
2194
- const state = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
2195
- method: "PUT",
2196
- body: JSON.stringify({ authJson })
2197
- });
2198
- let activated = false;
2199
- let activationError;
2200
- try {
2201
- await setCodexSubscriptionEnabled(config, true);
2202
- activated = true;
2203
- } catch (err) {
2204
- activationError = err instanceof Error ? err.message : String(err);
2205
- }
2206
- emit(command, options, { ...state, useCodexSubscription: activated }, (payload) => {
2207
- console.log(
2208
- activated ? "Codex subscription auth saved and activated for OpenAI sessions." : "Codex subscription auth saved."
2209
- );
2210
- const status = describeCredentialStatus(payload);
2211
- if (status) console.log(`Status: ${status}`);
2212
- if (!activated) {
2213
- console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
2214
- console.log("Run `arcanist codex use on` to start using it.");
2215
- }
2216
- });
2217
- } finally {
2218
- try {
2219
- if (codexHome) {
2220
- await rm2(codexHome, { recursive: true, force: true }).catch(() => {
2221
- });
2222
- }
2223
- } finally {
2224
- process.off("SIGINT", handleSigint);
2225
- }
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);
2226
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
+ });
2227
2206
  }
2228
2207
  async function codexUseCommand(state, options, command) {
2229
2208
  const normalized = state.trim().toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.223",
3
+ "version": "0.1.224",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {