cc-costline 0.3.1 → 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 +54 -19
- 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,7 +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
|
-
|
|
63
|
+
const cacheAge = Date.now() - (cached.timestamp || 0);
|
|
64
|
+
if (cacheAge < CACHE_TTL_MS)
|
|
61
65
|
return staleData;
|
|
62
66
|
}
|
|
63
67
|
catch { }
|
|
@@ -79,10 +83,14 @@ function getCcclubRank(sessionId) {
|
|
|
79
83
|
if (!me)
|
|
80
84
|
return staleData;
|
|
81
85
|
const result = { rank: me.rank, total: rankings.length, cost: me.costUSD };
|
|
82
|
-
writeFileSync(cacheFile, JSON.stringify({
|
|
86
|
+
writeFileSync(cacheFile, JSON.stringify({ data: result, timestamp: Date.now() }), "utf-8");
|
|
83
87
|
return result;
|
|
84
88
|
}
|
|
85
89
|
catch {
|
|
90
|
+
try {
|
|
91
|
+
writeFileSync(cacheFile, JSON.stringify({ data: staleData, timestamp: Date.now() }), "utf-8");
|
|
92
|
+
}
|
|
93
|
+
catch { }
|
|
86
94
|
return staleData;
|
|
87
95
|
}
|
|
88
96
|
}
|
|
@@ -95,8 +103,8 @@ export function rankColor(rank) {
|
|
|
95
103
|
return FG_ORANGE;
|
|
96
104
|
return FG_CYAN;
|
|
97
105
|
}
|
|
98
|
-
// Claude usage fetcher —
|
|
99
|
-
function getClaudeUsage(
|
|
106
|
+
// Claude usage fetcher — TTL-based cache (stale fallback on failure)
|
|
107
|
+
function getClaudeUsage() {
|
|
100
108
|
const cacheFile = "/tmp/sl-claude-usage";
|
|
101
109
|
const hitFile = "/tmp/sl-claude-usage-hit";
|
|
102
110
|
const now = Date.now();
|
|
@@ -105,7 +113,8 @@ function getClaudeUsage(sessionId) {
|
|
|
105
113
|
try {
|
|
106
114
|
const cached = JSON.parse(readFileSync(cacheFile, "utf-8"));
|
|
107
115
|
staleData = cached.data ?? null;
|
|
108
|
-
|
|
116
|
+
const cacheAge = now - (cached.timestamp || 0);
|
|
117
|
+
if (cacheAge < CACHE_TTL_MS)
|
|
109
118
|
return staleData;
|
|
110
119
|
}
|
|
111
120
|
catch { }
|
|
@@ -124,10 +133,13 @@ function getClaudeUsage(sessionId) {
|
|
|
124
133
|
if (expiresAt && Date.now() / 1000 > expiresAt)
|
|
125
134
|
return null;
|
|
126
135
|
const apiUrl = "https://api.anthropic.com/api/oauth/usage";
|
|
127
|
-
const curlCmd = `curl -sf "${apiUrl}" -H "Authorization: Bearer ${accessToken}" -H "anthropic-beta: oauth-2025-04-20"
|
|
136
|
+
const curlCmd = `curl -sf "${apiUrl}" -H "Authorization: Bearer ${accessToken}" -H "anthropic-beta: oauth-2025-04-20"`;
|
|
128
137
|
const response = execSync(curlCmd, { encoding: "utf-8", timeout: 5000 });
|
|
129
|
-
if (!response)
|
|
130
|
-
|
|
138
|
+
if (!response) {
|
|
139
|
+
// API failed — write cache with null data to prevent retry flood
|
|
140
|
+
writeFileSync(cacheFile, JSON.stringify({ data: null, timestamp: now }), "utf-8");
|
|
141
|
+
return staleData;
|
|
142
|
+
}
|
|
131
143
|
const data = JSON.parse(response);
|
|
132
144
|
const parseUtil = (val) => {
|
|
133
145
|
if (typeof val === "number")
|
|
@@ -172,10 +184,15 @@ function getClaudeUsage(sessionId) {
|
|
|
172
184
|
const result = { fiveHour, sevenDay };
|
|
173
185
|
if (fiveHourResetsAt)
|
|
174
186
|
result.fiveHourResetsAt = fiveHourResetsAt;
|
|
175
|
-
writeFileSync(cacheFile, JSON.stringify({
|
|
187
|
+
writeFileSync(cacheFile, JSON.stringify({ data: result, timestamp: now }), "utf-8");
|
|
176
188
|
return result;
|
|
177
189
|
}
|
|
178
190
|
catch {
|
|
191
|
+
// Write cache with stale/null data to prevent retry flood on persistent failures
|
|
192
|
+
try {
|
|
193
|
+
writeFileSync(cacheFile, JSON.stringify({ data: staleData, timestamp: now }), "utf-8");
|
|
194
|
+
}
|
|
195
|
+
catch { }
|
|
179
196
|
return staleData;
|
|
180
197
|
}
|
|
181
198
|
}
|
|
@@ -191,9 +208,7 @@ export function render(input) {
|
|
|
191
208
|
const cost = data.cost?.total_cost_usd ?? 0;
|
|
192
209
|
const model = data.model?.display_name ?? "—";
|
|
193
210
|
const contextPct = Math.floor(data.context_window?.used_percentage ?? 0);
|
|
194
|
-
// Session ID from transcript path (filename without extension)
|
|
195
211
|
const transcriptPath = data.transcript_path ?? "";
|
|
196
|
-
const sessionId = transcriptPath ? transcriptPath.replace(/^.*\//, "").replace(/\.jsonl$/, "") : "";
|
|
197
212
|
// Token stats from transcript
|
|
198
213
|
let totalTokens = 0;
|
|
199
214
|
if (transcriptPath) {
|
|
@@ -214,9 +229,23 @@ export function render(input) {
|
|
|
214
229
|
}
|
|
215
230
|
catch { }
|
|
216
231
|
}
|
|
217
|
-
|
|
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
|
+
}
|
|
218
247
|
const config = readConfig();
|
|
219
|
-
const claudeUsage = getClaudeUsage(
|
|
248
|
+
const claudeUsage = getClaudeUsage();
|
|
220
249
|
const g = FG_GRAY_DIM;
|
|
221
250
|
const y = FG_YELLOW;
|
|
222
251
|
const m = FG_MODEL;
|
|
@@ -242,14 +271,20 @@ export function render(input) {
|
|
|
242
271
|
}
|
|
243
272
|
if (cache) {
|
|
244
273
|
const period = config.period || "30d";
|
|
245
|
-
|
|
246
|
-
|
|
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
|
+
}
|
|
247
282
|
}
|
|
248
283
|
if (usageParts.length > 0) {
|
|
249
284
|
segments.push(usageParts.join(` ${g}·${r} `));
|
|
250
285
|
}
|
|
251
286
|
// #2 $53.6
|
|
252
|
-
const ccclubRank = getCcclubRank(
|
|
287
|
+
const ccclubRank = getCcclubRank();
|
|
253
288
|
if (ccclubRank) {
|
|
254
289
|
const rc = rankColor(ccclubRank.rank);
|
|
255
290
|
segments.push(`${rc}#${ccclubRank.rank} ${formatCost(ccclubRank.cost)}${r}`);
|