cc-context-stats 1.5.0 → 1.6.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/.github/ISSUE_TEMPLATE/bug_report.md +49 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +33 -0
- package/.github/workflows/ci.yml +39 -2
- package/.github/workflows/release.yml +3 -1
- package/CHANGELOG.md +16 -8
- package/CLAUDE.md +54 -0
- package/CODE_OF_CONDUCT.md +59 -0
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/RELEASE_NOTES.md +16 -6
- package/SECURITY.md +44 -0
- package/TODOS.md +72 -0
- package/assets/logo/favicon.svg +17 -14
- package/assets/logo/logo-black.svg +19 -18
- package/assets/logo/logo-full.svg +39 -29
- package/assets/logo/logo-icon.svg +20 -19
- package/assets/logo/logo-mark.svg +21 -19
- package/assets/logo/logo-white.svg +19 -18
- package/assets/logo/logo-wordmark.svg +5 -6
- package/docs/ARCHITECTURE.md +101 -0
- package/docs/CSV_FORMAT.md +40 -0
- package/docs/DEPLOYMENT.md +60 -0
- package/docs/DEVELOPMENT.md +125 -0
- package/package.json +2 -2
- package/pyproject.toml +1 -1
- package/scripts/statusline-full.sh +11 -3
- package/scripts/statusline-git.sh +8 -1
- package/scripts/statusline-minimal.sh +8 -1
- package/scripts/statusline.js +62 -8
- package/scripts/statusline.py +24 -10
- package/src/claude_statusline/__init__.py +1 -1
- package/src/claude_statusline/cli/context_stats.py +20 -1
- package/src/claude_statusline/core/config.py +5 -4
- package/src/claude_statusline/core/state.py +64 -7
- package/src/claude_statusline/formatters/layout.py +17 -2
- package/tests/bash/test_parity.bats +315 -0
- package/tests/fixtures/json/comma_in_path.json +31 -0
- package/tests/node/rotation.test.js +89 -0
- package/tests/python/test_data_pipeline.py +446 -0
- package/tests/python/test_layout.py +19 -2
- package/tests/python/test_state_rotation_validation.py +232 -0
- package/tests/python/test_statusline.py +2 -0
- package/.claude/commands/context-stats.md +0 -17
- package/.claude/settings.local.json +0 -117
|
@@ -1,30 +1,40 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<!--
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 72" width="360" height="72">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="full-wave-grad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
4
|
+
<stop offset="0%" stop-color="#22C55E"/>
|
|
5
|
+
<stop offset="50%" stop-color="#E9AB34"/>
|
|
6
|
+
<stop offset="100%" stop-color="#EF4444"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
9
|
+
|
|
10
|
+
<!-- Background pill -->
|
|
11
|
+
<rect width="360" height="72" rx="14" fill="#1A1A1A"/>
|
|
12
|
+
|
|
13
|
+
<!-- Mark: Context wave in left area -->
|
|
14
|
+
<g transform="translate(8, 4)">
|
|
15
|
+
<!-- Context wave -->
|
|
16
|
+
<path d="M 10 48 C 16 48, 18 42, 22 40 C 26 38, 28 36, 32 32 C 36 28, 38 22, 42 18 C 46 14, 50 16, 54 14"
|
|
17
|
+
stroke="url(#full-wave-grad)" stroke-width="3.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
|
18
|
+
|
|
19
|
+
<!-- Echo line -->
|
|
20
|
+
<path d="M 10 52 C 16 52, 18 46, 22 44 C 26 42, 28 40, 32 36 C 36 32, 38 26, 42 22 C 46 18, 50 20, 54 18"
|
|
21
|
+
stroke="url(#full-wave-grad)" stroke-width="1.5" fill="none" stroke-linecap="round" opacity="0.3"/>
|
|
22
|
+
|
|
23
|
+
<!-- Cursor dot -->
|
|
24
|
+
<circle cx="42" cy="18" r="3.5" fill="#E9AB34"/>
|
|
25
|
+
<circle cx="42" cy="18" r="6" fill="#E9AB34" opacity="0.15"/>
|
|
26
|
+
|
|
27
|
+
<!-- Baseline -->
|
|
28
|
+
<line x1="10" y1="54" x2="54" y2="54" stroke="#8B7D6B" stroke-width="1" stroke-linecap="round" opacity="0.4"/>
|
|
29
|
+
</g>
|
|
30
|
+
|
|
31
|
+
<!-- Divider -->
|
|
32
|
+
<line x1="76" y1="16" x2="76" y2="56" stroke="#8B7D6B" stroke-width="1" opacity="0.25"/>
|
|
33
|
+
|
|
34
|
+
<!-- Wordmark -->
|
|
35
|
+
<text x="90" y="35" font-family="'SF Mono', 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace" font-size="22" font-weight="700" fill="#FFFFFF" letter-spacing="-0.3">cc-context</text>
|
|
36
|
+
<text x="90" y="55" font-family="'SF Mono', 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace" font-size="15" font-weight="500" fill="#E9AB34" letter-spacing="3">stats</text>
|
|
37
|
+
|
|
38
|
+
<!-- Right accent bar -->
|
|
39
|
+
<rect x="348" y="20" width="2" height="32" rx="1" fill="#E9AB34" opacity="0.5"/>
|
|
30
40
|
</svg>
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="icon-wave-grad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
4
|
+
<stop offset="0%" stop-color="#22C55E"/>
|
|
5
|
+
<stop offset="50%" stop-color="#E9AB34"/>
|
|
6
|
+
<stop offset="100%" stop-color="#EF4444"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
4
9
|
|
|
5
|
-
<!--
|
|
6
|
-
<
|
|
10
|
+
<!-- Background -->
|
|
11
|
+
<rect width="512" height="512" rx="96" fill="#1A1A1A"/>
|
|
7
12
|
|
|
8
|
-
<!--
|
|
9
|
-
<path d="M 96
|
|
13
|
+
<!-- Context wave (scaled up) -->
|
|
14
|
+
<path d="M 96 376 C 144 376, 160 328, 192 312 C 224 296, 240 280, 272 248 C 304 216, 320 168, 352 136 C 384 104, 400 120, 432 104"
|
|
15
|
+
stroke="url(#icon-wave-grad)" stroke-width="24" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
|
10
16
|
|
|
11
|
-
<!--
|
|
12
|
-
<path d="M
|
|
17
|
+
<!-- Echo line -->
|
|
18
|
+
<path d="M 96 408 C 144 408, 160 360, 192 344 C 224 328, 240 312, 272 280 C 304 248, 320 200, 352 168 C 384 136, 400 152, 432 136"
|
|
19
|
+
stroke="url(#icon-wave-grad)" stroke-width="10" fill="none" stroke-linecap="round" opacity="0.25"/>
|
|
13
20
|
|
|
14
|
-
<!--
|
|
15
|
-
<
|
|
21
|
+
<!-- Cursor dot -->
|
|
22
|
+
<circle cx="352" cy="136" r="28" fill="#E9AB34"/>
|
|
23
|
+
<circle cx="352" cy="136" r="48" fill="#E9AB34" opacity="0.12"/>
|
|
16
24
|
|
|
17
|
-
<!--
|
|
18
|
-
<line x1="
|
|
19
|
-
|
|
20
|
-
<!-- Pivot dot -->
|
|
21
|
-
<circle cx="256" cy="256" r="24" fill="#22C55E"/>
|
|
22
|
-
|
|
23
|
-
<!-- Label dots at zone boundaries -->
|
|
24
|
-
<circle cx="96" cy="352" r="8" fill="#6B7280"/>
|
|
25
|
-
<circle cx="416" cy="352" r="8" fill="#6B7280"/>
|
|
25
|
+
<!-- Baseline -->
|
|
26
|
+
<line x1="96" y1="424" x2="432" y2="424" stroke="#8B7D6B" stroke-width="6" stroke-linecap="round" opacity="0.35"/>
|
|
26
27
|
</svg>
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="wave-grad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
4
|
+
<stop offset="0%" stop-color="#22C55E"/>
|
|
5
|
+
<stop offset="50%" stop-color="#E9AB34"/>
|
|
6
|
+
<stop offset="100%" stop-color="#EF4444"/>
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
4
9
|
|
|
5
|
-
<!--
|
|
6
|
-
<
|
|
10
|
+
<!-- Background -->
|
|
11
|
+
<rect width="64" height="64" rx="14" fill="#1A1A1A"/>
|
|
7
12
|
|
|
8
|
-
<!--
|
|
9
|
-
|
|
13
|
+
<!-- Context wave: a flowing curve rising from left (low usage) to right (high usage) -->
|
|
14
|
+
<!-- This represents the context growth graph - the core concept of the tool -->
|
|
15
|
+
<path d="M 10 48 C 16 48, 18 42, 22 40 C 26 38, 28 36, 32 32 C 36 28, 38 22, 42 18 C 46 14, 50 16, 54 14"
|
|
16
|
+
stroke="url(#wave-grad)" stroke-width="3.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
|
10
17
|
|
|
11
|
-
<!--
|
|
12
|
-
<path d="M
|
|
18
|
+
<!-- Secondary pulse line (echo effect for depth) -->
|
|
19
|
+
<path d="M 10 52 C 16 52, 18 46, 22 44 C 26 42, 28 40, 32 36 C 36 32, 38 26, 42 22 C 46 18, 50 20, 54 18"
|
|
20
|
+
stroke="url(#wave-grad)" stroke-width="1.5" fill="none" stroke-linecap="round" opacity="0.3"/>
|
|
13
21
|
|
|
14
|
-
<!--
|
|
15
|
-
<
|
|
22
|
+
<!-- Cursor dot at the "current position" on the wave -->
|
|
23
|
+
<circle cx="42" cy="18" r="3.5" fill="#E9AB34"/>
|
|
24
|
+
<circle cx="42" cy="18" r="6" fill="#E9AB34" opacity="0.15"/>
|
|
16
25
|
|
|
17
|
-
<!--
|
|
18
|
-
<line x1="
|
|
19
|
-
|
|
20
|
-
<!-- Needle pivot center dot -->
|
|
21
|
-
<circle cx="32" cy="32" r="3" fill="#22C55E"/>
|
|
22
|
-
|
|
23
|
-
<!-- Tick marks at zone boundaries -->
|
|
24
|
-
<line x1="12" y1="44" x2="14.5" y2="41.5" stroke="#6B7280" stroke-width="1.5" stroke-linecap="round"/>
|
|
25
|
-
<line x1="52" y1="44" x2="49.5" y2="41.5" stroke="#6B7280" stroke-width="1.5" stroke-linecap="round"/>
|
|
26
|
+
<!-- Baseline -->
|
|
27
|
+
<line x1="10" y1="54" x2="54" y2="54" stroke="#8B7D6B" stroke-width="1" stroke-linecap="round" opacity="0.4"/>
|
|
26
28
|
</svg>
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
2
|
-
<!-- White version for dark backgrounds — no
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 72" width="360" height="72">
|
|
2
|
+
<!-- White version for dark backgrounds — no background rectangle -->
|
|
3
3
|
|
|
4
|
-
<!--
|
|
5
|
-
<
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<!-- Pivot -->
|
|
15
|
-
<circle cx="32" cy="38" r="3" fill="#22C55E"/>
|
|
4
|
+
<!-- Mark: Context wave -->
|
|
5
|
+
<g transform="translate(8, 4)">
|
|
6
|
+
<path d="M 10 48 C 16 48, 18 42, 22 40 C 26 38, 28 36, 32 32 C 36 28, 38 22, 42 18 C 46 14, 50 16, 54 14"
|
|
7
|
+
stroke="#FFFFFF" stroke-width="3.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
|
8
|
+
<path d="M 10 52 C 16 52, 18 46, 22 44 C 26 42, 28 40, 32 36 C 36 32, 38 26, 42 22 C 46 18, 50 20, 54 18"
|
|
9
|
+
stroke="#FFFFFF" stroke-width="1.5" fill="none" stroke-linecap="round" opacity="0.25"/>
|
|
10
|
+
<circle cx="42" cy="18" r="3.5" fill="#FFFFFF"/>
|
|
11
|
+
<circle cx="42" cy="18" r="6" fill="#FFFFFF" opacity="0.12"/>
|
|
12
|
+
<line x1="10" y1="54" x2="54" y2="54" stroke="#FFFFFF" stroke-width="1" stroke-linecap="round" opacity="0.3"/>
|
|
13
|
+
</g>
|
|
16
14
|
|
|
17
15
|
<!-- Divider -->
|
|
18
|
-
<line x1="
|
|
16
|
+
<line x1="76" y1="16" x2="76" y2="56" stroke="#FFFFFF" stroke-width="1" opacity="0.2"/>
|
|
19
17
|
|
|
20
|
-
<!-- Wordmark
|
|
21
|
-
<text x="
|
|
22
|
-
<text x="
|
|
18
|
+
<!-- Wordmark -->
|
|
19
|
+
<text x="90" y="35" font-family="'SF Mono', 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace" font-size="22" font-weight="700" fill="#FFFFFF" letter-spacing="-0.3">cc-context</text>
|
|
20
|
+
<text x="90" y="55" font-family="'SF Mono', 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace" font-size="15" font-weight="500" fill="#FFFFFF" letter-spacing="3" opacity="0.7">stats</text>
|
|
21
|
+
|
|
22
|
+
<!-- Right accent bar -->
|
|
23
|
+
<rect x="348" y="20" width="2" height="32" rx="1" fill="#FFFFFF" opacity="0.4"/>
|
|
23
24
|
</svg>
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
2
|
-
<!--
|
|
3
|
-
<!--
|
|
4
|
-
<text x="0" y="
|
|
5
|
-
|
|
6
|
-
<text x="0" y="44" font-family="'SF Mono', 'Fira Mono', 'Courier New', monospace" font-size="14" font-weight="500" fill="#22C55E" letter-spacing="2">stats</text>
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 260 52" width="260" height="52">
|
|
2
|
+
<!-- Wordmark only — transparent background -->
|
|
3
|
+
<!-- "cc-context" bold, "stats" in golden amber below -->
|
|
4
|
+
<text x="0" y="30" font-family="'SF Mono', 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace" font-size="24" font-weight="700" fill="#1A1A1A" letter-spacing="-0.3">cc-context</text>
|
|
5
|
+
<text x="0" y="48" font-family="'SF Mono', 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace" font-size="15" font-weight="500" fill="#E9AB34" letter-spacing="3">stats</text>
|
|
7
6
|
</svg>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
cc-context-stats provides real-time context monitoring for Claude Code sessions. It consists of two main components:
|
|
6
|
+
|
|
7
|
+
1. **Status Line** - A compact one-line display integrated into Claude Code's UI
|
|
8
|
+
2. **Context Stats CLI** - A live terminal dashboard with ASCII graphs
|
|
9
|
+
|
|
10
|
+
## System Architecture
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
┌─────────────┐ JSON stdin ┌──────────────────┐
|
|
14
|
+
│ Claude Code │ ──────────────────> │ Statusline Script │
|
|
15
|
+
│ (host) │ <────────────────── │ (sh/py/js) │
|
|
16
|
+
└─────────────┘ stdout text └──────┬───────────┘
|
|
17
|
+
│ writes
|
|
18
|
+
▼
|
|
19
|
+
┌──────────────────┐
|
|
20
|
+
│ State Files │
|
|
21
|
+
│ ~/.claude/ │
|
|
22
|
+
│ statusline/ │
|
|
23
|
+
└──────┬───────────┘
|
|
24
|
+
│ reads
|
|
25
|
+
▼
|
|
26
|
+
┌──────────────────┐
|
|
27
|
+
│ Context Stats CLI │
|
|
28
|
+
│ (Python) │
|
|
29
|
+
└──────────────────┘
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Component Details
|
|
33
|
+
|
|
34
|
+
### Status Line Scripts
|
|
35
|
+
|
|
36
|
+
Three implementation languages with identical output:
|
|
37
|
+
|
|
38
|
+
| Script | Language | Dependencies |
|
|
39
|
+
| --------------------- | ---------- | ------------ |
|
|
40
|
+
| `statusline-full.sh` | Bash | `jq` |
|
|
41
|
+
| `statusline-git.sh` | Bash | `jq` |
|
|
42
|
+
| `statusline-minimal.sh` | Bash | `jq` |
|
|
43
|
+
| `statusline.py` | Python 3 | None |
|
|
44
|
+
| `statusline.js` | Node.js 18+| None |
|
|
45
|
+
|
|
46
|
+
**Data flow:**
|
|
47
|
+
1. Claude Code pipes JSON state via stdin on each refresh
|
|
48
|
+
2. Script parses model info, context tokens, session data
|
|
49
|
+
3. Script reads `~/.claude/statusline.conf` for user preferences
|
|
50
|
+
4. Script checks git status for branch/changes info
|
|
51
|
+
5. Script writes state to `~/.claude/statusline/<session_id>.state`
|
|
52
|
+
6. Script outputs formatted ANSI text to stdout
|
|
53
|
+
|
|
54
|
+
### Python Package (`src/claude_statusline/`)
|
|
55
|
+
|
|
56
|
+
The pip-installable package provides both the statusline and context-stats CLI:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
src/claude_statusline/
|
|
60
|
+
├── __init__.py
|
|
61
|
+
├── __main__.py
|
|
62
|
+
├── cli/
|
|
63
|
+
│ ├── statusline.py # claude-statusline entry point
|
|
64
|
+
│ └── context_stats.py # context-stats entry point
|
|
65
|
+
├── core/
|
|
66
|
+
│ ├── colors.py # ANSI color management
|
|
67
|
+
│ ├── config.py # Configuration loading
|
|
68
|
+
│ ├── git.py # Git status detection
|
|
69
|
+
│ └── state.py # State file reading/writing
|
|
70
|
+
├── formatters/
|
|
71
|
+
│ ├── layout.py # Output width/layout management
|
|
72
|
+
│ ├── time.py # Duration formatting
|
|
73
|
+
│ └── tokens.py # Token count formatting
|
|
74
|
+
├── graphs/
|
|
75
|
+
│ ├── renderer.py # ASCII graph rendering
|
|
76
|
+
│ └── statistics.py # Data statistics
|
|
77
|
+
└── ui/
|
|
78
|
+
├── icons.py # Unicode icons
|
|
79
|
+
└── waiting.py # Waiting animation
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### State Files
|
|
83
|
+
|
|
84
|
+
State files persist token history between statusline refreshes:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
~/.claude/statusline/statusline.<session_id>.state
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Each line is a CSV record with 14 comma-separated fields (timestamp, token counts, cost, session metadata, and context metrics). See [CSV_FORMAT.md](CSV_FORMAT.md) for the full field specification. The context-stats CLI reads these files to render graphs.
|
|
91
|
+
|
|
92
|
+
## Data Privacy
|
|
93
|
+
|
|
94
|
+
All data stays local:
|
|
95
|
+
- State files are written to `~/.claude/statusline/`
|
|
96
|
+
- No network requests are made
|
|
97
|
+
- No telemetry or analytics
|
|
98
|
+
|
|
99
|
+
## Configuration
|
|
100
|
+
|
|
101
|
+
User preferences are stored in `~/.claude/statusline.conf` as simple `key=value` pairs. See [Configuration](configuration.md) for details.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# CSV State File Format
|
|
2
|
+
|
|
3
|
+
State files are stored at `~/.claude/statusline/statusline.<session_id>.state`. Each line is a CSV record with 14 comma-separated fields.
|
|
4
|
+
|
|
5
|
+
## Field Specification
|
|
6
|
+
|
|
7
|
+
| Index | Field | Type | Description |
|
|
8
|
+
|-------|-------|------|-------------|
|
|
9
|
+
| 0 | `timestamp` | integer | Unix timestamp in seconds |
|
|
10
|
+
| 1 | `total_input_tokens` | integer | Cumulative input tokens for the session |
|
|
11
|
+
| 2 | `total_output_tokens` | integer | Cumulative output tokens for the session |
|
|
12
|
+
| 3 | `current_input_tokens` | integer | Input tokens for the current request |
|
|
13
|
+
| 4 | `current_output_tokens` | integer | Output tokens for the current request |
|
|
14
|
+
| 5 | `cache_creation` | integer | Cache creation input tokens |
|
|
15
|
+
| 6 | `cache_read` | integer | Cache read input tokens |
|
|
16
|
+
| 7 | `cost_usd` | float | Total session cost in USD |
|
|
17
|
+
| 8 | `lines_added` | integer | Total lines added in session |
|
|
18
|
+
| 9 | `lines_removed` | integer | Total lines removed in session |
|
|
19
|
+
| 10 | `session_id` | string | Session identifier (UUID) |
|
|
20
|
+
| 11 | `model_id` | string | Model identifier (e.g., `claude-opus-4-5`) |
|
|
21
|
+
| 12 | `workspace_project_dir` | string | Project directory path (commas replaced with underscores) |
|
|
22
|
+
| 13 | `context_window_size` | integer | Context window size in tokens |
|
|
23
|
+
|
|
24
|
+
## Constraints
|
|
25
|
+
|
|
26
|
+
- Fields are separated by commas with no quoting or escaping.
|
|
27
|
+
- The `workspace_project_dir` field (index 12) is sanitized before writing: all comma characters (`,`) are replaced with underscores (`_`) to prevent CSV corruption.
|
|
28
|
+
- Numeric fields default to `0` when absent. String fields default to empty string.
|
|
29
|
+
- Lines are newline-terminated (`\n`).
|
|
30
|
+
- Files are append-only.
|
|
31
|
+
|
|
32
|
+
## Legacy Format
|
|
33
|
+
|
|
34
|
+
Older state files may contain 2-field lines: `timestamp,total_input_tokens`. The reader defaults all other fields to zero/empty for these lines.
|
|
35
|
+
|
|
36
|
+
## Example
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
1710288000,75000,8500,50000,5000,10000,20000,0.05234,250,45,abc-123-def,claude-opus-4-5,/home/user/my-project,200000
|
|
40
|
+
```
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Deployment
|
|
2
|
+
|
|
3
|
+
## Distribution Channels
|
|
4
|
+
|
|
5
|
+
cc-context-stats is distributed through three channels:
|
|
6
|
+
|
|
7
|
+
| Channel | Package Name | Command |
|
|
8
|
+
| ------------ | ----------------- | ------------------------------------ |
|
|
9
|
+
| Shell script | N/A | `curl -fsSL .../install.sh \| bash` |
|
|
10
|
+
| PyPI | `cc-context-stats`| `pip install cc-context-stats` |
|
|
11
|
+
| npm | `cc-context-stats`| `npm install -g cc-context-stats` |
|
|
12
|
+
|
|
13
|
+
## Publishing to PyPI
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Ensure clean build
|
|
17
|
+
rm -rf dist/ build/
|
|
18
|
+
|
|
19
|
+
# Build
|
|
20
|
+
python -m build
|
|
21
|
+
|
|
22
|
+
# Check package
|
|
23
|
+
twine check dist/*
|
|
24
|
+
|
|
25
|
+
# Upload to PyPI
|
|
26
|
+
twine upload dist/*
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Publishing to npm
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Verify package.json
|
|
33
|
+
npm pack --dry-run
|
|
34
|
+
|
|
35
|
+
# Publish
|
|
36
|
+
npm publish
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Release Workflow
|
|
40
|
+
|
|
41
|
+
The project uses GitHub Actions for automated releases (`.github/workflows/release.yml`):
|
|
42
|
+
|
|
43
|
+
1. Create and push a version tag: `git tag v1.x.x && git push --tags`
|
|
44
|
+
2. The release workflow automatically:
|
|
45
|
+
- Runs the full test suite
|
|
46
|
+
- Builds Python and npm packages
|
|
47
|
+
- Creates a GitHub Release with release notes
|
|
48
|
+
|
|
49
|
+
## Version Management
|
|
50
|
+
|
|
51
|
+
Versions must be updated in sync across:
|
|
52
|
+
|
|
53
|
+
- `pyproject.toml` - `[project] version`
|
|
54
|
+
- `package.json` - `version`
|
|
55
|
+
- `CHANGELOG.md` - New version entry
|
|
56
|
+
- `RELEASE_NOTES.md` - Current release notes
|
|
57
|
+
|
|
58
|
+
## Install Script
|
|
59
|
+
|
|
60
|
+
The `install.sh` script is fetched directly from the `main` branch on GitHub. Changes to the installer take effect immediately for new users running the curl one-liner.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Development Guide
|
|
2
|
+
|
|
3
|
+
## Prerequisites
|
|
4
|
+
|
|
5
|
+
- **Git** - Version control
|
|
6
|
+
- **jq** - JSON processor (for bash scripts)
|
|
7
|
+
- **Python 3.9+** - For Python package and testing
|
|
8
|
+
- **Node.js 18+** - For Node.js script and testing
|
|
9
|
+
- **Bats** - Bash Automated Testing System (optional, for bash tests)
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Clone the repository
|
|
15
|
+
git clone https://github.com/luongnv89/cc-context-stats.git
|
|
16
|
+
cd cc-context-stats
|
|
17
|
+
|
|
18
|
+
# Python setup
|
|
19
|
+
python3 -m venv venv
|
|
20
|
+
source venv/bin/activate
|
|
21
|
+
pip install -r requirements-dev.txt
|
|
22
|
+
pip install -e ".[dev]"
|
|
23
|
+
|
|
24
|
+
# Node.js setup
|
|
25
|
+
npm install
|
|
26
|
+
|
|
27
|
+
# Install pre-commit hooks
|
|
28
|
+
pre-commit install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Project Layout
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
cc-context-stats/
|
|
35
|
+
├── src/claude_statusline/ # Python package source
|
|
36
|
+
├── scripts/ # Standalone scripts (sh/py/js)
|
|
37
|
+
├── tests/
|
|
38
|
+
│ ├── bash/ # Bats tests
|
|
39
|
+
│ ├── python/ # Pytest tests
|
|
40
|
+
│ └── node/ # Jest tests
|
|
41
|
+
├── config/ # Configuration examples
|
|
42
|
+
├── docs/ # Documentation
|
|
43
|
+
├── .github/workflows/ # CI/CD
|
|
44
|
+
├── pyproject.toml # Python build config
|
|
45
|
+
└── package.json # Node.js config
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Running Tests
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# All tests
|
|
52
|
+
npm test && pytest && bats tests/bash/*.bats
|
|
53
|
+
|
|
54
|
+
# Individual suites
|
|
55
|
+
pytest tests/python/ -v # Python
|
|
56
|
+
pytest tests/python/ -v --cov=scripts --cov-report=html # Python + coverage
|
|
57
|
+
npm test # Node.js (Jest)
|
|
58
|
+
npm run test:coverage # Node.js + coverage
|
|
59
|
+
bats tests/bash/*.bats # Bash
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Linting & Formatting
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Run all checks via pre-commit
|
|
66
|
+
pre-commit run --all-files
|
|
67
|
+
|
|
68
|
+
# Individual tools
|
|
69
|
+
ruff check src/ scripts/statusline.py # Python lint
|
|
70
|
+
ruff format src/ scripts/statusline.py # Python format
|
|
71
|
+
npx eslint scripts/statusline.js # JavaScript lint
|
|
72
|
+
npx prettier --write scripts/statusline.js # JavaScript format
|
|
73
|
+
shellcheck scripts/*.sh install.sh # Bash lint
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Manual Testing
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
# Test statusline scripts with mock input
|
|
80
|
+
echo '{"model":{"display_name":"Test"},"cwd":"/test","session_id":"abc123","context":{"tokens_remaining":64000,"context_window":200000}}' | python3 scripts/statusline.py
|
|
81
|
+
|
|
82
|
+
echo '{"model":{"display_name":"Test"}}' | node scripts/statusline.js
|
|
83
|
+
|
|
84
|
+
echo '{"model":{"display_name":"Test"}}' | bash scripts/statusline-full.sh
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Building
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Python package
|
|
91
|
+
python -m build
|
|
92
|
+
|
|
93
|
+
# Verify package
|
|
94
|
+
twine check dist/*
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Cross-Script Consistency
|
|
98
|
+
|
|
99
|
+
All three implementations (bash, Python, Node.js) must produce identical output for the same input. When modifying status line behavior:
|
|
100
|
+
|
|
101
|
+
1. Update all three script variants
|
|
102
|
+
2. Run integration tests to verify parity
|
|
103
|
+
3. Test on multiple platforms if possible
|
|
104
|
+
|
|
105
|
+
## Debugging
|
|
106
|
+
|
|
107
|
+
### State files
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# View current state files
|
|
111
|
+
ls -la ~/.claude/statusline/statusline.*.state
|
|
112
|
+
|
|
113
|
+
# Inspect state content
|
|
114
|
+
cat ~/.claude/statusline/statusline.<session_id>.state
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Verbose testing
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Python with verbose output
|
|
121
|
+
pytest tests/python/ -v -s
|
|
122
|
+
|
|
123
|
+
# Node.js with verbose output
|
|
124
|
+
npx jest --verbose
|
|
125
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cc-context-stats",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Monitor your Claude Code session context in real-time - track token usage and never run out of context",
|
|
5
5
|
"main": "scripts/statusline.js",
|
|
6
6
|
"scripts": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"eslint": "^8.56.0",
|
|
34
34
|
"jest": "^29.7.0",
|
|
35
|
-
"prettier": "^3.
|
|
35
|
+
"prettier": "^3.8.1"
|
|
36
36
|
},
|
|
37
37
|
"engines": {
|
|
38
38
|
"node": ">=18"
|
package/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cc-context-stats"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.6.0"
|
|
8
8
|
description = "Monitor your Claude Code session context in real-time - track token usage and never run out of context"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -116,13 +116,21 @@ visible_width() {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
get_terminal_width() {
|
|
119
|
-
# Return terminal width
|
|
119
|
+
# Return terminal width for fit_to_width truncation.
|
|
120
|
+
# When running inside Claude Code's statusline subprocess, neither $COLUMNS
|
|
121
|
+
# nor tput can detect the real terminal width (they always return 80).
|
|
122
|
+
# If COLUMNS is explicitly set, trust it. Otherwise use 200 as default
|
|
123
|
+
# so no parts are unnecessarily dropped; Claude Code handles overflow.
|
|
120
124
|
if [[ -n "$COLUMNS" ]]; then
|
|
121
125
|
echo "$COLUMNS"
|
|
122
126
|
else
|
|
123
127
|
local cols
|
|
124
128
|
cols=$(tput cols 2>/dev/null || echo 80)
|
|
125
|
-
|
|
129
|
+
if [[ "$cols" -eq 80 ]]; then
|
|
130
|
+
echo 200
|
|
131
|
+
else
|
|
132
|
+
echo "$cols"
|
|
133
|
+
fi
|
|
126
134
|
fi
|
|
127
135
|
}
|
|
128
136
|
|
|
@@ -170,7 +178,7 @@ cost_usd=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
|
|
|
170
178
|
lines_added=$(echo "$input" | jq -r '.cost.total_lines_added // 0')
|
|
171
179
|
lines_removed=$(echo "$input" | jq -r '.cost.total_lines_removed // 0')
|
|
172
180
|
model_id=$(echo "$input" | jq -r '.model.id // ""')
|
|
173
|
-
workspace_project_dir=$(echo "$input" | jq -r '.workspace.project_dir // ""')
|
|
181
|
+
workspace_project_dir=$(echo "$input" | jq -r '.workspace.project_dir // ""' | tr ',' '_')
|
|
174
182
|
|
|
175
183
|
if [[ "$total_size" -gt 0 && "$current_usage" != "null" ]]; then
|
|
176
184
|
# Get tokens from current_usage (includes cache)
|