cc-costline 0.3.2 → 0.4.0
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 +11 -9
- package/README.fr.md +11 -9
- package/README.ja.md +10 -8
- package/README.md +11 -9
- package/README.zh-CN.md +11 -9
- package/dist/cache.d.ts +4 -4
- package/dist/cache.js +12 -12
- package/dist/cli.js +12 -4
- package/dist/collector.d.ts +1 -1
- package/dist/collector.js +3 -3
- package/dist/statusline.js +40 -25
- package/package.json +1 -1
package/README.es.md
CHANGED
|
@@ -24,13 +24,13 @@ Abre una nueva sesión de Claude Code y verás la statusline mejorada. Requiere
|
|
|
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
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
|
-
| Costo del período | `30d: $866` | Costo acumulado (configurable: 7d o
|
|
27
|
+
| Costo del período | `30d: $866` | Costo acumulado (configurable: 7d, 30d o both) |
|
|
28
28
|
| Ranking | `#2/22 $67.0` | Posición en [ccclub](https://github.com/mazzzystar/ccclub) (si está instalado) |
|
|
29
29
|
|
|
30
30
|
### Colores
|
|
31
31
|
|
|
32
32
|
- **Contexto y límites de uso** — verde (< 60%) → naranja (60-79%) → rojo (≥ 80%)
|
|
33
|
-
- **Posición en ranking** — 1.o: dorado, 2.o: blanco, 3.o: naranja, resto:
|
|
33
|
+
- **Posición en ranking** — 1.o: dorado, 2.o: blanco, 3.o: naranja, resto: cian
|
|
34
34
|
- **Costo del período** — amarillo
|
|
35
35
|
|
|
36
36
|
### Integraciones opcionales
|
|
@@ -46,17 +46,19 @@ Ambas funcionan sin configuración: si no están disponibles, el segmento se ocu
|
|
|
46
46
|
cc-costline install # Configurar la integración con Claude Code
|
|
47
47
|
cc-costline uninstall # Eliminar de la configuración
|
|
48
48
|
cc-costline refresh # Recalcular manualmente la caché de costos
|
|
49
|
-
cc-costline config --period
|
|
50
|
-
cc-costline config --period
|
|
49
|
+
cc-costline config --period 7d # Mostrar costo de 7 días (por defecto)
|
|
50
|
+
cc-costline config --period 30d # Mostrar costo de 30 días
|
|
51
|
+
cc-costline config --period both # Mostrar ambos períodos
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
## Cómo funciona
|
|
54
55
|
|
|
55
|
-
1. `install` configura `~/.claude/settings.json` — establece el comando de statusline y añade hooks de fin de sesión
|
|
56
|
-
2. `render`
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
1. `install` configura `~/.claude/settings.json` — establece el comando de statusline y añade hooks de fin de sesión. Tu configuración existente se conserva.
|
|
57
|
+
2. `render` es llamado por Claude Code en cada turno de conversación. Lee el JSON de stdin para datos de sesión, luego actualiza todas las fuentes de datos en línea cuando su caché expira (TTL unificado de 2 minutos):
|
|
58
|
+
- **Costo local**: escanea `~/.claude/projects/**/*.jsonl`, aplica precios por modelo → `~/.cc-costline/cache.json`
|
|
59
|
+
- **Límites de uso**: obtiene de `api.anthropic.com/api/oauth/usage` → `/tmp/sl-claude-usage`
|
|
60
|
+
- **Ranking ccclub**: obtiene de `ccclub.dev/api/rank` → `/tmp/sl-ccclub-rank`
|
|
61
|
+
3. `refresh` también puede ejecutarse manualmente o mediante hooks de fin de sesión para precalentar la caché.
|
|
60
62
|
|
|
61
63
|
<details>
|
|
62
64
|
<summary>Tabla de precios</summary>
|
package/README.fr.md
CHANGED
|
@@ -24,13 +24,13 @@ Ouvrez une nouvelle session Claude Code et la statusline enrichie apparaîtra. N
|
|
|
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
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
|
-
| Coût périodique | `30d: $866` | Coût cumulé glissant (configurable : 7j ou
|
|
27
|
+
| Coût périodique | `30d: $866` | Coût cumulé glissant (configurable : 7j, 30j ou both) |
|
|
28
28
|
| Classement | `#2/22 $67.0` | Rang [ccclub](https://github.com/mazzzystar/ccclub) (si installé) |
|
|
29
29
|
|
|
30
30
|
### Couleurs
|
|
31
31
|
|
|
32
32
|
- **Contexte et limites** — vert (< 60 %) → orange (60-79 %) → rouge (≥ 80 %)
|
|
33
|
-
- **Rang au classement** — 1er : or, 2e : blanc, 3e : orange, autres :
|
|
33
|
+
- **Rang au classement** — 1er : or, 2e : blanc, 3e : orange, autres : cyan
|
|
34
34
|
- **Coût périodique** — jaune
|
|
35
35
|
|
|
36
36
|
### Intégrations optionnelles
|
|
@@ -46,17 +46,19 @@ Les deux fonctionnent sans configuration : si indisponibles, le segment est masq
|
|
|
46
46
|
cc-costline install # Configurer l'intégration Claude Code
|
|
47
47
|
cc-costline uninstall # Supprimer des paramètres
|
|
48
48
|
cc-costline refresh # Recalculer manuellement le cache des coûts
|
|
49
|
-
cc-costline config --period
|
|
50
|
-
cc-costline config --period
|
|
49
|
+
cc-costline config --period 7d # Afficher le coût sur 7 jours (par défaut)
|
|
50
|
+
cc-costline config --period 30d # Afficher le coût sur 30 jours
|
|
51
|
+
cc-costline config --period both # Afficher les deux périodes
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
## Fonctionnement
|
|
54
55
|
|
|
55
|
-
1. `install` configure `~/.claude/settings.json` — définit la commande statusline et ajoute des hooks de fin de session
|
|
56
|
-
2. `render` lit le JSON stdin de
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
1. `install` configure `~/.claude/settings.json` — définit la commande statusline et ajoute des hooks de fin de session. Vos paramètres existants sont préservés.
|
|
57
|
+
2. `render` est appelé par Claude Code à chaque tour de conversation. Il lit le JSON stdin pour les données de session, puis rafraîchit toutes les sources de données en ligne lorsque leur cache expire (TTL unifié de 2 minutes) :
|
|
58
|
+
- **Coût local** : parcourt `~/.claude/projects/**/*.jsonl`, applique la tarification par modèle → `~/.cc-costline/cache.json`
|
|
59
|
+
- **Limites d'utilisation** : récupère depuis `api.anthropic.com/api/oauth/usage` → `/tmp/sl-claude-usage`
|
|
60
|
+
- **Rang ccclub** : récupère depuis `ccclub.dev/api/rank` → `/tmp/sl-ccclub-rank`
|
|
61
|
+
3. `refresh` peut aussi être exécuté manuellement ou via les hooks de fin de session pour préchauffer le cache.
|
|
60
62
|
|
|
61
63
|
<details>
|
|
62
64
|
<summary>Grille tarifaire</summary>
|
package/README.ja.md
CHANGED
|
@@ -24,13 +24,13 @@ npm i -g cc-costline && cc-costline install
|
|
|
24
24
|
|-----------|---|------|
|
|
25
25
|
| トークン ~ コスト / コンテキスト | `14.6k ~ $2.42 / 40% by Opus 4.6` | セッションのトークン数、コスト、コンテキスト使用率、モデル |
|
|
26
26
|
| 使用制限 | `5h: 45% / 7d: 8%` | Claude の 5 時間・7 日間の使用率(コンテキストと同じ色分け)。100% 到達時はカウントダウン表示:`5h:-3:20` |
|
|
27
|
-
| 期間コスト | `30d: $866` | ローリングコスト合計(7d
|
|
27
|
+
| 期間コスト | `30d: $866` | ローリングコスト合計(7d、30d、または both で設定可能) |
|
|
28
28
|
| リーダーボード | `#2/22 $67.0` | [ccclub](https://github.com/mazzzystar/ccclub) ランキング(インストール時) |
|
|
29
29
|
|
|
30
30
|
### カラールール
|
|
31
31
|
|
|
32
32
|
- **コンテキスト・使用制限** — 緑(< 60%)→ オレンジ(60-79%)→ 赤(≥ 80%)
|
|
33
|
-
- **リーダーボードランク** — 1 位:ゴールド、2 位:ホワイト、3
|
|
33
|
+
- **リーダーボードランク** — 1 位:ゴールド、2 位:ホワイト、3 位:オレンジ、その他:シアン
|
|
34
34
|
- **期間コスト** — イエロー
|
|
35
35
|
|
|
36
36
|
### オプション連携
|
|
@@ -46,17 +46,19 @@ npm i -g cc-costline && cc-costline install
|
|
|
46
46
|
cc-costline install # Claude Code 連携のセットアップ
|
|
47
47
|
cc-costline uninstall # 設定から削除
|
|
48
48
|
cc-costline refresh # コストキャッシュを手動再計算
|
|
49
|
-
cc-costline config --period
|
|
50
|
-
cc-costline config --period
|
|
49
|
+
cc-costline config --period 7d # 7 日間のコストを表示(デフォルト)
|
|
50
|
+
cc-costline config --period 30d # 30 日間のコストを表示
|
|
51
|
+
cc-costline config --period both # 両方の期間を表示
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
## 仕組み
|
|
54
55
|
|
|
55
56
|
1. `install` は `~/.claude/settings.json` を設定 — ステータスラインコマンドとセッション終了フックを追加します。既存の設定は保持されます。
|
|
56
|
-
2. `render`
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
2. `render` は毎回の対話時に Claude Code から呼び出され、stdin JSON からセッションデータを読み取り、すべてのデータソースをキャッシュ期限切れ時にインラインで更新します(統一 2 分 TTL):
|
|
58
|
+
- **ローカルコスト**:`~/.claude/projects/**/*.jsonl` をスキャン、モデル別価格を適用 → `~/.cc-costline/cache.json`
|
|
59
|
+
- **使用率**:`api.anthropic.com/api/oauth/usage` から取得 → `/tmp/sl-claude-usage`
|
|
60
|
+
- **ccclub ランキング**:`ccclub.dev/api/rank` から取得 → `/tmp/sl-ccclub-rank`
|
|
61
|
+
3. `refresh` は手動実行やセッション終了フックによるキャッシュウォームアップにも使用できます。
|
|
60
62
|
|
|
61
63
|
<details>
|
|
62
64
|
<summary>料金表</summary>
|
package/README.md
CHANGED
|
@@ -24,13 +24,13 @@ Open a new Claude Code session and you'll see the enhanced statusline. Requires
|
|
|
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
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
|
-
| Period cost | `30d: $866` | Rolling cost total (configurable: 7d or
|
|
27
|
+
| Period cost | `30d: $866` | Rolling cost total (configurable: 7d, 30d, or both) |
|
|
28
28
|
| Leaderboard | `#2/22 $67.0` | [ccclub](https://github.com/mazzzystar/ccclub) rank (if installed) |
|
|
29
29
|
|
|
30
30
|
### Colors
|
|
31
31
|
|
|
32
32
|
- **Context & usage limits** — green (< 60%) → orange (60-79%) → red (≥ 80%)
|
|
33
|
-
- **Leaderboard rank** — #1 gold, #2 white, #3 orange, others
|
|
33
|
+
- **Leaderboard rank** — #1 gold, #2 white, #3 orange, others cyan
|
|
34
34
|
- **Period cost** — yellow
|
|
35
35
|
|
|
36
36
|
### Optional integrations
|
|
@@ -46,17 +46,19 @@ Both are zero-config: if not available, the segment is silently omitted.
|
|
|
46
46
|
cc-costline install # Set up Claude Code integration
|
|
47
47
|
cc-costline uninstall # Remove from settings
|
|
48
48
|
cc-costline refresh # Manually recalculate cost cache
|
|
49
|
-
cc-costline config --period
|
|
50
|
-
cc-costline config --period
|
|
49
|
+
cc-costline config --period 7d # Show 7-day cost (default)
|
|
50
|
+
cc-costline config --period 30d # Show 30-day cost
|
|
51
|
+
cc-costline config --period both # Show both periods
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
## How it works
|
|
54
55
|
|
|
55
|
-
1. `install` configures `~/.claude/settings.json` — sets the statusline command and adds session-end hooks
|
|
56
|
-
2. `render`
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
1. `install` configures `~/.claude/settings.json` — sets the statusline command and adds session-end hooks. Your existing settings are preserved.
|
|
57
|
+
2. `render` is called by Claude Code on every turn. It reads stdin JSON for session data, then refreshes all data sources inline when their cache expires (2-minute TTL):
|
|
58
|
+
- **Local cost**: scans `~/.claude/projects/**/*.jsonl`, applies per-model pricing → `~/.cc-costline/cache.json`
|
|
59
|
+
- **Usage limits**: fetches `api.anthropic.com/api/oauth/usage` → `/tmp/sl-claude-usage`
|
|
60
|
+
- **ccclub rank**: fetches `ccclub.dev/api/rank` → `/tmp/sl-ccclub-rank`
|
|
61
|
+
3. `refresh` can also be run manually or via session-end hooks to warm the cost cache.
|
|
60
62
|
|
|
61
63
|
<details>
|
|
62
64
|
<summary>Pricing table</summary>
|
package/README.zh-CN.md
CHANGED
|
@@ -24,13 +24,13 @@ npm i -g cc-costline && cc-costline install
|
|
|
24
24
|
|------|------|------|
|
|
25
25
|
| Token ~ 费用 / 上下文 | `14.6k ~ $2.42 / 40% by Opus 4.6` | 会话 token 数量、费用、上下文使用率和模型 |
|
|
26
26
|
| 使用限额 | `5h: 45% / 7d: 8%` | Claude 5 小时和 7 天使用率(颜色同上下文)。达到 100% 时显示倒计时:`5h:-3:20` |
|
|
27
|
-
| 周期费用 | `30d: $866` | 滚动费用合计(可配置:7d 或
|
|
27
|
+
| 周期费用 | `30d: $866` | 滚动费用合计(可配置:7d、30d 或 both) |
|
|
28
28
|
| 排行榜 | `#2/22 $67.0` | [ccclub](https://github.com/mazzzystar/ccclub) 排名(需安装) |
|
|
29
29
|
|
|
30
30
|
### 颜色规则
|
|
31
31
|
|
|
32
32
|
- **上下文和使用限额** — 绿色(< 60%)→ 橙色(60-79%)→ 红色(≥ 80%)
|
|
33
|
-
- **排行榜排名** — 第 1 名金色,第 2 名白色,第 3
|
|
33
|
+
- **排行榜排名** — 第 1 名金色,第 2 名白色,第 3 名橙色,其余青色
|
|
34
34
|
- **周期费用** — 黄色
|
|
35
35
|
|
|
36
36
|
### 可选集成
|
|
@@ -46,17 +46,19 @@ npm i -g cc-costline && cc-costline install
|
|
|
46
46
|
cc-costline install # 设置 Claude Code 集成
|
|
47
47
|
cc-costline uninstall # 从设置中移除
|
|
48
48
|
cc-costline refresh # 手动重新计算费用缓存
|
|
49
|
-
cc-costline config --period
|
|
50
|
-
cc-costline config --period
|
|
49
|
+
cc-costline config --period 7d # 显示 7 天费用(默认)
|
|
50
|
+
cc-costline config --period 30d # 显示 30 天费用
|
|
51
|
+
cc-costline config --period both # 同时显示两个周期
|
|
51
52
|
```
|
|
52
53
|
|
|
53
54
|
## 工作原理
|
|
54
55
|
|
|
55
|
-
1. `install` 配置 `~/.claude/settings.json` — 设置状态栏命令并添加会话结束 hook
|
|
56
|
-
2. `render`
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
1. `install` 配置 `~/.claude/settings.json` — 设置状态栏命令并添加会话结束 hook。你的现有设置会被保留。
|
|
57
|
+
2. `render` 在每次对话时被 Claude Code 调用,读取 stdin JSON 获取会话数据,然后按需刷新所有数据源(统一 2 分钟 TTL):
|
|
58
|
+
- **本地费用**:扫描 `~/.claude/projects/**/*.jsonl`,按模型定价计算 → `~/.cc-costline/cache.json`
|
|
59
|
+
- **使用率**:从 `api.anthropic.com/api/oauth/usage` 获取 → `/tmp/sl-claude-usage`
|
|
60
|
+
- **ccclub 排名**:从 `ccclub.dev/api/rank` 获取 → `/tmp/sl-ccclub-rank`
|
|
61
|
+
3. `refresh` 也可以手动运行或通过会话结束 hook 预热缓存。
|
|
60
62
|
|
|
61
63
|
<details>
|
|
62
64
|
<summary>定价表</summary>
|
package/dist/cache.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ export interface CacheData {
|
|
|
7
7
|
export interface ConfigData {
|
|
8
8
|
period: "7d" | "30d" | "both";
|
|
9
9
|
}
|
|
10
|
-
export declare function readCache(): CacheData | null;
|
|
11
|
-
export declare function writeCache(data: CacheData): void;
|
|
12
|
-
export declare function readConfig(): ConfigData;
|
|
13
|
-
export declare function writeConfig(data: ConfigData): void;
|
|
10
|
+
export declare function readCache(dir?: string): CacheData | null;
|
|
11
|
+
export declare function writeCache(data: CacheData, dir?: string): void;
|
|
12
|
+
export declare function readConfig(dir?: string): ConfigData;
|
|
13
|
+
export declare function writeConfig(data: ConfigData, dir?: string): void;
|
|
14
14
|
export { CACHE_DIR };
|
package/dist/cache.js
CHANGED
|
@@ -2,32 +2,32 @@ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
const CACHE_DIR = join(homedir(), ".cc-costline");
|
|
5
|
-
|
|
6
|
-
const CONFIG_FILE = join(CACHE_DIR, "config.json");
|
|
7
|
-
export function readCache() {
|
|
5
|
+
export function readCache(dir) {
|
|
8
6
|
try {
|
|
9
|
-
const raw = readFileSync(
|
|
7
|
+
const raw = readFileSync(join(dir || CACHE_DIR, "cache.json"), "utf-8");
|
|
10
8
|
return JSON.parse(raw);
|
|
11
9
|
}
|
|
12
10
|
catch {
|
|
13
11
|
return null;
|
|
14
12
|
}
|
|
15
13
|
}
|
|
16
|
-
export function writeCache(data) {
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
export function writeCache(data, dir) {
|
|
15
|
+
const d = dir || CACHE_DIR;
|
|
16
|
+
mkdirSync(d, { recursive: true });
|
|
17
|
+
writeFileSync(join(d, "cache.json"), JSON.stringify(data, null, 2) + "\n");
|
|
19
18
|
}
|
|
20
|
-
export function readConfig() {
|
|
19
|
+
export function readConfig(dir) {
|
|
21
20
|
try {
|
|
22
|
-
const raw = readFileSync(
|
|
21
|
+
const raw = readFileSync(join(dir || CACHE_DIR, "config.json"), "utf-8");
|
|
23
22
|
return JSON.parse(raw);
|
|
24
23
|
}
|
|
25
24
|
catch {
|
|
26
25
|
return { period: "7d" };
|
|
27
26
|
}
|
|
28
27
|
}
|
|
29
|
-
export function writeConfig(data) {
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
export function writeConfig(data, dir) {
|
|
29
|
+
const d = dir || CACHE_DIR;
|
|
30
|
+
mkdirSync(d, { recursive: true });
|
|
31
|
+
writeFileSync(join(d, "config.json"), JSON.stringify(data, null, 2) + "\n");
|
|
32
32
|
}
|
|
33
33
|
export { CACHE_DIR };
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { join, dirname } from "node:path";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
5
8
|
import { collectCosts } from "./collector.js";
|
|
6
9
|
import { writeCache, writeConfig, readConfig, CACHE_DIR } from "./cache.js";
|
|
7
10
|
import { render } from "./statusline.js";
|
|
@@ -10,11 +13,16 @@ const RENDER_COMMAND = "cc-costline render";
|
|
|
10
13
|
const REFRESH_COMMAND = "cc-costline refresh";
|
|
11
14
|
// ─── Helpers ──────────────────────────────────────────────
|
|
12
15
|
function readSettings() {
|
|
16
|
+
if (!existsSync(SETTINGS_PATH))
|
|
17
|
+
return {};
|
|
18
|
+
const raw = readFileSync(SETTINGS_PATH, "utf-8");
|
|
13
19
|
try {
|
|
14
|
-
return JSON.parse(
|
|
20
|
+
return JSON.parse(raw);
|
|
15
21
|
}
|
|
16
22
|
catch {
|
|
17
|
-
|
|
23
|
+
console.error(`✗ Failed to parse ${SETTINGS_PATH} — aborting to avoid overwriting your config.`);
|
|
24
|
+
console.error(" Please fix the JSON syntax and retry.");
|
|
25
|
+
process.exit(1);
|
|
18
26
|
}
|
|
19
27
|
}
|
|
20
28
|
function saveSettings(settings) {
|
|
@@ -143,7 +151,7 @@ switch (command) {
|
|
|
143
151
|
cmdRender();
|
|
144
152
|
break;
|
|
145
153
|
default:
|
|
146
|
-
console.log(`cc-costline
|
|
154
|
+
console.log(`cc-costline v${pkg.version} — Enhanced statusline for Claude Code
|
|
147
155
|
|
|
148
156
|
Commands:
|
|
149
157
|
install Configure Claude Code to use cc-costline
|
package/dist/collector.d.ts
CHANGED
package/dist/collector.js
CHANGED
|
@@ -31,8 +31,8 @@ function findJsonlFiles(dir) {
|
|
|
31
31
|
}
|
|
32
32
|
return results;
|
|
33
33
|
}
|
|
34
|
-
export function collectCosts() {
|
|
35
|
-
const projectsDir = join(homedir(), CLAUDE_PROJECTS_DIR);
|
|
34
|
+
export function collectCosts(baseDir) {
|
|
35
|
+
const projectsDir = baseDir || join(homedir(), CLAUDE_PROJECTS_DIR);
|
|
36
36
|
const files = findJsonlFiles(projectsDir);
|
|
37
37
|
if (files.length === 0) {
|
|
38
38
|
return { cost7d: 0, cost30d: 0 };
|
|
@@ -73,7 +73,7 @@ export function collectCosts() {
|
|
|
73
73
|
const sessionId = parsed.sessionId || "";
|
|
74
74
|
const dedupeKey = requestId
|
|
75
75
|
? `${sessionId}:${requestId}`
|
|
76
|
-
: `${sessionId}:${parsed.timestamp}:${usage.input_tokens}:${usage.output_tokens}`;
|
|
76
|
+
: `${sessionId}:${parsed.timestamp}:${parsed.message.model}:${usage.input_tokens}:${usage.output_tokens}:${usage.cache_creation_input_tokens || 0}:${usage.cache_read_input_tokens || 0}`;
|
|
77
77
|
if (seen.has(dedupeKey))
|
|
78
78
|
continue;
|
|
79
79
|
seen.add(dedupeKey);
|
package/dist/statusline.js
CHANGED
|
@@ -2,7 +2,10 @@ 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";
|
|
5
|
-
import { readCache, readConfig } from "./cache.js";
|
|
5
|
+
import { readCache, writeCache, readConfig } from "./cache.js";
|
|
6
|
+
import { collectCosts } from "./collector.js";
|
|
7
|
+
// Unified TTL for all cached data (2 minutes)
|
|
8
|
+
const CACHE_TTL_MS = 120_000;
|
|
6
9
|
// ANSI colors (matching original statusline.sh)
|
|
7
10
|
const FG_GRAY = "\x1b[38;5;245m";
|
|
8
11
|
const FG_GRAY_DIM = "\x1b[38;5;102m";
|
|
@@ -46,8 +49,8 @@ export function formatCountdown(resetsAtMs) {
|
|
|
46
49
|
const minutes = totalMinutes % 60;
|
|
47
50
|
return `-${hours}:${String(minutes).padStart(2, "0")}`;
|
|
48
51
|
}
|
|
49
|
-
// ccclub rank fetcher —
|
|
50
|
-
function getCcclubRank(
|
|
52
|
+
// ccclub rank fetcher — TTL-based cache (stale fallback on failure)
|
|
53
|
+
function getCcclubRank() {
|
|
51
54
|
const configPath = join(homedir(), ".ccclub", "config.json");
|
|
52
55
|
if (!existsSync(configPath))
|
|
53
56
|
return null;
|
|
@@ -57,11 +60,8 @@ function getCcclubRank(sessionId) {
|
|
|
57
60
|
try {
|
|
58
61
|
const cached = JSON.parse(readFileSync(cacheFile, "utf-8"));
|
|
59
62
|
staleData = cached.data ?? null;
|
|
60
|
-
if (cached.sessionId === sessionId)
|
|
61
|
-
return staleData;
|
|
62
|
-
// If cache is less than 2 minutes old, reuse it to avoid rate limits
|
|
63
63
|
const cacheAge = Date.now() - (cached.timestamp || 0);
|
|
64
|
-
if (cacheAge <
|
|
64
|
+
if (cacheAge < CACHE_TTL_MS)
|
|
65
65
|
return staleData;
|
|
66
66
|
}
|
|
67
67
|
catch { }
|
|
@@ -83,12 +83,12 @@ function getCcclubRank(sessionId) {
|
|
|
83
83
|
if (!me)
|
|
84
84
|
return staleData;
|
|
85
85
|
const result = { rank: me.rank, total: rankings.length, cost: me.costUSD };
|
|
86
|
-
writeFileSync(cacheFile, JSON.stringify({
|
|
86
|
+
writeFileSync(cacheFile, JSON.stringify({ data: result, timestamp: Date.now() }), "utf-8");
|
|
87
87
|
return result;
|
|
88
88
|
}
|
|
89
89
|
catch {
|
|
90
90
|
try {
|
|
91
|
-
writeFileSync(cacheFile, JSON.stringify({
|
|
91
|
+
writeFileSync(cacheFile, JSON.stringify({ data: staleData, timestamp: Date.now() }), "utf-8");
|
|
92
92
|
}
|
|
93
93
|
catch { }
|
|
94
94
|
return staleData;
|
|
@@ -103,8 +103,8 @@ export function rankColor(rank) {
|
|
|
103
103
|
return FG_ORANGE;
|
|
104
104
|
return FG_CYAN;
|
|
105
105
|
}
|
|
106
|
-
// Claude usage fetcher —
|
|
107
|
-
function getClaudeUsage(
|
|
106
|
+
// Claude usage fetcher — TTL-based cache (stale fallback on failure)
|
|
107
|
+
function getClaudeUsage() {
|
|
108
108
|
const cacheFile = "/tmp/sl-claude-usage";
|
|
109
109
|
const hitFile = "/tmp/sl-claude-usage-hit";
|
|
110
110
|
const now = Date.now();
|
|
@@ -113,11 +113,8 @@ function getClaudeUsage(sessionId) {
|
|
|
113
113
|
try {
|
|
114
114
|
const cached = JSON.parse(readFileSync(cacheFile, "utf-8"));
|
|
115
115
|
staleData = cached.data ?? null;
|
|
116
|
-
if (cached.sessionId === sessionId)
|
|
117
|
-
return staleData;
|
|
118
|
-
// If cache is less than 2 minutes old, reuse it to avoid rate limits
|
|
119
116
|
const cacheAge = now - (cached.timestamp || 0);
|
|
120
|
-
if (cacheAge <
|
|
117
|
+
if (cacheAge < CACHE_TTL_MS)
|
|
121
118
|
return staleData;
|
|
122
119
|
}
|
|
123
120
|
catch { }
|
|
@@ -140,7 +137,7 @@ function getClaudeUsage(sessionId) {
|
|
|
140
137
|
const response = execSync(curlCmd, { encoding: "utf-8", timeout: 5000 });
|
|
141
138
|
if (!response) {
|
|
142
139
|
// API failed — write cache with null data to prevent retry flood
|
|
143
|
-
writeFileSync(cacheFile, JSON.stringify({
|
|
140
|
+
writeFileSync(cacheFile, JSON.stringify({ data: null, timestamp: now }), "utf-8");
|
|
144
141
|
return staleData;
|
|
145
142
|
}
|
|
146
143
|
const data = JSON.parse(response);
|
|
@@ -187,13 +184,13 @@ function getClaudeUsage(sessionId) {
|
|
|
187
184
|
const result = { fiveHour, sevenDay };
|
|
188
185
|
if (fiveHourResetsAt)
|
|
189
186
|
result.fiveHourResetsAt = fiveHourResetsAt;
|
|
190
|
-
writeFileSync(cacheFile, JSON.stringify({
|
|
187
|
+
writeFileSync(cacheFile, JSON.stringify({ data: result, timestamp: now }), "utf-8");
|
|
191
188
|
return result;
|
|
192
189
|
}
|
|
193
190
|
catch {
|
|
194
191
|
// Write cache with stale/null data to prevent retry flood on persistent failures
|
|
195
192
|
try {
|
|
196
|
-
writeFileSync(cacheFile, JSON.stringify({
|
|
193
|
+
writeFileSync(cacheFile, JSON.stringify({ data: staleData, timestamp: now }), "utf-8");
|
|
197
194
|
}
|
|
198
195
|
catch { }
|
|
199
196
|
return staleData;
|
|
@@ -211,9 +208,7 @@ export function render(input) {
|
|
|
211
208
|
const cost = data.cost?.total_cost_usd ?? 0;
|
|
212
209
|
const model = data.model?.display_name ?? "—";
|
|
213
210
|
const contextPct = Math.floor(data.context_window?.used_percentage ?? 0);
|
|
214
|
-
// Session ID from transcript path (filename without extension)
|
|
215
211
|
const transcriptPath = data.transcript_path ?? "";
|
|
216
|
-
const sessionId = transcriptPath ? transcriptPath.replace(/^.*\//, "").replace(/\.jsonl$/, "") : "";
|
|
217
212
|
// Token stats from transcript
|
|
218
213
|
let totalTokens = 0;
|
|
219
214
|
if (transcriptPath) {
|
|
@@ -234,9 +229,23 @@ export function render(input) {
|
|
|
234
229
|
}
|
|
235
230
|
catch { }
|
|
236
231
|
}
|
|
237
|
-
|
|
232
|
+
// Refresh local cost cache if stale
|
|
233
|
+
let cache = readCache();
|
|
234
|
+
const cacheAge = cache ? Date.now() - new Date(cache.updatedAt).getTime() : Infinity;
|
|
235
|
+
if (cacheAge >= CACHE_TTL_MS) {
|
|
236
|
+
try {
|
|
237
|
+
const result = collectCosts();
|
|
238
|
+
// Don't overwrite valid cache with zeros (directory read failure)
|
|
239
|
+
if (result.cost7d > 0 || result.cost30d > 0 || !cache) {
|
|
240
|
+
const newCache = { cost7d: result.cost7d, cost30d: result.cost30d, updatedAt: new Date().toISOString() };
|
|
241
|
+
writeCache(newCache);
|
|
242
|
+
cache = newCache;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch { }
|
|
246
|
+
}
|
|
238
247
|
const config = readConfig();
|
|
239
|
-
const claudeUsage = getClaudeUsage(
|
|
248
|
+
const claudeUsage = getClaudeUsage();
|
|
240
249
|
const g = FG_GRAY_DIM;
|
|
241
250
|
const y = FG_YELLOW;
|
|
242
251
|
const m = FG_MODEL;
|
|
@@ -262,14 +271,20 @@ export function render(input) {
|
|
|
262
271
|
}
|
|
263
272
|
if (cache) {
|
|
264
273
|
const period = config.period || "30d";
|
|
265
|
-
|
|
266
|
-
|
|
274
|
+
if (period === "both") {
|
|
275
|
+
usageParts.push(`${y}7d:${formatCost(cache.cost7d)}${r}`);
|
|
276
|
+
usageParts.push(`${y}30d:${formatCost(cache.cost30d)}${r}`);
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
const periodCost = period === "7d" ? cache.cost7d : cache.cost30d;
|
|
280
|
+
usageParts.push(`${y}${period}:${formatCost(periodCost)}${r}`);
|
|
281
|
+
}
|
|
267
282
|
}
|
|
268
283
|
if (usageParts.length > 0) {
|
|
269
284
|
segments.push(usageParts.join(` ${g}·${r} `));
|
|
270
285
|
}
|
|
271
286
|
// #2 $53.6
|
|
272
|
-
const ccclubRank = getCcclubRank(
|
|
287
|
+
const ccclubRank = getCcclubRank();
|
|
273
288
|
if (ccclubRank) {
|
|
274
289
|
const rc = rankColor(ccclubRank.rank);
|
|
275
290
|
segments.push(`${rc}#${ccclubRank.rank} ${formatCost(ccclubRank.cost)}${r}`);
|