cc-costline 0.2.4 → 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.
- package/README.es.md +7 -1
- package/README.fr.md +7 -1
- package/README.ja.md +7 -1
- package/README.md +7 -1
- package/README.zh-CN.md +7 -1
- package/dist/statusline.d.ts +5 -0
- package/dist/statusline.js +90 -36
- package/package.json +3 -2
package/README.es.md
CHANGED
|
@@ -23,7 +23,7 @@ Abre una nueva sesión de Claude Code y verás la statusline mejorada. Requiere
|
|
|
23
23
|
| Segmento | Ejemplo | Descripción |
|
|
24
24
|
|----------|---------|-------------|
|
|
25
25
|
| Tokens ~ Costo / Contexto | `14.6k ~ $2.42 / 40% by Opus 4.6` | Tokens de la sesión, costo, uso de contexto y modelo |
|
|
26
|
-
| Límites de uso | `5h: 45% / 7d: 8%` | Utilización de Claude a 5 horas y 7 días (coloreado como el contexto) |
|
|
26
|
+
| Límites de uso | `5h: 45% / 7d: 8%` | Utilización de Claude a 5 horas y 7 días (coloreado como el contexto). Al 100%, muestra cuenta regresiva: `5h:-3:20` |
|
|
27
27
|
| Costo del período | `30d: $866` | Costo acumulado (configurable: 7d o 30d) |
|
|
28
28
|
| Ranking | `#2/22 $67.0` | Posición en [ccclub](https://github.com/mazzzystar/ccclub) (si está instalado) |
|
|
29
29
|
|
|
@@ -77,6 +77,12 @@ Los modelos desconocidos usan el precio de su familia, Sonnet por defecto.
|
|
|
77
77
|
|
|
78
78
|
</details>
|
|
79
79
|
|
|
80
|
+
## Desarrollo
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm test # Build + ejecutar tests unitarios (node:test, sin dependencias)
|
|
84
|
+
```
|
|
85
|
+
|
|
80
86
|
## Desinstalación
|
|
81
87
|
|
|
82
88
|
```bash
|
package/README.fr.md
CHANGED
|
@@ -23,7 +23,7 @@ Ouvrez une nouvelle session Claude Code et la statusline enrichie apparaîtra. N
|
|
|
23
23
|
| Segment | Exemple | Description |
|
|
24
24
|
|---------|---------|-------------|
|
|
25
25
|
| Tokens ~ Coût / Contexte | `14.6k ~ $2.42 / 40% by Opus 4.6` | Nombre de tokens, coût, utilisation du contexte et modèle |
|
|
26
|
-
| Limites d'utilisation | `5h: 45% / 7d: 8%` | Utilisation Claude sur 5 heures et 7 jours (colorée comme le contexte) |
|
|
26
|
+
| Limites d'utilisation | `5h: 45% / 7d: 8%` | Utilisation Claude sur 5 heures et 7 jours (colorée comme le contexte). À 100 %, affiche un compte à rebours : `5h:-3:20` |
|
|
27
27
|
| Coût périodique | `30d: $866` | Coût cumulé glissant (configurable : 7j ou 30j) |
|
|
28
28
|
| Classement | `#2/22 $67.0` | Rang [ccclub](https://github.com/mazzzystar/ccclub) (si installé) |
|
|
29
29
|
|
|
@@ -77,6 +77,12 @@ Les modèles inconnus utilisent le prix de leur famille, Sonnet par défaut.
|
|
|
77
77
|
|
|
78
78
|
</details>
|
|
79
79
|
|
|
80
|
+
## Développement
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm test # Build + exécuter les tests unitaires (node:test, zéro dépendance)
|
|
84
|
+
```
|
|
85
|
+
|
|
80
86
|
## Désinstallation
|
|
81
87
|
|
|
82
88
|
```bash
|
package/README.ja.md
CHANGED
|
@@ -23,7 +23,7 @@ npm i -g cc-costline && cc-costline install
|
|
|
23
23
|
| セグメント | 例 | 説明 |
|
|
24
24
|
|-----------|---|------|
|
|
25
25
|
| トークン ~ コスト / コンテキスト | `14.6k ~ $2.42 / 40% by Opus 4.6` | セッションのトークン数、コスト、コンテキスト使用率、モデル |
|
|
26
|
-
| 使用制限 | `5h: 45% / 7d: 8%` | Claude の 5 時間・7
|
|
26
|
+
| 使用制限 | `5h: 45% / 7d: 8%` | Claude の 5 時間・7 日間の使用率(コンテキストと同じ色分け)。100% 到達時はカウントダウン表示:`5h:-3:20` |
|
|
27
27
|
| 期間コスト | `30d: $866` | ローリングコスト合計(7d または 30d で設定可能) |
|
|
28
28
|
| リーダーボード | `#2/22 $67.0` | [ccclub](https://github.com/mazzzystar/ccclub) ランキング(インストール時) |
|
|
29
29
|
|
|
@@ -77,6 +77,12 @@ cc-costline config --period 7d # 7 日間のコストを表示
|
|
|
77
77
|
|
|
78
78
|
</details>
|
|
79
79
|
|
|
80
|
+
## 開発
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm test # ビルド + ユニットテスト実行(node:test、依存関係なし)
|
|
84
|
+
```
|
|
85
|
+
|
|
80
86
|
## アンインストール
|
|
81
87
|
|
|
82
88
|
```bash
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Open a new Claude Code session and you'll see the enhanced statusline. Requires
|
|
|
23
23
|
| Segment | Example | Description |
|
|
24
24
|
|---------|---------|-------------|
|
|
25
25
|
| Tokens ~ Cost / Context | `14.6k ~ $2.42 / 40% by Opus 4.6` | Session token count, cost, context usage, and model |
|
|
26
|
-
| Usage limits | `5h: 45% / 7d: 8%` | Claude 5-hour and 7-day utilization (auto-colored like context) |
|
|
26
|
+
| Usage limits | `5h: 45% / 7d: 8%` | Claude 5-hour and 7-day utilization (auto-colored like context). At 100%, shows countdown: `5h:-3:20` |
|
|
27
27
|
| Period cost | `30d: $866` | Rolling cost total (configurable: 7d or 30d) |
|
|
28
28
|
| Leaderboard | `#2/22 $67.0` | [ccclub](https://github.com/mazzzystar/ccclub) rank (if installed) |
|
|
29
29
|
|
|
@@ -77,6 +77,12 @@ Unknown models fall back by family name, defaulting to Sonnet pricing.
|
|
|
77
77
|
|
|
78
78
|
</details>
|
|
79
79
|
|
|
80
|
+
## Development
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm test # Build + run unit tests (node:test, zero dependencies)
|
|
84
|
+
```
|
|
85
|
+
|
|
80
86
|
## Uninstall
|
|
81
87
|
|
|
82
88
|
```bash
|
package/README.zh-CN.md
CHANGED
|
@@ -23,7 +23,7 @@ npm i -g cc-costline && cc-costline install
|
|
|
23
23
|
| 模块 | 示例 | 说明 |
|
|
24
24
|
|------|------|------|
|
|
25
25
|
| Token ~ 费用 / 上下文 | `14.6k ~ $2.42 / 40% by Opus 4.6` | 会话 token 数量、费用、上下文使用率和模型 |
|
|
26
|
-
| 使用限额 | `5h: 45% / 7d: 8%` | Claude 5 小时和 7
|
|
26
|
+
| 使用限额 | `5h: 45% / 7d: 8%` | Claude 5 小时和 7 天使用率(颜色同上下文)。达到 100% 时显示倒计时:`5h:-3:20` |
|
|
27
27
|
| 周期费用 | `30d: $866` | 滚动费用合计(可配置:7d 或 30d) |
|
|
28
28
|
| 排行榜 | `#2/22 $67.0` | [ccclub](https://github.com/mazzzystar/ccclub) 排名(需安装) |
|
|
29
29
|
|
|
@@ -77,6 +77,12 @@ cc-costline config --period 7d # 显示 7 天费用
|
|
|
77
77
|
|
|
78
78
|
</details>
|
|
79
79
|
|
|
80
|
+
## 开发
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npm test # 构建 + 运行单元测试(node:test,零依赖)
|
|
84
|
+
```
|
|
85
|
+
|
|
80
86
|
## 卸载
|
|
81
87
|
|
|
82
88
|
```bash
|
package/dist/statusline.d.ts
CHANGED
|
@@ -1 +1,6 @@
|
|
|
1
|
+
export declare function formatTokens(t: number): string;
|
|
2
|
+
export declare function formatCost(n: number): string;
|
|
3
|
+
export declare function ctxColor(pct: number): string;
|
|
4
|
+
export declare function formatCountdown(resetsAtMs: number): string;
|
|
5
|
+
export declare function rankColor(rank: number): string;
|
|
1
6
|
export declare function render(input: string): string;
|
package/dist/statusline.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync, existsSync,
|
|
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";
|
|
@@ -14,14 +14,14 @@ const FG_MODEL = "\x1b[38;2;202;124;94m";
|
|
|
14
14
|
const FG_CYAN = "\x1b[38;5;109m";
|
|
15
15
|
const FG_WHITE = "\x1b[38;5;255m";
|
|
16
16
|
const RESET = "\x1b[0m";
|
|
17
|
-
function formatTokens(t) {
|
|
17
|
+
export function formatTokens(t) {
|
|
18
18
|
if (t >= 1_000_000)
|
|
19
19
|
return (t / 1_000_000).toFixed(1) + "M";
|
|
20
20
|
if (t >= 1_000)
|
|
21
21
|
return (t / 1_000).toFixed(1) + "k";
|
|
22
22
|
return String(t);
|
|
23
23
|
}
|
|
24
|
-
function formatCost(n) {
|
|
24
|
+
export function formatCost(n) {
|
|
25
25
|
if (n >= 1000)
|
|
26
26
|
return "$" + Math.round(n).toLocaleString("en-US");
|
|
27
27
|
if (n >= 100)
|
|
@@ -30,54 +30,63 @@ function formatCost(n) {
|
|
|
30
30
|
return "$" + n.toFixed(1);
|
|
31
31
|
return "$" + n.toFixed(2);
|
|
32
32
|
}
|
|
33
|
-
function ctxColor(pct) {
|
|
33
|
+
export function ctxColor(pct) {
|
|
34
34
|
if (pct >= 80)
|
|
35
35
|
return FG_RED;
|
|
36
36
|
if (pct >= 60)
|
|
37
37
|
return FG_ORANGE;
|
|
38
38
|
return FG_GREEN;
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
export function formatCountdown(resetsAtMs) {
|
|
41
|
+
const remainingMs = resetsAtMs - Date.now();
|
|
42
|
+
if (remainingMs <= 0)
|
|
43
|
+
return "~0:00";
|
|
44
|
+
const totalMinutes = Math.ceil(remainingMs / 60000);
|
|
45
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
46
|
+
const minutes = totalMinutes % 60;
|
|
47
|
+
return `-${hours}:${String(minutes).padStart(2, "0")}`;
|
|
48
|
+
}
|
|
49
|
+
// ccclub rank fetcher — cached per session (stale fallback on failure)
|
|
50
|
+
function getCcclubRank(sessionId) {
|
|
42
51
|
const configPath = join(homedir(), ".ccclub", "config.json");
|
|
43
52
|
if (!existsSync(configPath))
|
|
44
53
|
return null;
|
|
45
54
|
const cacheFile = "/tmp/sl-ccclub-rank";
|
|
46
|
-
|
|
55
|
+
let staleData = null;
|
|
47
56
|
if (existsSync(cacheFile)) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
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;
|
|
54
62
|
}
|
|
63
|
+
catch { }
|
|
55
64
|
}
|
|
56
65
|
try {
|
|
57
66
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
58
67
|
const code = config.groups?.[0];
|
|
59
68
|
const userId = config.userId;
|
|
60
69
|
if (!code || !userId)
|
|
61
|
-
return
|
|
70
|
+
return staleData;
|
|
62
71
|
const tz = -(new Date()).getTimezoneOffset();
|
|
63
72
|
const url = `${config.apiUrl}/api/rank/${code}?period=today&tz=${tz}`;
|
|
64
73
|
const response = execSync(`curl -sf "${url}"`, { encoding: "utf-8", timeout: 5000 });
|
|
65
74
|
if (!response)
|
|
66
|
-
return
|
|
75
|
+
return staleData;
|
|
67
76
|
const data = JSON.parse(response);
|
|
68
77
|
const rankings = data.rankings || [];
|
|
69
78
|
const me = rankings.find((r) => r.userId === userId);
|
|
70
79
|
if (!me)
|
|
71
|
-
return
|
|
80
|
+
return staleData;
|
|
72
81
|
const result = { rank: me.rank, total: rankings.length, cost: me.costUSD };
|
|
73
|
-
writeFileSync(cacheFile, JSON.stringify(result), "utf-8");
|
|
82
|
+
writeFileSync(cacheFile, JSON.stringify({ sessionId, data: result }), "utf-8");
|
|
74
83
|
return result;
|
|
75
84
|
}
|
|
76
85
|
catch {
|
|
77
|
-
return
|
|
86
|
+
return staleData;
|
|
78
87
|
}
|
|
79
88
|
}
|
|
80
|
-
function rankColor(rank) {
|
|
89
|
+
export function rankColor(rank) {
|
|
81
90
|
if (rank === 1)
|
|
82
91
|
return FG_YELLOW;
|
|
83
92
|
if (rank === 2)
|
|
@@ -86,18 +95,20 @@ function rankColor(rank) {
|
|
|
86
95
|
return FG_ORANGE;
|
|
87
96
|
return FG_CYAN;
|
|
88
97
|
}
|
|
89
|
-
// Claude usage fetcher
|
|
90
|
-
function getClaudeUsage() {
|
|
98
|
+
// Claude usage fetcher — cached per session (stale fallback on failure)
|
|
99
|
+
function getClaudeUsage(sessionId) {
|
|
91
100
|
const cacheFile = "/tmp/sl-claude-usage";
|
|
92
|
-
const
|
|
101
|
+
const hitFile = "/tmp/sl-claude-usage-hit";
|
|
102
|
+
const now = Date.now();
|
|
103
|
+
let staleData = null;
|
|
93
104
|
if (existsSync(cacheFile)) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
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;
|
|
100
110
|
}
|
|
111
|
+
catch { }
|
|
101
112
|
}
|
|
102
113
|
try {
|
|
103
114
|
const username = process.env.USER || process.env.USERNAME;
|
|
@@ -125,12 +136,47 @@ function getClaudeUsage() {
|
|
|
125
136
|
return Math.round(parseFloat(val.replace("%", "")));
|
|
126
137
|
return 0;
|
|
127
138
|
};
|
|
128
|
-
const
|
|
129
|
-
|
|
139
|
+
const fiveHour = parseUtil(data.five_hour?.utilization);
|
|
140
|
+
const sevenDay = parseUtil(data.seven_day?.utilization);
|
|
141
|
+
let fiveHourResetsAt;
|
|
142
|
+
// Strategy 1: Use reset time from API if available
|
|
143
|
+
const resetsAtRaw = data.five_hour?.resets_at ?? data.five_hour?.reset_at ?? data.five_hour?.next_reset;
|
|
144
|
+
if (resetsAtRaw) {
|
|
145
|
+
const ts = typeof resetsAtRaw === "string" ? new Date(resetsAtRaw).getTime() : resetsAtRaw * 1000;
|
|
146
|
+
if (!isNaN(ts) && ts > now)
|
|
147
|
+
fiveHourResetsAt = ts;
|
|
148
|
+
}
|
|
149
|
+
// Strategy 2: Fallback - track when we first saw 100%
|
|
150
|
+
if (fiveHour >= 100) {
|
|
151
|
+
if (!fiveHourResetsAt) {
|
|
152
|
+
if (existsSync(hitFile)) {
|
|
153
|
+
const hitTime = parseFloat(readFileSync(hitFile, "utf-8").trim());
|
|
154
|
+
if (!isNaN(hitTime)) {
|
|
155
|
+
fiveHourResetsAt = hitTime + 5 * 3600 * 1000;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
writeFileSync(hitFile, String(now), "utf-8");
|
|
160
|
+
fiveHourResetsAt = now + 5 * 3600 * 1000;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
// Usage dropped below 100%, clear hit tracker
|
|
166
|
+
try {
|
|
167
|
+
if (existsSync(hitFile))
|
|
168
|
+
unlinkSync(hitFile);
|
|
169
|
+
}
|
|
170
|
+
catch { }
|
|
171
|
+
}
|
|
172
|
+
const result = { fiveHour, sevenDay };
|
|
173
|
+
if (fiveHourResetsAt)
|
|
174
|
+
result.fiveHourResetsAt = fiveHourResetsAt;
|
|
175
|
+
writeFileSync(cacheFile, JSON.stringify({ sessionId, data: result }), "utf-8");
|
|
130
176
|
return result;
|
|
131
177
|
}
|
|
132
178
|
catch {
|
|
133
|
-
return
|
|
179
|
+
return staleData;
|
|
134
180
|
}
|
|
135
181
|
}
|
|
136
182
|
export function render(input) {
|
|
@@ -145,9 +191,11 @@ export function render(input) {
|
|
|
145
191
|
const cost = data.cost?.total_cost_usd ?? 0;
|
|
146
192
|
const model = data.model?.display_name ?? "—";
|
|
147
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$/, "") : "";
|
|
148
197
|
// Token stats from transcript
|
|
149
198
|
let totalTokens = 0;
|
|
150
|
-
const transcriptPath = data.transcript_path ?? "";
|
|
151
199
|
if (transcriptPath) {
|
|
152
200
|
try {
|
|
153
201
|
const content = readFileSync(transcriptPath, "utf-8");
|
|
@@ -168,7 +216,7 @@ export function render(input) {
|
|
|
168
216
|
}
|
|
169
217
|
const cache = readCache();
|
|
170
218
|
const config = readConfig();
|
|
171
|
-
const claudeUsage = getClaudeUsage();
|
|
219
|
+
const claudeUsage = getClaudeUsage(sessionId);
|
|
172
220
|
const g = FG_GRAY_DIM;
|
|
173
221
|
const y = FG_YELLOW;
|
|
174
222
|
const m = FG_MODEL;
|
|
@@ -181,9 +229,15 @@ export function render(input) {
|
|
|
181
229
|
// 5h:100% · 7d:26% · 30d:$960
|
|
182
230
|
const usageParts = [];
|
|
183
231
|
if (claudeUsage) {
|
|
184
|
-
|
|
232
|
+
if (claudeUsage.fiveHour >= 100 && claudeUsage.fiveHourResetsAt) {
|
|
233
|
+
const countdown = formatCountdown(claudeUsage.fiveHourResetsAt);
|
|
234
|
+
usageParts.push(`${FG_RED}5h:${countdown}${r}`);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
const c5 = ctxColor(claudeUsage.fiveHour);
|
|
238
|
+
usageParts.push(`${c5}5h:${claudeUsage.fiveHour}%${r}`);
|
|
239
|
+
}
|
|
185
240
|
const c7 = ctxColor(claudeUsage.sevenDay);
|
|
186
|
-
usageParts.push(`${c5}5h:${claudeUsage.fiveHour}%${r}`);
|
|
187
241
|
usageParts.push(`${c7}7d:${claudeUsage.sevenDay}%${r}`);
|
|
188
242
|
}
|
|
189
243
|
if (cache) {
|
|
@@ -195,7 +249,7 @@ export function render(input) {
|
|
|
195
249
|
segments.push(usageParts.join(` ${g}·${r} `));
|
|
196
250
|
}
|
|
197
251
|
// #2 $53.6
|
|
198
|
-
const ccclubRank = getCcclubRank();
|
|
252
|
+
const ccclubRank = getCcclubRank(sessionId);
|
|
199
253
|
if (ccclubRank) {
|
|
200
254
|
const rc = rankColor(ccclubRank.rank);
|
|
201
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
|
+
"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": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "tsc",
|
|
11
|
-
"dev": "tsc --watch"
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"test": "tsc && node --experimental-strip-types --no-warnings --test test/*.test.ts"
|
|
12
13
|
},
|
|
13
14
|
"files": [
|
|
14
15
|
"dist"
|