codexuse-cli 2.1.0 → 2.2.0

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 CHANGED
@@ -8,9 +8,11 @@ npm install -g codexuse-cli
8
8
  Commands:
9
9
  ```bash
10
10
  codexuse profile list
11
+ codexuse profile list --no-usage
12
+ codexuse profile list --compact
11
13
  codexuse profile current
12
- codexuse profile add <name>
13
- codexuse profile refresh <name>
14
+ codexuse profile add <name> [--skip-login] [--device-auth] [--login=browser|device]
15
+ codexuse profile refresh <name> [--skip-login] [--device-auth] [--login=browser|device]
14
16
  codexuse profile switch <name>
15
17
  codexuse profile delete <name>
16
18
  codexuse profile rename <old> <new>
@@ -21,5 +23,9 @@ codexuse license activate <license-key>
21
23
 
22
24
  Notes:
23
25
  - Requires Codex CLI on PATH for `profile add` / `refresh` (runs `codex login`).
26
+ - `profile list` reads live rate-limit usage via Codex CLI by default.
27
+ - Use `--no-usage` to skip rate-limit fetch.
28
+ - Use `--compact` for names only.
29
+ - On headless/SSH, login defaults to device auth. Override with `--login=browser`.
24
30
  - Use `--skip-login` if you already ran `codex login`.
25
31
  - License state stored in `~/.codex/settings.json` (shared with desktop app).
package/dist/index.js CHANGED
@@ -747,6 +747,9 @@ function parseCodexCliChannel(value) {
747
747
  }
748
748
  return KNOWN_CHANNELS.has(normalized) ? normalized : null;
749
749
  }
750
+ function normalizeCodexCliChannel(value, fallback = "stable") {
751
+ return parseCodexCliChannel(value) ?? fallback;
752
+ }
750
753
 
751
754
  // ../../lib/codex-settings.ts
752
755
  var KEY_LAST_PROFILE_NAME = "last_profile_name";
@@ -1076,6 +1079,10 @@ async function persistLicense(license) {
1076
1079
  }
1077
1080
  await setCodexSettings(settings);
1078
1081
  }
1082
+ async function getCodexCliChannel() {
1083
+ const settings = await getCodexSettings();
1084
+ return normalizeCodexCliChannel(settings.cliChannel);
1085
+ }
1079
1086
 
1080
1087
  // ../../lib/profile-manager.ts
1081
1088
  var TOKEN_EXPIRING_SOON_WINDOW_MS = 15 * 60 * 1e3;
@@ -3216,9 +3223,30 @@ function buildCodexCommand(codexPath, args) {
3216
3223
  const useShell = process.platform === "win32";
3217
3224
  return { command: codexPath, args, shell: useShell };
3218
3225
  }
3219
- async function runCodexLogin() {
3226
+ function isHeadless() {
3227
+ if (process.env.CODEXUSE_HEADLESS === "1") return true;
3228
+ if (process.env.CI) return true;
3229
+ if (process.env.SSH_CONNECTION || process.env.SSH_TTY) return true;
3230
+ if (process.platform === "linux") {
3231
+ if (!process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) return true;
3232
+ }
3233
+ return false;
3234
+ }
3235
+ function resolveLoginMode(preferred) {
3236
+ if (preferred === "browser" || preferred === "device") {
3237
+ return preferred;
3238
+ }
3239
+ const envMode = process.env.CODEXUSE_LOGIN_MODE;
3240
+ if (envMode === "browser" || envMode === "device") {
3241
+ return envMode;
3242
+ }
3243
+ return isHeadless() ? "device" : "browser";
3244
+ }
3245
+ async function runCodexLogin(mode) {
3220
3246
  const codexPath = requireCodexBinary("Codex CLI is required to login.");
3221
- const { command, args, shell } = buildCodexCommand(codexPath, ["login"]);
3247
+ const resolvedMode = resolveLoginMode(mode ?? null);
3248
+ const loginArgs = resolvedMode === "device" ? ["login", "--device-auth"] : ["login"];
3249
+ const { command, args, shell } = buildCodexCommand(codexPath, loginArgs);
3222
3250
  const child = (0, import_node_child_process.spawn)(command, args, {
3223
3251
  stdio: "inherit",
3224
3252
  env: process.env,
@@ -3232,16 +3260,270 @@ async function runCodexLogin() {
3232
3260
  }
3233
3261
  }
3234
3262
 
3263
+ // ../../lib/codex-rpc.ts
3264
+ var import_node_child_process2 = require("child_process");
3265
+ var import_node_readline = __toESM(require("readline"));
3266
+ var import_node_events = require("events");
3267
+ var import_node_fs6 = require("fs");
3268
+ var import_node_os = __toESM(require("os"));
3269
+ var import_node_path6 = __toESM(require("path"));
3270
+
3271
+ // ../../lib/codex-cli.ts
3272
+ var import_node_fs5 = require("fs");
3273
+ var import_node_path5 = __toESM(require("path"));
3274
+ var cachedStatus = null;
3275
+ var cachedChannel = null;
3276
+ function fileExists2(candidate) {
3277
+ if (!candidate) {
3278
+ return null;
3279
+ }
3280
+ const normalized = import_node_path5.default.resolve(candidate);
3281
+ try {
3282
+ const stats = (0, import_node_fs5.statSync)(normalized);
3283
+ if (stats.isFile()) {
3284
+ return normalized;
3285
+ }
3286
+ } catch {
3287
+ }
3288
+ return null;
3289
+ }
3290
+ function resolveBundledCodexBinary(channel) {
3291
+ const candidates = [];
3292
+ const processWithResources = process;
3293
+ const resourcesPath = processWithResources.resourcesPath;
3294
+ const packageSegments = channel === "alpha" ? ["@openai", "codex-alpha"] : ["@openai", "codex"];
3295
+ if (resourcesPath) {
3296
+ candidates.push(
3297
+ import_node_path5.default.join(resourcesPath, "app.asar.unpacked", "node_modules", ...packageSegments, "bin", "codex.js")
3298
+ );
3299
+ candidates.push(
3300
+ import_node_path5.default.join(resourcesPath, "app.asar.unpacked", "node_modules", ...packageSegments, "bin", "codex")
3301
+ );
3302
+ }
3303
+ const projectRoot = process.cwd();
3304
+ candidates.push(import_node_path5.default.join(projectRoot, "node_modules", ...packageSegments, "bin", "codex.js"));
3305
+ candidates.push(import_node_path5.default.join(projectRoot, "node_modules", ...packageSegments, "bin", "codex"));
3306
+ for (const candidate of candidates) {
3307
+ const resolved = fileExists2(candidate);
3308
+ if (resolved) {
3309
+ return resolved;
3310
+ }
3311
+ }
3312
+ return null;
3313
+ }
3314
+ function resolveBundledCodexBinaryWithFallback(requestedChannel) {
3315
+ const direct = resolveBundledCodexBinary(requestedChannel);
3316
+ if (direct) {
3317
+ return { path: direct, resolvedChannel: requestedChannel, fallbackUsed: false };
3318
+ }
3319
+ if (requestedChannel === "alpha") {
3320
+ const stable = resolveBundledCodexBinary("stable");
3321
+ if (stable) {
3322
+ return { path: stable, resolvedChannel: "stable", fallbackUsed: true };
3323
+ }
3324
+ }
3325
+ return { path: null, resolvedChannel: null, fallbackUsed: false };
3326
+ }
3327
+ function buildUnavailableStatus(channel) {
3328
+ const channelLabel = channel === "alpha" ? "alpha " : "";
3329
+ const channelSuffix = channel === "alpha" ? " Switch to Stable in Settings or reinstall CodexUse to restore the CLI." : " Reinstall CodexUse to restore the CLI.";
3330
+ return {
3331
+ available: false,
3332
+ path: null,
3333
+ reason: `Bundled ${channelLabel}Codex CLI is missing.${channelSuffix}`,
3334
+ source: null,
3335
+ channel,
3336
+ requestedChannel: channel,
3337
+ fallbackUsed: false
3338
+ };
3339
+ }
3340
+ function evaluateCodexCliStatus(requestedChannel) {
3341
+ const resolved = resolveBundledCodexBinaryWithFallback(requestedChannel);
3342
+ if (resolved.path && resolved.resolvedChannel) {
3343
+ return {
3344
+ available: true,
3345
+ path: resolved.path,
3346
+ reason: null,
3347
+ source: "bundled",
3348
+ channel: resolved.resolvedChannel,
3349
+ requestedChannel,
3350
+ fallbackUsed: resolved.fallbackUsed
3351
+ };
3352
+ }
3353
+ return buildUnavailableStatus(requestedChannel);
3354
+ }
3355
+ async function refreshCodexStatus() {
3356
+ const channel = await getCodexCliChannel();
3357
+ cachedChannel = channel;
3358
+ cachedStatus = evaluateCodexCliStatus(channel);
3359
+ return { ...cachedStatus };
3360
+ }
3361
+ var CodexCliMissingError = class extends Error {
3362
+ constructor(status, message) {
3363
+ super(message ?? status.reason ?? "Codex CLI is not available");
3364
+ Object.setPrototypeOf(this, new.target.prototype);
3365
+ this.name = "CodexCliMissingError";
3366
+ this.status = { ...status };
3367
+ }
3368
+ };
3369
+ async function requireCodexCli() {
3370
+ const status = await refreshCodexStatus();
3371
+ if (!status.available || !status.path) {
3372
+ throw new CodexCliMissingError(status);
3373
+ }
3374
+ return status.path;
3375
+ }
3376
+
3377
+ // ../../lib/codex-rpc.ts
3378
+ var RPC_TIMEOUT_MS = 1e4;
3379
+ async function sendPayload(child, payload) {
3380
+ child.stdin?.write(JSON.stringify(payload));
3381
+ child.stdin?.write("\n");
3382
+ }
3383
+ async function readMessage(rl, timeoutMs) {
3384
+ const timeout = new Promise((_, reject) => {
3385
+ const timer = setTimeout(() => {
3386
+ clearTimeout(timer);
3387
+ reject(new Error("codex RPC timed out"));
3388
+ }, timeoutMs);
3389
+ });
3390
+ const linePromise = (0, import_node_events.once)(rl, "line").then((args) => args[0]);
3391
+ const line = await Promise.race([linePromise, timeout]);
3392
+ try {
3393
+ return JSON.parse(line);
3394
+ } catch {
3395
+ throw new Error("codex RPC returned malformed JSON");
3396
+ }
3397
+ }
3398
+ function toWindow(rpc) {
3399
+ if (!rpc) return void 0;
3400
+ const usedPercent = typeof rpc.usedPercent === "number" && Number.isFinite(rpc.usedPercent) ? rpc.usedPercent : void 0;
3401
+ const windowMinutes = typeof rpc.windowDurationMins === "number" && Number.isFinite(rpc.windowDurationMins) ? rpc.windowDurationMins : void 0;
3402
+ let resetsAt;
3403
+ let resetsInSeconds;
3404
+ if (typeof rpc.resetsAt === "number" && Number.isFinite(rpc.resetsAt)) {
3405
+ const ms = rpc.resetsAt > 1e10 ? rpc.resetsAt : rpc.resetsAt * 1e3;
3406
+ resetsAt = new Date(ms).toISOString();
3407
+ resetsInSeconds = Math.max(0, Math.round((ms - Date.now()) / 1e3));
3408
+ }
3409
+ if (typeof usedPercent === "undefined" && typeof windowMinutes === "undefined" && typeof resetsAt === "undefined") {
3410
+ return void 0;
3411
+ }
3412
+ const window = {};
3413
+ if (typeof usedPercent === "number") {
3414
+ window.usedPercent = usedPercent;
3415
+ }
3416
+ if (typeof windowMinutes === "number") {
3417
+ window.windowMinutes = windowMinutes;
3418
+ }
3419
+ if (typeof resetsAt === "string") {
3420
+ window.resetsAt = resetsAt;
3421
+ }
3422
+ if (typeof resetsInSeconds === "number") {
3423
+ window.resetsInSeconds = resetsInSeconds;
3424
+ }
3425
+ return window;
3426
+ }
3427
+ async function fetchRateLimitsViaRpc(envOverride, options = {}) {
3428
+ const binaryPath = options.codexPath ?? await requireCodexCli();
3429
+ const tempHome = await import_node_fs6.promises.mkdtemp(import_node_path6.default.join(import_node_os.default.tmpdir(), "codex-rpc-"));
3430
+ const sourceAuthPath = options.authPath ?? (envOverride?.CODEX_HOME ? import_node_path6.default.join(envOverride.CODEX_HOME, "auth.json") : import_node_path6.default.join(
3431
+ envOverride?.HOME ?? process.env.HOME ?? process.env.USERPROFILE ?? import_node_os.default.homedir(),
3432
+ ".codex",
3433
+ "auth.json"
3434
+ ));
3435
+ try {
3436
+ const authContent = await import_node_fs6.promises.readFile(sourceAuthPath, "utf8").catch(() => null);
3437
+ if (!authContent) {
3438
+ return null;
3439
+ }
3440
+ await import_node_fs6.promises.writeFile(import_node_path6.default.join(tempHome, "auth.json"), authContent, "utf8");
3441
+ } catch {
3442
+ await import_node_fs6.promises.rm(tempHome, { recursive: true, force: true }).catch(() => {
3443
+ });
3444
+ return null;
3445
+ }
3446
+ const child = (0, import_node_child_process2.spawn)(process.execPath, [binaryPath, "-s", "read-only", "-a", "untrusted", "app-server"], {
3447
+ stdio: ["pipe", "pipe", "pipe"],
3448
+ env: {
3449
+ ...process.env,
3450
+ ...envOverride ?? {},
3451
+ HOME: tempHome,
3452
+ USERPROFILE: tempHome,
3453
+ CODEX_HOME: tempHome,
3454
+ CODEX_TELEMETRY_LABEL: "codex-rpc",
3455
+ ELECTRON_RUN_AS_NODE: "1"
3456
+ }
3457
+ });
3458
+ const rl = import_node_readline.default.createInterface({
3459
+ input: child.stdout,
3460
+ crlfDelay: Infinity
3461
+ });
3462
+ try {
3463
+ await sendPayload(child, {
3464
+ id: 1,
3465
+ method: "initialize",
3466
+ params: { clientInfo: { name: "codexuse", version: "0.0.0" } }
3467
+ });
3468
+ await readMessage(rl, RPC_TIMEOUT_MS);
3469
+ await sendPayload(child, { method: "initialized", params: {} });
3470
+ await sendPayload(child, { id: 2, method: "account/rateLimits/read", params: {} });
3471
+ const message = await readMessage(rl, RPC_TIMEOUT_MS);
3472
+ const result = message.result;
3473
+ const limits = result?.rateLimits;
3474
+ const primary = toWindow(limits?.primary ?? null);
3475
+ const secondary = toWindow(limits?.secondary ?? null);
3476
+ if (!primary && !secondary) {
3477
+ return null;
3478
+ }
3479
+ const snapshot = {
3480
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
3481
+ source: "codex-rpc"
3482
+ };
3483
+ if (primary) {
3484
+ snapshot.primary = primary;
3485
+ }
3486
+ if (secondary) {
3487
+ snapshot.secondary = secondary;
3488
+ }
3489
+ return snapshot;
3490
+ } finally {
3491
+ child.kill();
3492
+ rl.close();
3493
+ await import_node_fs6.promises.rm(tempHome, { recursive: true, force: true }).catch(() => {
3494
+ });
3495
+ }
3496
+ }
3497
+
3498
+ // ../../lib/rate-limit-notifier.ts
3499
+ var DEFAULT_APPROACH_COOLDOWN_MS = 30 * 60 * 1e3;
3500
+ var DEFAULT_RESET_COOLDOWN_MS = 5 * 60 * 1e3;
3501
+ var APPROACH_RENOTIFY_FLOOR_MS = 2 * 60 * 1e3;
3502
+ var RESET_WINDOW_BUCKET_MS = 5 * 60 * 1e3;
3503
+ function maxUsedPercent(snapshot) {
3504
+ if (!snapshot) {
3505
+ return null;
3506
+ }
3507
+ const candidates = [
3508
+ snapshot.primary?.usedPercent,
3509
+ snapshot.secondary?.usedPercent
3510
+ ].filter((value) => typeof value === "number" && Number.isFinite(value));
3511
+ if (candidates.length === 0) {
3512
+ return null;
3513
+ }
3514
+ return Math.max(...candidates);
3515
+ }
3516
+
3235
3517
  // src/index.ts
3236
- var VERSION = true ? "2.1.0" : "0.0.0";
3518
+ var VERSION = true ? "2.2.0" : "0.0.0";
3237
3519
  function printHelp() {
3238
3520
  console.log(`CodexUse CLI v${VERSION}
