@tryarcanist/cli 0.1.187 → 0.1.189

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 +149 -3
  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
@@ -1086,9 +1086,12 @@ var AUTOMATION_ERROR_HINTS = {
1086
1086
  invalid_model: "Model is unknown or not selectable for its backend. Run `arcanist automations create --help` for allowed models.",
1087
1087
  repo_not_available: "Check that the token owner has GitHub access and Arcanist is installed for that repo.",
1088
1088
  duplicate_rule: "An enabled automation already exists for this repo, cron, and prompt.",
1089
+ invalid_skill: "Use a leading slash skill that exists in the selected repo, for example: /audit-prod-docs",
1089
1090
  rule_cap_reached: "Delete or disable an existing automation before creating another one. The cap is 20 enabled rules.",
1090
1091
  installation_unresolved: "Reconnect or reinstall the GitHub integration for that repository, then retry.",
1091
- model_not_available: "That model's backend (Claude/opencode) is limited to Arcanist team businesses; use the codex default or a codex model."
1092
+ model_not_available: "That model's backend (Claude/opencode) is limited to Arcanist team businesses; use the codex default or a codex model.",
1093
+ repo_skills_unavailable: "Arcanist could not verify the repo's skills right now. Retry once GitHub skill discovery is healthy.",
1094
+ unknown_skill: "That leading slash skill does not exist in the selected repository."
1092
1095
  };
1093
1096
  var MAX_ALL_PAGES = 1e3;
1094
1097
  async function createAutomationCommand(repoUrl, promptArg, options, command) {
@@ -1246,6 +1249,133 @@ function formatTime(value) {
1246
1249
  return typeof value === "number" ? new Date(value).toISOString() : "none";
1247
1250
  }
1248
1251
 
1252
+ // src/commands/codex.ts
1253
+ import { spawn } from "child_process";
1254
+ import { mkdtemp, readFile, rm } from "fs/promises";
1255
+ import { tmpdir } from "os";
1256
+ import { join as join2 } from "path";
1257
+ var CODEX_SUBSCRIPTION_PATH = "/api/settings/codex-subscription";
1258
+ var CODEX_SUBSCRIPTION_AUTH_JSON_PATH = "/api/settings/codex-subscription/auth-json";
1259
+ var CODEX_SUBSCRIPTION_ENABLED_PATH = "/api/settings/codex-subscription/enabled";
1260
+ function resolveCodexPath(optionPath) {
1261
+ return optionPath?.trim() || process.env.ARCANIST_CODEX_BIN?.trim() || "codex";
1262
+ }
1263
+ function runCodexDeviceLogin(codexPath, codexHome) {
1264
+ return new Promise((resolve2, reject) => {
1265
+ const child = spawn(codexPath, ["login", "--device-auth"], {
1266
+ stdio: "inherit",
1267
+ env: { ...process.env, CODEX_HOME: codexHome }
1268
+ });
1269
+ child.on("error", (err) => {
1270
+ if (err.code === "ENOENT") {
1271
+ reject(
1272
+ new CliError("user", `Could not find the \`${codexPath}\` executable.`, {
1273
+ hint: "Install the Codex CLI, or point at it with --codex-path <path> or ARCANIST_CODEX_BIN."
1274
+ })
1275
+ );
1276
+ return;
1277
+ }
1278
+ reject(new CliError("user", `Failed to launch \`${codexPath} login --device-auth\`: ${err.message}`));
1279
+ });
1280
+ child.on("close", (code) => {
1281
+ if (code === 0) {
1282
+ resolve2();
1283
+ return;
1284
+ }
1285
+ reject(
1286
+ new CliError("user", `\`${codexPath} login --device-auth\` exited with code ${code ?? "unknown"}.`, {
1287
+ hint: "Complete the device approval in your browser, then re-run `arcanist codex login`."
1288
+ })
1289
+ );
1290
+ });
1291
+ });
1292
+ }
1293
+ async function setCodexSubscriptionEnabled(config, enabled) {
1294
+ return apiFetch(config, CODEX_SUBSCRIPTION_ENABLED_PATH, {
1295
+ method: "PUT",
1296
+ body: JSON.stringify({ enabled })
1297
+ });
1298
+ }
1299
+ async function codexLoginCommand(options, command) {
1300
+ const { config } = resolveBusinessContext(command, options);
1301
+ const codexPath = resolveCodexPath(options.codexPath);
1302
+ const codexHome = await mkdtemp(join2(tmpdir(), "arcanist-codex-"));
1303
+ try {
1304
+ await runCodexDeviceLogin(codexPath, codexHome);
1305
+ let authJson;
1306
+ try {
1307
+ authJson = await readFile(join2(codexHome, "auth.json"), "utf8");
1308
+ } catch {
1309
+ throw new CliError("user", "Codex login completed but no auth.json was written.", {
1310
+ hint: "Verify `codex login --device-auth` succeeds on its own, then re-run `arcanist codex login`."
1311
+ });
1312
+ }
1313
+ if (!authJson.trim()) {
1314
+ throw new CliError("user", "Codex login produced an empty auth.json.");
1315
+ }
1316
+ const state = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
1317
+ method: "PUT",
1318
+ body: JSON.stringify({ authJson })
1319
+ });
1320
+ let activated = false;
1321
+ let activationError;
1322
+ try {
1323
+ await setCodexSubscriptionEnabled(config, true);
1324
+ activated = true;
1325
+ } catch (err) {
1326
+ activationError = err instanceof Error ? err.message : String(err);
1327
+ }
1328
+ emit(command, options, { ...state, useCodexSubscription: activated }, (payload) => {
1329
+ console.log(
1330
+ activated ? "Codex subscription auth saved and activated for OpenAI sessions." : "Codex subscription auth saved."
1331
+ );
1332
+ if (payload.lastValidationStatus) console.log(`Status: ${payload.lastValidationStatus}`);
1333
+ if (!activated) {
1334
+ console.log(`Could not activate it automatically${activationError ? ` (${activationError})` : ""}.`);
1335
+ console.log("Run `arcanist codex use on` to start using it.");
1336
+ }
1337
+ });
1338
+ } finally {
1339
+ await rm(codexHome, { recursive: true, force: true }).catch(() => {
1340
+ });
1341
+ }
1342
+ }
1343
+ async function codexUseCommand(state, options, command) {
1344
+ const normalized = state.trim().toLowerCase();
1345
+ if (normalized !== "on" && normalized !== "off") {
1346
+ throw new CliError("user", "Usage: arcanist codex use <on|off>");
1347
+ }
1348
+ const enabled = normalized === "on";
1349
+ const { config } = resolveBusinessContext(command, options);
1350
+ const result = await setCodexSubscriptionEnabled(config, enabled);
1351
+ emit(
1352
+ command,
1353
+ options,
1354
+ result,
1355
+ () => console.log(
1356
+ result.useCodexSubscription ? "Codex subscription auth is now used for OpenAI sessions." : "Codex subscription auth is no longer used for OpenAI sessions."
1357
+ )
1358
+ );
1359
+ }
1360
+ async function codexStatusCommand(options, command) {
1361
+ const { config } = resolveBusinessContext(command, options);
1362
+ const payload = await apiFetch(config, CODEX_SUBSCRIPTION_PATH);
1363
+ emit(command, options, payload, (state) => {
1364
+ console.log(`Eligible: ${state.eligible ? "yes" : "no"}`);
1365
+ console.log(`Auth.json saved: ${state.credential.isSet ? "yes" : "no"}`);
1366
+ if (state.credential.lastValidationStatus) console.log(`Status: ${state.credential.lastValidationStatus}`);
1367
+ });
1368
+ }
1369
+ async function codexLogoutCommand(options, command) {
1370
+ const { config } = resolveBusinessContext(command, options);
1371
+ await setCodexSubscriptionEnabled(config, false).catch(() => {
1372
+ });
1373
+ const payload = await apiFetch(config, CODEX_SUBSCRIPTION_AUTH_JSON_PATH, {
1374
+ method: "DELETE"
1375
+ });
1376
+ emit(command, options, payload, () => console.log("Codex subscription auth deactivated and cleared."));
1377
+ }
1378
+
1249
1379
  // ../../shared/utils/timing.ts
