opencode-enhancer 1.0.8 → 1.1.0

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.
@@ -1,28 +1,28 @@
1
1
  // CLI usage command: formatted table output for provider usage
2
- import { fetchAllUsage, allProviders } from './providers/index.js';
3
- import { decodeJwtPayload } from './jwt.js';
4
- import { loadStore } from './store.js';
5
- import { readUsageCache, writeUsageCache } from './usage-cache.js';
2
+ import { fetchAllUsage, allProviders } from "./providers/index.js";
3
+ import { decodeJwtPayload } from "./jwt.js";
4
+ import { loadStore } from "./store.js";
5
+ import { readUsageCache, writeUsageCache } from "./usage-cache.js";
6
6
  // ── ANSI helpers ──────────────────────────────────────────────
7
- const isColorSupported = process.env.FORCE_COLOR !== '0' &&
7
+ const isColorSupported = process.env.FORCE_COLOR !== "0" &&
8
8
  process.env.NO_COLOR === undefined &&
9
9
  (process.stdout.isTTY || process.env.FORCE_COLOR !== undefined);
10
10
  const c = {
11
- reset: isColorSupported ? '\x1b[0m' : '',
12
- bold: isColorSupported ? '\x1b[1m' : '',
13
- dim: isColorSupported ? '\x1b[2m' : '',
14
- red: isColorSupported ? '\x1b[31m' : '',
15
- green: isColorSupported ? '\x1b[32m' : '',
16
- yellow: isColorSupported ? '\x1b[33m' : '',
17
- blue: isColorSupported ? '\x1b[34m' : '',
18
- cyan: isColorSupported ? '\x1b[36m' : '',
19
- gray: isColorSupported ? '\x1b[90m' : '',
20
- white: isColorSupported ? '\x1b[37m' : '',
11
+ reset: isColorSupported ? "\x1b[0m" : "",
12
+ bold: isColorSupported ? "\x1b[1m" : "",
13
+ dim: isColorSupported ? "\x1b[2m" : "",
14
+ red: isColorSupported ? "\x1b[31m" : "",
15
+ green: isColorSupported ? "\x1b[32m" : "",
16
+ yellow: isColorSupported ? "\x1b[33m" : "",
17
+ blue: isColorSupported ? "\x1b[34m" : "",
18
+ cyan: isColorSupported ? "\x1b[36m" : "",
19
+ gray: isColorSupported ? "\x1b[90m" : "",
20
+ white: isColorSupported ? "\x1b[37m" : "",
21
21
  };
22
22
  // ── Formatting helpers ────────────────────────────────────────
23
23
  function formatDuration(ms) {
24
24
  if (ms <= 0)
25
- return 'now';
25
+ return "now";
26
26
  const totalSeconds = Math.floor(ms / 1000);
27
27
  const days = Math.floor(totalSeconds / 86400);
28
28
  const hours = Math.floor((totalSeconds % 86400) / 3600);
@@ -52,10 +52,10 @@ function buildBar(pct, width = 8) {
52
52
  const filled = Math.round((pct / 100) * width);
53
53
  const empty = width - filled;
54
54
  const color = utilizationColor(pct);
55
- return `${color}${''.repeat(filled)}${c.dim}${''.repeat(empty)}${c.reset}`;
55
+ return `${color}${"".repeat(filled)}${c.dim}${"".repeat(empty)}${c.reset}`;
56
56
  }
57
57
  function stripAnsi(str) {
58
- return str.replace(/\x1b\[[0-9;]*m/g, '');
58
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
59
59
  }
60
60
  function visibleLength(str) {
61
61
  return stripAnsi(str).length;
@@ -67,58 +67,266 @@ function truncateText(str, maxLen) {
67
67
  if (str.length <= maxLen)
68
68
  return str;
69
69
  if (maxLen <= 1)
70
- return '';
70
+ return "";
71
71
  return `${str.slice(0, maxLen - 1)}…`;
72
72
  }
73
73
  function padRight(str, len) {
74
74
  const pad = Math.max(0, len - visibleLength(str));
75
- return str + ' '.repeat(pad);
75
+ return str + " ".repeat(pad);
76
76
  }
77
77
  function padLeft(str, len) {
78
78
  const pad = Math.max(0, len - visibleLength(str));
79
- return ' '.repeat(pad) + str;
79
+ return " ".repeat(pad) + str;
80
80
  }
81
- const TABLE_INDENT = ' ';
82
- const COLUMN_GAP = ' ';
81
+ const TABLE_INDENT = " ";
82
+ const COLUMN_GAP = " ";
83
83
  const MINI_BAR_WIDTH = 4;
84
84
  function normalizeWindowLabel(label) {
85
85
  return label.trim().toLowerCase();
86
86
  }
87
87
  function abbreviateWindowLabel(label) {
88
88
  const normalized = normalizeWindowLabel(label);
89
- if (normalized === 'weekly')
90
- return 'wk';
91
- if (normalized === 'monthly')
92
- return 'mo';
89
+ if (normalized === "weekly")
90
+ return "wk";
91
+ if (normalized === "monthly")
92
+ return "mo";
93
93
  return truncateText(label, 14);
94
94
  }
95
95
  function formatCount(value) {
96
96
  if (!Number.isFinite(value))
97
- return '0';
97
+ return "0";
98
98
  if (Math.abs(value) >= 1000) {
99
- return new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 }).format(value);
99
+ return new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 }).format(value);
100
100
  }
