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.
- package/README.md +1 -1
- package/dist/providers/auth.d.ts +1 -1
- package/dist/providers/auth.d.ts.map +1 -1
- package/dist/providers/auth.js +56 -48
- package/dist/providers/auth.js.map +1 -1
- package/dist/providers/chutes.d.ts +1 -1
- package/dist/providers/chutes.d.ts.map +1 -1
- package/dist/providers/chutes.js +19 -16
- package/dist/providers/chutes.js.map +1 -1
- package/dist/providers/codex.d.ts +1 -1
- package/dist/providers/codex.d.ts.map +1 -1
- package/dist/providers/codex.js +54 -35
- package/dist/providers/codex.js.map +1 -1
- package/dist/providers/copilot.d.ts +1 -1
- package/dist/providers/copilot.d.ts.map +1 -1
- package/dist/providers/copilot.js +92 -60
- package/dist/providers/copilot.js.map +1 -1
- package/dist/providers/types.d.ts +8 -4
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/providers/types.js.map +1 -1
- package/dist/providers/zai.d.ts +1 -1
- package/dist/providers/zai.d.ts.map +1 -1
- package/dist/providers/zai.js +29 -21
- package/dist/providers/zai.js.map +1 -1
- package/dist/usage-command.d.ts.map +1 -1
- package/dist/usage-command.js +390 -112
- package/dist/usage-command.js.map +1 -1
- package/package.json +1 -1
package/dist/usage-command.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
// CLI usage command: formatted table output for provider usage
|
|
2
|
-
import { fetchAllUsage, allProviders } from
|
|
3
|
-
import { decodeJwtPayload } from
|
|
4
|
-
import { loadStore } from
|
|
5
|
-
import { readUsageCache, writeUsageCache } from
|
|
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 !==
|
|
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 ?
|
|
12
|
-
bold: isColorSupported ?
|
|
13
|
-
dim: isColorSupported ?
|
|
14
|
-
red: isColorSupported ?
|
|
15
|
-
green: isColorSupported ?
|
|
16
|
-
yellow: isColorSupported ?
|
|
17
|
-
blue: isColorSupported ?
|
|
18
|
-
cyan: isColorSupported ?
|
|
19
|
-
gray: isColorSupported ?
|
|
20
|
-
white: isColorSupported ?
|
|
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
|
|
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}${
|
|
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 +
|
|
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
|
|
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 ===
|
|
90
|
-
return
|
|
91
|
-
if (normalized ===
|
|
92
|
-
return
|
|
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
|
|
97
|
+
return "0";
|
|
98
98
|
if (Math.abs(value) >= 1000) {
|
|
99
|
-
return new Intl.NumberFormat(
|
|
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(
|
|
103
|
+
return new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 }).format(value);
|
|
104
104
|
}
|
|
105
105
|
function getUsageWindows(usage) {
|
|
106
|
-
if (usage.type !==
|
|
106
|
+
if (usage.type !== "quotaBased")
|
|
107
107
|
return [];
|
|
108
|
-
return usage.windows.filter((window) => window.label !==
|
|
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 ===
|
|
120
|
-
typeof usage.remaining ===
|
|
121
|
-
typeof usage.entitlement ===
|
|
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
|
-
|
|
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:
|
|
152
|
-
pro:
|
|
153
|
-
team:
|
|
154
|
-
business:
|
|
155
|
-
enterprise:
|
|
156
|
-
free:
|
|
157
|
-
individual_pro:
|
|
158
|
-
individual_free:
|
|
159
|
-
chatgpt_plus:
|
|
160
|
-
chatgptplus:
|
|
161
|
-
chatgpt_pro:
|
|
162
|
-
chatgptpro:
|
|
163
|
-
chatgpt_team:
|
|
164
|
-
chatgptteam:
|
|
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 !==
|
|
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 !==
|
|
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 ===
|
|
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[
|
|
201
|
-
return typeof auth?.chatgpt_plan_type ===
|
|
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 ===
|
|
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)
|
|
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 ===
|
|
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}${
|
|
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,
|
|
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,
|
|
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,
|
|
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 ===
|
|
301
|
-
return `${TABLE_INDENT}${c.dim}${name}${COLUMN_GAP}${padRight(
|
|
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 ===
|
|
304
|
-
return `${tablePrefix}${c.red}${padRight(
|
|
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 ===
|
|
307
|
-
return `${tablePrefix}${c.red}${padRight(
|
|
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 ===
|
|
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}${
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
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
|
-
|
|
604
|
+
if (!providerIds) {
|
|
605
|
+
writeUsageCache(results);
|
|
606
|
+
}
|
|
379
607
|
// Clear spinner line
|
|
380
608
|
if (!opts.json) {
|
|
381
|
-
process.stdout.write(
|
|
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
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
const
|
|
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(
|
|
398
|
-
const divider = `${TABLE_INDENT}${c.dim}${
|
|
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(
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
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
|
-
|
|
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
|
-
|
|
689
|
+
if (configured.length > 0) {
|
|
690
|
+
console.log(divider);
|
|
691
|
+
}
|
|
414
692
|
for (const result of notConfigured) {
|
|
415
|
-
|
|
693
|
+
const row = buildSummaryRow(result, context, verbose);
|
|
694
|
+
console.log(formatSummaryRow(row, layout));
|
|
416
695
|
}
|
|
417
696
|
}
|
|
418
697
|
console.log(divider);
|
|
419
|
-
|
|
420
|
-
const
|
|
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)
|