1250
1380
  function sleep(ms) {
1251
1381
  return new Promise((resolve2) => setTimeout(resolve2, ms));
@@ -1297,7 +1427,7 @@ function isWatchTerminal(phase) {
1297
1427
  }
1298
1428
 
1299
1429
  // src/uploads.ts
1300
- import { readFile } from "fs/promises";
1430
+ import { readFile as readFile2 } from "fs/promises";
1301
1431
  import { basename, extname } from "path";
1302
1432
 
1303
1433
  // ../../shared/constants/uploads.ts
@@ -1400,7 +1530,7 @@ async function resolveUploadedFileOptions(files) {
1400
1530
  paths.map(async (path) => {
1401
1531
  const name = basename(path);
1402
1532
  try {
1403
- return { name, content: await readFile(path, "utf8") };
1533
+ return { name, content: await readFile2(path, "utf8") };
1404
1534
  } catch (err) {
1405
1535
  const message = stringifyError(err);
1406
1536
  throw new CliError("user", `Failed to read uploaded file ${path}: ${message}`);
@@ -5350,6 +5480,22 @@ Examples:
5350
5480
  ARCANIST_TOKEN=arc_... arcanist auth whoami --json
5351
5481
  `
5352
5482
  ).action((options, command) => whoamiCommand(options, command));
5483
+ var codex = program.command("codex").description("Codex subscription (bring-your-own-subscription) commands");
5484
+ 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(
5485
+ "after",
5486
+ `
5487
+ Runs the Codex CLI device-authorization login locally under a temporary CODEX_HOME, then uploads
5488
+ the resulting auth.json to Arcanist (encrypted, per user) and activates it. Your workspace must have
5489
+ Codex subscription auth enabled. The credential is never written to your default ~/.codex.
5490
+
5491
+ Examples:
5492
+ arcanist codex login
5493
+ arcanist codex login --codex-path /usr/local/bin/codex
5494
+ `
5495
+ ).action((options, command) => codexLoginCommand(options, command));
5496
+ 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));
5497
+ codex.command("status").description("Show whether your workspace is eligible and whether a Codex subscription auth is saved").action((options, command) => codexStatusCommand(options, command));
5498
+ codex.command("logout").description("Deactivate the selector and remove the stored Codex subscription auth for your user").action((options, command) => codexLogoutCommand(options, command));
5353
5499
  var sessions = program.command("sessions").description("Session commands");
5354
5500
  addCreateOptions(sessions.command("create").description("Create a session and send a prompt")).action(
5355
5501
  (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.189",
4
4
  "description": "CLI for Arcanist — create and manage coding agent sessions",
5
5
  "type": "module",
6
6
  "bin": {