@leo000001/opencode-quota-sidebar 1.3.0 → 1.5.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/dist/format.js +92 -39
- package/dist/providers/third_party/rightcode.js +5 -2
- package/dist/storage.js +2 -0
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/format.js
CHANGED
|
@@ -127,9 +127,22 @@ function fitLine(value, width) {
|
|
|
127
127
|
return truncateToCellWidth(safe, width);
|
|
128
128
|
return `${head}~`;
|
|
129
129
|
}
|
|
130
|
-
function
|
|
130
|
+
function formatCurrency(value, currency) {
|
|
131
131
|
const safe = Number.isFinite(value) && value > 0 ? value : 0;
|
|
132
|
-
|
|
132
|
+
const prefix = typeof currency === 'string' && currency ? currency : '$';
|
|
133
|
+
if (safe === 0)
|
|
134
|
+
return `${prefix}0.00`;
|
|
135
|
+
if (safe < 10)
|
|
136
|
+
return `${prefix}${safe.toFixed(2)}`;
|
|
137
|
+
const one = safe.toFixed(1);
|
|
138
|
+
const trimmed = one.endsWith('.0') ? one.slice(0, -2) : one;
|
|
139
|
+
return `${prefix}${trimmed}`;
|
|
140
|
+
}
|
|
141
|
+
function formatUsd(value) {
|
|
142
|
+
return formatCurrency(value, '$');
|
|
143
|
+
}
|
|
144
|
+
function formatApiCostValue(value) {
|
|
145
|
+
return formatUsd(value);
|
|
133
146
|
}
|
|
134
147
|
function formatApiCostLine(value) {
|
|
135
148
|
return `${formatApiCostValue(value)} as API cost`;
|
|
@@ -187,7 +200,10 @@ export function renderSidebarTitle(baseTitle, usage, quotas, config) {
|
|
|
187
200
|
return Math.max(max, stringCellWidth(label));
|
|
188
201
|
}, 0);
|
|
189
202
|
const quotaItems = visibleQuotas
|
|
190
|
-
.flatMap((item) => compactQuotaWide(item, labelWidth
|
|
203
|
+
.flatMap((item) => compactQuotaWide(item, labelWidth, {
|
|
204
|
+
width,
|
|
205
|
+
wrapLines: config.sidebar.wrapQuotaLines,
|
|
206
|
+
}))
|
|
191
207
|
.filter((s) => Boolean(s));
|
|
192
208
|
if (quotaItems.length > 0) {
|
|
193
209
|
lines.push('');
|
|
@@ -200,14 +216,31 @@ export function renderSidebarTitle(baseTitle, usage, quotas, config) {
|
|
|
200
216
|
}
|
|
201
217
|
/**
|
|
202
218
|
* Multi-window quota format for sidebar.
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
219
|
+
*
|
|
220
|
+
* When wrapLines=false (or content fits):
|
|
221
|
+
* "OpenAI 5h 80% Rst 16:20"
|
|
222
|
+
* " Weekly 70% Rst 03-01"
|
|
223
|
+
*
|
|
224
|
+
* When wrapLines=true and label+content overflows width:
|
|
225
|
+
* "RC-openai"
|
|
226
|
+
* " Daily $349.66/$180 Exp+ 02-27"
|
|
227
|
+
* " Balance $108.88"
|
|
206
228
|
*/
|
|
207
|
-
function compactQuotaWide(quota, labelWidth = 0) {
|
|
229
|
+
function compactQuotaWide(quota, labelWidth = 0, options) {
|
|
208
230
|
const label = sanitizeLine(quotaDisplayLabel(quota));
|
|
209
231
|
const labelPadded = padEndCells(label, labelWidth);
|
|
232
|
+
const indent = ' '.repeat(labelWidth + 1);
|
|
233
|
+
const detailIndent = ' ';
|
|
210
234
|
const withLabel = (content) => `${labelPadded} ${content}`;
|
|
235
|
+
const wrap = options?.wrapLines === true && (options?.width || 0) > 0;
|
|
236
|
+
const width = options?.width || 0;
|
|
237
|
+
/** If inline version overflows, break into label-line + indented detail lines. */
|
|
238
|
+
const maybeBreak = (inlineText, detailLines) => {
|
|
239
|
+
const inline = withLabel(inlineText);
|
|
240
|
+
if (!wrap || stringCellWidth(inline) <= width)
|
|
241
|
+
return [inline];
|
|
242
|
+
return [label, ...detailLines.map((d) => `${detailIndent}${d}`)];
|
|
243
|
+
};
|
|
211
244
|
if (quota.status === 'error')
|
|
212
245
|
return [withLabel('Remaining ?')];
|
|
213
246
|
if (quota.status === 'unsupported')
|
|
@@ -217,7 +250,7 @@ function compactQuotaWide(quota, labelWidth = 0) {
|
|
|
217
250
|
if (quota.status !== 'ok')
|
|
218
251
|
return [];
|
|
219
252
|
const balanceText = quota.balance
|
|
220
|
-
? `Balance ${quota.balance.
|
|
253
|
+
? `Balance ${formatCurrency(quota.balance.amount, quota.balance.currency)}`
|
|
221
254
|
: undefined;
|
|
222
255
|
const renderWindow = (win) => {
|
|
223
256
|
const showPercent = win.showPercent !== false;
|
|
@@ -229,7 +262,7 @@ function compactQuotaWide(quota, labelWidth = 0) {
|
|
|
229
262
|
? [sanitizeLine(win.label), pct]
|
|
230
263
|
: [sanitizeLine(win.label)]
|
|
231
264
|
: [pct];
|
|
232
|
-
const reset = compactReset(win.resetAt);
|
|
265
|
+
const reset = compactReset(win.resetAt, win.resetLabel);
|
|
233
266
|
if (reset) {
|
|
234
267
|
parts.push(`${sanitizeLine(win.resetLabel || 'Rst')} ${reset}`);
|
|
235
268
|
}
|
|
@@ -238,42 +271,64 @@ function compactQuotaWide(quota, labelWidth = 0) {
|
|
|
238
271
|
// Multi-window rendering
|
|
239
272
|
if (quota.windows && quota.windows.length > 0) {
|
|
240
273
|
const parts = quota.windows.map(renderWindow);
|
|
274
|
+
// Build the detail lines (window texts + optional balance)
|
|
275
|
+
const details = [...parts];
|
|
276
|
+
if (balanceText && !parts.some((p) => p.includes('Balance '))) {
|
|
277
|
+
details.push(balanceText);
|
|
278
|
+
}
|
|
279
|
+
// Try inline first (single window, fits in one line)
|
|
241
280
|
if (parts.length === 1) {
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
281
|
+
const firstInline = withLabel(parts[0]);
|
|
282
|
+
if (!wrap || stringCellWidth(firstInline) <= width) {
|
|
283
|
+
// Inline fits — use classic layout
|
|
284
|
+
const lines = [firstInline];
|
|
285
|
+
if (balanceText && !parts[0].includes('Balance ')) {
|
|
286
|
+
lines.push(`${indent}${balanceText}`);
|
|
287
|
+
}
|
|
288
|
+
return lines;
|
|
246
289
|
}
|
|
247
|
-
|
|
290
|
+
// Overflow — break: label on its own line, details indented
|
|
291
|
+
return [label, ...details.map((d) => `${detailIndent}${d}`)];
|
|
248
292
|
}
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
293
|
+
// Multiple windows: try classic inline layout first
|
|
294
|
+
const firstInline = withLabel(parts[0]);
|
|
295
|
+
if (!wrap || stringCellWidth(firstInline) <= width) {
|
|
296
|
+
const lines = [
|
|
297
|
+
firstInline,
|
|
298
|
+
...parts.slice(1).map((part) => `${indent}${part}`),
|
|
299
|
+
];
|
|
300
|
+
if (balanceText && !parts.some((p) => p.includes('Balance '))) {
|
|
301
|
+
lines.push(`${indent}${balanceText}`);
|
|
302
|
+
}
|
|
303
|
+
return lines;
|
|
257
304
|
}
|
|
258
|
-
|
|
305
|
+
// Overflow — break all
|
|
306
|
+
return [label, ...details.map((d) => `${detailIndent}${d}`)];
|
|
259
307
|
}
|
|
260
308
|
if (balanceText) {
|
|
261
|
-
return
|
|
309
|
+
return maybeBreak(balanceText, [balanceText]);
|
|
262
310
|
}
|
|
263
311
|
// Fallback: single value from top-level remainingPercent
|
|
264
312
|
const percent = quota.remainingPercent === undefined
|
|
265
313
|
? '?'
|
|
266
314
|
: `${Math.round(quota.remainingPercent)}%`;
|
|
267
|
-
const reset = compactReset(quota.resetAt);
|
|
268
|
-
|
|
315
|
+
const reset = compactReset(quota.resetAt, 'Rst');
|
|
316
|
+
const fallbackText = `Remaining ${percent}${reset ? ` Rst ${reset}` : ''}`;
|
|
317
|
+
return maybeBreak(fallbackText, [fallbackText]);
|
|
269
318
|
}
|
|
270
|
-
function compactReset(iso) {
|
|
319
|
+
function compactReset(iso, resetLabel) {
|
|
271
320
|
if (!iso)
|
|
272
321
|
return undefined;
|
|
273
322
|
const timestamp = Date.parse(iso);
|
|
274
323
|
if (Number.isNaN(timestamp))
|
|
275
324
|
return undefined;
|
|
276
325
|
const value = new Date(timestamp);
|
|
326
|
+
// RightCode subscriptions are displayed as an expiry date (MM-DD), not a time.
|
|
327
|
+
// Using UTC here makes the output stable across time zones for ISO `...Z` input.
|
|
328
|
+
if (typeof resetLabel === 'string' && resetLabel.startsWith('Exp')) {
|
|
329
|
+
const two = (num) => `${num}`.padStart(2, '0');
|
|
330
|
+
return `${two(value.getUTCMonth() + 1)}-${two(value.getUTCDate())}`;
|
|
331
|
+
}
|
|
277
332
|
const now = new Date();
|
|
278
333
|
const sameDay = value.getFullYear() === now.getFullYear() &&
|
|
279
334
|
value.getMonth() === now.getMonth() &&
|
|
@@ -317,7 +372,7 @@ export function renderMarkdownReport(period, usage, quotas, options) {
|
|
|
317
372
|
rightCodeSubscriptionProviderIDs.has(providerID);
|
|
318
373
|
if (isSubscription)
|
|
319
374
|
return '-';
|
|
320
|
-
return
|
|
375
|
+
return formatUsd(cost);
|
|
321
376
|
};
|
|
322
377
|
const isSubscriptionMeasuredProvider = (providerID) => {
|
|
323
378
|
const canonical = canonicalProviderID(providerID);
|
|
@@ -329,18 +384,16 @@ export function renderMarkdownReport(period, usage, quotas, options) {
|
|
|
329
384
|
const canonical = canonicalProviderID(providerID);
|
|
330
385
|
if (canonical === 'github-copilot')
|
|
331
386
|
return '-';
|
|
332
|
-
|
|
333
|
-
return '$0.00';
|
|
334
|
-
return `$${apiCost.toFixed(2)}`;
|
|
387
|
+
return formatUsd(apiCost);
|
|
335
388
|
};
|
|
336
389
|
const measuredCostSummaryValue = () => {
|
|
337
390
|
const providers = Object.values(usage.providers);
|
|
338
391
|
if (providers.length === 0)
|
|
339
|
-
return
|
|
392
|
+
return formatUsd(usage.cost);
|
|
340
393
|
const hasNonSubscription = providers.some((provider) => !isSubscriptionMeasuredProvider(provider.providerID));
|
|
341
394
|
if (!hasNonSubscription)
|
|
342
395
|
return '-';
|
|
343
|
-
return
|
|
396
|
+
return formatUsd(usage.cost);
|
|
344
397
|
};
|
|
345
398
|
const apiCostSummaryValue = () => {
|
|
346
399
|
const providers = Object.values(usage.providers);
|
|
@@ -376,7 +429,7 @@ export function renderMarkdownReport(period, usage, quotas, options) {
|
|
|
376
429
|
}
|
|
377
430
|
if (quota.status === 'ok' && quota.balance) {
|
|
378
431
|
return [
|
|
379
|
-
mdCell(`- ${quota.label}: ${quota.status} | balance ${quota.balance.
|
|
432
|
+
mdCell(`- ${quota.label}: ${quota.status} | balance ${formatCurrency(quota.balance.amount, quota.balance.currency)}`),
|
|
380
433
|
];
|
|
381
434
|
}
|
|
382
435
|
const remaining = quota.remainingPercent === undefined
|
|
@@ -455,7 +508,7 @@ export function renderToastMessage(period, usage, quotas, options) {
|
|
|
455
508
|
.sort((left, right) => right.apiCost - left.apiCost)
|
|
456
509
|
.map((provider) => ({
|
|
457
510
|
label: displayShortLabel(provider.providerID),
|
|
458
|
-
value:
|
|
511
|
+
value: formatUsd(provider.apiCost),
|
|
459
512
|
}));
|
|
460
513
|
lines.push('');
|
|
461
514
|
lines.push(fitLine('Cost as API', width));
|
|
@@ -475,7 +528,7 @@ export function renderToastMessage(period, usage, quotas, options) {
|
|
|
475
528
|
const pct = win.remainingPercent === undefined
|
|
476
529
|
? '-'
|
|
477
530
|
: `${win.remainingPercent.toFixed(1)}%`;
|
|
478
|
-
const reset = compactReset(win.resetAt);
|
|
531
|
+
const reset = compactReset(win.resetAt, win.resetLabel);
|
|
479
532
|
const parts = [win.label];
|
|
480
533
|
if (showPercent)
|
|
481
534
|
parts.push(pct);
|
|
@@ -489,7 +542,7 @@ export function renderToastMessage(period, usage, quotas, options) {
|
|
|
489
542
|
if (item.balance) {
|
|
490
543
|
pairs.push({
|
|
491
544
|
label: '',
|
|
492
|
-
value: `Balance ${item.balance.
|
|
545
|
+
value: `Balance ${formatCurrency(item.balance.amount, item.balance.currency)}`,
|
|
493
546
|
});
|
|
494
547
|
}
|
|
495
548
|
return pairs;
|
|
@@ -498,14 +551,14 @@ export function renderToastMessage(period, usage, quotas, options) {
|
|
|
498
551
|
return [
|
|
499
552
|
{
|
|
500
553
|
label: quotaDisplayLabel(item),
|
|
501
|
-
value: `Balance ${item.balance.
|
|
554
|
+
value: `Balance ${formatCurrency(item.balance.amount, item.balance.currency)}`,
|
|
502
555
|
},
|
|
503
556
|
];
|
|
504
557
|
}
|
|
505
558
|
const percent = item.remainingPercent === undefined
|
|
506
559
|
? '-'
|
|
507
560
|
: `${item.remainingPercent.toFixed(1)}%`;
|
|
508
|
-
const reset = compactReset(item.resetAt);
|
|
561
|
+
const reset = compactReset(item.resetAt, 'Rst');
|
|
509
562
|
return [
|
|
510
563
|
{
|
|
511
564
|
label: quotaDisplayLabel(item),
|
|
@@ -51,8 +51,11 @@ function matchesSubscriptionPrefix(providerPrefixes, availablePrefixes) {
|
|
|
51
51
|
function formatQuotaValue(value) {
|
|
52
52
|
if (!Number.isFinite(value))
|
|
53
53
|
return '0';
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
if (Math.abs(value) >= 10) {
|
|
55
|
+
const one = value.toFixed(1);
|
|
56
|
+
return one.endsWith('.0') ? one.slice(0, -2) : one;
|
|
57
|
+
}
|
|
58
|
+
return value.toFixed(2);
|
|
56
59
|
}
|
|
57
60
|
function parseSubscription(value) {
|
|
58
61
|
const total = asNumber(value.total_quota);
|
package/dist/storage.js
CHANGED
|
@@ -13,6 +13,7 @@ export const defaultConfig = {
|
|
|
13
13
|
width: 36,
|
|
14
14
|
showCost: true,
|
|
15
15
|
showQuota: true,
|
|
16
|
+
wrapQuotaLines: true,
|
|
16
17
|
includeChildren: true,
|
|
17
18
|
childrenMaxDepth: 6,
|
|
18
19
|
childrenMaxSessions: 128,
|
|
@@ -68,6 +69,7 @@ export async function loadConfig(paths) {
|
|
|
68
69
|
width: Math.max(20, Math.min(60, asNumber(sidebar.width, defaultConfig.sidebar.width))),
|
|
69
70
|
showCost: asBoolean(sidebar.showCost, defaultConfig.sidebar.showCost),
|
|
70
71
|
showQuota: asBoolean(sidebar.showQuota, defaultConfig.sidebar.showQuota),
|
|
72
|
+
wrapQuotaLines: asBoolean(sidebar.wrapQuotaLines, defaultConfig.sidebar.wrapQuotaLines),
|
|
71
73
|
includeChildren: asBoolean(sidebar.includeChildren, defaultConfig.sidebar.includeChildren),
|
|
72
74
|
childrenMaxDepth: Math.max(1, Math.min(32, Math.floor(asNumber(sidebar.childrenMaxDepth, defaultConfig.sidebar.childrenMaxDepth)))),
|
|
73
75
|
childrenMaxSessions: Math.max(0, Math.min(2000, Math.floor(asNumber(sidebar.childrenMaxSessions, defaultConfig.sidebar.childrenMaxSessions)))),
|
package/dist/types.d.ts
CHANGED
|
@@ -99,6 +99,8 @@ export type QuotaSidebarConfig = {
|
|
|
99
99
|
width: number;
|
|
100
100
|
showCost: boolean;
|
|
101
101
|
showQuota: boolean;
|
|
102
|
+
/** When true, wrap long quota lines and indent continuations. */
|
|
103
|
+
wrapQuotaLines: boolean;
|
|
102
104
|
/** Include descendant subagent sessions in session-scoped usage/quota. */
|
|
103
105
|
includeChildren: boolean;
|
|
104
106
|
/** Max descendant traversal depth when includeChildren is enabled. */
|