cc-api-statusline 0.1.2 → 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 +163 -22
- package/package.json +1 -1
|
@@ -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";
|
|
@@ -672,14 +731,12 @@ function mapPeriodTokens(data) {
|
|
|
672
731
|
function createQuotaWindow(used, limit, resetsAt) {
|
|
673
732
|
if (used === undefined)
|
|
674
733
|
return null;
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
remaining = Math.max(0, actualLimit - used);
|
|
679
|
-
}
|
|
734
|
+
if (limit === null || limit === undefined)
|
|
735
|
+
return null;
|
|
736
|
+
const remaining = Math.max(0, limit - used);
|
|
680
737
|
return {
|
|
681
738
|
used,
|
|
682
|
-
limit
|
|
739
|
+
limit,
|
|
683
740
|
remaining,
|
|
684
741
|
resetsAt
|
|
685
742
|
};
|
|
@@ -775,6 +832,44 @@ async function fetchSub2api(baseUrl, token, config, timeoutMs = 5000) {
|
|
|
775
832
|
}
|
|
776
833
|
}
|
|
777
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
|
+
|
|
778
873
|
// src/providers/claude-relay-service.ts
|
|
779
874
|
function computeWeeklyResetTime(resetDay, resetHour) {
|
|
780
875
|
const now = new Date;
|
|
@@ -790,20 +885,19 @@ function computeWeeklyResetTime(resetDay, resetHour) {
|
|
|
790
885
|
function createQuotaWindow2(used, limit, resetsAt) {
|
|
791
886
|
if (used === undefined)
|
|
792
887
|
return null;
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
remaining = Math.max(0, actualLimit - used);
|
|
797
|
-
}
|
|
888
|
+
if (!limit || limit <= 0)
|
|
889
|
+
return null;
|
|
890
|
+
const remaining = Math.max(0, limit - used);
|
|
798
891
|
return {
|
|
799
892
|
used,
|
|
800
|
-
limit
|
|
893
|
+
limit,
|
|
801
894
|
remaining,
|
|
802
895
|
resetsAt
|
|
803
896
|
};
|
|
804
897
|
}
|
|
805
898
|
async function fetchClaudeRelayService(baseUrl, token, config, timeoutMs = 5000) {
|
|
806
|
-
const
|
|
899
|
+
const origin = extractOrigin(baseUrl);
|
|
900
|
+
const url = `${origin}/apiStats/api/user-stats`;
|
|
807
901
|
const resolvedUA = resolveUserAgent(config.spoofClaudeCodeUA);
|
|
808
902
|
if (resolvedUA) {
|
|
809
903
|
logger.debug(`Using User-Agent: ${resolvedUA}`);
|
|
@@ -1105,7 +1199,7 @@ async function fetchCustom(baseUrl, token, appConfig, providerConfig, timeoutMs
|
|
|
1105
1199
|
|
|
1106
1200
|
// src/providers/autodetect.ts
|
|
1107
1201
|
var detectionCache = new Map;
|
|
1108
|
-
function
|
|
1202
|
+
function detectProviderFromUrlPattern(baseUrl, customProviders = {}) {
|
|
1109
1203
|
const normalizedUrl = baseUrl.toLowerCase().replace(/\/$/, "");
|
|
1110
1204
|
for (const [providerId, config] of Object.entries(customProviders)) {
|
|
1111
1205
|
if (config.urlPatterns && config.urlPatterns.length > 0) {
|
|
@@ -1117,25 +1211,71 @@ function detectProvider(baseUrl, customProviders = {}) {
|
|
|
1117
1211
|
}
|
|
1118
1212
|
}
|
|
1119
1213
|
}
|
|
1120
|
-
if (normalizedUrl.includes("/apistats") || normalizedUrl.includes("
|
|
1214
|
+
if (normalizedUrl.includes("/apistats") || normalizedUrl.includes("/api/user-stats")) {
|
|
1121
1215
|
return "claude-relay-service";
|
|
1122
1216
|
}
|
|
1123
1217
|
return "sub2api";
|
|
1124
1218
|
}
|
|
1125
|
-
function resolveProvider(baseUrl, providerOverride, customProviders = {}) {
|
|
1219
|
+
async function resolveProvider(baseUrl, providerOverride, customProviders = {}, probeTimeoutMs = 1500) {
|
|
1126
1220
|
if (providerOverride) {
|
|
1221
|
+
logger.debug("Provider override detected", { provider: providerOverride });
|
|
1127
1222
|
return providerOverride;
|
|
1128
1223
|
}
|
|
1129
1224
|
const cached = detectionCache.get(baseUrl);
|
|
1130
1225
|
if (cached) {
|
|
1226
|
+
logger.debug("Provider detection cache hit (memory)", { provider: cached.provider });
|
|
1131
1227
|
return cached.provider;
|
|
1132
1228
|
}
|
|
1133
|
-
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();
|
|
1134
1268
|
detectionCache.set(baseUrl, {
|
|
1135
1269
|
provider,
|
|
1136
|
-
detectedAt:
|
|
1270
|
+
detectedAt: now
|
|
1271
|
+
});
|
|
1272
|
+
writeProviderDetectionCache(baseUrl, {
|
|
1273
|
+
baseUrl,
|
|
1274
|
+
provider,
|
|
1275
|
+
detectedVia,
|
|
1276
|
+
detectedAt: now,
|
|
1277
|
+
ttlSeconds: PROVIDER_DETECTION_TTL_SECONDS
|
|
1137
1278
|
});
|
|
1138
|
-
return provider;
|
|
1139
1279
|
}
|
|
1140
1280
|
|
|
1141
1281
|
// src/providers/index.ts
|
|
@@ -2086,7 +2226,7 @@ function uninstallStatusLine() {
|
|
|
2086
2226
|
// package.json
|
|
2087
2227
|
var package_default = {
|
|
2088
2228
|
name: "cc-api-statusline",
|
|
2089
|
-
version: "0.1.
|
|
2229
|
+
version: "0.1.3",
|
|
2090
2230
|
description: "Claude Code statusline tool that polls API usage from third-party proxy backends",
|
|
2091
2231
|
type: "module",
|
|
2092
2232
|
bin: {
|
|
@@ -2292,9 +2432,10 @@ async function main() {
|
|
|
2292
2432
|
const configPath = getConfigPath(args.configPath);
|
|
2293
2433
|
const configHash = computeConfigHash(configPath);
|
|
2294
2434
|
logger.debug("Config loaded", { configPath, configHash });
|
|
2295
|
-
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);
|
|
2296
2437
|
const provider = getProvider(providerId, config.customProviders ?? {});
|
|
2297
|
-
logger.debug("Provider resolved", { providerId });
|
|
2438
|
+
logger.debug("Provider resolved", { providerId, probeTimeout });
|
|
2298
2439
|
if (!provider) {
|
|
2299
2440
|
logger.error("Provider not found", { providerId });
|
|
2300
2441
|
const errorOutput = renderError("provider-unknown", "without-cache");
|