101
101
  if (Number.isInteger(value))
102
102
  return String(value);
103
- return new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 }).format(value);
103
+ return new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 }).format(value);
104
104
  }
105
105
  function getUsageWindows(usage) {
106
- if (usage.type !== 'quotaBased')
106
+ if (usage.type !== "quotaBased")
107
107
  return [];
108
- return usage.windows.filter((window) => window.label !== 'balance' && !window.label.startsWith('$'));
108
+ return usage.windows.filter((window) => window.label !== "balance" && !window.label.startsWith("$"));
109
109
  }
110
110
  function formatMiniChart(utilization) {
111
111
  return buildBar(utilization, MINI_BAR_WIDTH);
112
112
  }
113
+ function getUsageTotals(usage) {
114
+ if (usage.type === "payAsYouGo") {
115
+ return {
116
+ usedLabel: `$${usage.used.toFixed(2)}`,
117
+ totalLabel: `$${usage.total.toFixed(2)}`,
118
+ remainingLabel: `$${usage.remaining.toFixed(2)}`,
119
+ };
120
+ }
121
+ if (typeof usage.entitlement === "number" &&
122
+ typeof usage.remaining === "number" &&
123
+ usage.entitlement > 0) {
124
+ const remaining = Math.max(0, usage.remaining);
125
+ const used = usage.remaining < 0
126
+ ? usage.entitlement + Math.abs(usage.remaining)
127
+ : usage.entitlement - remaining;
128
+ return {
129
+ usedLabel: formatCount(used),
130
+ totalLabel: formatCount(usage.entitlement),
131
+ remainingLabel: formatCount(remaining),
132
+ };
133
+ }
134
+ const usedPct = Math.round(usage.utilization);
135
+ const remainingPct = Math.max(0, 100 - usedPct);
136
+ return {
137
+ usedLabel: `${usedPct}%`,
138
+ totalLabel: "100%",
139
+ remainingLabel: `${remainingPct}%`,
140
+ };
141
+ }
142
+ function hasAbsoluteQuotaTotals(usage) {
143
+ return (usage.type === "quotaBased" &&
144
+ typeof usage.entitlement === "number" &&
145
+ typeof usage.remaining === "number" &&
146
+ usage.entitlement > 0);
147
+ }
148
+ function formatUsageSummary(usage) {
149
+ const totals = getUsageTotals(usage);
150
+ const pctColor = utilizationColor(usage.utilization);
151
+ const parts = [
152
+ `${formatMiniChart(usage.utilization)} ${pctColor}${Math.round(usage.utilization)}%${c.reset}`,
153
+ `${c.red}${totals.usedLabel}${c.reset} ${c.dim}used${c.reset}`,
154
+ `${c.cyan}${totals.totalLabel}${c.reset} ${c.dim}total${c.reset}`,
155
+ `${c.green}${totals.remainingLabel}${c.reset} ${c.dim}left${c.reset}`,
156
+ ];
157
+ return parts.join(` ${c.dim}·${c.reset} `);
158
+ }
159
+ function formatQuotaWindowDetails(window, usage, index) {
160
+ const used = Math.round(window.utilization);
161
+ const left = Math.max(0, 100 - used);
162
+ const totals = index === 0 && hasAbsoluteQuotaTotals(usage) ? getUsageTotals(usage) : undefined;
163
+ const parts = [
164
+ `${c.bold}${window.label}${c.reset}`,
165
+ `${formatMiniChart(window.utilization)} ${utilizationColor(window.utilization)}${used}%${c.reset} ${c.dim}used${c.reset}`,
166
+ ];
167
+ if (totals) {
168
+ parts.push(`${c.red}${totals.usedLabel}${c.reset} ${c.dim}used${c.reset}`, `${c.cyan}${totals.totalLabel}${c.reset} ${c.dim}total${c.reset}`, `${c.green}${totals.remainingLabel}${c.reset} ${c.dim}left${c.reset}`);
169
+ }
170
+ else {
171
+ parts.push(`${utilizationColor(window.utilization)}${left}%${c.reset} ${c.dim}left${c.reset}`);
172
+ }
173
+ parts.push(`${c.dim}resets ${formatResetTime(window.resetsAt)}${c.reset}`);
174
+ return parts.join(` ${c.dim}·${c.reset} `);
175
+ }
176
+ function formatUsageDetailLines(usage) {
177
+ if (usage.type === "payAsYouGo") {
178
+ return [
179
+ `${c.dim}credits${c.reset} ${c.red}$${usage.used.toFixed(2)}${c.reset} ${c.dim}used${c.reset} ${c.dim}·${c.reset} ${c.cyan}$${usage.total.toFixed(2)}${c.reset} ${c.dim}total${c.reset} ${c.dim}·${c.reset} ${c.green}$${usage.remaining.toFixed(2)}${c.reset} ${c.dim}left${c.reset}`,
180
+ ];
181
+ }
182
+ return getUsageWindows(usage).map((window, index) => formatQuotaWindowDetails(window, usage, index));
183
+ }
184
+ function getStatusLabel(status) {
185
+ if (status === "auth_expired")
186
+ return "auth expired";
187
+ if (status === "not_configured")
188
+ return "not configured";
189
+ return status;
190
+ }
191
+ function formatStatusCell(status, width) {
192
+ const label = getStatusLabel(status);
193
+ const color = status === "ok"
194
+ ? c.green
195
+ : status === "not_configured"
196
+ ? c.dim
197
+ : status === "auth_expired"
198
+ ? c.yellow
199
+ : c.red;
200
+ return padRight(`${color}${label}${c.reset}`, width);
201
+ }
202
+ function formatMetricCell(value, width, color) {
203
+ return padLeft(`${color}${value}${c.reset}`, width);
204
+ }
205
+ function getPrimaryResetLabel(usage) {
206
+ if (!usage || usage.type !== "quotaBased")
207
+ return "—";
208
+ const resets = usage.windows
209
+ .filter((window) => window.resetsAt && window.resetsAt > Date.now())
210
+ .sort((a, b) => (a.resetsAt || 0) - (b.resetsAt || 0));
211
+ return resets.length > 0 ? stripAnsi(formatResetTime(resets[0].resetsAt)) : "—";
212
+ }
213
+ function getWindowTotals(window) {
214
+ if (typeof window.entitlement !== "number" ||
215
+ typeof window.remaining !== "number" ||
216
+ window.entitlement <= 0) {
217
+ return undefined;
218
+ }
219
+ const remaining = Math.max(0, window.remaining);
220
+ const used = window.remaining < 0
221
+ ? window.entitlement + Math.abs(window.remaining)
222
+ : window.entitlement - remaining;
223
+ return {
224
+ usedLabel: formatCount(used),
225
+ totalLabel: formatCount(window.entitlement),
226
+ remainingLabel: formatCount(remaining),
227
+ };
228
+ }
229
+ function formatWindowDetail(window) {
230
+ const pctUsed = Math.round(window.utilization);
231
+ const pctLeft = Math.max(0, 100 - pctUsed);
232
+ const totals = getWindowTotals(window);
233
+ const parts = totals
234
+ ? [
235
+ `${c.red}${totals.usedLabel}${c.reset} ${c.dim}used${c.reset}`,
236
+ `${c.cyan}${totals.totalLabel}${c.reset} ${c.dim}total${c.reset}`,
237
+ `${c.green}${totals.remainingLabel}${c.reset} ${c.dim}left${c.reset}`,
238
+ `${utilizationColor(window.utilization)}${pctUsed}%${c.reset}`,
239
+ ]
240
+ : [
241
+ `${utilizationColor(window.utilization)}${pctUsed}%${c.reset} ${c.dim}used${c.reset}`,
242
+ `${c.green}${pctLeft}%${c.reset} ${c.dim}left${c.reset}`,
243
+ ];
244
+ parts.push(`${c.dim}resets ${stripAnsi(formatResetTime(window.resetsAt))}${c.reset}`);
245
+ return `${c.bold}${window.label}${c.reset}: ${parts.join(` ${c.dim}·${c.reset} `)}`;
246
+ }
247
+ function getResultDetailLines(result, verbose) {
248
+ if (result.status === "error" || result.status === "auth_expired") {
249
+ return result.error ? [result.error] : [];
250
+ }
251
+ if (result.status !== "ok" || !result.usage)
252
+ return [];
253
+ if (result.usage.type === "payAsYouGo") {
254
+ return verbose ? formatUsageDetailLines(result.usage) : [];
255
+ }
256
+ const windows = getUsageWindows(result.usage);
257
+ if (windows.length === 0)
258
+ return [];
259
+ if (!verbose && windows.length === 1)
260
+ return [];
261
+ return windows.map((window) => formatWindowDetail(window));
262
+ }
263
+ function buildSummaryRow(result, context, verbose, labels) {
264
+ const rawLabel = labels?.rawLabel;
265
+ const provider = labels?.displayLabel || getDisplayLabel(result, rawLabel, context);
266
+ const plan = getPlanLabel(result, result.usage, rawLabel, context, result.plan) || "—";
267
+ if (result.status !== "ok" || !result.usage) {
268
+ return {
269
+ provider,
270
+ status: result.status,
271
+ plan,
272
+ used: "—",
273
+ total: "—",
274
+ left: "—",
275
+ reset: "—",
276
+ details: getResultDetailLines(result, verbose),
277
+ };
278
+ }
279
+ const totals = getUsageTotals(result.usage);
280
+ return {
281
+ provider,
282
+ status: result.status,
283
+ plan,
284
+ used: totals.usedLabel,
285
+ total: totals.totalLabel,
286
+ left: totals.remainingLabel,
287
+ reset: getPrimaryResetLabel(result.usage),
288
+ usage: result.usage,
289
+ details: getResultDetailLines(result, verbose),
290
+ };
291
+ }
292
+ function getSummaryTableLayout(rows) {
293
+ return {
294
+ providerWidth: Math.max(18, ...rows.map((row) => visibleLength(row.provider)), visibleLength("Provider / Account")),
295
+ statusWidth: Math.max(14, ...rows.map((row) => visibleLength(getStatusLabel(row.status))), visibleLength("Status")),
296
+ planWidth: Math.max(10, ...rows.map((row) => visibleLength(row.plan)), visibleLength("Plan")),
297
+ usedWidth: Math.max(8, ...rows.map((row) => visibleLength(row.used)), visibleLength("Used")),
298
+ totalWidth: Math.max(8, ...rows.map((row) => visibleLength(row.total)), visibleLength("Total")),
299
+ leftWidth: Math.max(8, ...rows.map((row) => visibleLength(row.left)), visibleLength("Left")),
300
+ resetWidth: Math.max(8, ...rows.map((row) => visibleLength(row.reset)), visibleLength("Reset")),
301
+ };
302
+ }
303
+ function formatSummaryRow(row, layout) {
304
+ const providerCell = padRight(row.provider, layout.providerWidth);
305
+ const planCell = padRight(row.plan === "—" ? `${c.dim}—${c.reset}` : `${c.cyan}${row.plan}${c.reset}`, layout.planWidth);
306
+ const usedCell = row.status === "ok"
307
+ ? formatMetricCell(row.used, layout.usedWidth, c.red)
308
+ : padLeft(`${c.dim}${row.used}${c.reset}`, layout.usedWidth);
309
+ const totalCell = row.status === "ok"
310
+ ? formatMetricCell(row.total, layout.totalWidth, c.cyan)
311
+ : padLeft(`${c.dim}${row.total}${c.reset}`, layout.totalWidth);
312
+ const leftCell = row.status === "ok"
313
+ ? formatMetricCell(row.left, layout.leftWidth, c.green)
314
+ : padLeft(`${c.dim}${row.left}${c.reset}`, layout.leftWidth);
315
+ const resetCell = padLeft(row.reset === "—" ? `${c.dim}—${c.reset}` : row.reset, layout.resetWidth);
316
+ return `${TABLE_INDENT}${providerCell}${COLUMN_GAP}${formatStatusCell(row.status, layout.statusWidth)}${COLUMN_GAP}${planCell}${COLUMN_GAP}${usedCell}${COLUMN_GAP}${totalCell}${COLUMN_GAP}${leftCell}${COLUMN_GAP}${resetCell}`;
317
+ }
318
+ function formatDetailLine(detail) {
319
+ return `${TABLE_INDENT} ${c.dim}↳${c.reset} ${detail}`;
320
+ }
113
321
  function formatQuotaWindow(window, usage) {
114
322
  const used = Math.round(window.utilization);
115
323
  const left = Math.max(0, 100 - used);
116
324
  const color = utilizationColor(used);
117
325
  const label = `${c.dim}${abbreviateWindowLabel(window.label)}${c.reset}`;
118
326
  const chart = formatMiniChart(window.utilization);
119
- if (usage.type === 'quotaBased' &&
120
- typeof usage.remaining === 'number' &&
121
- typeof usage.entitlement === 'number' &&
327
+ if (usage.type === "quotaBased" &&
328
+ typeof usage.remaining === "number" &&
329
+ typeof usage.entitlement === "number" &&
122
330
  usage.entitlement > 0 &&
123
331
  getUsageWindows(usage)[0] === window) {
124
332
  return `${label} ${chart} ${c.green}${formatCount(usage.remaining)}${c.reset}${c.dim}/${c.reset}${c.cyan}${formatCount(usage.entitlement)}${c.reset}`;
@@ -126,19 +334,7 @@ function formatQuotaWindow(window, usage) {
126
334
  return `${label} ${chart} ${color}${used}${c.reset}${c.dim}/${c.reset}${color}${left}${c.reset}`;
127
335
  }
128
336
  function formatUsageDetails(usage) {
129
- if (usage.type === 'payAsYouGo') {
130
- return `${formatMiniChart(usage.utilization)} $${usage.used.toFixed(2)} / $${usage.total.toFixed(2)}`;
131
- }
132
- const windows = getUsageWindows(usage);
133
- const parts = windows.slice(0, 2).map((window) => formatQuotaWindow(window, usage));
134
- if (windows.length > 2) {
135
- parts.push(`${c.dim}+${windows.length - 2} more${c.reset}`);
136
- }
137
- if (parts.length > 0) {
138
- return parts.join(` ${c.dim}·${c.reset} `);
139
- }
140
- const pctColor = utilizationColor(usage.utilization);
141
- return `${pctColor}${Math.round(usage.utilization)}%${c.reset}`;
337
+ return formatUsageSummary(usage);
142
338
  }
143
339
  function formatPlanValue(rawPlan) {
144
340
  if (!rawPlan)
@@ -146,22 +342,22 @@ function formatPlanValue(rawPlan) {
146
342
  const trimmed = rawPlan.trim();
147
343
  if (!trimmed)
148
344
  return undefined;
149
- const normalized = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, '_');
345
+ const normalized = trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "_");
150
346
  const knownPlans = {
151
- plus: 'Plus',
152
- pro: 'Pro',
153
- team: 'Team',
154
- business: 'Business',
155
- enterprise: 'Enterprise',
156
- free: 'Free',
157
- individual_pro: 'Individual Pro',
158
- individual_free: 'Individual Free',
159
- chatgpt_plus: 'Plus',
160
- chatgptplus: 'Plus',
161
- chatgpt_pro: 'Pro',
162
- chatgptpro: 'Pro',
163
- chatgpt_team: 'Team',
164
- chatgptteam: 'Team',
347
+ plus: "Plus",
348
+ pro: "Pro",
349
+ team: "Team",
350
+ business: "Business",
351
+ enterprise: "Enterprise",
352
+ free: "Free",
353
+ individual_pro: "Individual Pro",
354
+ individual_free: "Individual Free",
355
+ chatgpt_plus: "Plus",
356
+ chatgptplus: "Plus",
357
+ chatgpt_pro: "Pro",
358
+ chatgptpro: "Pro",
359
+ chatgpt_team: "Team",
360
+ chatgptteam: "Team",
165
361
  };
166
362
  const known = knownPlans[normalized];
167
363
  if (known)
@@ -170,10 +366,10 @@ function formatPlanValue(rawPlan) {
170
366
  .split(/[_\s-]+/)
171
367
  .filter(Boolean)
172
368
  .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
173
- .join(' ');
369
+ .join(" ");
174
370
  }
175
371
  function extractPlanFromUsage(usage) {
176
- if (!usage || usage.type !== 'quotaBased')
372
+ if (!usage || usage.type !== "quotaBased")
177
373
  return undefined;
178
374
  for (const window of usage.windows) {
179
375
  const match = window.label.match(/\(([^()]+)\)/);
@@ -183,22 +379,22 @@ function extractPlanFromUsage(usage) {
183
379
  return undefined;
184
380
  }
185
381
  function extractPlanFromProviderName(result) {
186
- if (result.providerId !== 'copilot')
382
+ if (result.providerId !== "copilot")
187
383
  return undefined;
188
384
  const match = result.providerName.match(/\(([^()]+)\)\s*$/);
189
385
  return match?.[1] ? formatPlanValue(match[1]) : undefined;
190
386
  }
191
387
  function getProviderDisplayName(result) {
192
- if (result.providerId === 'copilot') {
193
- return result.providerName.replace(/\s*\([^()]+\)\s*$/, '');
388
+ if (result.providerId === "copilot") {
389
+ return result.providerName.replace(/\s*\([^()]+\)\s*$/, "");
194
390
  }
195
391
  return result.providerName;
196
392
  }
197
393
  function getPlanTypeFromClaims(claims) {
198
394
  if (!claims)
199
395
  return undefined;
200
- const auth = claims['https://api.openai.com/auth'];
201
- return typeof auth?.chatgpt_plan_type === 'string' ? auth.chatgpt_plan_type : undefined;
396
+ const auth = claims["https://api.openai.com/auth"];
397
+ return typeof auth?.chatgpt_plan_type === "string" ? auth.chatgpt_plan_type : undefined;
202
398
  }
203
399
  function getCodexPlanFromAccount(account) {
204
400
  const explicitPlan = formatPlanValue(account.planType);
@@ -207,7 +403,7 @@ function getCodexPlanFromAccount(account) {
207
403
  const accessTokenPlan = formatPlanValue(getPlanTypeFromClaims(decodeJwtPayload(account.accessToken)));
208
404
  if (accessTokenPlan)
209
405
  return accessTokenPlan;
210
- const idTokenPlan = formatPlanValue(getPlanTypeFromClaims(decodeJwtPayload(account.idToken || '')));
406
+ const idTokenPlan = formatPlanValue(getPlanTypeFromClaims(decodeJwtPayload(account.idToken || "")));
211
407
  if (idTokenPlan)
212
408
  return idTokenPlan;
213
409
  return undefined;
@@ -231,7 +427,7 @@ function hasAccountRows(result) {
231
427
  }
232
428
  function getDisplayLabel(result, rawLabel, context) {
233
429
  const label = rawLabel || getProviderDisplayName(result);
234
- if (result.providerId === 'codex' && rawLabel && rawLabel === context.activeAlias) {
430
+ if (result.providerId === "codex" && rawLabel && rawLabel === context.activeAlias) {
235
431
  return `${rawLabel} ${c.cyan}(active)${c.reset}`;
236
432
  }
237
433
  return label;
@@ -239,14 +435,16 @@ function getDisplayLabel(result, rawLabel, context) {
239
435
  function normalizePlanLookupLabel(label) {
240
436
  if (!label)
241
437
  return undefined;
242
- const plain = stripAnsi(label).replace(/\s+\(active\)\s*$/, '').trim();
438
+ const plain = stripAnsi(label)
439
+ .replace(/\s+\(active\)\s*$/, "")
440
+ .trim();
243
441
  return plain || undefined;
244
442
  }
245
443
  function getPlanLabel(result, usage, rawLabel, context, explicitPlan) {
246
444
  const directPlan = formatPlanValue(explicitPlan || result.plan);
247
445
  if (directPlan)
248
446
  return directPlan;
249
- if (result.providerId === 'codex') {
447
+ if (result.providerId === "codex") {
250
448
  const lookupLabel = normalizePlanLookupLabel(rawLabel);
251
449
  if (lookupLabel) {
252
450
  return context.codexPlansByAlias.get(lookupLabel);
@@ -258,14 +456,14 @@ function formatPlanCell(plan, width) {
258
456
  if (!plan) {
259
457
  return padRight(`${c.dim}—${c.reset}`, width);
260
458
  }
261
- return padRight(`${c.cyan}${truncateText(plan, width)}${c.reset}`, width);
459
+ return padRight(`${c.cyan}${plan}${c.reset}`, width);
262
460
  }
263
461
  function getTableLayout(results, context) {
264
462
  const labels = results.flatMap((result) => [
265
463
  getProviderDisplayName(result),
266
464
  ...(result.accounts?.map((acc) => ` ${getDisplayLabel(result, acc.label, context)}`) ?? []),
267
465
  ]);
268
- const providerWidth = clamp(Math.max(18, ...labels.map((label) => visibleLength(label))), 18, 34);
466
+ const providerWidth = clamp(Math.max(18, ...labels.map((label) => visibleLength(label))), 18, Number.MAX_SAFE_INTEGER);
269
467
  const planLengths = results.flatMap((result) => {
270
468
  const plans = [];
271
469
  if (!hasAccountRows(result)) {
@@ -274,9 +472,9 @@ function getTableLayout(results, context) {
274
472
  for (const account of result.accounts ?? []) {
275
473
  plans.push(getPlanLabel(result, account.usage, account.label, context, account.plan));
276
474
  }
277
- return plans.map((plan) => visibleLength(plan ?? ''));
475
+ return plans.map((plan) => visibleLength(plan ?? ""));
278
476
  });
279
- const planWidth = clamp(Math.max(10, ...planLengths), 10, 24);
477
+ const planWidth = clamp(Math.max(10, ...planLengths), 10, Number.MAX_SAFE_INTEGER);
280
478
  return {
281
479
  providerWidth,
282
480
  usageWidth: clamp(Math.max(18, ...results.flatMap((result) => {
@@ -287,7 +485,7 @@ function getTableLayout(results, context) {
287
485
  values.push(formatUsageDetails(account.usage));
288
486
  }
289
487
  return values.map((value) => visibleLength(value));
290
- })), 18, 52),
488
+ })), 18, Number.MAX_SAFE_INTEGER),
291
489
  planWidth,
292
490
  };
293
491
  }
@@ -297,14 +495,14 @@ function formatResultRow(result, layout, context, labels) {
297
495
  const displayLabel = labels?.displayLabel || getDisplayLabel(result, rawLabel, context);
298
496
  const name = padRight(displayLabel, layout.providerWidth);
299
497
  const tablePrefix = `${TABLE_INDENT}${name}${COLUMN_GAP}`;
300
- if (result.status === 'not_configured') {
301
- return `${TABLE_INDENT}${c.dim}${name}${COLUMN_GAP}${padRight('not configured', layout.usageWidth + layout.planWidth + COLUMN_GAP.length + 6)}${c.reset}`;
498
+ if (result.status === "not_configured") {
499
+ return `${TABLE_INDENT}${c.dim}${name}${COLUMN_GAP}${padRight("not configured", layout.usageWidth + layout.planWidth + COLUMN_GAP.length + 6)}${c.reset}`;
302
500
  }
303
- if (result.status === 'auth_expired') {
304
- return `${tablePrefix}${c.red}${padRight('auth expired', layout.usageWidth)}${c.reset}${COLUMN_GAP}${formatPlanCell(undefined, layout.planWidth)}${COLUMN_GAP}${c.dim}${result.error || ''}${c.reset}`;
501
+ if (result.status === "auth_expired") {
502
+ return `${tablePrefix}${c.red}${padRight("auth expired", layout.usageWidth)}${c.reset}${COLUMN_GAP}${formatPlanCell(undefined, layout.planWidth)}${COLUMN_GAP}${c.dim}${result.error || ""}${c.reset}`;
305
503
  }
306
- if (result.status === 'error') {
307
- return `${tablePrefix}${c.red}${padRight('error', layout.usageWidth)}${c.reset}${COLUMN_GAP}${formatPlanCell(undefined, layout.planWidth)}${COLUMN_GAP}${c.dim}${(result.error || '').slice(0, 50)}${c.reset}`;
504
+ if (result.status === "error") {
505
+ return `${tablePrefix}${c.red}${padRight("error", layout.usageWidth)}${c.reset}${COLUMN_GAP}${formatPlanCell(undefined, layout.planWidth)}${COLUMN_GAP}${c.dim}${result.error || ""}${c.reset}`;
308
506
  }
309
507
  if (!result.usage) {
310
508
  return `${tablePrefix}${c.dim}no data${c.reset}`;
@@ -312,7 +510,7 @@ function formatResultRow(result, layout, context, labels) {
312
510
  const usage = result.usage;
313
511
  const usageCell = padRight(formatUsageDetails(usage), layout.usageWidth);
314
512
  const planCell = formatPlanCell(getPlanLabel(result, usage, rawLabel, context, result.plan), layout.planWidth);
315
- if (usage.type === 'payAsYouGo') {
513
+ if (usage.type === "payAsYouGo") {
316
514
  return `${tablePrefix}${usageCell}${COLUMN_GAP}${planCell}${COLUMN_GAP}${c.dim}—${c.reset}`;
317
515
  }
318
516
  // Reset time: pick earliest reset
@@ -324,7 +522,17 @@ function formatResultRow(result, layout, context, labels) {
324
522
  }
325
523
  function formatProviderHeaderRow(result, layout) {
326
524
  const name = padRight(getProviderDisplayName(result), layout.providerWidth);
327
- return `${TABLE_INDENT}${c.bold}${name}${c.reset}${COLUMN_GAP}${' '.repeat(layout.usageWidth)}${COLUMN_GAP}${' '.repeat(layout.planWidth)}`;
525
+ return `${TABLE_INDENT}${c.bold}${name}${c.reset}${COLUMN_GAP}${" ".repeat(layout.usageWidth)}${COLUMN_GAP}${" ".repeat(layout.planWidth)}`;
526
+ }
527
+ function formatDetailRow(layout, detail) {
528
+ const emptyProvider = " ".repeat(layout.providerWidth);
529
+ const emptyPlan = " ".repeat(layout.planWidth);
530
+ return `${TABLE_INDENT}${emptyProvider}${COLUMN_GAP}${c.dim}↳${c.reset} ${detail}${COLUMN_GAP}${emptyPlan}`;
531
+ }
532
+ function formatResultDetailRows(result, layout) {
533
+ if (result.status !== "ok" || !result.usage)
534
+ return [];
535
+ return formatUsageDetailLines(result.usage).map((detail) => formatDetailRow(layout, detail));
328
536
  }
329
537
  // ── Format account sub-rows ───────────────────────────────────
330
538
  function formatAccountRows(result, layout, context) {
@@ -348,13 +556,24 @@ function formatAccountRows(result, layout, context) {
348
556
  });
349
557
  });
350
558
  }
559
+ function isFullUsageCache(results) {
560
+ const expectedProviderIds = new Set(allProviders.map((provider) => provider.id));
561
+ if (results.length !== expectedProviderIds.size)
562
+ return false;
563
+ for (const result of results) {
564
+ if (!expectedProviderIds.has(result.providerId)) {
565
+ return false;
566
+ }
567
+ }
568
+ return true;
569
+ }
351
570
  export async function runUsageCommand(opts) {
352
571
  const providerIds = opts.provider ? [opts.provider] : undefined;
353
572
  // Validate provider name
354
573
  if (opts.provider) {
355
574
  const valid = allProviders.map((p) => p.id);
356
575
  if (!valid.includes(opts.provider)) {
357
- console.error(`Unknown provider: ${opts.provider}\nAvailable: ${valid.join(', ')}`);
576
+ console.error(`Unknown provider: ${opts.provider}\nAvailable: ${valid.join(", ")}`);
358
577
  process.exit(1);
359
578
  }
360
579
  }
@@ -362,11 +581,18 @@ export async function runUsageCommand(opts) {
362
581
  if (!opts.noCache && !opts.json) {
363
582
  const cached = readUsageCache();
364
583
  if (cached) {
365
- const filtered = providerIds
366
- ? cached.results.filter((r) => providerIds.includes(r.providerId))
367
- : cached.results;
368
- renderTable(filtered, opts.verbose);
369
- return;
584
+ if (!providerIds && !isFullUsageCache(cached.results)) {
585
+ // Ignore stale partial caches left by older provider-scoped fetches.
586
+ }
587
+ else {
588
+ const filtered = providerIds
589
+ ? cached.results.filter((r) => providerIds.includes(r.providerId))
590
+ : cached.results;
591
+ if (providerIds ? filtered.length > 0 : true) {
592
+ renderTable(filtered, opts.verbose);
593
+ return;
594
+ }
595
+ }
370
596
  }
371
597
  }
372
598
  // Show spinner
@@ -375,10 +601,12 @@ export async function runUsageCommand(opts) {
375
601
  }
376
602
  const results = await fetchAllUsage({ providerIds });
377
603
  // Cache results
378
- writeUsageCache(results);
604
+ if (!providerIds) {
605
+ writeUsageCache(results);
606
+ }
379
607
  // Clear spinner line
380
608
  if (!opts.json) {
381
- process.stdout.write('\r' + ' '.repeat(60) + '\r');
609
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
382
610
  }
383
611
  // JSON output
384
612
  if (opts.json) {
@@ -389,36 +617,86 @@ export async function runUsageCommand(opts) {
389
617
  }
390
618
  function renderTable(results, verbose = false) {
391
619
  const context = getRenderContext();
392
- const layout = getTableLayout(results, context);
393
- // Table output
394
- const configured = results.filter((r) => r.status !== 'not_configured');
395
- const notConfigured = results.filter((r) => r.status === 'not_configured');
620
+ const configured = results.filter((r) => r.status !== "not_configured");
621
+ const notConfigured = results.filter((r) => r.status === "not_configured");
622
+ const summaryRows = [];
623
+ for (const result of results) {
624
+ if (hasAccountRows(result)) {
625
+ for (const account of result.accounts ?? []) {
626
+ const subResult = {
627
+ providerId: result.providerId,
628
+ providerName: account.label,
629
+ billingType: result.billingType,
630
+ plan: account.plan,
631
+ status: account.status,
632
+ usage: account.usage,
633
+ error: account.error,
634
+ fetchedAt: result.fetchedAt,
635
+ };
636
+ summaryRows.push(buildSummaryRow(subResult, context, verbose, {
637
+ rawLabel: account.label,
638
+ displayLabel: ` ${getDisplayLabel(result, account.label, context)}`,
639
+ }));
640
+ }
641
+ continue;
642
+ }
643
+ summaryRows.push(buildSummaryRow(result, context, verbose));
644
+ }
645
+ const layout = getSummaryTableLayout(summaryRows);
396
646
  console.log();
397
- const header = `${TABLE_INDENT}${c.bold}${padRight('Provider', layout.providerWidth)}${COLUMN_GAP}${padRight('Usage', layout.usageWidth)}${COLUMN_GAP}${padRight('Plan', layout.planWidth)}${COLUMN_GAP}Resets${c.reset}`;
398
- const divider = `${TABLE_INDENT}${c.dim}${''.repeat(visibleLength(header) - visibleLength(TABLE_INDENT))}${c.reset}`;
647
+ const header = `${TABLE_INDENT}${c.bold}${padRight("Provider / Account", layout.providerWidth)}${COLUMN_GAP}${padRight("Status", layout.statusWidth)}${COLUMN_GAP}${padRight("Plan", layout.planWidth)}${COLUMN_GAP}${padLeft("Used", layout.usedWidth)}${COLUMN_GAP}${padLeft("Total", layout.totalWidth)}${COLUMN_GAP}${padLeft("Left", layout.leftWidth)}${COLUMN_GAP}${padLeft("Reset", layout.resetWidth)}${c.reset}`;
648
+ const divider = `${TABLE_INDENT}${c.dim}${"".repeat(visibleLength(header) - visibleLength(TABLE_INDENT))}${c.reset}`;
399
649
  console.log(header);
400
650
  console.log(divider);
401
- for (const result of configured) {
651
+ for (const [resultIndex, result] of configured.entries()) {
402
652
  if (hasAccountRows(result)) {
403
- console.log(formatProviderHeaderRow(result, layout));
404
- const subRows = formatAccountRows(result, layout, context);
405
- for (const row of subRows) {
406
- console.log(row);
653
+ console.log(`${TABLE_INDENT}${c.bold}${getProviderDisplayName(result)}${c.reset}`);
654
+ for (const account of result.accounts ?? []) {
655
+ const subResult = {
656
+ providerId: result.providerId,
657
+ providerName: account.label,
658
+ billingType: result.billingType,
659
+ plan: account.plan,
660
+ status: account.status,
661
+ usage: account.usage,
662
+ error: account.error,
663
+ fetchedAt: result.fetchedAt,
664
+ };
665
+ const row = buildSummaryRow(subResult, context, verbose, {
666
+ rawLabel: account.label,
667
+ displayLabel: ` ${getDisplayLabel(result, account.label, context)}`,
668
+ });
669
+ console.log(formatSummaryRow(row, layout));
670
+ for (const detail of row.details) {
671
+ console.log(formatDetailLine(detail));
672
+ }
673
+ }
674
+ if (resultIndex < configured.length - 1) {
675
+ console.log();
407
676
  }
408
677
  continue;
409
678
  }
410
- console.log(formatResultRow(result, layout, context));
679
+ const row = buildSummaryRow(result, context, verbose);
680
+ console.log(formatSummaryRow(row, layout));
681
+ for (const detail of row.details) {
682
+ console.log(formatDetailLine(detail));
683
+ }
684
+ if (resultIndex < configured.length - 1) {
685
+ console.log();
686
+ }
411
687
  }
412
688
  if (notConfigured.length > 0) {
413
- console.log(divider);
689
+ if (configured.length > 0) {
690
+ console.log(divider);
691
+ }
414
692
  for (const result of notConfigured) {
415
- console.log(formatResultRow(result, layout, context));
693
+ const row = buildSummaryRow(result, context, verbose);
694
+ console.log(formatSummaryRow(row, layout));
416
695
  }
417
696
  }
418
697
  console.log(divider);
419
- // Summary
420
- const okCount = configured.filter((r) => r.status === 'ok').length;
421
- const errCount = configured.filter((r) => r.status === 'error' || r.status === 'auth_expired').length;
698
+ const okCount = configured.filter((r) => r.status === "ok").length;
699
+ const errCount = configured.filter((r) => r.status === "error" || r.status === "auth_expired").length;
422
700
  const notConfCount = notConfigured.length;
423
701
  const parts = [];
424
702
  if (okCount > 0)