cc-context-stats 1.8.0 → 1.8.2

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.
Files changed (106) hide show
  1. package/package.json +8 -1
  2. package/scripts/context-stats.sh +1 -1
  3. package/.editorconfig +0 -60
  4. package/.eslintrc.json +0 -35
  5. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -49
  6. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -31
  7. package/.github/PULL_REQUEST_TEMPLATE.md +0 -33
  8. package/.github/dependabot.yml +0 -44
  9. package/.github/workflows/ci.yml +0 -294
  10. package/.github/workflows/release.yml +0 -151
  11. package/.pre-commit-config.yaml +0 -74
  12. package/.prettierrc +0 -33
  13. package/.shellcheckrc +0 -10
  14. package/CHANGELOG.md +0 -187
  15. package/CLAUDE.md +0 -66
  16. package/CODE_OF_CONDUCT.md +0 -59
  17. package/CONTRIBUTING.md +0 -240
  18. package/RELEASE_NOTES.md +0 -19
  19. package/SECURITY.md +0 -44
  20. package/TODOS.md +0 -72
  21. package/assets/logo/favicon.svg +0 -19
  22. package/assets/logo/logo-black.svg +0 -24
  23. package/assets/logo/logo-full.svg +0 -40
  24. package/assets/logo/logo-icon.svg +0 -27
  25. package/assets/logo/logo-mark.svg +0 -28
  26. package/assets/logo/logo-white.svg +0 -24
  27. package/assets/logo/logo-wordmark.svg +0 -6
  28. package/config/settings-example.json +0 -7
  29. package/config/settings-node.json +0 -7
  30. package/config/settings-python.json +0 -7
  31. package/docs/ARCHITECTURE.md +0 -128
  32. package/docs/CSV_FORMAT.md +0 -42
  33. package/docs/DEPLOYMENT.md +0 -71
  34. package/docs/DEVELOPMENT.md +0 -161
  35. package/docs/MODEL_INTELLIGENCE.md +0 -396
  36. package/docs/configuration.md +0 -118
  37. package/docs/context-stats.md +0 -143
  38. package/docs/installation.md +0 -255
  39. package/docs/scripts.md +0 -140
  40. package/docs/troubleshooting.md +0 -278
  41. package/images/claude-statusline-token-graph.gif +0 -0
  42. package/images/claude-statusline.png +0 -0
  43. package/images/context-status-dumbzone.png +0 -0
  44. package/images/context-status.png +0 -0
  45. package/images/statusline-detail.png +0 -0
  46. package/images/token-graph.jpeg +0 -0
  47. package/images/token-graph.png +0 -0
  48. package/images/v1.6.1.png +0 -0
  49. package/install +0 -351
  50. package/install.sh +0 -298
  51. package/jest.config.js +0 -11
  52. package/pyproject.toml +0 -115
  53. package/requirements-dev.txt +0 -12
  54. package/scripts/statusline-full.sh +0 -438
  55. package/scripts/statusline-git.sh +0 -88
  56. package/scripts/statusline-minimal.sh +0 -67
  57. package/scripts/statusline.py +0 -569
  58. package/src/claude_statusline/__init__.py +0 -11
  59. package/src/claude_statusline/__main__.py +0 -6
  60. package/src/claude_statusline/cli/__init__.py +0 -1
  61. package/src/claude_statusline/cli/context_stats.py +0 -542
  62. package/src/claude_statusline/cli/explain.py +0 -228
  63. package/src/claude_statusline/cli/statusline.py +0 -184
  64. package/src/claude_statusline/core/__init__.py +0 -1
  65. package/src/claude_statusline/core/colors.py +0 -124
  66. package/src/claude_statusline/core/config.py +0 -165
  67. package/src/claude_statusline/core/git.py +0 -78
  68. package/src/claude_statusline/core/state.py +0 -323
  69. package/src/claude_statusline/formatters/__init__.py +0 -1
  70. package/src/claude_statusline/formatters/layout.py +0 -67
  71. package/src/claude_statusline/formatters/time.py +0 -50
  72. package/src/claude_statusline/formatters/tokens.py +0 -70
  73. package/src/claude_statusline/graphs/__init__.py +0 -1
  74. package/src/claude_statusline/graphs/intelligence.py +0 -162
  75. package/src/claude_statusline/graphs/renderer.py +0 -401
  76. package/src/claude_statusline/graphs/statistics.py +0 -92
  77. package/src/claude_statusline/ui/__init__.py +0 -1
  78. package/src/claude_statusline/ui/icons.py +0 -93
  79. package/src/claude_statusline/ui/waiting.py +0 -62
  80. package/tests/bash/test_delta_parity.bats +0 -199
  81. package/tests/bash/test_install.bats +0 -29
  82. package/tests/bash/test_parity.bats +0 -315
  83. package/tests/bash/test_statusline_full.bats +0 -139
  84. package/tests/bash/test_statusline_git.bats +0 -42
  85. package/tests/bash/test_statusline_minimal.bats +0 -37
  86. package/tests/fixtures/json/comma_in_path.json +0 -31
  87. package/tests/fixtures/json/high_usage.json +0 -17
  88. package/tests/fixtures/json/low_usage.json +0 -17
  89. package/tests/fixtures/json/medium_usage.json +0 -17
  90. package/tests/fixtures/json/valid_full.json +0 -30
  91. package/tests/fixtures/json/valid_minimal.json +0 -9
  92. package/tests/fixtures/mi_test_vectors.json +0 -140
  93. package/tests/node/intelligence.test.js +0 -98
  94. package/tests/node/rotation.test.js +0 -89
  95. package/tests/node/statusline.test.js +0 -240
  96. package/tests/python/conftest.py +0 -84
  97. package/tests/python/test_colors.py +0 -105
  98. package/tests/python/test_config_colors.py +0 -78
  99. package/tests/python/test_data_pipeline.py +0 -446
  100. package/tests/python/test_explain.py +0 -177
  101. package/tests/python/test_icons.py +0 -152
  102. package/tests/python/test_intelligence.py +0 -314
  103. package/tests/python/test_layout.py +0 -127
  104. package/tests/python/test_state_rotation_validation.py +0 -232
  105. package/tests/python/test_statusline.py +0 -215
  106. package/tests/python/test_waiting.py +0 -127
