opencode-usage 0.5.4 → 0.5.6

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.
@@ -4,4 +4,9 @@
4
4
  * Each command is registered via `registerCommand` so the command-runner can
5
5
  * execute them as background jobs.
6
6
  */
7
- export {};
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
+ export declare function proactiveRefreshCodexTokens(): Promise<void>;
package/dist/index.js CHANGED
@@ -1,5 +1,15 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
+ var __defProp = Object.defineProperty;
4
+ var __export = (target, all) => {
5
+ for (var name in all)
6
+ __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true,
9
+ configurable: true,
10
+ set: (newValue) => all[name] = () => newValue
11
+ });
12
+ };
3
13
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
4
14
 
5
15
  // src/commander/services/command-runner.ts
@@ -232,6 +242,9 @@ var init_config_service = __esm(() => {
232
242
 
233
243
  // src/commander/services/plugin-adapters.ts
234
244
  var exports_plugin_adapters = {};
245
+ __export(exports_plugin_adapters, {
246
+ proactiveRefreshCodexTokens: () => proactiveRefreshCodexTokens
247
+ });
235
248
  import { homedir as homedir6, tmpdir } from "os";
236
249
  import { join as join8 } from "path";
237
250
  function resolveSource2(provider) {
@@ -308,6 +321,120 @@ async function directCodexPing(alias) {
308
321
  };
309
322
  }
310
323
  }
324
+ function decodeJwtPayload(token) {
325
+ try {
326
+ const parts = token.split(".");
327
+ if (parts.length !== 3)
328
+ return null;
329
+ const payload = Buffer.from(parts[1], "base64").toString("utf-8");
330
+ return JSON.parse(payload);
331
+ } catch {
332
+ return null;
333
+ }
334
+ }
335
+ function getExpiryFromJwt(claims) {
336
+ if (!claims || typeof claims.exp !== "number")
337
+ return null;
338
+ return claims.exp * 1000;
339
+ }
340
+ function getAccountIdFromJwt(claims) {
341
+ const auth = claims?.["https://api.openai.com/auth"];
342
+ return auth?.chatgpt_account_id ?? null;
343
+ }
344
+ async function refreshSingleCodexToken(alias, account, storePath) {
345
+ const refreshToken = account.refreshToken;
346
+ if (typeof refreshToken !== "string" || !refreshToken) {
347
+ console.log(`[proactiveRefresh] ${alias}: no refresh token, skipping`);
348
+ return false;
349
+ }
350
+ try {
351
+ const t0 = Date.now();
352
+ const res = await fetch(CODEX_TOKEN_URL, {
353
+ method: "POST",
354
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
355
+ body: new URLSearchParams({
356
+ grant_type: "refresh_token",
357
+ client_id: CODEX_CLIENT_ID,
358
+ refresh_token: refreshToken
359
+ })
360
+ });
361
+ if (!res.ok) {
362
+ console.log(`[proactiveRefresh] ${alias}: refresh failed HTTP ${res.status} in ${Date.now() - t0}ms`);
363
+ return false;
364
+ }
365
+ const tokens = await res.json();
366
+ const accessClaims = decodeJwtPayload(tokens.access_token);
367
+ const idClaims = tokens.id_token ? decodeJwtPayload(tokens.id_token) : null;
368
+ const expiresAt = getExpiryFromJwt(accessClaims) ?? getExpiryFromJwt(idClaims) ?? Date.now() + tokens.expires_in * 1000;
369
+ const freshStore = JSON.parse(await Bun.file(storePath).text());
370
+ const freshAccounts = freshStore.accounts ?? {};
371
+ const freshAcct = freshAccounts[alias];
372
+ if (freshAcct) {
373
+ freshAcct.accessToken = tokens.access_token;
374
+ if (tokens.refresh_token)
375
+ freshAcct.refreshToken = tokens.refresh_token;
376
+ if (tokens.id_token)
377
+ freshAcct.idToken = tokens.id_token;
378
+ freshAcct.expiresAt = expiresAt;
379
+ freshAcct.lastRefresh = new Date().toISOString();
380
+ freshAcct.accountId = getAccountIdFromJwt(idClaims) ?? getAccountIdFromJwt(accessClaims) ?? freshAcct.accountId;
381
+ freshAcct.authInvalid = false;
382
+ }
383
+ await Bun.write(storePath, JSON.stringify(freshStore, null, 2));
384
+ const daysLeft = ((expiresAt - Date.now()) / (24 * 60 * 60 * 1000)).toFixed(1);
385
+ console.log(`[proactiveRefresh] ${alias}: refreshed in ${Date.now() - t0}ms, new expiry in ${daysLeft}d`);
386
+ return true;
387
+ } catch (err) {
388
+ console.log(`[proactiveRefresh] ${alias}: error \u2014 ${err instanceof Error ? err.message : String(err)}`);
389
+ return false;
390
+ }
391
+ }
392
+ async function proactiveRefreshCodexTokens() {
393
+ const STORE_PATHS = [
394
+ join8(homedir6(), ".config", "opencode", "codex-multi-account-accounts.json"),
395
+ join8(homedir6(), ".config", "opencode", "codex-multi-accounts.json"),
396
+ join8(homedir6(), ".config", "oc-codex-multi-account", "accounts.json")
397
+ ];
398
+ let storePath = null;
399
+ let store = null;
400
+ for (const p of STORE_PATHS) {
401
+ try {
402
+ store = JSON.parse(await Bun.file(p).text());
403
+ storePath = p;
404
+ break;
405
+ } catch {
406
+ continue;
407
+ }
408
+ }
409
+ if (!store || !storePath)
410
+ return;
411
+ const accounts = store.accounts ?? {};
412
+ const now = Date.now();
413
+ let refreshed = 0;
414
+ for (const [alias, account] of Object.entries(accounts)) {
415
+ const expiresAt = typeof account.expiresAt === "number" ? account.expiresAt : 0;
416
+ const lastRefresh = typeof account.lastRefresh === "string" ? new Date(account.lastRefresh).getTime() : 0;
417
+ const timeSinceRefresh = now - lastRefresh;
418
+ const timeToExpiry = expiresAt - now;
419
+ if (timeToExpiry <= 0) {
420
+ console.log(`[proactiveRefresh] ${alias}: token expired, needs reauth`);
421
+ continue;
422
+ }
423
+ if (timeSinceRefresh < REFRESH_COOLDOWN_MS) {
424
+ const hoursAgo = (timeSinceRefresh / (60 * 60 * 1000)).toFixed(1);
425
+ console.log(`[proactiveRefresh] ${alias}: refreshed ${hoursAgo}h ago, skipping`);
426
+ continue;
427
+ }
428
+ const hoursStale = (timeSinceRefresh / (60 * 60 * 1000)).toFixed(1);
429
+ console.log(`[proactiveRefresh] ${alias}: ${hoursStale}h since refresh, refreshing\u2026`);
430
+ const ok = await refreshSingleCodexToken(alias, account, storePath);
431
+ if (ok)
432
+ refreshed++;
433
+ }
434
+ if (refreshed > 0) {
435
+ console.log(`[proactiveRefresh] refreshed ${refreshed} codex token(s)`);
436
+ }
437
+ }
311
438
  async function readCredentialFile(filename) {
312
439
  const filePath = join8(homedir6(), ".config", "opencode", filename);
313
440
  const text = await Bun.file(filePath).text();
@@ -415,8 +542,8 @@ async function clearStaleMetrics(provider, alias) {
415
542
  const rl = acct.rateLimits;
416
543
  for (const key of ["fiveHour", "weekly"]) {
417
544
  const w2 = rl[key];
418
- if (w2?.resetAt && typeof w2.resetAt === "string") {
419
- if (new Date(w2.resetAt).getTime() < now) {
545
+ if (w2?.resetAt && typeof w2.resetAt === "number") {
546
+ if (w2.resetAt < now) {
420
547
  w2.remaining = w2.limit;
421
548
  delete w2.resetAt;
422
549
  changed = true;
@@ -461,7 +588,7 @@ function reauthCliCommand(provider) {
461
588
  throw new Error(`Re-auth not supported for provider: ${provider}`);
462
589
  return cmd;
463
590
  }
464
- var isBun4, PROVIDER_SOURCE, bunxQueue, REAUTH_PROVIDERS;
591
+ var isBun4, PROVIDER_SOURCE, CODEX_TOKEN_URL = "https://auth.openai.com/oauth/token", CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann", REFRESH_COOLDOWN_MS, bunxQueue, REAUTH_PROVIDERS;
465
592
  var init_plugin_adapters = __esm(() => {
466
593
  init_command_runner();
467
594
  init_config_service();
@@ -562,6 +689,7 @@ var init_plugin_adapters = __esm(() => {
562
689
  return { ok: true };
563
690
  }
564
691
  });
692
+ REFRESH_COOLDOWN_MS = 24 * 60 * 60 * 1000;
565
693
  bunxQueue = new Map;
566
694
  registerCommand({
567
695
  id: "accounts.ping",
@@ -602,6 +730,8 @@ var init_plugin_adapters = __esm(() => {
602
730
  ctx.log("info", `Direct ping result: ${direct.status}`);
603
731
  if (direct.status === "ok") {
604
732
  await clearStaleMetrics(input.provider, input.alias);
733
+ proactiveRefreshCodexTokens().catch(() => {
734
+ });
605
735
  return { status: "ok", message: "pong" };
606
736
  }
607
737
  if (direct.status === "expired") {
@@ -4972,10 +5102,10 @@ async function createCliRenderer(config = {}) {
4972
5102
  await renderer.setupTerminal();
4973
5103
  return renderer;
4974
5104
  }
4975
- var __defProp = Object.defineProperty;
4976
- var __export = (target, all) => {
5105
+ var __defProp2 = Object.defineProperty;
5106
+ var __export2 = (target, all) => {
4977
5107
  for (var name in all)
4978
- __defProp(target, name, {
5108
+ __defProp2(target, name, {
4979
5109
  get: all[name],
4980
5110
  enumerable: true,
4981
5111
  configurable: true,
@@ -4984,7 +5114,7 @@ var __export = (target, all) => {
4984
5114
  };
4985
5115
  var __require = import.meta.require;
4986
5116
  var exports_src = {};
4987
- __export(exports_src, {
5117
+ __export2(exports_src, {
4988
5118
  default: () => src_default,
4989
5119
  Wrap: () => Wrap,
4990
5120
  Unit: () => Unit,
@@ -30311,6 +30441,9 @@ function ensureActionsRegistered() {
30311
30441
  var isBun5 = typeof globalThis.Bun !== "undefined";
30312
30442
  var registered = false;
30313
30443
 
30444
+ // src/commander/server.ts
30445
+ init_plugin_adapters();
30446
+
30314
30447
  // src/commander/services/app-init-service.ts
30315
30448
  init_command_runner();
30316
30449
  import { homedir as homedir7 } from "os";
@@ -30834,6 +30967,8 @@ async function runCommanderServer(args) {
30834
30967
  });
30835
30968
  const serverUrl = `http://${hostname}:${port}`;
30836
30969
  console.log(`Commander ready at ${serverUrl}`);
30970
+ proactiveRefreshCodexTokens().catch(() => {
30971
+ });
30837
30972
  if (isBun7 && !process.env.NO_OPEN) {
30838
30973
  const cmd = process.platform === "win32" ? ["cmd", "/c", "start", serverUrl] : process.platform === "darwin" ? ["open", serverUrl] : ["xdg-open", serverUrl];
30839
30974
  Bun.spawn(cmd, { stdio: ["ignore", "ignore", "ignore"] });
@@ -30979,4 +31114,4 @@ async function main2() {
30979
31114
  var WATCH_INTERVAL_MS = 5 * 60 * 1000;
30980
31115
  main2().catch(console.error);
30981
31116
 
30982
- //# debugId=E047BEB14D24A3D264756E2164756E21
31117
+ //# debugId=79D8BC2A04D06ACC64756E2164756E21