cc-usage-bar 0.1.0 → 0.4.1
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 +233 -0
- package/README.md +15 -3
- package/README.zh-CN.md +15 -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 +188 -51
- 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,81 @@
|
|
|
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
|
+
const ANSI_REVERSE = '\x1b[7m';
|
|
27
|
+
exports.COLOR_MAP = {
|
|
28
|
+
none: '',
|
|
29
|
+
dim: '\x1b[2m',
|
|
30
|
+
black: '\x1b[30m',
|
|
31
|
+
red: '\x1b[31m',
|
|
32
|
+
green: '\x1b[32m',
|
|
33
|
+
yellow: '\x1b[33m',
|
|
34
|
+
blue: '\x1b[34m',
|
|
35
|
+
magenta: '\x1b[35m',
|
|
36
|
+
cyan: '\x1b[36m',
|
|
37
|
+
white: '\x1b[37m',
|
|
38
|
+
gray: '\x1b[90m',
|
|
39
|
+
boldRed: '\x1b[1;31m',
|
|
40
|
+
boldGreen: '\x1b[1;32m',
|
|
41
|
+
boldYellow: '\x1b[1;33m',
|
|
42
|
+
boldBlue: '\x1b[1;34m',
|
|
43
|
+
boldMagenta: '\x1b[1;35m',
|
|
44
|
+
boldCyan: '\x1b[1;36m',
|
|
45
|
+
boldWhite: '\x1b[1;37m',
|
|
46
|
+
};
|
|
47
|
+
exports.COLOR_NAMES = Object.keys(exports.COLOR_MAP);
|
|
48
|
+
exports.DEFAULT_RAMP = [
|
|
49
|
+
{ min: 0, ansi: exports.COLOR_MAP.green },
|
|
50
|
+
{ min: 60, ansi: exports.COLOR_MAP.yellow },
|
|
51
|
+
{ min: 85, ansi: exports.COLOR_MAP.red },
|
|
52
|
+
];
|
|
13
53
|
exports.DEFAULT_FORMAT = {
|
|
14
|
-
format: '
|
|
54
|
+
format: 'bar-countdown',
|
|
15
55
|
barWidth: 10,
|
|
16
56
|
color: true,
|
|
17
57
|
showProviderName: true,
|
|
58
|
+
colorRamp5h: exports.DEFAULT_RAMP,
|
|
59
|
+
colorRampWk: exports.DEFAULT_RAMP,
|
|
60
|
+
colorRampBalance: exports.DEFAULT_RAMP,
|
|
18
61
|
};
|
|
19
62
|
const SUB_PRESET_TPL = {
|
|
20
63
|
compact: '{label} {percent}%',
|
|
21
64
|
numeric: '{percent}%',
|
|
22
65
|
time: '{percent}% until {expiry}',
|
|
66
|
+
countdown: '{percent}% in {countdown}',
|
|
23
67
|
bar: '[{bar}] {percent}%',
|
|
24
68
|
'bar-time': '[{bar}] {percent}% until {expiry}',
|
|
69
|
+
'bar-countdown': '[{bar}] {percent}% in {countdown}',
|
|
25
70
|
};
|
|
26
71
|
const BAL_PRESET_TPL = {
|
|
27
72
|
compact: '{amount}',
|
|
28
73
|
numeric: '{amount}',
|
|
29
74
|
time: '{amount}',
|
|
75
|
+
countdown: '{amount}',
|
|
30
76
|
bar: '[{bar}] {amount}',
|
|
31
77
|
'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',
|
|
78
|
+
'bar-countdown': '[{bar}] {amount}',
|
|
40
79
|
};
|
|
41
80
|
const pad2 = (n) => n.toString().padStart(2, '0');
|
|
42
81
|
function formatExpiry(iso, now = new Date()) {
|
|
@@ -57,6 +96,26 @@ function formatExpiry(iso, now = new Date()) {
|
|
|
57
96
|
}
|
|
58
97
|
return `${target.getFullYear()}-${pad2(target.getMonth() + 1)}-${pad2(target.getDate())}`;
|
|
59
98
|
}
|
|
99
|
+
function formatCountdown(iso, now = new Date()) {
|
|
100
|
+
if (!iso)
|
|
101
|
+
return '?';
|
|
102
|
+
const target = new Date(iso);
|
|
103
|
+
if (Number.isNaN(target.getTime()))
|
|
104
|
+
return '?';
|
|
105
|
+
const secs = Math.max(0, Math.floor((target.getTime() - now.getTime()) / 1000));
|
|
106
|
+
if (secs < 60)
|
|
107
|
+
return `${secs}s`;
|
|
108
|
+
const mins = Math.floor(secs / 60);
|
|
109
|
+
if (mins < 60)
|
|
110
|
+
return `${mins}m`;
|
|
111
|
+
const hrs = Math.floor(mins / 60);
|
|
112
|
+
const remMin = mins % 60;
|
|
113
|
+
if (hrs < 24)
|
|
114
|
+
return remMin > 0 ? `${hrs}h${remMin}m` : `${hrs}h`;
|
|
115
|
+
const days = Math.floor(hrs / 24);
|
|
116
|
+
const remHr = hrs % 24;
|
|
117
|
+
return remHr > 0 ? `${days}d${remHr}h` : `${days}d`;
|
|
118
|
+
}
|
|
60
119
|
function isNonEmptyString(v) {
|
|
61
120
|
return typeof v === 'string' && v.length > 0;
|
|
62
121
|
}
|
|
@@ -88,6 +147,8 @@ function parseBarSpec(raw) {
|
|
|
88
147
|
const out = { mode: 'tint', text: obj.text };
|
|
89
148
|
if (obj.emptyStyle === 'plain' || obj.emptyStyle === 'dim')
|
|
90
149
|
out.emptyStyle = obj.emptyStyle;
|
|
150
|
+
if (obj.style === 'fg' || obj.style === 'reverse')
|
|
151
|
+
out.style = obj.style;
|
|
91
152
|
return out;
|
|
92
153
|
}
|
|
93
154
|
if (obj.mode === 'frames') {
|
|
@@ -100,6 +161,71 @@ function parseBarSpec(raw) {
|
|
|
100
161
|
}
|
|
101
162
|
return null;
|
|
102
163
|
}
|
|
164
|
+
function hexToAnsi(hex) {
|
|
165
|
+
const m = hex.match(/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/);
|
|
166
|
+
if (!m)
|
|
167
|
+
return null;
|
|
168
|
+
const h = m[1];
|
|
169
|
+
let r;
|
|
170
|
+
let g;
|
|
171
|
+
let b;
|
|
172
|
+
if (h.length === 3) {
|
|
173
|
+
r = parseInt(h[0] + h[0], 16);
|
|
174
|
+
g = parseInt(h[1] + h[1], 16);
|
|
175
|
+
b = parseInt(h[2] + h[2], 16);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
r = parseInt(h.slice(0, 2), 16);
|
|
179
|
+
g = parseInt(h.slice(2, 4), 16);
|
|
180
|
+
b = parseInt(h.slice(4, 6), 16);
|
|
181
|
+
}
|
|
182
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
183
|
+
}
|
|
184
|
+
function parseColorRamp(spec) {
|
|
185
|
+
if (!spec)
|
|
186
|
+
return null;
|
|
187
|
+
const parts = spec.split(',').map((s) => s.trim()).filter(Boolean);
|
|
188
|
+
if (parts.length === 0)
|
|
189
|
+
return null;
|
|
190
|
+
const rules = [];
|
|
191
|
+
for (const p of parts) {
|
|
192
|
+
const m = p.match(/^(\d+(?:\.\d+)?):(\S+)$/);
|
|
193
|
+
if (!m)
|
|
194
|
+
return null;
|
|
195
|
+
const min = parseFloat(m[1]);
|
|
196
|
+
const token = m[2];
|
|
197
|
+
let ansi;
|
|
198
|
+
if (token === 'none') {
|
|
199
|
+
ansi = null;
|
|
200
|
+
}
|
|
201
|
+
else if (token.startsWith('#')) {
|
|
202
|
+
ansi = hexToAnsi(token);
|
|
203
|
+
if (ansi === null)
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
else if (token in exports.COLOR_MAP) {
|
|
207
|
+
ansi = exports.COLOR_MAP[token] || null;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
rules.push({ min, ansi });
|
|
213
|
+
}
|
|
214
|
+
rules.sort((a, b) => a.min - b.min);
|
|
215
|
+
return rules;
|
|
216
|
+
}
|
|
217
|
+
function colorFromRamp(util, ramp) {
|
|
218
|
+
if (!ramp || ramp.length === 0)
|
|
219
|
+
return null;
|
|
220
|
+
let picked = null;
|
|
221
|
+
for (const r of ramp) {
|
|
222
|
+
if (util >= r.min)
|
|
223
|
+
picked = r.ansi;
|
|
224
|
+
else
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
return picked;
|
|
228
|
+
}
|
|
103
229
|
function splitText(s) {
|
|
104
230
|
return Array.from(s);
|
|
105
231
|
}
|
|
@@ -117,8 +243,9 @@ function formatBar(pct, width = 10, spec, activeColor = null, useColor = false)
|
|
|
117
243
|
const inactive = chars.slice(filled).join('');
|
|
118
244
|
if (!useColor)
|
|
119
245
|
return active + inactive;
|
|
120
|
-
const
|
|
121
|
-
const
|
|
246
|
+
const activeStyle = activeColor && spec.style === 'reverse' ? `${activeColor}${ANSI_REVERSE}` : activeColor;
|
|
247
|
+
const activePart = activeStyle && active ? paint(active, activeStyle, true) : active;
|
|
248
|
+
const inactivePart = inactive && spec.emptyStyle !== 'plain' ? paint(inactive, exports.COLOR_MAP.dim, true) : inactive;
|
|
122
249
|
return activePart + inactivePart;
|
|
123
250
|
}
|
|
124
251
|
if (spec?.mode === 'frames') {
|
|
@@ -128,20 +255,30 @@ function formatBar(pct, width = 10, spec, activeColor = null, useColor = false)
|
|
|
128
255
|
const filled = Math.round((clamped / 100) * width);
|
|
129
256
|
return '█'.repeat(filled) + '░'.repeat(Math.max(0, width - filled));
|
|
130
257
|
}
|
|
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
258
|
function paint(text, color, useColor) {
|
|
142
259
|
if (!useColor || !color)
|
|
143
260
|
return text;
|
|
144
|
-
return `${color}${text}${
|
|
261
|
+
return `${color}${text}${ANSI_RESET}`;
|
|
262
|
+
}
|
|
263
|
+
function applyTierPaint(text, color, opts, tpl) {
|
|
264
|
+
return paint(text, color, opts.color);
|
|
265
|
+
}
|
|
266
|
+
function applyTintTemplate(tpl, vars, color, opts) {
|
|
267
|
+
if (!opts.color || !color)
|
|
268
|
+
return applyTemplate(tpl, vars);
|
|
269
|
+
return tpl
|
|
270
|
+
.split(/(\{bar\})/g)
|
|
271
|
+
.map((part) => (part === '{bar}' ? vars.bar ?? '{bar}' : paint(applyTemplate(part, vars), color, true)))
|
|
272
|
+
.join('');
|
|
273
|
+
}
|
|
274
|
+
function renderTemplate(tpl, vars, color, opts) {
|
|
275
|
+
if (opts.barSpec?.mode === 'tint' && tpl.includes('{bar}')) {
|
|
276
|
+
return applyTintTemplate(tpl, vars, color, opts);
|
|
277
|
+
}
|
|
278
|
+
return applyTierPaint(applyTemplate(tpl, vars), color, opts, tpl);
|
|
279
|
+
}
|
|
280
|
+
function prependProvider(text, planName, opts) {
|
|
281
|
+
return opts.showProviderName && planName ? `${planName} ${text}` : text;
|
|
145
282
|
}
|
|
146
283
|
function applyTemplate(tpl, vars) {
|
|
147
284
|
return tpl.replace(/\{(\w+)\}/g, (_, k) => (k in vars ? vars[k] : `{${k}}`));
|
|
@@ -149,17 +286,17 @@ function applyTemplate(tpl, vars) {
|
|
|
149
286
|
function renderTier(ctx, opts) {
|
|
150
287
|
const pct = Math.round(ctx.tier.utilization);
|
|
151
288
|
const tpl = opts.template ?? SUB_PRESET_TPL[opts.format];
|
|
152
|
-
const
|
|
153
|
-
const
|
|
289
|
+
const ramp = ctx.label === '5h' ? opts.colorRamp5h ?? exports.DEFAULT_RAMP : opts.colorRampWk ?? exports.DEFAULT_RAMP;
|
|
290
|
+
const color = colorFromRamp(pct, ramp);
|
|
291
|
+
const vars = {
|
|
154
292
|
label: ctx.label,
|
|
155
293
|
percent: String(pct),
|
|
156
294
|
bar: formatBar(pct, opts.barWidth, opts.barSpec, color, opts.color),
|
|
157
295
|
expiry: formatExpiry(ctx.tier.resets_at, opts.now),
|
|
296
|
+
countdown: formatCountdown(ctx.tier.resets_at, opts.now),
|
|
158
297
|
provider: ctx.provider,
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return text;
|
|
162
|
-
return paint(text, color, opts.color);
|
|
298
|
+
};
|
|
299
|
+
return renderTemplate(tpl, vars, color, opts);
|
|
163
300
|
}
|
|
164
301
|
function currencySymbol(unit) {
|
|
165
302
|
switch (unit.toUpperCase()) {
|
|
@@ -186,35 +323,35 @@ function renderSubscription(d, opts) {
|
|
|
186
323
|
if (parts.length === 0)
|
|
187
324
|
return '';
|
|
188
325
|
const sep = opts.format === 'compact' && !opts.template ? ' ' : ' / ';
|
|
189
|
-
|
|
190
|
-
return opts.showProviderName && d.planName ? `${d.planName} ${body}` : body;
|
|
326
|
+
return prependProvider(parts.join(sep), d.planName, opts);
|
|
191
327
|
}
|
|
192
328
|
function renderBalance(d, opts) {
|
|
193
329
|
if (d.isValid === false && d.invalidMessage) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
330
|
+
return prependProvider(paint(d.invalidMessage, exports.COLOR_MAP.red, opts.color), d.planName, opts);
|
|
331
|
+
}
|
|
332
|
+
if (typeof d.total !== 'number') {
|
|
333
|
+
const tpl = opts.template ?? BAL_PRESET_TPL.compact;
|
|
334
|
+
const vars = {
|
|
335
|
+
label: '', percent: '0', bar: '', expiry: '', countdown: '',
|
|
336
|
+
provider: d.planName ?? '',
|
|
337
|
+
amount: fmtMoney(d.remaining, d.unit),
|
|
338
|
+
};
|
|
339
|
+
return prependProvider(renderTemplate(tpl, vars, null, opts), d.planName, opts);
|
|
340
|
+
}
|
|
341
|
+
const total = d.total;
|
|
342
|
+
const usedPct = total > 0 ? ((total - d.remaining) / total) * 100 : 0;
|
|
203
343
|
const tpl = opts.template ?? BAL_PRESET_TPL[opts.format];
|
|
204
|
-
const color =
|
|
205
|
-
const
|
|
344
|
+
const color = colorFromRamp(usedPct, opts.colorRampBalance ?? exports.DEFAULT_RAMP);
|
|
345
|
+
const vars = {
|
|
206
346
|
label: '',
|
|
207
347
|
percent: String(Math.round(usedPct)),
|
|
208
|
-
bar:
|
|
348
|
+
bar: formatBar(usedPct, opts.barWidth, opts.barSpec, color, opts.color),
|
|
209
349
|
expiry: '',
|
|
350
|
+
countdown: '',
|
|
210
351
|
provider: d.planName ?? '',
|
|
211
|
-
amount,
|
|
212
|
-
}
|
|
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;
|
|
352
|
+
amount: `${fmtMoney(d.remaining, d.unit)}/${fmtMoney(total, d.unit)}`,
|
|
353
|
+
};
|
|
354
|
+
return prependProvider(renderTemplate(tpl, vars, color, opts), d.planName, opts);
|
|
218
355
|
}
|
|
219
356
|
function renderUsage(data, opts) {
|
|
220
357
|
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' };
|