@@ -1,315 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # Cross-implementation parity tests: Python vs Node.js statusline scripts
4
- # Ensures both implementations produce equivalent output for identical input.
5
-
6
- strip_ansi() {
7
- printf '%s' "$1" | sed -e $'s/\033\[[0-9;]*m//g' -e 's/\\033\[[0-9;]*m//g'
8
- }
9
-
10
- setup() {
11
- PROJECT_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
12
- PYTHON_SCRIPT="$PROJECT_ROOT/scripts/statusline.py"
13
- NODE_SCRIPT="$PROJECT_ROOT/scripts/statusline.js"
14
- FIXTURES="$PROJECT_ROOT/tests/fixtures/json"
15
-
16
- # Create isolated temp HOME so state files don't pollute real ~/.claude/
17
- TEST_HOME=$(mktemp -d)
18
- export HOME="$TEST_HOME"
19
-
20
- # Normalize terminal width for deterministic output
21
- export COLUMNS=120
22
-
23
- # Disable delta display to avoid cross-fixture state file interference
24
- mkdir -p "$TEST_HOME/.claude"
25
- echo "show_delta=false" > "$TEST_HOME/.claude/statusline.conf"
26
-
27
- # Create a non-git temp working directory so both scripts skip git info
28
- TEST_WORKDIR=$(mktemp -d)
29
- cd "$TEST_WORKDIR"
30
- }
31
-
32
- teardown() {
33
- rm -rf "$TEST_HOME"
34
- rm -rf "$TEST_WORKDIR"
35
- }
36
-
37
- # Helper: inject a session_id into a JSON fixture via Python
38
- inject_session_py() {
39
- local fixture="$1" session="$2"
40
- python3 -c "
41
- import json, sys
42
- data = json.load(open('$fixture'))
43
- data['session_id'] = '$session'
44
- json.dump(data, sys.stdout)
45
- "
46
- }
47
-
48
- # Helper: inject a session_id into a JSON fixture via Node
49
- inject_session_node() {
50
- local fixture="$1" session="$2"
51
- node -e "
52
- const fs = require('fs');
53
- const data = JSON.parse(fs.readFileSync('$fixture', 'utf8'));
54
- data.session_id = '$session';
55
- process.stdout.write(JSON.stringify(data));
56
- "
57
- }
58
-
59
- # ============================================
60
- # Stdout Parity Tests
61
- # ============================================
62
-
63
- @test "stdout parity: Python and Node.js produce identical ANSI-stripped output for all fixtures" {
64
- for fixture in "$FIXTURES"/*.json; do
65
- fixture_name=$(basename "$fixture")
66
-
67
- py_output=$(cat "$fixture" | python3 "$PYTHON_SCRIPT" 2>/dev/null)
68
- node_output=$(cat "$fixture" | node "$NODE_SCRIPT" 2>/dev/null)
69
-
70
- py_clean=$(strip_ansi "$py_output")
71
- node_clean=$(strip_ansi "$node_output")
72
-
73
- if [ "$py_clean" != "$node_clean" ]; then
74
- echo "STDOUT MISMATCH for fixture: $fixture_name"
75
- echo "---"
76
- echo "Python output: $py_clean"
77
- echo "Node.js output: $node_clean"
78
- echo "---"
79
- return 1
80
- fi
81
- done
82
- }
83
-
84
- # ============================================
85
- # CSV State File Parity Tests
86
- # ============================================
87
-
88
- @test "CSV parity: both scripts write exactly 14 fields for all fixtures" {
89
- for fixture in "$FIXTURES"/*.json; do
90
- fixture_name=$(basename "$fixture" .json)
91
-
92
- py_session="parity-py-${fixture_name}"
93
- node_session="parity-node-${fixture_name}"
94
-
95
- py_input=$(inject_session_py "$fixture" "$py_session")
96
- node_input=$(inject_session_node "$fixture" "$node_session")
97
-
98
- echo "$py_input" | python3 "$PYTHON_SCRIPT" > /dev/null 2>&1
99
- echo "$node_input" | node "$NODE_SCRIPT" > /dev/null 2>&1
100
-
101
- py_state_file="$TEST_HOME/.claude/statusline/statusline.${py_session}.state"
102
- node_state_file="$TEST_HOME/.claude/statusline/statusline.${node_session}.state"
103
-
104
- # Skip fixtures that don't produce state files (e.g., no context_window data)
105
- if [ ! -f "$py_state_file" ] && [ ! -f "$node_state_file" ]; then
106
- continue
107
- fi
108
-
109
- # If only one script wrote a state file, that's a parity failure
110
- if [ ! -f "$py_state_file" ]; then
111
- echo "PARITY ERROR for fixture: $fixture_name"
112
- echo "Node.js wrote a state file but Python did not"
113
- return 1
114
- fi
115
- if [ ! -f "$node_state_file" ]; then
116
- echo "PARITY ERROR for fixture: $fixture_name"
117
- echo "Python wrote a state file but Node.js did not"
118
- return 1
119
- fi
120
-
121
- # Read last line of each state file
122
- py_line=$(tail -1 "$py_state_file")
123
- node_line=$(tail -1 "$node_state_file")
124
-
125
- # Count fields (comma-separated)
126
- py_field_count=$(echo "$py_line" | awk -F',' '{print NF}')
127
- node_field_count=$(echo "$node_line" | awk -F',' '{print NF}')
128
-
129
- if [ "$py_field_count" -ne 14 ]; then
130
- echo "FIELD COUNT ERROR for fixture: $fixture_name"
131
- echo "Python state has $py_field_count fields (expected 14)"
132
- echo "Python line: $py_line"
133
- return 1
134
- fi
135
- if [ "$node_field_count" -ne 14 ]; then
136
- echo "FIELD COUNT ERROR for fixture: $fixture_name"
137
- echo "Node.js state has $node_field_count fields (expected 14)"
138
- echo "Node.js line: $node_line"
139
- return 1
140
- fi
141
- done
142
- }
143
-
144
- @test "CSV parity: fields 1-13 match between Python and Node.js for all fixtures" {
145
- # Field names for diagnostic output (index 0 = timestamp, 1-13 = data fields)
146
- local field_names=(
147
- "timestamp"
148
- "total_input_tokens"
149
- "total_output_tokens"
150
- "current_usage_input_tokens"
151
- "current_usage_output_tokens"
152
- "current_usage_cache_creation"
153
- "current_usage_cache_read"
154
- "total_cost_usd"
155
- "total_lines_added"
156
- "total_lines_removed"
157
- "session_id"
158
- "model_id"
159
- "workspace_project_dir"
160
- "context_window_size"
161
- )
162
-
163
- for fixture in "$FIXTURES"/*.json; do
164
- fixture_name=$(basename "$fixture" .json)
165
-
166
- py_session="parity-fields-py-${fixture_name}"
167
- node_session="parity-fields-node-${fixture_name}"
168
-
169
- py_input=$(inject_session_py "$fixture" "$py_session")
170
- node_input=$(inject_session_node "$fixture" "$node_session")
171
-
172
- echo "$py_input" | python3 "$PYTHON_SCRIPT" > /dev/null 2>&1
173
- echo "$node_input" | node "$NODE_SCRIPT" > /dev/null 2>&1
174
-
175
- py_state_file="$TEST_HOME/.claude/statusline/statusline.${py_session}.state"
176
- node_state_file="$TEST_HOME/.claude/statusline/statusline.${node_session}.state"
177
-
178
- # Skip fixtures that don't produce state files
179
- if [ ! -f "$py_state_file" ] && [ ! -f "$node_state_file" ]; then
180
- continue
181
- fi
182
-
183
- [ -f "$py_state_file" ] || { echo "Python wrote no state file but Node did for $fixture_name"; return 1; }
184
- [ -f "$node_state_file" ] || { echo "Node wrote no state file but Python did for $fixture_name"; return 1; }
185
-
186
- py_line=$(tail -1 "$py_state_file")
187
- node_line=$(tail -1 "$node_state_file")
188
-
189
- # Compare fields 1-13 (skip timestamp at index 0, and skip session_id at index 10 since we set different ones)
190
- local has_mismatch=0
191
- for i in $(seq 1 13); do
192
- # Skip field 10 (session_id) since we intentionally set different session IDs
193
- if [ "$i" -eq 10 ]; then
194
- continue
195
- fi
196
-
197
- py_field=$(echo "$py_line" | cut -d',' -f$((i + 1)))
198
- node_field=$(echo "$node_line" | cut -d',' -f$((i + 1)))
199
-
200
- if [ "$py_field" != "$node_field" ]; then
201
- echo "FIELD MISMATCH for fixture: $fixture_name"
202
- echo " Field $i (${field_names[$i]}): Python='$py_field' Node='$node_field'"
203
- has_mismatch=1
204
- fi
205
- done
206
-
207
- if [ "$has_mismatch" -eq 1 ]; then
208
- echo "Full Python line: $py_line"
209
- echo "Full Node.js line: $node_line"
210
- return 1
211
- fi
212
- done
213
- }
214
-
215
- @test "CSV parity: comma in workspace_project_dir is sanitized to underscore" {
216
- fixture="$FIXTURES/comma_in_path.json"
217
- [ -f "$fixture" ] || skip "comma_in_path.json fixture missing"
218
-
219
- # Enable show_delta so state files are written
220
- echo "show_delta=true" > "$TEST_HOME/.claude/statusline.conf"
221
-
222
- py_session="parity-comma-py"
223
- node_session="parity-comma-node"
224
-
225
- py_input=$(inject_session_py "$fixture" "$py_session")
226
- node_input=$(inject_session_node "$fixture" "$node_session")
227
-
228
- echo "$py_input" | python3 "$PYTHON_SCRIPT" > /dev/null 2>&1
229
- echo "$node_input" | node "$NODE_SCRIPT" > /dev/null 2>&1
230
-
231
- # Restore show_delta=false for subsequent tests
232
- echo "show_delta=false" > "$TEST_HOME/.claude/statusline.conf"
233
-
234
- py_state_file="$TEST_HOME/.claude/statusline/statusline.${py_session}.state"
235
- node_state_file="$TEST_HOME/.claude/statusline/statusline.${node_session}.state"
236
-
237
- [ -f "$py_state_file" ] || { echo "Python wrote no state file"; return 1; }
238
- [ -f "$node_state_file" ] || { echo "Node wrote no state file"; return 1; }
239
-
240
- # Extract workspace_project_dir (field 13, 1-indexed in cut)
241
- py_dir=$(tail -1 "$py_state_file" | cut -d',' -f13)
242
- node_dir=$(tail -1 "$node_state_file" | cut -d',' -f13)
243
-
244
- # Both must have commas replaced with underscores
245
- expected="/home/user/my_project_dir"
246
-
247
- if [ "$py_dir" != "$expected" ]; then
248
- echo "Python did not sanitize commas: got '$py_dir', expected '$expected'"
249
- return 1
250
- fi
251
- if [ "$node_dir" != "$expected" ]; then
252
- echo "Node.js did not sanitize commas: got '$node_dir', expected '$expected'"
253
- return 1
254
- fi
255
-
256
- # Both must produce exactly 14 fields (commas didn't corrupt the CSV)
257
- py_fields=$(tail -1 "$py_state_file" | awk -F',' '{print NF}')
258
- node_fields=$(tail -1 "$node_state_file" | awk -F',' '{print NF}')
259
-
260
- if [ "$py_fields" -ne 14 ]; then
261
- echo "Python state has $py_fields fields (expected 14)"
262
- return 1
263
- fi
264
- if [ "$node_fields" -ne 14 ]; then
265
- echo "Node.js state has $node_fields fields (expected 14)"
266
- return 1
267
- fi
268
- }
269
-
270
- @test "CSV parity: timestamp differs by at most 2 seconds between Python and Node.js" {
271
- for fixture in "$FIXTURES"/*.json; do
272
- fixture_name=$(basename "$fixture" .json)
273
-
274
- py_session="parity-ts-py-${fixture_name}"
275
- node_session="parity-ts-node-${fixture_name}"
276
-
277
- py_input=$(inject_session_py "$fixture" "$py_session")
278
- node_input=$(inject_session_node "$fixture" "$node_session")
279
-
280
- echo "$py_input" | python3 "$PYTHON_SCRIPT" > /dev/null 2>&1
281
- echo "$node_input" | node "$NODE_SCRIPT" > /dev/null 2>&1
282
-
283
- py_state_file="$TEST_HOME/.claude/statusline/statusline.${py_session}.state"
284
- node_state_file="$TEST_HOME/.claude/statusline/statusline.${node_session}.state"
285
-
286
- # Skip fixtures that don't produce state files
287
- if [ ! -f "$py_state_file" ] && [ ! -f "$node_state_file" ]; then
288
- continue
289
- fi
290
-
291
- [ -f "$py_state_file" ] || { echo "Python wrote no state file but Node did for $fixture_name"; return 1; }
292
- [ -f "$node_state_file" ] || { echo "Node wrote no state file but Python did for $fixture_name"; return 1; }
293
-
294
- py_line=$(tail -1 "$py_state_file")
295
- node_line=$(tail -1 "$node_state_file")
296
-
297
- # Extract timestamp (field 0, which is field 1 in cut 1-indexed)
298
- py_ts=$(echo "$py_line" | cut -d',' -f1)
299
- node_ts=$(echo "$node_line" | cut -d',' -f1)
300
-
301
- # Calculate absolute difference
302
- if [ -n "$py_ts" ] && [ -n "$node_ts" ]; then
303
- diff=$((py_ts - node_ts))
304
- abs_diff=${diff#-}
305
-
306
- if [ "$abs_diff" -gt 2 ]; then
307
- echo "TIMESTAMP DRIFT for fixture: $fixture_name"
308
- echo " Python timestamp: $py_ts"
309
- echo " Node.js timestamp: $node_ts"
310
- echo " Difference: ${abs_diff}s (max allowed: 2s)"
311
- return 1
312
- fi
313
- fi
314
- done
315
- }
@@ -1,139 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # Test suite for statusline-full.sh
4
-
5
- setup() {
6
- PROJECT_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
7
- SCRIPT="$PROJECT_ROOT/scripts/statusline-full.sh"
8
- FIXTURES="$PROJECT_ROOT/tests/fixtures/json"
9
-
10
- # Create a temp directory for config tests
11
- TEST_HOME=$(mktemp -d)
12
- export HOME="$TEST_HOME"
13
- mkdir -p "$TEST_HOME/.claude"
14
- }
15
-
16
- teardown() {
17
- rm -rf "$TEST_HOME"
18
- }
19
-
20
- @test "statusline-full.sh exists and is executable" {
21
- [ -f "$SCRIPT" ]
22
- [ -x "$SCRIPT" ]
23
- }
24
-
25
- @test "outputs model name from JSON input" {
26
- input='{"model":{"display_name":"Opus 4.5"},"workspace":{"current_dir":"/tmp/test","project_dir":"/tmp/test"}}'
27
- result=$(echo "$input" | "$SCRIPT")
28
- [[ "$result" == *"Opus 4.5"* ]]
29
- }
30
-
31
- @test "outputs directory name from path" {
32
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/home/user/myproject","project_dir":"/home/user/myproject"}}'
33
- result=$(echo "$input" | "$SCRIPT")
34
- [[ "$result" == *"myproject"* ]]
35
- }
36
-
37
- @test "handles full valid input with context window" {
38
- result=$(cat "$FIXTURES/valid_full.json" | "$SCRIPT")
39
- [[ "$result" == *"Opus 4.5"* ]]
40
- [[ "$result" == *"my-project"* ]]
41
- [[ "$result" == *"%"* ]]
42
- }
43
-
44
- @test "shows AC indicator when autocompact enabled" {
45
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/tmp","project_dir":"/tmp"},"context_window":{"context_window_size":200000,"current_usage":{"input_tokens":10000,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}}'
46
- result=$(echo "$input" | "$SCRIPT")
47
- [[ "$result" == *"[AC:"* ]]
48
- }
49
-
50
- @test "shows AC:off when autocompact disabled in config" {
51
- echo "autocompact=false" > "$TEST_HOME/.claude/statusline.conf"
52
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/tmp","project_dir":"/tmp"},"context_window":{"context_window_size":200000,"current_usage":{"input_tokens":10000,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}}'
53
- result=$(echo "$input" | "$SCRIPT")
54
- [[ "$result" == *"[AC:off]"* ]]
55
- }
56
-
57
- @test "shows exact tokens by default (token_detail=true)" {
58
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/tmp","project_dir":"/tmp"},"context_window":{"context_window_size":200000,"current_usage":{"input_tokens":10000,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}}'
59
- result=$(echo "$input" | "$SCRIPT")
60
- # Should NOT show 'k' suffix by default, should show comma-formatted number
61
- [[ "$result" != *"k ("* ]]
62
- [[ "$result" == *"%"* ]]
63
- }
64
-
65
- @test "shows abbreviated tokens when token_detail=false" {
66
- echo "token_detail=false" > "$TEST_HOME/.claude/statusline.conf"
67
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/tmp","project_dir":"/tmp"},"context_window":{"context_window_size":200000,"current_usage":{"input_tokens":10000,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}}'
68
- result=$(echo "$input" | "$SCRIPT")
69
- # Should show 'k' suffix for abbreviated format
70
- [[ "$result" == *"k ("* ]]
71
- }
72
-
73
- @test "handles missing context window gracefully" {
74
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/tmp/test","project_dir":"/tmp/test"}}'
75
- run bash "$SCRIPT" <<< "$input"
76
- [ "$status" -eq 0 ]
77
- }
78
-
79
- @test "calculates free tokens percentage correctly" {
80
- # Low usage fixture: 30k tokens used out of 200k = 85% free
81
- result=$(cat "$FIXTURES/low_usage.json" | "$SCRIPT")
82
- [[ "$result" == *"%"* ]]
83
- }
84
-
85
- @test "uses fixture files correctly" {
86
- for fixture in valid_full valid_minimal low_usage medium_usage high_usage; do
87
- run bash "$SCRIPT" < "$FIXTURES/${fixture}.json"
88
- [ "$status" -eq 0 ]
89
- done
90
- }
91
-
92
- @test "shows session_id by default (show_session=true)" {
93
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/tmp","project_dir":"/tmp"},"session_id":"test-session-123"}'
94
- result=$(echo "$input" | "$SCRIPT")
95
- [[ "$result" == *"test-session-123"* ]]
96
- }
97
-
98
- @test "hides session_id when show_session=false" {
99
- echo "show_session=false" > "$TEST_HOME/.claude/statusline.conf"
100
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/tmp","project_dir":"/tmp"},"session_id":"test-session-123"}'
101
- result=$(echo "$input" | "$SCRIPT")
102
- [[ "$result" != *"test-session-123"* ]]
103
- }
104
-
105
- @test "handles missing session_id gracefully" {
106
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/tmp","project_dir":"/tmp"}}'
107
- run bash "$SCRIPT" <<< "$input"
108
- [ "$status" -eq 0 ]
109
- }
110
-
111
- # Width truncation tests
112
-
113
- strip_ansi() {
114
- printf '%s' "$1" | sed -e $'s/\033\[[0-9;]*m//g' -e 's/\\033\[[0-9;]*m//g'
115
- }
116
-
117
- @test "output fits within 80 columns" {
118
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/tmp/proj","project_dir":"/tmp/proj"},"context_window":{"context_window_size":200000,"current_usage":{"input_tokens":10000,"cache_creation_input_tokens":500,"cache_read_input_tokens":200}},"session_id":"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"}'
119
- result=$(COLUMNS=80 bash "$SCRIPT" <<< "$input")
120
- visible=$(strip_ansi "$result")
121
- len=$(printf '%s' "$visible" | wc -m | tr -d ' ')
122
- [ "$len" -le 80 ]
123
- }
124
-
125
- @test "narrow terminal still shows model and directory" {
126
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/tmp/proj","project_dir":"/tmp/proj"},"context_window":{"context_window_size":200000,"current_usage":{"input_tokens":10000,"cache_creation_input_tokens":500,"cache_read_input_tokens":200}}}'
127
- result=$(COLUMNS=40 bash "$SCRIPT" <<< "$input")
128
- visible=$(strip_ansi "$result")
129
- len=$(printf '%s' "$visible" | wc -m | tr -d ' ')
130
- [ "$len" -le 40 ]
131
- [[ "$visible" == *"Claude"* ]]
132
- [[ "$visible" == *"proj"* ]]
133
- }
134
-
135
- @test "wide terminal shows session_id" {
136
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/tmp/proj","project_dir":"/tmp/proj"},"session_id":"test-wide-session-uuid"}'
137
- result=$(COLUMNS=200 bash "$SCRIPT" <<< "$input")
138
- [[ "$result" == *"test-wide-session-uuid"* ]]
139
- }
@@ -1,42 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # Test suite for statusline-git.sh
4
-
5
- setup() {
6
- PROJECT_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
7
- SCRIPT="$PROJECT_ROOT/scripts/statusline-git.sh"
8
- FIXTURES="$PROJECT_ROOT/tests/fixtures/json"
9
- }
10
-
11
- @test "statusline-git.sh exists and is executable" {
12
- [ -f "$SCRIPT" ]
13
- [ -x "$SCRIPT" ]
14
- }
15
-
16
- @test "outputs model name from JSON input" {
17
- input='{"model":{"display_name":"Opus 4.5"},"workspace":{"current_dir":"/tmp/test","project_dir":"/tmp/test"}}'
18
- result=$(echo "$input" | "$SCRIPT")
19
- [[ "$result" == *"Opus 4.5"* ]]
20
- }
21
-
22
- @test "outputs directory name" {
23
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/home/user/myproject","project_dir":"/tmp"}}'
24
- result=$(echo "$input" | "$SCRIPT")
25
- [[ "$result" == *"myproject"* ]]
26
- }
27
-
28
- @test "handles valid input without crashing" {
29
- run bash "$SCRIPT" < "$FIXTURES/valid_full.json"
30
- [ "$status" -eq 0 ]
31
- }
32
-
33
- @test "shows git branch when in git repo" {
34
- # Use project dir which is a git repo
35
- input=$(cat <<EOF
36
- {"model":{"display_name":"Claude"},"workspace":{"current_dir":"$PROJECT_ROOT","project_dir":"$PROJECT_ROOT"}}
37
- EOF
38
- )
39
- result=$(echo "$input" | "$SCRIPT")
40
- # Should contain branch name (main or master usually)
41
- [[ "$result" == *"main"* ]] || [[ "$result" == *"master"* ]] || true
42
- }
@@ -1,37 +0,0 @@
1
- #!/usr/bin/env bats
2
-
3
- # Test suite for statusline-minimal.sh
4
-
5
- setup() {
6
- PROJECT_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
7
- SCRIPT="$PROJECT_ROOT/scripts/statusline-minimal.sh"
8
- FIXTURES="$PROJECT_ROOT/tests/fixtures/json"
9
- }
10
-
11
- @test "statusline-minimal.sh exists and is executable" {
12
- [ -f "$SCRIPT" ]
13
- [ -x "$SCRIPT" ]
14
- }
15
-
16
- @test "outputs model name from JSON input" {
17
- input='{"model":{"display_name":"Opus 4.5"},"workspace":{"current_dir":"/tmp/test","project_dir":"/tmp/test"}}'
18
- result=$(echo "$input" | "$SCRIPT")
19
- [[ "$result" == *"Opus 4.5"* ]]
20
- }
21
-
22
- @test "outputs directory name" {
23
- input='{"model":{"display_name":"Claude"},"workspace":{"current_dir":"/home/user/myproject","project_dir":"/tmp"}}'
24
- result=$(echo "$input" | "$SCRIPT")
25
- [[ "$result" == *"myproject"* ]]
26
- }
27
-
28
- @test "handles minimal valid input" {
29
- result=$(cat "$FIXTURES/valid_minimal.json" | "$SCRIPT")
30
- [[ "$result" == *"Claude"* ]]
31
- [[ "$result" == *"test"* ]]
32
- }
33
-
34
- @test "script runs without errors on valid input" {
35
- run bash "$SCRIPT" < "$FIXTURES/valid_full.json"
36
- [ "$status" -eq 0 ]
37
- }
@@ -1,31 +0,0 @@
1
- {
2
- "model": {
3
- "display_name": "Opus 4.5",
4
- "api_name": "claude-opus-4-5",
5
- "id": "claude-opus-4-5"
6
- },
7
- "workspace": {
8
- "current_dir": "/home/user/my,project,dir",
9
- "project_dir": "/home/user/my,project,dir"
10
- },
11
- "context_window": {
12
- "context_window_size": 200000,
13
- "total_input_tokens": 75000,
14
- "total_output_tokens": 8500,
15
- "current_usage": {
16
- "input_tokens": 50000,
17
- "output_tokens": 5000,
18
- "cache_creation_input_tokens": 10000,
19
- "cache_read_input_tokens": 20000
20
- }
21
- },
22
- "cost": {
23
- "total_cost_usd": 0.05234,
24
- "total_duration_ms": 120000,
25
- "total_api_duration_ms": 5000,
26
- "total_lines_added": 250,
27
- "total_lines_removed": 45
28
- },
29
- "session_id": "test-comma-path",
30
- "version": "1.0.80"
31
- }
@@ -1,17 +0,0 @@
1
- {
2
- "model": {
3
- "display_name": "Opus 4.5"
4
- },
5
- "workspace": {
6
- "current_dir": "/home/user/project",
7
- "project_dir": "/home/user/project"
8
- },
9
- "context_window": {
10
- "context_window_size": 200000,
11
- "current_usage": {
12
- "input_tokens": 170000,
13
- "cache_creation_input_tokens": 0,
14
- "cache_read_input_tokens": 0
15
- }
16
- }
17
- }
@@ -1,17 +0,0 @@
1
- {
2
- "model": {
3
- "display_name": "Opus 4.5"
4
- },
5
- "workspace": {
6
- "current_dir": "/home/user/project",
7
- "project_dir": "/home/user/project"
8
- },
9
- "context_window": {
10
- "context_window_size": 200000,
11
- "current_usage": {
12
- "input_tokens": 20000,
13
- "cache_creation_input_tokens": 5000,
14
- "cache_read_input_tokens": 5000
15
- }
16
- }
17
- }
@@ -1,17 +0,0 @@
1
- {
2
- "model": {
3
- "display_name": "Opus 4.5"
4
- },
5
- "workspace": {
6
- "current_dir": "/home/user/project",
7
- "project_dir": "/home/user/project"
8
- },
9
- "context_window": {
10
- "context_window_size": 200000,
11
- "current_usage": {
12
- "input_tokens": 100000,
13
- "cache_creation_input_tokens": 20000,
14
- "cache_read_input_tokens": 0
15
- }
16
- }
17
- }
@@ -1,30 +0,0 @@
1
- {
2
- "model": {
3
- "display_name": "Opus 4.5",
4
- "api_name": "claude-opus-4-5"
5
- },
6
- "workspace": {
7
- "current_dir": "/home/user/my-project",
8
- "project_dir": "/home/user/my-project"
9
- },
10
- "context_window": {
11
- "context_window_size": 200000,
12
- "total_input_tokens": 75000,
13
- "total_output_tokens": 8500,
14
- "current_usage": {
15
- "input_tokens": 50000,
16
- "output_tokens": 5000,
17
- "cache_creation_input_tokens": 10000,
18
- "cache_read_input_tokens": 20000
19
- }
20
- },
21
- "cost": {
22
- "total_cost_usd": 0.05234,
23
- "total_duration_ms": 120000,
24
- "total_api_duration_ms": 5000,
25
- "total_lines_added": 250,
26
- "total_lines_removed": 45
27
- },
28
- "session_id": "test-session-123",
29
- "version": "1.0.80"
30
- }
@@ -1,9 +0,0 @@
1
- {
2
- "model": {
3
- "display_name": "Claude"
4
- },
5
- "workspace": {
6
- "current_dir": "/tmp/test",
7
- "project_dir": "/tmp/test"
8
- }
9
- }