cc-usage-bar 0.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/LICENSE +21 -0
- package/README.md +191 -0
- package/README.zh-CN.md +198 -0
- package/bin/cc-usage-fetch.js +7 -0
- package/bin/cc-usage-statusline.js +2 -0
- package/dist/src/cli.js +241 -0
- package/dist/src/fetch.js +307 -0
- package/dist/src/format.js +229 -0
- package/dist/src/providers/anthropic.js +55 -0
- package/dist/src/providers/deepseek.js +55 -0
- package/dist/src/providers/glm.js +59 -0
- package/dist/src/providers/http.js +26 -0
- package/dist/src/providers/kimi.js +57 -0
- package/dist/src/providers/minimax.js +66 -0
- package/dist/src/providers/novita.js +43 -0
- package/dist/src/providers/openrouter.js +47 -0
- package/dist/src/providers/registry.js +40 -0
- package/dist/src/providers/siliconflow.js +55 -0
- package/dist/src/providers/stepfun.js +52 -0
- package/dist/src/providers/types.js +2 -0
- package/dist/src/render.js +29 -0
- package/dist/src/settings.js +215 -0
- package/dist/src/token.js +85 -0
- package/package.json +60 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.readStdinJson = readStdinJson;
|
|
37
|
+
exports.extractFromStdin = extractFromStdin;
|
|
38
|
+
exports.run = run;
|
|
39
|
+
exports.main = main;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const os = __importStar(require("node:os"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const render_1 = require("./render");
|
|
44
|
+
const registry_1 = require("./providers/registry");
|
|
45
|
+
const format_1 = require("./format");
|
|
46
|
+
const CACHE_PATH = path.join(os.tmpdir(), 'cc-oauth-usage.json');
|
|
47
|
+
const SUCCESS_TTL_MS = 30_000;
|
|
48
|
+
const AUTH_FAIL_TTL_MS = 60_000;
|
|
49
|
+
const STDIN_TIMEOUT_MS = 500;
|
|
50
|
+
async function readStdinJson(timeoutMs = STDIN_TIMEOUT_MS) {
|
|
51
|
+
if (process.stdin.isTTY)
|
|
52
|
+
return null;
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
const chunks = [];
|
|
55
|
+
let resolved = false;
|
|
56
|
+
const finish = (val) => {
|
|
57
|
+
if (resolved)
|
|
58
|
+
return;
|
|
59
|
+
resolved = true;
|
|
60
|
+
resolve(val);
|
|
61
|
+
};
|
|
62
|
+
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
63
|
+
process.stdin.on('data', (c) => {
|
|
64
|
+
chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));
|
|
65
|
+
});
|
|
66
|
+
process.stdin.on('end', () => {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
const raw = Buffer.concat(chunks).toString('utf8').trim();
|
|
69
|
+
if (!raw)
|
|
70
|
+
return finish(null);
|
|
71
|
+
try {
|
|
72
|
+
finish(JSON.parse(raw));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
finish(null);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
process.stdin.on('error', () => {
|
|
79
|
+
clearTimeout(timer);
|
|
80
|
+
finish(null);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function normalizeStdinTier(t) {
|
|
85
|
+
if (!t || typeof t !== 'object')
|
|
86
|
+
return null;
|
|
87
|
+
const obj = t;
|
|
88
|
+
const pct = obj.used_percentage;
|
|
89
|
+
if (typeof pct !== 'number' || !Number.isFinite(pct))
|
|
90
|
+
return null;
|
|
91
|
+
const out = { utilization: pct };
|
|
92
|
+
const r = obj.resets_at;
|
|
93
|
+
if (typeof r === 'number' && Number.isFinite(r)) {
|
|
94
|
+
out.resets_at = new Date(r * 1000).toISOString();
|
|
95
|
+
}
|
|
96
|
+
else if (typeof r === 'string' && r) {
|
|
97
|
+
out.resets_at = r;
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
function extractFromStdin(json) {
|
|
102
|
+
if (!json || typeof json !== 'object')
|
|
103
|
+
return null;
|
|
104
|
+
const rl = json.rate_limits;
|
|
105
|
+
if (!rl || typeof rl !== 'object')
|
|
106
|
+
return null;
|
|
107
|
+
const obj = rl;
|
|
108
|
+
const result = {};
|
|
109
|
+
const fh = normalizeStdinTier(obj.five_hour);
|
|
110
|
+
if (fh)
|
|
111
|
+
result.five_hour = fh;
|
|
112
|
+
const sd = normalizeStdinTier(obj.seven_day);
|
|
113
|
+
if (sd)
|
|
114
|
+
result.seven_day = sd;
|
|
115
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
116
|
+
}
|
|
117
|
+
function stdinAsSubscription(d) {
|
|
118
|
+
if (!d)
|
|
119
|
+
return null;
|
|
120
|
+
const out = { kind: 'subscription' };
|
|
121
|
+
if (typeof d.five_hour?.utilization === 'number') {
|
|
122
|
+
out.five_hour = { utilization: d.five_hour.utilization, resets_at: d.five_hour.resets_at };
|
|
123
|
+
}
|
|
124
|
+
if (typeof d.seven_day?.utilization === 'number') {
|
|
125
|
+
out.seven_day = { utilization: d.seven_day.utilization, resets_at: d.seven_day.resets_at };
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
function readCache() {
|
|
130
|
+
try {
|
|
131
|
+
const raw = fs.readFileSync(CACHE_PATH, 'utf8');
|
|
132
|
+
const p = JSON.parse(raw);
|
|
133
|
+
if (typeof p.fetched_at !== 'number' || !p.status || !p.adapter_id)
|
|
134
|
+
return null;
|
|
135
|
+
return p;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function writeCache(entry) {
|
|
142
|
+
try {
|
|
143
|
+
fs.writeFileSync(CACHE_PATH, JSON.stringify(entry));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function isCacheFresh(entry) {
|
|
149
|
+
const age = Date.now() - entry.fetched_at;
|
|
150
|
+
if (entry.status === 'ok')
|
|
151
|
+
return age >= 0 && age < SUCCESS_TTL_MS;
|
|
152
|
+
if (entry.status === 'auth_failed')
|
|
153
|
+
return age >= 0 && age < AUTH_FAIL_TTL_MS;
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
async function run(opts = {}) {
|
|
157
|
+
if (!opts.skipStdin) {
|
|
158
|
+
const stdinJson = await readStdinJson();
|
|
159
|
+
const fromStdin = stdinAsSubscription(extractFromStdin(stdinJson));
|
|
160
|
+
if (fromStdin) {
|
|
161
|
+
return {
|
|
162
|
+
data: fromStdin,
|
|
163
|
+
source: 'stdin',
|
|
164
|
+
adapterId: 'anthropic',
|
|
165
|
+
cacheStatus: 'skipped',
|
|
166
|
+
authFailed: false,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const env = (0, registry_1.getEnv)();
|
|
171
|
+
const adapter = (0, registry_1.selectAdapter)(env);
|
|
172
|
+
if (!adapter) {
|
|
173
|
+
return {
|
|
174
|
+
data: null,
|
|
175
|
+
source: 'none',
|
|
176
|
+
adapterId: null,
|
|
177
|
+
cacheStatus: 'miss',
|
|
178
|
+
authFailed: false,
|
|
179
|
+
error: `unsupported provider for ${env.baseUrl ?? '<no base url>'}`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const cache = opts.forceFresh ? null : readCache();
|
|
183
|
+
if (cache && cache.adapter_id === adapter.id && isCacheFresh(cache)) {
|
|
184
|
+
return {
|
|
185
|
+
data: cache.data,
|
|
186
|
+
source: 'cache',
|
|
187
|
+
adapterId: adapter.id,
|
|
188
|
+
cacheStatus: cache.status === 'auth_failed' ? 'auth_failed_cached' : 'hit',
|
|
189
|
+
authFailed: cache.status === 'auth_failed',
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const cacheStatusOnMiss = cache && cache.adapter_id !== adapter.id ? 'adapter_changed' : cache ? 'expired' : 'miss';
|
|
193
|
+
const result = await adapter.fetch(env);
|
|
194
|
+
if (result.ok && result.data) {
|
|
195
|
+
writeCache({ fetched_at: Date.now(), status: 'ok', adapter_id: adapter.id, data: result.data });
|
|
196
|
+
return {
|
|
197
|
+
data: result.data,
|
|
198
|
+
source: 'api',
|
|
199
|
+
adapterId: adapter.id,
|
|
200
|
+
cacheStatus: cacheStatusOnMiss,
|
|
201
|
+
authFailed: false,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (result.authFailed) {
|
|
205
|
+
writeCache({ fetched_at: Date.now(), status: 'auth_failed', adapter_id: adapter.id, data: null });
|
|
206
|
+
return {
|
|
207
|
+
data: null,
|
|
208
|
+
source: 'api',
|
|
209
|
+
adapterId: adapter.id,
|
|
210
|
+
cacheStatus: cacheStatusOnMiss,
|
|
211
|
+
authFailed: true,
|
|
212
|
+
error: result.error,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
data: null,
|
|
217
|
+
source: 'api',
|
|
218
|
+
adapterId: adapter.id,
|
|
219
|
+
cacheStatus: cacheStatusOnMiss,
|
|
220
|
+
authFailed: false,
|
|
221
|
+
error: result.error,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function parseArg(args, name) {
|
|
225
|
+
const eq = args.find((a) => a.startsWith(`--${name}=`));
|
|
226
|
+
if (eq)
|
|
227
|
+
return eq.slice(name.length + 3);
|
|
228
|
+
const idx = args.indexOf(`--${name}`);
|
|
229
|
+
if (idx >= 0 && idx + 1 < args.length)
|
|
230
|
+
return args[idx + 1];
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
const HELP = `cc-usage-fetch — emit one statusline string from Claude Code subscription data.
|
|
234
|
+
|
|
235
|
+
Usage: cc-usage-fetch [options]
|
|
236
|
+
|
|
237
|
+
Output:
|
|
238
|
+
--json Emit raw JSON (data + meta) instead of rendered string
|
|
239
|
+
--plain Drop ANSI colors
|
|
240
|
+
--no-stdin Skip reading rate_limits from stdin (force live query)
|
|
241
|
+
|
|
242
|
+
Format:
|
|
243
|
+
--format=<preset> ${format_1.FORMAT_PRESETS.join(' | ')} (default: compact)
|
|
244
|
+
--bar-width=<N> Bar width for bar / bar-time presets (default: 10)
|
|
245
|
+
--bar-spec=<json> Custom bar JSON: cells, tint, or frames
|
|
246
|
+
|
|
247
|
+
Custom template (env, overrides --format):
|
|
248
|
+
CC_USAGE_TEMPLATE e.g. '{label} {percent}% ({expiry})'
|
|
249
|
+
placeholders: {label} {percent} {bar} {expiry} {provider} {amount}
|
|
250
|
+
CC_USAGE_BAR_SPEC Same JSON shape as --bar-spec
|
|
251
|
+
|
|
252
|
+
Presets render like:
|
|
253
|
+
compact 5h 47% Wk 59%
|
|
254
|
+
numeric 47% / 59%
|
|
255
|
+
time 47% until 18:23 / 59% until 5/12 09:00
|
|
256
|
+
bar [█████░░░░░] 47% / [██████░░░░] 59%
|
|
257
|
+
bar-time [█████░░░░░] 47% until 18:23 / [██████░░░░] 59% until 5/12 09:00
|
|
258
|
+
|
|
259
|
+
Other:
|
|
260
|
+
NO_COLOR=1 Force-disable colors
|
|
261
|
+
--help Show this message
|
|
262
|
+
`;
|
|
263
|
+
async function main() {
|
|
264
|
+
const args = process.argv.slice(2);
|
|
265
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
266
|
+
process.stdout.write(HELP);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const wantJson = args.includes('--json');
|
|
270
|
+
const wantPlain = args.includes('--plain');
|
|
271
|
+
const skipStdin = args.includes('--no-stdin');
|
|
272
|
+
const formatArg = parseArg(args, 'format');
|
|
273
|
+
const barWidthArg = parseArg(args, 'bar-width');
|
|
274
|
+
const barSpecArg = parseArg(args, 'bar-spec');
|
|
275
|
+
const templateEnv = process.env.CC_USAGE_TEMPLATE;
|
|
276
|
+
const barSpecEnv = process.env.CC_USAGE_BAR_SPEC;
|
|
277
|
+
const fmt = { ...format_1.DEFAULT_FORMAT };
|
|
278
|
+
if (formatArg && (0, format_1.isValidPreset)(formatArg))
|
|
279
|
+
fmt.format = formatArg;
|
|
280
|
+
if (barWidthArg) {
|
|
281
|
+
const n = parseInt(barWidthArg, 10);
|
|
282
|
+
if (Number.isFinite(n) && n > 0 && n <= 50)
|
|
283
|
+
fmt.barWidth = n;
|
|
284
|
+
}
|
|
285
|
+
if (templateEnv)
|
|
286
|
+
fmt.template = templateEnv;
|
|
287
|
+
const barSpec = (0, format_1.parseBarSpec)(barSpecArg ?? barSpecEnv);
|
|
288
|
+
if (barSpec)
|
|
289
|
+
fmt.barSpec = barSpec;
|
|
290
|
+
const result = await run({ skipStdin });
|
|
291
|
+
if (wantJson) {
|
|
292
|
+
process.stdout.write(JSON.stringify({
|
|
293
|
+
data: result.data,
|
|
294
|
+
source: result.source,
|
|
295
|
+
adapterId: result.adapterId,
|
|
296
|
+
cacheStatus: result.cacheStatus,
|
|
297
|
+
authFailed: result.authFailed,
|
|
298
|
+
error: result.error ?? null,
|
|
299
|
+
}, null, 2) + '\n');
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
fmt.color = !wantPlain && (0, render_1.shouldUseColor)();
|
|
303
|
+
fmt.showProviderName = result.adapterId !== null && result.adapterId !== 'anthropic';
|
|
304
|
+
const out = (0, render_1.renderNormalized)(result.data, fmt);
|
|
305
|
+
if (out)
|
|
306
|
+
process.stdout.write(out);
|
|
307
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_FORMAT = exports.FORMAT_PRESETS = void 0;
|
|
4
|
+
exports.formatExpiry = formatExpiry;
|
|
5
|
+
exports.parseBarSpec = parseBarSpec;
|
|
6
|
+
exports.formatBar = formatBar;
|
|
7
|
+
exports.renderSubscription = renderSubscription;
|
|
8
|
+
exports.renderBalance = renderBalance;
|
|
9
|
+
exports.renderUsage = renderUsage;
|
|
10
|
+
exports.isValidPreset = isValidPreset;
|
|
11
|
+
exports.shouldUseColor = shouldUseColor;
|
|
12
|
+
exports.FORMAT_PRESETS = ['compact', 'numeric', 'time', 'bar', 'bar-time'];
|
|
13
|
+
exports.DEFAULT_FORMAT = {
|
|
14
|
+
format: 'compact',
|
|
15
|
+
barWidth: 10,
|
|
16
|
+
color: true,
|
|
17
|
+
showProviderName: true,
|
|
18
|
+
};
|
|
19
|
+
const SUB_PRESET_TPL = {
|
|
20
|
+
compact: '{label} {percent}%',
|
|
21
|
+
numeric: '{percent}%',
|
|
22
|
+
time: '{percent}% until {expiry}',
|
|
23
|
+
bar: '[{bar}] {percent}%',
|
|
24
|
+
'bar-time': '[{bar}] {percent}% until {expiry}',
|
|
25
|
+
};
|
|
26
|
+
const BAL_PRESET_TPL = {
|
|
27
|
+
compact: '{amount}',
|
|
28
|
+
numeric: '{amount}',
|
|
29
|
+
time: '{amount}',
|
|
30
|
+
bar: '[{bar}] {amount}',
|
|
31
|
+
'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',
|
|
40
|
+
};
|
|
41
|
+
const pad2 = (n) => n.toString().padStart(2, '0');
|
|
42
|
+
function formatExpiry(iso, now = new Date()) {
|
|
43
|
+
if (!iso)
|
|
44
|
+
return '?';
|
|
45
|
+
const target = new Date(iso);
|
|
46
|
+
if (Number.isNaN(target.getTime()))
|
|
47
|
+
return '?';
|
|
48
|
+
const sameDay = target.getFullYear() === now.getFullYear() &&
|
|
49
|
+
target.getMonth() === now.getMonth() &&
|
|
50
|
+
target.getDate() === now.getDate();
|
|
51
|
+
if (sameDay)
|
|
52
|
+
return `${pad2(target.getHours())}:${pad2(target.getMinutes())}`;
|
|
53
|
+
const diffMs = target.getTime() - now.getTime();
|
|
54
|
+
const diffDays = Math.floor(diffMs / 86_400_000);
|
|
55
|
+
if (diffDays >= 0 && diffDays < 7) {
|
|
56
|
+
return `${target.getMonth() + 1}/${target.getDate()} ${pad2(target.getHours())}:${pad2(target.getMinutes())}`;
|
|
57
|
+
}
|
|
58
|
+
return `${target.getFullYear()}-${pad2(target.getMonth() + 1)}-${pad2(target.getDate())}`;
|
|
59
|
+
}
|
|
60
|
+
function isNonEmptyString(v) {
|
|
61
|
+
return typeof v === 'string' && v.length > 0;
|
|
62
|
+
}
|
|
63
|
+
function parseBarSpec(raw) {
|
|
64
|
+
if (!raw)
|
|
65
|
+
return null;
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(raw);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (!parsed || typeof parsed !== 'object')
|
|
74
|
+
return null;
|
|
75
|
+
const obj = parsed;
|
|
76
|
+
if (obj.mode === 'cells') {
|
|
77
|
+
if (!isNonEmptyString(obj.filled) || !isNonEmptyString(obj.empty))
|
|
78
|
+
return null;
|
|
79
|
+
const out = { mode: 'cells', filled: obj.filled, empty: obj.empty };
|
|
80
|
+
if (typeof obj.width === 'number' && Number.isFinite(obj.width) && obj.width >= 1 && obj.width <= 50) {
|
|
81
|
+
out.width = Math.floor(obj.width);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
if (obj.mode === 'tint') {
|
|
86
|
+
if (!isNonEmptyString(obj.text))
|
|
87
|
+
return null;
|
|
88
|
+
const out = { mode: 'tint', text: obj.text };
|
|
89
|
+
if (obj.emptyStyle === 'plain' || obj.emptyStyle === 'dim')
|
|
90
|
+
out.emptyStyle = obj.emptyStyle;
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
if (obj.mode === 'frames') {
|
|
94
|
+
if (!Array.isArray(obj.frames))
|
|
95
|
+
return null;
|
|
96
|
+
const frames = obj.frames.filter(isNonEmptyString);
|
|
97
|
+
if (frames.length < 2)
|
|
98
|
+
return null;
|
|
99
|
+
return { mode: 'frames', frames };
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
function splitText(s) {
|
|
104
|
+
return Array.from(s);
|
|
105
|
+
}
|
|
106
|
+
function formatBar(pct, width = 10, spec, activeColor = null, useColor = false) {
|
|
107
|
+
const clamped = Math.max(0, Math.min(100, pct));
|
|
108
|
+
if (spec?.mode === 'cells') {
|
|
109
|
+
const specWidth = spec.width ?? width;
|
|
110
|
+
const filled = Math.round((clamped / 100) * specWidth);
|
|
111
|
+
return spec.filled.repeat(filled) + spec.empty.repeat(Math.max(0, specWidth - filled));
|
|
112
|
+
}
|
|
113
|
+
if (spec?.mode === 'tint') {
|
|
114
|
+
const chars = splitText(spec.text);
|
|
115
|
+
const filled = Math.round((clamped / 100) * chars.length);
|
|
116
|
+
const active = chars.slice(0, filled).join('');
|
|
117
|
+
const inactive = chars.slice(filled).join('');
|
|
118
|
+
if (!useColor)
|
|
119
|
+
return active + inactive;
|
|
120
|
+
const activePart = activeColor && active ? paint(active, activeColor, true) : active;
|
|
121
|
+
const inactivePart = inactive && spec.emptyStyle !== 'plain' ? paint(inactive, ANSI.dim, true) : inactive;
|
|
122
|
+
return activePart + inactivePart;
|
|
123
|
+
}
|
|
124
|
+
if (spec?.mode === 'frames') {
|
|
125
|
+
const idx = Math.min(spec.frames.length - 1, Math.floor((clamped / 100) * spec.frames.length));
|
|
126
|
+
return spec.frames[idx];
|
|
127
|
+
}
|
|
128
|
+
const filled = Math.round((clamped / 100) * width);
|
|
129
|
+
return '█'.repeat(filled) + '░'.repeat(Math.max(0, width - filled));
|
|
130
|
+
}
|
|
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
|
+
function paint(text, color, useColor) {
|
|
142
|
+
if (!useColor || !color)
|
|
143
|
+
return text;
|
|
144
|
+
return `${color}${text}${ANSI.reset}`;
|
|
145
|
+
}
|
|
146
|
+
function applyTemplate(tpl, vars) {
|
|
147
|
+
return tpl.replace(/\{(\w+)\}/g, (_, k) => (k in vars ? vars[k] : `{${k}}`));
|
|
148
|
+
}
|
|
149
|
+
function renderTier(ctx, opts) {
|
|
150
|
+
const pct = Math.round(ctx.tier.utilization);
|
|
151
|
+
const tpl = opts.template ?? SUB_PRESET_TPL[opts.format];
|
|
152
|
+
const color = ctx.label === '5h' ? fiveHourColor(pct) : weekColor(pct);
|
|
153
|
+
const text = applyTemplate(tpl, {
|
|
154
|
+
label: ctx.label,
|
|
155
|
+
percent: String(pct),
|
|
156
|
+
bar: formatBar(pct, opts.barWidth, opts.barSpec, color, opts.color),
|
|
157
|
+
expiry: formatExpiry(ctx.tier.resets_at, opts.now),
|
|
158
|
+
provider: ctx.provider,
|
|
159
|
+
});
|
|
160
|
+
if (opts.barSpec?.mode === 'tint' && opts.color && tpl.includes('{bar}'))
|
|
161
|
+
return text;
|
|
162
|
+
return paint(text, color, opts.color);
|
|
163
|
+
}
|
|
164
|
+
function currencySymbol(unit) {
|
|
165
|
+
switch (unit.toUpperCase()) {
|
|
166
|
+
case 'CNY':
|
|
167
|
+
case 'RMB':
|
|
168
|
+
return '¥';
|
|
169
|
+
case 'USD':
|
|
170
|
+
return '$';
|
|
171
|
+
default:
|
|
172
|
+
return '';
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function fmtMoney(n, unit) {
|
|
176
|
+
const sym = currencySymbol(unit);
|
|
177
|
+
return sym ? `${sym}${n.toFixed(2)}` : `${n.toFixed(2)} ${unit}`;
|
|
178
|
+
}
|
|
179
|
+
function renderSubscription(d, opts) {
|
|
180
|
+
const provider = d.planName ?? '';
|
|
181
|
+
const parts = [];
|
|
182
|
+
if (d.five_hour)
|
|
183
|
+
parts.push(renderTier({ tier: d.five_hour, label: '5h', provider }, opts));
|
|
184
|
+
if (d.seven_day)
|
|
185
|
+
parts.push(renderTier({ tier: d.seven_day, label: 'Wk', provider }, opts));
|
|
186
|
+
if (parts.length === 0)
|
|
187
|
+
return '';
|
|
188
|
+
const sep = opts.format === 'compact' && !opts.template ? ' ' : ' / ';
|
|
189
|
+
const body = parts.join(sep);
|
|
190
|
+
return opts.showProviderName && d.planName ? `${d.planName} ${body}` : body;
|
|
191
|
+
}
|
|
192
|
+
function renderBalance(d, opts) {
|
|
193
|
+
if (d.isValid === false && d.invalidMessage) {
|
|
194
|
+
const msg = paint(d.invalidMessage, ANSI.red, opts.color);
|
|
195
|
+
return opts.showProviderName && d.planName ? `${d.planName} ${msg}` : msg;
|
|
196
|
+
}
|
|
197
|
+
const amount = typeof d.total === 'number'
|
|
198
|
+
? `${fmtMoney(d.remaining, d.unit)}/${fmtMoney(d.total, d.unit)}`
|
|
199
|
+
: fmtMoney(d.remaining, d.unit);
|
|
200
|
+
const usedPct = typeof d.total === 'number' && d.total > 0
|
|
201
|
+
? ((d.total - d.remaining) / d.total) * 100
|
|
202
|
+
: 0;
|
|
203
|
+
const tpl = opts.template ?? BAL_PRESET_TPL[opts.format];
|
|
204
|
+
const color = typeof d.total === 'number' ? fiveHourColor(usedPct) : null;
|
|
205
|
+
const text = applyTemplate(tpl, {
|
|
206
|
+
label: '',
|
|
207
|
+
percent: String(Math.round(usedPct)),
|
|
208
|
+
bar: typeof d.total === 'number' ? formatBar(usedPct, opts.barWidth, opts.barSpec, color, opts.color) : '',
|
|
209
|
+
expiry: '',
|
|
210
|
+
provider: d.planName ?? '',
|
|
211
|
+
amount,
|
|
212
|
+
});
|
|
213
|
+
if (opts.barSpec?.mode === 'tint' && opts.color && tpl.includes('{bar}')) {
|
|
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;
|
|
218
|
+
}
|
|
219
|
+
function renderUsage(data, opts) {
|
|
220
|
+
if (!data)
|
|
221
|
+
return '';
|
|
222
|
+
return data.kind === 'subscription' ? renderSubscription(data, opts) : renderBalance(data, opts);
|
|
223
|
+
}
|
|
224
|
+
function isValidPreset(s) {
|
|
225
|
+
return exports.FORMAT_PRESETS.includes(s);
|
|
226
|
+
}
|
|
227
|
+
function shouldUseColor() {
|
|
228
|
+
return !process.env.NO_COLOR;
|
|
229
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.anthropicAdapter = void 0;
|
|
4
|
+
exports.parseAnthropic = parseAnthropic;
|
|
5
|
+
const token_1 = require("../token");
|
|
6
|
+
const http_1 = require("./http");
|
|
7
|
+
const ENDPOINT = 'https://api.anthropic.com/api/oauth/usage';
|
|
8
|
+
const BETA = 'oauth-2025-04-20';
|
|
9
|
+
function parseAnthropic(body) {
|
|
10
|
+
if (!body || typeof body !== 'object')
|
|
11
|
+
return null;
|
|
12
|
+
const r = body;
|
|
13
|
+
const out = { kind: 'subscription' };
|
|
14
|
+
if (typeof r.five_hour?.utilization === 'number') {
|
|
15
|
+
out.five_hour = {
|
|
16
|
+
utilization: r.five_hour.utilization,
|
|
17
|
+
resets_at: typeof r.five_hour.resets_at === 'string' ? r.five_hour.resets_at : undefined,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
if (typeof r.seven_day?.utilization === 'number') {
|
|
21
|
+
out.seven_day = {
|
|
22
|
+
utilization: r.seven_day.utilization,
|
|
23
|
+
resets_at: typeof r.seven_day.resets_at === 'string' ? r.seven_day.resets_at : undefined,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
if (!out.five_hour && !out.seven_day)
|
|
27
|
+
return null;
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
exports.anthropicAdapter = {
|
|
31
|
+
id: 'anthropic',
|
|
32
|
+
displayName: 'Anthropic',
|
|
33
|
+
matches(env) {
|
|
34
|
+
if (!env.baseUrl)
|
|
35
|
+
return true;
|
|
36
|
+
return env.baseUrl.toLowerCase().includes('anthropic.com');
|
|
37
|
+
},
|
|
38
|
+
async fetch() {
|
|
39
|
+
const tk = (0, token_1.getToken)();
|
|
40
|
+
if (!tk.token)
|
|
41
|
+
return { ok: false, error: tk.error };
|
|
42
|
+
const res = await (0, http_1.httpGetJson)(ENDPOINT, {
|
|
43
|
+
Authorization: `Bearer ${tk.token}`,
|
|
44
|
+
'anthropic-beta': BETA,
|
|
45
|
+
});
|
|
46
|
+
if (res.authFailed)
|
|
47
|
+
return { ok: false, authFailed: true, error: res.error };
|
|
48
|
+
if (!res.ok || !res.body)
|
|
49
|
+
return { ok: false, error: res.error ?? 'request failed' };
|
|
50
|
+
const data = parseAnthropic(res.body);
|
|
51
|
+
if (!data)
|
|
52
|
+
return { ok: false, error: 'invalid response shape' };
|
|
53
|
+
return { ok: true, data };
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deepseekAdapter = void 0;
|
|
4
|
+
exports.parseDeepSeek = parseDeepSeek;
|
|
5
|
+
const http_1 = require("./http");
|
|
6
|
+
const ENDPOINT = 'https://api.deepseek.com/user/balance';
|
|
7
|
+
function toNum(v) {
|
|
8
|
+
if (typeof v === 'number' && Number.isFinite(v))
|
|
9
|
+
return v;
|
|
10
|
+
if (typeof v === 'string') {
|
|
11
|
+
const n = parseFloat(v);
|
|
12
|
+
return Number.isFinite(n) ? n : null;
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function parseDeepSeek(body) {
|
|
17
|
+
if (!body || typeof body !== 'object')
|
|
18
|
+
return null;
|
|
19
|
+
const r = body;
|
|
20
|
+
const infos = r.balance_infos;
|
|
21
|
+
if (!Array.isArray(infos) || infos.length === 0)
|
|
22
|
+
return null;
|
|
23
|
+
const pick = infos.find((i) => i?.currency === 'CNY') ?? infos[0];
|
|
24
|
+
const balance = toNum(pick?.total_balance);
|
|
25
|
+
if (balance === null)
|
|
26
|
+
return null;
|
|
27
|
+
const unit = typeof pick?.currency === 'string' ? pick.currency : 'CNY';
|
|
28
|
+
return {
|
|
29
|
+
kind: 'balance',
|
|
30
|
+
remaining: balance,
|
|
31
|
+
unit,
|
|
32
|
+
planName: 'DeepSeek',
|
|
33
|
+
isValid: r.is_available !== false,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
exports.deepseekAdapter = {
|
|
37
|
+
id: 'deepseek',
|
|
38
|
+
displayName: 'DeepSeek',
|
|
39
|
+
matches(env) {
|
|
40
|
+
return !!env.baseUrl && env.baseUrl.toLowerCase().includes('api.deepseek.com');
|
|
41
|
+
},
|
|
42
|
+
async fetch(env) {
|
|
43
|
+
if (!env.authToken)
|
|
44
|
+
return { ok: false, error: 'ANTHROPIC_AUTH_TOKEN not set' };
|
|
45
|
+
const res = await (0, http_1.httpGetJson)(ENDPOINT, { Authorization: `Bearer ${env.authToken}` });
|
|
46
|
+
if (res.authFailed)
|
|
47
|
+
return { ok: false, authFailed: true, error: res.error };
|
|
48
|
+
if (!res.ok || !res.body)
|
|
49
|
+
return { ok: false, error: res.error ?? 'request failed' };
|
|
50
|
+
const data = parseDeepSeek(res.body);
|
|
51
|
+
if (!data)
|
|
52
|
+
return { ok: false, error: 'invalid response shape' };
|
|
53
|
+
return { ok: true, data };
|
|
54
|
+
},
|
|
55
|
+
};
|