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.
@@ -134,7 +134,7 @@ var DEFAULT_CONFIG = {
134
134
  barSize: "medium",
135
135
  barStyle: "classic",
136
136
  separator: " | ",
137
- maxWidth: 80,
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 = new Date().toISOString();
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 computeNextMidnightUTC() {
701
+ function computeNextMidnightLocal() {
638
702
  const now = new Date;
639
- const tomorrow = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1, 0, 0, 0, 0));
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 computeNextMondayUTC() {
706
+ function computeNextMondayLocal() {
643
707
  const now = new Date;
644
- const dayOfWeek = now.getUTCDay();
708
+ const dayOfWeek = now.getDay();
645
709
  const daysUntilMonday = dayOfWeek === 0 ? 1 : 8 - dayOfWeek;
646
- const nextMonday = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + daysUntilMonday, 0, 0, 0, 0));
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 computeFirstOfNextMonthUTC() {
713
+ function computeFirstOfNextMonthLocal() {
650
714
  const now = new Date;
651
- const nextMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1, 0, 0, 0, 0));
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
- const actualLimit = limit === null || limit === undefined ? null : limit;
671
- let remaining = null;
672
- if (actualLimit !== null) {
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: actualLimit,
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, computeNextMidnightUTC());
734
- result.weekly = createQuotaWindow(sub.weekly_usage_usd, sub.weekly_limit_usd, computeNextMondayUTC());
735
- result.monthly = createQuotaWindow(sub.monthly_usage_usd, sub.monthly_limit_usd, computeFirstOfNextMonthUTC());
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: computeNextMidnightUTC()
821
+ resetsAt: computeNextMidnightLocal()
760
822
  },
761
823
  weekly: null,
762
824
  monthly: null,
763
825
  balance: null,
764
- resetsAt: computeNextMidnightUTC(),
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
- const actualLimit = limit && limit > 0 ? limit : null;
789
- let remaining = null;
790
- if (actualLimit !== null) {
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: actualLimit,
893
+ limit,
796
894
  remaining,
797
895
  resetsAt
798
896
  };
799
897
  }
800
898
  async function fetchClaudeRelayService(baseUrl, token, config, timeoutMs = 5000) {
801
- const url = `${baseUrl}/apiStats/api/user-stats`;
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 detectProvider(baseUrl, customProviders = {}) {
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("relay") || normalizedUrl.includes("/api/user-stats")) {
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 provider = detectProvider(baseUrl, customProviders);
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: new Date().toISOString()
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 <= 86400000) {
1581
+ } else if (remainingMs <= 604800000) {
1437
1582
  timeStr = formatDuration(remainingMs);
1438
1583
  } else {
1439
- timeStr = formatWallClock(resetDate, clockFormat);
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
- return 80;
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 renderedComponents = [];
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
- renderedComponents.push(rendered);
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: 1,
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: 1,
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.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(1);
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 providerId = resolveProvider(baseUrl, env.providerOverride, config.customProviders ?? {});
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(1);
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" + result.output.replace(/ /g, " ");
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(result.output);
2482
+ process.stdout.write(output);
2248
2483
  logger.debug("Output written (TTY mode)");
2249
2484
  }
2250
2485
  if (result.cacheUpdate) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-api-statusline",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Claude Code statusline tool that polls API usage from third-party proxy backends",
5
5
  "type": "module",
6
6
  "bin": {