@tryarcanist/cli 0.1.204 → 0.1.206
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 +413 -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}`);
|
|
@@ -2095,6 +2402,7 @@ function createFlattenState() {
|
|
|
2095
2402
|
toolCallIndexById: /* @__PURE__ */ new Map(),
|
|
2096
2403
|
agentProgressIndexByKey: /* @__PURE__ */ new Map(),
|
|
2097
2404
|
questionIndexById: /* @__PURE__ */ new Map(),
|
|
2405
|
+
narrationIndex: null,
|
|
2098
2406
|
malformedSearchBlockedPromptIds: /* @__PURE__ */ new Set(),
|
|
2099
2407
|
malformedSearchBlockedWithoutPrompt: false
|
|
2100
2408
|
};
|
|
@@ -2426,6 +2734,23 @@ function applyTodoUpdate(data, state) {
|
|
|
2426
2734
|
}
|
|
2427
2735
|
}
|
|
2428
2736
|
}
|
|
2737
|
+
function projectNarration(data, state) {
|
|
2738
|
+
const text = typeof data?.text === "string" ? data.text.trim() : "";
|
|
2739
|
+
if (!text) return;
|
|
2740
|
+
const promptId = resolvePromptId(data);
|
|
2741
|
+
const event = {
|
|
2742
|
+
type: "narration",
|
|
2743
|
+
id: "narration",
|
|
2744
|
+
text,
|
|
2745
|
+
...promptId ? { promptId } : {}
|
|
2746
|
+
};
|
|
2747
|
+
if (state.narrationIndex === null) {
|
|
2748
|
+
state.narrationIndex = state.merged.length;
|
|
2749
|
+
state.merged.push(event);
|
|
2750
|
+
} else {
|
|
2751
|
+
state.merged[state.narrationIndex] = event;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2429
2754
|
function flattenSessionEvents(raw) {
|
|
2430
2755
|
const state = createFlattenState();
|
|
2431
2756
|
const normalizedEvents = normalizeRawSessionEvents(raw);
|
|
@@ -2499,6 +2824,9 @@ function flattenSessionEvents(raw) {
|
|
|
2499
2824
|
case "question":
|
|
2500
2825
|
projectQuestion(data, state);
|
|
2501
2826
|
break;
|
|
2827
|
+
case "narration":
|
|
2828
|
+
projectNarration(data, state);
|
|
2829
|
+
break;
|
|
2502
2830
|
}
|
|
2503
2831
|
}
|
|
2504
2832
|
return suppressMalformedSearchSegments(state);
|
|
@@ -2828,6 +3156,9 @@ ${event.answer ? `**Answer:** ${event.answer}
|
|
|
2828
3156
|
return "";
|
|
2829
3157
|
case "agent_progress":
|
|
2830
3158
|
return `*[${event.label}]*
|
|
3159
|
+
`;
|
|
3160
|
+
case "narration":
|
|
3161
|
+
return `*[Currently: ${event.text}]*
|
|
2831
3162
|
`;
|
|
2832
3163
|
case "raw_agent_runtime":
|
|
2833
3164
|
return "";
|
|
@@ -3126,6 +3457,10 @@ function resolveModelAndBackend(options) {
|
|
|
3126
3457
|
);
|
|
3127
3458
|
}
|
|
3128
3459
|
agentRuntimeBackend = options.backend;
|
|
3460
|
+
} else if (options.model !== void 0) {
|
|
3461
|
+
const normalizedModel = extractModelId(options.model);
|
|
3462
|
+
const derived = normalizedModel ? getAgentRuntimeBackendForModel(normalizedModel) : void 0;
|
|
3463
|
+
if (derived) agentRuntimeBackend = derived;
|
|
3129
3464
|
}
|
|
3130
3465
|
if (options.model !== void 0) {
|
|
3131
3466
|
const normalizedModel = extractModelId(options.model);
|
|
@@ -3936,10 +4271,10 @@ async function qaCommand(prUrl, options, command) {
|
|
|
3936
4271
|
}
|
|
3937
4272
|
|
|
3938
4273
|
// src/git.ts
|
|
3939
|
-
import { execFileSync } from "child_process";
|
|
4274
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
3940
4275
|
function git(args) {
|
|
3941
4276
|
try {
|
|
3942
|
-
return
|
|
4277
|
+
return execFileSync2("git", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
3943
4278
|
} catch (err) {
|
|
3944
4279
|
throw new CliError("user", `git ${args.join(" ")} failed: ${stringifyError(err)}`);
|
|
3945
4280
|
}
|
|
@@ -4046,7 +4381,7 @@ function parseRespondConflict(err) {
|
|
|
4046
4381
|
}
|
|
4047
4382
|
|
|
4048
4383
|
// src/commands/sandbox.ts
|
|
4049
|
-
import { existsSync as
|
|
4384
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4050
4385
|
import { dirname as dirname2 } from "path";
|
|
4051
4386
|
|
|
4052
4387
|
// ../../shared/sandbox-layer/parser.ts
|
|
@@ -4593,7 +4928,7 @@ function assertCleanAndPushed() {
|
|
|
4593
4928
|
throw new CliError("user", "Current commit is not pushed; push before building the sandbox layer.");
|
|
4594
4929
|
}
|
|
4595
4930
|
function assertManifestExists(path) {
|
|
4596
|
-
if (!
|
|
4931
|
+
if (!existsSync3(path)) throw new CliError("user", `Missing sandbox manifest: ${path}`);
|
|
4597
4932
|
}
|
|
4598
4933
|
function parseRepoArg3(value, fallback) {
|
|
4599
4934
|
if (!value) return fallback();
|
|
@@ -4824,9 +5159,9 @@ function sourceBody(sourceRepo, manifestPath) {
|
|
|
4824
5159
|
}
|
|
4825
5160
|
async function sandboxInitCommand(options = {}, command) {
|
|
4826
5161
|
const runtime = getRuntimeOptions(command, options);
|
|
4827
|
-
if (
|
|
5162
|
+
if (existsSync3(DEFAULT_MANIFEST_PATH)) throw new CliError("conflict", `${DEFAULT_MANIFEST_PATH} already exists`);
|
|
4828
5163
|
const layerPath = ".arcanist/sandbox.layer.Dockerfile";
|
|
4829
|
-
if (
|
|
5164
|
+
if (existsSync3(layerPath)) throw new CliError("conflict", `${layerPath} already exists`);
|
|
4830
5165
|
mkdirSync2(dirname2(DEFAULT_MANIFEST_PATH), { recursive: true });
|
|
4831
5166
|
writeFileSync2(
|
|
4832
5167
|
DEFAULT_MANIFEST_PATH,
|
|
@@ -5124,7 +5459,7 @@ async function listSessionsCommand(options, command) {
|
|
|
5124
5459
|
const runtime = getRuntimeOptions(command, options);
|
|
5125
5460
|
const config = requireConfig(runtime);
|
|
5126
5461
|
const sessions2 = [];
|
|
5127
|
-
let
|
|
5462
|
+
let cursor2 = options.cursor;
|
|
5128
5463
|
let nextCursor = null;
|
|
5129
5464
|
let pageCount = 0;
|
|
5130
5465
|
do {
|
|
@@ -5138,14 +5473,14 @@ async function listSessionsCommand(options, command) {
|
|
|
5138
5473
|
if (options.search) query.set("q", options.search);
|
|
5139
5474
|
if (options.repo) query.set("repo", options.repo);
|
|
5140
5475
|
if (options.limit) query.set("limit", options.limit);
|
|
5141
|
-
if (
|
|
5476
|
+
if (cursor2) query.set("cursor", cursor2);
|
|
5142
5477
|
const payload2 = await apiFetch(
|
|
5143
5478
|
config,
|
|
5144
5479
|
`/api/sessions${query.size ? `?${query.toString()}` : ""}`
|
|
5145
5480
|
);
|
|
5146
5481
|
sessions2.push(...payload2.sessions);
|
|
5147
5482
|
nextCursor = payload2.nextCursor;
|
|
5148
|
-
|
|
5483
|
+
cursor2 = nextCursor ?? void 0;
|
|
5149
5484
|
} while (options.all === true && nextCursor);
|
|
5150
5485
|
const payload = { sessions: sessions2, nextCursor: options.all === true ? null : nextCursor };
|
|
5151
5486
|
if (isJson(command, options)) {
|
|
@@ -5377,7 +5712,7 @@ var MAX_ALL_PAGES3 = 1e3;
|
|
|
5377
5712
|
async function listTokensCommand(options, command) {
|
|
5378
5713
|
const { config } = resolveBusinessContext(command, options);
|
|
5379
5714
|
const data = [];
|
|
5380
|
-
let
|
|
5715
|
+
let cursor2 = options.cursor;
|
|
5381
5716
|
let nextCursor = null;
|
|
5382
5717
|
let pageCount = 0;
|
|
5383
5718
|
do {
|
|
@@ -5387,14 +5722,14 @@ async function listTokensCommand(options, command) {
|
|
|
5387
5722
|
}
|
|
5388
5723
|
const query = new URLSearchParams();
|
|
5389
5724
|
if (options.limit) query.set("limit", options.limit);
|
|
5390
|
-
if (
|
|
5725
|
+
if (cursor2) query.set("cursor", cursor2);
|
|
5391
5726
|
const payload2 = await apiFetch(
|
|
5392
5727
|
config,
|
|
5393
5728
|
`/api/cli-tokens${query.size ? `?${query.toString()}` : ""}`
|
|
5394
5729
|
);
|
|
5395
5730
|
data.push(...payload2.data);
|
|
5396
5731
|
nextCursor = payload2.nextCursor;
|
|
5397
|
-
|
|
5732
|
+
cursor2 = nextCursor ?? void 0;
|
|
5398
5733
|
} while (options.all === true && nextCursor);
|
|
5399
5734
|
const payload = { data, nextCursor: options.all === true ? null : nextCursor };
|
|
5400
5735
|
emit(command, options, payload, (listPayload) => {
|
|
@@ -5628,6 +5963,52 @@ Examples:
|
|
|
5628
5963
|
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
5964
|
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
5965
|
codex.command("logout").description("Deactivate the selector and remove the stored Codex subscription auth for your user").action((options, command) => codexLogoutCommand(options, command));
|
|
5966
|
+
var grok = program.command("grok").description("Grok (xAI) subscription (bring-your-own-subscription) commands");
|
|
5967
|
+
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(
|
|
5968
|
+
"after",
|
|
5969
|
+
`
|
|
5970
|
+
Runs the Grok CLI device-authorization login locally under a temporary HOME, then uploads the
|
|
5971
|
+
resulting auth.json to Arcanist (encrypted, per user) and activates the selector. Your workspace
|
|
5972
|
+
must have Grok subscription auth enabled. The credential is never written to your default ~/.grok.
|
|
5973
|
+
Grok BYOS session execution is not live yet; the credential is stored for when it ships.
|
|
5974
|
+
|
|
5975
|
+
Examples:
|
|
5976
|
+
arcanist grok login
|
|
5977
|
+
arcanist grok login --grok-path /usr/local/bin/grok
|
|
5978
|
+
`
|
|
5979
|
+
).action(
|
|
5980
|
+
(options, command) => agentSubscriptionLoginCommand(GROK_SUBSCRIPTION_CLI_CONFIG, { ...options, binPath: options.grokPath }, command)
|
|
5981
|
+
);
|
|
5982
|
+
grok.command("use <state>").description("Turn using your saved Grok subscription auth on or off (state: on|off)").action(
|
|
5983
|
+
(state, options, command) => agentSubscriptionUseCommand(GROK_SUBSCRIPTION_CLI_CONFIG, state, options, command)
|
|
5984
|
+
);
|
|
5985
|
+
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));
|
|
5986
|
+
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));
|
|
5987
|
+
var cursor = program.command("cursor").description("Cursor subscription (bring-your-own-subscription) commands");
|
|
5988
|
+
cursor.command("login").description("Authenticate a Cursor subscription and store it for future Cursor BYOS sessions").option(
|
|
5989
|
+
"--cursor-path <path>",
|
|
5990
|
+
"Path to the cursor-agent executable (default: cursor-agent on PATH, or ARCANIST_CURSOR_AGENT_BIN)"
|
|
5991
|
+
).addHelpText(
|
|
5992
|
+
"after",
|
|
5993
|
+
`
|
|
5994
|
+
Runs the Cursor CLI login locally under a temporary HOME/CURSOR_CONFIG_DIR, then uploads the
|
|
5995
|
+
captured auth credential to Arcanist (encrypted, per user) and activates the selector. Your
|
|
5996
|
+
workspace must have Cursor subscription auth enabled. Cursor's login-token location is not
|
|
5997
|
+
formally documented; if capture fails, use a Cursor API key in Settings instead. Cursor BYOS
|
|
5998
|
+
session execution is not live yet; the credential is stored for when it ships.
|
|
5999
|
+
|
|
6000
|
+
Examples:
|
|
6001
|
+
arcanist cursor login
|
|
6002
|
+
arcanist cursor login --cursor-path /usr/local/bin/cursor-agent
|
|
6003
|
+
`
|
|
6004
|
+
).action(
|
|
6005
|
+
(options, command) => agentSubscriptionLoginCommand(CURSOR_SUBSCRIPTION_CLI_CONFIG, { ...options, binPath: options.cursorPath }, command)
|
|
6006
|
+
);
|
|
6007
|
+
cursor.command("use <state>").description("Turn using your saved Cursor subscription auth on or off (state: on|off)").action(
|
|
6008
|
+
(state, options, command) => agentSubscriptionUseCommand(CURSOR_SUBSCRIPTION_CLI_CONFIG, state, options, command)
|
|
6009
|
+
);
|
|
6010
|
+
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));
|
|
6011
|
+
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
6012
|
var sessions = program.command("sessions").description("Session commands");
|
|
5632
6013
|
addCreateOptions(sessions.command("create").description("Create a session and send a prompt")).action(
|
|
5633
6014
|
(repoUrl, prompt, options, command) => createCommand(repoUrl, prompt, options, command)
|