opencode-usage 0.5.8 → 0.5.11

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
@@ -74,11 +74,16 @@ opencode-usage --commander
74
74
  opencode-usage --commander --commander-port 5000
75
75
  ```
76
76
 
77
+ ![Commander Dashboard](docs/commander.png)
78
+
77
79
  The Commander provides a single-page web UI with:
78
80
 
79
- - **Quota Status** - Per-provider account usage with progress bars, thresholds, and stale detection (Anthropic, Codex, Antigravity)
80
- - **Usage Breakdown** - Daily token usage table with cost estimates and provider drill-down
81
+ - **Quota Status** - Per-provider account usage with progress bars, configurable thresholds, and stale detection
82
+ - **Multi-Account** - Full multi-account support for Anthropic and Gemini providers
83
+ - **Usage Breakdown** - Daily/monthly token usage table with cost estimates, provider filter, and date range
81
84
  - **Account Management** - Add, switch, remove, and re-authenticate accounts
85
+ - **Plugins** - Extensible plugin system for custom providers and integrations
86
+ - **Configuration** - Customizable thresholds, display preferences, and provider settings
82
87
  - **Ping** - Verify account connectivity with live PONG/FAIL indicators
83
88
  - **Dark mode** toggle
84
89
  - Auto-refresh every 5 minutes
@@ -4,9 +4,4 @@
4
4
  * Each command is registered via `registerCommand` so the command-runner can
5
5
  * execute them as background jobs.
6
6
  */
7
- /**
8
- * Proactively refresh Codex tokens that haven't been refreshed in 24+ hours.
9
- * Keeps tokens fresh so they never silently expire.
10
- * Safe to call frequently — skips accounts refreshed recently.
11
- */
12
7
  export declare function proactiveRefreshCodexTokens(): Promise<void>;
package/dist/index.js CHANGED
@@ -366,6 +366,56 @@ function getAccountIdFromJwt(claims) {
366
366
  const auth = claims?.["https://api.openai.com/auth"];
367
367
  return auth?.chatgpt_account_id ?? null;
368
368
  }
369
+ async function tryReadCodexCliAuth() {
370
+ try {
371
+ const raw = JSON.parse(await Bun.file(CODEX_CLI_AUTH_PATH).text());
372
+ const tokens = raw.tokens;
373
+ if (!tokens?.access_token)
374
+ return null;
375
+ const accessClaims = decodeJwtPayload(tokens.access_token);
376
+ const idClaims = tokens.id_token ? decodeJwtPayload(tokens.id_token) : null;
377
+ const expiresAt = getExpiryFromJwt(accessClaims) ?? getExpiryFromJwt(idClaims) ?? 0;
378
+ if (expiresAt <= Date.now())
379
+ return null;
380
+ return {
381
+ accessToken: tokens.access_token,
382
+ refreshToken: tokens.refresh_token ?? "",
383
+ idToken: tokens.id_token ?? null,
384
+ accountId: getAccountIdFromJwt(idClaims) ?? getAccountIdFromJwt(accessClaims),
385
+ expiresAt
386
+ };
387
+ } catch {
388
+ return null;
389
+ }
390
+ }
391
+ async function tryImportFromCodexCli(alias, account, storePath) {
392
+ const cli = await tryReadCodexCliAuth();
393
+ if (!cli || !cli.refreshToken)
394
+ return false;
395
+ const pluginAccountId = account.accountId;
396
+ if (!pluginAccountId || cli.accountId !== pluginAccountId)
397
+ return false;
398
+ const pluginExpiry = typeof account.expiresAt === "number" ? account.expiresAt : 0;
399
+ if (cli.expiresAt <= pluginExpiry)
400
+ return false;
401
+ console.log(`[proactiveRefresh] ${alias}: importing fresher token from ~/.codex/auth.json`);
402
+ const freshStore = JSON.parse(await Bun.file(storePath).text());
403
+ const freshAccounts = freshStore.accounts ?? {};
404
+ const freshAcct = freshAccounts[alias];
405
+ if (!freshAcct)
406
+ return false;
407
+ freshAcct.accessToken = cli.accessToken;
408
+ freshAcct.refreshToken = cli.refreshToken;
409
+ if (cli.idToken)
410
+ freshAcct.idToken = cli.idToken;
411
+ freshAcct.expiresAt = cli.expiresAt;
412
+ freshAcct.lastRefresh = new Date().toISOString();
413
+ freshAcct.authInvalid = false;
414
+ await Bun.write(storePath, JSON.stringify(freshStore, null, 2));
415
+ const daysLeft = ((cli.expiresAt - Date.now()) / (24 * 60 * 60 * 1000)).toFixed(1);
416
+ console.log(`[proactiveRefresh] ${alias}: imported from CLI, expiry in ${daysLeft}d`);
417
+ return true;
418
+ }
369
419
  async function refreshSingleCodexToken(alias, account, storePath) {
370
420
  const refreshToken = account.refreshToken;
371
421
  if (typeof refreshToken !== "string" || !refreshToken) {
@@ -385,7 +435,7 @@ async function refreshSingleCodexToken(alias, account, storePath) {
385
435
  });
386
436
  if (!res.ok) {
387
437
  console.log(`[proactiveRefresh] ${alias}: refresh failed HTTP ${res.status} in ${Date.now() - t0}ms`);
388
- return false;
438
+ return tryImportFromCodexCli(alias, account, storePath);
389
439
  }
390
440
  const tokens = await res.json();
391
441
  const accessClaims = decodeJwtPayload(tokens.access_token);
@@ -415,6 +465,16 @@ async function refreshSingleCodexToken(alias, account, storePath) {
415
465
  }
416
466
  }
417
467
  async function proactiveRefreshCodexTokens() {
468
+ if (_proactiveRefreshRunning)
469
+ return;
470
+ _proactiveRefreshRunning = true;
471
+ try {
472
+ await _proactiveRefreshCodexTokensInner();
473
+ } finally {
474
+ _proactiveRefreshRunning = false;
475
+ }
476
+ }
477
+ async function _proactiveRefreshCodexTokensInner() {
418
478
  const STORE_PATHS = [
419
479
  join8(homedir6(), ".config", "opencode", "codex-multi-account-accounts.json"),
420
480
  join8(homedir6(), ".config", "opencode", "codex-multi-accounts.json"),
@@ -437,6 +497,10 @@ async function proactiveRefreshCodexTokens() {
437
497
  const now = Date.now();
438
498
  let refreshed = 0;
439
499
  for (const [alias, account] of Object.entries(accounts)) {
500
+ if (account.authInvalid === true) {
501
+ console.log(`[proactiveRefresh] ${alias}: authInvalid, skipping (needs reauth)`);
502
+ continue;
503
+ }
440
504
  const expiresAt = typeof account.expiresAt === "number" ? account.expiresAt : 0;
441
505
  const lastRefresh = typeof account.lastRefresh === "string" ? new Date(account.lastRefresh).getTime() : 0;
442
506
  const timeSinceRefresh = now - lastRefresh;
@@ -619,7 +683,7 @@ function reauthCliCommand(provider) {
619
683
  throw new Error(`Re-auth not supported for provider: ${provider}`);
620
684
  return cmd;
621
685
  }
622
- var isBun4, PROVIDER_SOURCE, CODEX_TOKEN_URL = "https://auth.openai.com/oauth/token", CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", REFRESH_COOLDOWN_MS, bunxQueue, REAUTH_PROVIDERS;
686
+ var isBun4, PROVIDER_SOURCE, CODEX_TOKEN_URL = "https://auth.openai.com/oauth/token", CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", REFRESH_COOLDOWN_MS, CODEX_CLI_AUTH_PATH, _proactiveRefreshRunning = false, bunxQueue, REAUTH_PROVIDERS;
623
687
  var init_plugin_adapters = __esm(() => {
624
688
  init_command_runner();
625
689
  init_config_service();
@@ -721,6 +785,7 @@ var init_plugin_adapters = __esm(() => {
721
785
  }
722
786
  });
723
787
  REFRESH_COOLDOWN_MS = 24 * 60 * 60 * 1000;
788
+ CODEX_CLI_AUTH_PATH = join8(homedir6(), ".codex", "auth.json");
724
789
  bunxQueue = new Map;
725
790
  registerCommand({
726
791
  id: "accounts.ping",
@@ -31158,4 +31223,4 @@ async function main2() {
31158
31223
  var WATCH_INTERVAL_MS = 5 * 60 * 1000;
31159
31224
  main2().catch(console.error);
31160
31225
 
31161
- //# debugId=5A3E5FE73F68D7CA64756E2164756E21
31226
+ //# debugId=F9964D445D944A6564756E2164756E21