cc-api-statusline 0.1.1 → 0.1.2

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: {
@@ -557,8 +557,13 @@ class Logger {
557
557
  this.enabled = false;
558
558
  }
559
559
  }
560
+ formatLocalTimestamp() {
561
+ const d = new Date;
562
+ const pad = (n, len = 2) => n.toString().padStart(len, "0");
563
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${pad(d.getMilliseconds(), 3)}`;
564
+ }
560
565
  format(level, message, data) {
561
- const timestamp = new Date().toISOString();
566
+ const timestamp = this.formatLocalTimestamp();
562
567
  const dataStr = data ? ` ${JSON.stringify(data)}` : "";
563
568
  return `[${timestamp}] [${level.toUpperCase()}] ${message}${dataStr}
564
569
  `;
@@ -634,21 +639,21 @@ function detectClaudeVersion() {
634
639
  }
635
640
 
636
641
  // src/providers/sub2api.ts
637
- function computeNextMidnightUTC() {
642
+ function computeNextMidnightLocal() {
638
643
  const now = new Date;
639
- const tomorrow = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1, 0, 0, 0, 0));
644
+ const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 0, 0);
640
645
  return tomorrow.toISOString();
641
646
  }
642
- function computeNextMondayUTC() {
647
+ function computeNextMondayLocal() {
643
648
  const now = new Date;
644
- const dayOfWeek = now.getUTCDay();
649
+ const dayOfWeek = now.getDay();
645
650
  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));
651
+ const nextMonday = new Date(now.getFullYear(), now.getMonth(), now.getDate() + daysUntilMonday, 0, 0, 0, 0);
647
652
  return nextMonday.toISOString();
648
653
  }
649
- function computeFirstOfNextMonthUTC() {
654
+ function computeFirstOfNextMonthLocal() {
650
655
  const now = new Date;
651
- const nextMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1, 0, 0, 0, 0));
656
+ const nextMonth = new Date(now.getFullYear(), now.getMonth() + 1, 1, 0, 0, 0, 0);
652
657
  return nextMonth.toISOString();
653
658
  }
654
659
  function mapPeriodTokens(data) {
@@ -730,9 +735,9 @@ async function fetchSub2api(baseUrl, token, config, timeoutMs = 5000) {
730
735
  if (!sub) {
731
736
  throw new Error("Subscription mode but no subscription object in response");
732
737
  }
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());
738
+ result.daily = createQuotaWindow(sub.daily_usage_usd, sub.daily_limit_usd, computeNextMidnightLocal());
739
+ result.weekly = createQuotaWindow(sub.weekly_usage_usd, sub.weekly_limit_usd, computeNextMondayLocal());
740
+ result.monthly = createQuotaWindow(sub.monthly_usage_usd, sub.monthly_limit_usd, computeFirstOfNextMonthLocal());
736
741
  result.resetsAt = computeSoonestReset(result);
737
742
  }
738
743
  if (data.usage) {
@@ -756,12 +761,12 @@ async function fetchSub2api(baseUrl, token, config, timeoutMs = 5000) {
756
761
  used: 0,
757
762
  limit: 0,
758
763
  remaining: 0,
759
- resetsAt: computeNextMidnightUTC()
764
+ resetsAt: computeNextMidnightLocal()
760
765
  },
761
766
  weekly: null,
762
767
  monthly: null,
763
768
  balance: null,
764
- resetsAt: computeNextMidnightUTC(),
769
+ resetsAt: computeNextMidnightLocal(),
765
770
  tokenStats: null,
766
771
  rateLimit: null
767
772
  };
@@ -1433,10 +1438,10 @@ function renderCountdown(resetsAt, config, clockFormat) {
1433
1438
  if (format === "auto") {
1434
1439
  if (remainingMs < 60000) {
1435
1440
  timeStr = "now";
1436
- } else if (remainingMs <= 86400000) {
1441
+ } else if (remainingMs <= 604800000) {
1437
1442
  timeStr = formatDuration(remainingMs);
1438
1443
  } else {
1439
- timeStr = formatWallClock(resetDate, clockFormat);
1444
+ timeStr = formatDateOnly(resetDate);
1440
1445
  }
1441
1446
  } else if (format === "duration") {
1442
1447
  if (remainingMs < 60000) {
@@ -1459,11 +1464,23 @@ function formatDuration(ms) {
1459
1464
  return `${days}d ${remainingHours}h`;
1460
1465
  } else if (hours >= 1) {
1461
1466
  const remainingMinutes = minutes % 60;
1462
- return `${hours}h${remainingMinutes}m`;
1467
+ return `${hours}h ${remainingMinutes}m`;
1463
1468
  } else {
1464
1469
  return `${minutes}m`;
1465
1470
  }
1466
1471
  }
1472
+ function formatDateOnly(date) {
1473
+ const now = new Date;
1474
+ const dayOfWeek = date.toLocaleDateString("en-US", { weekday: "short" });
1475
+ const month = date.toLocaleDateString("en-US", { month: "short" });
1476
+ const dayOfMonth = date.getDate();
1477
+ const isSameMonth = date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth();
1478
+ if (isSameMonth) {
1479
+ return `${dayOfWeek} ${dayOfMonth}`;
1480
+ } else {
1481
+ return `${month} ${dayOfMonth}`;
1482
+ }
1483
+ }
1467
1484
  function formatWallClock(date, clockFormat) {
1468
1485
  const now = new Date;
1469
1486
  const dayOfWeek = date.toLocaleDateString("en-US", { weekday: "short" });
@@ -1715,7 +1732,14 @@ function getTerminalWidth() {
1715
1732
  if (process.stdout.columns && process.stdout.columns > 0) {
1716
1733
  return process.stdout.columns;
1717
1734
  }
1718
- return 80;
1735
+ const colsOverride = process.env["CC_STATUSLINE_COLS"];
1736
+ if (colsOverride) {
1737
+ const parsed = parseInt(colsOverride, 10);
1738
+ if (!isNaN(parsed) && parsed > 0) {
1739
+ return parsed;
1740
+ }
1741
+ }
1742
+ return 200;
1719
1743
  }
1720
1744
  function computeMaxWidth(termWidth, maxWidthPct) {
1721
1745
  const pct = Math.max(20, Math.min(100, maxWidthPct));
@@ -1756,6 +1780,16 @@ function ansiAwareTruncate(text, maxWidth) {
1756
1780
  }
1757
1781
  return output + "…";
1758
1782
  }
1783
+ var COMPONENT_DROP_PRIORITY = [
1784
+ "plan",
1785
+ "tokens",
1786
+ "rateLimit",
1787
+ "monthly",
1788
+ "countdown",
1789
+ "weekly",
1790
+ "daily",
1791
+ "balance"
1792
+ ];
1759
1793
 
1760
1794
  // src/renderer/index.ts
1761
1795
  var DEFAULT_COMPONENT_ORDER2 = [
@@ -1769,7 +1803,7 @@ var DEFAULT_COMPONENT_ORDER2 = [
1769
1803
  ];
1770
1804
  function renderStatusline(data, config, errorState, cacheAge) {
1771
1805
  const componentOrder = getComponentOrder(config);
1772
- const renderedComponents = [];
1806
+ const componentMap = new Map;
1773
1807
  for (const componentId of componentOrder) {
1774
1808
  const componentConfig = config.components[componentId];
1775
1809
  if (componentConfig === false) {
@@ -1777,10 +1811,38 @@ function renderStatusline(data, config, errorState, cacheAge) {
1777
1811
  }
1778
1812
  const rendered = renderComponent(componentId, data, componentConfig === true || componentConfig === undefined ? {} : componentConfig, config);
1779
1813
  if (rendered !== null) {
1780
- renderedComponents.push(rendered);
1814
+ componentMap.set(componentId, rendered);
1781
1815
  }
1782
1816
  }
1817
+ const termWidth = getTerminalWidth();
1818
+ const maxWidth = computeMaxWidth(termWidth, config.display.maxWidth ?? 100);
1783
1819
  const separator = config.display.separator ?? " | ";
1820
+ const activeComponents = new Set(componentMap.keys());
1821
+ let currentWidth = calculateStatuslineWidth(componentMap, activeComponents, componentOrder, separator, errorState, data, cacheAge);
1822
+ for (const dropCandidate of COMPONENT_DROP_PRIORITY) {
1823
+ if (dropCandidate === "countdown") {
1824
+ continue;
1825
+ }
1826
+ if (currentWidth <= maxWidth) {
1827
+ break;
1828
+ }
1829
+ if (activeComponents.size <= 1) {
1830
+ break;
1831
+ }
1832
+ if (activeComponents.has(dropCandidate)) {
1833
+ activeComponents.delete(dropCandidate);
1834
+ currentWidth = calculateStatuslineWidth(componentMap, activeComponents, componentOrder, separator, errorState, data, cacheAge);
1835
+ }
1836
+ }
1837
+ const renderedComponents = [];
1838
+ for (const componentId of componentOrder) {
1839
+ if (activeComponents.has(componentId)) {
1840
+ const rendered = componentMap.get(componentId);
1841
+ if (rendered) {
1842
+ renderedComponents.push(rendered);
1843
+ }
1844
+ }
1845
+ }
1784
1846
  let statusline = renderedComponents.join(separator);
1785
1847
  if (errorState) {
1786
1848
  const isTransition = errorState === "switching-provider" || errorState === "new-credentials" || errorState === "new-endpoint" || errorState === "auth-error-waiting";
@@ -1797,11 +1859,37 @@ function renderStatusline(data, config, errorState, cacheAge) {
1797
1859
  }
1798
1860
  }
1799
1861
  }
1800
- const termWidth = getTerminalWidth();
1801
- const maxWidth = computeMaxWidth(termWidth, config.display.maxWidth ?? 80);
1802
1862
  statusline = ansiAwareTruncate(statusline, maxWidth);
1803
1863
  return statusline;
1804
1864
  }
1865
+ function calculateStatuslineWidth(componentMap, activeComponents, componentOrder, separator, errorState, data, cacheAge) {
1866
+ const components = [];
1867
+ for (const id of componentOrder) {
1868
+ if (activeComponents.has(id)) {
1869
+ const rendered = componentMap.get(id);
1870
+ if (rendered) {
1871
+ components.push(rendered);
1872
+ }
1873
+ }
1874
+ }
1875
+ let statusline = components.join(separator);
1876
+ if (errorState) {
1877
+ const isTransition = errorState === "switching-provider" || errorState === "new-credentials" || errorState === "new-endpoint" || errorState === "auth-error-waiting";
1878
+ if (isTransition) {
1879
+ statusline = renderError(errorState, "with-cache", data.provider, undefined, cacheAge);
1880
+ } else {
1881
+ const hasCache = components.length > 0;
1882
+ const errorMode = hasCache ? "with-cache" : "without-cache";
1883
+ const errorIndicator = renderError(errorState, errorMode, data.provider, undefined, cacheAge);
1884
+ if (hasCache) {
1885
+ statusline = `${statusline} ${errorIndicator}`;
1886
+ } else {
1887
+ statusline = errorIndicator;
1888
+ }
1889
+ }
1890
+ }
1891
+ return visibleLength(statusline);
1892
+ }
1805
1893
  function getComponentOrder(config) {
1806
1894
  const explicitOrder = [];
1807
1895
  const explicitSet = new Set;
@@ -1878,7 +1966,7 @@ async function executeCycle(ctx) {
1878
1966
  if (!baseUrl || !authToken) {
1879
1967
  return {
1880
1968
  output: renderError("missing-env", "without-cache"),
1881
- exitCode: 1,
1969
+ exitCode: 0,
1882
1970
  cacheUpdate: null
1883
1971
  };
1884
1972
  }
@@ -1898,7 +1986,8 @@ async function executeCycle(ctx) {
1898
1986
  ttlSeconds,
1899
1987
  data,
1900
1988
  renderedLine: statusline,
1901
- configHash
1989
+ configHash,
1990
+ errorState: null
1902
1991
  };
1903
1992
  return {
1904
1993
  output: statusline,
@@ -1921,7 +2010,7 @@ async function executeCycle(ctx) {
1921
2010
  const errorOutput = renderError("network-error", "without-cache", providerId);
1922
2011
  return {
1923
2012
  output: errorOutput,
1924
- exitCode: 1,
2013
+ exitCode: 0,
1925
2014
  cacheUpdate: null
1926
2015
  };
1927
2016
  }
@@ -1997,7 +2086,7 @@ function uninstallStatusLine() {
1997
2086
  // package.json
1998
2087
  var package_default = {
1999
2088
  name: "cc-api-statusline",
2000
- version: "0.1.1",
2089
+ version: "0.1.2",
2001
2090
  description: "Claude Code statusline tool that polls API usage from third-party proxy backends",
2002
2091
  type: "module",
2003
2092
  bin: {
@@ -2192,7 +2281,7 @@ async function main() {
2192
2281
  if (envError) {
2193
2282
  const errorOutput = renderError("missing-env", "without-cache");
2194
2283
  process.stdout.write(errorOutput);
2195
- process.exit(1);
2284
+ process.exit(0);
2196
2285
  }
2197
2286
  const baseUrl = env.baseUrl;
2198
2287
  const authToken = env.authToken;
@@ -2210,7 +2299,7 @@ async function main() {
2210
2299
  logger.error("Provider not found", { providerId });
2211
2300
  const errorOutput = renderError("provider-unknown", "without-cache");
2212
2301
  process.stdout.write(errorOutput);
2213
- process.exit(1);
2302
+ process.exit(0);
2214
2303
  }
2215
2304
  const cachedEntry = readCache(baseUrl);
2216
2305
  logger.debug("Cache read", {
@@ -2239,12 +2328,17 @@ async function main() {
2239
2328
  outputLength: result.output.length,
2240
2329
  cacheUpdate: !!result.cacheUpdate
2241
2330
  });
2331
+ let output = result.output;
2332
+ if (!output || output.trim().length === 0) {
2333
+ output = "[loading...]";
2334
+ logger.debug("Empty output detected, using fallback");
2335
+ }
2242
2336
  if (isPiped) {
2243
- const formatted = "\x1B[0m" + result.output.replace(/ /g, " ");
2337
+ const formatted = "\x1B[0m" + output.replace(/ /g, " ");
2244
2338
  process.stdout.write(formatted);
2245
2339
  logger.debug("Output formatted for piped mode (ANSI reset + NBSP)");
2246
2340
  } else {
2247
- process.stdout.write(result.output);
2341
+ process.stdout.write(output);
2248
2342
  logger.debug("Output written (TTY mode)");
2249
2343
  }
2250
2344
  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.2",
4
4
  "description": "Claude Code statusline tool that polls API usage from third-party proxy backends",
5
5
  "type": "module",
6
6
  "bin": {