cc-api-statusline 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cc-api-statusline.js +286 -51
- package/package.json +1 -1
|
@@ -134,7 +134,7 @@ var DEFAULT_CONFIG = {
|
|
|
134
134
|
barSize: "medium",
|
|
135
135
|
barStyle: "classic",
|
|
136
136
|
separator: " | ",
|
|
137
|
-
maxWidth:
|
|
137
|
+
maxWidth: 100,
|
|
138
138
|
clockFormat: "24h"
|
|
139
139
|
},
|
|
140
140
|
components: {
|
|
@@ -209,6 +209,13 @@ function isCacheEntry(value) {
|
|
|
209
209
|
const c = value;
|
|
210
210
|
return typeof c["version"] === "number" && typeof c["provider"] === "string" && typeof c["baseUrl"] === "string" && typeof c["tokenHash"] === "string" && typeof c["configHash"] === "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");
|
|
211
211
|
}
|
|
212
|
+
var PROVIDER_DETECTION_TTL_SECONDS = 86400;
|
|
213
|
+
function isProviderDetectionCacheEntry(value) {
|
|
214
|
+
if (typeof value !== "object" || value === null)
|
|
215
|
+
return false;
|
|
216
|
+
const c = value;
|
|
217
|
+
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";
|
|
218
|
+
}
|
|
212
219
|
// src/services/cache.ts
|
|
213
220
|
function getCacheDir() {
|
|
214
221
|
const override = process.env["CC_API_STATUSLINE_CACHE_DIR"];
|
|
@@ -310,6 +317,58 @@ function getEffectivePollInterval(config, envOverride) {
|
|
|
310
317
|
const fromConfig = config.pollIntervalSeconds ?? DEFAULT_POLL_INTERVAL_SECONDS;
|
|
311
318
|
return Math.max(5, fromConfig);
|
|
312
319
|
}
|
|
320
|
+
function getProviderDetectionCachePath(baseUrl) {
|
|
321
|
+
const hash = shortHash(baseUrl, 12);
|
|
322
|
+
return join2(getCacheDir(), `provider-detect-${hash}.json`);
|
|
323
|
+
}
|
|
324
|
+
function readProviderDetectionCache(baseUrl) {
|
|
325
|
+
const path = getProviderDetectionCachePath(baseUrl);
|
|
326
|
+
if (!existsSync2(path)) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
const content = readFileSync2(path, "utf-8");
|
|
331
|
+
const data = JSON.parse(content);
|
|
332
|
+
if (!isProviderDetectionCacheEntry(data)) {
|
|
333
|
+
console.warn(`Invalid provider detection cache structure at ${path}`);
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
const detectedAt = new Date(data.detectedAt).getTime();
|
|
337
|
+
const now = Date.now();
|
|
338
|
+
const age = now - detectedAt;
|
|
339
|
+
const ttlMs = data.ttlSeconds * 1000;
|
|
340
|
+
if (age >= ttlMs) {
|
|
341
|
+
try {
|
|
342
|
+
unlinkSync(path);
|
|
343
|
+
} catch {}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
return data;
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.warn(`Failed to read provider detection cache from ${path}: ${error}`);
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function writeProviderDetectionCache(baseUrl, entry) {
|
|
353
|
+
const path = getProviderDetectionCachePath(baseUrl);
|
|
354
|
+
const tmpPath = `${path}.tmp`;
|
|
355
|
+
try {
|
|
356
|
+
ensureCacheDir();
|
|
357
|
+
const content = JSON.stringify(entry, null, 2);
|
|
358
|
+
writeFileSync(tmpPath, content, { encoding: "utf-8", mode: 384 });
|
|
359
|
+
try {
|
|
360
|
+
chmodSync(tmpPath, 384);
|
|
361
|
+
} catch {}
|
|
362
|
+
renameSync(tmpPath, path);
|
|
363
|
+
} catch (error) {
|
|
364
|
+
console.warn(`Failed to write provider detection cache to ${path}: ${error}`);
|
|
365
|
+
try {
|
|
366
|
+
if (existsSync2(tmpPath)) {
|
|
367
|
+
unlinkSync(tmpPath);
|
|
368
|
+
}
|
|
369
|
+
} catch {}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
313
372
|
|
|
314
373
|
// src/services/config.ts
|
|
315
374
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync3, renameSync as renameSync2, unlinkSync as unlinkSync2 } from "fs";
|
|
@@ -557,8 +616,13 @@ class Logger {
|
|
|
557
616
|
this.enabled = false;
|
|
558
617
|
}
|
|
559
618
|
}
|
|
619
|
+
formatLocalTimestamp() {
|
|
620
|
+
const d = new Date;
|
|
621
|
+
const pad = (n, len = 2) => n.toString().padStart(len, "0");
|
|
622
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
|
|
623
|
+
}
|
|
560
624
|
format(level, message, data) {
|
|
561
|
-
const timestamp =
|
|
625
|
+
const timestamp = this.formatLocalTimestamp();
|
|
562
626
|
const dataStr = data ? ` ${JSON.stringify(data)}` : "";
|
|
563
627
|
return `[${timestamp}] [${level.toUpperCase()}] ${message}${dataStr}
|
|
564
628
|
`;
|
|
@@ -634,21 +698,21 @@ function detectClaudeVersion() {
|
|
|
634
698
|
}
|
|
635
699
|
|
|
636
700
|
// src/providers/sub2api.ts
|
|
637
|
-
function
|
|
701
|
+
function computeNextMidnightLocal() {
|
|
638
702
|
const now = new Date;
|
|
639
|
-
const tomorrow = new Date(
|
|
703
|
+
const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 0, 0);
|
|
640
704
|
return tomorrow.toISOString();
|
|
641
705
|
}
|
|
642
|
-
function
|
|
706
|
+
function computeNextMondayLocal() {
|
|
643
707
|
const now = new Date;
|
|
644
|
-
const dayOfWeek = now.
|
|
708
|
+
const dayOfWeek = now.getDay();
|
|
645
709
|
const daysUntilMonday = dayOfWeek === 0 ? 1 : 8 - dayOfWeek;
|
|
646
|
-
const nextMonday = new Date(
|
|
710
|
+
const nextMonday = new Date(now.getFullYear(), now.getMonth(), now.getDate() + daysUntilMonday, 0, 0, 0, 0);
|
|
647
711
|
return nextMonday.toISOString();
|
|
648
712
|
}
|
|
649
|
-
function
|
|
713
|
+
function computeFirstOfNextMonthLocal() {
|
|
650
714
|
const now = new Date;
|
|
651
|
-
const nextMonth = new Date(
|
|
715
|
+
const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1, 0, 0, 0, 0);
|
|
652
716
|
return nextMonth.toISOString();
|
|
653
717
|
}
|
|
654
718
|
function mapPeriodTokens(data) {
|
|
@@ -667,14 +731,12 @@ function mapPeriodTokens(data) {
|
|
|
667
731
|
function createQuotaWindow(used, limit, resetsAt) {
|
|
668
732
|
if (used === undefined)
|
|
669
733
|
return null;
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
remaining = Math.max(0, actualLimit - used);
|
|
674
|
-
}
|
|
734
|
+
if (limit === null || limit === undefined)
|
|
735
|
+
return null;
|
|
736
|
+
const remaining = Math.max(0, limit - used);
|
|
675
737
|
return {
|
|
676
738
|
used,
|
|
677
|
-
limit
|
|
739
|
+
limit,
|
|
678
740
|
remaining,
|
|
679
741
|
resetsAt
|
|
680
742
|
};
|
|
@@ -730,9 +792,9 @@ async function fetchSub2api(baseUrl, token, config, timeoutMs = 5000) {
|
|
|
730
792
|
if (!sub) {
|
|
731
793
|
throw new Error("Subscription mode but no subscription object in response");
|
|
732
794
|
}
|
|
733
|
-
result.daily = createQuotaWindow(sub.daily_usage_usd, sub.daily_limit_usd,
|
|
734
|
-
result.weekly = createQuotaWindow(sub.weekly_usage_usd, sub.weekly_limit_usd,
|
|
735
|
-
result.monthly = createQuotaWindow(sub.monthly_usage_usd, sub.monthly_limit_usd,
|
|
795
|
+
result.daily = createQuotaWindow(sub.daily_usage_usd, sub.daily_limit_usd, computeNextMidnightLocal());
|
|
796
|
+
result.weekly = createQuotaWindow(sub.weekly_usage_usd, sub.weekly_limit_usd, computeNextMondayLocal());
|
|
797
|
+
result.monthly = createQuotaWindow(sub.monthly_usage_usd, sub.monthly_limit_usd, computeFirstOfNextMonthLocal());
|
|
736
798
|
result.resetsAt = computeSoonestReset(result);
|
|
737
799
|
}
|
|
738
800
|
if (data.usage) {
|
|
@@ -756,12 +818,12 @@ async function fetchSub2api(baseUrl, token, config, timeoutMs = 5000) {
|
|
|
756
818
|
used: 0,
|
|
757
819
|
limit: 0,
|
|
758
820
|
remaining: 0,
|
|
759
|
-
resetsAt:
|
|
821
|
+
resetsAt: computeNextMidnightLocal()
|
|
760
822
|
},
|
|
761
823
|
weekly: null,
|
|
762
824
|
monthly: null,
|
|
763
825
|
balance: null,
|
|
764
|
-
resetsAt:
|
|
826
|
+
resetsAt: computeNextMidnightLocal(),
|
|
765
827
|
tokenStats: null,
|
|
766
828
|
rateLimit: null
|
|
767
829
|
};
|
|
@@ -770,6 +832,44 @@ async function fetchSub2api(baseUrl, token, config, timeoutMs = 5000) {
|
|
|
770
832
|
}
|
|
771
833
|
}
|
|
772
834
|
|
|
835
|
+
// src/providers/health-probe.ts
|
|
836
|
+
function extractOrigin(baseUrl) {
|
|
837
|
+
try {
|
|
838
|
+
const url = new URL(baseUrl);
|
|
839
|
+
return url.origin;
|
|
840
|
+
} catch {
|
|
841
|
+
return baseUrl;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
async function probeHealth(baseUrl, timeoutMs = 1500) {
|
|
845
|
+
const origin = extractOrigin(baseUrl);
|
|
846
|
+
const healthUrl = `${origin}/health`;
|
|
847
|
+
logger.debug("Probing health endpoint", { healthUrl, timeoutMs });
|
|
848
|
+
try {
|
|
849
|
+
const responseText = await secureFetch(healthUrl, {
|
|
850
|
+
method: "GET",
|
|
851
|
+
headers: {
|
|
852
|
+
Accept: "application/json"
|
|
853
|
+
}
|
|
854
|
+
}, timeoutMs);
|
|
855
|
+
const data = JSON.parse(responseText);
|
|
856
|
+
logger.debug("Health probe response", { data });
|
|
857
|
+
if (typeof data["service"] === "string") {
|
|
858
|
+
logger.debug("Detected provider from service field", { provider: data["service"] });
|
|
859
|
+
return data["service"];
|
|
860
|
+
}
|
|
861
|
+
if (data["status"] === "ok") {
|
|
862
|
+
logger.debug("Detected sub2api from status: ok pattern");
|
|
863
|
+
return "sub2api";
|
|
864
|
+
}
|
|
865
|
+
logger.debug("Health probe returned unrecognized pattern", { data });
|
|
866
|
+
return null;
|
|
867
|
+
} catch (error) {
|
|
868
|
+
logger.debug("Health probe failed", { error: String(error) });
|
|
869
|
+
return null;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
773
873
|
// src/providers/claude-relay-service.ts
|
|
774
874
|
function computeWeeklyResetTime(resetDay, resetHour) {
|
|
775
875
|
const now = new Date;
|
|
@@ -785,20 +885,19 @@ function computeWeeklyResetTime(resetDay, resetHour) {
|
|
|
785
885
|
function createQuotaWindow2(used, limit, resetsAt) {
|
|
786
886
|
if (used === undefined)
|
|
787
887
|
return null;
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
remaining = Math.max(0, actualLimit - used);
|
|
792
|
-
}
|
|
888
|
+
if (!limit || limit <= 0)
|
|
889
|
+
return null;
|
|
890
|
+
const remaining = Math.max(0, limit - used);
|
|
793
891
|
return {
|
|
794
892
|
used,
|
|
795
|
-
limit
|
|
893
|
+
limit,
|
|
796
894
|
remaining,
|
|
797
895
|
resetsAt
|
|
798
896
|
};
|
|
799
897
|
}
|
|
800
898
|
async function fetchClaudeRelayService(baseUrl, token, config, timeoutMs = 5000) {
|
|
801
|
-
const
|
|
899
|
+
const origin = extractOrigin(baseUrl);
|
|
900
|
+
const url = `${origin}/apiStats/api/user-stats`;
|
|
802
901
|
const resolvedUA = resolveUserAgent(config.spoofClaudeCodeUA);
|
|
803
902
|
if (resolvedUA) {
|
|
804
903
|
logger.debug(`Using User-Agent: ${resolvedUA}`);
|
|
@@ -1100,7 +1199,7 @@ async function fetchCustom(baseUrl, token, appConfig, providerConfig, timeoutMs
|
|
|
1100
1199
|
|
|
1101
1200
|
// src/providers/autodetect.ts
|
|
1102
1201
|
var detectionCache = new Map;
|
|
1103
|
-
function
|
|
1202
|
+
function detectProviderFromUrlPattern(baseUrl, customProviders = {}) {
|
|
1104
1203
|
const normalizedUrl = baseUrl.toLowerCase().replace(/\/$/, "");
|
|
1105
1204
|
for (const [providerId, config] of Object.entries(customProviders)) {
|
|
1106
1205
|
if (config.urlPatterns && config.urlPatterns.length > 0) {
|
|
@@ -1112,25 +1211,71 @@ function detectProvider(baseUrl, customProviders = {}) {
|
|
|
1112
1211
|
}
|
|
1113
1212
|
}
|
|
1114
1213
|
}
|
|
1115
|
-
if (normalizedUrl.includes("/apistats") || normalizedUrl.includes("
|
|
1214
|
+
if (normalizedUrl.includes("/apistats") || normalizedUrl.includes("/api/user-stats")) {
|
|
1116
1215
|
return "claude-relay-service";
|
|
1117
1216
|
}
|
|
1118
1217
|
return "sub2api";
|
|
1119
1218
|
}
|
|
1120
|
-
function resolveProvider(baseUrl, providerOverride, customProviders = {}) {
|
|
1219
|
+
async function resolveProvider(baseUrl, providerOverride, customProviders = {}, probeTimeoutMs = 1500) {
|
|
1121
1220
|
if (providerOverride) {
|
|
1221
|
+
logger.debug("Provider override detected", { provider: providerOverride });
|
|
1122
1222
|
return providerOverride;
|
|
1123
1223
|
}
|
|
1124
1224
|
const cached = detectionCache.get(baseUrl);
|
|
1125
1225
|
if (cached) {
|
|
1226
|
+
logger.debug("Provider detection cache hit (memory)", { provider: cached.provider });
|
|
1126
1227
|
return cached.provider;
|
|
1127
1228
|
}
|
|
1128
|
-
const
|
|
1229
|
+
const diskCached = readProviderDetectionCache(baseUrl);
|
|
1230
|
+
if (diskCached) {
|
|
1231
|
+
logger.debug("Provider detection cache hit (disk)", {
|
|
1232
|
+
provider: diskCached.provider,
|
|
1233
|
+
detectedVia: diskCached.detectedVia
|
|
1234
|
+
});
|
|
1235
|
+
detectionCache.set(baseUrl, {
|
|
1236
|
+
provider: diskCached.provider,
|
|
1237
|
+
detectedAt: diskCached.detectedAt
|
|
1238
|
+
});
|
|
1239
|
+
return diskCached.provider;
|
|
1240
|
+
}
|
|
1241
|
+
for (const [providerId, config] of Object.entries(customProviders)) {
|
|
1242
|
+
if (config.urlPatterns && config.urlPatterns.length > 0) {
|
|
1243
|
+
const normalizedUrl = baseUrl.toLowerCase().replace(/\/$/, "");
|
|
1244
|
+
for (const pattern of config.urlPatterns) {
|
|
1245
|
+
const normalizedPattern = pattern.toLowerCase();
|
|
1246
|
+
if (normalizedUrl.includes(normalizedPattern)) {
|
|
1247
|
+
logger.debug("Provider detected via custom URL pattern", { provider: providerId, pattern });
|
|
1248
|
+
cacheProviderDetection(baseUrl, providerId, "url-pattern");
|
|
1249
|
+
return providerId;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
logger.debug("Attempting health probe", { baseUrl, timeoutMs: probeTimeoutMs });
|
|
1255
|
+
const probedProvider = await probeHealth(baseUrl, probeTimeoutMs);
|
|
1256
|
+
if (probedProvider) {
|
|
1257
|
+
logger.debug("Provider detected via health probe", { provider: probedProvider });
|
|
1258
|
+
cacheProviderDetection(baseUrl, probedProvider, "health-probe");
|
|
1259
|
+
return probedProvider;
|
|
1260
|
+
}
|
|
1261
|
+
const patternProvider = detectProviderFromUrlPattern(baseUrl, {});
|
|
1262
|
+
logger.debug("Provider detected via built-in URL pattern", { provider: patternProvider });
|
|
1263
|
+
cacheProviderDetection(baseUrl, patternProvider, "url-pattern");
|
|
1264
|
+
return patternProvider;
|
|
1265
|
+
}
|
|
1266
|
+
function cacheProviderDetection(baseUrl, provider, detectedVia) {
|
|
1267
|
+
const now = new Date().toISOString();
|
|
1129
1268
|
detectionCache.set(baseUrl, {
|
|
1130
1269
|
provider,
|
|
1131
|
-
detectedAt:
|
|
1270
|
+
detectedAt: now
|
|
1271
|
+
});
|
|
1272
|
+
writeProviderDetectionCache(baseUrl, {
|
|
1273
|
+
baseUrl,
|
|
1274
|
+
provider,
|
|
1275
|
+
detectedVia,
|
|
1276
|
+
detectedAt: now,
|
|
1277
|
+
ttlSeconds: PROVIDER_DETECTION_TTL_SECONDS
|
|
1132
1278
|
});
|
|
1133
|
-
return provider;
|
|
1134
1279
|
}
|
|
1135
1280
|
|
|
1136
1281
|
// src/providers/index.ts
|
|
@@ -1433,10 +1578,10 @@ function renderCountdown(resetsAt, config, clockFormat) {
|
|
|
1433
1578
|
if (format === "auto") {
|
|
1434
1579
|
if (remainingMs < 60000) {
|
|
1435
1580
|
timeStr = "now";
|
|
1436
|
-
} else if (remainingMs <=
|
|
1581
|
+
} else if (remainingMs <= 604800000) {
|
|
1437
1582
|
timeStr = formatDuration(remainingMs);
|
|
1438
1583
|
} else {
|
|
1439
|
-
timeStr =
|
|
1584
|
+
timeStr = formatDateOnly(resetDate);
|
|
1440
1585
|
}
|
|
1441
1586
|
} else if (format === "duration") {
|
|
1442
1587
|
if (remainingMs < 60000) {
|
|
@@ -1459,11 +1604,23 @@ function formatDuration(ms) {
|
|
|
1459
1604
|
return `${days}d ${remainingHours}h`;
|
|
1460
1605
|
} else if (hours >= 1) {
|
|
1461
1606
|
const remainingMinutes = minutes % 60;
|
|
1462
|
-
return `${hours}h${remainingMinutes}m`;
|
|
1607
|
+
return `${hours}h ${remainingMinutes}m`;
|
|
1463
1608
|
} else {
|
|
1464
1609
|
return `${minutes}m`;
|
|
1465
1610
|
}
|
|
1466
1611
|
}
|
|
1612
|
+
function formatDateOnly(date) {
|
|
1613
|
+
const now = new Date;
|
|
1614
|
+
const dayOfWeek = date.toLocaleDateString("en-US", { weekday: "short" });
|
|
1615
|
+
const month = date.toLocaleDateString("en-US", { month: "short" });
|
|
1616
|
+
const dayOfMonth = date.getDate();
|
|
1617
|
+
const isSameMonth = date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth();
|
|
1618
|
+
if (isSameMonth) {
|
|
1619
|
+
return `${dayOfWeek} ${dayOfMonth}`;
|
|
1620
|
+
} else {
|
|
1621
|
+
return `${month} ${dayOfMonth}`;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1467
1624
|
function formatWallClock(date, clockFormat) {
|
|
1468
1625
|
const now = new Date;
|
|
1469
1626
|
const dayOfWeek = date.toLocaleDateString("en-US", { weekday: "short" });
|
|
@@ -1715,7 +1872,14 @@ function getTerminalWidth() {
|
|
|
1715
1872
|
if (process.stdout.columns && process.stdout.columns > 0) {
|
|
1716
1873
|
return process.stdout.columns;
|
|
1717
1874
|
}
|
|
1718
|
-
|
|
1875
|
+
const colsOverride = process.env["CC_STATUSLINE_COLS"];
|
|
1876
|
+
if (colsOverride) {
|
|
1877
|
+
const parsed = parseInt(colsOverride, 10);
|
|
1878
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
1879
|
+
return parsed;
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
return 200;
|
|
1719
1883
|
}
|
|
1720
1884
|
function computeMaxWidth(termWidth, maxWidthPct) {
|
|
1721
1885
|
const pct = Math.max(20, Math.min(100, maxWidthPct));
|
|
@@ -1756,6 +1920,16 @@ function ansiAwareTruncate(text, maxWidth) {
|
|
|
1756
1920
|
}
|
|
1757
1921
|
return output + "…";
|
|
1758
1922
|
}
|
|
1923
|
+
var COMPONENT_DROP_PRIORITY = [
|
|
1924
|
+
"plan",
|
|
1925
|
+
"tokens",
|
|
1926
|
+
"rateLimit",
|
|
1927
|
+
"monthly",
|
|
1928
|
+
"countdown",
|
|
1929
|
+
"weekly",
|
|
1930
|
+
"daily",
|
|
1931
|
+
"balance"
|
|
1932
|
+
];
|
|
1759
1933
|
|
|
1760
1934
|
// src/renderer/index.ts
|
|
1761
1935
|
var DEFAULT_COMPONENT_ORDER2 = [
|
|
@@ -1769,7 +1943,7 @@ var DEFAULT_COMPONENT_ORDER2 = [
|
|
|
1769
1943
|
];
|
|
1770
1944
|
function renderStatusline(data, config, errorState, cacheAge) {
|
|
1771
1945
|
const componentOrder = getComponentOrder(config);
|
|
1772
|
-
const
|
|
1946
|
+
const componentMap = new Map;
|
|
1773
1947
|
for (const componentId of componentOrder) {
|
|
1774
1948
|
const componentConfig = config.components[componentId];
|
|
1775
1949
|
if (componentConfig === false) {
|
|
@@ -1777,10 +1951,38 @@ function renderStatusline(data, config, errorState, cacheAge) {
|
|
|
1777
1951
|
}
|
|
1778
1952
|
const rendered = renderComponent(componentId, data, componentConfig === true || componentConfig === undefined ? {} : componentConfig, config);
|
|
1779
1953
|
if (rendered !== null) {
|
|
1780
|
-
|
|
1954
|
+
componentMap.set(componentId, rendered);
|
|
1781
1955
|
}
|
|
1782
1956
|
}
|
|
1957
|
+
const termWidth = getTerminalWidth();
|
|
1958
|
+
const maxWidth = computeMaxWidth(termWidth, config.display.maxWidth ?? 100);
|
|
1783
1959
|
const separator = config.display.separator ?? " | ";
|
|
1960
|
+
const activeComponents = new Set(componentMap.keys());
|
|
1961
|
+
let currentWidth = calculateStatuslineWidth(componentMap, activeComponents, componentOrder, separator, errorState, data, cacheAge);
|
|
1962
|
+
for (const dropCandidate of COMPONENT_DROP_PRIORITY) {
|
|
1963
|
+
if (dropCandidate === "countdown") {
|
|
1964
|
+
continue;
|
|
1965
|
+
}
|
|
1966
|
+
if (currentWidth <= maxWidth) {
|
|
1967
|
+
break;
|
|
1968
|
+
}
|
|
1969
|
+
if (activeComponents.size <= 1) {
|
|
1970
|
+
break;
|
|
1971
|
+
}
|
|
1972
|
+
if (activeComponents.has(dropCandidate)) {
|
|
1973
|
+
activeComponents.delete(dropCandidate);
|
|
1974
|
+
currentWidth = calculateStatuslineWidth(componentMap, activeComponents, componentOrder, separator, errorState, data, cacheAge);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
const renderedComponents = [];
|
|
1978
|
+
for (const componentId of componentOrder) {
|
|
1979
|
+
if (activeComponents.has(componentId)) {
|
|
1980
|
+
const rendered = componentMap.get(componentId);
|
|
1981
|
+
if (rendered) {
|
|
1982
|
+
renderedComponents.push(rendered);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1784
1986
|
let statusline = renderedComponents.join(separator);
|
|
1785
1987
|
if (errorState) {
|
|
1786
1988
|
const isTransition = errorState === "switching-provider" || errorState === "new-credentials" || errorState === "new-endpoint" || errorState === "auth-error-waiting";
|
|
@@ -1797,11 +1999,37 @@ function renderStatusline(data, config, errorState, cacheAge) {
|
|
|
1797
1999
|
}
|
|
1798
2000
|
}
|
|
1799
2001
|
}
|
|
1800
|
-
const termWidth = getTerminalWidth();
|
|
1801
|
-
const maxWidth = computeMaxWidth(termWidth, config.display.maxWidth ?? 80);
|
|
1802
2002
|
statusline = ansiAwareTruncate(statusline, maxWidth);
|
|
1803
2003
|
return statusline;
|
|
1804
2004
|
}
|
|
2005
|
+
function calculateStatuslineWidth(componentMap, activeComponents, componentOrder, separator, errorState, data, cacheAge) {
|
|
2006
|
+
const components = [];
|
|
2007
|
+
for (const id of componentOrder) {
|
|
2008
|
+
if (activeComponents.has(id)) {
|
|
2009
|
+
const rendered = componentMap.get(id);
|
|
2010
|
+
if (rendered) {
|
|
2011
|
+
components.push(rendered);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
let statusline = components.join(separator);
|
|
2016
|
+
if (errorState) {
|
|
2017
|
+
const isTransition = errorState === "switching-provider" || errorState === "new-credentials" || errorState === "new-endpoint" || errorState === "auth-error-waiting";
|
|
2018
|
+
if (isTransition) {
|
|
2019
|
+
statusline = renderError(errorState, "with-cache", data.provider, undefined, cacheAge);
|
|
2020
|
+
} else {
|
|
2021
|
+
const hasCache = components.length > 0;
|
|
2022
|
+
const errorMode = hasCache ? "with-cache" : "without-cache";
|
|
2023
|
+
const errorIndicator = renderError(errorState, errorMode, data.provider, undefined, cacheAge);
|
|
2024
|
+
if (hasCache) {
|
|
2025
|
+
statusline = `${statusline} ${errorIndicator}`;
|
|
2026
|
+
} else {
|
|
2027
|
+
statusline = errorIndicator;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
return visibleLength(statusline);
|
|
2032
|
+
}
|
|
1805
2033
|
function getComponentOrder(config) {
|
|
1806
2034
|
const explicitOrder = [];
|
|
1807
2035
|
const explicitSet = new Set;
|
|
@@ -1878,7 +2106,7 @@ async function executeCycle(ctx) {
|
|
|
1878
2106
|
if (!baseUrl || !authToken) {
|
|
1879
2107
|
return {
|
|
1880
2108
|
output: renderError("missing-env", "without-cache"),
|
|
1881
|
-
exitCode:
|
|
2109
|
+
exitCode: 0,
|
|
1882
2110
|
cacheUpdate: null
|
|
1883
2111
|
};
|
|
1884
2112
|
}
|
|
@@ -1898,7 +2126,8 @@ async function executeCycle(ctx) {
|
|
|
1898
2126
|
ttlSeconds,
|
|
1899
2127
|
data,
|
|
1900
2128
|
renderedLine: statusline,
|
|
1901
|
-
configHash
|
|
2129
|
+
configHash,
|
|
2130
|
+
errorState: null
|
|
1902
2131
|
};
|
|
1903
2132
|
return {
|
|
1904
2133
|
output: statusline,
|
|
@@ -1921,7 +2150,7 @@ async function executeCycle(ctx) {
|
|
|
1921
2150
|
const errorOutput = renderError("network-error", "without-cache", providerId);
|
|
1922
2151
|
return {
|
|
1923
2152
|
output: errorOutput,
|
|
1924
|
-
exitCode:
|
|
2153
|
+
exitCode: 0,
|
|
1925
2154
|
cacheUpdate: null
|
|
1926
2155
|
};
|
|
1927
2156
|
}
|
|
@@ -1997,7 +2226,7 @@ function uninstallStatusLine() {
|
|
|
1997
2226
|
// package.json
|
|
1998
2227
|
var package_default = {
|
|
1999
2228
|
name: "cc-api-statusline",
|
|
2000
|
-
version: "0.1.
|
|
2229
|
+
version: "0.1.3",
|
|
2001
2230
|
description: "Claude Code statusline tool that polls API usage from third-party proxy backends",
|
|
2002
2231
|
type: "module",
|
|
2003
2232
|
bin: {
|
|
@@ -2192,7 +2421,7 @@ async function main() {
|
|
|
2192
2421
|
if (envError) {
|
|
2193
2422
|
const errorOutput = renderError("missing-env", "without-cache");
|
|
2194
2423
|
process.stdout.write(errorOutput);
|
|
2195
|
-
process.exit(
|
|
2424
|
+
process.exit(0);
|
|
2196
2425
|
}
|
|
2197
2426
|
const baseUrl = env.baseUrl;
|
|
2198
2427
|
const authToken = env.authToken;
|
|
@@ -2203,14 +2432,15 @@ async function main() {
|
|
|
2203
2432
|
const configPath = getConfigPath(args.configPath);
|
|
2204
2433
|
const configHash = computeConfigHash(configPath);
|
|
2205
2434
|
logger.debug("Config loaded", { configPath, configHash });
|
|
2206
|
-
const
|
|
2435
|
+
const probeTimeout = isPiped ? Math.min(1500, Math.max(200, Number(process.env["CC_STATUSLINE_TIMEOUT"] ?? 1000) - 200)) : 3000;
|
|
2436
|
+
const providerId = await resolveProvider(baseUrl, env.providerOverride, config.customProviders ?? {}, probeTimeout);
|
|
2207
2437
|
const provider = getProvider(providerId, config.customProviders ?? {});
|
|
2208
|
-
logger.debug("Provider resolved", { providerId });
|
|
2438
|
+
logger.debug("Provider resolved", { providerId, probeTimeout });
|
|
2209
2439
|
if (!provider) {
|
|
2210
2440
|
logger.error("Provider not found", { providerId });
|
|
2211
2441
|
const errorOutput = renderError("provider-unknown", "without-cache");
|
|
2212
2442
|
process.stdout.write(errorOutput);
|
|
2213
|
-
process.exit(
|
|
2443
|
+
process.exit(0);
|
|
2214
2444
|
}
|
|
2215
2445
|
const cachedEntry = readCache(baseUrl);
|
|
2216
2446
|
logger.debug("Cache read", {
|
|
@@ -2239,12 +2469,17 @@ async function main() {
|
|
|
2239
2469
|
outputLength: result.output.length,
|
|
2240
2470
|
cacheUpdate: !!result.cacheUpdate
|
|
2241
2471
|
});
|
|
2472
|
+
let output = result.output;
|
|
2473
|
+
if (!output || output.trim().length === 0) {
|
|
2474
|
+
output = "[loading...]";
|
|
2475
|
+
logger.debug("Empty output detected, using fallback");
|
|
2476
|
+
}
|
|
2242
2477
|
if (isPiped) {
|
|
2243
|
-
const formatted = "\x1B[0m" +
|
|
2478
|
+
const formatted = "\x1B[0m" + output.replace(/ /g, " ");
|
|
2244
2479
|
process.stdout.write(formatted);
|
|
2245
2480
|
logger.debug("Output formatted for piped mode (ANSI reset + NBSP)");
|
|
2246
2481
|
} else {
|
|
2247
|
-
process.stdout.write(
|
|
2482
|
+
process.stdout.write(output);
|
|
2248
2483
|
logger.debug("Output written (TTY mode)");
|
|
2249
2484
|
}
|
|
2250
2485
|
if (result.cacheUpdate) {
|