opencode-usage 0.5.3 → 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.
- package/dist/commander/services/plugin-adapters.d.ts +6 -1
- package/dist/index.js +258 -36
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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) {
|
|
@@ -241,36 +254,215 @@ function resolveSource2(provider) {
|
|
|
241
254
|
}
|
|
242
255
|
return source;
|
|
243
256
|
}
|
|
257
|
+
async function directCodexPing(alias) {
|
|
258
|
+
console.log(`[directCodexPing] pinging "${alias}"\u2026`);
|
|
259
|
+
const STORE_PATHS = [
|
|
260
|
+
join8(homedir6(), ".config", "opencode", "codex-multi-account-accounts.json"),
|
|
261
|
+
join8(homedir6(), ".config", "opencode", "codex-multi-accounts.json"),
|
|
262
|
+
join8(homedir6(), ".config", "oc-codex-multi-account", "accounts.json")
|
|
263
|
+
];
|
|
264
|
+
let store = null;
|
|
265
|
+
for (const p of STORE_PATHS) {
|
|
266
|
+
try {
|
|
267
|
+
store = JSON.parse(await Bun.file(p).text());
|
|
268
|
+
break;
|
|
269
|
+
} catch {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (!store)
|
|
274
|
+
return { status: "error", error: "No codex store found" };
|
|
275
|
+
console.log(`[directCodexPing] found account "${alias}", calling ChatGPT Codex API\u2026`);
|
|
276
|
+
const accounts = store.accounts ?? {};
|
|
277
|
+
const account = accounts[alias];
|
|
278
|
+
if (!account)
|
|
279
|
+
return { status: "error", error: `Account "${alias}" not found` };
|
|
280
|
+
const token = account.accessToken;
|
|
281
|
+
const accountId = account.accountId;
|
|
282
|
+
if (typeof token !== "string" || !token) {
|
|
283
|
+
return { status: "error", error: "Missing access token" };
|
|
284
|
+
}
|
|
285
|
+
if (typeof accountId !== "string" || !accountId) {
|
|
286
|
+
return { status: "error", error: "Missing accountId" };
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
const res = await fetch("https://chatgpt.com/backend-api/codex/responses", {
|
|
290
|
+
method: "POST",
|
|
291
|
+
headers: {
|
|
292
|
+
Authorization: `Bearer ${token}`,
|
|
293
|
+
"Content-Type": "application/json",
|
|
294
|
+
"chatgpt-account-id": accountId,
|
|
295
|
+
"OpenAI-Beta": "responses=experimental",
|
|
296
|
+
originator: "codex_cli_rs",
|
|
297
|
+
accept: "text/event-stream"
|
|
298
|
+
},
|
|
299
|
+
body: JSON.stringify({
|
|
300
|
+
model: "gpt-5.3-codex",
|
|
301
|
+
instructions: "reply ok",
|
|
302
|
+
input: [{ type: "message", role: "user", content: "hi" }],
|
|
303
|
+
store: false,
|
|
304
|
+
stream: true
|
|
305
|
+
})
|
|
306
|
+
});
|
|
307
|
+
if (res.ok || res.status === 429) {
|
|
308
|
+
console.log(`[directCodexPing] "${alias}" \u2192 ok (HTTP ${res.status})`);
|
|
309
|
+
return { status: "ok" };
|
|
310
|
+
}
|
|
311
|
+
if (res.status === 401 || res.status === 403) {
|
|
312
|
+
console.log(`[directCodexPing] "${alias}" \u2192 expired (HTTP ${res.status})`);
|
|
313
|
+
return { status: "expired", error: `HTTP ${res.status}` };
|
|
314
|
+
}
|
|
315
|
+
console.log(`[directCodexPing] "${alias}" \u2192 error (HTTP ${res.status})`);
|
|
316
|
+
return { status: "error", error: `HTTP ${res.status}` };
|
|
317
|
+
} catch (err) {
|
|
318
|
+
return {
|
|
319
|
+
status: "error",
|
|
320
|
+
error: err instanceof Error ? err.message : String(err)
|
|
321
|
+
};
|
|
322
|
+
}
|
|
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
|
+
}
|
|
244
433
|
async function readCredentialFile(filename) {
|
|
245
434
|
const filePath = join8(homedir6(), ".config", "opencode", filename);
|
|
246
435
|
const text = await Bun.file(filePath).text();
|
|
247
436
|
return JSON.parse(text);
|
|
248
437
|
}
|
|
249
438
|
async function spawnPluginCli(command, args, timeoutMs = 15000) {
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
439
|
+
const candidates = [
|
|
440
|
+
`./node_modules/${command}/dist/cli.js`,
|
|
441
|
+
join8(homedir6(), ".config", "opencode", "node_modules", command, "dist", "cli.js")
|
|
442
|
+
];
|
|
443
|
+
let localBin = null;
|
|
444
|
+
for (const c of candidates) {
|
|
445
|
+
if (await Bun.file(c).exists()) {
|
|
446
|
+
localBin = c;
|
|
447
|
+
break;
|
|
258
448
|
}
|
|
259
|
-
|
|
260
|
-
|
|
449
|
+
}
|
|
450
|
+
const useLocal = localBin !== null;
|
|
451
|
+
if (!useLocal) {
|
|
452
|
+
const prev = bunxQueue.get(command) ?? Promise.resolve();
|
|
453
|
+
const run = prev.catch(() => {
|
|
454
|
+
}).then(() => runPluginCli(command, args, timeoutMs, null));
|
|
455
|
+
bunxQueue.set(command, run.then(() => {
|
|
261
456
|
}, () => {
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
barrier.finally(() => bunxBarrier.delete(command));
|
|
265
|
-
return warmup;
|
|
457
|
+
}));
|
|
458
|
+
return run;
|
|
266
459
|
}
|
|
267
|
-
return runPluginCli(command, args, timeoutMs,
|
|
460
|
+
return runPluginCli(command, args, timeoutMs, localBin);
|
|
268
461
|
}
|
|
269
|
-
async function runPluginCli(command, args, timeoutMs,
|
|
462
|
+
async function runPluginCli(command, args, timeoutMs, localBin) {
|
|
270
463
|
const t0 = Date.now();
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
const cwd = useLocal ? undefined : join8(tmpdir(), `bunx-${crypto.randomUUID()}`);
|
|
464
|
+
const cmd = localBin ? ["bun", localBin, ...args] : ["bunx", `${command}@latest`, ...args];
|
|
465
|
+
const cwd = localBin ? undefined : join8(tmpdir(), `bunx-${crypto.randomUUID()}`);
|
|
274
466
|
if (cwd)
|
|
275
467
|
await Bun.$`mkdir -p ${cwd}`.quiet();
|
|
276
468
|
console.log(`[spawnPluginCli] starting: ${cmd.join(" ")}`);
|
|
@@ -391,7 +583,7 @@ function reauthCliCommand(provider) {
|
|
|
391
583
|
throw new Error(`Re-auth not supported for provider: ${provider}`);
|
|
392
584
|
return cmd;
|
|
393
585
|
}
|
|
394
|
-
var isBun4, PROVIDER_SOURCE,
|
|
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;
|
|
395
587
|
var init_plugin_adapters = __esm(() => {
|
|
396
588
|
init_command_runner();
|
|
397
589
|
init_config_service();
|
|
@@ -492,7 +684,8 @@ var init_plugin_adapters = __esm(() => {
|
|
|
492
684
|
return { ok: true };
|
|
493
685
|
}
|
|
494
686
|
});
|
|
495
|
-
|
|
687
|
+
REFRESH_COOLDOWN_MS = 24 * 60 * 60 * 1000;
|
|
688
|
+
bunxQueue = new Map;
|
|
496
689
|
registerCommand({
|
|
497
690
|
id: "accounts.ping",
|
|
498
691
|
timeoutMs: 30000,
|
|
@@ -527,19 +720,43 @@ var init_plugin_adapters = __esm(() => {
|
|
|
527
720
|
};
|
|
528
721
|
}
|
|
529
722
|
case "codex": {
|
|
530
|
-
ctx.log("info",
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
]);
|
|
535
|
-
const status = String(result.status ?? "error");
|
|
536
|
-
ctx.log("info", `Result: ${status}`);
|
|
537
|
-
if (status === "ok") {
|
|
723
|
+
ctx.log("info", `Direct-pinging codex account "${input.alias}"\u2026`);
|
|
724
|
+
const direct = await directCodexPing(input.alias);
|
|
725
|
+
ctx.log("info", `Direct ping result: ${direct.status}`);
|
|
726
|
+
if (direct.status === "ok") {
|
|
538
727
|
await clearStaleMetrics(input.provider, input.alias);
|
|
728
|
+
proactiveRefreshCodexTokens().catch(() => {
|
|
729
|
+
});
|
|
730
|
+
return { status: "ok", message: "pong" };
|
|
731
|
+
}
|
|
732
|
+
if (direct.status === "expired") {
|
|
733
|
+
ctx.log("info", "Token expired \u2014 trying plugin CLI for refresh\u2026");
|
|
734
|
+
try {
|
|
735
|
+
const result = await spawnPluginCli("oc-codex-multi-account", [
|
|
736
|
+
"ping",
|
|
737
|
+
input.alias
|
|
738
|
+
]);
|
|
739
|
+
const cliStatus = String(result.status ?? "error");
|
|
740
|
+
ctx.log("info", `Plugin CLI result: ${cliStatus}`);
|
|
741
|
+
if (cliStatus === "ok") {
|
|
742
|
+
await clearStaleMetrics(input.provider, input.alias);
|
|
743
|
+
return { status: "ok", message: "pong (token refreshed)" };
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
status: cliStatus,
|
|
747
|
+
message: String(result.error ?? "Token refresh failed")
|
|
748
|
+
};
|
|
749
|
+
} catch (cliErr) {
|
|
750
|
+
ctx.log("info", `Plugin CLI failed: ${cliErr instanceof Error ? cliErr.message : String(cliErr)}`);
|
|
751
|
+
return {
|
|
752
|
+
status: "error",
|
|
753
|
+
message: direct.error ?? "Token expired and plugin refresh failed"
|
|
754
|
+
};
|
|
755
|
+
}
|
|
539
756
|
}
|
|
540
757
|
return {
|
|
541
|
-
status,
|
|
542
|
-
message:
|
|
758
|
+
status: "error",
|
|
759
|
+
message: direct.error ?? "unknown error"
|
|
543
760
|
};
|
|
544
761
|
}
|
|
545
762
|
case "antigravity": {
|
|
@@ -4880,10 +5097,10 @@ async function createCliRenderer(config = {}) {
|
|
|
4880
5097
|
await renderer.setupTerminal();
|
|
4881
5098
|
return renderer;
|
|
4882
5099
|
}
|
|
4883
|
-
var
|
|
4884
|
-
var
|
|
5100
|
+
var __defProp2 = Object.defineProperty;
|
|
5101
|
+
var __export2 = (target, all) => {
|
|
4885
5102
|
for (var name in all)
|
|
4886
|
-
|
|
5103
|
+
__defProp2(target, name, {
|
|
4887
5104
|
get: all[name],
|
|
4888
5105
|
enumerable: true,
|
|
4889
5106
|
configurable: true,
|
|
@@ -4892,7 +5109,7 @@ var __export = (target, all) => {
|
|
|
4892
5109
|
};
|
|
4893
5110
|
var __require = import.meta.require;
|
|
4894
5111
|
var exports_src = {};
|
|
4895
|
-
|
|
5112
|
+
__export2(exports_src, {
|
|
4896
5113
|
default: () => src_default,
|
|
4897
5114
|
Wrap: () => Wrap,
|
|
4898
5115
|
Unit: () => Unit,
|
|
@@ -30219,6 +30436,9 @@ function ensureActionsRegistered() {
|
|
|
30219
30436
|
var isBun5 = typeof globalThis.Bun !== "undefined";
|
|
30220
30437
|
var registered = false;
|
|
30221
30438
|
|
|
30439
|
+
// src/commander/server.ts
|
|
30440
|
+
init_plugin_adapters();
|
|
30441
|
+
|
|
30222
30442
|
// src/commander/services/app-init-service.ts
|
|
30223
30443
|
init_command_runner();
|
|
30224
30444
|
import { homedir as homedir7 } from "os";
|
|
@@ -30742,6 +30962,8 @@ async function runCommanderServer(args) {
|
|
|
30742
30962
|
});
|
|
30743
30963
|
const serverUrl = `http://${hostname}:${port}`;
|
|
30744
30964
|
console.log(`Commander ready at ${serverUrl}`);
|
|
30965
|
+
proactiveRefreshCodexTokens().catch(() => {
|
|
30966
|
+
});
|
|
30745
30967
|
if (isBun7 && !process.env.NO_OPEN) {
|
|
30746
30968
|
const cmd = process.platform === "win32" ? ["cmd", "/c", "start", serverUrl] : process.platform === "darwin" ? ["open", serverUrl] : ["xdg-open", serverUrl];
|
|
30747
30969
|
Bun.spawn(cmd, { stdio: ["ignore", "ignore", "ignore"] });
|
|
@@ -30887,4 +31109,4 @@ async function main2() {
|
|
|
30887
31109
|
var WATCH_INTERVAL_MS = 5 * 60 * 1000;
|
|
30888
31110
|
main2().catch(console.error);
|
|
30889
31111
|
|
|
30890
|
-
//# debugId=
|
|
31112
|
+
//# debugId=DF21C38AB467E5FE64756E2164756E21
|