@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.
Files changed (3) hide show
  1. package/README.md +39 -0
  2. package/dist/index.js +145 -2
  3. 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 readFile(path, "utf8") };
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryarcanist/cli",
3
- "version": "0.1.187",
3
+ "version": "0.1.188",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {