cc-api-statusline 0.2.2 → 1.0.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/README.md +126 -428
- package/dist/cc-api-statusline.js +525 -567
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
4
4
|
// package.json
|
|
5
5
|
var package_default = {
|
|
6
6
|
name: "cc-api-statusline",
|
|
7
|
-
version: "0.
|
|
7
|
+
version: "1.0.0",
|
|
8
8
|
description: "Claude Code statusline tool that polls API usage from third-party proxy backends",
|
|
9
9
|
type: "module",
|
|
10
10
|
bin: {
|
|
@@ -138,13 +138,13 @@ function showVersion() {
|
|
|
138
138
|
console.log(`cc-api-statusline v${package_default.version}`);
|
|
139
139
|
}
|
|
140
140
|
// src/services/settings.ts
|
|
141
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
141
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
142
142
|
import { execSync } from "child_process";
|
|
143
143
|
|
|
144
144
|
// src/services/env.ts
|
|
145
|
-
import { readFileSync
|
|
146
|
-
import { join } from "path";
|
|
147
|
-
import { homedir } from "os";
|
|
145
|
+
import { readFileSync } from "fs";
|
|
146
|
+
import { join as join4 } from "path";
|
|
147
|
+
import { homedir as homedir2 } from "os";
|
|
148
148
|
|
|
149
149
|
// src/services/hash.ts
|
|
150
150
|
function sha256(input) {
|
|
@@ -161,21 +161,193 @@ function shortHash(input, length = 12) {
|
|
|
161
161
|
return fullHash.slice(0, length);
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
// src/services/logger.ts
|
|
165
|
+
import { appendFileSync } from "fs";
|
|
166
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
167
|
+
|
|
168
|
+
// src/services/ensure-dir.ts
|
|
169
|
+
import { mkdirSync } from "fs";
|
|
170
|
+
function ensureDir(dirPath) {
|
|
171
|
+
mkdirSync(dirPath, { recursive: true, mode: 448 });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/services/log-rotator.ts
|
|
175
|
+
import { statSync, renameSync, readdirSync, unlinkSync } from "fs";
|
|
176
|
+
import { spawn } from "child_process";
|
|
177
|
+
import { dirname, join } from "path";
|
|
178
|
+
|
|
179
|
+
// src/core/constants.ts
|
|
180
|
+
var DEFAULT_FETCH_TIMEOUT_MS = 5000;
|
|
181
|
+
var EXIT_BUFFER_MS = 50;
|
|
182
|
+
var STALENESS_THRESHOLD_MINUTES = 5;
|
|
183
|
+
var VERY_STALE_THRESHOLD_MINUTES = 30;
|
|
184
|
+
var GC_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
185
|
+
var GC_MAX_CACHE_FILES = 20;
|
|
186
|
+
var GC_ORPHAN_TMP_AGE_MS = 60 * 60 * 1000;
|
|
187
|
+
var LOG_ROTATION_PROBABILITY = 0.05;
|
|
188
|
+
var LOG_MAX_SIZE_BYTES = 512 * 1024;
|
|
189
|
+
var LOG_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
190
|
+
var LOG_RETENTION_MS = 3 * 24 * 60 * 60 * 1000;
|
|
191
|
+
|
|
192
|
+
// src/services/log-rotator.ts
|
|
193
|
+
var ARCHIVE_LOG_RE = /^debug\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}\.log$/;
|
|
194
|
+
var ARCHIVE_GZ_RE = /^debug\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}\.log\.gz$/;
|
|
195
|
+
function archiveName(logPath, now = new Date) {
|
|
196
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
197
|
+
const y = now.getFullYear();
|
|
198
|
+
const mo = pad(now.getMonth() + 1);
|
|
199
|
+
const d = pad(now.getDate());
|
|
200
|
+
const h = pad(now.getHours());
|
|
201
|
+
const min = pad(now.getMinutes());
|
|
202
|
+
return join(dirname(logPath), `debug.${y}-${mo}-${d}T${h}-${min}.log`);
|
|
203
|
+
}
|
|
204
|
+
function spawnGzip(filePath) {
|
|
205
|
+
try {
|
|
206
|
+
const child = spawn("gzip", ["-f", filePath], {
|
|
207
|
+
detached: true,
|
|
208
|
+
stdio: "ignore"
|
|
209
|
+
});
|
|
210
|
+
child.unref();
|
|
211
|
+
} catch {}
|
|
212
|
+
}
|
|
213
|
+
function runCleanup(logDir, excludePath) {
|
|
214
|
+
try {
|
|
215
|
+
const files = readdirSync(logDir);
|
|
216
|
+
const now = Date.now();
|
|
217
|
+
for (const name of files) {
|
|
218
|
+
const filePath = join(logDir, name);
|
|
219
|
+
if (filePath === excludePath)
|
|
220
|
+
continue;
|
|
221
|
+
if (ARCHIVE_LOG_RE.test(name)) {
|
|
222
|
+
const s = statSync(filePath, { throwIfNoEntry: false });
|
|
223
|
+
if (s && now - s.mtimeMs >= LOG_MAX_AGE_MS) {
|
|
224
|
+
spawnGzip(filePath);
|
|
225
|
+
}
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (ARCHIVE_GZ_RE.test(name)) {
|
|
229
|
+
const s = statSync(filePath, { throwIfNoEntry: false });
|
|
230
|
+
if (s && now - s.mtimeMs >= LOG_RETENTION_MS) {
|
|
231
|
+
try {
|
|
232
|
+
unlinkSync(filePath);
|
|
233
|
+
} catch {}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} catch {}
|
|
238
|
+
}
|
|
239
|
+
function maybeRotateLogs(logPath) {
|
|
240
|
+
if (Math.random() > LOG_ROTATION_PROBABILITY)
|
|
241
|
+
return;
|
|
242
|
+
const logDir = dirname(logPath);
|
|
243
|
+
const stat = statSync(logPath, { throwIfNoEntry: false });
|
|
244
|
+
let rotatedArchive = null;
|
|
245
|
+
if (stat) {
|
|
246
|
+
const age = Date.now() - stat.mtimeMs;
|
|
247
|
+
const archive = archiveName(logPath);
|
|
248
|
+
try {
|
|
249
|
+
if (age >= LOG_MAX_AGE_MS) {
|
|
250
|
+
renameSync(logPath, archive);
|
|
251
|
+
spawnGzip(archive);
|
|
252
|
+
rotatedArchive = archive;
|
|
253
|
+
} else if (stat.size >= LOG_MAX_SIZE_BYTES) {
|
|
254
|
+
renameSync(logPath, archive);
|
|
255
|
+
rotatedArchive = archive;
|
|
256
|
+
}
|
|
257
|
+
} catch {}
|
|
258
|
+
}
|
|
259
|
+
runCleanup(logDir, rotatedArchive);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/services/paths.ts
|
|
263
|
+
import { homedir } from "node:os";
|
|
264
|
+
import { join as join2 } from "node:path";
|
|
265
|
+
function getConfigDir() {
|
|
266
|
+
return join2(homedir(), ".claude", "cc-api-statusline");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/services/logger.ts
|
|
270
|
+
class Logger {
|
|
271
|
+
enabled;
|
|
272
|
+
logPath;
|
|
273
|
+
constructor() {
|
|
274
|
+
this.enabled = !!(process.env["DEBUG"] || process.env["CC_STATUSLINE_DEBUG"]);
|
|
275
|
+
const logDir = process.env["CC_API_STATUSLINE_LOG_DIR"] || getConfigDir();
|
|
276
|
+
this.logPath = join3(logDir, "debug.log");
|
|
277
|
+
if (this.enabled) {
|
|
278
|
+
this.ensureLogDir();
|
|
279
|
+
maybeRotateLogs(this.logPath);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
ensureLogDir() {
|
|
283
|
+
try {
|
|
284
|
+
const dir = dirname2(this.logPath);
|
|
285
|
+
ensureDir(dir);
|
|
286
|
+
} catch {
|
|
287
|
+
this.enabled = false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
formatLocalTimestamp() {
|
|
291
|
+
const d = new Date;
|
|
292
|
+
const pad = (n, len = 2) => n.toString().padStart(len, "0");
|
|
293
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
|
|
294
|
+
}
|
|
295
|
+
format(level, message, data) {
|
|
296
|
+
const timestamp = this.formatLocalTimestamp();
|
|
297
|
+
const dataStr = data ? ` ${JSON.stringify(data)}` : "";
|
|
298
|
+
return `[${timestamp}] [${level.toUpperCase()}] ${message}${dataStr}
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
write(level, message, data) {
|
|
302
|
+
if (!this.enabled) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
const entry = this.format(level, message, data);
|
|
307
|
+
appendFileSync(this.logPath, entry, { encoding: "utf-8" });
|
|
308
|
+
} catch {}
|
|
309
|
+
}
|
|
310
|
+
debug(message, data) {
|
|
311
|
+
this.write("debug", message, data);
|
|
312
|
+
}
|
|
313
|
+
info(message, data) {
|
|
314
|
+
this.write("info", message, data);
|
|
315
|
+
}
|
|
316
|
+
warn(message, data) {
|
|
317
|
+
this.write("warn", message, data);
|
|
318
|
+
}
|
|
319
|
+
error(message, data) {
|
|
320
|
+
this.write("error", message, data);
|
|
321
|
+
}
|
|
322
|
+
isEnabled() {
|
|
323
|
+
return this.enabled;
|
|
324
|
+
}
|
|
325
|
+
getLogPath() {
|
|
326
|
+
return this.logPath;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
var logger = new Logger;
|
|
330
|
+
|
|
164
331
|
// src/services/env.ts
|
|
165
332
|
function getSettingsJsonPath() {
|
|
166
333
|
const configDir = process.env["CLAUDE_CONFIG_DIR"];
|
|
167
334
|
if (configDir) {
|
|
168
|
-
return
|
|
335
|
+
return join4(configDir, "settings.json");
|
|
169
336
|
}
|
|
170
|
-
return
|
|
337
|
+
return join4(homedir2(), ".claude", "settings.json");
|
|
171
338
|
}
|
|
172
339
|
function readSettingsJsonEnv() {
|
|
173
340
|
const settingsPath = getSettingsJsonPath();
|
|
174
|
-
|
|
175
|
-
|
|
341
|
+
let content;
|
|
342
|
+
try {
|
|
343
|
+
content = readFileSync(settingsPath, "utf-8");
|
|
344
|
+
} catch (err) {
|
|
345
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
346
|
+
return {};
|
|
347
|
+
}
|
|
348
|
+
throw err;
|
|
176
349
|
}
|
|
177
350
|
try {
|
|
178
|
-
const content = readFileSync(settingsPath, "utf-8");
|
|
179
351
|
const settings = JSON.parse(content);
|
|
180
352
|
if (settings["env"] && typeof settings["env"] === "object") {
|
|
181
353
|
const env = settings["env"];
|
|
@@ -190,8 +362,8 @@ function readSettingsJsonEnv() {
|
|
|
190
362
|
return result;
|
|
191
363
|
}
|
|
192
364
|
return {};
|
|
193
|
-
} catch (
|
|
194
|
-
|
|
365
|
+
} catch (err) {
|
|
366
|
+
logger.warn(`Could not read settings.json: ${err}`);
|
|
195
367
|
return {};
|
|
196
368
|
}
|
|
197
369
|
}
|
|
@@ -231,25 +403,15 @@ function validateRequiredEnv(env) {
|
|
|
231
403
|
}
|
|
232
404
|
|
|
233
405
|
// src/services/atomic-write.ts
|
|
234
|
-
import { writeFileSync, renameSync, unlinkSync
|
|
235
|
-
import { dirname } from "path";
|
|
236
|
-
|
|
237
|
-
// src/services/ensure-dir.ts
|
|
238
|
-
import { mkdirSync, existsSync as existsSync2 } from "fs";
|
|
239
|
-
function ensureDir(dirPath) {
|
|
240
|
-
if (!existsSync2(dirPath)) {
|
|
241
|
-
mkdirSync(dirPath, { recursive: true, mode: 448 });
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// src/services/atomic-write.ts
|
|
406
|
+
import { writeFileSync, renameSync as renameSync2, unlinkSync as unlinkSync2, existsSync, chmodSync } from "fs";
|
|
407
|
+
import { dirname as dirname3 } from "path";
|
|
246
408
|
function atomicWriteFile(filePath, content, opts = {}) {
|
|
247
409
|
const { mode = 384, ensureParentDir: ensureParent = false, appendNewline = false } = opts;
|
|
248
410
|
const nonce = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
249
411
|
const tmpPath = `${filePath}.${nonce}.tmp`;
|
|
250
412
|
try {
|
|
251
413
|
if (ensureParent) {
|
|
252
|
-
const dir =
|
|
414
|
+
const dir = dirname3(filePath);
|
|
253
415
|
ensureDir(dir);
|
|
254
416
|
}
|
|
255
417
|
const finalContent = appendNewline ? `${content}
|
|
@@ -258,11 +420,11 @@ function atomicWriteFile(filePath, content, opts = {}) {
|
|
|
258
420
|
try {
|
|
259
421
|
chmodSync(tmpPath, mode);
|
|
260
422
|
} catch {}
|
|
261
|
-
|
|
423
|
+
renameSync2(tmpPath, filePath);
|
|
262
424
|
} catch (error) {
|
|
263
425
|
try {
|
|
264
|
-
if (
|
|
265
|
-
|
|
426
|
+
if (existsSync(tmpPath)) {
|
|
427
|
+
unlinkSync2(tmpPath);
|
|
266
428
|
}
|
|
267
429
|
} catch {}
|
|
268
430
|
throw new Error(`Failed to write file atomically: ${error}`);
|
|
@@ -272,14 +434,14 @@ function atomicWriteFile(filePath, content, opts = {}) {
|
|
|
272
434
|
// src/services/settings.ts
|
|
273
435
|
function loadClaudeSettings() {
|
|
274
436
|
const path = getSettingsJsonPath();
|
|
275
|
-
if (!
|
|
437
|
+
if (!existsSync2(path)) {
|
|
276
438
|
return {};
|
|
277
439
|
}
|
|
278
440
|
try {
|
|
279
441
|
const content = readFileSync2(path, "utf-8");
|
|
280
442
|
return JSON.parse(content);
|
|
281
443
|
} catch (error) {
|
|
282
|
-
|
|
444
|
+
logger.warn(`Failed to read settings from ${path}: ${error}`);
|
|
283
445
|
return {};
|
|
284
446
|
}
|
|
285
447
|
}
|
|
@@ -326,9 +488,8 @@ function uninstallStatusLine() {
|
|
|
326
488
|
}
|
|
327
489
|
|
|
328
490
|
// src/services/config-defaults.ts
|
|
329
|
-
import { join as
|
|
330
|
-
import {
|
|
331
|
-
import { existsSync as existsSync7 } from "fs";
|
|
491
|
+
import { join as join8 } from "path";
|
|
492
|
+
import { existsSync as existsSync4 } from "fs";
|
|
332
493
|
|
|
333
494
|
// src/types/normalized-usage.ts
|
|
334
495
|
function createEmptyNormalizedUsage(provider, billingMode, planName) {
|
|
@@ -361,6 +522,14 @@ function computeSoonestReset(usage) {
|
|
|
361
522
|
return sorted[0] ?? null;
|
|
362
523
|
}
|
|
363
524
|
// src/types/config.ts
|
|
525
|
+
var DEFAULT_DIVIDER_CONFIG = { text: "|", margin: 1, color: "#555753" };
|
|
526
|
+
var DEFAULT_TIER_THRESHOLDS = [37.5, 62.5, 75, 87.5, 100];
|
|
527
|
+
function buildTiers(colors, thresholds = DEFAULT_TIER_THRESHOLDS) {
|
|
528
|
+
if (colors.length !== thresholds.length) {
|
|
529
|
+
throw new Error(`buildTiers: colors.length (${colors.length}) must equal thresholds.length (${thresholds.length})`);
|
|
530
|
+
}
|
|
531
|
+
return colors.map((color, i) => ({ color, maxPercent: thresholds[i] }));
|
|
532
|
+
}
|
|
364
533
|
var DEFAULT_CONFIG = {
|
|
365
534
|
display: {
|
|
366
535
|
layout: "standard",
|
|
@@ -368,7 +537,7 @@ var DEFAULT_CONFIG = {
|
|
|
368
537
|
progressStyle: "icon",
|
|
369
538
|
barSize: "medium",
|
|
370
539
|
barStyle: "block",
|
|
371
|
-
|
|
540
|
+
divider: DEFAULT_DIVIDER_CONFIG,
|
|
372
541
|
maxWidth: 100,
|
|
373
542
|
clockFormat: "24h",
|
|
374
543
|
colorMode: "auto",
|
|
@@ -381,73 +550,16 @@ var DEFAULT_CONFIG = {
|
|
|
381
550
|
balance: true,
|
|
382
551
|
tokens: false,
|
|
383
552
|
rateLimit: false,
|
|
384
|
-
plan: false
|
|
385
|
-
divider: true
|
|
553
|
+
plan: false
|
|
386
554
|
},
|
|
387
555
|
colors: {
|
|
388
|
-
auto: {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
]
|
|
396
|
-
},
|
|
397
|
-
vibrant: {
|
|
398
|
-
tiers: [
|
|
399
|
-
{ color: "#00D9FF", maxPercent: 37.5 },
|
|
400
|
-
{ color: "#4ADE80", maxPercent: 62.5 },
|
|
401
|
-
{ color: "#FDE047", maxPercent: 75 },
|
|
402
|
-
{ color: "#FB923C", maxPercent: 85 },
|
|
403
|
-
{ color: "#F87171", maxPercent: 92.5 }
|
|
404
|
-
]
|
|
405
|
-
},
|
|
406
|
-
pastel: {
|
|
407
|
-
tiers: [
|
|
408
|
-
{ color: "pastel-cool", maxPercent: 37.5 },
|
|
409
|
-
{ color: "pastel-comfortable", maxPercent: 62.5 },
|
|
410
|
-
{ color: "pastel-medium", maxPercent: 75 },
|
|
411
|
-
{ color: "pastel-warm", maxPercent: 85 },
|
|
412
|
-
{ color: "pastel-hot", maxPercent: 92.5 }
|
|
413
|
-
]
|
|
414
|
-
},
|
|
415
|
-
bright: {
|
|
416
|
-
tiers: [
|
|
417
|
-
{ color: "bright-cool", maxPercent: 37.5 },
|
|
418
|
-
{ color: "bright-comfortable", maxPercent: 62.5 },
|
|
419
|
-
{ color: "bright-medium", maxPercent: 75 },
|
|
420
|
-
{ color: "bright-warm", maxPercent: 85 },
|
|
421
|
-
{ color: "bright-hot", maxPercent: 92.5 }
|
|
422
|
-
]
|
|
423
|
-
},
|
|
424
|
-
ocean: {
|
|
425
|
-
tiers: [
|
|
426
|
-
{ color: "ocean-cool", maxPercent: 37.5 },
|
|
427
|
-
{ color: "ocean-comfortable", maxPercent: 62.5 },
|
|
428
|
-
{ color: "ocean-medium", maxPercent: 75 },
|
|
429
|
-
{ color: "ocean-warm", maxPercent: 85 },
|
|
430
|
-
{ color: "ocean-hot", maxPercent: 92.5 }
|
|
431
|
-
]
|
|
432
|
-
},
|
|
433
|
-
neutral: {
|
|
434
|
-
tiers: [
|
|
435
|
-
{ color: "neutral-cool", maxPercent: 37.5 },
|
|
436
|
-
{ color: "neutral-comfortable", maxPercent: 62.5 },
|
|
437
|
-
{ color: "neutral-warm", maxPercent: 75 },
|
|
438
|
-
{ color: "neutral-hot", maxPercent: 85 },
|
|
439
|
-
{ color: "neutral-critical", maxPercent: 92.5 }
|
|
440
|
-
]
|
|
441
|
-
},
|
|
442
|
-
chill: {
|
|
443
|
-
tiers: [
|
|
444
|
-
{ color: "cyan", maxPercent: 37.5 },
|
|
445
|
-
{ color: "cyan", maxPercent: 62.5 },
|
|
446
|
-
{ color: "blue", maxPercent: 75 },
|
|
447
|
-
{ color: "blue", maxPercent: 87.5 },
|
|
448
|
-
{ color: "magenta", maxPercent: 92.5 }
|
|
449
|
-
]
|
|
450
|
-
}
|
|
556
|
+
auto: { tiers: buildTiers(["cool", "comfortable", "warm", "hot", "critical"]) },
|
|
557
|
+
vibrant: { tiers: buildTiers(["#00D9FF", "#4ADE80", "#FDE047", "#FB923C", "#F87171"]) },
|
|
558
|
+
pastel: { tiers: buildTiers(["pastel-cool", "pastel-comfortable", "pastel-medium", "pastel-warm", "pastel-hot"]) },
|
|
559
|
+
bright: { tiers: buildTiers(["bright-cool", "bright-comfortable", "bright-medium", "bright-warm", "bright-hot"]) },
|
|
560
|
+
ocean: { tiers: buildTiers(["ocean-cool", "ocean-comfortable", "ocean-medium", "ocean-warm", "ocean-hot"]) },
|
|
561
|
+
neutral: { tiers: buildTiers(["neutral-cool", "neutral-comfortable", "neutral-warm", "neutral-hot", "neutral-critical"]) },
|
|
562
|
+
chill: { tiers: buildTiers(["cyan", "cyan", "blue", "blue", "magenta"]) }
|
|
451
563
|
},
|
|
452
564
|
pollIntervalSeconds: 30,
|
|
453
565
|
pipedRequestTimeoutMs: 800
|
|
@@ -478,226 +590,68 @@ var COMPONENT_SHORT_LABELS = {
|
|
|
478
590
|
rateLimit: "R",
|
|
479
591
|
plan: "P"
|
|
480
592
|
};
|
|
481
|
-
var COMPONENT_FULL_LABELS = {
|
|
482
|
-
daily: "Daily",
|
|
483
|
-
weekly: "Weekly",
|
|
484
|
-
monthly: "Monthly",
|
|
485
|
-
balance: "Balance",
|
|
486
|
-
tokens: "Tokens",
|
|
487
|
-
rateLimit: "Rate",
|
|
488
|
-
plan: "Plan"
|
|
489
|
-
};
|
|
490
|
-
var COMPONENT_EMOJI_LABELS = {
|
|
491
|
-
daily: "\uD83D\uDCC5",
|
|
492
|
-
weekly: "\uD83D\uDCC6",
|
|
493
|
-
monthly: "\uD83D\uDDD3️",
|
|
494
|
-
balance: "\uD83D\uDCB0",
|
|
495
|
-
tokens: "\uD83D\uDD22",
|
|
496
|
-
rateLimit: "⚡",
|
|
497
|
-
plan: "\uD83D\uDCCB"
|
|
498
|
-
};
|
|
499
|
-
var COMPONENT_NERD_LABELS = {
|
|
500
|
-
daily: "",
|
|
501
|
-
weekly: "",
|
|
502
|
-
monthly: "",
|
|
503
|
-
balance: "",
|
|
504
|
-
tokens: "",
|
|
505
|
-
rateLimit: "",
|
|
506
|
-
plan: ""
|
|
507
|
-
};
|
|
508
|
-
var DEFAULT_COMPONENT_ORDER = [
|
|
509
|
-
"daily",
|
|
510
|
-
"weekly",
|
|
511
|
-
"monthly",
|
|
512
|
-
"balance",
|
|
513
|
-
"tokens",
|
|
514
|
-
"rateLimit",
|
|
515
|
-
"plan"
|
|
516
|
-
];
|
|
517
|
-
// src/types/cache.ts
|
|
518
|
-
var CACHE_VERSION = 2;
|
|
519
|
-
function isCacheEntry(value) {
|
|
520
|
-
if (typeof value !== "object" || value === null)
|
|
521
|
-
return false;
|
|
522
|
-
const c = value;
|
|
523
|
-
return typeof c["version"] === "number" && typeof c["provider"] === "string" && typeof c["baseUrl"] === "string" && typeof c["tokenHash"] === "string" && typeof c["configHash"] === "string" && typeof c["endpointConfigHash"] === "string" && typeof c["data"] === "object" && c["data"] !== null && typeof c["renderedLine"] === "string" && typeof c["fetchedAt"] === "string" && typeof c["ttlSeconds"] === "number" && (c["errorState"] === null || typeof c["errorState"] === "object");
|
|
524
|
-
}
|
|
525
|
-
var PROVIDER_DETECTION_TTL_SECONDS = 86400;
|
|
526
|
-
function isProviderDetectionCacheEntry(value) {
|
|
527
|
-
if (typeof value !== "object" || value === null)
|
|
528
|
-
return false;
|
|
529
|
-
const c = value;
|
|
530
|
-
return typeof c["baseUrl"] === "string" && typeof c["provider"] === "string" && (c["detectedVia"] === "health-probe" || c["detectedVia"] === "url-pattern" || c["detectedVia"] === "override") && typeof c["detectedAt"] === "string" && typeof c["ttlSeconds"] === "number";
|
|
531
|
-
}
|
|
532
|
-
// src/services/endpoint-config.ts
|
|
533
|
-
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
534
|
-
import { join as join4 } from "path";
|
|
535
|
-
import { homedir as homedir3 } from "os";
|
|
536
|
-
|
|
537
|
-
// src/services/logger.ts
|
|
538
|
-
import { appendFileSync } from "fs";
|
|
539
|
-
import { join as join3, dirname as dirname3 } from "path";
|
|
540
|
-
import { homedir as homedir2 } from "os";
|
|
541
|
-
|
|
542
|
-
// src/services/log-rotator.ts
|
|
543
|
-
import { statSync, renameSync as renameSync2, readdirSync, unlinkSync as unlinkSync2 } from "fs";
|
|
544
|
-
import { spawn } from "child_process";
|
|
545
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
546
|
-
|
|
547
|
-
// src/core/constants.ts
|
|
548
|
-
var DEFAULT_FETCH_TIMEOUT_MS = 5000;
|
|
549
|
-
var EXIT_BUFFER_MS = 50;
|
|
550
|
-
var STALENESS_THRESHOLD_MINUTES = 5;
|
|
551
|
-
var VERY_STALE_THRESHOLD_MINUTES = 30;
|
|
552
|
-
var GC_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
553
|
-
var GC_MAX_CACHE_FILES = 20;
|
|
554
|
-
var GC_ORPHAN_TMP_AGE_MS = 60 * 60 * 1000;
|
|
555
|
-
var LOG_ROTATION_PROBABILITY = 0.05;
|
|
556
|
-
var LOG_MAX_SIZE_BYTES = 512 * 1024;
|
|
557
|
-
var LOG_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
558
|
-
var LOG_RETENTION_MS = 3 * 24 * 60 * 60 * 1000;
|
|
559
|
-
|
|
560
|
-
// src/services/log-rotator.ts
|
|
561
|
-
var ARCHIVE_LOG_RE = /^debug\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}\.log$/;
|
|
562
|
-
var ARCHIVE_GZ_RE = /^debug\.\d{4}-\d{2}-\d{2}T\d{2}-\d{2}\.log\.gz$/;
|
|
563
|
-
function archiveName(logPath, now = new Date) {
|
|
564
|
-
const pad = (n) => n.toString().padStart(2, "0");
|
|
565
|
-
const y = now.getFullYear();
|
|
566
|
-
const mo = pad(now.getMonth() + 1);
|
|
567
|
-
const d = pad(now.getDate());
|
|
568
|
-
const h = pad(now.getHours());
|
|
569
|
-
const min = pad(now.getMinutes());
|
|
570
|
-
return join2(dirname2(logPath), `debug.${y}-${mo}-${d}T${h}-${min}.log`);
|
|
571
|
-
}
|
|
572
|
-
function spawnGzip(filePath) {
|
|
573
|
-
try {
|
|
574
|
-
const child = spawn("gzip", ["-f", filePath], {
|
|
575
|
-
detached: true,
|
|
576
|
-
stdio: "ignore"
|
|
577
|
-
});
|
|
578
|
-
child.unref();
|
|
579
|
-
} catch {}
|
|
580
|
-
}
|
|
581
|
-
function runCleanup(logDir, excludePath) {
|
|
582
|
-
try {
|
|
583
|
-
const files = readdirSync(logDir);
|
|
584
|
-
const now = Date.now();
|
|
585
|
-
for (const name of files) {
|
|
586
|
-
const filePath = join2(logDir, name);
|
|
587
|
-
if (filePath === excludePath)
|
|
588
|
-
continue;
|
|
589
|
-
if (ARCHIVE_LOG_RE.test(name)) {
|
|
590
|
-
const s = statSync(filePath, { throwIfNoEntry: false });
|
|
591
|
-
if (s && now - s.mtimeMs >= LOG_MAX_AGE_MS) {
|
|
592
|
-
spawnGzip(filePath);
|
|
593
|
-
}
|
|
594
|
-
continue;
|
|
595
|
-
}
|
|
596
|
-
if (ARCHIVE_GZ_RE.test(name)) {
|
|
597
|
-
const s = statSync(filePath, { throwIfNoEntry: false });
|
|
598
|
-
if (s && now - s.mtimeMs >= LOG_RETENTION_MS) {
|
|
599
|
-
try {
|
|
600
|
-
unlinkSync2(filePath);
|
|
601
|
-
} catch {}
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
} catch {}
|
|
606
|
-
}
|
|
607
|
-
function maybeRotateLogs(logPath) {
|
|
608
|
-
if (Math.random() > LOG_ROTATION_PROBABILITY)
|
|
609
|
-
return;
|
|
610
|
-
const logDir = dirname2(logPath);
|
|
611
|
-
const stat = statSync(logPath, { throwIfNoEntry: false });
|
|
612
|
-
let rotatedArchive = null;
|
|
613
|
-
if (stat) {
|
|
614
|
-
const age = Date.now() - stat.mtimeMs;
|
|
615
|
-
const archive = archiveName(logPath);
|
|
616
|
-
try {
|
|
617
|
-
if (age >= LOG_MAX_AGE_MS) {
|
|
618
|
-
renameSync2(logPath, archive);
|
|
619
|
-
spawnGzip(archive);
|
|
620
|
-
rotatedArchive = archive;
|
|
621
|
-
} else if (stat.size >= LOG_MAX_SIZE_BYTES) {
|
|
622
|
-
renameSync2(logPath, archive);
|
|
623
|
-
rotatedArchive = archive;
|
|
624
|
-
}
|
|
625
|
-
} catch {}
|
|
626
|
-
}
|
|
627
|
-
runCleanup(logDir, rotatedArchive);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// src/services/logger.ts
|
|
631
|
-
class Logger {
|
|
632
|
-
enabled;
|
|
633
|
-
logPath;
|
|
634
|
-
constructor() {
|
|
635
|
-
this.enabled = !!(process.env["DEBUG"] || process.env["CC_STATUSLINE_DEBUG"]);
|
|
636
|
-
const logDir = process.env["CC_API_STATUSLINE_LOG_DIR"] || join3(homedir2(), ".claude", "cc-api-statusline");
|
|
637
|
-
this.logPath = join3(logDir, "debug.log");
|
|
638
|
-
if (this.enabled) {
|
|
639
|
-
this.ensureLogDir();
|
|
640
|
-
maybeRotateLogs(this.logPath);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
ensureLogDir() {
|
|
644
|
-
try {
|
|
645
|
-
const dir = dirname3(this.logPath);
|
|
646
|
-
ensureDir(dir);
|
|
647
|
-
} catch {
|
|
648
|
-
this.enabled = false;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
formatLocalTimestamp() {
|
|
652
|
-
const d = new Date;
|
|
653
|
-
const pad = (n, len = 2) => n.toString().padStart(len, "0");
|
|
654
|
-
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
|
|
655
|
-
}
|
|
656
|
-
format(level, message, data) {
|
|
657
|
-
const timestamp = this.formatLocalTimestamp();
|
|
658
|
-
const dataStr = data ? ` ${JSON.stringify(data)}` : "";
|
|
659
|
-
return `[${timestamp}] [${level.toUpperCase()}] ${message}${dataStr}
|
|
660
|
-
`;
|
|
661
|
-
}
|
|
662
|
-
write(level, message, data) {
|
|
663
|
-
if (!this.enabled) {
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
try {
|
|
667
|
-
const entry = this.format(level, message, data);
|
|
668
|
-
appendFileSync(this.logPath, entry, { encoding: "utf-8" });
|
|
669
|
-
} catch {}
|
|
670
|
-
}
|
|
671
|
-
debug(message, data) {
|
|
672
|
-
this.write("debug", message, data);
|
|
673
|
-
}
|
|
674
|
-
info(message, data) {
|
|
675
|
-
this.write("info", message, data);
|
|
676
|
-
}
|
|
677
|
-
warn(message, data) {
|
|
678
|
-
this.write("warn", message, data);
|
|
679
|
-
}
|
|
680
|
-
error(message, data) {
|
|
681
|
-
this.write("error", message, data);
|
|
682
|
-
}
|
|
683
|
-
isEnabled() {
|
|
684
|
-
return this.enabled;
|
|
685
|
-
}
|
|
686
|
-
getLogPath() {
|
|
687
|
-
return this.logPath;
|
|
688
|
-
}
|
|
593
|
+
var COMPONENT_FULL_LABELS = {
|
|
594
|
+
daily: "Daily",
|
|
595
|
+
weekly: "Weekly",
|
|
596
|
+
monthly: "Monthly",
|
|
597
|
+
balance: "Balance",
|
|
598
|
+
tokens: "Tokens",
|
|
599
|
+
rateLimit: "Rate",
|
|
600
|
+
plan: "Plan"
|
|
601
|
+
};
|
|
602
|
+
var COMPONENT_EMOJI_LABELS = {
|
|
603
|
+
daily: "\uD83D\uDCC5",
|
|
604
|
+
weekly: "\uD83D\uDCC6",
|
|
605
|
+
monthly: "\uD83D\uDDD3️",
|
|
606
|
+
balance: "\uD83D\uDCB0",
|
|
607
|
+
tokens: "\uD83D\uDD22",
|
|
608
|
+
rateLimit: "⚡",
|
|
609
|
+
plan: "\uD83D\uDCCB"
|
|
610
|
+
};
|
|
611
|
+
var COMPONENT_NERD_LABELS = {
|
|
612
|
+
daily: "",
|
|
613
|
+
weekly: "",
|
|
614
|
+
monthly: "",
|
|
615
|
+
balance: "",
|
|
616
|
+
tokens: "",
|
|
617
|
+
rateLimit: "",
|
|
618
|
+
plan: ""
|
|
619
|
+
};
|
|
620
|
+
var DEFAULT_COMPONENT_ORDER = [
|
|
621
|
+
"daily",
|
|
622
|
+
"weekly",
|
|
623
|
+
"monthly",
|
|
624
|
+
"balance",
|
|
625
|
+
"tokens",
|
|
626
|
+
"rateLimit",
|
|
627
|
+
"plan"
|
|
628
|
+
];
|
|
629
|
+
// src/types/cache.ts
|
|
630
|
+
var CACHE_VERSION = 2;
|
|
631
|
+
function isCacheEntry(value) {
|
|
632
|
+
if (typeof value !== "object" || value === null)
|
|
633
|
+
return false;
|
|
634
|
+
const c = value;
|
|
635
|
+
return typeof c["version"] === "number" && typeof c["provider"] === "string" && typeof c["baseUrl"] === "string" && typeof c["tokenHash"] === "string" && typeof c["configHash"] === "string" && typeof c["endpointConfigHash"] === "string" && typeof c["data"] === "object" && c["data"] !== null && typeof c["renderedLine"] === "string" && typeof c["fetchedAt"] === "string" && typeof c["ttlSeconds"] === "number" && (c["errorState"] === null || typeof c["errorState"] === "object");
|
|
636
|
+
}
|
|
637
|
+
var PROVIDER_DETECTION_TTL_SECONDS = 86400;
|
|
638
|
+
function isProviderDetectionCacheEntry(value) {
|
|
639
|
+
if (typeof value !== "object" || value === null)
|
|
640
|
+
return false;
|
|
641
|
+
const c = value;
|
|
642
|
+
return typeof c["baseUrl"] === "string" && typeof c["provider"] === "string" && (c["detectedVia"] === "health-probe" || c["detectedVia"] === "url-pattern" || c["detectedVia"] === "override") && typeof c["detectedAt"] === "string" && typeof c["ttlSeconds"] === "number";
|
|
689
643
|
}
|
|
690
|
-
var logger = new Logger;
|
|
691
|
-
|
|
692
644
|
// src/services/endpoint-config.ts
|
|
645
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync3 } from "fs";
|
|
646
|
+
import { join as join5 } from "path";
|
|
693
647
|
function getEndpointConfigDir(customRoot) {
|
|
694
648
|
const envRoot = process.env["CC_API_STATUSLINE_CONFIG_DIR"];
|
|
695
|
-
const root = customRoot || envRoot ||
|
|
696
|
-
return
|
|
649
|
+
const root = customRoot || envRoot || getConfigDir();
|
|
650
|
+
return join5(root, "api-config");
|
|
697
651
|
}
|
|
698
652
|
function loadEndpointConfigs(customDir) {
|
|
699
653
|
const configDir = getEndpointConfigDir(customDir);
|
|
700
|
-
if (!
|
|
654
|
+
if (!existsSync3(configDir)) {
|
|
701
655
|
return getBuiltInEndpointConfigs();
|
|
702
656
|
}
|
|
703
657
|
const registry = {};
|
|
@@ -706,7 +660,7 @@ function loadEndpointConfigs(customDir) {
|
|
|
706
660
|
return getBuiltInEndpointConfigs();
|
|
707
661
|
}
|
|
708
662
|
for (const file of files) {
|
|
709
|
-
const filePath =
|
|
663
|
+
const filePath = join5(configDir, file);
|
|
710
664
|
try {
|
|
711
665
|
const config = loadEndpointConfigFile(filePath);
|
|
712
666
|
registry[config.provider] = config;
|
|
@@ -771,20 +725,20 @@ function validateEndpointConfig(data, filename) {
|
|
|
771
725
|
}
|
|
772
726
|
function computeEndpointConfigHash(customDir) {
|
|
773
727
|
const configDir = getEndpointConfigDir(customDir);
|
|
774
|
-
if (!
|
|
728
|
+
if (!existsSync3(configDir)) {
|
|
775
729
|
const builtIn = getBuiltInEndpointConfigs();
|
|
776
730
|
const serialized = JSON.stringify(builtIn, Object.keys(builtIn).sort());
|
|
777
|
-
return
|
|
731
|
+
return shortHash(serialized, 12);
|
|
778
732
|
}
|
|
779
733
|
const files = readdirSync2(configDir).filter((f) => f.endsWith(".json")).sort();
|
|
780
734
|
if (files.length === 0) {
|
|
781
735
|
const builtIn = getBuiltInEndpointConfigs();
|
|
782
736
|
const serialized = JSON.stringify(builtIn, Object.keys(builtIn).sort());
|
|
783
|
-
return
|
|
737
|
+
return shortHash(serialized, 12);
|
|
784
738
|
}
|
|
785
739
|
let combined = "";
|
|
786
740
|
for (const file of files) {
|
|
787
|
-
const filePath =
|
|
741
|
+
const filePath = join5(configDir, file);
|
|
788
742
|
try {
|
|
789
743
|
const content = readFileSync3(filePath, "utf-8");
|
|
790
744
|
combined += `\x00${file}\x00${content}`;
|
|
@@ -792,7 +746,7 @@ function computeEndpointConfigHash(customDir) {
|
|
|
792
746
|
continue;
|
|
793
747
|
}
|
|
794
748
|
}
|
|
795
|
-
return
|
|
749
|
+
return shortHash(combined, 12);
|
|
796
750
|
}
|
|
797
751
|
function getBuiltInEndpointConfigs() {
|
|
798
752
|
return {
|
|
@@ -889,22 +843,26 @@ function getBuiltInEndpointConfigs() {
|
|
|
889
843
|
}
|
|
890
844
|
|
|
891
845
|
// src/services/endpoint-lock.ts
|
|
892
|
-
import {
|
|
893
|
-
import { join as
|
|
894
|
-
import { homedir as homedir4 } from "os";
|
|
846
|
+
import { readFileSync as readFileSync4, unlinkSync as unlinkSync3 } from "fs";
|
|
847
|
+
import { join as join6 } from "path";
|
|
895
848
|
function getLockFilePath(customDir) {
|
|
896
849
|
if (customDir) {
|
|
897
|
-
return
|
|
850
|
+
return join6(customDir, ".endpoint-config.lock");
|
|
898
851
|
}
|
|
899
|
-
return
|
|
852
|
+
return join6(getConfigDir(), ".endpoint-config.lock");
|
|
900
853
|
}
|
|
901
854
|
function readEndpointLock(customDir) {
|
|
902
855
|
const lockPath = getLockFilePath(customDir);
|
|
903
|
-
|
|
904
|
-
|
|
856
|
+
let content;
|
|
857
|
+
try {
|
|
858
|
+
content = readFileSync4(lockPath, "utf-8");
|
|
859
|
+
} catch (err) {
|
|
860
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
throw err;
|
|
905
864
|
}
|
|
906
865
|
try {
|
|
907
|
-
const content = readFileSync4(lockPath, "utf-8");
|
|
908
866
|
const data = JSON.parse(content);
|
|
909
867
|
if (typeof data === "object" && data !== null && "hash" in data && "lockedAt" in data && typeof data.hash === "string" && typeof data.lockedAt === "string") {
|
|
910
868
|
return {
|
|
@@ -929,6 +887,90 @@ function writeEndpointLock(hash, customDir) {
|
|
|
929
887
|
});
|
|
930
888
|
}
|
|
931
889
|
|
|
890
|
+
// src/services/config.ts
|
|
891
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
892
|
+
import { join as join7 } from "path";
|
|
893
|
+
function getConfigPath(customPath) {
|
|
894
|
+
if (customPath) {
|
|
895
|
+
return customPath;
|
|
896
|
+
}
|
|
897
|
+
return join7(getConfigDir(), "config.json");
|
|
898
|
+
}
|
|
899
|
+
function deepMerge(target, source) {
|
|
900
|
+
const result = { ...target };
|
|
901
|
+
for (const key in source) {
|
|
902
|
+
const sourceValue = source[key];
|
|
903
|
+
const targetValue = result[key];
|
|
904
|
+
if (sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue && typeof targetValue === "object" && !Array.isArray(targetValue)) {
|
|
905
|
+
result[key] = deepMerge(targetValue, sourceValue);
|
|
906
|
+
} else if (sourceValue !== undefined) {
|
|
907
|
+
result[key] = sourceValue;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return result;
|
|
911
|
+
}
|
|
912
|
+
function validateConfig(config) {
|
|
913
|
+
let maxWidth = config.display.maxWidth;
|
|
914
|
+
let pollIntervalSeconds = config.pollIntervalSeconds;
|
|
915
|
+
let pipedRequestTimeoutMs = config.pipedRequestTimeoutMs;
|
|
916
|
+
if (maxWidth < 20) {
|
|
917
|
+
logger.warn("display.maxWidth < 20, clamping to 20");
|
|
918
|
+
maxWidth = 20;
|
|
919
|
+
}
|
|
920
|
+
if (maxWidth > 100) {
|
|
921
|
+
logger.warn("display.maxWidth > 100, clamping to 100");
|
|
922
|
+
maxWidth = 100;
|
|
923
|
+
}
|
|
924
|
+
if (pollIntervalSeconds !== undefined && pollIntervalSeconds < 5) {
|
|
925
|
+
logger.warn("pollIntervalSeconds < 5, clamping to 5");
|
|
926
|
+
pollIntervalSeconds = 5;
|
|
927
|
+
}
|
|
928
|
+
if (pipedRequestTimeoutMs !== undefined && pipedRequestTimeoutMs < 100) {
|
|
929
|
+
logger.warn("pipedRequestTimeoutMs < 100, clamping to 100");
|
|
930
|
+
pipedRequestTimeoutMs = 100;
|
|
931
|
+
}
|
|
932
|
+
return {
|
|
933
|
+
...config,
|
|
934
|
+
display: {
|
|
935
|
+
...config.display,
|
|
936
|
+
maxWidth
|
|
937
|
+
},
|
|
938
|
+
pollIntervalSeconds,
|
|
939
|
+
pipedRequestTimeoutMs
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
function parseConfigContent(content, path) {
|
|
943
|
+
try {
|
|
944
|
+
const userConfig = JSON.parse(content);
|
|
945
|
+
const merged = deepMerge(DEFAULT_CONFIG, userConfig);
|
|
946
|
+
return validateConfig(merged);
|
|
947
|
+
} catch (err) {
|
|
948
|
+
logger.warn(`Could not load config from ${path}: ${err}`);
|
|
949
|
+
logger.warn("Using default configuration");
|
|
950
|
+
return DEFAULT_CONFIG;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
function loadConfigWithHash(configPath) {
|
|
954
|
+
const path = getConfigPath(configPath);
|
|
955
|
+
let content;
|
|
956
|
+
try {
|
|
957
|
+
content = readFileSync5(path, "utf-8");
|
|
958
|
+
} catch (err) {
|
|
959
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
960
|
+
return { config: DEFAULT_CONFIG, configHash: shortHash("", 12) };
|
|
961
|
+
}
|
|
962
|
+
throw err;
|
|
963
|
+
}
|
|
964
|
+
return {
|
|
965
|
+
config: parseConfigContent(content, path),
|
|
966
|
+
configHash: shortHash(content, 12)
|
|
967
|
+
};
|
|
968
|
+
}
|
|
969
|
+
function serializableConfig(config) {
|
|
970
|
+
const { colors: _colors, ...rest } = config;
|
|
971
|
+
return rest;
|
|
972
|
+
}
|
|
973
|
+
|
|
932
974
|
// src/services/config-defaults.ts
|
|
933
975
|
function getDefaultStyleConfig() {
|
|
934
976
|
return DEFAULT_CONFIG;
|
|
@@ -948,26 +990,26 @@ function getDefaultCrsConfig() {
|
|
|
948
990
|
return config;
|
|
949
991
|
}
|
|
950
992
|
function writeDefaultConfigs(customDir) {
|
|
951
|
-
const configDir = customDir ||
|
|
952
|
-
const configPath =
|
|
993
|
+
const configDir = customDir || getConfigDir();
|
|
994
|
+
const configPath = join8(configDir, "config.json");
|
|
953
995
|
const apiConfigDir = getEndpointConfigDir(customDir);
|
|
954
996
|
ensureDir(configDir);
|
|
955
997
|
ensureDir(apiConfigDir);
|
|
956
|
-
if (!
|
|
957
|
-
const
|
|
958
|
-
atomicWriteFile(configPath, JSON.stringify(
|
|
998
|
+
if (!existsSync4(configPath)) {
|
|
999
|
+
const styleConfigWithoutColors = serializableConfig(getDefaultStyleConfig());
|
|
1000
|
+
atomicWriteFile(configPath, JSON.stringify(styleConfigWithoutColors, null, 2), {
|
|
959
1001
|
appendNewline: true
|
|
960
1002
|
});
|
|
961
1003
|
}
|
|
962
|
-
const sub2apiPath =
|
|
963
|
-
if (!
|
|
1004
|
+
const sub2apiPath = join8(apiConfigDir, "sub2api.json");
|
|
1005
|
+
if (!existsSync4(sub2apiPath)) {
|
|
964
1006
|
const sub2apiConfig = getDefaultSub2apiConfig();
|
|
965
1007
|
atomicWriteFile(sub2apiPath, JSON.stringify(sub2apiConfig, null, 2), {
|
|
966
1008
|
appendNewline: true
|
|
967
1009
|
});
|
|
968
1010
|
}
|
|
969
|
-
const crsPath =
|
|
970
|
-
if (!
|
|
1011
|
+
const crsPath = join8(apiConfigDir, "crs.json");
|
|
1012
|
+
if (!existsSync4(crsPath)) {
|
|
971
1013
|
const crsConfig = getDefaultCrsConfig();
|
|
972
1014
|
atomicWriteFile(crsPath, JSON.stringify(crsConfig, null, 2), {
|
|
973
1015
|
appendNewline: true
|
|
@@ -977,10 +1019,10 @@ function writeDefaultConfigs(customDir) {
|
|
|
977
1019
|
writeEndpointLock(currentHash, customDir);
|
|
978
1020
|
}
|
|
979
1021
|
function needsConfigInit(customDir) {
|
|
980
|
-
const configDir = customDir ||
|
|
981
|
-
const configPath =
|
|
1022
|
+
const configDir = customDir || getConfigDir();
|
|
1023
|
+
const configPath = join8(configDir, "config.json");
|
|
982
1024
|
const apiConfigDir = getEndpointConfigDir(customDir);
|
|
983
|
-
return !
|
|
1025
|
+
return !existsSync4(configPath) || !existsSync4(apiConfigDir);
|
|
984
1026
|
}
|
|
985
1027
|
|
|
986
1028
|
// src/providers/http.ts
|
|
@@ -1126,15 +1168,14 @@ async function probeHealth(baseUrl, timeoutMs = 1500) {
|
|
|
1126
1168
|
}
|
|
1127
1169
|
|
|
1128
1170
|
// src/services/cache.ts
|
|
1129
|
-
import { readFileSync as
|
|
1130
|
-
import { join as
|
|
1131
|
-
import { homedir as homedir6 } from "os";
|
|
1171
|
+
import { readFileSync as readFileSync6, unlinkSync as unlinkSync4 } from "fs";
|
|
1172
|
+
import { join as join9 } from "path";
|
|
1132
1173
|
function getCacheDir() {
|
|
1133
1174
|
const override = process.env["CC_API_STATUSLINE_CACHE_DIR"];
|
|
1134
1175
|
if (override) {
|
|
1135
1176
|
return override;
|
|
1136
1177
|
}
|
|
1137
|
-
return
|
|
1178
|
+
return getConfigDir();
|
|
1138
1179
|
}
|
|
1139
1180
|
function ensureCacheDir() {
|
|
1140
1181
|
const dir = getCacheDir();
|
|
@@ -1142,23 +1183,28 @@ function ensureCacheDir() {
|
|
|
1142
1183
|
}
|
|
1143
1184
|
function getCachePath(baseUrl) {
|
|
1144
1185
|
const hash = shortHash(baseUrl, 12);
|
|
1145
|
-
return
|
|
1186
|
+
return join9(getCacheDir(), `cache-${hash}.json`);
|
|
1146
1187
|
}
|
|
1147
1188
|
function readCache(baseUrl) {
|
|
1148
1189
|
const path = getCachePath(baseUrl);
|
|
1149
|
-
|
|
1150
|
-
|
|
1190
|
+
let content;
|
|
1191
|
+
try {
|
|
1192
|
+
content = readFileSync6(path, "utf-8");
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
1195
|
+
return null;
|
|
1196
|
+
}
|
|
1197
|
+
throw err;
|
|
1151
1198
|
}
|
|
1152
1199
|
try {
|
|
1153
|
-
const content = readFileSync5(path, "utf-8");
|
|
1154
1200
|
const data = JSON.parse(content);
|
|
1155
1201
|
if (!isCacheEntry(data)) {
|
|
1156
|
-
|
|
1202
|
+
logger.warn(`Invalid cache structure at ${path}`);
|
|
1157
1203
|
return null;
|
|
1158
1204
|
}
|
|
1159
1205
|
return data;
|
|
1160
|
-
} catch (
|
|
1161
|
-
|
|
1206
|
+
} catch (err) {
|
|
1207
|
+
logger.warn(`Failed to parse cache from ${path}: ${err}`);
|
|
1162
1208
|
return null;
|
|
1163
1209
|
}
|
|
1164
1210
|
}
|
|
@@ -1169,7 +1215,7 @@ function writeCache(baseUrl, entry) {
|
|
|
1169
1215
|
const content = JSON.stringify(entry, null, 2);
|
|
1170
1216
|
atomicWriteFile(path, content);
|
|
1171
1217
|
} catch (error) {
|
|
1172
|
-
|
|
1218
|
+
logger.warn(`Failed to write cache to ${path}: ${error}`);
|
|
1173
1219
|
}
|
|
1174
1220
|
}
|
|
1175
1221
|
function isCacheValid(entry, currentEnv) {
|
|
@@ -1197,18 +1243,6 @@ function isCacheProviderValid(entry, currentProvider) {
|
|
|
1197
1243
|
function isCacheRenderedLineUsable(entry, currentConfigHash) {
|
|
1198
1244
|
return entry.configHash === currentConfigHash;
|
|
1199
1245
|
}
|
|
1200
|
-
function computeConfigHash(configPath) {
|
|
1201
|
-
if (!existsSync8(configPath)) {
|
|
1202
|
-
return sha256("").slice(0, 12);
|
|
1203
|
-
}
|
|
1204
|
-
try {
|
|
1205
|
-
const bytes = readFileSync5(configPath);
|
|
1206
|
-
return shortHash(bytes.toString("utf-8"), 12);
|
|
1207
|
-
} catch (error) {
|
|
1208
|
-
console.warn(`Failed to read config for hash: ${error}`);
|
|
1209
|
-
return sha256("").slice(0, 12);
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
1246
|
var DEFAULT_POLL_INTERVAL_SECONDS = 30;
|
|
1213
1247
|
function getEffectivePollInterval(config, envOverride) {
|
|
1214
1248
|
if (envOverride !== null) {
|
|
@@ -1219,18 +1253,23 @@ function getEffectivePollInterval(config, envOverride) {
|
|
|
1219
1253
|
}
|
|
1220
1254
|
function getProviderDetectionCachePath(baseUrl) {
|
|
1221
1255
|
const hash = shortHash(baseUrl, 12);
|
|
1222
|
-
return
|
|
1256
|
+
return join9(getCacheDir(), `provider-detect-${hash}.json`);
|
|
1223
1257
|
}
|
|
1224
1258
|
function readProviderDetectionCache(baseUrl) {
|
|
1225
1259
|
const path = getProviderDetectionCachePath(baseUrl);
|
|
1226
|
-
|
|
1227
|
-
|
|
1260
|
+
let content;
|
|
1261
|
+
try {
|
|
1262
|
+
content = readFileSync6(path, "utf-8");
|
|
1263
|
+
} catch (err) {
|
|
1264
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
1265
|
+
return null;
|
|
1266
|
+
}
|
|
1267
|
+
throw err;
|
|
1228
1268
|
}
|
|
1229
1269
|
try {
|
|
1230
|
-
const content = readFileSync5(path, "utf-8");
|
|
1231
1270
|
const data = JSON.parse(content);
|
|
1232
1271
|
if (!isProviderDetectionCacheEntry(data)) {
|
|
1233
|
-
|
|
1272
|
+
logger.warn(`Invalid provider detection cache structure at ${path}`);
|
|
1234
1273
|
return null;
|
|
1235
1274
|
}
|
|
1236
1275
|
const detectedAt = new Date(data.detectedAt).getTime();
|
|
@@ -1244,8 +1283,8 @@ function readProviderDetectionCache(baseUrl) {
|
|
|
1244
1283
|
return null;
|
|
1245
1284
|
}
|
|
1246
1285
|
return data;
|
|
1247
|
-
} catch (
|
|
1248
|
-
|
|
1286
|
+
} catch (err) {
|
|
1287
|
+
logger.warn(`Failed to parse provider detection cache from ${path}: ${err}`);
|
|
1249
1288
|
return null;
|
|
1250
1289
|
}
|
|
1251
1290
|
}
|
|
@@ -1256,13 +1295,15 @@ function writeProviderDetectionCache(baseUrl, entry) {
|
|
|
1256
1295
|
const content = JSON.stringify(entry, null, 2);
|
|
1257
1296
|
atomicWriteFile(path, content);
|
|
1258
1297
|
} catch (error) {
|
|
1259
|
-
|
|
1298
|
+
logger.warn(`Failed to write provider detection cache to ${path}: ${error}`);
|
|
1260
1299
|
}
|
|
1261
1300
|
}
|
|
1262
1301
|
|
|
1263
1302
|
// src/providers/autodetect.ts
|
|
1264
1303
|
var detectionCache = new Map;
|
|
1265
|
-
function detectProviderFromUrlPattern(baseUrl, endpointConfigs = {}) {
|
|
1304
|
+
function detectProviderFromUrlPattern(baseUrl, endpointConfigs = {}, options = {}) {
|
|
1305
|
+
const includeBuiltInPatterns = options.includeBuiltInPatterns ?? true;
|
|
1306
|
+
const fallbackProvider = Object.prototype.hasOwnProperty.call(options, "fallbackProvider") ? options.fallbackProvider ?? null : "sub2api";
|
|
1266
1307
|
const normalizedUrl = baseUrl.toLowerCase().replace(/\/$/, "");
|
|
1267
1308
|
for (const [providerId, config] of Object.entries(endpointConfigs)) {
|
|
1268
1309
|
const urlPatterns = config.detection?.urlPatterns;
|
|
@@ -1275,10 +1316,10 @@ function detectProviderFromUrlPattern(baseUrl, endpointConfigs = {}) {
|
|
|
1275
1316
|
}
|
|
1276
1317
|
}
|
|
1277
1318
|
}
|
|
1278
|
-
if (normalizedUrl.includes("/apistats") || normalizedUrl.includes("/api/user-stats")) {
|
|
1319
|
+
if (includeBuiltInPatterns && (normalizedUrl.includes("/apistats") || normalizedUrl.includes("/api/user-stats"))) {
|
|
1279
1320
|
return "claude-relay-service";
|
|
1280
1321
|
}
|
|
1281
|
-
return
|
|
1322
|
+
return fallbackProvider;
|
|
1282
1323
|
}
|
|
1283
1324
|
async function resolveProvider(baseUrl, providerOverride, endpointConfigs = {}, probeTimeoutMs = 1500) {
|
|
1284
1325
|
if (providerOverride) {
|
|
@@ -1302,19 +1343,14 @@ async function resolveProvider(baseUrl, providerOverride, endpointConfigs = {},
|
|
|
1302
1343
|
});
|
|
1303
1344
|
return diskCached.provider;
|
|
1304
1345
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
cacheProviderDetection(baseUrl, providerId, "url-pattern");
|
|
1314
|
-
return providerId;
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1346
|
+
const endpointPatternProvider = detectProviderFromUrlPattern(baseUrl, endpointConfigs, {
|
|
1347
|
+
includeBuiltInPatterns: false,
|
|
1348
|
+
fallbackProvider: null
|
|
1349
|
+
});
|
|
1350
|
+
if (endpointPatternProvider) {
|
|
1351
|
+
logger.debug("Provider detected via endpoint URL pattern", { provider: endpointPatternProvider });
|
|
1352
|
+
cacheProviderDetection(baseUrl, endpointPatternProvider, "url-pattern");
|
|
1353
|
+
return endpointPatternProvider;
|
|
1318
1354
|
}
|
|
1319
1355
|
logger.debug("Attempting health probe", { baseUrl, timeoutMs: probeTimeoutMs });
|
|
1320
1356
|
const probedProvider = await probeHealth(baseUrl, probeTimeoutMs);
|
|
@@ -1324,6 +1360,11 @@ async function resolveProvider(baseUrl, providerOverride, endpointConfigs = {},
|
|
|
1324
1360
|
return probedProvider;
|
|
1325
1361
|
}
|
|
1326
1362
|
const patternProvider = detectProviderFromUrlPattern(baseUrl, {});
|
|
1363
|
+
if (!patternProvider) {
|
|
1364
|
+
logger.debug("Provider URL pattern detection had no match, defaulting to sub2api");
|
|
1365
|
+
cacheProviderDetection(baseUrl, "sub2api", "url-pattern");
|
|
1366
|
+
return "sub2api";
|
|
1367
|
+
}
|
|
1327
1368
|
logger.debug("Provider detected via built-in URL pattern", { provider: patternProvider });
|
|
1328
1369
|
cacheProviderDetection(baseUrl, patternProvider, "url-pattern");
|
|
1329
1370
|
return patternProvider;
|
|
@@ -1347,8 +1388,8 @@ function clearDetectionCache() {
|
|
|
1347
1388
|
}
|
|
1348
1389
|
|
|
1349
1390
|
// src/cli/commands.ts
|
|
1350
|
-
import { existsSync as
|
|
1351
|
-
import { join as
|
|
1391
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3, unlinkSync as unlinkSync5 } from "fs";
|
|
1392
|
+
import { join as join10 } from "path";
|
|
1352
1393
|
function handleInstall(args) {
|
|
1353
1394
|
const existing = getExistingStatusLine();
|
|
1354
1395
|
if (existing && !args.force) {
|
|
@@ -1391,11 +1432,11 @@ function handleApplyConfig() {
|
|
|
1391
1432
|
console.log("✓ Provider detection cache cleared");
|
|
1392
1433
|
const cacheDir = getCacheDir();
|
|
1393
1434
|
let failCount = 0;
|
|
1394
|
-
if (
|
|
1435
|
+
if (existsSync5(cacheDir)) {
|
|
1395
1436
|
const files = readdirSync3(cacheDir).filter((f) => f.startsWith("cache-") && f.endsWith(".json"));
|
|
1396
1437
|
for (const file of files) {
|
|
1397
1438
|
try {
|
|
1398
|
-
const filePath =
|
|
1439
|
+
const filePath = join10(cacheDir, file);
|
|
1399
1440
|
unlinkSync5(filePath);
|
|
1400
1441
|
} catch {
|
|
1401
1442
|
failCount++;
|
|
@@ -1415,7 +1456,7 @@ function handleApplyConfig() {
|
|
|
1415
1456
|
console.log("");
|
|
1416
1457
|
console.log("Config files:");
|
|
1417
1458
|
const apiConfigDir = getEndpointConfigDir();
|
|
1418
|
-
if (
|
|
1459
|
+
if (existsSync5(apiConfigDir)) {
|
|
1419
1460
|
const configFiles = readdirSync3(apiConfigDir).filter((f) => f.endsWith(".json"));
|
|
1420
1461
|
for (const file of configFiles) {
|
|
1421
1462
|
console.log(` - ${apiConfigDir}/${file}`);
|
|
@@ -1423,83 +1464,10 @@ function handleApplyConfig() {
|
|
|
1423
1464
|
}
|
|
1424
1465
|
process.exit(0);
|
|
1425
1466
|
}
|
|
1426
|
-
// src/services/config.ts
|
|
1427
|
-
import { readFileSync as readFileSync6, existsSync as existsSync10 } from "fs";
|
|
1428
|
-
import { join as join9 } from "path";
|
|
1429
|
-
import { homedir as homedir7 } from "os";
|
|
1430
|
-
function getConfigDir() {
|
|
1431
|
-
return join9(homedir7(), ".claude", "cc-api-statusline");
|
|
1432
|
-
}
|
|
1433
|
-
function getConfigPath(customPath) {
|
|
1434
|
-
if (customPath) {
|
|
1435
|
-
return customPath;
|
|
1436
|
-
}
|
|
1437
|
-
return join9(getConfigDir(), "config.json");
|
|
1438
|
-
}
|
|
1439
|
-
function deepMerge(target, source) {
|
|
1440
|
-
const result = { ...target };
|
|
1441
|
-
for (const key in source) {
|
|
1442
|
-
const sourceValue = source[key];
|
|
1443
|
-
const targetValue = result[key];
|
|
1444
|
-
if (sourceValue && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue && typeof targetValue === "object" && !Array.isArray(targetValue)) {
|
|
1445
|
-
result[key] = deepMerge(targetValue, sourceValue);
|
|
1446
|
-
} else if (sourceValue !== undefined) {
|
|
1447
|
-
result[key] = sourceValue;
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
return result;
|
|
1451
|
-
}
|
|
1452
|
-
function validateConfig(config) {
|
|
1453
|
-
let maxWidth = config.display.maxWidth;
|
|
1454
|
-
let pollIntervalSeconds = config.pollIntervalSeconds;
|
|
1455
|
-
let pipedRequestTimeoutMs = config.pipedRequestTimeoutMs;
|
|
1456
|
-
if (maxWidth < 20) {
|
|
1457
|
-
console.warn("Warning: display.maxWidth < 20, clamping to 20");
|
|
1458
|
-
maxWidth = 20;
|
|
1459
|
-
}
|
|
1460
|
-
if (maxWidth > 100) {
|
|
1461
|
-
console.warn("Warning: display.maxWidth > 100, clamping to 100");
|
|
1462
|
-
maxWidth = 100;
|
|
1463
|
-
}
|
|
1464
|
-
if (pollIntervalSeconds !== undefined && pollIntervalSeconds < 5) {
|
|
1465
|
-
console.warn("Warning: pollIntervalSeconds < 5, clamping to 5");
|
|
1466
|
-
pollIntervalSeconds = 5;
|
|
1467
|
-
}
|
|
1468
|
-
if (pipedRequestTimeoutMs !== undefined && pipedRequestTimeoutMs < 100) {
|
|
1469
|
-
console.warn("Warning: pipedRequestTimeoutMs < 100, clamping to 100");
|
|
1470
|
-
pipedRequestTimeoutMs = 100;
|
|
1471
|
-
}
|
|
1472
|
-
return {
|
|
1473
|
-
...config,
|
|
1474
|
-
display: {
|
|
1475
|
-
...config.display,
|
|
1476
|
-
maxWidth
|
|
1477
|
-
},
|
|
1478
|
-
pollIntervalSeconds,
|
|
1479
|
-
pipedRequestTimeoutMs
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
function loadConfig(configPath) {
|
|
1483
|
-
const path = getConfigPath(configPath);
|
|
1484
|
-
if (!existsSync10(path)) {
|
|
1485
|
-
return DEFAULT_CONFIG;
|
|
1486
|
-
}
|
|
1487
|
-
try {
|
|
1488
|
-
const content = readFileSync6(path, "utf-8");
|
|
1489
|
-
const userConfig = JSON.parse(content);
|
|
1490
|
-
const merged = deepMerge(DEFAULT_CONFIG, userConfig);
|
|
1491
|
-
return validateConfig(merged);
|
|
1492
|
-
} catch (error) {
|
|
1493
|
-
console.warn(`Warning: Could not load config from ${path}: ${error}`);
|
|
1494
|
-
console.warn("Using default configuration");
|
|
1495
|
-
return DEFAULT_CONFIG;
|
|
1496
|
-
}
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
1467
|
// src/services/user-agent.ts
|
|
1500
1468
|
import { execSync as execSync2 } from "child_process";
|
|
1501
|
-
import { join as
|
|
1502
|
-
import { homedir as
|
|
1469
|
+
import { join as join11 } from "path";
|
|
1470
|
+
import { homedir as homedir3 } from "os";
|
|
1503
1471
|
var FALLBACK_UA = "claude-cli/2.1.56 (external, cli)";
|
|
1504
1472
|
function resolveUserAgent(config) {
|
|
1505
1473
|
if (!config) {
|
|
@@ -1526,10 +1494,10 @@ function detectClaudeVersion() {
|
|
|
1526
1494
|
if (!process.env["CLAUDECODE"]) {
|
|
1527
1495
|
return null;
|
|
1528
1496
|
}
|
|
1529
|
-
const claudePath =
|
|
1497
|
+
const claudePath = join11(homedir3(), ".claude", "bin", "claude");
|
|
1530
1498
|
const result = execSync2(`"${claudePath}" --version`, {
|
|
1531
1499
|
encoding: "utf-8",
|
|
1532
|
-
timeout:
|
|
1500
|
+
timeout: 100,
|
|
1533
1501
|
stdio: ["ignore", "pipe", "ignore"]
|
|
1534
1502
|
});
|
|
1535
1503
|
const match = result.match(/(\d+\.\d+\.\d+)/);
|
|
@@ -1844,19 +1812,17 @@ function mapResponseToUsage(responseData, mapping, endpointConfig) {
|
|
|
1844
1812
|
remainingSeconds: extractNumber(responseData, mapping["rateLimit.remainingSeconds"]) ?? 0
|
|
1845
1813
|
};
|
|
1846
1814
|
})();
|
|
1847
|
-
const resetsAt = (
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
return sorted[0] ?? null;
|
|
1859
|
-
})();
|
|
1815
|
+
const resetsAt = computeSoonestReset({
|
|
1816
|
+
...base,
|
|
1817
|
+
resetSemantics: billingMode === "balance" ? "expiry" : "end-of-day",
|
|
1818
|
+
balance,
|
|
1819
|
+
daily,
|
|
1820
|
+
weekly,
|
|
1821
|
+
monthly,
|
|
1822
|
+
tokenStats,
|
|
1823
|
+
rateLimit,
|
|
1824
|
+
resetsAt: null
|
|
1825
|
+
});
|
|
1860
1826
|
return {
|
|
1861
1827
|
...base,
|
|
1862
1828
|
resetSemantics: billingMode === "balance" ? "expiry" : "end-of-day",
|
|
@@ -1871,7 +1837,7 @@ function mapResponseToUsage(responseData, mapping, endpointConfig) {
|
|
|
1871
1837
|
}
|
|
1872
1838
|
|
|
1873
1839
|
// src/providers/endpoint-fetch.ts
|
|
1874
|
-
function
|
|
1840
|
+
function validateEndpointConfigSemantics(config) {
|
|
1875
1841
|
if (!config.provider)
|
|
1876
1842
|
return "Endpoint config missing required field: provider";
|
|
1877
1843
|
if (!config.endpoint?.path)
|
|
@@ -1894,7 +1860,7 @@ function validateEndpointConfig2(config) {
|
|
|
1894
1860
|
return null;
|
|
1895
1861
|
}
|
|
1896
1862
|
async function fetchEndpoint(baseUrl, token, appConfig, endpointConfig, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
|
|
1897
|
-
const validationError =
|
|
1863
|
+
const validationError = validateEndpointConfigSemantics(endpointConfig);
|
|
1898
1864
|
if (validationError) {
|
|
1899
1865
|
throw new Error(`Invalid endpoint config: ${validationError}`);
|
|
1900
1866
|
}
|
|
@@ -2099,6 +2065,9 @@ function hexToRgb(hex) {
|
|
|
2099
2065
|
function dimText(text) {
|
|
2100
2066
|
return `${ANSI_DIM}${text}${ANSI_RESET}`;
|
|
2101
2067
|
}
|
|
2068
|
+
function stripAnsi(text) {
|
|
2069
|
+
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
2070
|
+
}
|
|
2102
2071
|
function resolveColor(colorName, usagePercent, config) {
|
|
2103
2072
|
const effectiveColor = colorName ?? "auto";
|
|
2104
2073
|
if (effectiveColor.startsWith("#") || ANSI_COLORS[effectiveColor.toLowerCase()]) {
|
|
@@ -2493,46 +2462,49 @@ function formatCompactNumber(n) {
|
|
|
2493
2462
|
|
|
2494
2463
|
// src/renderer/component.ts
|
|
2495
2464
|
function renderComponent(componentId, data, componentConfig, globalConfig, renderContext) {
|
|
2496
|
-
const
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2465
|
+
const options = {
|
|
2466
|
+
layout: componentConfig.layout ?? globalConfig.display.layout,
|
|
2467
|
+
displayMode: resolveEffectiveDisplayMode(componentConfig.displayMode ?? globalConfig.display.displayMode, renderContext),
|
|
2468
|
+
progressStyle: resolveEffectiveProgressStyle(componentConfig.progressStyle ?? globalConfig.display.progressStyle, renderContext),
|
|
2469
|
+
barSize: componentConfig.barSize ?? globalConfig.display.barSize,
|
|
2470
|
+
barStyle: componentConfig.barStyle ?? globalConfig.display.barStyle,
|
|
2471
|
+
clockFormat: globalConfig.display.clockFormat
|
|
2472
|
+
};
|
|
2502
2473
|
switch (componentId) {
|
|
2503
2474
|
case "daily":
|
|
2504
|
-
return renderQuotaComponent("daily", data.daily, effectiveLayout, effectiveDisplayMode, effectiveProgressStyle, effectiveBarSize, effectiveBarStyle, componentConfig, globalConfig, clockFormat, renderContext);
|
|
2505
2475
|
case "weekly":
|
|
2506
|
-
return renderQuotaComponent("weekly", data.weekly, effectiveLayout, effectiveDisplayMode, effectiveProgressStyle, effectiveBarSize, effectiveBarStyle, componentConfig, globalConfig, clockFormat, renderContext);
|
|
2507
2476
|
case "monthly":
|
|
2508
|
-
return renderQuotaComponent(
|
|
2477
|
+
return renderQuotaComponent(componentId, data[componentId], options, componentConfig, globalConfig, renderContext);
|
|
2509
2478
|
case "balance":
|
|
2510
|
-
return renderBalanceComponent(data.balance,
|
|
2479
|
+
return renderBalanceComponent(data.balance, options, componentConfig, globalConfig, renderContext);
|
|
2511
2480
|
case "tokens":
|
|
2512
|
-
return renderTokensComponent(data.tokenStats,
|
|
2481
|
+
return renderTokensComponent(data.tokenStats, options, componentConfig, globalConfig, renderContext);
|
|
2513
2482
|
case "rateLimit":
|
|
2514
|
-
return renderRateLimitComponent(data.rateLimit,
|
|
2483
|
+
return renderRateLimitComponent(data.rateLimit, options, componentConfig, globalConfig, renderContext);
|
|
2515
2484
|
case "plan":
|
|
2516
|
-
return renderPlanComponent(data.planName,
|
|
2485
|
+
return renderPlanComponent(data.planName, options, componentConfig, globalConfig, renderContext);
|
|
2517
2486
|
default:
|
|
2518
2487
|
return null;
|
|
2519
2488
|
}
|
|
2520
2489
|
}
|
|
2521
|
-
function renderQuotaComponent(componentId, quota,
|
|
2490
|
+
function renderQuotaComponent(componentId, quota, options, componentConfig, globalConfig, renderContext) {
|
|
2491
|
+
const { layout, displayMode, progressStyle, barSize, barStyle, clockFormat } = options;
|
|
2522
2492
|
if (!quota)
|
|
2523
2493
|
return null;
|
|
2524
2494
|
const usagePercent = calculateUsagePercent(quota.used, quota.limit);
|
|
2525
2495
|
const label = renderLabel(componentId, displayMode, componentConfig, quota.qualifier);
|
|
2496
|
+
const showPercentage = componentConfig.percentage !== false;
|
|
2526
2497
|
const barColor = resolvePartColor("bar", usagePercent, componentConfig, globalConfig);
|
|
2527
|
-
const valueColor = resolvePartColor("value", usagePercent, componentConfig, globalConfig);
|
|
2498
|
+
const valueColor = showPercentage ? resolvePartColor("value", usagePercent, componentConfig, globalConfig) : null;
|
|
2528
2499
|
const labelColor = resolvePartColor("label", usagePercent, componentConfig, globalConfig);
|
|
2529
2500
|
const countdownColor = resolvePartColor("countdown", usagePercent, componentConfig, globalConfig);
|
|
2530
2501
|
const progress = renderProgress(progressStyle, usagePercent, barSize, barStyle, barColor, null, renderContext);
|
|
2531
|
-
const value = ansiColor(`${Math.round(usagePercent)}%`, valueColor, renderContext);
|
|
2502
|
+
const value = showPercentage ? ansiColor(`${Math.round(usagePercent)}%`, valueColor, renderContext) : "";
|
|
2532
2503
|
const countdown = renderSecondaryDisplay(quota.resetsAt, quota, componentConfig.countdown, countdownColor, clockFormat, renderContext);
|
|
2533
2504
|
return assembleComponent(layout, label, labelColor, progress, value, countdown, renderContext);
|
|
2534
2505
|
}
|
|
2535
|
-
function renderBalanceComponent(balance,
|
|
2506
|
+
function renderBalanceComponent(balance, options, componentConfig, globalConfig, renderContext) {
|
|
2507
|
+
const { layout, displayMode, progressStyle, barSize, barStyle, clockFormat } = options;
|
|
2536
2508
|
if (!balance)
|
|
2537
2509
|
return null;
|
|
2538
2510
|
const isUnlimited = balance.remaining === -1;
|
|
@@ -2551,7 +2523,8 @@ function renderBalanceComponent(balance, layout, displayMode, progressStyle, bar
|
|
|
2551
2523
|
const countdown = "";
|
|
2552
2524
|
return assembleComponent(layout, label, labelColor, progress, value, countdown, renderContext);
|
|
2553
2525
|
}
|
|
2554
|
-
function renderTokensComponent(tokenStats,
|
|
2526
|
+
function renderTokensComponent(tokenStats, options, componentConfig, globalConfig, renderContext) {
|
|
2527
|
+
const { layout, displayMode, progressStyle, barSize, barStyle, clockFormat } = options;
|
|
2555
2528
|
if (!tokenStats)
|
|
2556
2529
|
return null;
|
|
2557
2530
|
const stats = tokenStats.total ?? tokenStats.today;
|
|
@@ -2567,7 +2540,8 @@ function renderTokensComponent(tokenStats, layout, displayMode, componentConfig,
|
|
|
2567
2540
|
const countdown = "";
|
|
2568
2541
|
return assembleComponent(layout, label, labelColor, progress, value, countdown, renderContext);
|
|
2569
2542
|
}
|
|
2570
|
-
function renderRateLimitComponent(rateLimit,
|
|
2543
|
+
function renderRateLimitComponent(rateLimit, options, componentConfig, globalConfig, renderContext) {
|
|
2544
|
+
const { layout, displayMode, progressStyle, barSize, barStyle, clockFormat } = options;
|
|
2571
2545
|
if (!rateLimit)
|
|
2572
2546
|
return null;
|
|
2573
2547
|
let usagePercent = null;
|
|
@@ -2584,7 +2558,8 @@ function renderRateLimitComponent(rateLimit, layout, displayMode, progressStyle,
|
|
|
2584
2558
|
const countdown = "";
|
|
2585
2559
|
return assembleComponent(layout, label, labelColor, progress, value, countdown, renderContext);
|
|
2586
2560
|
}
|
|
2587
|
-
function renderPlanComponent(planName,
|
|
2561
|
+
function renderPlanComponent(planName, options, componentConfig, globalConfig, renderContext) {
|
|
2562
|
+
const { layout, displayMode, progressStyle, barSize, barStyle, clockFormat } = options;
|
|
2588
2563
|
if (displayMode === "hidden")
|
|
2589
2564
|
return null;
|
|
2590
2565
|
const labelColor = resolvePartColor("label", null, componentConfig, globalConfig);
|
|
@@ -2698,21 +2673,25 @@ function assembleComponent(layout, label, labelColor, progress, value, countdown
|
|
|
2698
2673
|
if (layout === "percent-first") {
|
|
2699
2674
|
if (coloredLabel)
|
|
2700
2675
|
parts.push(coloredLabel);
|
|
2701
|
-
|
|
2676
|
+
if (value)
|
|
2677
|
+
parts.push(value);
|
|
2702
2678
|
if (progress)
|
|
2703
2679
|
parts.push(progress);
|
|
2704
|
-
if (countdown)
|
|
2705
|
-
parts.push(countdown);
|
|
2706
2680
|
} else {
|
|
2707
2681
|
if (coloredLabel)
|
|
2708
2682
|
parts.push(coloredLabel);
|
|
2709
2683
|
if (progress)
|
|
2710
2684
|
parts.push(progress);
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
parts.push(countdown);
|
|
2685
|
+
if (value)
|
|
2686
|
+
parts.push(value);
|
|
2714
2687
|
}
|
|
2715
|
-
|
|
2688
|
+
if (countdown && parts.length > 0) {
|
|
2689
|
+
const idx = parts.length - 1;
|
|
2690
|
+
parts[idx] = (parts[idx] ?? "") + countdown;
|
|
2691
|
+
} else if (countdown) {
|
|
2692
|
+
parts.push(countdown);
|
|
2693
|
+
}
|
|
2694
|
+
return parts.join(" ");
|
|
2716
2695
|
}
|
|
2717
2696
|
function calculateUsagePercent(used, limit) {
|
|
2718
2697
|
if (limit === null)
|
|
@@ -2749,8 +2728,7 @@ function computeMaxWidth(termWidth, maxWidthPct) {
|
|
|
2749
2728
|
return Math.floor(termWidth * pct / 100);
|
|
2750
2729
|
}
|
|
2751
2730
|
function visibleLength(text) {
|
|
2752
|
-
|
|
2753
|
-
return stripped.length;
|
|
2731
|
+
return stripAnsi(text).length;
|
|
2754
2732
|
}
|
|
2755
2733
|
function ansiAwareTruncate(text, maxWidth) {
|
|
2756
2734
|
const visible = visibleLength(text);
|
|
@@ -2797,8 +2775,8 @@ var COMPONENT_DROP_PRIORITY = [
|
|
|
2797
2775
|
// src/renderer/divider.ts
|
|
2798
2776
|
function renderDivider(divider) {
|
|
2799
2777
|
const text = divider.text ?? "|";
|
|
2800
|
-
const
|
|
2801
|
-
const pad = " ".repeat(
|
|
2778
|
+
const margin = divider.margin ?? 1;
|
|
2779
|
+
const pad = " ".repeat(margin);
|
|
2802
2780
|
const padded = `${pad}${text}${pad}`;
|
|
2803
2781
|
return ansiColor(padded, divider.color ?? "#555753");
|
|
2804
2782
|
}
|
|
@@ -2890,63 +2868,46 @@ function renderStatusline(data, config, errorState, cacheAge, isPiped = false) {
|
|
|
2890
2868
|
currentWidth = calculateStatuslineWidth(componentMap, activeComponents, componentOrder, separator, errorState, data, cacheAge);
|
|
2891
2869
|
}
|
|
2892
2870
|
}
|
|
2893
|
-
|
|
2894
|
-
for (const componentId of componentOrder) {
|
|
2895
|
-
if (activeComponents.has(componentId)) {
|
|
2896
|
-
const rendered = componentMap.get(componentId);
|
|
2897
|
-
if (rendered)
|
|
2898
|
-
renderedComponents.push(rendered);
|
|
2899
|
-
}
|
|
2900
|
-
}
|
|
2901
|
-
let statusline = renderedComponents.join(separator);
|
|
2902
|
-
if (errorState) {
|
|
2903
|
-
if (isTransitionState(errorState)) {
|
|
2904
|
-
statusline = renderError(errorState, "with-cache", data.provider, undefined, cacheAge);
|
|
2905
|
-
} else {
|
|
2906
|
-
const hasCache = renderedComponents.length > 0;
|
|
2907
|
-
const errorMode = hasCache ? "with-cache" : "without-cache";
|
|
2908
|
-
const errorIndicator = renderError(errorState, errorMode, data.provider, undefined, cacheAge);
|
|
2909
|
-
statusline = hasCache ? `${statusline} ${errorIndicator}` : errorIndicator;
|
|
2910
|
-
}
|
|
2911
|
-
}
|
|
2871
|
+
let statusline = assembleStatuslineString(componentOrder, componentMap, activeComponents, separator, errorState, data, cacheAge);
|
|
2912
2872
|
const termWidth = getTerminalWidth();
|
|
2913
2873
|
const maxW = computeMaxWidth(termWidth, config.display.maxWidth ?? 100);
|
|
2914
2874
|
statusline = ansiAwareTruncate(statusline, maxW);
|
|
2915
2875
|
return statusline;
|
|
2916
2876
|
}
|
|
2917
2877
|
function computeSeparator(config) {
|
|
2918
|
-
const dividerConfig = config.
|
|
2878
|
+
const dividerConfig = config.display.divider;
|
|
2919
2879
|
if (dividerConfig === false)
|
|
2920
2880
|
return "";
|
|
2921
|
-
|
|
2922
|
-
return renderDivider(dividerConfig);
|
|
2923
|
-
return config.display.separator ?? " | ";
|
|
2881
|
+
return renderDivider(dividerConfig ?? DEFAULT_DIVIDER_CONFIG);
|
|
2924
2882
|
}
|
|
2925
2883
|
function maxWidth(config) {
|
|
2926
2884
|
const termWidth = getTerminalWidth();
|
|
2927
2885
|
return computeMaxWidth(termWidth, config.display.maxWidth ?? 100);
|
|
2928
2886
|
}
|
|
2929
|
-
function
|
|
2930
|
-
const
|
|
2887
|
+
function assembleStatuslineString(componentOrder, componentMap, activeComponents, separator, errorState, data, cacheAge) {
|
|
2888
|
+
const rendered = [];
|
|
2931
2889
|
for (const id of componentOrder) {
|
|
2932
2890
|
if (activeComponents.has(id)) {
|
|
2933
|
-
const
|
|
2934
|
-
if (
|
|
2935
|
-
|
|
2891
|
+
const r = componentMap.get(id);
|
|
2892
|
+
if (r)
|
|
2893
|
+
rendered.push(r);
|
|
2936
2894
|
}
|
|
2937
2895
|
}
|
|
2938
|
-
let statusline =
|
|
2896
|
+
let statusline = rendered.join(separator);
|
|
2939
2897
|
if (errorState) {
|
|
2940
2898
|
if (isTransitionState(errorState)) {
|
|
2941
2899
|
statusline = renderError(errorState, "with-cache", data.provider, undefined, cacheAge);
|
|
2942
2900
|
} else {
|
|
2943
|
-
const hasCache =
|
|
2901
|
+
const hasCache = rendered.length > 0;
|
|
2944
2902
|
const errorMode = hasCache ? "with-cache" : "without-cache";
|
|
2945
2903
|
const errorIndicator = renderError(errorState, errorMode, data.provider, undefined, cacheAge);
|
|
2946
2904
|
statusline = hasCache ? `${statusline} ${errorIndicator}` : errorIndicator;
|
|
2947
2905
|
}
|
|
2948
2906
|
}
|
|
2949
|
-
return
|
|
2907
|
+
return statusline;
|
|
2908
|
+
}
|
|
2909
|
+
function calculateStatuslineWidth(componentMap, activeComponents, componentOrder, separator, errorState, data, cacheAge) {
|
|
2910
|
+
return visibleLength(assembleStatuslineString(componentOrder, componentMap, activeComponents, separator, errorState, data, cacheAge));
|
|
2950
2911
|
}
|
|
2951
2912
|
function getComponentOrder(config) {
|
|
2952
2913
|
const explicitOrder = [];
|
|
@@ -3094,11 +3055,11 @@ async function executeCycle(ctx) {
|
|
|
3094
3055
|
}
|
|
3095
3056
|
}
|
|
3096
3057
|
// src/services/cache-gc.ts
|
|
3097
|
-
import { readdirSync as readdirSync4, statSync as statSync2, unlinkSync as unlinkSync6, existsSync as
|
|
3098
|
-
import { join as
|
|
3058
|
+
import { readdirSync as readdirSync4, statSync as statSync2, unlinkSync as unlinkSync6, existsSync as existsSync6 } from "fs";
|
|
3059
|
+
import { join as join12 } from "path";
|
|
3099
3060
|
function runCacheGC(cacheDir) {
|
|
3100
3061
|
try {
|
|
3101
|
-
if (!
|
|
3062
|
+
if (!existsSync6(cacheDir)) {
|
|
3102
3063
|
logger.debug("GC: Cache directory does not exist, skipping", { cacheDir });
|
|
3103
3064
|
return;
|
|
3104
3065
|
}
|
|
@@ -3109,7 +3070,7 @@ function runCacheGC(cacheDir) {
|
|
|
3109
3070
|
const tmpFiles = [];
|
|
3110
3071
|
for (const file of files) {
|
|
3111
3072
|
try {
|
|
3112
|
-
const filePath =
|
|
3073
|
+
const filePath = join12(cacheDir, file);
|
|
3113
3074
|
const stats = statSync2(filePath);
|
|
3114
3075
|
const mtime = stats.mtimeMs;
|
|
3115
3076
|
if (file.startsWith("cache-") && file.endsWith(".json")) {
|
|
@@ -3129,7 +3090,7 @@ function runCacheGC(cacheDir) {
|
|
|
3129
3090
|
const age = now - file.mtime;
|
|
3130
3091
|
if (age > GC_MAX_AGE_MS) {
|
|
3131
3092
|
try {
|
|
3132
|
-
unlinkSync6(
|
|
3093
|
+
unlinkSync6(join12(cacheDir, file.name));
|
|
3133
3094
|
deletedCount++;
|
|
3134
3095
|
logger.debug("GC: Deleted old cache file", { file: file.name, ageDays: Math.floor(age / (24 * 60 * 60 * 1000)) });
|
|
3135
3096
|
} catch (error) {
|
|
@@ -3141,7 +3102,7 @@ function runCacheGC(cacheDir) {
|
|
|
3141
3102
|
const age = now - file.mtime;
|
|
3142
3103
|
if (age > GC_MAX_AGE_MS) {
|
|
3143
3104
|
try {
|
|
3144
|
-
unlinkSync6(
|
|
3105
|
+
unlinkSync6(join12(cacheDir, file.name));
|
|
3145
3106
|
deletedCount++;
|
|
3146
3107
|
logger.debug("GC: Deleted old provider-detect file", { file: file.name, ageDays: Math.floor(age / (24 * 60 * 60 * 1000)) });
|
|
3147
3108
|
} catch (error) {
|
|
@@ -3153,7 +3114,7 @@ function runCacheGC(cacheDir) {
|
|
|
3153
3114
|
const age = now - file.mtime;
|
|
3154
3115
|
if (age > GC_ORPHAN_TMP_AGE_MS) {
|
|
3155
3116
|
try {
|
|
3156
|
-
unlinkSync6(
|
|
3117
|
+
unlinkSync6(join12(cacheDir, file.name));
|
|
3157
3118
|
deletedCount++;
|
|
3158
3119
|
logger.debug("GC: Deleted orphaned tmp file", { file: file.name, ageMinutes: Math.floor(age / (60 * 1000)) });
|
|
3159
3120
|
} catch (error) {
|
|
@@ -3170,7 +3131,7 @@ function runCacheGC(cacheDir) {
|
|
|
3170
3131
|
const toDelete = remainingCacheFiles.slice(0, remainingCacheFiles.length - GC_MAX_CACHE_FILES);
|
|
3171
3132
|
for (const file of toDelete) {
|
|
3172
3133
|
try {
|
|
3173
|
-
unlinkSync6(
|
|
3134
|
+
unlinkSync6(join12(cacheDir, file.name));
|
|
3174
3135
|
deletedCount++;
|
|
3175
3136
|
logger.debug("GC: Deleted cache file (count limit)", { file: file.name });
|
|
3176
3137
|
} catch (error) {
|
|
@@ -3192,6 +3153,11 @@ class StatuslineError extends Error {
|
|
|
3192
3153
|
this.errorType = errorType;
|
|
3193
3154
|
}
|
|
3194
3155
|
}
|
|
3156
|
+
function safeStdoutWrite(data) {
|
|
3157
|
+
try {
|
|
3158
|
+
process.stdout["write"](data);
|
|
3159
|
+
} catch {}
|
|
3160
|
+
}
|
|
3195
3161
|
function readAndValidateEnv() {
|
|
3196
3162
|
const env = readCurrentEnv();
|
|
3197
3163
|
logger.debug("Environment loaded", {
|
|
@@ -3206,7 +3172,7 @@ function readAndValidateEnv() {
|
|
|
3206
3172
|
}
|
|
3207
3173
|
const { baseUrl } = env;
|
|
3208
3174
|
if (!baseUrl) {
|
|
3209
|
-
process.exit(
|
|
3175
|
+
process.exit(0);
|
|
3210
3176
|
}
|
|
3211
3177
|
return { env, baseUrl };
|
|
3212
3178
|
}
|
|
@@ -3216,13 +3182,6 @@ function ensureDefaultConfigs() {
|
|
|
3216
3182
|
writeDefaultConfigs();
|
|
3217
3183
|
}
|
|
3218
3184
|
}
|
|
3219
|
-
function loadConfigWithHash(configPath) {
|
|
3220
|
-
const config = loadConfig(configPath);
|
|
3221
|
-
const resolvedPath = getConfigPath(configPath);
|
|
3222
|
-
const configHash = computeConfigHash(resolvedPath);
|
|
3223
|
-
logger.debug("Config loaded", { configPath: resolvedPath, configHash });
|
|
3224
|
-
return { config, configHash };
|
|
3225
|
-
}
|
|
3226
3185
|
function loadEndpointConfigsWithHash() {
|
|
3227
3186
|
const endpointConfigs = loadEndpointConfigs();
|
|
3228
3187
|
const endpointConfigHash = computeEndpointConfigHash();
|
|
@@ -3330,7 +3289,7 @@ async function executePipedMode(args) {
|
|
|
3330
3289
|
logger.error("Watchdog timeout - forcing clean exit", { watchdogMs });
|
|
3331
3290
|
const fallback = dimText("⟳ Refreshing...");
|
|
3332
3291
|
const formatted = formatOutput(fallback, isPiped);
|
|
3333
|
-
|
|
3292
|
+
safeStdoutWrite(formatted);
|
|
3334
3293
|
process.exit(0);
|
|
3335
3294
|
}, watchdogMs).unref();
|
|
3336
3295
|
}
|
|
@@ -3343,7 +3302,7 @@ async function executePipedMode(args) {
|
|
|
3343
3302
|
const errorType = error instanceof StatuslineError ? error.errorType : "network-error";
|
|
3344
3303
|
const errorOutput = renderError(errorType, "without-cache");
|
|
3345
3304
|
const formattedOutput2 = formatOutput(errorOutput, isPiped);
|
|
3346
|
-
|
|
3305
|
+
safeStdoutWrite(formattedOutput2);
|
|
3347
3306
|
logger.debug("=== cc-api-statusline execution completed ===");
|
|
3348
3307
|
process.exit(0);
|
|
3349
3308
|
}
|
|
@@ -3358,7 +3317,7 @@ async function executePipedMode(args) {
|
|
|
3358
3317
|
logger.error("Execution cycle failed", { error: String(error) });
|
|
3359
3318
|
const errorOutput = renderError("network-error", "without-cache");
|
|
3360
3319
|
const formattedOutput2 = formatOutput(errorOutput, isPiped);
|
|
3361
|
-
|
|
3320
|
+
safeStdoutWrite(formattedOutput2);
|
|
3362
3321
|
logger.debug("=== cc-api-statusline execution completed ===");
|
|
3363
3322
|
process.exit(0);
|
|
3364
3323
|
}
|
|
@@ -3370,7 +3329,7 @@ async function executePipedMode(args) {
|
|
|
3370
3329
|
cacheUpdate: !!result.cacheUpdate
|
|
3371
3330
|
});
|
|
3372
3331
|
const formattedOutput = formatOutput(result.output, isPiped);
|
|
3373
|
-
|
|
3332
|
+
safeStdoutWrite(formattedOutput);
|
|
3374
3333
|
if (result.cacheUpdate) {
|
|
3375
3334
|
writeCache(baseUrl, result.cacheUpdate);
|
|
3376
3335
|
logger.debug("Cache written", { baseUrl });
|
|
@@ -3428,6 +3387,5 @@ process.on("uncaughtException", (error) => {
|
|
|
3428
3387
|
});
|
|
3429
3388
|
main().catch((error) => {
|
|
3430
3389
|
logger.error("Unhandled error in main", { error: String(error) });
|
|
3431
|
-
|
|
3432
|
-
process.exit(1);
|
|
3390
|
+
process.exit(0);
|
|
3433
3391
|
});
|