@juspay/neurolink 12.7.2 → 12.7.4
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/CHANGELOG.md +2 -2
- package/dist/browser/neurolink.min.js +422 -414
- package/dist/cli/commands/auth.js +252 -125
- package/dist/cli/commands/proxy.js +115 -75
- package/dist/cli/factories/authCommandFactory.js +8 -11
- package/dist/proxy/codexAccountUsage.d.ts +5 -0
- package/dist/proxy/codexAccountUsage.js +30 -0
- package/dist/proxy/codexFallback.d.ts +26 -0
- package/dist/proxy/codexFallback.js +371 -0
- package/dist/proxy/proxyActivity.js +12 -1
- package/dist/proxy/usageStats.d.ts +2 -2
- package/dist/proxy/usageStats.js +66 -10
- package/dist/server/routes/claudeProxyRoutes.d.ts +25 -2
- package/dist/server/routes/claudeProxyRoutes.js +413 -108
- package/dist/server/routes/codexProxyRoutes.d.ts +10 -2
- package/dist/server/routes/codexProxyRoutes.js +192 -59
- package/dist/types/claudeProxy.d.ts +16 -0
- package/dist/types/claudeProxy.js +4 -0
- package/dist/types/cli.d.ts +37 -5
- package/dist/types/codex.d.ts +62 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/proxy.d.ts +37 -0
- package/package.json +1 -1
|
@@ -63,8 +63,10 @@ const ANTHROPIC_CONSOLE_OAUTH_CONFIG = {
|
|
|
63
63
|
// API key creation endpoint
|
|
64
64
|
createApiKeyUrl: "https://api.anthropic.com/api/oauth/claude_cli/create_api_key",
|
|
65
65
|
};
|
|
66
|
-
//
|
|
67
|
-
|
|
66
|
+
// Providers with first-class credential flows in `neurolink auth`. This is
|
|
67
|
+
// intentionally narrower than the CLI's full AI-provider catalog: `auth list`
|
|
68
|
+
// renders every provider-qualified account stored by the CLI.
|
|
69
|
+
const AUTH_LOGIN_PROVIDERS = ["anthropic", "codex"];
|
|
68
70
|
// =============================================================================
|
|
69
71
|
// SUBCOMMAND HANDLERS
|
|
70
72
|
// =============================================================================
|
|
@@ -79,8 +81,8 @@ export async function handleLogin(argv) {
|
|
|
79
81
|
try {
|
|
80
82
|
const provider = argv.provider?.toLowerCase();
|
|
81
83
|
// Validate provider
|
|
82
|
-
if (!
|
|
83
|
-
logger.error(chalk.red(`Unsupported provider: ${provider}. Supported: ${
|
|
84
|
+
if (!AUTH_LOGIN_PROVIDERS.includes(provider)) {
|
|
85
|
+
logger.error(chalk.red(`Unsupported provider: ${provider}. Supported: ${AUTH_LOGIN_PROVIDERS.join(", ")}`));
|
|
84
86
|
process.exit(1);
|
|
85
87
|
}
|
|
86
88
|
// Codex has a dedicated import-based login path (imports the current
|
|
@@ -294,13 +296,148 @@ export function formatQuotaWindowRows(quota) {
|
|
|
294
296
|
/**
|
|
295
297
|
* Fetch fresh limits for `auth list --refresh`.
|
|
296
298
|
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
299
|
+
* Every provider-qualified token-store account receives an explicit result.
|
|
300
|
+
* The running proxy is authoritative for provider adapters that declare proxy
|
|
301
|
+
* support, or for namespaces it returns. Registered direct adapters fill in
|
|
302
|
+
* the remaining namespaces. Providers with no quota adapter are shown as
|
|
303
|
+
* `not_supported`, never silently omitted.
|
|
301
304
|
*/
|
|
302
|
-
|
|
305
|
+
function providerFromAccountKey(key) {
|
|
306
|
+
const separator = key.indexOf(":");
|
|
307
|
+
const provider = (separator === -1 ? key : key.slice(0, separator))
|
|
308
|
+
.trim()
|
|
309
|
+
.toLowerCase();
|
|
310
|
+
return provider || "unknown";
|
|
311
|
+
}
|
|
312
|
+
const DIRECT_QUOTA_REFRESH_CONCURRENCY = 3;
|
|
313
|
+
// Provider-specific protocol knowledge is kept behind this capability map.
|
|
314
|
+
// The CLI result itself remains provider-generic: adding a new adapter only
|
|
315
|
+
// requires registering it here, while every other configured provider still
|
|
316
|
+
// receives an explicit `not_supported` result.
|
|
317
|
+
const PROVIDER_QUOTA_ADAPTERS = {
|
|
318
|
+
anthropic: {
|
|
319
|
+
supportsProxyRefresh: true,
|
|
320
|
+
listAccounts: () => listAnthropicAccountsForUsage(),
|
|
321
|
+
priorQuotaKeys: (account) => [account.key, account.label],
|
|
322
|
+
async refreshAccount(account, { prior }) {
|
|
323
|
+
if (account.type !== "oauth") {
|
|
324
|
+
return { status: "not_supported" };
|
|
325
|
+
}
|
|
326
|
+
const result = await fetchAccountUsage(account);
|
|
327
|
+
if (result.ok === false) {
|
|
328
|
+
return { status: "unavailable", error: result.error };
|
|
329
|
+
}
|
|
330
|
+
const quota = usageToQuota(result.usage, { now: Date.now(), prior });
|
|
331
|
+
if (!quota) {
|
|
332
|
+
return {
|
|
333
|
+
status: "unavailable",
|
|
334
|
+
error: "usage payload had no recognizable limit windows",
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
return { status: "refreshed", quota };
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
codex: {
|
|
341
|
+
listAccounts: () => listCodexAccountsForUsage(),
|
|
342
|
+
priorQuotaKeys: (account) => [account.key],
|
|
343
|
+
async refreshAccount(account) {
|
|
344
|
+
if (account.type !== "oauth") {
|
|
345
|
+
return { status: "not_supported" };
|
|
346
|
+
}
|
|
347
|
+
const result = await fetchCodexAccountUsage(account);
|
|
348
|
+
if (result.ok === false) {
|
|
349
|
+
return result.reason === "not_oauth"
|
|
350
|
+
? { status: "not_supported" }
|
|
351
|
+
: { status: "unavailable", error: `usage ${result.reason}` };
|
|
352
|
+
}
|
|
353
|
+
return { status: "refreshed", quota: result.quota };
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
async function refreshDirectProviderLimits(options) {
|
|
358
|
+
const { provider, adapter, accountKeys, prior, quotas, errors, accountResults, setAccountResult, } = options;
|
|
359
|
+
let accounts;
|
|
360
|
+
try {
|
|
361
|
+
accounts = await adapter.listAccounts();
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
365
|
+
for (const key of accountKeys) {
|
|
366
|
+
if (providerFromAccountKey(key) === provider) {
|
|
367
|
+
setAccountResult(key, "unavailable", message);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
errors.push(`${provider} accounts: ${message}`);
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
let attemptedDirectRefresh = false;
|
|
374
|
+
let nextIndex = 0;
|
|
375
|
+
const worker = async () => {
|
|
376
|
+
for (;;) {
|
|
377
|
+
const index = nextIndex++;
|
|
378
|
+
if (index >= accounts.length) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
const account = accounts[index];
|
|
382
|
+
if (accountResults[account.key]?.status === "refreshed" ||
|
|
383
|
+
accountResults[account.key]?.status === "snapshot") {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (account.type === "oauth") {
|
|
387
|
+
attemptedDirectRefresh = true;
|
|
388
|
+
}
|
|
389
|
+
try {
|
|
390
|
+
const priorQuota = adapter
|
|
391
|
+
.priorQuotaKeys(account)
|
|
392
|
+
.map((key) => prior[key])
|
|
393
|
+
.find((quota) => quota !== undefined) ?? null;
|
|
394
|
+
const result = await adapter.refreshAccount(account, {
|
|
395
|
+
prior: priorQuota,
|
|
396
|
+
});
|
|
397
|
+
if (result.status === "refreshed") {
|
|
398
|
+
await saveAccountQuota(account.key, result.quota);
|
|
399
|
+
quotas[account.key] = result.quota;
|
|
400
|
+
setAccountResult(account.key, "refreshed");
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
setAccountResult(account.key, result.status, result.error);
|
|
404
|
+
if (result.status === "unavailable" && result.error) {
|
|
405
|
+
errors.push(`${provider}:${account.label}: ${result.error}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
410
|
+
setAccountResult(account.key, "unavailable", message);
|
|
411
|
+
errors.push(`${provider}:${account.label}: ${message}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
await Promise.all(Array.from({
|
|
416
|
+
length: Math.min(DIRECT_QUOTA_REFRESH_CONCURRENCY, accounts.length || 1),
|
|
417
|
+
}, () => worker()));
|
|
418
|
+
return attemptedDirectRefresh;
|
|
419
|
+
}
|
|
420
|
+
async function refreshAccountLimitsForList(accountKeys) {
|
|
303
421
|
const errors = [];
|
|
422
|
+
const quotas = {};
|
|
423
|
+
const accountResults = {};
|
|
424
|
+
const proxyRefreshedProviders = new Set();
|
|
425
|
+
let refreshedViaProxy = false;
|
|
426
|
+
let refreshedDirectly = false;
|
|
427
|
+
const setAccountResult = (key, status, error) => {
|
|
428
|
+
accountResults[key] = {
|
|
429
|
+
provider: providerFromAccountKey(key),
|
|
430
|
+
status,
|
|
431
|
+
...(error ? { error } : {}),
|
|
432
|
+
};
|
|
433
|
+
};
|
|
434
|
+
// `auth list` supports arbitrary provider-prefixed token-store entries.
|
|
435
|
+
// Providers with no quota adapter stay visible as explicitly unsupported
|
|
436
|
+
// rather than being rendered as an unexplained unavailable account.
|
|
437
|
+
for (const accountKey of accountKeys) {
|
|
438
|
+
setAccountResult(accountKey, "not_supported");
|
|
439
|
+
}
|
|
440
|
+
const configuredProviders = new Set(accountKeys.map((accountKey) => providerFromAccountKey(accountKey)));
|
|
304
441
|
const proxyState = detectRunningProxyState();
|
|
305
442
|
if (proxyState?.port) {
|
|
306
443
|
const host = proxyState.host && proxyState.host !== "0.0.0.0"
|
|
@@ -312,113 +449,81 @@ async function refreshAccountLimitsForList() {
|
|
|
312
449
|
});
|
|
313
450
|
if (response.ok) {
|
|
314
451
|
const payload = (await response.json());
|
|
315
|
-
const
|
|
452
|
+
for (const provider of configuredProviders) {
|
|
453
|
+
if (PROVIDER_QUOTA_ADAPTERS[provider]?.supportsProxyRefresh) {
|
|
454
|
+
proxyRefreshedProviders.add(provider);
|
|
455
|
+
refreshedViaProxy = true;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
316
458
|
for (const result of payload.results) {
|
|
459
|
+
// The proxy already returns the canonical key. Keep the fallback for
|
|
460
|
+
// a pre-provider-key proxy during a rolling package transition.
|
|
461
|
+
const key = result.key || `anthropic:${result.account}`;
|
|
462
|
+
proxyRefreshedProviders.add(providerFromAccountKey(key));
|
|
463
|
+
refreshedViaProxy = true;
|
|
317
464
|
if (result.quota) {
|
|
318
|
-
quotas[
|
|
465
|
+
quotas[key] = result.quota;
|
|
319
466
|
}
|
|
467
|
+
setAccountResult(key, result.status === "refreshed"
|
|
468
|
+
? "refreshed"
|
|
469
|
+
: result.quota
|
|
470
|
+
? "snapshot"
|
|
471
|
+
: result.status === "skipped_api_key"
|
|
472
|
+
? "not_supported"
|
|
473
|
+
: "unavailable", result.error);
|
|
320
474
|
if (result.status === "error" && result.error) {
|
|
321
|
-
errors.push(`${result.account}: ${result.error}`);
|
|
475
|
+
errors.push(`${providerFromAccountKey(key)}:${result.account}: ${result.error}`);
|
|
322
476
|
}
|
|
323
477
|
}
|
|
324
|
-
return { via: "proxy", quotas, errors };
|
|
325
478
|
}
|
|
326
|
-
|
|
479
|
+
if (!response.ok) {
|
|
480
|
+
errors.push(`running proxy /limits returned HTTP ${response.status}; trying direct quota adapters`);
|
|
481
|
+
}
|
|
327
482
|
}
|
|
328
483
|
catch {
|
|
329
484
|
// Keep the message generic: a raw fetch error can echo the requested
|
|
330
485
|
// URL, and this string reaches the text and JSON CLI output.
|
|
331
|
-
errors.push("running proxy /limits unreachable;
|
|
486
|
+
errors.push("running proxy /limits unreachable; trying direct quota adapters");
|
|
332
487
|
}
|
|
333
488
|
}
|
|
489
|
+
const prior = await loadAccountQuotas().catch(() => ({}));
|
|
334
490
|
try {
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
let nextIndex = 0;
|
|
340
|
-
const worker = async () => {
|
|
341
|
-
for (;;) {
|
|
342
|
-
const index = nextIndex++;
|
|
343
|
-
if (index >= accounts.length) {
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const account = accounts[index];
|
|
347
|
-
if (account.type !== "oauth") {
|
|
348
|
-
continue; // api_key accounts have no subscription windows
|
|
349
|
-
}
|
|
350
|
-
// Isolate failures per account: one rejection must not abort the
|
|
351
|
-
// Promise.all sweep or discard the other accounts' refreshed quotas.
|
|
352
|
-
try {
|
|
353
|
-
const result = await fetchAccountUsage(account);
|
|
354
|
-
if (!result.ok) {
|
|
355
|
-
errors.push(`${account.label}: ${result.error}`);
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
const quota = usageToQuota(result.usage, {
|
|
359
|
-
now: Date.now(),
|
|
360
|
-
prior: prior[account.label] ?? null,
|
|
361
|
-
});
|
|
362
|
-
if (!quota) {
|
|
363
|
-
errors.push(`${account.label}: usage payload had no recognizable limit windows`);
|
|
364
|
-
continue;
|
|
365
|
-
}
|
|
366
|
-
await saveAccountQuota(account.label, quota);
|
|
367
|
-
quotas[account.label] = quota;
|
|
368
|
-
}
|
|
369
|
-
catch (err) {
|
|
370
|
-
errors.push(`${account.label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
try {
|
|
375
|
-
await Promise.all(Array.from({ length: Math.min(CONCURRENCY, accounts.length || 1) }, () => worker()));
|
|
376
|
-
}
|
|
377
|
-
finally {
|
|
378
|
-
// The quota store's debounced flush timer is unref()'d and this process
|
|
379
|
-
// is short-lived — flush now (even on a partial sweep) or the completed
|
|
380
|
-
// saves never reach disk.
|
|
381
|
-
await flushAccountQuotas().catch(() => undefined);
|
|
382
|
-
}
|
|
383
|
-
// Codex accounts: fetch the ChatGPT usage windows. Keyed by the full
|
|
384
|
-
// `codex:` account key so quota never collides with an anthropic account
|
|
385
|
-
// that shares a bare label.
|
|
386
|
-
try {
|
|
387
|
-
const codexAccounts = await listCodexAccountsForUsage();
|
|
388
|
-
for (const account of codexAccounts) {
|
|
389
|
-
if (account.type !== "oauth") {
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
try {
|
|
393
|
-
const result = await fetchCodexAccountUsage(account);
|
|
394
|
-
if (!result.ok) {
|
|
395
|
-
errors.push(`${account.label}: codex usage ${result.reason}`);
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
await saveAccountQuota(account.key, result.quota);
|
|
399
|
-
quotas[account.key] = result.quota;
|
|
400
|
-
}
|
|
401
|
-
catch (err) {
|
|
402
|
-
errors.push(`${account.label}: ${err instanceof Error ? err.message : String(err)}`);
|
|
403
|
-
}
|
|
491
|
+
for (const [provider, adapter] of Object.entries(PROVIDER_QUOTA_ADAPTERS)) {
|
|
492
|
+
if (!configuredProviders.has(provider) ||
|
|
493
|
+
proxyRefreshedProviders.has(provider)) {
|
|
494
|
+
continue;
|
|
404
495
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
496
|
+
refreshedDirectly =
|
|
497
|
+
(await refreshDirectProviderLimits({
|
|
498
|
+
provider,
|
|
499
|
+
adapter,
|
|
500
|
+
accountKeys,
|
|
501
|
+
prior,
|
|
502
|
+
quotas,
|
|
503
|
+
errors,
|
|
504
|
+
accountResults,
|
|
505
|
+
setAccountResult,
|
|
506
|
+
})) || refreshedDirectly;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
finally {
|
|
510
|
+
// The quota store's debounced flush timer is unref()'d and this process is
|
|
511
|
+
// short-lived — flush every completed provider snapshot before returning.
|
|
512
|
+
await flushAccountQuotas().catch(() => undefined);
|
|
513
|
+
}
|
|
514
|
+
const via = refreshedViaProxy && refreshedDirectly
|
|
515
|
+
? "mixed"
|
|
516
|
+
: refreshedViaProxy
|
|
517
|
+
? "proxy"
|
|
518
|
+
: refreshedDirectly
|
|
519
|
+
? "direct"
|
|
520
|
+
: "none";
|
|
521
|
+
return {
|
|
522
|
+
via,
|
|
523
|
+
quotas: Object.keys(quotas).length > 0 ? quotas : null,
|
|
524
|
+
accounts: accountResults,
|
|
525
|
+
errors,
|
|
526
|
+
};
|
|
422
527
|
}
|
|
423
528
|
/**
|
|
424
529
|
* Handle the list subcommand
|
|
@@ -514,10 +619,10 @@ export async function handleList(argv) {
|
|
|
514
619
|
tokenType,
|
|
515
620
|
};
|
|
516
621
|
}));
|
|
517
|
-
// Optionally fetch
|
|
622
|
+
// Optionally fetch fresh provider limits before rendering.
|
|
518
623
|
let refreshOutcome;
|
|
519
624
|
if (argv.refresh) {
|
|
520
|
-
refreshOutcome = await refreshAccountLimitsForList();
|
|
625
|
+
refreshOutcome = await refreshAccountLimitsForList(allKeys);
|
|
521
626
|
}
|
|
522
627
|
// Load persisted quota data (captured from proxy responses), then overlay
|
|
523
628
|
// anything just refreshed — freshly fetched values win over the snapshot.
|
|
@@ -534,9 +639,19 @@ export async function handleList(argv) {
|
|
|
534
639
|
if (argv.format === "json") {
|
|
535
640
|
// Merge quota data into each account object for JSON output
|
|
536
641
|
const withQuota = enrichedAccounts.map((acct) => {
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
642
|
+
const quota = quotas[acct.key] ??
|
|
643
|
+
// Old Anthropic snapshots used bare labels. Read them once so an
|
|
644
|
+
// upgrade does not hide a useful historic reading, but all new
|
|
645
|
+
// writes use the provider-qualified key above.
|
|
646
|
+
(acct.provider === "anthropic" && acct.label
|
|
647
|
+
? quotas[acct.label]
|
|
648
|
+
: undefined) ??
|
|
649
|
+
null;
|
|
650
|
+
return {
|
|
651
|
+
...acct,
|
|
652
|
+
quota,
|
|
653
|
+
refresh: refreshOutcome?.accounts[acct.key] ?? null,
|
|
654
|
+
};
|
|
540
655
|
});
|
|
541
656
|
if (refreshOutcome) {
|
|
542
657
|
// --refresh envelopes the array so the fetch outcome travels with it.
|
|
@@ -544,6 +659,7 @@ export async function handleList(argv) {
|
|
|
544
659
|
refresh: {
|
|
545
660
|
via: refreshOutcome.via,
|
|
546
661
|
errors: refreshOutcome.errors,
|
|
662
|
+
accounts: refreshOutcome.accounts,
|
|
547
663
|
},
|
|
548
664
|
accounts: withQuota,
|
|
549
665
|
}, null, 2));
|
|
@@ -555,18 +671,21 @@ export async function handleList(argv) {
|
|
|
555
671
|
else {
|
|
556
672
|
if (refreshOutcome) {
|
|
557
673
|
if (refreshOutcome.via !== "none") {
|
|
558
|
-
logger.always(chalk.gray(`\nFetched fresh limits
|
|
674
|
+
logger.always(chalk.gray(`\nFetched fresh limits (${refreshOutcome.via === "proxy" ? "via running proxy" : refreshOutcome.via === "direct" ? "direct provider APIs" : "via the running proxy and direct provider APIs"}).`));
|
|
559
675
|
}
|
|
560
676
|
for (const refreshError of refreshOutcome.errors) {
|
|
561
677
|
logger.always(chalk.yellow(`⚠ ${refreshError}`));
|
|
562
678
|
}
|
|
563
679
|
}
|
|
564
680
|
logger.always(chalk.bold("\nAuthenticated Accounts:\n"));
|
|
565
|
-
//
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
681
|
+
// `--refresh` always shows the limit columns, including an explicit
|
|
682
|
+
// unavailable/not-supported state. A blank table silently looked like a
|
|
683
|
+
// healthy Codex account had no quota information.
|
|
684
|
+
const hasQuota = !!refreshOutcome ||
|
|
685
|
+
enrichedAccounts.some((acct) => quotas[acct.key] !== undefined ||
|
|
686
|
+
(acct.provider === "anthropic" && acct.label
|
|
687
|
+
? quotas[acct.label] !== undefined
|
|
688
|
+
: false));
|
|
570
689
|
// Table header
|
|
571
690
|
// Why an account is (or isn't) in the proxy pool. Without this a
|
|
572
691
|
// disabled or parked account simply vanishes from routing with no clue.
|
|
@@ -575,10 +694,10 @@ export async function handleList(argv) {
|
|
|
575
694
|
const colEmail = "EMAIL".padEnd(28);
|
|
576
695
|
const colStatus = "TOKEN STATUS".padEnd(14);
|
|
577
696
|
const colProvider = "PROVIDER".padEnd(12);
|
|
578
|
-
const colSession = hasQuota ? "SESSION".padEnd(
|
|
579
|
-
const colWeekly = hasQuota ? "WEEKLY".padEnd(
|
|
697
|
+
const colSession = hasQuota ? "SESSION".padEnd(14) : "";
|
|
698
|
+
const colWeekly = hasQuota ? "WEEKLY".padEnd(14) : "";
|
|
580
699
|
logger.always(` ${chalk.gray(colKey)} ${chalk.gray(colProvider)} ${chalk.gray(colEmail)} ${chalk.gray(colStatus)}${hasQuota ? ` ${chalk.gray(colSession)} ${chalk.gray(colWeekly)}` : ""}`);
|
|
581
|
-
logger.always(` ${chalk.gray("-".repeat(hasQuota ?
|
|
700
|
+
logger.always(` ${chalk.gray("-".repeat(hasQuota ? 108 : 78))}`);
|
|
582
701
|
for (const acct of enrichedAccounts) {
|
|
583
702
|
const displayLabel = (acct.label ?? acct.key).padEnd(20);
|
|
584
703
|
const displayEmail = (acct.email ?? "-").padEnd(28);
|
|
@@ -593,16 +712,18 @@ export async function handleList(argv) {
|
|
|
593
712
|
else {
|
|
594
713
|
statusText = chalk.yellow("unknown".padEnd(14));
|
|
595
714
|
}
|
|
596
|
-
const
|
|
597
|
-
|
|
715
|
+
const quota = quotas[acct.key] ??
|
|
716
|
+
(acct.provider === "anthropic" && acct.label
|
|
717
|
+
? quotas[acct.label]
|
|
718
|
+
: undefined);
|
|
598
719
|
const poolNote = poolState[acct.key];
|
|
599
720
|
if (hasQuota && quota) {
|
|
600
721
|
const qc = formatQuotaColumns(quota);
|
|
601
|
-
logger.always(` ${chalk.cyan(displayLabel)} ${displayProvider} ${displayEmail} ${statusText} ${qc.sessionText.padEnd(
|
|
722
|
+
logger.always(` ${chalk.cyan(displayLabel)} ${displayProvider} ${displayEmail} ${statusText} ${qc.sessionText.padEnd(14)} ${qc.weeklyText.padEnd(14)}`);
|
|
602
723
|
const indent = " ".repeat(2 + 20 + 1 + 12 + 1 + 28 + 1 + 14 + 1);
|
|
603
724
|
// Second line: reset times (indented under session/weekly columns)
|
|
604
725
|
if (qc.sessionReset || qc.weeklyReset) {
|
|
605
|
-
logger.always(`${indent}${(qc.sessionReset || "").padEnd(
|
|
726
|
+
logger.always(`${indent}${(qc.sessionReset || "").padEnd(14)} ${qc.weeklyReset || ""}`);
|
|
606
727
|
}
|
|
607
728
|
// Dynamic per-plan windows (e.g. the Fable-only weekly limit)
|
|
608
729
|
for (const windowRow of formatQuotaWindowRows(quota)) {
|
|
@@ -613,10 +734,16 @@ export async function handleList(argv) {
|
|
|
613
734
|
}
|
|
614
735
|
}
|
|
615
736
|
else {
|
|
737
|
+
const refreshState = refreshOutcome?.accounts[acct.key];
|
|
738
|
+
const unavailableText = refreshState?.status === "not_supported"
|
|
739
|
+
? "not supported"
|
|
740
|
+
: refreshOutcome
|
|
741
|
+
? "unavailable"
|
|
742
|
+
: "-";
|
|
616
743
|
const apiKeyNote = refreshOutcome && acct.tokenType && acct.tokenType !== "Bearer"
|
|
617
744
|
? chalk.gray(" (api key — not refreshed)")
|
|
618
745
|
: "";
|
|
619
|
-
logger.always(` ${chalk.cyan(displayLabel)} ${displayProvider} ${displayEmail} ${statusText}${hasQuota ? "
|
|
746
|
+
logger.always(` ${chalk.cyan(displayLabel)} ${displayProvider} ${displayEmail} ${statusText}${hasQuota ? ` ${chalk.yellow(unavailableText.padEnd(14))} ${chalk.gray("n/a".padEnd(14))}` : ""}${apiKeyNote}`);
|
|
620
747
|
if (poolNote) {
|
|
621
748
|
logger.always(` ${poolNote}`);
|
|
622
749
|
}
|
|
@@ -693,8 +820,8 @@ export async function handleLogout(argv) {
|
|
|
693
820
|
try {
|
|
694
821
|
const provider = argv.provider?.toLowerCase();
|
|
695
822
|
// Validate provider
|
|
696
|
-
if (!
|
|
697
|
-
logger.error(chalk.red(`Unsupported provider: ${provider}. Supported: ${
|
|
823
|
+
if (!AUTH_LOGIN_PROVIDERS.includes(provider)) {
|
|
824
|
+
logger.error(chalk.red(`Unsupported provider: ${provider}. Supported: ${AUTH_LOGIN_PROVIDERS.join(", ")}`));
|
|
698
825
|
process.exit(1);
|
|
699
826
|
}
|
|
700
827
|
logger.always(chalk.blue(`\nClearing ${provider} credentials...\n`));
|
|
@@ -784,7 +911,7 @@ export async function handleStatus(argv) {
|
|
|
784
911
|
// If provider specified, show just that provider
|
|
785
912
|
const providersToCheck = provider
|
|
786
913
|
? [provider]
|
|
787
|
-
: [...
|
|
914
|
+
: [...AUTH_LOGIN_PROVIDERS];
|
|
788
915
|
const results = [];
|
|
789
916
|
for (const p of providersToCheck) {
|
|
790
917
|
const status = await getAuthStatus(p);
|
|
@@ -892,8 +1019,8 @@ export async function handleRefresh(argv) {
|
|
|
892
1019
|
try {
|
|
893
1020
|
const provider = argv.provider?.toLowerCase();
|
|
894
1021
|
// Validate provider
|
|
895
|
-
if (!
|
|
896
|
-
logger.error(chalk.red(`Unsupported provider: ${provider}. Supported: ${
|
|
1022
|
+
if (!AUTH_LOGIN_PROVIDERS.includes(provider)) {
|
|
1023
|
+
logger.error(chalk.red(`Unsupported provider: ${provider}. Supported: ${AUTH_LOGIN_PROVIDERS.join(", ")}`));
|
|
897
1024
|
process.exit(1);
|
|
898
1025
|
}
|
|
899
1026
|
// Codex credentials live only in the pooled token store — there is no
|