cc-usage-bar 0.1.0 → 0.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/AGENTS.md +228 -0
- package/README.md +9 -3
- package/README.zh-CN.md +9 -3
- package/bin/cc-usage-bar-wrap.js +7 -0
- package/dist/src/cli.js +23 -23
- package/dist/src/fetch.js +80 -10
- package/dist/src/format.js +167 -46
- package/dist/src/providers/anthropic.js +1 -1
- package/dist/src/providers/deepseek.js +1 -1
- package/dist/src/providers/glm.js +1 -1
- package/dist/src/providers/kimi.js +1 -1
- package/dist/src/providers/minimax.js +1 -1
- package/dist/src/providers/novita.js +1 -1
- package/dist/src/providers/openrouter.js +1 -1
- package/dist/src/providers/siliconflow.js +1 -1
- package/dist/src/providers/stepfun.js +1 -1
- package/dist/src/render.js +1 -0
- package/dist/src/settings.js +61 -35
- package/dist/src/token.js +54 -0
- package/dist/src/wrap.js +222 -0
- package/package.json +4 -2
package/dist/src/format.js
CHANGED
|
@@ -1,42 +1,80 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_FORMAT = exports.FORMAT_PRESETS = void 0;
|
|
3
|
+
exports.DEFAULT_FORMAT = exports.DEFAULT_RAMP = exports.COLOR_NAMES = exports.COLOR_MAP = exports.FORMAT_PRESETS = void 0;
|
|
4
4
|
exports.formatExpiry = formatExpiry;
|
|
5
|
+
exports.formatCountdown = formatCountdown;
|
|
5
6
|
exports.parseBarSpec = parseBarSpec;
|
|
7
|
+
exports.hexToAnsi = hexToAnsi;
|
|
8
|
+
exports.parseColorRamp = parseColorRamp;
|
|
9
|
+
exports.colorFromRamp = colorFromRamp;
|
|
6
10
|
exports.formatBar = formatBar;
|
|
7
11
|
exports.renderSubscription = renderSubscription;
|
|
8
12
|
exports.renderBalance = renderBalance;
|
|
9
13
|
exports.renderUsage = renderUsage;
|
|
10
14
|
exports.isValidPreset = isValidPreset;
|
|
11
15
|
exports.shouldUseColor = shouldUseColor;
|
|
12
|
-
exports.FORMAT_PRESETS = [
|
|
16
|
+
exports.FORMAT_PRESETS = [
|
|
17
|
+
'compact',
|
|
18
|
+
'numeric',
|
|
19
|
+
'time',
|
|
20
|
+
'countdown',
|
|
21
|
+
'bar',
|
|
22
|
+
'bar-time',
|
|
23
|
+
'bar-countdown',
|
|
24
|
+
];
|
|
25
|
+
const ANSI_RESET = '\x1b[0m';
|
|
26
|
+
exports.COLOR_MAP = {
|
|
27
|
+
none: '',
|
|
28
|
+
dim: '\x1b[2m',
|
|
29
|
+
black: '\x1b[30m',
|
|
30
|
+
red: '\x1b[31m',
|
|
31
|
+
green: '\x1b[32m',
|
|
32
|
+
yellow: '\x1b[33m',
|
|
33
|
+
blue: '\x1b[34m',
|
|
34
|
+
magenta: '\x1b[35m',
|
|
35
|
+
cyan: '\x1b[36m',
|
|
36
|
+
white: '\x1b[37m',
|
|
37
|
+
gray: '\x1b[90m',
|
|
38
|
+
boldRed: '\x1b[1;31m',
|
|
39
|
+
boldGreen: '\x1b[1;32m',
|
|
40
|
+
boldYellow: '\x1b[1;33m',
|
|
41
|
+
boldBlue: '\x1b[1;34m',
|
|
42
|
+
boldMagenta: '\x1b[1;35m',
|
|
43
|
+
boldCyan: '\x1b[1;36m',
|
|
44
|
+
boldWhite: '\x1b[1;37m',
|
|
45
|
+
};
|
|
46
|
+
exports.COLOR_NAMES = Object.keys(exports.COLOR_MAP);
|
|
47
|
+
exports.DEFAULT_RAMP = [
|
|
48
|
+
{ min: 0, ansi: exports.COLOR_MAP.green },
|
|
49
|
+
{ min: 60, ansi: exports.COLOR_MAP.yellow },
|
|
50
|
+
{ min: 85, ansi: exports.COLOR_MAP.red },
|
|
51
|
+
];
|
|
13
52
|
exports.DEFAULT_FORMAT = {
|
|
14
|
-
format: '
|
|
53
|
+
format: 'bar-countdown',
|
|
15
54
|
barWidth: 10,
|
|
16
55
|
color: true,
|
|
17
56
|
showProviderName: true,
|
|
57
|
+
colorRamp5h: exports.DEFAULT_RAMP,
|
|
58
|
+
colorRampWk: exports.DEFAULT_RAMP,
|
|
59
|
+
colorRampBalance: exports.DEFAULT_RAMP,
|
|
18
60
|
};
|
|
19
61
|
const SUB_PRESET_TPL = {
|
|
20
62
|
compact: '{label} {percent}%',
|
|
21
63
|
numeric: '{percent}%',
|
|
22
64
|
time: '{percent}% until {expiry}',
|
|
65
|
+
countdown: '{percent}% in {countdown}',
|
|
23
66
|
bar: '[{bar}] {percent}%',
|
|
24
67
|
'bar-time': '[{bar}] {percent}% until {expiry}',
|
|
68
|
+
'bar-countdown': '[{bar}] {percent}% in {countdown}',
|
|
25
69
|
};
|
|
26
70
|
const BAL_PRESET_TPL = {
|
|
27
71
|
compact: '{amount}',
|
|
28
72
|
numeric: '{amount}',
|
|
29
73
|
time: '{amount}',
|
|
74
|
+
countdown: '{amount}',
|
|
30
75
|
bar: '[{bar}] {amount}',
|
|
31
76
|
'bar-time': '[{bar}] {amount}',
|
|
32
|
-
}
|
|
33
|
-
const ANSI = {
|
|
34
|
-
reset: '\x1b[0m',
|
|
35
|
-
dim: '\x1b[2m',
|
|
36
|
-
green: '\x1b[32m',
|
|
37
|
-
yellow: '\x1b[33m',
|
|
38
|
-
red: '\x1b[31m',
|
|
39
|
-
boldRed: '\x1b[1;31m',
|
|
77
|
+
'bar-countdown': '[{bar}] {amount}',
|
|
40
78
|
};
|
|
41
79
|
const pad2 = (n) => n.toString().padStart(2, '0');
|
|
42
80
|
function formatExpiry(iso, now = new Date()) {
|
|
@@ -57,6 +95,26 @@ function formatExpiry(iso, now = new Date()) {
|
|
|
57
95
|
}
|
|
58
96
|
return `${target.getFullYear()}-${pad2(target.getMonth() + 1)}-${pad2(target.getDate())}`;
|
|
59
97
|
}
|
|
98
|
+
function formatCountdown(iso, now = new Date()) {
|
|
99
|
+
if (!iso)
|
|
100
|
+
return '?';
|
|
101
|
+
const target = new Date(iso);
|
|
102
|
+
if (Number.isNaN(target.getTime()))
|
|
103
|
+
return '?';
|
|
104
|
+
const secs = Math.max(0, Math.floor((target.getTime() - now.getTime()) / 1000));
|
|
105
|
+
if (secs < 60)
|
|
106
|
+
return `${secs}s`;
|
|
107
|
+
const mins = Math.floor(secs / 60);
|
|
108
|
+
if (mins < 60)
|
|
109
|
+
return `${mins}m`;
|
|
110
|
+
const hrs = Math.floor(mins / 60);
|
|
111
|
+
const remMin = mins % 60;
|
|
112
|
+
if (hrs < 24)
|
|
113
|
+
return remMin > 0 ? `${hrs}h${remMin}m` : `${hrs}h`;
|
|
114
|
+
const days = Math.floor(hrs / 24);
|
|
115
|
+
const remHr = hrs % 24;
|
|
116
|
+
return remHr > 0 ? `${days}d${remHr}h` : `${days}d`;
|
|
117
|
+
}
|
|
60
118
|
function isNonEmptyString(v) {
|
|
61
119
|
return typeof v === 'string' && v.length > 0;
|
|
62
120
|
}
|
|
@@ -100,6 +158,71 @@ function parseBarSpec(raw) {
|
|
|
100
158
|
}
|
|
101
159
|
return null;
|
|
102
160
|
}
|
|
161
|
+
function hexToAnsi(hex) {
|
|
162
|
+
const m = hex.match(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
|
|
163
|
+
if (!m)
|
|
164
|
+
return null;
|
|
165
|
+
const h = m[1];
|
|
166
|
+
let r;
|
|
167
|
+
let g;
|
|
168
|
+
let b;
|
|
169
|
+
if (h.length === 3) {
|
|
170
|
+
r = parseInt(h[0] + h[0], 16);
|
|
171
|
+
g = parseInt(h[1] + h[1], 16);
|
|
172
|
+
b = parseInt(h[2] + h[2], 16);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
r = parseInt(h.slice(0, 2), 16);
|
|
176
|
+
g = parseInt(h.slice(2, 4), 16);
|
|
177
|
+
b = parseInt(h.slice(4, 6), 16);
|
|
178
|
+
}
|
|
179
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
180
|
+
}
|
|
181
|
+
function parseColorRamp(spec) {
|
|
182
|
+
if (!spec)
|
|
183
|
+
return null;
|
|
184
|
+
const parts = spec.split(',').map((s) => s.trim()).filter(Boolean);
|
|
185
|
+
if (parts.length === 0)
|
|
186
|
+
return null;
|
|
187
|
+
const rules = [];
|
|
188
|
+
for (const p of parts) {
|
|
189
|
+
const m = p.match(/^(\d+(?:\.\d+)?):(\S+)$/);
|
|
190
|
+
if (!m)
|
|
191
|
+
return null;
|
|
192
|
+
const min = parseFloat(m[1]);
|
|
193
|
+
const token = m[2];
|
|
194
|
+
let ansi;
|
|
195
|
+
if (token === 'none') {
|
|
196
|
+
ansi = null;
|
|
197
|
+
}
|
|
198
|
+
else if (token.startsWith('#')) {
|
|
199
|
+
ansi = hexToAnsi(token);
|
|
200
|
+
if (ansi === null)
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
else if (token in exports.COLOR_MAP) {
|
|
204
|
+
ansi = exports.COLOR_MAP[token] || null;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
rules.push({ min, ansi });
|
|
210
|
+
}
|
|
211
|
+
rules.sort((a, b) => a.min - b.min);
|
|
212
|
+
return rules;
|
|
213
|
+
}
|
|
214
|
+
function colorFromRamp(util, ramp) {
|
|
215
|
+
if (!ramp || ramp.length === 0)
|
|
216
|
+
return null;
|
|
217
|
+
let picked = null;
|
|
218
|
+
for (const r of ramp) {
|
|
219
|
+
if (util >= r.min)
|
|
220
|
+
picked = r.ansi;
|
|
221
|
+
else
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
return picked;
|
|
225
|
+
}
|
|
103
226
|
function splitText(s) {
|
|
104
227
|
return Array.from(s);
|
|
105
228
|
}
|
|
@@ -118,7 +241,7 @@ function formatBar(pct, width = 10, spec, activeColor = null, useColor = false)
|
|
|
118
241
|
if (!useColor)
|
|
119
242
|
return active + inactive;
|
|
120
243
|
const activePart = activeColor && active ? paint(active, activeColor, true) : active;
|
|
121
|
-
const inactivePart = inactive && spec.emptyStyle !== 'plain' ? paint(inactive,
|
|
244
|
+
const inactivePart = inactive && spec.emptyStyle !== 'plain' ? paint(inactive, exports.COLOR_MAP.dim, true) : inactive;
|
|
122
245
|
return activePart + inactivePart;
|
|
123
246
|
}
|
|
124
247
|
if (spec?.mode === 'frames') {
|
|
@@ -128,20 +251,18 @@ function formatBar(pct, width = 10, spec, activeColor = null, useColor = false)
|
|
|
128
251
|
const filled = Math.round((clamped / 100) * width);
|
|
129
252
|
return '█'.repeat(filled) + '░'.repeat(Math.max(0, width - filled));
|
|
130
253
|
}
|
|
131
|
-
function fiveHourColor(util) {
|
|
132
|
-
if (util < 60)
|
|
133
|
-
return ANSI.green;
|
|
134
|
-
if (util < 85)
|
|
135
|
-
return ANSI.yellow;
|
|
136
|
-
return ANSI.red;
|
|
137
|
-
}
|
|
138
|
-
function weekColor(util) {
|
|
139
|
-
return util >= 80 ? ANSI.boldRed : null;
|
|
140
|
-
}
|
|
141
254
|
function paint(text, color, useColor) {
|
|
142
255
|
if (!useColor || !color)
|
|
143
256
|
return text;
|
|
144
|
-
return `${color}${text}${
|
|
257
|
+
return `${color}${text}${ANSI_RESET}`;
|
|
258
|
+
}
|
|
259
|
+
function applyTierPaint(text, color, opts, tpl) {
|
|
260
|
+
if (opts.barSpec?.mode === 'tint' && opts.color && tpl.includes('{bar}'))
|
|
261
|
+
return text;
|
|
262
|
+
return paint(text, color, opts.color);
|
|
263
|
+
}
|
|
264
|
+
function prependProvider(text, planName, opts) {
|
|
265
|
+
return opts.showProviderName && planName ? `${planName} ${text}` : text;
|
|
145
266
|
}
|
|
146
267
|
function applyTemplate(tpl, vars) {
|
|
147
268
|
return tpl.replace(/\{(\w+)\}/g, (_, k) => (k in vars ? vars[k] : `{${k}}`));
|
|
@@ -149,17 +270,17 @@ function applyTemplate(tpl, vars) {
|
|
|
149
270
|
function renderTier(ctx, opts) {
|
|
150
271
|
const pct = Math.round(ctx.tier.utilization);
|
|
151
272
|
const tpl = opts.template ?? SUB_PRESET_TPL[opts.format];
|
|
152
|
-
const
|
|
273
|
+
const ramp = ctx.label === '5h' ? opts.colorRamp5h ?? exports.DEFAULT_RAMP : opts.colorRampWk ?? exports.DEFAULT_RAMP;
|
|
274
|
+
const color = colorFromRamp(pct, ramp);
|
|
153
275
|
const text = applyTemplate(tpl, {
|
|
154
276
|
label: ctx.label,
|
|
155
277
|
percent: String(pct),
|
|
156
278
|
bar: formatBar(pct, opts.barWidth, opts.barSpec, color, opts.color),
|
|
157
279
|
expiry: formatExpiry(ctx.tier.resets_at, opts.now),
|
|
280
|
+
countdown: formatCountdown(ctx.tier.resets_at, opts.now),
|
|
158
281
|
provider: ctx.provider,
|
|
159
282
|
});
|
|
160
|
-
|
|
161
|
-
return text;
|
|
162
|
-
return paint(text, color, opts.color);
|
|
283
|
+
return applyTierPaint(text, color, opts, tpl);
|
|
163
284
|
}
|
|
164
285
|
function currencySymbol(unit) {
|
|
165
286
|
switch (unit.toUpperCase()) {
|
|
@@ -186,35 +307,35 @@ function renderSubscription(d, opts) {
|
|
|
186
307
|
if (parts.length === 0)
|
|
187
308
|
return '';
|
|
188
309
|
const sep = opts.format === 'compact' && !opts.template ? ' ' : ' / ';
|
|
189
|
-
|
|
190
|
-
return opts.showProviderName && d.planName ? `${d.planName} ${body}` : body;
|
|
310
|
+
return prependProvider(parts.join(sep), d.planName, opts);
|
|
191
311
|
}
|
|
192
312
|
function renderBalance(d, opts) {
|
|
193
313
|
if (d.isValid === false && d.invalidMessage) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
314
|
+
return prependProvider(paint(d.invalidMessage, exports.COLOR_MAP.red, opts.color), d.planName, opts);
|
|
315
|
+
}
|
|
316
|
+
if (typeof d.total !== 'number') {
|
|
317
|
+
const tpl = opts.template ?? BAL_PRESET_TPL.compact;
|
|
318
|
+
const text = applyTemplate(tpl, {
|
|
319
|
+
label: '', percent: '0', bar: '', expiry: '', countdown: '',
|
|
320
|
+
provider: d.planName ?? '',
|
|
321
|
+
amount: fmtMoney(d.remaining, d.unit),
|
|
322
|
+
});
|
|
323
|
+
return prependProvider(applyTierPaint(text, null, opts, tpl), d.planName, opts);
|
|
324
|
+
}
|
|
325
|
+
const total = d.total;
|
|
326
|
+
const usedPct = total > 0 ? ((total - d.remaining) / total) * 100 : 0;
|
|
203
327
|
const tpl = opts.template ?? BAL_PRESET_TPL[opts.format];
|
|
204
|
-
const color =
|
|
328
|
+
const color = colorFromRamp(usedPct, opts.colorRampBalance ?? exports.DEFAULT_RAMP);
|
|
205
329
|
const text = applyTemplate(tpl, {
|
|
206
330
|
label: '',
|
|
207
331
|
percent: String(Math.round(usedPct)),
|
|
208
|
-
bar:
|
|
332
|
+
bar: formatBar(usedPct, opts.barWidth, opts.barSpec, color, opts.color),
|
|
209
333
|
expiry: '',
|
|
334
|
+
countdown: '',
|
|
210
335
|
provider: d.planName ?? '',
|
|
211
|
-
amount,
|
|
336
|
+
amount: `${fmtMoney(d.remaining, d.unit)}/${fmtMoney(total, d.unit)}`,
|
|
212
337
|
});
|
|
213
|
-
|
|
214
|
-
return opts.showProviderName && d.planName ? `${d.planName} ${text}` : text;
|
|
215
|
-
}
|
|
216
|
-
const colored = paint(text, color, opts.color);
|
|
217
|
-
return opts.showProviderName && d.planName ? `${d.planName} ${colored}` : colored;
|
|
338
|
+
return prependProvider(applyTierPaint(text, color, opts, tpl), d.planName, opts);
|
|
218
339
|
}
|
|
219
340
|
function renderUsage(data, opts) {
|
|
220
341
|
if (!data)
|
|
@@ -46,7 +46,7 @@ exports.anthropicAdapter = {
|
|
|
46
46
|
if (res.authFailed)
|
|
47
47
|
return { ok: false, authFailed: true, error: res.error };
|
|
48
48
|
if (!res.ok || !res.body)
|
|
49
|
-
return { ok: false, error: res.error ?? 'request failed' };
|
|
49
|
+
return { ok: false, status: res.status, error: res.error ?? 'request failed' };
|
|
50
50
|
const data = parseAnthropic(res.body);
|
|
51
51
|
if (!data)
|
|
52
52
|
return { ok: false, error: 'invalid response shape' };
|
|
@@ -46,7 +46,7 @@ exports.deepseekAdapter = {
|
|
|
46
46
|
if (res.authFailed)
|
|
47
47
|
return { ok: false, authFailed: true, error: res.error };
|
|
48
48
|
if (!res.ok || !res.body)
|
|
49
|
-
return { ok: false, error: res.error ?? 'request failed' };
|
|
49
|
+
return { ok: false, status: res.status, error: res.error ?? 'request failed' };
|
|
50
50
|
const data = parseDeepSeek(res.body);
|
|
51
51
|
if (!data)
|
|
52
52
|
return { ok: false, error: 'invalid response shape' };
|
|
@@ -48,7 +48,7 @@ exports.glmAdapter = {
|
|
|
48
48
|
if (res.authFailed)
|
|
49
49
|
return { ok: false, authFailed: true, error: res.error };
|
|
50
50
|
if (!res.ok || !res.body)
|
|
51
|
-
return { ok: false, error: res.error ?? 'request failed' };
|
|
51
|
+
return { ok: false, status: res.status, error: res.error ?? 'request failed' };
|
|
52
52
|
const parsed = parseGlm(res.body);
|
|
53
53
|
if (!parsed)
|
|
54
54
|
return { ok: false, error: 'invalid response shape' };
|
|
@@ -48,7 +48,7 @@ exports.kimiAdapter = {
|
|
|
48
48
|
if (res.authFailed)
|
|
49
49
|
return { ok: false, authFailed: true, error: res.error };
|
|
50
50
|
if (!res.ok || !res.body)
|
|
51
|
-
return { ok: false, error: res.error ?? 'request failed' };
|
|
51
|
+
return { ok: false, status: res.status, error: res.error ?? 'request failed' };
|
|
52
52
|
const data = parseKimi(res.body);
|
|
53
53
|
if (!data)
|
|
54
54
|
return { ok: false, error: 'invalid response shape' };
|
|
@@ -55,7 +55,7 @@ exports.minimaxAdapter = {
|
|
|
55
55
|
if (res.authFailed)
|
|
56
56
|
return { ok: false, authFailed: true, error: res.error };
|
|
57
57
|
if (!res.ok || !res.body)
|
|
58
|
-
return { ok: false, error: res.error ?? 'request failed' };
|
|
58
|
+
return { ok: false, status: res.status, error: res.error ?? 'request failed' };
|
|
59
59
|
const parsed = parseMinimax(res.body);
|
|
60
60
|
if (!parsed)
|
|
61
61
|
return { ok: false, error: 'invalid response shape' };
|
|
@@ -34,7 +34,7 @@ exports.novitaAdapter = {
|
|
|
34
34
|
if (res.authFailed)
|
|
35
35
|
return { ok: false, authFailed: true, error: res.error };
|
|
36
36
|
if (!res.ok || !res.body)
|
|
37
|
-
return { ok: false, error: res.error ?? 'request failed' };
|
|
37
|
+
return { ok: false, status: res.status, error: res.error ?? 'request failed' };
|
|
38
38
|
const data = parseNovita(res.body);
|
|
39
39
|
if (!data)
|
|
40
40
|
return { ok: false, error: 'invalid response shape' };
|
|
@@ -38,7 +38,7 @@ exports.openrouterAdapter = {
|
|
|
38
38
|
if (res.authFailed)
|
|
39
39
|
return { ok: false, authFailed: true, error: res.error };
|
|
40
40
|
if (!res.ok || !res.body)
|
|
41
|
-
return { ok: false, error: res.error ?? 'request failed' };
|
|
41
|
+
return { ok: false, status: res.status, error: res.error ?? 'request failed' };
|
|
42
42
|
const data = parseOpenRouter(res.body);
|
|
43
43
|
if (!data)
|
|
44
44
|
return { ok: false, error: 'invalid response shape' };
|
|
@@ -46,7 +46,7 @@ exports.siliconflowAdapter = {
|
|
|
46
46
|
if (res.authFailed)
|
|
47
47
|
return { ok: false, authFailed: true, error: res.error };
|
|
48
48
|
if (!res.ok || !res.body)
|
|
49
|
-
return { ok: false, error: res.error ?? 'request failed' };
|
|
49
|
+
return { ok: false, status: res.status, error: res.error ?? 'request failed' };
|
|
50
50
|
const data = parseSiliconFlow(res.body);
|
|
51
51
|
if (!data)
|
|
52
52
|
return { ok: false, error: 'invalid response shape' };
|
|
@@ -43,7 +43,7 @@ exports.stepfunAdapter = {
|
|
|
43
43
|
if (res.authFailed)
|
|
44
44
|
return { ok: false, authFailed: true, error: res.error };
|
|
45
45
|
if (!res.ok || !res.body)
|
|
46
|
-
return { ok: false, error: res.error ?? 'request failed' };
|
|
46
|
+
return { ok: false, status: res.status, error: res.error ?? 'request failed' };
|
|
47
47
|
const data = parseStepFun(res.body);
|
|
48
48
|
if (!data)
|
|
49
49
|
return { ok: false, error: 'invalid response shape' };
|
package/dist/src/render.js
CHANGED
package/dist/src/settings.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.SettingsError = exports.FETCH_BIN = exports.BACKUP_PREFIX = exports.SETTINGS_PATH = exports.CLAUDE_DIR = void 0;
|
|
36
|
+
exports.SettingsError = exports.WRAP_BIN = exports.FETCH_BIN = exports.BACKUP_PREFIX = exports.SETTINGS_PATH = exports.CLAUDE_DIR = void 0;
|
|
37
37
|
exports.claudeDirExists = claudeDirExists;
|
|
38
38
|
exports.readSettings = readSettings;
|
|
39
39
|
exports.writeSettings = writeSettings;
|
|
@@ -41,6 +41,7 @@ exports.backupSettings = backupSettings;
|
|
|
41
41
|
exports.findBackups = findBackups;
|
|
42
42
|
exports.findLatestBackup = findLatestBackup;
|
|
43
43
|
exports.isInstalled = isInstalled;
|
|
44
|
+
exports.shellQuoteForPlatform = shellQuoteForPlatform;
|
|
44
45
|
exports.applyInstall = applyInstall;
|
|
45
46
|
exports.applyUninstallSurgical = applyUninstallSurgical;
|
|
46
47
|
exports.restoreFromBackup = restoreFromBackup;
|
|
@@ -52,6 +53,7 @@ exports.CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
|
52
53
|
exports.SETTINGS_PATH = path.join(exports.CLAUDE_DIR, 'settings.json');
|
|
53
54
|
exports.BACKUP_PREFIX = 'settings.json.backup-cc-usage-';
|
|
54
55
|
exports.FETCH_BIN = 'cc-usage-fetch';
|
|
56
|
+
exports.WRAP_BIN = 'cc-usage-bar-wrap';
|
|
55
57
|
class SettingsError extends Error {
|
|
56
58
|
hint;
|
|
57
59
|
constructor(message, hint) {
|
|
@@ -121,74 +123,98 @@ function unwrapPrior(existing) {
|
|
|
121
123
|
}
|
|
122
124
|
return undefined;
|
|
123
125
|
}
|
|
124
|
-
function
|
|
126
|
+
function shellQuotePosix(s) {
|
|
125
127
|
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
126
128
|
}
|
|
127
|
-
function
|
|
128
|
-
|
|
129
|
+
function shellQuoteWindows(s) {
|
|
130
|
+
return `"${s.replace(/"/g, '\\"')}"`;
|
|
131
|
+
}
|
|
132
|
+
function shellQuoteForPlatform(s, platform = process.platform) {
|
|
133
|
+
return platform === 'win32' ? shellQuoteWindows(s) : shellQuotePosix(s);
|
|
134
|
+
}
|
|
135
|
+
function buildFetchFlags(opts, platform) {
|
|
136
|
+
const parts = [];
|
|
129
137
|
if (opts.format)
|
|
130
138
|
parts.push(`--format=${opts.format}`);
|
|
131
139
|
if (typeof opts.barWidth === 'number')
|
|
132
140
|
parts.push(`--bar-width=${opts.barWidth}`);
|
|
133
141
|
if (opts.barSpec)
|
|
134
|
-
parts.push(`--bar-spec=${
|
|
142
|
+
parts.push(`--bar-spec=${shellQuoteForPlatform(opts.barSpec, platform)}`);
|
|
135
143
|
return parts.join(' ');
|
|
136
144
|
}
|
|
137
|
-
function
|
|
145
|
+
function buildBareFetchCommand(opts, platform) {
|
|
146
|
+
const flags = buildFetchFlags(opts, platform);
|
|
147
|
+
return flags ? `${exports.FETCH_BIN} ${flags}` : exports.FETCH_BIN;
|
|
148
|
+
}
|
|
149
|
+
function buildWrapCommand(prefix, opts, platform) {
|
|
150
|
+
const flags = buildFetchFlags(opts, platform);
|
|
151
|
+
const head = `${exports.WRAP_BIN} --prefix=${shellQuoteForPlatform(prefix, platform)}`;
|
|
152
|
+
return flags ? `${head} ${flags}` : head;
|
|
153
|
+
}
|
|
154
|
+
function resolveBasePrefix(sl) {
|
|
155
|
+
if (!sl)
|
|
156
|
+
return undefined;
|
|
157
|
+
if (sl._ccUsageInstalled)
|
|
158
|
+
return unwrapPrior(sl);
|
|
159
|
+
if (sl.command && (sl.command.includes(exports.FETCH_BIN) || sl.command.includes(exports.WRAP_BIN))) {
|
|
160
|
+
return applyUninstallSurgical({ statusLine: sl }).statusLine;
|
|
161
|
+
}
|
|
162
|
+
return sl;
|
|
163
|
+
}
|
|
164
|
+
function applyInstall(s, opts = {}, platform = process.platform) {
|
|
138
165
|
const next = { ...s };
|
|
139
|
-
const baseExisting =
|
|
140
|
-
const fetchCmd = buildFetchCommand(opts);
|
|
166
|
+
const baseExisting = resolveBasePrefix(next.statusLine);
|
|
141
167
|
if (!baseExisting || !baseExisting.command) {
|
|
142
168
|
next.statusLine = {
|
|
143
169
|
...(baseExisting ?? {}),
|
|
144
170
|
type: baseExisting?.type ?? 'command',
|
|
145
|
-
command:
|
|
171
|
+
command: buildBareFetchCommand(opts, platform),
|
|
146
172
|
refreshInterval: baseExisting?.refreshInterval ?? 30,
|
|
147
173
|
_ccUsageInstalled: true,
|
|
148
174
|
};
|
|
149
175
|
return next;
|
|
150
176
|
}
|
|
151
177
|
const original = baseExisting.command;
|
|
152
|
-
const escaped = original.replace(/'/g, `'\\''`);
|
|
153
|
-
const escapedFetchCmd = fetchCmd.replace(/'/g, `'\\''`);
|
|
154
|
-
const wrapped = `sh -c 'printf "%s " "$(${escaped})"; ${escapedFetchCmd}'`;
|
|
155
178
|
next.statusLine = {
|
|
156
179
|
...baseExisting,
|
|
157
|
-
command:
|
|
180
|
+
command: buildWrapCommand(original, opts, platform),
|
|
158
181
|
refreshInterval: baseExisting.refreshInterval ?? 30,
|
|
159
182
|
_ccUsageInstalled: true,
|
|
160
183
|
_ccUsageOriginal: original,
|
|
161
184
|
};
|
|
162
185
|
return next;
|
|
163
186
|
}
|
|
187
|
+
function restoreCommand(s, inner) {
|
|
188
|
+
const next = { ...s };
|
|
189
|
+
const sl = { ...s.statusLine, command: inner };
|
|
190
|
+
delete sl._ccUsageInstalled;
|
|
191
|
+
delete sl._ccUsageOriginal;
|
|
192
|
+
next.statusLine = sl;
|
|
193
|
+
return next;
|
|
194
|
+
}
|
|
164
195
|
function applyUninstallSurgical(s) {
|
|
165
196
|
if (!s.statusLine?.command)
|
|
166
197
|
return s;
|
|
167
198
|
const cmd = s.statusLine.command;
|
|
168
|
-
if (!cmd.includes(exports.FETCH_BIN))
|
|
199
|
+
if (!cmd.includes(exports.FETCH_BIN) && !cmd.includes(exports.WRAP_BIN))
|
|
169
200
|
return s;
|
|
170
|
-
const
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const inner = wrapMatch[1].replace(/'\\''/g, "'");
|
|
183
|
-
const next = { ...s };
|
|
184
|
-
const sl = { ...s.statusLine, command: inner };
|
|
185
|
-
delete sl._ccUsageInstalled;
|
|
186
|
-
delete sl._ccUsageOriginal;
|
|
187
|
-
next.statusLine = sl;
|
|
188
|
-
return next;
|
|
189
|
-
}
|
|
201
|
+
const wrapFormPosix = cmd.match(/^cc-usage-bar-wrap --prefix='((?:[^']|'\\'')*)'.*$/s);
|
|
202
|
+
if (wrapFormPosix)
|
|
203
|
+
return restoreCommand(s, wrapFormPosix[1].replace(/'\\''/g, "'"));
|
|
204
|
+
const wrapFormWin = cmd.match(/^cc-usage-bar-wrap --prefix="((?:\\.|[^"])*)".*$/s);
|
|
205
|
+
if (wrapFormWin)
|
|
206
|
+
return restoreCommand(s, wrapFormWin[1].replace(/\\"/g, '"'));
|
|
207
|
+
const printfDollarForm = cmd.match(/^sh -c 'printf "%s " "\$\((.*)\)"; cc-usage-fetch.*'$/s);
|
|
208
|
+
if (printfDollarForm)
|
|
209
|
+
return restoreCommand(s, printfDollarForm[1].replace(/'\\''/g, "'"));
|
|
210
|
+
const echoForm = cmd.match(/^sh -c '(.*); (?:echo -n|printf) " "; cc-usage-fetch[^']*'$/s);
|
|
211
|
+
if (echoForm)
|
|
212
|
+
return restoreCommand(s, echoForm[1].replace(/'\\''/g, "'"));
|
|
190
213
|
const trimmed = cmd.trim();
|
|
191
|
-
if (trimmed === exports.FETCH_BIN ||
|
|
214
|
+
if (trimmed === exports.FETCH_BIN ||
|
|
215
|
+
trimmed.startsWith(exports.FETCH_BIN + ' ') ||
|
|
216
|
+
trimmed === exports.WRAP_BIN ||
|
|
217
|
+
trimmed.startsWith(exports.WRAP_BIN + ' ')) {
|
|
192
218
|
const next = { ...s };
|
|
193
219
|
delete next.statusLine;
|
|
194
220
|
return next;
|
package/dist/src/token.js
CHANGED
|
@@ -61,6 +61,57 @@ function tryKeychain() {
|
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
const WIN_CRED_TARGET = 'Claude Code-credentials';
|
|
65
|
+
const WIN_PS_SCRIPT = `$ErrorActionPreference='Stop';try{$src=@'
|
|
66
|
+
using System;
|
|
67
|
+
using System.Runtime.InteropServices;
|
|
68
|
+
using System.Text;
|
|
69
|
+
public static class CcuCred {
|
|
70
|
+
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
|
|
71
|
+
private struct CRED {
|
|
72
|
+
public uint Flags;
|
|
73
|
+
public uint Type;
|
|
74
|
+
public IntPtr TargetName;
|
|
75
|
+
public IntPtr Comment;
|
|
76
|
+
public long LastWritten;
|
|
77
|
+
public uint BlobSize;
|
|
78
|
+
public IntPtr Blob;
|
|
79
|
+
public uint Persist;
|
|
80
|
+
public uint AttributeCount;
|
|
81
|
+
public IntPtr Attributes;
|
|
82
|
+
public IntPtr TargetAlias;
|
|
83
|
+
public IntPtr UserName;
|
|
84
|
+
}
|
|
85
|
+
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
86
|
+
private static extern bool CredRead(string target, int type, int flag, out IntPtr cred);
|
|
87
|
+
[DllImport("advapi32.dll", SetLastError=true)]
|
|
88
|
+
private static extern void CredFree(IntPtr cred);
|
|
89
|
+
public static string Read(string target) {
|
|
90
|
+
IntPtr p;
|
|
91
|
+
if (!CredRead(target, 1, 0, out p)) return null;
|
|
92
|
+
try {
|
|
93
|
+
var c = (CRED)Marshal.PtrToStructure(p, typeof(CRED));
|
|
94
|
+
if (c.BlobSize == 0 || c.Blob == IntPtr.Zero) return null;
|
|
95
|
+
byte[] buf = new byte[c.BlobSize];
|
|
96
|
+
Marshal.Copy(c.Blob, buf, 0, (int)c.BlobSize);
|
|
97
|
+
return Encoding.Unicode.GetString(buf);
|
|
98
|
+
} finally {
|
|
99
|
+
CredFree(p);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
'@;Add-Type -TypeDefinition $src -Language CSharp | Out-Null;$v=[CcuCred]::Read('${WIN_CRED_TARGET}');if($v){[Console]::Out.Write($v)}}catch{exit 1}`;
|
|
104
|
+
function tryWinCredentialManager() {
|
|
105
|
+
if (process.platform !== 'win32')
|
|
106
|
+
return null;
|
|
107
|
+
try {
|
|
108
|
+
const out = (0, node_child_process_1.execFileSync)('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', WIN_PS_SCRIPT], { stdio: ['ignore', 'pipe', 'ignore'], timeout: 5000, encoding: 'utf8', windowsHide: true });
|
|
109
|
+
return extractToken(out);
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
64
115
|
function tryFile() {
|
|
65
116
|
const credPath = path.join(os.homedir(), '.claude', '.credentials.json');
|
|
66
117
|
try {
|
|
@@ -74,6 +125,9 @@ function getToken() {
|
|
|
74
125
|
const fromKeychain = tryKeychain();
|
|
75
126
|
if (fromKeychain)
|
|
76
127
|
return { token: fromKeychain, source: 'keychain' };
|
|
128
|
+
const fromWinCred = tryWinCredentialManager();
|
|
129
|
+
if (fromWinCred)
|
|
130
|
+
return { token: fromWinCred, source: 'wincred' };
|
|
77
131
|
const fromFile = tryFile();
|
|
78
132
|
if (fromFile)
|
|
79
133
|
return { token: fromFile, source: 'file' };
|