@leo000001/opencode-quota-sidebar 1.3.0 → 1.4.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 +68 -26
- package/dist/storage.js +2 -0
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/format.js
CHANGED
|
@@ -187,7 +187,10 @@ export function renderSidebarTitle(baseTitle, usage, quotas, config) {
|
|
|
187
187
|
return Math.max(max, stringCellWidth(label));
|
|
188
188
|
}, 0);
|
|
189
189
|
const quotaItems = visibleQuotas
|
|
190
|
-
.flatMap((item) => compactQuotaWide(item, labelWidth
|
|
190
|
+
.flatMap((item) => compactQuotaWide(item, labelWidth, {
|
|
191
|
+
width,
|
|
192
|
+
wrapLines: config.sidebar.wrapQuotaLines,
|
|
193
|
+
}))
|
|
191
194
|
.filter((s) => Boolean(s));
|
|
192
195
|
if (quotaItems.length > 0) {
|
|
193
196
|
lines.push('');
|
|
@@ -200,14 +203,31 @@ export function renderSidebarTitle(baseTitle, usage, quotas, config) {
|
|
|
200
203
|
}
|
|
201
204
|
/**
|
|
202
205
|
* Multi-window quota format for sidebar.
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
+
*
|
|
207
|
+
* When wrapLines=false (or content fits):
|
|
208
|
+
* "OpenAI 5h 80% Rst 16:20"
|
|
209
|
+
* " Weekly 70% Rst 03-01"
|
|
210
|
+
*
|
|
211
|
+
* When wrapLines=true and label+content overflows width:
|
|
212
|
+
* "RC-openai"
|
|
213
|
+
* " Daily $349.66/$180 Exp+ 02-27"
|
|
214
|
+
* " Balance $108.88"
|
|
206
215
|
*/
|
|
207
|
-
function compactQuotaWide(quota, labelWidth = 0) {
|
|
216
|
+
function compactQuotaWide(quota, labelWidth = 0, options) {
|
|
208
217
|
const label = sanitizeLine(quotaDisplayLabel(quota));
|
|
209
218
|
const labelPadded = padEndCells(label, labelWidth);
|
|
219
|
+
const indent = ' '.repeat(labelWidth + 1);
|
|
220
|
+
const detailIndent = ' ';
|
|
210
221
|
const withLabel = (content) => `${labelPadded} ${content}`;
|
|
222
|
+
const wrap = options?.wrapLines === true && (options?.width || 0) > 0;
|
|
223
|
+
const width = options?.width || 0;
|
|
224
|
+
/** If inline version overflows, break into label-line + indented detail lines. */
|
|
225
|
+
const maybeBreak = (inlineText, detailLines) => {
|
|
226
|
+
const inline = withLabel(inlineText);
|
|
227
|
+
if (!wrap || stringCellWidth(inline) <= width)
|
|
228
|
+
return [inline];
|
|
229
|
+
return [label, ...detailLines.map((d) => `${detailIndent}${d}`)];
|
|
230
|
+
};
|
|
211
231
|
if (quota.status === 'error')
|
|
212
232
|
return [withLabel('Remaining ?')];
|
|
213
233
|
if (quota.status === 'unsupported')
|
|
@@ -229,7 +249,7 @@ function compactQuotaWide(quota, labelWidth = 0) {
|
|
|
229
249
|
? [sanitizeLine(win.label), pct]
|
|
230
250
|
: [sanitizeLine(win.label)]
|
|
231
251
|
: [pct];
|
|
232
|
-
const reset = compactReset(win.resetAt);
|
|
252
|
+
const reset = compactReset(win.resetAt, win.resetLabel);
|
|
233
253
|
if (reset) {
|
|
234
254
|
parts.push(`${sanitizeLine(win.resetLabel || 'Rst')} ${reset}`);
|
|
235
255
|
}
|
|
@@ -238,42 +258,64 @@ function compactQuotaWide(quota, labelWidth = 0) {
|
|
|
238
258
|
// Multi-window rendering
|
|
239
259
|
if (quota.windows && quota.windows.length > 0) {
|
|
240
260
|
const parts = quota.windows.map(renderWindow);
|
|
261
|
+
// Build the detail lines (window texts + optional balance)
|
|
262
|
+
const details = [...parts];
|
|
263
|
+
if (balanceText && !parts.some((p) => p.includes('Balance '))) {
|
|
264
|
+
details.push(balanceText);
|
|
265
|
+
}
|
|
266
|
+
// Try inline first (single window, fits in one line)
|
|
241
267
|
if (parts.length === 1) {
|
|
242
|
-
const
|
|
243
|
-
if (
|
|
244
|
-
|
|
245
|
-
|
|
268
|
+
const firstInline = withLabel(parts[0]);
|
|
269
|
+
if (!wrap || stringCellWidth(firstInline) <= width) {
|
|
270
|
+
// Inline fits — use classic layout
|
|
271
|
+
const lines = [firstInline];
|
|
272
|
+
if (balanceText && !parts[0].includes('Balance ')) {
|
|
273
|
+
lines.push(`${indent}${balanceText}`);
|
|
274
|
+
}
|
|
275
|
+
return lines;
|
|
246
276
|
}
|
|
247
|
-
|
|
277
|
+
// Overflow — break: label on its own line, details indented
|
|
278
|
+
return [label, ...details.map((d) => `${detailIndent}${d}`)];
|
|
248
279
|
}
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
280
|
+
// Multiple windows: try classic inline layout first
|
|
281
|
+
const firstInline = withLabel(parts[0]);
|
|
282
|
+
if (!wrap || stringCellWidth(firstInline) <= width) {
|
|
283
|
+
const lines = [
|
|
284
|
+
firstInline,
|
|
285
|
+
...parts.slice(1).map((part) => `${indent}${part}`),
|
|
286
|
+
];
|
|
287
|
+
if (balanceText && !parts.some((p) => p.includes('Balance '))) {
|
|
288
|
+
lines.push(`${indent}${balanceText}`);
|
|
289
|
+
}
|
|
290
|
+
return lines;
|
|
257
291
|
}
|
|
258
|
-
|
|
292
|
+
// Overflow — break all
|
|
293
|
+
return [label, ...details.map((d) => `${detailIndent}${d}`)];
|
|
259
294
|
}
|
|
260
295
|
if (balanceText) {
|
|
261
|
-
return
|
|
296
|
+
return maybeBreak(balanceText, [balanceText]);
|
|
262
297
|
}
|
|
263
298
|
// Fallback: single value from top-level remainingPercent
|
|
264
299
|
const percent = quota.remainingPercent === undefined
|
|
265
300
|
? '?'
|
|
266
301
|
: `${Math.round(quota.remainingPercent)}%`;
|
|
267
|
-
const reset = compactReset(quota.resetAt);
|
|
268
|
-
|
|
302
|
+
const reset = compactReset(quota.resetAt, 'Rst');
|
|
303
|
+
const fallbackText = `Remaining ${percent}${reset ? ` Rst ${reset}` : ''}`;
|
|
304
|
+
return maybeBreak(fallbackText, [fallbackText]);
|
|
269
305
|
}
|
|
270
|
-
function compactReset(iso) {
|
|
306
|
+
function compactReset(iso, resetLabel) {
|
|
271
307
|
if (!iso)
|
|
272
308
|
return undefined;
|
|
273
309
|
const timestamp = Date.parse(iso);
|
|
274
310
|
if (Number.isNaN(timestamp))
|
|
275
311
|
return undefined;
|
|
276
312
|
const value = new Date(timestamp);
|
|
313
|
+
// RightCode subscriptions are displayed as an expiry date (MM-DD), not a time.
|
|
314
|
+
// Using UTC here makes the output stable across time zones for ISO `...Z` input.
|
|
315
|
+
if (typeof resetLabel === 'string' && resetLabel.startsWith('Exp')) {
|
|
316
|
+
const two = (num) => `${num}`.padStart(2, '0');
|
|
317
|
+
return `${two(value.getUTCMonth() + 1)}-${two(value.getUTCDate())}`;
|
|
318
|
+
}
|
|
277
319
|
const now = new Date();
|
|
278
320
|
const sameDay = value.getFullYear() === now.getFullYear() &&
|
|
279
321
|
value.getMonth() === now.getMonth() &&
|
|
@@ -475,7 +517,7 @@ export function renderToastMessage(period, usage, quotas, options) {
|
|
|
475
517
|
const pct = win.remainingPercent === undefined
|
|
476
518
|
? '-'
|
|
477
519
|
: `${win.remainingPercent.toFixed(1)}%`;
|
|
478
|
-
const reset = compactReset(win.resetAt);
|
|
520
|
+
const reset = compactReset(win.resetAt, win.resetLabel);
|
|
479
521
|
const parts = [win.label];
|
|
480
522
|
if (showPercent)
|
|
481
523
|
parts.push(pct);
|
|
@@ -505,7 +547,7 @@ export function renderToastMessage(period, usage, quotas, options) {
|
|
|
505
547
|
const percent = item.remainingPercent === undefined
|
|
506
548
|
? '-'
|
|
507
549
|
: `${item.remainingPercent.toFixed(1)}%`;
|
|
508
|
-
const reset = compactReset(item.resetAt);
|
|
550
|
+
const reset = compactReset(item.resetAt, 'Rst');
|
|
509
551
|
return [
|
|
510
552
|
{
|
|
511
553
|
label: quotaDisplayLabel(item),
|
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. */
|