3239
3521
 
3240
3522
  Usage:
3241
- codexuse profile list
3523
+ codexuse profile list [--no-usage] [--compact]
3242
3524
  codexuse profile current
3243
- codexuse profile add <name> [--skip-login]
3244
- codexuse profile refresh <name> [--skip-login]
3525
+ codexuse profile add <name> [--skip-login] [--device-auth] [--login=browser|device]
3526
+ codexuse profile refresh <name> [--skip-login] [--device-auth] [--login=browser|device]
3245
3527
  codexuse profile switch <name>
3246
3528
  codexuse profile delete <name>
3247
3529
  codexuse profile rename <old> <new>
@@ -3252,6 +3534,10 @@ Usage:
3252
3534
  Flags:
3253
3535
  -h, --help Show help
3254
3536
  -v, --version Show version
3537
+ --no-usage Skip rate-limit usage fetch
3538
+ --compact Names only
3539
+ --device-auth Use device auth for Codex login
3540
+ --login=MODE Login mode: browser | device
3255
3541
  `);
3256
3542
  }
3257
3543
  function hasFlag(args, flag) {
@@ -3260,12 +3546,238 @@ function hasFlag(args, flag) {
3260
3546
  function stripFlags(args) {
3261
3547
  return args.filter((arg) => !arg.startsWith("-"));
3262
3548
  }
3549
+ function parseLoginMode(flags) {
3550
+ if (flags.includes("--device-auth")) return "device";
3551
+ const explicit = flags.find((flag) => flag.startsWith("--login="));
3552
+ if (!explicit) return null;
3553
+ const value = explicit.split("=")[1];
3554
+ if (value === "browser" || value === "device") {
3555
+ return value;
3556
+ }
3557
+ return null;
3558
+ }
3263
3559
  function formatProfileLabel(name, displayName) {
3264
3560
  if (displayName && displayName.trim() && displayName !== name) {
3265
3561
  return `${displayName} (${name})`;
3266
3562
  }
3267
3563
  return name;
3268
3564
  }
3565
+ function toTitleCase(value) {
3566
+ return value.replace(/\w\S*/g, (word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase());
3567
+ }
3568
+ function formatShortDate(value) {
3569
+ if (!value) return null;
3570
+ const date = new Date(value);
3571
+ if (Number.isNaN(date.getTime())) return null;
3572
+ if (typeof Intl !== "undefined" && Intl.DateTimeFormat) {
3573
+ return new Intl.DateTimeFormat(void 0, { month: "short", day: "numeric" }).format(date);
3574
+ }
3575
+ return date.toLocaleDateString();
3576
+ }
3577
+ function formatRelativeTime(value) {
3578
+ if (!value) return null;
3579
+ const parsed = Date.parse(value);
3580
+ if (Number.isNaN(parsed)) return null;
3581
+ const diffMs = parsed - Date.now();
3582
+ const absMs = Math.abs(diffMs);
3583
+ if (absMs < 6e4) {
3584
+ return null;
3585
+ }
3586
+ const minutes = Math.round(absMs / 6e4);
3587
+ if (minutes < 60) {
3588
+ return diffMs >= 0 ? `in ${minutes}m` : `${minutes}m ago`;
3589
+ }
3590
+ const hours = Math.round(minutes / 60);
3591
+ if (hours < 24) {
3592
+ return diffMs >= 0 ? `in ${hours}h` : `${hours}h ago`;
3593
+ }
3594
+ const days = Math.round(hours / 24);
3595
+ return diffMs >= 0 ? `in ${days}d` : `${days}d ago`;
3596
+ }
3597
+ function formatOrganizations(organizations) {
3598
+ if (!organizations || !Array.isArray(organizations) || organizations.length === 0) {
3599
+ return null;
3600
+ }
3601
+ const parts = organizations.map((org) => {
3602
+ if (!org) return null;
3603
+ const name = org.title || org.id;
3604
+ if (!name) return null;
3605
+ return org.role ? `${name} (${org.role})` : name;
3606
+ }).filter((value) => Boolean(value));
3607
+ return parts.length > 0 ? parts.join(", ") : null;
3608
+ }
3609
+ function formatWindowLabel(windowMinutes) {
3610
+ if (typeof windowMinutes !== "number" || Number.isNaN(windowMinutes)) {
3611
+ return null;
3612
+ }
3613
+ if (windowMinutes >= 2880) {
3614
+ const days = windowMinutes / 1440;
3615
+ const rounded = Math.round(days * 10) / 10;
3616
+ const display = Number.isInteger(rounded) ? rounded.toFixed(0) : rounded.toFixed(1);
3617
+ return `${display.replace(/\.0$/, "")}d window`;
3618
+ }
3619
+ if (windowMinutes >= 60) {
3620
+ const hours = windowMinutes / 60;
3621
+ const rounded = Math.round(hours * 10) / 10;
3622
+ const display = Number.isInteger(rounded) ? rounded.toFixed(0) : rounded.toFixed(1);
3623
+ return `${display.replace(/\.0$/, "")}h window`;
3624
+ }
3625
+ const minutes = Math.round(windowMinutes);
3626
+ return `${minutes}m window`;
3627
+ }
3628
+ function formatDurationFromSeconds(seconds) {
3629
+ if (typeof seconds !== "number" || Number.isNaN(seconds)) {
3630
+ return null;
3631
+ }
3632
+ if (seconds <= 0) {
3633
+ return "now";
3634
+ }
3635
+ const minutes = Math.floor(seconds / 60);
3636
+ if (minutes === 0) {
3637
+ return `${Math.max(1, Math.round(seconds))}s`;
3638
+ }
3639
+ if (minutes < 60) {
3640
+ return `${minutes}m`;
3641
+ }
3642
+ const hours = Math.floor(minutes / 60);
3643
+ const remainingMinutes = minutes % 60;
3644
+ if (hours < 24) {
3645
+ return remainingMinutes ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
3646
+ }
3647
+ const days = Math.floor(hours / 24);
3648
+ const remainingHours = hours % 24;
3649
+ return remainingHours ? `${days}d ${remainingHours}h` : `${days}d`;
3650
+ }
3651
+ function formatResetText(window) {
3652
+ if (window.resetsAt) {
3653
+ return formatRelativeTime(window.resetsAt) ?? window.resetsAt;
3654
+ }
3655
+ const durationText = formatDurationFromSeconds(window.resetsInSeconds);
3656
+ if (!durationText) return null;
3657
+ if (durationText === "now") return "now";
3658
+ return `in ${durationText}`;
3659
+ }
3660
+ function formatWindowUsage(window) {
3661
+ const label = formatWindowLabel(window.windowMinutes) ?? "window";
3662
+ const usedPercent = typeof window.usedPercent === "number" && Number.isFinite(window.usedPercent) ? `${Math.round(Math.max(0, Math.min(100, window.usedPercent)))}%` : "n/a";
3663
+ const resetText = formatResetText(window);
3664
+ return {
3665
+ label,
3666
+ used: usedPercent,
3667
+ reset: resetText ? `reset ${resetText}` : null
3668
+ };
3669
+ }
3670
+ function fallbackUsage(profile) {
3671
+ if (!profile.isValid) {
3672
+ return { summary: "invalid", rows: null };
3673
+ }
3674
+ if (profile.tokenStatus?.requiresUserAction) {
3675
+ return { summary: "auth required", rows: null };
3676
+ }
3677
+ return { summary: "n/a", rows: null };
3678
+ }
3679
+ async function resolveProfileUsage(manager, profile, codexPath) {
3680
+ if (!profile.isValid || profile.tokenStatus?.requiresUserAction) {
3681
+ return fallbackUsage(profile);
3682
+ }
3683
+ try {
3684
+ const snapshot = await manager.runWithProfileAuth(
3685
+ profile.name,
3686
+ (env) => fetchRateLimitsViaRpc(env, { codexPath })
3687
+ );
3688
+ if (!snapshot) {
3689
+ return { summary: "n/a", rows: null };
3690
+ }
3691
+ const rows = [];
3692
+ if (snapshot.primary) {
3693
+ const row = formatWindowUsage(snapshot.primary);
3694
+ if (row) rows.push(row);
3695
+ }
3696
+ if (snapshot.secondary) {
3697
+ const row = formatWindowUsage(snapshot.secondary);
3698
+ if (row) rows.push(row);
3699
+ }
3700
+ const maxPercent = maxUsedPercent(snapshot);
3701
+ const summary = typeof maxPercent === "number" ? `${Math.round(maxPercent)}%` : "n/a";
3702
+ return { summary, rows: rows.length > 0 ? rows : null };
3703
+ } catch {
3704
+ return { summary: "n/a", rows: null };
3705
+ }
3706
+ }
3707
+ function formatPlan(profile) {
3708
+ const metadata = profile.metadata;
3709
+ if (!metadata) {
3710
+ return "No plan details";
3711
+ }
3712
+ const parts = [];
3713
+ if (metadata.planType) {
3714
+ parts.push(toTitleCase(metadata.planType));
3715
+ }
3716
+ const until = metadata.subscription?.activeUntil;
3717
+ if (until) {
3718
+ const formatted = formatShortDate(until);
3719
+ if (formatted) {
3720
+ parts.push(`Until ${formatted}`);
3721
+ }
3722
+ }
3723
+ return parts.join(" \xB7 ") || "No plan details";
3724
+ }
3725
+ function formatAuth(profile) {
3726
+ const metadata = profile.metadata;
3727
+ const relative = formatRelativeTime(metadata?.tokenAuthTime);
3728
+ if (relative) return relative;
3729
+ if (metadata?.tokenAuthTime) return metadata.tokenAuthTime;
3730
+ return "Not authenticated yet";
3731
+ }
3732
+ function printProfileDetails(profile, usage) {
3733
+ const email = profile.email ?? "Unknown";
3734
+ const plan = formatPlan(profile);
3735
+ const orgs = formatOrganizations(profile.metadata?.organizations) ?? "No organizations";
3736
+ const auth = formatAuth(profile);
3737
+ const workspace = profile.workspaceName ?? profile.workspaceId ?? null;
3738
+ const entries = [
3739
+ { label: "email", value: email },
3740
+ { label: "plan", value: plan },
3741
+ { label: "orgs", value: orgs },
3742
+ { label: "auth", value: auth }
3743
+ ];
3744
+ if (workspace) {
3745
+ entries.push({ label: "workspace", value: workspace });
3746
+ }
3747
+ const pad = Math.max(...entries.map((entry) => entry.label.length));
3748
+ for (const entry of entries) {
3749
+ console.log(` ${entry.label.padEnd(pad)} : ${entry.value}`);
3750
+ }
3751
+ if (usage) {
3752
+ if (usage.rows && usage.rows.length > 0) {
3753
+ const labelPad = Math.max(...usage.rows.map((row) => row.label.length));
3754
+ const usedPad = Math.max(...usage.rows.map((row) => row.used.length));
3755
+ console.log(` rate limits:`);
3756
+ for (const row of usage.rows) {
3757
+ const resetText = row.reset ? ` ${row.reset}` : "";
3758
+ console.log(` ${row.label.padEnd(labelPad)} ${row.used.padStart(usedPad)}${resetText}`);
3759
+ }
3760
+ return;
3761
+ }
3762
+ console.log(` rate limits: ${usage.summary}`);
3763
+ }
3764
+ }
3765
+ async function mapWithConcurrency(items, limit, fn) {
3766
+ const results = new Array(items.length);
3767
+ const workerCount = Math.max(1, Math.min(limit, items.length));
3768
+ let nextIndex = 0;
3769
+ const workers = Array.from({ length: workerCount }, async () => {
3770
+ while (true) {
3771
+ const current = nextIndex++;
3772
+ if (current >= items.length) {
3773
+ return;
3774
+ }
3775
+ results[current] = await fn(items[current], current);
3776
+ }
3777
+ });
3778
+ await Promise.all(workers);
3779
+ return results;
3780
+ }
3269
3781
  async function handleProfile(args) {
3270
3782
  const flags = args.filter((arg) => arg.startsWith("-"));
3271
3783
  const params = stripFlags(args);
@@ -3283,10 +3795,41 @@ async function handleProfile(args) {
3283
3795
  console.log("No profiles found.");
3284
3796
  return;
3285
3797
  }
3286
- for (const profile of profiles) {
3798
+ const compact = hasFlag(flags, "--compact");
3799
+ const withUsage = !hasFlag(flags, "--no-usage");
3800
+ let usageMap = null;
3801
+ if (withUsage) {
3802
+ usageMap = /* @__PURE__ */ new Map();
3803
+ const codexPath = resolveCodexBinary();
3804
+ const codexAvailable = Boolean(codexPath);
3805
+ if (!codexAvailable || !codexPath) {
3806
+ for (const profile of profiles) {
3807
+ usageMap.set(profile.name, { summary: "codex cli missing", rows: null });
3808
+ }
3809
+ } else {
3810
+ const limitEnv = Number.parseInt(process.env.CODEXUSE_CLI_USAGE_CONCURRENCY ?? "3", 10);
3811
+ const limit = Number.isFinite(limitEnv) && limitEnv > 0 ? limitEnv : 3;
3812
+ const results = await mapWithConcurrency(
3813
+ profiles,
3814
+ limit,
3815
+ (profile) => resolveProfileUsage(manager, profile, codexPath)
3816
+ );
3817
+ for (const [index, profile] of profiles.entries()) {
3818
+ usageMap.set(profile.name, results[index]);
3819
+ }
3820
+ }
3821
+ }
3822
+ for (const [index, profile] of profiles.entries()) {
3287
3823
  const marker = current.name === profile.name ? "*" : " ";
3288
3824
  const label = formatProfileLabel(profile.name, profile.displayName);
3289
- console.log(`${marker} ${label}`);
3825
+ if (index > 0) {
3826
+ console.log("");
3827
+ }
3828
+ const activeLabel = current.name === profile.name ? " (active)" : "";
3829
+ console.log(`${marker} ${label}${activeLabel}`);
3830
+ if (compact) continue;
3831
+ const usage = usageMap?.get(profile.name) ?? null;
3832
+ printProfileDetails(profile, usage);
3290
3833
  }
3291
3834
  return;
3292
3835
  }
@@ -3306,7 +3849,8 @@ async function handleProfile(args) {
3306
3849
  }
3307
3850
  await assertProfileCreationAllowed(manager);
3308
3851
  if (!hasFlag(flags, "--skip-login")) {
3309
- await runCodexLogin();
3852
+ const loginMode = resolveLoginMode(parseLoginMode(flags));
3853
+ await runCodexLogin(loginMode);
3310
3854
  }
3311
3855
  const profile = await manager.createProfile(name);
3312
3856
  const label = profile ? formatProfileLabel(profile.name, profile.displayName) : name;
@@ -3319,7 +3863,8 @@ async function handleProfile(args) {
3319
3863
  throw new Error("Profile name is required.");
3320
3864
  }
3321
3865
  if (!hasFlag(flags, "--skip-login")) {
3322
- await runCodexLogin();
3866
+ const loginMode = resolveLoginMode(parseLoginMode(flags));
3867
+ await runCodexLogin(loginMode);
3323
3868
  }
3324
3869
  const profile = await manager.refreshProfileAuth(name);
3325
3870
  const label = formatProfileLabel(profile.name, profile.displayName);