@tryarcanist/cli 0.1.187 → 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 +145 -2
- 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
|
@@ -1246,6 +1246,133 @@ function formatTime(value) {
|
|
|
1246
1246
|
return typeof value === "number" ? new Date(value).toISOString() : "none";
|
|
1247
1247
|
}
|
|
1248
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
|
+
|
|
1249
1376
|
// ../../shared/utils/timing.ts
|
|
1250
1377
|
function sleep(ms) {
|
|
1251
1378
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
@@ -1297,7 +1424,7 @@ function isWatchTerminal(phase) {
|
|
|
1297
1424
|
}
|
|
1298
1425
|
|
|
1299
1426
|
// src/uploads.ts
|
|
1300
|
-
import { readFile } from "fs/promises";
|
|
1427
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1301
1428
|
import { basename, extname } from "path";
|
|
1302
1429
|
|
|
1303
1430
|
// ../../shared/constants/uploads.ts
|
|
@@ -1400,7 +1527,7 @@ async function resolveUploadedFileOptions(files) {
|
|
|
1400
1527
|
paths.map(async (path) => {
|
|
1401
1528
|
const name = basename(path);
|
|
1402
1529
|
try {
|
|
1403
|
-
return { name, content: await
|
|
1530
|
+
return { name, content: await readFile2(path, "utf8") };
|
|
1404
1531
|
} catch (err) {
|
|
1405
1532
|
const message = stringifyError(err);
|
|
1406
1533
|
throw new CliError("user", `Failed to read uploaded file ${path}: ${message}`);
|
|
@@ -5350,6 +5477,22 @@ Examples:
|
|
|
5350
5477
|
ARCANIST_TOKEN=arc_... arcanist auth whoami --json
|
|
5351
5478
|
`
|
|
5352
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));
|
|
5353
5496
|
var sessions = program.command("sessions").description("Session commands");
|
|
5354
5497
|
addCreateOptions(sessions.command("create").description("Create a session and send a prompt")).action(
|
|
5355
5498
|
(repoUrl, prompt, options, command) => createCommand(repoUrl, prompt, options, command)
|