@tryarcanist/cli 0.1.204 → 0.1.205
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 +389 -32
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|
import { createRequire as createRequire2 } from "module";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
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 { tmpdir } from "os";
|
|
12
|
+
import { join as join2 } from "path";
|
|
13
|
+
|
|
7
14
|
// src/api.ts
|
|
8
15
|
import { createRequire } from "module";
|
|
9
16
|
|
|
@@ -604,6 +611,259 @@ function isControlCharacter(char) {
|
|
|
604
611
|
return code < ASCII_FIRST_PRINTABLE || code === ASCII_DELETE;
|
|
605
612
|
}
|
|
606
613
|
|
|
614
|
+
// src/commands/agent-subscription.ts
|
|
615
|
+
var GROK_SUBSCRIPTION_CLI_CONFIG = {
|
|
616
|
+
harness: "grok",
|
|
617
|
+
displayName: "Grok",
|
|
618
|
+
defaultBin: "grok",
|
|
619
|
+
binEnvVar: "ARCANIST_GROK_BIN",
|
|
620
|
+
binOption: "grok-path",
|
|
621
|
+
// RFC 8628 device flow: prints a verification URL + code, usable from a
|
|
622
|
+
// terminal without a local browser.
|
|
623
|
+
loginArgs: ["login", "--device-auth"],
|
|
624
|
+
loginEnv: () => ({}),
|
|
625
|
+
authJsonCandidates: [join2(".grok", "auth.json")],
|
|
626
|
+
// Grok Build's auth.json is issuer-keyed (observed 2026-07-15 against the
|
|
627
|
+
// installed CLI's embedded docs): {"https://accounts.x.ai/sign-in": {"key":
|
|
628
|
+
// "…"}} — so the credential fields live one level down and the token field
|
|
629
|
+
// is named `key`. Token detection scans nested objects.
|
|
630
|
+
tokenFields: ["access_token", "refresh_token", "key"],
|
|
631
|
+
installHint: "Install the Grok CLI (https://x.ai/cli), or point at it with --grok-path <path> or ARCANIST_GROK_BIN."
|
|
632
|
+
};
|
|
633
|
+
var CURSOR_SUBSCRIPTION_CLI_CONFIG = {
|
|
634
|
+
harness: "cursor",
|
|
635
|
+
displayName: "Cursor",
|
|
636
|
+
defaultBin: "cursor-agent",
|
|
637
|
+
binEnvVar: "ARCANIST_CURSOR_AGENT_BIN",
|
|
638
|
+
binOption: "cursor-path",
|
|
639
|
+
loginArgs: ["login"],
|
|
640
|
+
// Cursor's CLI stores login state under its config dir; the location is not
|
|
641
|
+
// formally documented, so pin every documented override at the throwaway
|
|
642
|
+
// HOME and search the known spots afterwards.
|
|
643
|
+
loginEnv: (tempHome) => ({
|
|
644
|
+
CURSOR_CONFIG_DIR: join2(tempHome, ".cursor"),
|
|
645
|
+
XDG_CONFIG_HOME: join2(tempHome, ".config")
|
|
646
|
+
}),
|
|
647
|
+
authJsonCandidates: [
|
|
648
|
+
join2(".cursor", "auth.json"),
|
|
649
|
+
join2(".cursor", "cli-config.json"),
|
|
650
|
+
join2(".config", "cursor", "auth.json"),
|
|
651
|
+
join2(".config", "cursor", "cli-config.json")
|
|
652
|
+
],
|
|
653
|
+
tokenFields: ["accessToken", "access_token", "refreshToken", "refresh_token", "token"],
|
|
654
|
+
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.",
|
|
655
|
+
// Entry names observed 2026-07-15 after `cursor-agent login` on macOS
|
|
656
|
+
// (login keychain, account "cursor-user").
|
|
657
|
+
darwinKeychain: {
|
|
658
|
+
entries: [
|
|
659
|
+
{ service: "cursor-access-token", field: "accessToken", required: true },
|
|
660
|
+
{ service: "cursor-refresh-token", field: "refreshToken", required: false }
|
|
661
|
+
]
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
function subscriptionBasePath(harness) {
|
|
665
|
+
return `/api/settings/agent-subscriptions/${harness}`;
|
|
666
|
+
}
|
|
667
|
+
function resolveVendorBin(config, optionPath) {
|
|
668
|
+
return optionPath?.trim() || process.env[config.binEnvVar]?.trim() || config.defaultBin;
|
|
669
|
+
}
|
|
670
|
+
function runVendorLogin(config, binPath, tempHome) {
|
|
671
|
+
return new Promise((resolve2, reject) => {
|
|
672
|
+
const child = spawn(binPath, config.loginArgs, {
|
|
673
|
+
stdio: "inherit",
|
|
674
|
+
env: tempHome ? { ...process.env, HOME: tempHome, ...config.loginEnv(tempHome) } : process.env
|
|
675
|
+
});
|
|
676
|
+
child.on("error", (err) => {
|
|
677
|
+
if (err.code === "ENOENT") {
|
|
678
|
+
reject(new CliError("user", `Could not find the \`${binPath}\` executable.`, { hint: config.installHint }));
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
reject(new CliError("user", `Failed to launch \`${binPath} ${config.loginArgs.join(" ")}\`: ${err.message}`));
|
|
682
|
+
});
|
|
683
|
+
child.on("close", (code) => {
|
|
684
|
+
if (code === 0) {
|
|
685
|
+
resolve2();
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
reject(
|
|
689
|
+
new CliError("user", `\`${binPath} ${config.loginArgs.join(" ")}\` exited with code ${code ?? "unknown"}.`, {
|
|
690
|
+
hint: `Complete the ${config.displayName} login, then re-run \`arcanist ${config.harness} login\`.`
|
|
691
|
+
})
|
|
692
|
+
);
|
|
693
|
+
});
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
function hasTokenField(value, tokenFields, depth = 0) {
|
|
697
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || depth > 2) return false;
|
|
698
|
+
const record = value;
|
|
699
|
+
if (tokenFields.some((field) => typeof record[field] === "string" && record[field].trim().length > 0)) {
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
return Object.values(record).some((nested) => hasTokenField(nested, tokenFields, depth + 1));
|
|
703
|
+
}
|
|
704
|
+
function readDarwinKeychainAuthJson(config) {
|
|
705
|
+
const record = {};
|
|
706
|
+
for (const entry of config.darwinKeychain?.entries ?? []) {
|
|
707
|
+
let value = "";
|
|
708
|
+
try {
|
|
709
|
+
value = execFileSync("security", ["find-generic-password", "-s", entry.service, "-w"], {
|
|
710
|
+
encoding: "utf8"
|
|
711
|
+
}).trim();
|
|
712
|
+
} catch {
|
|
713
|
+
}
|
|
714
|
+
if (value) {
|
|
715
|
+
record[entry.field] = value;
|
|
716
|
+
} else if (entry.required) {
|
|
717
|
+
throw new CliError(
|
|
718
|
+
"user",
|
|
719
|
+
`${config.displayName} login completed but the '${entry.service}' Keychain item could not be read.`,
|
|
720
|
+
{
|
|
721
|
+
hint: `Approve the macOS Keychain access prompt if one appeared, then re-run \`arcanist ${config.harness} login\`.`
|
|
722
|
+
}
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return JSON.stringify(record);
|
|
727
|
+
}
|
|
728
|
+
async function readLoginAuthJson(config, tempHome) {
|
|
729
|
+
for (const candidate of config.authJsonCandidates) {
|
|
730
|
+
const path = join2(tempHome, candidate);
|
|
731
|
+
if (!existsSync2(path)) continue;
|
|
732
|
+
let raw;
|
|
733
|
+
try {
|
|
734
|
+
raw = await readFile(path, "utf8");
|
|
735
|
+
} catch {
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
try {
|
|
739
|
+
if (hasTokenField(JSON.parse(raw), config.tokenFields)) return raw;
|
|
740
|
+
} catch {
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
throw new CliError("user", `${config.displayName} login completed but no auth credential file was found.`, {
|
|
744
|
+
hint: `Verify \`${config.defaultBin} ${config.loginArgs.join(" ")}\` succeeds on its own, then re-run \`arcanist ${config.harness} login\`.`
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
async function setSubscriptionEnabled(apiConfig, harness, enabled) {
|
|
748
|
+
return apiFetch(apiConfig, `${subscriptionBasePath(harness)}/enabled`, {
|
|
749
|
+
method: "PUT",
|
|
750
|
+
body: JSON.stringify({ enabled })
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
async function agentSubscriptionLoginCommand(config, options, command) {
|
|
754
|
+
const { config: apiConfig } = resolveBusinessContext(command, options);
|
|
755
|
+
const binPath = resolveVendorBin(config, options.binPath);
|
|
756
|
+
let tempHome;
|
|
757
|
+
const handleSigint = () => {
|
|
758
|
+
if (tempHome) {
|
|
759
|
+
try {
|
|
760
|
+
rmSync(tempHome, { recursive: true, force: true });
|
|
761
|
+
} catch {
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
process.exit(EXIT_CODE_INTERRUPTED);
|
|
765
|
+
};
|
|
766
|
+
process.on("SIGINT", handleSigint);
|
|
767
|
+
try {
|
|
768
|
+
const captureViaKeychain = process.platform === "darwin" && config.darwinKeychain !== void 0;
|
|
769
|
+
let authJson;
|
|
770
|
+
if (captureViaKeychain) {
|
|
771
|
+
await runVendorLogin(config, binPath, null);
|
|
772
|
+
authJson = readDarwinKeychainAuthJson(config);
|
|
773
|
+
} else {
|
|
774
|
+
tempHome = await mkdtemp(join2(tmpdir(), `arcanist-${config.harness}-`));
|
|
775
|
+
await runVendorLogin(config, binPath, tempHome);
|
|
776
|
+
authJson = await readLoginAuthJson(config, tempHome);
|
|
777
|
+
}
|
|
778
|
+
const state = await apiFetch(
|
|
779
|
+
apiConfig,
|
|
780
|
+
`${subscriptionBasePath(config.harness)}/auth-json`,
|
|
781
|
+
{
|
|
782
|
+
method: "PUT",
|
|
783
|
+
body: JSON.stringify({ authJson })
|
|
784
|
+
}
|
|
785
|
+
);
|
|
786
|
+
let activated = false;
|
|
787
|
+
let activationError;
|
|
788
|
+
try {
|
|
789
|
+
await setSubscriptionEnabled(apiConfig, config.harness, true);
|
|
790
|
+
activated = true;
|
|
791
|
+
} catch (err) {
|
|
792
|
+
activationError = err instanceof Error ? err.message : String(err);
|
|
793
|
+
}
|
|
794
|
+
emit(command, options, { ...state, enabled: activated }, (payload) => {
|
|
795
|
+
console.log(
|
|
796
|
+
activated ? `${config.displayName} subscription auth saved and activated.` : `${config.displayName} subscription auth saved.`
|
|
797
|
+
);
|
|
798
|
+
if (payload.lastValidationStatus) console.log(`Status: ${payload.lastValidationStatus}`);
|
|
799
|
+
if (!activated) {
|
|
800
|
+
console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
|
|
801
|
+
console.log(`Run \`arcanist ${config.harness} use on\` to start using it.`);
|
|
802
|
+
}
|
|
803
|
+
console.log(
|
|
804
|
+
`Note: ${config.displayName} BYOS session execution is not live yet; the credential is stored for when it ships.`
|
|
805
|
+
);
|
|
806
|
+
if (captureViaKeychain) {
|
|
807
|
+
console.log(
|
|
808
|
+
`Your local ${config.defaultBin} login also remains active (Keychain-backed); run \`${config.defaultBin} logout\` to remove it.`
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
} finally {
|
|
813
|
+
try {
|
|
814
|
+
if (tempHome) {
|
|
815
|
+
await rm(tempHome, { recursive: true, force: true }).catch(() => {
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
} finally {
|
|
819
|
+
process.off("SIGINT", handleSigint);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
async function agentSubscriptionUseCommand(config, state, options, command) {
|
|
824
|
+
const normalized = state.trim().toLowerCase();
|
|
825
|
+
if (normalized !== "on" && normalized !== "off") {
|
|
826
|
+
throw new CliError("user", `Usage: arcanist ${config.harness} use <on|off>`);
|
|
827
|
+
}
|
|
828
|
+
const enabled = normalized === "on";
|
|
829
|
+
const { config: apiConfig } = resolveBusinessContext(command, options);
|
|
830
|
+
const result = await setSubscriptionEnabled(apiConfig, config.harness, enabled);
|
|
831
|
+
emit(
|
|
832
|
+
command,
|
|
833
|
+
options,
|
|
834
|
+
result,
|
|
835
|
+
() => console.log(
|
|
836
|
+
result.enabled ? `${config.displayName} subscription auth is now selected for ${config.displayName} sessions.` : `${config.displayName} subscription auth is no longer selected.`
|
|
837
|
+
)
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
async function agentSubscriptionStatusCommand(config, options, command) {
|
|
841
|
+
const { config: apiConfig } = resolveBusinessContext(command, options);
|
|
842
|
+
const payload = await apiFetch(apiConfig, subscriptionBasePath(config.harness));
|
|
843
|
+
emit(command, options, payload, (state) => {
|
|
844
|
+
console.log(`Eligible: ${state.eligible ? "yes" : "no"}`);
|
|
845
|
+
console.log(`Auth saved: ${state.credential.isSet ? "yes" : "no"}`);
|
|
846
|
+
console.log(`Selected: ${state.enabled ? "yes" : "no"}`);
|
|
847
|
+
if (state.credential.lastValidationStatus) console.log(`Status: ${state.credential.lastValidationStatus}`);
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
async function agentSubscriptionLogoutCommand(config, options, command) {
|
|
851
|
+
const { config: apiConfig } = resolveBusinessContext(command, options);
|
|
852
|
+
await setSubscriptionEnabled(apiConfig, config.harness, false).catch(() => {
|
|
853
|
+
});
|
|
854
|
+
const payload = await apiFetch(
|
|
855
|
+
apiConfig,
|
|
856
|
+
`${subscriptionBasePath(config.harness)}/auth-json`,
|
|
857
|
+
{ method: "DELETE" }
|
|
858
|
+
);
|
|
859
|
+
emit(
|
|
860
|
+
command,
|
|
861
|
+
options,
|
|
862
|
+
payload,
|
|
863
|
+
() => console.log(`${config.displayName} subscription auth deactivated and cleared.`)
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
|
|
607
867
|
// src/commands/auth.ts
|
|
608
868
|
async function whoamiCommand(options, command) {
|
|
609
869
|
const { config } = resolveBusinessContext(command, options);
|
|
@@ -618,16 +878,19 @@ async function whoamiCommand(options, command) {
|
|
|
618
878
|
var CODEX_AGENT_RUNTIME_BACKEND = "codex";
|
|
619
879
|
var CLAUDE_CODE_AGENT_RUNTIME_BACKEND = "claude_code";
|
|
620
880
|
var OPENCODE_AGENT_RUNTIME_BACKEND = "opencode";
|
|
881
|
+
var CURSOR_AGENT_RUNTIME_BACKEND = "cursor";
|
|
621
882
|
var AGENT_RUNTIME_BACKENDS = [
|
|
622
883
|
CODEX_AGENT_RUNTIME_BACKEND,
|
|
623
884
|
CLAUDE_CODE_AGENT_RUNTIME_BACKEND,
|
|
624
|
-
OPENCODE_AGENT_RUNTIME_BACKEND
|
|
885
|
+
OPENCODE_AGENT_RUNTIME_BACKEND,
|
|
886
|
+
CURSOR_AGENT_RUNTIME_BACKEND
|
|
625
887
|
];
|
|
626
888
|
var AGENT_RUNTIME_BACKEND_NAMES = {
|
|
627
889
|
[CODEX_AGENT_RUNTIME_BACKEND]: "Codex",
|
|
628
890
|
[CLAUDE_CODE_AGENT_RUNTIME_BACKEND]: "Claude Code",
|
|
629
891
|
// "opencode" is intentionally lowercase to match the project's brand name.
|
|
630
|
-
[OPENCODE_AGENT_RUNTIME_BACKEND]: "opencode"
|
|
892
|
+
[OPENCODE_AGENT_RUNTIME_BACKEND]: "opencode",
|
|
893
|
+
[CURSOR_AGENT_RUNTIME_BACKEND]: "Cursor"
|
|
631
894
|
};
|
|
632
895
|
function isAgentRuntimeBackend(value) {
|
|
633
896
|
return AGENT_RUNTIME_BACKENDS.includes(value);
|
|
@@ -669,10 +932,18 @@ var AnthropicModel = {
|
|
|
669
932
|
var BasetenModel = {
|
|
670
933
|
KimiK27Code: "kimi-k2.7-code"
|
|
671
934
|
};
|
|
935
|
+
var XaiModel = {
|
|
936
|
+
Grok45: "grok-4.5"
|
|
937
|
+
};
|
|
938
|
+
var CursorModel = {
|
|
939
|
+
Composer25: "composer-2.5"
|
|
940
|
+
};
|
|
672
941
|
var MODEL_PROVIDERS_SET = /* @__PURE__ */ new Set([
|
|
673
942
|
"openai",
|
|
674
943
|
"anthropic",
|
|
675
|
-
"baseten"
|
|
944
|
+
"baseten",
|
|
945
|
+
"xai",
|
|
946
|
+
"cursor"
|
|
676
947
|
]);
|
|
677
948
|
var BACKEND_DESKTOP_IMAGE_FEEDBACK_CONFIGS = {
|
|
678
949
|
[CODEX_AGENT_RUNTIME_BACKEND]: {
|
|
@@ -986,12 +1257,47 @@ var MODEL_REGISTRY = [
|
|
|
986
1257
|
},
|
|
987
1258
|
pricing: { inputPerMillion: 0.95, outputPerMillion: 4, cacheReadPerMillion: 0.16 },
|
|
988
1259
|
sessionStart: { eligible: true, isDefault: true }
|
|
1260
|
+
},
|
|
1261
|
+
{
|
|
1262
|
+
id: XaiModel.Grok45,
|
|
1263
|
+
name: "Grok 4.5",
|
|
1264
|
+
provider: "xai",
|
|
1265
|
+
backends: [OPENCODE_AGENT_RUNTIME_BACKEND],
|
|
1266
|
+
contextWindow: 5e5,
|
|
1267
|
+
// No reasoning config on purpose: xAI's chat-completions reference
|
|
1268
|
+
// (verified 2026-07-14, https://docs.x.ai/docs/api-reference) documents
|
|
1269
|
+
// `reasoning_effort` as supported only by grok-4.3, so grok-4.5 is
|
|
1270
|
+
// classified no-reasoning per the registry rule. Pricing/context verified
|
|
1271
|
+
// 2026-07-14 against https://docs.x.ai/docs/pricing ($2/M in, $6/M out,
|
|
1272
|
+
// 500k context); no cached-input rate is documented, so none is recorded.
|
|
1273
|
+
pricing: { inputPerMillion: 2, outputPerMillion: 6 },
|
|
1274
|
+
sessionStart: { eligible: true }
|
|
1275
|
+
},
|
|
1276
|
+
{
|
|
1277
|
+
id: CursorModel.Composer25,
|
|
1278
|
+
name: "Composer 2.5",
|
|
1279
|
+
provider: "cursor",
|
|
1280
|
+
backends: [CURSOR_AGENT_RUNTIME_BACKEND],
|
|
1281
|
+
// Cursor bills Composer usage through the user's Cursor account (API key
|
|
1282
|
+
// or subscription); Arcanist does not meter it, so no registry pricing is
|
|
1283
|
+
// recorded (costTracked: false requires pricing to stay undefined). No
|
|
1284
|
+
// reasoning config: Cursor encodes effort in distinct model ids
|
|
1285
|
+
// (`--list-models`), not a per-request parameter. Context window is not
|
|
1286
|
+
// published for Composer 2.5 (checked cursor.com/docs/models 2026-07-15),
|
|
1287
|
+
// so none is recorded.
|
|
1288
|
+
costTracked: false,
|
|
1289
|
+
sessionStart: { eligible: true, isDefault: true },
|
|
1290
|
+
// Internal probe: hidden from the public model picker; selectable via
|
|
1291
|
+
// CLI/API only while the cursor backend is gated.
|
|
1292
|
+
visibility: "internal_probe"
|
|
989
1293
|
}
|
|
990
1294
|
];
|
|
991
1295
|
var MODEL_PROVIDER_NAMES = {
|
|
992
1296
|
openai: "OpenAI",
|
|
993
1297
|
anthropic: "Anthropic",
|
|
994
|
-
baseten: "Baseten"
|
|
1298
|
+
baseten: "Baseten",
|
|
1299
|
+
xai: "xAI",
|
|
1300
|
+
cursor: "Cursor"
|
|
995
1301
|
};
|
|
996
1302
|
function buildSessionStartModelIdsByBackend() {
|
|
997
1303
|
const byBackend = Object.fromEntries(AGENT_RUNTIME_BACKENDS.map((backend) => [backend, []]));
|
|
@@ -1030,7 +1336,8 @@ var VALID_SESSION_START_MODEL_IDS_BY_BACKEND = {
|
|
|
1030
1336
|
[CLAUDE_CODE_AGENT_RUNTIME_BACKEND]: new Set(
|
|
1031
1337
|
SESSION_START_MODEL_IDS_BY_BACKEND[CLAUDE_CODE_AGENT_RUNTIME_BACKEND]
|
|
1032
1338
|
),
|
|
1033
|
-
[OPENCODE_AGENT_RUNTIME_BACKEND]: new Set(SESSION_START_MODEL_IDS_BY_BACKEND[OPENCODE_AGENT_RUNTIME_BACKEND])
|
|
1339
|
+
[OPENCODE_AGENT_RUNTIME_BACKEND]: new Set(SESSION_START_MODEL_IDS_BY_BACKEND[OPENCODE_AGENT_RUNTIME_BACKEND]),
|
|
1340
|
+
[CURSOR_AGENT_RUNTIME_BACKEND]: new Set(SESSION_START_MODEL_IDS_BY_BACKEND[CURSOR_AGENT_RUNTIME_BACKEND])
|
|
1034
1341
|
};
|
|
1035
1342
|
var MODEL_CONTEXT_WINDOWS = {
|
|
1036
1343
|
...Object.fromEntries(
|
|
@@ -1231,7 +1538,7 @@ async function listAutomationsCommand(options, command) {
|
|
|
1231
1538
|
const runtime = getRuntimeOptions(command, options);
|
|
1232
1539
|
const config = requireConfig(runtime);
|
|
1233
1540
|
const items = [];
|
|
1234
|
-
let
|
|
1541
|
+
let cursor2 = options.cursor;
|
|
1235
1542
|
let nextCursor = null;
|
|
1236
1543
|
let pageCount = 0;
|
|
1237
1544
|
do {
|
|
@@ -1241,14 +1548,14 @@ async function listAutomationsCommand(options, command) {
|
|
|
1241
1548
|
}
|
|
1242
1549
|
const query = new URLSearchParams();
|
|
1243
1550
|
if (options.limit) query.set("limit", options.limit);
|
|
1244
|
-
if (
|
|
1551
|
+
if (cursor2) query.set("cursor", cursor2);
|
|
1245
1552
|
const payload = await automationApiFetch(
|
|
1246
1553
|
config,
|
|
1247
1554
|
`/api/automation/schedules${query.size ? `?${query.toString()}` : ""}`
|
|
1248
1555
|
);
|
|
1249
1556
|
items.push(...payload.data.items);
|
|
1250
1557
|
nextCursor = payload.data.nextCursor;
|
|
1251
|
-
|
|
1558
|
+
cursor2 = nextCursor ?? void 0;
|
|
1252
1559
|
} while (options.all === true && nextCursor);
|
|
1253
1560
|
const output = { items, nextCursor: options.all === true ? null : nextCursor };
|
|
1254
1561
|
if (isJson(command, options)) {
|
|
@@ -1341,11 +1648,11 @@ function formatTime(value) {
|
|
|
1341
1648
|
}
|
|
1342
1649
|
|
|
1343
1650
|
// src/commands/codex.ts
|
|
1344
|
-
import { spawn } from "child_process";
|
|
1345
|
-
import { rmSync } from "fs";
|
|
1346
|
-
import { mkdtemp, readFile, rm } from "fs/promises";
|
|
1347
|
-
import { tmpdir } from "os";
|
|
1348
|
-
import { join as
|
|
1651
|
+
import { spawn as spawn2 } from "child_process";
|
|
1652
|
+
import { rmSync as rmSync2 } from "fs";
|
|
1653
|
+
import { mkdtemp as mkdtemp2, readFile as readFile2, rm as rm2 } from "fs/promises";
|
|
1654
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
1655
|
+
import { join as join3 } from "path";
|
|
1349
1656
|
var CODEX_SUBSCRIPTION_PATH = "/api/settings/codex-subscription";
|
|
1350
1657
|
var CODEX_SUBSCRIPTION_AUTH_JSON_PATH = "/api/settings/codex-subscription/auth-json";
|
|
1351
1658
|
var CODEX_SUBSCRIPTION_ENABLED_PATH = "/api/settings/codex-subscription/enabled";
|
|
@@ -1354,7 +1661,7 @@ function resolveCodexPath(optionPath) {
|
|
|
1354
1661
|
}
|
|
1355
1662
|
function runCodexDeviceLogin(codexPath, codexHome) {
|
|
1356
1663
|
return new Promise((resolve2, reject) => {
|
|
1357
|
-
const child =
|
|
1664
|
+
const child = spawn2(codexPath, ["login", "--device-auth"], {
|
|
1358
1665
|
stdio: "inherit",
|
|
1359
1666
|
env: { ...process.env, CODEX_HOME: codexHome }
|
|
1360
1667
|
});
|
|
@@ -1395,7 +1702,7 @@ async function codexLoginCommand(options, command) {
|
|
|
1395
1702
|
const handleSigint = () => {
|
|
1396
1703
|
if (codexHome) {
|
|
1397
1704
|
try {
|
|
1398
|
-
|
|
1705
|
+
rmSync2(codexHome, { recursive: true, force: true });
|
|
1399
1706
|
} catch {
|
|
1400
1707
|
}
|
|
1401
1708
|
}
|
|
@@ -1403,11 +1710,11 @@ async function codexLoginCommand(options, command) {
|
|
|
1403
1710
|
};
|
|
1404
1711
|
process.on("SIGINT", handleSigint);
|
|
1405
1712
|
try {
|
|
1406
|
-
codexHome = await
|
|
1713
|
+
codexHome = await mkdtemp2(join3(tmpdir2(), "arcanist-codex-"));
|
|
1407
1714
|
await runCodexDeviceLogin(codexPath, codexHome);
|
|
1408
1715
|
let authJson;
|
|
1409
1716
|
try {
|
|
1410
|
-
authJson = await
|
|
1717
|
+
authJson = await readFile2(join3(codexHome, "auth.json"), "utf8");
|
|
1411
1718
|
} catch {
|
|
1412
1719
|
throw new CliError("user", "Codex login completed but no auth.json was written.", {
|
|
1413
1720
|
hint: "Verify `codex login --device-auth` succeeds on its own, then re-run `arcanist codex login`."
|
|
@@ -1441,7 +1748,7 @@ async function codexLoginCommand(options, command) {
|
|
|
1441
1748
|
} finally {
|
|
1442
1749
|
try {
|
|
1443
1750
|
if (codexHome) {
|
|
1444
|
-
await
|
|
1751
|
+
await rm2(codexHome, { recursive: true, force: true }).catch(() => {
|
|
1445
1752
|
});
|
|
1446
1753
|
}
|
|
1447
1754
|
} finally {
|
|
@@ -1536,7 +1843,7 @@ function isWatchTerminal(phase) {
|
|
|
1536
1843
|
}
|
|
1537
1844
|
|
|
1538
1845
|
// src/uploads.ts
|
|
1539
|
-
import { readFile as
|
|
1846
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1540
1847
|
import { basename, extname } from "path";
|
|
1541
1848
|
|
|
1542
1849
|
// ../../shared/constants/uploads.ts
|
|
@@ -1639,7 +1946,7 @@ async function resolveUploadedFileOptions(files) {
|
|
|
1639
1946
|
paths.map(async (path) => {
|
|
1640
1947
|
const name = basename(path);
|
|
1641
1948
|
try {
|
|
1642
|
-
return { name, content: await
|
|
1949
|
+
return { name, content: await readFile3(path, "utf8") };
|
|
1643
1950
|
} catch (err) {
|
|
1644
1951
|
const message = stringifyError(err);
|
|
1645
1952
|
throw new CliError("user", `Failed to read uploaded file ${path}: ${message}`);
|
|
@@ -3126,6 +3433,10 @@ function resolveModelAndBackend(options) {
|
|
|
3126
3433
|
);
|
|
3127
3434
|
}
|
|
3128
3435
|
agentRuntimeBackend = options.backend;
|
|
3436
|
+
} else if (options.model !== void 0) {
|
|
3437
|
+
const normalizedModel = extractModelId(options.model);
|
|
3438
|
+
const derived = normalizedModel ? getAgentRuntimeBackendForModel(normalizedModel) : void 0;
|
|
3439
|
+
if (derived) agentRuntimeBackend = derived;
|
|
3129
3440
|
}
|
|
3130
3441
|
if (options.model !== void 0) {
|
|
3131
3442
|
const normalizedModel = extractModelId(options.model);
|
|
@@ -3936,10 +4247,10 @@ async function qaCommand(prUrl, options, command) {
|
|
|
3936
4247
|
}
|
|
3937
4248
|
|
|
3938
4249
|
// src/git.ts
|
|
3939
|
-
import { execFileSync } from "child_process";
|
|
4250
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
3940
4251
|
function git(args) {
|
|
3941
4252
|
try {
|
|
3942
|
-
return
|
|
4253
|
+
return execFileSync2("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
3943
4254
|
} catch (err) {
|
|
3944
4255
|
throw new CliError("user", `git ${args.join(" ")} failed: ${stringifyError(err)}`);
|
|
3945
4256
|
}
|
|
@@ -4046,7 +4357,7 @@ function parseRespondConflict(err) {
|
|
|
4046
4357
|
}
|
|
4047
4358
|
|
|
4048
4359
|
// src/commands/sandbox.ts
|
|
4049
|
-
import { existsSync as
|
|
4360
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4050
4361
|
import { dirname as dirname2 } from "path";
|
|
4051
4362
|
|
|
4052
4363
|
// ../../shared/sandbox-layer/parser.ts
|
|
@@ -4593,7 +4904,7 @@ function assertCleanAndPushed() {
|
|
|
4593
4904
|
throw new CliError("user", "Current commit is not pushed; push before building the sandbox layer.");
|
|
4594
4905
|
}
|
|
4595
4906
|
function assertManifestExists(path) {
|
|
4596
|
-
if (!
|
|
4907
|
+
if (!existsSync3(path)) throw new CliError("user", `Missing sandbox manifest: ${path}`);
|
|
4597
4908
|
}
|
|
4598
4909
|
function parseRepoArg3(value, fallback) {
|
|
4599
4910
|
if (!value) return fallback();
|
|
@@ -4824,9 +5135,9 @@ function sourceBody(sourceRepo, manifestPath) {
|
|
|
4824
5135
|
}
|
|
4825
5136
|
async function sandboxInitCommand(options = {}, command) {
|
|
4826
5137
|
const runtime = getRuntimeOptions(command, options);
|
|
4827
|
-
if (
|
|
5138
|
+
if (existsSync3(DEFAULT_MANIFEST_PATH)) throw new CliError("conflict", `${DEFAULT_MANIFEST_PATH} already exists`);
|
|
4828
5139
|
const layerPath = ".arcanist/sandbox.layer.Dockerfile";
|
|
4829
|
-
if (
|
|
5140
|
+
if (existsSync3(layerPath)) throw new CliError("conflict", `${layerPath} already exists`);
|
|
4830
5141
|
mkdirSync2(dirname2(DEFAULT_MANIFEST_PATH), { recursive: true });
|
|
4831
5142
|
writeFileSync2(
|
|
4832
5143
|
DEFAULT_MANIFEST_PATH,
|
|
@@ -5124,7 +5435,7 @@ async function listSessionsCommand(options, command) {
|
|
|
5124
5435
|
const runtime = getRuntimeOptions(command, options);
|
|
5125
5436
|
const config = requireConfig(runtime);
|
|
5126
5437
|
const sessions2 = [];
|
|
5127
|
-
let
|
|
5438
|
+
let cursor2 = options.cursor;
|
|
5128
5439
|
let nextCursor = null;
|
|
5129
5440
|
let pageCount = 0;
|
|
5130
5441
|
do {
|
|
@@ -5138,14 +5449,14 @@ async function listSessionsCommand(options, command) {
|
|
|
5138
5449
|
if (options.search) query.set("q", options.search);
|
|
5139
5450
|
if (options.repo) query.set("repo", options.repo);
|
|
5140
5451
|
if (options.limit) query.set("limit", options.limit);
|
|
5141
|
-
if (
|
|
5452
|
+
if (cursor2) query.set("cursor", cursor2);
|
|
5142
5453
|
const payload2 = await apiFetch(
|
|
5143
5454
|
config,
|
|
5144
5455
|
`/api/sessions${query.size ? `?${query.toString()}` : ""}`
|
|
5145
5456
|
);
|
|
5146
5457
|
sessions2.push(...payload2.sessions);
|
|
5147
5458
|
nextCursor = payload2.nextCursor;
|
|
5148
|
-
|
|
5459
|
+
cursor2 = nextCursor ?? void 0;
|
|
5149
5460
|
} while (options.all === true && nextCursor);
|
|
5150
5461
|
const payload = { sessions: sessions2, nextCursor: options.all === true ? null : nextCursor };
|
|
5151
5462
|
if (isJson(command, options)) {
|
|
@@ -5377,7 +5688,7 @@ var MAX_ALL_PAGES3 = 1e3;
|
|
|
5377
5688
|
async function listTokensCommand(options, command) {
|
|
5378
5689
|
const { config } = resolveBusinessContext(command, options);
|
|
5379
5690
|
const data = [];
|
|
5380
|
-
let
|
|
5691
|
+
let cursor2 = options.cursor;
|
|
5381
5692
|
let nextCursor = null;
|
|
5382
5693
|
let pageCount = 0;
|
|
5383
5694
|
do {
|
|
@@ -5387,14 +5698,14 @@ async function listTokensCommand(options, command) {
|
|
|
5387
5698
|
}
|
|
5388
5699
|
const query = new URLSearchParams();
|
|
5389
5700
|
if (options.limit) query.set("limit", options.limit);
|
|
5390
|
-
if (
|
|
5701
|
+
if (cursor2) query.set("cursor", cursor2);
|
|
5391
5702
|
const payload2 = await apiFetch(
|
|
5392
5703
|
config,
|
|
5393
5704
|
`/api/cli-tokens${query.size ? `?${query.toString()}` : ""}`
|
|
5394
5705
|
);
|
|
5395
5706
|
data.push(...payload2.data);
|
|
5396
5707
|
nextCursor = payload2.nextCursor;
|
|
5397
|
-
|
|
5708
|
+
cursor2 = nextCursor ?? void 0;
|
|
5398
5709
|
} while (options.all === true && nextCursor);
|
|
5399
5710
|
const payload = { data, nextCursor: options.all === true ? null : nextCursor };
|
|
5400
5711
|
emit(command, options, payload, (listPayload) => {
|
|
@@ -5628,6 +5939,52 @@ Examples:
|
|
|
5628
5939
|
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));
|
|
5629
5940
|
codex.command("status").description("Show whether your workspace is eligible and whether a Codex subscription auth is saved").action((options, command) => codexStatusCommand(options, command));
|
|
5630
5941
|
codex.command("logout").description("Deactivate the selector and remove the stored Codex subscription auth for your user").action((options, command) => codexLogoutCommand(options, command));
|
|
5942
|
+
var grok = program.command("grok").description("Grok (xAI) subscription (bring-your-own-subscription) commands");
|
|
5943
|
+
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(
|
|
5944
|
+
"after",
|
|
5945
|
+
`
|
|
5946
|
+
Runs the Grok CLI device-authorization login locally under a temporary HOME, then uploads the
|
|
5947
|
+
resulting auth.json to Arcanist (encrypted, per user) and activates the selector. Your workspace
|
|
5948
|
+
must have Grok subscription auth enabled. The credential is never written to your default ~/.grok.
|
|
5949
|
+
Grok BYOS session execution is not live yet; the credential is stored for when it ships.
|
|
5950
|
+
|
|
5951
|
+
Examples:
|
|
5952
|
+
arcanist grok login
|
|
5953
|
+
arcanist grok login --grok-path /usr/local/bin/grok
|
|
5954
|
+
`
|
|
5955
|
+
).action(
|
|
5956
|
+
(options, command) => agentSubscriptionLoginCommand(GROK_SUBSCRIPTION_CLI_CONFIG, { ...options, binPath: options.grokPath }, command)
|
|
5957
|
+
);
|
|
5958
|
+
grok.command("use <state>").description("Turn using your saved Grok subscription auth on or off (state: on|off)").action(
|
|
5959
|
+
(state, options, command) => agentSubscriptionUseCommand(GROK_SUBSCRIPTION_CLI_CONFIG, state, options, command)
|
|
5960
|
+
);
|
|
5961
|
+
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));
|
|
5962
|
+
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));
|
|
5963
|
+
var cursor = program.command("cursor").description("Cursor subscription (bring-your-own-subscription) commands");
|
|
5964
|
+
cursor.command("login").description("Authenticate a Cursor subscription and store it for future Cursor BYOS sessions").option(
|
|
5965
|
+
"--cursor-path <path>",
|
|
5966
|
+
"Path to the cursor-agent executable (default: cursor-agent on PATH, or ARCANIST_CURSOR_AGENT_BIN)"
|
|
5967
|
+
).addHelpText(
|
|
5968
|
+
"after",
|
|
5969
|
+
`
|
|
5970
|
+
Runs the Cursor CLI login locally under a temporary HOME/CURSOR_CONFIG_DIR, then uploads the
|
|
5971
|
+
captured auth credential to Arcanist (encrypted, per user) and activates the selector. Your
|
|
5972
|
+
workspace must have Cursor subscription auth enabled. Cursor's login-token location is not
|
|
5973
|
+
formally documented; if capture fails, use a Cursor API key in Settings instead. Cursor BYOS
|
|
5974
|
+
session execution is not live yet; the credential is stored for when it ships.
|
|
5975
|
+
|
|
5976
|
+
Examples:
|
|
5977
|
+
arcanist cursor login
|
|
5978
|
+
arcanist cursor login --cursor-path /usr/local/bin/cursor-agent
|
|
5979
|
+
`
|
|
5980
|
+
).action(
|
|
5981
|
+
(options, command) => agentSubscriptionLoginCommand(CURSOR_SUBSCRIPTION_CLI_CONFIG, { ...options, binPath: options.cursorPath }, command)
|
|
5982
|
+
);
|
|
5983
|
+
cursor.command("use <state>").description("Turn using your saved Cursor subscription auth on or off (state: on|off)").action(
|
|
5984
|
+
(state, options, command) => agentSubscriptionUseCommand(CURSOR_SUBSCRIPTION_CLI_CONFIG, state, options, command)
|
|
5985
|
+
);
|
|
5986
|
+
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));
|
|
5987
|
+
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));
|
|
5631
5988
|
var sessions = program.command("sessions").description("Session commands");
|
|
5632
5989
|
addCreateOptions(sessions.command("create").description("Create a session and send a prompt")).action(
|
|
5633
5990
|
(repoUrl, prompt, options, command) => createCommand(repoUrl, prompt, options, command)
|