opencode-usage 0.5.4 → 0.5.5

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,115 @@ 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, store) {
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
+ account.accessToken = tokens.access_token;
370
+ if (tokens.refresh_token)
371
+ account.refreshToken = tokens.refresh_token;
372
+ if (tokens.id_token)
373
+ account.idToken = tokens.id_token;
374
+ account.expiresAt = expiresAt;
375
+ account.lastRefresh = new Date().toISOString();
376
+ account.accountId = getAccountIdFromJwt(idClaims) ?? getAccountIdFromJwt(accessClaims) ?? account.accountId;
377
+ account.authInvalid = false;
378
+ await Bun.write(storePath, JSON.stringify(store, null, 2));
379
+ const daysLeft = ((expiresAt - Date.now()) / (24 * 60 * 60 * 1000)).toFixed(1);
380
+ console.log(`[proactiveRefresh] ${alias}: refreshed in ${Date.now() - t0}ms, new expiry in ${daysLeft}d`);
381
+ return true;
382
+ } catch (err) {
383
+ console.log(`[proactiveRefresh] ${alias}: error \u2014 ${err instanceof Error ? err.message : String(err)}`);
384
+ return false;
385
+ }
386
+ }
387
+ async function proactiveRefreshCodexTokens() {
388
+ const STORE_PATHS = [
389
+ join8(homedir6(), ".config", "opencode", "codex-multi-account-accounts.json"),
390
+ join8(homedir6(), ".config", "opencode", "codex-multi-accounts.json"),
391
+ join8(homedir6(), ".config", "oc-codex-multi-account", "accounts.json")
392
+ ];
393
+ let storePath = null;
394
+ let store = null;
395
+ for (const p of STORE_PATHS) {
396
+ try {
397
+ store = JSON.parse(await Bun.file(p).text());
398
+ storePath = p;
399
+ break;
400
+ } catch {
401
+ continue;
402
+ }
403
+ }
404
+ if (!store || !storePath)
405
+ return;
406
+ const accounts = store.accounts ?? {};
407
+ const now = Date.now();
408
+ let refreshed = 0;
409
+ for (const [alias, account] of Object.entries(accounts)) {
410
+ const expiresAt = typeof account.expiresAt === "number" ? account.expiresAt : 0;
411
+ const lastRefresh = typeof account.lastRefresh === "string" ? new Date(account.lastRefresh).getTime() : 0;
412
+ const timeSinceRefresh = now - lastRefresh;
413
+ const timeToExpiry = expiresAt - now;
414
+ if (timeToExpiry <= 0) {
415
+ console.log(`[proactiveRefresh] ${alias}: token expired, needs reauth`);
416
+ continue;
417
+ }
418
+ if (timeSinceRefresh < REFRESH_COOLDOWN_MS) {
419
+ const hoursAgo = (timeSinceRefresh / (60 * 60 * 1000)).toFixed(1);
420
+ console.log(`[proactiveRefresh] ${alias}: refreshed ${hoursAgo}h ago, skipping`);
421
+ continue;
422
+ }
423
+ const hoursStale = (timeSinceRefresh / (60 * 60 * 1000)).toFixed(1);
424
+ console.log(`[proactiveRefresh] ${alias}: ${hoursStale}h since refresh, refreshing\u2026`);
425
+ const ok = await refreshSingleCodexToken(alias, account, storePath, store);
426
+ if (ok)
427
+ refreshed++;
428
+ }
429
+ if (refreshed > 0) {
430
+ console.log(`[proactiveRefresh] refreshed ${refreshed} codex token(s)`);
431
+ }
432
+ }
311
433
  async function readCredentialFile(filename) {
312
434
  const filePath = join8(homedir6(), ".config", "opencode", filename);
313
435
  const text = await Bun.file(filePath).text();
@@ -461,7 +583,7 @@ function reauthCliCommand(provider) {
461
583
  throw new Error(`Re-auth not supported for provider: ${provider}`);
462
584
  return cmd;
463
585
  }
464
- var isBun4, PROVIDER_SOURCE, bunxQueue, REAUTH_PROVIDERS;
586
+ 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
587
  var init_plugin_adapters = __esm(() => {
466
588
  init_command_runner();
467
589
  init_config_service();
@@ -562,6 +684,7 @@ var init_plugin_adapters = __esm(() => {
562
684
  return { ok: true };
563
685
  }
564
686
  });
687
+ REFRESH_COOLDOWN_MS = 24 * 60 * 60 * 1000;
565
688
  bunxQueue = new Map;
566
689
  registerCommand({
567
690
  id: "accounts.ping",
@@ -602,6 +725,8 @@ var init_plugin_adapters = __esm(() => {
602
725
  ctx.log("info", `Direct ping result: ${direct.status}`);
603
726
  if (direct.status === "ok") {
604
727
  await clearStaleMetrics(input.provider, input.alias);
728
+ proactiveRefreshCodexTokens().catch(() => {
729
+ });
605
730
  return { status: "ok", message: "pong" };
606
731
  }
607
732
  if (direct.status === "expired") {
@@ -4972,10 +5097,10 @@ async function createCliRenderer(config = {}) {
4972
5097
  await renderer.setupTerminal();
4973
5098
  return renderer;
4974
5099
  }
4975
- var __defProp = Object.defineProperty;
4976
- var __export = (target, all) => {
5100
+ var __defProp2 = Object.defineProperty;
5101
+ var __export2 = (target, all) => {
4977
5102
  for (var name in all)
4978
- __defProp(target, name, {
5103
+ __defProp2(target, name, {
4979
5104
  get: all[name],
4980
5105
  enumerable: true,
4981
5106
  configurable: true,
@@ -4984,7 +5109,7 @@ var __export = (target, all) => {
4984
5109
  };
4985
5110
  var __require = import.meta.require;
4986
5111
  var exports_src = {};
4987
- __export(exports_src, {
5112
+ __export2(exports_src, {
4988
5113
  default: () => src_default,
4989
5114
  Wrap: () => Wrap,
4990
5115
  Unit: () => Unit,
@@ -30311,6 +30436,9 @@ function ensureActionsRegistered() {
30311
30436
  var isBun5 = typeof globalThis.Bun !== "undefined";
30312
30437
  var registered = false;
30313
30438
 
30439
+ // src/commander/server.ts
30440
+ init_plugin_adapters();
30441
+
30314
30442
  // src/commander/services/app-init-service.ts
30315
30443
  init_command_runner();
30316
30444
  import { homedir as homedir7 } from "os";
@@ -30834,6 +30962,8 @@ async function runCommanderServer(args) {
30834
30962
  });
30835
30963
  const serverUrl = `http://${hostname}:${port}`;
30836
30964
  console.log(`Commander ready at ${serverUrl}`);
30965
+ proactiveRefreshCodexTokens().catch(() => {
30966
+ });
30837
30967
  if (isBun7 && !process.env.NO_OPEN) {
30838
30968
  const cmd = process.platform === "win32" ? ["cmd", "/c", "start", serverUrl] : process.platform === "darwin" ? ["open", serverUrl] : ["xdg-open", serverUrl];
30839
30969
  Bun.spawn(cmd, { stdio: ["ignore", "ignore", "ignore"] });
@@ -30979,4 +31109,4 @@ async function main2() {
30979
31109
  var WATCH_INTERVAL_MS = 5 * 60 * 1000;
30980
31110
  main2().catch(console.error);
30981
31111
 
30982
- //# debugId=E047BEB14D24A3D264756E2164756E21
31112
+ //# debugId=DF21C38AB467E5FE64756E2164756E21