cc-costline 0.3.2 → 0.4.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 +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.d.ts +2 -0
- package/dist/statusline.js +56 -26
- 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.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { CacheData } from "./cache.js";
|
|
1
2
|
export declare function formatTokens(t: number): string;
|
|
2
3
|
export declare function formatCost(n: number): string;
|
|
3
4
|
export declare function ctxColor(pct: number): string;
|
|
4
5
|
export declare function formatCountdown(resetsAtMs: number): string;
|
|
6
|
+
export declare function shouldRefreshLocalCostCache(cache: CacheData | null, transcriptPath?: string, now?: number): boolean;
|
|
5
7
|
export declare function rankColor(rank: number): string;
|
|
6
8
|
export declare function render(input: string): string;
|
package/dist/statusline.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { readFileSync, existsSync, writeFileSync, unlinkSync } from "node:fs";
|
|
1
|
+
import { readFileSync, existsSync, statSync, 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,24 @@ export function formatCountdown(resetsAtMs) {
|
|
|
46
49
|
const minutes = totalMinutes % 60;
|
|
47
50
|
return `-${hours}:${String(minutes).padStart(2, "0")}`;
|
|
48
51
|
}
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
export function shouldRefreshLocalCostCache(cache, transcriptPath = "", now = Date.now()) {
|
|
53
|
+
if (!cache)
|
|
54
|
+
return true;
|
|
55
|
+
const cacheUpdatedAt = new Date(cache.updatedAt).getTime();
|
|
56
|
+
if (isNaN(cacheUpdatedAt))
|
|
57
|
+
return true;
|
|
58
|
+
if (transcriptPath) {
|
|
59
|
+
try {
|
|
60
|
+
const transcriptMtime = statSync(transcriptPath).mtimeMs;
|
|
61
|
+
if (transcriptMtime > cacheUpdatedAt)
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
}
|
|
66
|
+
return now - cacheUpdatedAt >= CACHE_TTL_MS;
|
|
67
|
+
}
|
|
68
|
+
// ccclub rank fetcher — TTL-based cache (stale fallback on failure)
|
|
69
|
+
function getCcclubRank() {
|
|
51
70
|
const configPath = join(homedir(), ".ccclub", "config.json");
|
|
52
71
|
if (!existsSync(configPath))
|
|
53
72
|
return null;
|
|
@@ -57,11 +76,8 @@ function getCcclubRank(sessionId) {
|
|
|
57
76
|
try {
|
|
58
77
|
const cached = JSON.parse(readFileSync(cacheFile, "utf-8"));
|
|
59
78
|
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
79
|
const cacheAge = Date.now() - (cached.timestamp || 0);
|
|
64
|
-
if (cacheAge <
|
|
80
|
+
if (cacheAge < CACHE_TTL_MS)
|
|
65
81
|
return staleData;
|
|
66
82
|
}
|
|
67
83
|
catch { }
|
|
@@ -83,12 +99,12 @@ function getCcclubRank(sessionId) {
|
|
|
83
99
|
if (!me)
|
|
84
100
|
return staleData;
|
|
85
101
|
const result = { rank: me.rank, total: rankings.length, cost: me.costUSD };
|
|
86
|
-
writeFileSync(cacheFile, JSON.stringify({
|
|
102
|
+
writeFileSync(cacheFile, JSON.stringify({ data: result, timestamp: Date.now() }), "utf-8");
|
|
87
103
|
return result;
|
|
88
104
|
}
|
|
89
105
|
catch {
|
|
90
106
|
try {
|
|
91
|
-
writeFileSync(cacheFile, JSON.stringify({
|
|
107
|
+
writeFileSync(cacheFile, JSON.stringify({ data: staleData, timestamp: Date.now() }), "utf-8");
|
|
92
108
|
}
|
|
93
109
|
catch { }
|
|
94
110
|
return staleData;
|
|
@@ -103,8 +119,8 @@ export function rankColor(rank) {
|
|
|
103
119
|
return FG_ORANGE;
|
|
104
120
|
return FG_CYAN;
|
|
105
121
|
}
|
|
106
|
-
// Claude usage fetcher —
|
|
107
|
-
function getClaudeUsage(
|
|
122
|
+
// Claude usage fetcher — TTL-based cache (stale fallback on failure)
|
|
123
|
+
function getClaudeUsage() {
|
|
108
124
|
const cacheFile = "/tmp/sl-claude-usage";
|
|
109
125
|
const hitFile = "/tmp/sl-claude-usage-hit";
|
|
110
126
|
const now = Date.now();
|
|
@@ -113,11 +129,8 @@ function getClaudeUsage(sessionId) {
|
|
|
113
129
|
try {
|
|
114
130
|
const cached = JSON.parse(readFileSync(cacheFile, "utf-8"));
|
|
115
131
|
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
132
|
const cacheAge = now - (cached.timestamp || 0);
|
|
120
|
-
if (cacheAge <
|
|
133
|
+
if (cacheAge < CACHE_TTL_MS)
|
|
121
134
|
return staleData;
|
|
122
135
|
}
|
|
123
136
|
catch { }
|
|
@@ -140,7 +153,7 @@ function getClaudeUsage(sessionId) {
|
|
|
140
153
|
const response = execSync(curlCmd, { encoding: "utf-8", timeout: 5000 });
|
|
141
154
|
if (!response) {
|
|
142
155
|
// API failed — write cache with null data to prevent retry flood
|
|
143
|
-
writeFileSync(cacheFile, JSON.stringify({
|
|
156
|
+
writeFileSync(cacheFile, JSON.stringify({ data: null, timestamp: now }), "utf-8");
|
|
144
157
|
return staleData;
|
|
145
158
|
}
|
|
146
159
|
const data = JSON.parse(response);
|
|
@@ -187,13 +200,13 @@ function getClaudeUsage(sessionId) {
|
|
|
187
200
|
const result = { fiveHour, sevenDay };
|
|
188
201
|
if (fiveHourResetsAt)
|
|
189
202
|
result.fiveHourResetsAt = fiveHourResetsAt;
|
|
190
|
-
writeFileSync(cacheFile, JSON.stringify({
|
|
203
|
+
writeFileSync(cacheFile, JSON.stringify({ data: result, timestamp: now }), "utf-8");
|
|
191
204
|
return result;
|
|
192
205
|
}
|
|
193
206
|
catch {
|
|
194
207
|
// Write cache with stale/null data to prevent retry flood on persistent failures
|
|
195
208
|
try {
|
|
196
|
-
writeFileSync(cacheFile, JSON.stringify({
|
|
209
|
+
writeFileSync(cacheFile, JSON.stringify({ data: staleData, timestamp: now }), "utf-8");
|
|
197
210
|
}
|
|
198
211
|
catch { }
|
|
199
212
|
return staleData;
|
|
@@ -211,9 +224,7 @@ export function render(input) {
|
|
|
211
224
|
const cost = data.cost?.total_cost_usd ?? 0;
|
|
212
225
|
const model = data.model?.display_name ?? "—";
|
|
213
226
|
const contextPct = Math.floor(data.context_window?.used_percentage ?? 0);
|
|
214
|
-
// Session ID from transcript path (filename without extension)
|
|
215
227
|
const transcriptPath = data.transcript_path ?? "";
|
|
216
|
-
const sessionId = transcriptPath ? transcriptPath.replace(/^.*\//, "").replace(/\.jsonl$/, "") : "";
|
|
217
228
|
// Token stats from transcript
|
|
218
229
|
let totalTokens = 0;
|
|
219
230
|
if (transcriptPath) {
|
|
@@ -234,9 +245,22 @@ export function render(input) {
|
|
|
234
245
|
}
|
|
235
246
|
catch { }
|
|
236
247
|
}
|
|
237
|
-
|
|
248
|
+
// Refresh local cost cache if stale
|
|
249
|
+
let cache = readCache();
|
|
250
|
+
if (shouldRefreshLocalCostCache(cache, transcriptPath)) {
|
|
251
|
+
try {
|
|
252
|
+
const result = collectCosts();
|
|
253
|
+
// Don't overwrite valid cache with zeros (directory read failure)
|
|
254
|
+
if (result.cost7d > 0 || result.cost30d > 0 || !cache) {
|
|
255
|
+
const newCache = { cost7d: result.cost7d, cost30d: result.cost30d, updatedAt: new Date().toISOString() };
|
|
256
|
+
writeCache(newCache);
|
|
257
|
+
cache = newCache;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch { }
|
|
261
|
+
}
|
|
238
262
|
const config = readConfig();
|
|
239
|
-
const claudeUsage = getClaudeUsage(
|
|
263
|
+
const claudeUsage = getClaudeUsage();
|
|
240
264
|
const g = FG_GRAY_DIM;
|
|
241
265
|
const y = FG_YELLOW;
|
|
242
266
|
const m = FG_MODEL;
|
|
@@ -262,14 +286,20 @@ export function render(input) {
|
|
|
262
286
|
}
|
|
263
287
|
if (cache) {
|
|
264
288
|
const period = config.period || "30d";
|
|
265
|
-
|
|
266
|
-
|
|
289
|
+
if (period === "both") {
|
|
290
|
+
usageParts.push(`${y}7d:${formatCost(cache.cost7d)}${r}`);
|
|
291
|
+
usageParts.push(`${y}30d:${formatCost(cache.cost30d)}${r}`);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
const periodCost = period === "7d" ? cache.cost7d : cache.cost30d;
|
|
295
|
+
usageParts.push(`${y}${period}:${formatCost(periodCost)}${r}`);
|
|
296
|
+
}
|
|
267
297
|
}
|
|
268
298
|
if (usageParts.length > 0) {
|
|
269
299
|
segments.push(usageParts.join(` ${g}·${r} `));
|
|
270
300
|
}
|
|
271
301
|
// #2 $53.6
|
|
272
|
-
const ccclubRank = getCcclubRank(
|
|
302
|
+
const ccclubRank = getCcclubRank();
|
|
273
303
|
if (ccclubRank) {
|
|
274
304
|
const rc = rankColor(ccclubRank.rank);
|
|
275
305
|
segments.push(`${rc}#${ccclubRank.rank} ${formatCost(ccclubRank.cost)}${r}`);
|