codexuse-cli 5.0.2 → 5.0.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.
package/dist/index.js CHANGED
@@ -2054,6 +2054,7 @@ var DEFAULT_AUTO_ROLL_ENABLED = false;
2054
2054
  var DEFAULT_AUTO_ROLL_REARM_REMAINING_THRESHOLD = 15;
2055
2055
  var DEFAULT_AUTO_ROLL_SWITCH_REMAINING_THRESHOLD = 5;
2056
2056
  var DEFAULT_LAUNCH_OFFICIAL_CODEX_WHEN_CLOSED_ON_AUTO_ROLL = false;
2057
+ var DEFAULT_HANDOFF_RUNNING_THREADS_ON_ACCOUNT_SWITCH = false;
2057
2058
  var DEFAULT_AUTO_ROLL_PRIORITY_ORDER = [];
2058
2059
  var DEFAULT_LOW_REMAINING_NOTIFICATION_ENABLED = false;
2059
2060
  var DEFAULT_LOW_REMAINING_NOTIFICATION_THRESHOLD = 1;
@@ -2157,6 +2158,7 @@ function normalizeAutoRollSettings(raw) {
2157
2158
  rearmRemainingThreshold: normalizedRearm,
2158
2159
  switchRemainingThreshold: normalizedSwitch,
2159
2160
  launchOfficialCodexWhenClosedOnAutoRoll: raw?.launchOfficialCodexWhenClosedOnAutoRoll === true ? true : DEFAULT_LAUNCH_OFFICIAL_CODEX_WHEN_CLOSED_ON_AUTO_ROLL,
2161
+ handoffRunningThreadsOnAccountSwitch: raw?.handoffRunningThreadsOnAccountSwitch === true ? true : DEFAULT_HANDOFF_RUNNING_THREADS_ON_ACCOUNT_SWITCH,
2160
2162
  priorityOrder: sanitizeAutoRollPriorityOrder(raw?.priorityOrder),
2161
2163
  lowRemainingNotificationEnabled: raw?.lowRemainingNotificationEnabled === true ? true : DEFAULT_LOW_REMAINING_NOTIFICATION_ENABLED,
2162
2164
  lowRemainingNotificationThreshold: sanitizeLowRemainingNotificationThreshold(
@@ -2530,6 +2532,7 @@ function createDefaultAppState() {
2530
2532
  rearmRemainingThreshold: 15,
2531
2533
  switchRemainingThreshold: 5,
2532
2534
  launchOfficialCodexWhenClosedOnAutoRoll: false,
2535
+ handoffRunningThreadsOnAccountSwitch: false,
2533
2536
  priorityOrder: [],
2534
2537
  lowRemainingNotificationEnabled: false,
2535
2538
  lowRemainingNotificationThreshold: 1
@@ -2631,6 +2634,20 @@ function asString(value) {
2631
2634
  const trimmed = value.trim();
2632
2635
  return trimmed.length > 0 ? trimmed : null;
2633
2636
  }
2637
+ function asStringArray(value) {
2638
+ if (!Array.isArray(value)) {
2639
+ return [];
2640
+ }
2641
+ const seen = /* @__PURE__ */ new Set();
2642
+ return value.flatMap((entry) => {
2643
+ const normalized = asString(entry);
2644
+ if (!normalized || seen.has(normalized)) {
2645
+ return [];
2646
+ }
2647
+ seen.add(normalized);
2648
+ return [normalized];
2649
+ });
2650
+ }
2634
2651
  function asNumberOrNull(value) {
2635
2652
  return typeof value === "number" && Number.isFinite(value) ? value : null;
2636
2653
  }
@@ -2954,7 +2971,10 @@ function normalizeAppState(raw) {
2954
2971
  sourceProfileKey: asString(debt.sourceProfileKey),
2955
2972
  decisionId: asString(debt.decisionId),
2956
2973
  attempts: asNumberOrNull(debt.attempts) ?? 0,
2957
- lastReason: asString(debt.lastReason)
2974
+ lastReason: asString(debt.lastReason),
2975
+ handoffReason: debt.handoffReason === "manual" || debt.handoffReason === "auto-roll" ? debt.handoffReason : null,
2976
+ handoffThreadIds: asStringArray(debt.handoffThreadIds),
2977
+ handoffSentThreadIds: asStringArray(debt.handoffSentThreadIds)
2958
2978
  } : null;
2959
2979
  } else {
2960
2980
  merged.officialCodex.pendingActivationDebt = null;
@@ -3496,6 +3516,7 @@ async function writeCodexSettingsJsonRaw(payload) {
3496
3516
  rearmRemainingThreshold: autoRoll.rearmRemainingThreshold,
3497
3517
  switchRemainingThreshold: autoRoll.switchRemainingThreshold,
3498
3518
  launchOfficialCodexWhenClosedOnAutoRoll: autoRoll.launchOfficialCodexWhenClosedOnAutoRoll,
3519
+ handoffRunningThreadsOnAccountSwitch: autoRoll.handoffRunningThreadsOnAccountSwitch,
3499
3520
  priorityOrder: autoRoll.priorityOrder,
3500
3521
  lowRemainingNotificationEnabled: autoRoll.lowRemainingNotificationEnabled,
3501
3522
  lowRemainingNotificationThreshold: autoRoll.lowRemainingNotificationThreshold
@@ -3927,7 +3948,7 @@ var import_node_crypto3 = require("crypto");
3927
3948
  var import_fs = require("fs");
3928
3949
  var import_path = __toESM(require("path"), 1);
3929
3950
  var import_path2 = require("path");
3930
- var import_toml = __toESM(require_toml(), 1);
3951
+ var import_toml2 = __toESM(require_toml(), 1);
3931
3952
 
3932
3953
  // ../../packages/contracts/src/profiles/identity.ts
3933
3954
  function normalizeValue(value) {
@@ -3981,6 +4002,7 @@ var import_node_readline = __toESM(require("readline"), 1);
3981
4002
  var import_node_fs3 = require("fs");
3982
4003
  var import_node_os2 = __toESM(require("os"), 1);
3983
4004
  var import_node_path5 = __toESM(require("path"), 1);
4005
+ var import_toml = __toESM(require_toml(), 1);
3984
4006
 
3985
4007
  // ../../packages/runtime-codex/src/codex/cli.ts
3986
4008
  var import_node_fs2 = require("fs");
@@ -4206,6 +4228,12 @@ var ACTIVATION_MODEL = "gpt-5.1-codex-mini";
4206
4228
  var MAX_STDERR_CAPTURE_CHARS = 32768;
4207
4229
  var REFRESH_TOKEN_REDEEMED_SNIPPET = "refresh token was already used";
4208
4230
  var authFileCache = /* @__PURE__ */ new Map();
4231
+ var CODEX_OAUTH_REFRESH_URL = "https://auth.openai.com/oauth/token";
4232
+ var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
4233
+ var CODEX_OAUTH_REFRESH_MAX_AGE_MS = 8 * 24 * 60 * 60 * 1e3;
4234
+ var CODEX_OAUTH_ACCESS_TOKEN_REFRESH_SKEW_MS = 2 * 60 * 1e3;
4235
+ var CODEX_USAGE_DEFAULT_BASE_URL = "https://chatgpt.com/backend-api";
4236
+ var CODEX_USAGE_TIMEOUT_MS = 3e4;
4209
4237
  function isRecord4(value) {
4210
4238
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
4211
4239
  }
@@ -4429,6 +4457,17 @@ function extractJwtIssuedAtMs(token) {
4429
4457
  }
4430
4458
  return issuedAt * 1e3;
4431
4459
  }
4460
+ function extractJwtExpiresAtMs(token) {
4461
+ const payload = decodeJwtPayload(token);
4462
+ if (!payload) {
4463
+ return null;
4464
+ }
4465
+ const expiresAt = payload["exp"];
4466
+ if (typeof expiresAt !== "number" || !Number.isFinite(expiresAt)) {
4467
+ return null;
4468
+ }
4469
+ return expiresAt * 1e3;
4470
+ }
4432
4471
  function parseAuthRecord(content) {
4433
4472
  try {
4434
4473
  const parsed = JSON.parse(content);
@@ -4517,6 +4556,353 @@ async function writeAuthFileCached(filePath, content) {
4517
4556
  authFileCache.delete(filePath);
4518
4557
  }
4519
4558
  }
4559
+ function nonEmptyString(value) {
4560
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
4561
+ }
4562
+ function resolveCodexAuthPathForEnv(envOverride) {
4563
+ return envOverride?.CODEX_HOME ? import_node_path5.default.join(envOverride.CODEX_HOME, "auth.json") : import_node_path5.default.join(
4564
+ envOverride?.HOME ?? process.env.HOME ?? import_node_os2.default.homedir(),
4565
+ ".codex",
4566
+ "auth.json"
4567
+ );
4568
+ }
4569
+ function parseCodexOAuthCredentials(content) {
4570
+ const parsed = parseAuthRecord(content);
4571
+ if (!parsed) {
4572
+ return null;
4573
+ }
4574
+ const tokens = isRecord4(parsed.tokens) ? parsed.tokens : {};
4575
+ const accessToken = nonEmptyString(tokens.access_token) ?? nonEmptyString(parsed.access_token);
4576
+ const refreshToken = nonEmptyString(tokens.refresh_token) ?? nonEmptyString(parsed.refresh_token);
4577
+ if (!accessToken && !refreshToken) {
4578
+ return null;
4579
+ }
4580
+ return {
4581
+ accessToken: accessToken ?? "",
4582
+ refreshToken: refreshToken ?? "",
4583
+ idToken: nonEmptyString(tokens.id_token) ?? nonEmptyString(parsed.id_token),
4584
+ accountId: nonEmptyString(tokens.account_id) ?? nonEmptyString(parsed.account_id),
4585
+ lastRefreshMs: parseTimestamp2(tokens.last_refresh) ?? parseTimestamp2(parsed.last_refresh)
4586
+ };
4587
+ }
4588
+ function shouldRefreshCodexOAuthToken(credentials) {
4589
+ if (!credentials.refreshToken) {
4590
+ return false;
4591
+ }
4592
+ const accessExpiresAt = extractJwtExpiresAtMs(credentials.accessToken);
4593
+ if (accessExpiresAt !== null && accessExpiresAt - Date.now() <= CODEX_OAUTH_ACCESS_TOKEN_REFRESH_SKEW_MS) {
4594
+ return true;
4595
+ }
4596
+ if (credentials.lastRefreshMs === null) {
4597
+ return true;
4598
+ }
4599
+ return Date.now() - credentials.lastRefreshMs > CODEX_OAUTH_REFRESH_MAX_AGE_MS;
4600
+ }
4601
+ async function writeCodexOAuthCredentials(authPath, content, credentials) {
4602
+ let json = {};
4603
+ try {
4604
+ const parsed = JSON.parse(content);
4605
+ if (isRecord4(parsed)) {
4606
+ json = { ...parsed };
4607
+ }
4608
+ } catch {
4609
+ json = {};
4610
+ }
4611
+ const existingTokens = isRecord4(json.tokens) ? json.tokens : {};
4612
+ json.tokens = {
4613
+ ...existingTokens,
4614
+ access_token: credentials.accessToken,
4615
+ refresh_token: credentials.refreshToken,
4616
+ ...credentials.idToken ? { id_token: credentials.idToken } : {},
4617
+ ...credentials.accountId ? { account_id: credentials.accountId } : {},
4618
+ last_refresh: (/* @__PURE__ */ new Date()).toISOString()
4619
+ };
4620
+ json.last_refresh = (/* @__PURE__ */ new Date()).toISOString();
4621
+ await writeAuthFileCached(authPath, `${JSON.stringify(json, null, 2)}
4622
+ `);
4623
+ }
4624
+ async function refreshCodexOAuthCredentials(credentials) {
4625
+ if (!credentials.refreshToken) {
4626
+ return credentials;
4627
+ }
4628
+ const response = await fetch(CODEX_OAUTH_REFRESH_URL, {
4629
+ method: "POST",
4630
+ headers: { "Content-Type": "application/json" },
4631
+ signal: AbortSignal.timeout(CODEX_USAGE_TIMEOUT_MS),
4632
+ body: JSON.stringify({
4633
+ client_id: CODEX_OAUTH_CLIENT_ID,
4634
+ grant_type: "refresh_token",
4635
+ refresh_token: credentials.refreshToken,
4636
+ scope: "openid profile email"
4637
+ })
4638
+ });
4639
+ const body = await response.text();
4640
+ if (!response.ok) {
4641
+ let code = "";
4642
+ try {
4643
+ const parsed2 = JSON.parse(body);
4644
+ if (isRecord4(parsed2)) {
4645
+ const error = isRecord4(parsed2.error) ? parsed2.error : {};
4646
+ code = nonEmptyString(error.code) ?? nonEmptyString(parsed2.error) ?? nonEmptyString(parsed2.code) ?? "";
4647
+ }
4648
+ } catch {
4649
+ code = "";
4650
+ }
4651
+ if (code === "refresh_token_reused") {
4652
+ throw new Error("refresh token was already used");
4653
+ }
4654
+ if (code === "refresh_token_expired") {
4655
+ throw new Error("refresh token expired");
4656
+ }
4657
+ if (code === "invalid_grant" || code === "refresh_token_invalidated") {
4658
+ throw new Error("refresh token was revoked");
4659
+ }
4660
+ throw new Error(`Codex OAuth refresh failed HTTP ${response.status}`);
4661
+ }
4662
+ let parsed;
4663
+ try {
4664
+ parsed = JSON.parse(body);
4665
+ } catch {
4666
+ throw new Error("Codex OAuth refresh returned invalid JSON");
4667
+ }
4668
+ if (!isRecord4(parsed)) {
4669
+ throw new Error("Codex OAuth refresh returned invalid data");
4670
+ }
4671
+ return {
4672
+ accessToken: nonEmptyString(parsed.access_token) ?? credentials.accessToken,
4673
+ refreshToken: nonEmptyString(parsed.refresh_token) ?? credentials.refreshToken,
4674
+ idToken: nonEmptyString(parsed.id_token) ?? credentials.idToken,
4675
+ accountId: credentials.accountId,
4676
+ lastRefreshMs: Date.now()
4677
+ };
4678
+ }
4679
+ async function loadCodexConfigContents(envOverride) {
4680
+ const codexHome = envOverride?.CODEX_HOME?.trim();
4681
+ const root = codexHome || import_node_path5.default.join(envOverride?.HOME ?? process.env.HOME ?? import_node_os2.default.homedir(), ".codex");
4682
+ try {
4683
+ return await import_node_fs3.promises.readFile(import_node_path5.default.join(root, "config.toml"), "utf8");
4684
+ } catch {
4685
+ return null;
4686
+ }
4687
+ }
4688
+ function parseChatGPTBaseUrl(contents) {
4689
+ if (!contents) {
4690
+ return CODEX_USAGE_DEFAULT_BASE_URL;
4691
+ }
4692
+ try {
4693
+ const parsed = (0, import_toml.parse)(contents);
4694
+ if (isRecord4(parsed)) {
4695
+ return nonEmptyString(parsed.chatgpt_base_url) ?? CODEX_USAGE_DEFAULT_BASE_URL;
4696
+ }
4697
+ } catch {
4698
+ return CODEX_USAGE_DEFAULT_BASE_URL;
4699
+ }
4700
+ return CODEX_USAGE_DEFAULT_BASE_URL;
4701
+ }
4702
+ async function resolveCodexUsageUrl(envOverride) {
4703
+ let base = parseChatGPTBaseUrl(await loadCodexConfigContents(envOverride));
4704
+ while (base.endsWith("/")) {
4705
+ base = base.slice(0, -1);
4706
+ }
4707
+ if ((base.startsWith("https://chatgpt.com") || base.startsWith("https://chat.openai.com")) && !base.includes("/backend-api")) {
4708
+ base += "/backend-api";
4709
+ }
4710
+ return `${base}${base.includes("/backend-api") ? "/wham/usage" : "/api/codex/usage"}`;
4711
+ }
4712
+ function numberFromUnknown(value) {
4713
+ if (typeof value === "number" && Number.isFinite(value)) {
4714
+ return value;
4715
+ }
4716
+ if (typeof value === "string" && value.trim()) {
4717
+ const parsed = Number(value.trim());
4718
+ return Number.isFinite(parsed) ? parsed : null;
4719
+ }
4720
+ return null;
4721
+ }
4722
+ function toOAuthWindow(window) {
4723
+ if (!window) {
4724
+ return void 0;
4725
+ }
4726
+ const usedPercent = numberFromUnknown(window.used_percent);
4727
+ const resetAt = numberFromUnknown(window.reset_at);
4728
+ const limitWindowSeconds = numberFromUnknown(window.limit_window_seconds);
4729
+ if (usedPercent === null && resetAt === null && limitWindowSeconds === null) {
4730
+ return void 0;
4731
+ }
4732
+ const result = {};
4733
+ if (usedPercent !== null) {
4734
+ result.usedPercent = Math.max(0, Math.min(100, usedPercent));
4735
+ }
4736
+ if (limitWindowSeconds !== null) {
4737
+ result.windowMinutes = Math.round(limitWindowSeconds / 60);
4738
+ }
4739
+ if (resetAt !== null) {
4740
+ const resetMs = resetAt > 1e10 ? resetAt : resetAt * 1e3;
4741
+ result.resetsAt = new Date(resetMs).toISOString();
4742
+ result.resetsInSeconds = Math.max(0, Math.round((resetMs - Date.now()) / 1e3));
4743
+ }
4744
+ return result;
4745
+ }
4746
+ function firstNonEmptyString(...values) {
4747
+ for (const value of values) {
4748
+ const text = nonEmptyString(value);
4749
+ if (text) {
4750
+ return text;
4751
+ }
4752
+ }
4753
+ return null;
4754
+ }
4755
+ function slugForRateLimitId(value) {
4756
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4757
+ }
4758
+ function isSparkRateLimit(limitName, meteredFeature) {
4759
+ return [limitName, meteredFeature].filter((value) => Boolean(value)).some((value) => value.toLowerCase().includes("spark"));
4760
+ }
4761
+ function sparkWindowIdentity(window, fallback) {
4762
+ const limitWindowSeconds = numberFromUnknown(window.limit_window_seconds);
4763
+ const windowMinutes = limitWindowSeconds !== null ? Math.round(limitWindowSeconds / 60) : null;
4764
+ const kind = windowMinutes !== null && windowMinutes > 0 && windowMinutes <= 6 * 60 ? "five-hour" : windowMinutes !== null && windowMinutes >= 6 * 24 * 60 ? "weekly" : fallback;
4765
+ return kind === "weekly" ? { id: "codex-spark-weekly", label: "Codex Spark Weekly" } : { id: "codex-spark", label: "Codex Spark 5-hour" };
4766
+ }
4767
+ function toExtraRateLimitWindow(args) {
4768
+ const window = toOAuthWindow(args.window);
4769
+ if (!window) {
4770
+ return null;
4771
+ }
4772
+ return {
4773
+ id: args.id,
4774
+ label: args.label,
4775
+ ...args.meteredFeature ? { meteredFeature: args.meteredFeature } : {},
4776
+ ...window
4777
+ };
4778
+ }
4779
+ function parseExtraRateLimitWindows(additionalRateLimits) {
4780
+ if (!Array.isArray(additionalRateLimits)) {
4781
+ return [];
4782
+ }
4783
+ const usedIds = /* @__PURE__ */ new Set();
4784
+ const extraWindows = [];
4785
+ for (const entry of additionalRateLimits) {
4786
+ if (!isRecord4(entry)) {
4787
+ continue;
4788
+ }
4789
+ const limitName = firstNonEmptyString(entry.limit_name);
4790
+ const meteredFeature = firstNonEmptyString(entry.metered_feature);
4791
+ const rateLimit = isRecord4(entry.rate_limit) ? entry.rate_limit : null;
4792
+ if (!rateLimit) {
4793
+ continue;
4794
+ }
4795
+ const primary = isRecord4(rateLimit.primary_window) ? rateLimit.primary_window : null;
4796
+ const secondary = isRecord4(rateLimit.secondary_window) ? rateLimit.secondary_window : null;
4797
+ if (isSparkRateLimit(limitName, meteredFeature)) {
4798
+ const candidates = [
4799
+ { window: primary, fallback: "five-hour" },
4800
+ { window: secondary, fallback: "weekly" }
4801
+ ];
4802
+ for (const candidate of candidates) {
4803
+ if (!candidate.window) {
4804
+ continue;
4805
+ }
4806
+ const identity = sparkWindowIdentity(candidate.window, candidate.fallback);
4807
+ if (usedIds.has(identity.id)) {
4808
+ continue;
4809
+ }
4810
+ const extraWindow2 = toExtraRateLimitWindow({
4811
+ ...identity,
4812
+ meteredFeature,
4813
+ window: candidate.window
4814
+ });
4815
+ if (extraWindow2) {
4816
+ usedIds.add(identity.id);
4817
+ extraWindows.push(extraWindow2);
4818
+ }
4819
+ }
4820
+ continue;
4821
+ }
4822
+ const idSource = firstNonEmptyString(meteredFeature, limitName);
4823
+ const slug = idSource ? slugForRateLimitId(idSource) : "";
4824
+ if (!slug) {
4825
+ continue;
4826
+ }
4827
+ const id = `codex-${slug}`;
4828
+ if (usedIds.has(id)) {
4829
+ continue;
4830
+ }
4831
+ const extraWindow = toExtraRateLimitWindow({
4832
+ id,
4833
+ label: firstNonEmptyString(limitName, meteredFeature) ?? "Codex extra limit",
4834
+ meteredFeature,
4835
+ window: primary ?? secondary
4836
+ });
4837
+ if (extraWindow) {
4838
+ usedIds.add(id);
4839
+ extraWindows.push(extraWindow);
4840
+ }
4841
+ }
4842
+ return extraWindows;
4843
+ }
4844
+ function parseRateLimitSnapshotFromOAuthUsage(value) {
4845
+ if (!isRecord4(value)) {
4846
+ throw new Error("Codex usage API returned invalid data");
4847
+ }
4848
+ const usage = value;
4849
+ const primary = toOAuthWindow(usage.rate_limit?.primary_window ?? null);
4850
+ const secondary = toOAuthWindow(usage.rate_limit?.secondary_window ?? null);
4851
+ const extraWindows = parseExtraRateLimitWindows(usage.additional_rate_limits);
4852
+ if (!primary && !secondary && extraWindows.length === 0) {
4853
+ return null;
4854
+ }
4855
+ return {
4856
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
4857
+ source: "codex-oauth",
4858
+ ...primary ? { primary } : {},
4859
+ ...secondary ? { secondary } : {},
4860
+ ...extraWindows.length > 0 ? { extraWindows } : {}
4861
+ };
4862
+ }
4863
+ async function fetchRateLimitsViaOAuth(envOverride) {
4864
+ const authPath = resolveCodexAuthPathForEnv(envOverride);
4865
+ const authContent = await readAuthFileCached(authPath);
4866
+ if (!authContent) {
4867
+ return null;
4868
+ }
4869
+ let credentials = parseCodexOAuthCredentials(authContent);
4870
+ if (!credentials) {
4871
+ return null;
4872
+ }
4873
+ if (shouldRefreshCodexOAuthToken(credentials)) {
4874
+ credentials = await refreshCodexOAuthCredentials(credentials);
4875
+ await writeCodexOAuthCredentials(authPath, authContent, credentials);
4876
+ }
4877
+ if (!credentials.accessToken) {
4878
+ return null;
4879
+ }
4880
+ const usageUrl = await resolveCodexUsageUrl(envOverride);
4881
+ const response = await fetch(usageUrl, {
4882
+ method: "GET",
4883
+ headers: {
4884
+ Authorization: `Bearer ${credentials.accessToken}`,
4885
+ Accept: "application/json",
4886
+ "User-Agent": "CodexUse",
4887
+ ...credentials.accountId ? { "ChatGPT-Account-Id": credentials.accountId } : {}
4888
+ },
4889
+ signal: AbortSignal.timeout(CODEX_USAGE_TIMEOUT_MS)
4890
+ });
4891
+ const body = await response.text();
4892
+ if (response.status === 401 || response.status === 403) {
4893
+ throw new Error(`Codex OAuth usage failed HTTP ${response.status} (${usageUrl})`);
4894
+ }
4895
+ if (!response.ok) {
4896
+ throw new Error(`Codex usage API failed HTTP ${response.status} (${usageUrl}): ${body.slice(0, 500)}`);
4897
+ }
4898
+ let parsed;
4899
+ try {
4900
+ parsed = JSON.parse(body);
4901
+ } catch {
4902
+ throw new Error("Codex usage API returned invalid JSON");
4903
+ }
4904
+ return parseRateLimitSnapshotFromOAuthUsage(parsed);
4905
+ }
4520
4906
  function inferRefreshFailureHint(stderrOutput) {
4521
4907
  if (!stderrOutput) {
4522
4908
  return null;
@@ -6129,7 +6515,7 @@ var ProfileManager = class {
6129
6515
  }
6130
6516
  let parsed;
6131
6517
  try {
6132
- parsed = (0, import_toml.parse)(raw);
6518
+ parsed = (0, import_toml2.parse)(raw);
6133
6519
  } catch (error) {
6134
6520
  logWarn(`Failed to parse config.toml for profile home '${profileHome}':`, error);
6135
6521
  return;
@@ -6139,7 +6525,7 @@ var ProfileManager = class {
6139
6525
  }
6140
6526
  delete parsed["model_reasoning_effort"];
6141
6527
  const sanitized = this.ensureTrailingNewline(
6142
- (0, import_toml.stringify)(parsed)
6528
+ (0, import_toml2.stringify)(parsed)
6143
6529
  );
6144
6530
  try {
6145
6531
  await import_fs.promises.writeFile(configPath, sanitized, "utf8");
@@ -6503,7 +6889,16 @@ var ProfileManager = class {
6503
6889
  profileName,
6504
6890
  () => this.runWithPreparedProfileHome(
6505
6891
  profileName,
6506
- (env) => fetchRateLimitsViaRpc(env, { codexPath: options.codexPath }),
6892
+ async (env) => {
6893
+ try {
6894
+ const oauthSnapshot = await fetchRateLimitsViaOAuth(env);
6895
+ if (oauthSnapshot) {
6896
+ return oauthSnapshot;
6897
+ }
6898
+ } catch {
6899
+ }
6900
+ return fetchRateLimitsViaRpc(env, { codexPath: options.codexPath });
6901
+ },
6507
6902
  { syncFromActiveAuthBeforeAction: false }
6508
6903
  )
6509
6904
  );
@@ -7877,7 +8272,8 @@ function maxUsedPercent(snapshot) {
7877
8272
  }
7878
8273
  const candidates = [
7879
8274
  snapshot.primary?.usedPercent,
7880
- snapshot.secondary?.usedPercent
8275
+ snapshot.secondary?.usedPercent,
8276
+ ...(snapshot.extraWindows ?? []).map((window) => window.usedPercent)
7881
8277
  ].filter((value) => typeof value === "number" && Number.isFinite(value));
7882
8278
  if (candidates.length === 0) {
7883
8279
  return null;
@@ -8006,7 +8402,8 @@ function formatUsagePercent(value) {
8006
8402
  function snapshotResetSeconds(snapshot) {
8007
8403
  const values = [
8008
8404
  snapshot?.primary?.resetsInSeconds,
8009
- snapshot?.secondary?.resetsInSeconds
8405
+ snapshot?.secondary?.resetsInSeconds,
8406
+ ...(snapshot?.extraWindows ?? []).map((window) => window.resetsInSeconds)
8010
8407
  ].filter((value) => typeof value === "number" && Number.isFinite(value));
8011
8408
  return values.length > 0 ? Math.min(...values) : null;
8012
8409
  }
@@ -12743,9 +13140,12 @@ function isMainProcessRow(row, candidate) {
12743
13140
  }
12744
13141
  function findAppServerPid(rows, mainPid) {
12745
13142
  return rows.find(
12746
- (row) => row.ppid === mainPid && row.args.includes("/Contents/Resources/codex app-server")
13143
+ (row) => row.ppid === mainPid && isAppServerProcessRow(row)
12747
13144
  )?.pid ?? null;
12748
13145
  }
13146
+ function isAppServerProcessRow(row) {
13147
+ return row.args.includes("/Contents/Resources/codex app-server");
13148
+ }
12749
13149
  async function processMatchesProfileHome(pid, profileHome) {
12750
13150
  const result = await runCommand2(
12751
13151
  "/bin/ps",
@@ -12759,7 +13159,7 @@ async function processMatchesProfileHome(pid, profileHome) {
12759
13159
  }
12760
13160
  async function findAppServerPidForProfile(rows, mainPid, profileHome) {
12761
13161
  const candidates = rows.filter(
12762
- (row) => row.ppid === mainPid && row.args.includes("/Contents/Resources/codex app-server")
13162
+ (row) => row.ppid === mainPid && isAppServerProcessRow(row)
12763
13163
  );
12764
13164
  for (const row of candidates) {
12765
13165
  if (row.args.includes(profileHome) || await processMatchesProfileHome(row.pid, profileHome)) {
@@ -12768,6 +13168,19 @@ async function findAppServerPidForProfile(rows, mainPid, profileHome) {
12768
13168
  }
12769
13169
  return null;
12770
13170
  }
13171
+ async function resolveStoredAppServerPid(instance, rows) {
13172
+ if (!instance.appServerPid) {
13173
+ return null;
13174
+ }
13175
+ const row = rows.find((entry) => entry.pid === instance.appServerPid);
13176
+ if (!row || !isAppServerProcessRow(row)) {
13177
+ return null;
13178
+ }
13179
+ if (instance.profileHome && !row.args.includes(instance.profileHome) && !await processMatchesProfileHome(row.pid, instance.profileHome)) {
13180
+ return null;
13181
+ }
13182
+ return row.pid;
13183
+ }
12771
13184
  function parseRemoteDebuggingPort(args) {
12772
13185
  const match = args.match(/--remote-debugging-port(?:=|\s+)(\d{2,5})\b/);
12773
13186
  const port = Number(match?.[1]);
@@ -12896,11 +13309,27 @@ async function readManagedInstance(profileName) {
12896
13309
  async function resolveInstanceRuntimeState(args) {
12897
13310
  const pid = args.instance.pid;
12898
13311
  if (!pid) {
12899
- return { running: false, appServerPid: null, startedAt: null };
13312
+ const appServerPid2 = await resolveStoredAppServerPid(
13313
+ args.instance,
13314
+ args.rows
13315
+ );
13316
+ return {
13317
+ running: appServerPid2 !== null,
13318
+ appServerPid: appServerPid2,
13319
+ startedAt: null
13320
+ };
12900
13321
  }
12901
13322
  const mainRow = args.rows.find((row) => row.pid === pid);
12902
13323
  if (!mainRow || !isMainProcessRow(mainRow, args.candidate)) {
12903
- return { running: false, appServerPid: null, startedAt: null };
13324
+ const appServerPid2 = await resolveStoredAppServerPid(
13325
+ args.instance,
13326
+ args.rows
13327
+ );
13328
+ return {
13329
+ running: appServerPid2 !== null,
13330
+ appServerPid: appServerPid2,
13331
+ startedAt: null
13332
+ };
12904
13333
  }
12905
13334
  const appServerPid = args.instance.profileHome ? await findAppServerPidForProfile(args.rows, pid, args.instance.profileHome) : findAppServerPid(args.rows, pid);
12906
13335
  if (args.instance.profileHome && appServerPid === null) {
@@ -13813,8 +14242,8 @@ function formatResetText(window) {
13813
14242
  if (durationText === "now") return "now";
13814
14243
  return `in ${durationText}`;
13815
14244
  }
13816
- function formatWindowUsage(window) {
13817
- const label = formatWindowLabel(window.windowMinutes) ?? "window";
14245
+ function formatWindowUsage(window, labelOverride) {
14246
+ const label = labelOverride ?? formatWindowLabel(window.windowMinutes) ?? "window";
13818
14247
  const usedPercent = typeof window.usedPercent === "number" && Number.isFinite(window.usedPercent) ? `${Math.round(Math.max(0, Math.min(100, window.usedPercent)))}%` : "n/a";
13819
14248
  const resetText = formatResetText(window);
13820
14249
  return {
@@ -13943,6 +14372,10 @@ async function resolveProfileUsage(manager, profile, codexPath) {
13943
14372
  const row = formatWindowUsage(snapshot.secondary);
13944
14373
  if (row) rows.push(row);
13945
14374
  }
14375
+ for (const window of snapshot.extraWindows ?? []) {
14376
+ const row = formatWindowUsage(window, window.label);
14377
+ if (row) rows.push(row);
14378
+ }
13946
14379
  const maxPercent = maxUsedPercent(snapshot);
13947
14380
  const summary = typeof maxPercent === "number" ? `${Math.round(maxPercent)}%` : "n/a";
13948
14381
  return { summary, rows: rows.length > 0 ? rows : null, usageSummary: summarizeRateLimitSnapshot(snapshot) };
@@ -14833,7 +15266,7 @@ async function pushRemoteSnapshot(licenseKey, snapshot, options) {
14833
15266
  }
14834
15267
 
14835
15268
  // ../../packages/runtime-codex/src/codex/config.ts
14836
- var import_toml2 = __toESM(require_toml(), 1);
15269
+ var import_toml3 = __toESM(require_toml(), 1);
14837
15270
 
14838
15271
  // ../../packages/runtime-codex/src/codex/config-metadata.ts
14839
15272
  var import_node_child_process6 = require("child_process");
@@ -15472,7 +15905,7 @@ function formatUserFacingError(error, options = {}) {
15472
15905
  // ../../packages/runtime-codex/src/codex/config.ts
15473
15906
  function tryParseToml(content) {
15474
15907
  try {
15475
- const parsed = (0, import_toml2.parse)(content);
15908
+ const parsed = (0, import_toml3.parse)(content);
15476
15909
  return { data: parsed ?? {}, error: null };
15477
15910
  } catch (error) {
15478
15911
  const maybe = error;
@@ -16157,7 +16590,7 @@ async function ensureCliStorageReady() {
16157
16590
  }
16158
16591
 
16159
16592
  // src/app/main.ts
16160
- var VERSION = true ? "5.0.2" : "0.0.0";
16593
+ var VERSION = true ? "5.0.6" : "0.0.0";
16161
16594
  async function runCli() {
16162
16595
  const args = process.argv.slice(2);
16163
16596
  if (args.length === 0) {