codexuse-cli 2.1.0 → 2.3.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 +8 -2
- package/dist/index.js +555 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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.
|
|
3518
|
+
var VERSION = true ? "2.3.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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|