@tryarcanist/cli 0.1.186 → 0.1.188
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/README.md +39 -0
- package/dist/index.js +219 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -137,6 +137,45 @@ ARCANIST_TOKEN=arc_... arcanist auth whoami --json
|
|
|
137
137
|
|
|
138
138
|
JSON mode prints the raw `/api/auth/whoami` API response. Fields currently include `userId`, `email`, `tokenId`, `tokenScope`, and `authMode`.
|
|
139
139
|
|
|
140
|
+
### `arcanist codex login`
|
|
141
|
+
|
|
142
|
+
Authenticates a Codex/ChatGPT subscription and stores it for your Codex sessions, then activates it (turns on "use subscription auth for OpenAI sessions"). Runs the Codex CLI device-authorization login locally under a temporary `CODEX_HOME`, then uploads the resulting `auth.json` to Arcanist (encrypted, per user). The credential is never written to your default `~/.codex`. Requires a write-scoped token and a workspace with Codex subscription auth enabled. If activation fails the credential is still saved; run `arcanist codex use on`.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
arcanist codex login
|
|
146
|
+
arcanist codex login --codex-path /usr/local/bin/codex
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`--codex-path <path>` overrides the `codex` executable used (default: `codex` on `PATH`, or `ARCANIST_CODEX_BIN`).
|
|
150
|
+
|
|
151
|
+
### `arcanist codex use <on|off>`
|
|
152
|
+
|
|
153
|
+
Turns using your saved Codex subscription auth for OpenAI sessions on or off, without changing the stored credential. `arcanist codex login` turns it on automatically; use this to toggle it later.
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
arcanist codex use on
|
|
157
|
+
arcanist codex use off
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `arcanist codex status`
|
|
161
|
+
|
|
162
|
+
Shows whether your workspace is eligible for Codex subscription auth and whether an `auth.json` is currently saved.
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
arcanist codex status
|
|
166
|
+
arcanist codex status --json
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
JSON mode returns `{eligible, credential: {isSet, lastValidationStatus?, lastValidationReasonCode?}}`.
|
|
170
|
+
|
|
171
|
+
### `arcanist codex logout`
|
|
172
|
+
|
|
173
|
+
Deactivates the selector and removes the stored Codex subscription auth for your user (write-scoped token).
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
arcanist codex logout
|
|
177
|
+
```
|
|
178
|
+
|
|
140
179
|
### `arcanist sessions create <repo-url> [prompt]`
|
|
141
180
|
|
|
142
181
|
Creates a new session and sends the initial prompt.
|
package/dist/index.js
CHANGED
|
@@ -221,9 +221,9 @@ import { randomUUID } from "crypto";
|
|
|
221
221
|
import { createInterface } from "readline/promises";
|
|
222
222
|
|
|
223
223
|
// src/config.ts
|
|
224
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
224
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
225
225
|
import { homedir } from "os";
|
|
226
|
-
import { dirname, join } from "path";
|
|
226
|
+
import { dirname, join, resolve } from "path";
|
|
227
227
|
var CONFIG_DIR = join(homedir(), ".arcanist");
|
|
228
228
|
var CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
229
229
|
var PROJECT_CONFIG_FILE = ".arcanist-cli.json";
|
|
@@ -330,12 +330,34 @@ function validateAndNormalizeConfig(config) {
|
|
|
330
330
|
function findGitRoot(cwd) {
|
|
331
331
|
let current = cwd;
|
|
332
332
|
while (true) {
|
|
333
|
-
if (
|
|
333
|
+
if (hasGitMetadata(current)) return current;
|
|
334
334
|
const parent = dirname(current);
|
|
335
335
|
if (parent === current) return null;
|
|
336
336
|
current = parent;
|
|
337
337
|
}
|
|
338
338
|
}
|
|
339
|
+
function hasGitMetadata(repoPath2) {
|
|
340
|
+
const gitPath = join(repoPath2, ".git");
|
|
341
|
+
if (!existsSync(gitPath)) return false;
|
|
342
|
+
try {
|
|
343
|
+
const gitStat = statSync(gitPath);
|
|
344
|
+
if (gitStat.isDirectory()) {
|
|
345
|
+
return existsSync(join(gitPath, "HEAD")) && existsSync(join(gitPath, "config"));
|
|
346
|
+
}
|
|
347
|
+
if (!gitStat.isFile()) return false;
|
|
348
|
+
const gitDir = readGitDirPointer(gitPath);
|
|
349
|
+
if (!gitDir) return false;
|
|
350
|
+
return existsSync(join(resolve(repoPath2, gitDir), "HEAD"));
|
|
351
|
+
} catch {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
function readGitDirPointer(gitFilePath) {
|
|
356
|
+
const gitDirLine = readFileSync(gitFilePath, "utf-8").split(/\r?\n/).find((line) => line.startsWith("gitdir:"));
|
|
357
|
+
if (!gitDirLine) return null;
|
|
358
|
+
const gitDir = gitDirLine.slice("gitdir:".length).trim();
|
|
359
|
+
return gitDir || null;
|
|
360
|
+
}
|
|
339
361
|
function parseProjectConfig(configPath) {
|
|
340
362
|
let parsed;
|
|
341
363
|
try {
|
|
@@ -434,7 +456,7 @@ async function readHiddenPrompt(prompt) {
|
|
|
434
456
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
435
457
|
throw new CliError("user", "No interactive terminal available. Re-run with --token-stdin or set ARCANIST_TOKEN.");
|
|
436
458
|
}
|
|
437
|
-
return new Promise((
|
|
459
|
+
return new Promise((resolve2, reject) => {
|
|
438
460
|
const stdin = process.stdin;
|
|
439
461
|
const inputChars = [];
|
|
440
462
|
let ansiCarry = "";
|
|
@@ -450,7 +472,7 @@ async function readHiddenPrompt(prompt) {
|
|
|
450
472
|
settled = true;
|
|
451
473
|
cleanup();
|
|
452
474
|
process.stdout.write("\n");
|
|
453
|
-
|
|
475
|
+
resolve2(inputChars.join(""));
|
|
454
476
|
};
|
|
455
477
|
const fail2 = (error) => {
|
|
456
478
|
if (settled) return;
|
|
@@ -605,6 +627,10 @@ function asNonEmptyString(value) {
|
|
|
605
627
|
|
|
606
628
|
// ../../shared/constants/models.ts
|
|
607
629
|
var OpenAIModel = {
|
|
630
|
+
GPT56: "gpt-5.6",
|
|
631
|
+
GPT56Sol: "gpt-5.6-sol",
|
|
632
|
+
GPT56Terra: "gpt-5.6-terra",
|
|
633
|
+
GPT56Luna: "gpt-5.6-luna",
|
|
608
634
|
GPT55: "gpt-5.5",
|
|
609
635
|
GPT54: "gpt-5.4",
|
|
610
636
|
GPT54Pro: "gpt-5.4-pro",
|
|
@@ -654,6 +680,48 @@ var MODEL_REGISTRY = [
|
|
|
654
680
|
},
|
|
655
681
|
sessionStart: { eligible: true, isDefault: true }
|
|
656
682
|
},
|
|
683
|
+
{
|
|
684
|
+
id: OpenAIModel.GPT56,
|
|
685
|
+
name: "GPT-5.6",
|
|
686
|
+
provider: "openai",
|
|
687
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
688
|
+
contextWindow: 105e4,
|
|
689
|
+
// Verified 2026-07-09 against OpenAI's GPT-5.6 migration guide and model
|
|
690
|
+
// catalog: the alias routes to gpt-5.6-sol and supports max effort.
|
|
691
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
|
|
692
|
+
costTracked: false,
|
|
693
|
+
sessionStart: { eligible: true }
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
id: OpenAIModel.GPT56Sol,
|
|
697
|
+
name: "GPT-5.6 Sol",
|
|
698
|
+
provider: "openai",
|
|
699
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
700
|
+
contextWindow: 105e4,
|
|
701
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
|
|
702
|
+
costTracked: false,
|
|
703
|
+
sessionStart: { eligible: true }
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
id: OpenAIModel.GPT56Terra,
|
|
707
|
+
name: "GPT-5.6 Terra",
|
|
708
|
+
provider: "openai",
|
|
709
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
710
|
+
contextWindow: 105e4,
|
|
711
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
|
|
712
|
+
costTracked: false,
|
|
713
|
+
sessionStart: { eligible: true }
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
id: OpenAIModel.GPT56Luna,
|
|
717
|
+
name: "GPT-5.6 Luna",
|
|
718
|
+
provider: "openai",
|
|
719
|
+
backends: [CODEX_AGENT_RUNTIME_BACKEND],
|
|
720
|
+
contextWindow: 105e4,
|
|
721
|
+
reasoning: { efforts: ["none", "low", "medium", "high", "xhigh", "max"], default: "medium" },
|
|
722
|
+
costTracked: false,
|
|
723
|
+
sessionStart: { eligible: true }
|
|
724
|
+
},
|
|
657
725
|
{
|
|
658
726
|
id: OpenAIModel.GPT55,
|
|
659
727
|
name: "GPT-5.5",
|
|
@@ -1178,9 +1246,136 @@ function formatTime(value) {
|
|
|
1178
1246
|
return typeof value === "number" ? new Date(value).toISOString() : "none";
|
|
1179
1247
|
}
|
|
1180
1248
|
|
|
1249
|
+
// src/commands/codex.ts
|
|
1250
|
+
import { spawn } from "child_process";
|
|
1251
|
+
import { mkdtemp, readFile, rm } from "fs/promises";
|
|
1252
|
+
import { tmpdir } from "os";
|
|
1253
|
+
import { join as join2 } from "path";
|
|
1254
|
+
var CODEX_SUBSCRIPTION_PATH = "/api/settings/codex-subscription";
|
|
1255
|
+
var CODEX_SUBSCRIPTION_AUTH_JSON_PATH = "/api/settings/codex-subscription/auth-json";
|
|
1256
|
+
var CODEX_SUBSCRIPTION_ENABLED_PATH = "/api/settings/codex-subscription/enabled";
|
|
1257
|
+
function resolveCodexPath(optionPath) {
|
|
1258
|
+
return optionPath?.trim() || process.env.ARCANIST_CODEX_BIN?.trim() || "codex";
|
|
1259
|
+
}
|
|
1260
|
+
function runCodexDeviceLogin(codexPath, codexHome) {
|
|
1261
|
+
return new Promise((resolve2, reject) => {
|
|
1262
|
+
const child = spawn(codexPath, ["login", "--device-auth"], {
|
|
1263
|
+
stdio: "inherit",
|
|
1264
|
+
env: { ...process.env, CODEX_HOME: codexHome }
|
|
1265
|
+
});
|
|
1266
|
+
child.on("error", (err) => {
|
|
1267
|
+
if (err.code === "ENOENT") {
|
|
1268
|
+
reject(
|
|
1269
|
+
new CliError("user", `Could not find the \`${codexPath}\` executable.`, {
|
|
1270
|
+
hint: "Install the Codex CLI, or point at it with --codex-path <path> or ARCANIST_CODEX_BIN."
|
|
1271
|
+
})
|
|
1272
|
+
);
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
reject(new CliError("user", `Failed to launch \`${codexPath} login --device-auth\`: ${err.message}`));
|
|
1276
|
+
});
|
|
1277
|
+
child.on("close", (code) => {
|
|
1278
|
+
if (code === 0) {
|
|
1279
|
+
resolve2();
|
|
1280
|
+
return;
|
|
1281
|
+
}
|
|
1282
|
+
reject(
|
|
1283
|
+
new CliError("user", `\`${codexPath} login --device-auth\` exited with code ${code ?? "unknown"}.`, {
|
|
1284
|
+
hint: "Complete the device approval in your browser, then re-run `arcanist codex login`."
|
|
1285
|
+
})
|
|
1286
|
+
);
|
|
1287
|
+
});
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
async function setCodexSubscriptionEnabled(config, enabled) {
|
|
1291
|
+
return apiFetch(config, CODEX_SUBSCRIPTION_ENABLED_PATH, {
|
|
1292
|
+
method: "PUT",
|
|
1293
|
+
body: JSON.stringify({ enabled })
|
|
1294
|
+
});
|
|
1295
|
+
}
|
|
1296
|
+
async function codexLoginCommand(options, command) {
|
|
1297
|
+
const { config } = resolveBusinessContext(command, options);
|
|
1298
|
+
const codexPath = resolveCodexPath(options.codexPath);
|
|
1299
|
+
const codexHome = await mkdtemp(join2(tmpdir(), "arcanist-codex-"));
|
|
1300
|
+
try {
|
|
1301
|
+
await runCodexDeviceLogin(codexPath, codexHome);
|
|
1302
|
+
let authJson;
|
|
1303
|
+
try {
|
|
1304
|
+
authJson = await readFile(join2(codexHome, "auth.json"), "utf8");
|
|
1305
|
+
} catch {
|
|
1306
|
+
throw new CliError("user", "Codex login completed but no auth.json was written.", {
|
|
1307
|
+
hint: "Verify `codex login --device-auth` succeeds on its own, then re-run `arcanist codex login`."
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
if (!authJson.trim()) {
|
|
1311
|
+
throw new CliError("user", "Codex login produced an empty auth.json.");
|
|
1312
|
+
}
|
|
1313
|
+
const state = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
|
|
1314
|
+
method: "PUT",
|
|
1315
|
+
body: JSON.stringify({ authJson })
|
|
1316
|
+
});
|
|
1317
|
+
let activated = false;
|
|
1318
|
+
let activationError;
|
|
1319
|
+
try {
|
|
1320
|
+
await setCodexSubscriptionEnabled(config, true);
|
|
1321
|
+
activated = true;
|
|
1322
|
+
} catch (err) {
|
|
1323
|
+
activationError = err instanceof Error ? err.message : String(err);
|
|
1324
|
+
}
|
|
1325
|
+
emit(command, options, { ...state, useCodexSubscription: activated }, (payload) => {
|
|
1326
|
+
console.log(
|
|
1327
|
+
activated ? "Codex subscription auth saved and activated for OpenAI sessions." : "Codex subscription auth saved."
|
|
1328
|
+
);
|
|
1329
|
+
if (payload.lastValidationStatus) console.log(`Status: ${payload.lastValidationStatus}`);
|
|
1330
|
+
if (!activated) {
|
|
1331
|
+
console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
|
|
1332
|
+
console.log("Run `arcanist codex use on` to start using it.");
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
} finally {
|
|
1336
|
+
await rm(codexHome, { recursive: true, force: true }).catch(() => {
|
|
1337
|
+
});
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
async function codexUseCommand(state, options, command) {
|
|
1341
|
+
const normalized = state.trim().toLowerCase();
|
|
1342
|
+
if (normalized !== "on" && normalized !== "off") {
|
|
1343
|
+
throw new CliError("user", "Usage: arcanist codex use <on|off>");
|
|
1344
|
+
}
|
|
1345
|
+
const enabled = normalized === "on";
|
|
1346
|
+
const { config } = resolveBusinessContext(command, options);
|
|
1347
|
+
const result = await setCodexSubscriptionEnabled(config, enabled);
|
|
1348
|
+
emit(
|
|
1349
|
+
command,
|
|
1350
|
+
options,
|
|
1351
|
+
result,
|
|
1352
|
+
() => console.log(
|
|
1353
|
+
result.useCodexSubscription ? "Codex subscription auth is now used for OpenAI sessions." : "Codex subscription auth is no longer used for OpenAI sessions."
|
|
1354
|
+
)
|
|
1355
|
+
);
|
|
1356
|
+
}
|
|
1357
|
+
async function codexStatusCommand(options, command) {
|
|
1358
|
+
const { config } = resolveBusinessContext(command, options);
|
|
1359
|
+
const payload = await apiFetch(config, CODEX_SUBSCRIPTION_PATH);
|
|
1360
|
+
emit(command, options, payload, (state) => {
|
|
1361
|
+
console.log(`Eligible: ${state.eligible ? "yes" : "no"}`);
|
|
1362
|
+
console.log(`Auth.json saved: ${state.credential.isSet ? "yes" : "no"}`);
|
|
1363
|
+
if (state.credential.lastValidationStatus) console.log(`Status: ${state.credential.lastValidationStatus}`);
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
async function codexLogoutCommand(options, command) {
|
|
1367
|
+
const { config } = resolveBusinessContext(command, options);
|
|
1368
|
+
await setCodexSubscriptionEnabled(config, false).catch(() => {
|
|
1369
|
+
});
|
|
1370
|
+
const payload = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
|
|
1371
|
+
method: "DELETE"
|
|
1372
|
+
});
|
|
1373
|
+
emit(command, options, payload, () => console.log("Codex subscription auth deactivated and cleared."));
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1181
1376
|
// ../../shared/utils/timing.ts
|
|
1182
1377
|
function sleep(ms) {
|
|
1183
|
-
return new Promise((
|
|
1378
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1184
1379
|
}
|
|
1185
1380
|
|
|
1186
1381
|
// ../../shared/session/phase.ts
|
|
@@ -1229,7 +1424,7 @@ function isWatchTerminal(phase) {
|
|
|
1229
1424
|
}
|
|
1230
1425
|
|
|
1231
1426
|
// src/uploads.ts
|
|
1232
|
-
import { readFile } from "fs/promises";
|
|
1427
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1233
1428
|
import { basename, extname } from "path";
|
|
1234
1429
|
|
|
1235
1430
|
// ../../shared/constants/uploads.ts
|
|
@@ -1332,7 +1527,7 @@ async function resolveUploadedFileOptions(files) {
|
|
|
1332
1527
|
paths.map(async (path) => {
|
|
1333
1528
|
const name = basename(path);
|
|
1334
1529
|
try {
|
|
1335
|
-
return { name, content: await
|
|
1530
|
+
return { name, content: await readFile2(path, "utf8") };
|
|
1336
1531
|
} catch (err) {
|
|
1337
1532
|
const message = stringifyError(err);
|
|
1338
1533
|
throw new CliError("user", `Failed to read uploaded file ${path}: ${message}`);
|
|
@@ -5282,6 +5477,22 @@ Examples:
|
|
|
5282
5477
|
ARCANIST_TOKEN=arc_... arcanist auth whoami --json
|
|
5283
5478
|
`
|
|
5284
5479
|
).action((options, command) => whoamiCommand(options, command));
|
|
5480
|
+
var codex = program.command("codex").description("Codex subscription (bring-your-own-subscription) commands");
|
|
5481
|
+
codex.command("login").description("Authenticate a Codex/ChatGPT subscription and store it for your Codex sessions").option("--codex-path <path>", "Path to the codex executable (default: codex on PATH, or ARCANIST_CODEX_BIN)").addHelpText(
|
|
5482
|
+
"after",
|
|
5483
|
+
`
|
|
5484
|
+
Runs the Codex CLI device-authorization login locally under a temporary CODEX_HOME, then uploads
|
|
5485
|
+
the resulting auth.json to Arcanist (encrypted, per user) and activates it. Your workspace must have
|
|
5486
|
+
Codex subscription auth enabled. The credential is never written to your default ~/.codex.
|
|
5487
|
+
|
|
5488
|
+
Examples:
|
|
5489
|
+
arcanist codex login
|
|
5490
|
+
arcanist codex login --codex-path /usr/local/bin/codex
|
|
5491
|
+
`
|
|
5492
|
+
).action((options, command) => codexLoginCommand(options, command));
|
|
5493
|
+
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));
|
|
5494
|
+
codex.command("status").description("Show whether your workspace is eligible and whether a Codex subscription auth is saved").action((options, command) => codexStatusCommand(options, command));
|
|
5495
|
+
codex.command("logout").description("Deactivate the selector and remove the stored Codex subscription auth for your user").action((options, command) => codexLogoutCommand(options, command));
|
|
5285
5496
|
var sessions = program.command("sessions").description("Session commands");
|
|
5286
5497
|
addCreateOptions(sessions.command("create").description("Create a session and send a prompt")).action(
|
|
5287
5498
|
(repoUrl, prompt, options, command) => createCommand(repoUrl, prompt, options, command)
|