cc-costline 0.3.0 → 0.3.1

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.
Files changed (2) hide show
  1. package/dist/statusline.js +31 -29
  2. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { readFileSync, existsSync, statSync, writeFileSync, unlinkSync } from "node:fs";
1
+ import { readFileSync, existsSync, writeFileSync, unlinkSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { homedir } from "node:os";
4
4
  import { execSync } from "node:child_process";
@@ -46,44 +46,44 @@ export function formatCountdown(resetsAtMs) {
46
46
  const minutes = totalMinutes % 60;
47
47
  return `-${hours}:${String(minutes).padStart(2, "0")}`;
48
48
  }
49
- // ccclub rank fetcher with 120s file cache
50
- function getCcclubRank() {
49
+ // ccclub rank fetcher cached per session (stale fallback on failure)
50
+ function getCcclubRank(sessionId) {
51
51
  const configPath = join(homedir(), ".ccclub", "config.json");
52
52
  if (!existsSync(configPath))
53
53
  return null;
54
54
  const cacheFile = "/tmp/sl-ccclub-rank";
55
- const now = Date.now() / 1000;
55
+ let staleData = null;
56
56
  if (existsSync(cacheFile)) {
57
- const mtime = statSync(cacheFile).mtimeMs / 1000;
58
- if (now - mtime <= 120) {
59
- try {
60
- return JSON.parse(readFileSync(cacheFile, "utf-8"));
61
- }
62
- catch { }
57
+ try {
58
+ const cached = JSON.parse(readFileSync(cacheFile, "utf-8"));
59
+ staleData = cached.data ?? null;
60
+ if (cached.sessionId === sessionId)
61
+ return staleData;
63
62
  }
63
+ catch { }
64
64
  }
65
65
  try {
66
66
  const config = JSON.parse(readFileSync(configPath, "utf-8"));
67
67
  const code = config.groups?.[0];
68
68
  const userId = config.userId;
69
69
  if (!code || !userId)
70
- return null;
70
+ return staleData;
71
71
  const tz = -(new Date()).getTimezoneOffset();
72
72
  const url = `${config.apiUrl}/api/rank/${code}?period=today&tz=${tz}`;
73
73
  const response = execSync(`curl -sf "${url}"`, { encoding: "utf-8", timeout: 5000 });
74
74
  if (!response)
75
- return null;
75
+ return staleData;
76
76
  const data = JSON.parse(response);
77
77
  const rankings = data.rankings || [];
78
78
  const me = rankings.find((r) => r.userId === userId);
79
79
  if (!me)
80
- return null;
80
+ return staleData;
81
81
  const result = { rank: me.rank, total: rankings.length, cost: me.costUSD };
82
- writeFileSync(cacheFile, JSON.stringify(result), "utf-8");
82
+ writeFileSync(cacheFile, JSON.stringify({ sessionId, data: result }), "utf-8");
83
83
  return result;
84
84
  }
85
85
  catch {
86
- return null;
86
+ return staleData;
87
87
  }
88
88
  }
89
89
  export function rankColor(rank) {
@@ -95,20 +95,20 @@ export function rankColor(rank) {
95
95
  return FG_ORANGE;
96
96
  return FG_CYAN;
97
97
  }
98
- // Claude usage fetcher with 60s file cache
99
- function getClaudeUsage() {
98
+ // Claude usage fetcher cached per session (stale fallback on failure)
99
+ function getClaudeUsage(sessionId) {
100
100
  const cacheFile = "/tmp/sl-claude-usage";
101
101
  const hitFile = "/tmp/sl-claude-usage-hit";
102
102
  const now = Date.now();
103
- const nowSec = now / 1000;
103
+ let staleData = null;
104
104
  if (existsSync(cacheFile)) {
105
- const mtime = statSync(cacheFile).mtimeMs / 1000;
106
- if (nowSec - mtime <= 60) {
107
- try {
108
- return JSON.parse(readFileSync(cacheFile, "utf-8"));
109
- }
110
- catch { }
105
+ try {
106
+ const cached = JSON.parse(readFileSync(cacheFile, "utf-8"));
107
+ staleData = cached.data ?? null;
108
+ if (cached.sessionId === sessionId)
109
+ return staleData;
111
110
  }
111
+ catch { }
112
112
  }
113
113
  try {
114
114
  const username = process.env.USER || process.env.USERNAME;
@@ -172,11 +172,11 @@ function getClaudeUsage() {
172
172
  const result = { fiveHour, sevenDay };
173
173
  if (fiveHourResetsAt)
174
174
  result.fiveHourResetsAt = fiveHourResetsAt;
175
- writeFileSync(cacheFile, JSON.stringify(result), "utf-8");
175
+ writeFileSync(cacheFile, JSON.stringify({ sessionId, data: result }), "utf-8");
176
176
  return result;
177
177
  }
178
178
  catch {
179
- return null;
179
+ return staleData;
180
180
  }
181
181
  }
182
182
  export function render(input) {
@@ -191,9 +191,11 @@ export function render(input) {
191
191
  const cost = data.cost?.total_cost_usd ?? 0;
192
192
  const model = data.model?.display_name ?? "—";
193
193
  const contextPct = Math.floor(data.context_window?.used_percentage ?? 0);
194
+ // Session ID from transcript path (filename without extension)
195
+ const transcriptPath = data.transcript_path ?? "";
196
+ const sessionId = transcriptPath ? transcriptPath.replace(/^.*\//, "").replace(/\.jsonl$/, "") : "";
194
197
  // Token stats from transcript
195
198
  let totalTokens = 0;
196
- const transcriptPath = data.transcript_path ?? "";
197
199
  if (transcriptPath) {
198
200
  try {
199
201
  const content = readFileSync(transcriptPath, "utf-8");
@@ -214,7 +216,7 @@ export function render(input) {
214
216
  }
215
217
  const cache = readCache();
216
218
  const config = readConfig();
217
- const claudeUsage = getClaudeUsage();
219
+ const claudeUsage = getClaudeUsage(sessionId);
218
220
  const g = FG_GRAY_DIM;
219
221
  const y = FG_YELLOW;
220
222
  const m = FG_MODEL;
@@ -247,7 +249,7 @@ export function render(input) {
247
249
  segments.push(usageParts.join(` ${g}·${r} `));
248
250
  }
249
251
  // #2 $53.6
250
- const ccclubRank = getCcclubRank();
252
+ const ccclubRank = getCcclubRank(sessionId);
251
253
  if (ccclubRank) {
252
254
  const rc = rankColor(ccclubRank.rank);
253
255
  segments.push(`${rc}#${ccclubRank.rank} ${formatCost(ccclubRank.cost)}${r}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-costline",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Enhanced statusline for Claude Code with cost tracking, usage limits, and leaderboard",
5
5
  "type": "module",
6
6
  "bin": {