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.
- package/package.json +8 -1
- package/scripts/context-stats.sh +1 -1
- package/.editorconfig +0 -60
- package/.eslintrc.json +0 -35
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -49
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -31
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -33
- package/.github/dependabot.yml +0 -44
- package/.github/workflows/ci.yml +0 -294
- package/.github/workflows/release.yml +0 -151
- package/.pre-commit-config.yaml +0 -74
- package/.prettierrc +0 -33
- package/.shellcheckrc +0 -10
- package/CHANGELOG.md +0 -187
- package/CLAUDE.md +0 -66
- package/CODE_OF_CONDUCT.md +0 -59
- package/CONTRIBUTING.md +0 -240
- package/RELEASE_NOTES.md +0 -19
- package/SECURITY.md +0 -44
- package/TODOS.md +0 -72
- package/assets/logo/favicon.svg +0 -19
- package/assets/logo/logo-black.svg +0 -24
- package/assets/logo/logo-full.svg +0 -40
- package/assets/logo/logo-icon.svg +0 -27
- package/assets/logo/logo-mark.svg +0 -28
- package/assets/logo/logo-white.svg +0 -24
- package/assets/logo/logo-wordmark.svg +0 -6
- package/config/settings-example.json +0 -7
- package/config/settings-node.json +0 -7
- package/config/settings-python.json +0 -7
- package/docs/ARCHITECTURE.md +0 -128
- package/docs/CSV_FORMAT.md +0 -42
- package/docs/DEPLOYMENT.md +0 -71
- package/docs/DEVELOPMENT.md +0 -161
- package/docs/MODEL_INTELLIGENCE.md +0 -396
- package/docs/configuration.md +0 -118
- package/docs/context-stats.md +0 -143
- package/docs/installation.md +0 -255
- package/docs/scripts.md +0 -140
- package/docs/troubleshooting.md +0 -278
- package/images/claude-statusline-token-graph.gif +0 -0
- package/images/claude-statusline.png +0 -0
- package/images/context-status-dumbzone.png +0 -0
- package/images/context-status.png +0 -0
- package/images/statusline-detail.png +0 -0
- package/images/token-graph.jpeg +0 -0
- package/images/token-graph.png +0 -0
- package/images/v1.6.1.png +0 -0
- package/install +0 -351
- package/install.sh +0 -298
- package/jest.config.js +0 -11
- package/pyproject.toml +0 -115
- package/requirements-dev.txt +0 -12
- package/scripts/statusline-full.sh +0 -438
- package/scripts/statusline-git.sh +0 -88
- package/scripts/statusline-minimal.sh +0 -67
- package/scripts/statusline.py +0 -569
- package/src/claude_statusline/__init__.py +0 -11
- package/src/claude_statusline/__main__.py +0 -6
- package/src/claude_statusline/cli/__init__.py +0 -1
- package/src/claude_statusline/cli/context_stats.py +0 -542
- package/src/claude_statusline/cli/explain.py +0 -228
- package/src/claude_statusline/cli/statusline.py +0 -184
- package/src/claude_statusline/core/__init__.py +0 -1
- package/src/claude_statusline/core/colors.py +0 -124
- package/src/claude_statusline/core/config.py +0 -165
- package/src/claude_statusline/core/git.py +0 -78
- package/src/claude_statusline/core/state.py +0 -323
- package/src/claude_statusline/formatters/__init__.py +0 -1
- package/src/claude_statusline/formatters/layout.py +0 -67
- package/src/claude_statusline/formatters/time.py +0 -50
- package/src/claude_statusline/formatters/tokens.py +0 -70
- package/src/claude_statusline/graphs/__init__.py +0 -1
- package/src/claude_statusline/graphs/intelligence.py +0 -162
- package/src/claude_statusline/graphs/renderer.py +0 -401
- package/src/claude_statusline/graphs/statistics.py +0 -92
- package/src/claude_statusline/ui/__init__.py +0 -1
- package/src/claude_statusline/ui/icons.py +0 -93
- package/src/claude_statusline/ui/waiting.py +0 -62
- package/tests/bash/test_delta_parity.bats +0 -199
- package/tests/bash/test_install.bats +0 -29
- package/tests/bash/test_parity.bats +0 -315
- package/tests/bash/test_statusline_full.bats +0 -139
- package/tests/bash/test_statusline_git.bats +0 -42
- package/tests/bash/test_statusline_minimal.bats +0 -37
- package/tests/fixtures/json/comma_in_path.json +0 -31
- package/tests/fixtures/json/high_usage.json +0 -17
- package/tests/fixtures/json/low_usage.json +0 -17
- package/tests/fixtures/json/medium_usage.json +0 -17
- package/tests/fixtures/json/valid_full.json +0 -30
- package/tests/fixtures/json/valid_minimal.json +0 -9
- package/tests/fixtures/mi_test_vectors.json +0 -140
- package/tests/node/intelligence.test.js +0 -98
- package/tests/node/rotation.test.js +0 -89
- package/tests/node/statusline.test.js +0 -240
- package/tests/python/conftest.py +0 -84
- package/tests/python/test_colors.py +0 -105
- package/tests/python/test_config_colors.py +0 -78
- package/tests/python/test_data_pipeline.py +0 -446
- package/tests/python/test_explain.py +0 -177
- package/tests/python/test_icons.py +0 -152
- package/tests/python/test_intelligence.py +0 -314
- package/tests/python/test_layout.py +0 -127
- package/tests/python/test_state_rotation_validation.py +0 -232
- package/tests/python/test_statusline.py +0 -215
- package/tests/python/test_waiting.py +0 -127
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
"""Diagnostic command that dumps the raw JSON context from Claude Code.
|
|
2
|
-
|
|
3
|
-
Usage:
|
|
4
|
-
echo '{"model":...}' | context-stats explain
|
|
5
|
-
echo '{"model":...}' | context-stats explain --no-color
|
|
6
|
-
|
|
7
|
-
Reads the same JSON that Claude Code pipes to the statusline script,
|
|
8
|
-
pretty-prints every field with labels, and shows how cc-context-stats
|
|
9
|
-
interprets them. Useful for debugging blank or missing modules.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import json
|
|
15
|
-
import sys
|
|
16
|
-
|
|
17
|
-
from claude_statusline.core.colors import ColorManager
|
|
18
|
-
from claude_statusline.core.config import Config
|
|
19
|
-
from claude_statusline.formatters.tokens import format_tokens
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _pct_color(colors: ColorManager, pct: float) -> str:
|
|
23
|
-
"""Return ANSI color based on free-space percentage."""
|
|
24
|
-
if pct > 50:
|
|
25
|
-
return colors.green
|
|
26
|
-
if pct > 25:
|
|
27
|
-
return colors.yellow
|
|
28
|
-
return colors.red
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def _fv(colors: ColorManager, value: object) -> str:
|
|
32
|
-
"""Format a value for display, handling None gracefully."""
|
|
33
|
-
if value is None:
|
|
34
|
-
return f"{colors.dim}(absent){colors.reset}"
|
|
35
|
-
if isinstance(value, float):
|
|
36
|
-
return f"{value:.4f}"
|
|
37
|
-
return str(value)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def _render_model(data: dict, colors: ColorManager) -> None:
|
|
41
|
-
model = data.get("model", {})
|
|
42
|
-
print(f"{colors.bold}Model{colors.reset}")
|
|
43
|
-
print(f" display_name: {_fv(colors, model.get('display_name'))}")
|
|
44
|
-
print(f" id: {_fv(colors, model.get('id'))}")
|
|
45
|
-
print(f" api_name: {_fv(colors, model.get('api_name'))}")
|
|
46
|
-
print()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def _render_workspace(data: dict, colors: ColorManager) -> None:
|
|
50
|
-
workspace = data.get("workspace", {})
|
|
51
|
-
print(f"{colors.bold}Workspace{colors.reset}")
|
|
52
|
-
print(f" current_dir: {_fv(colors, workspace.get('current_dir'))}")
|
|
53
|
-
print(f" project_dir: {_fv(colors, workspace.get('project_dir'))}")
|
|
54
|
-
print()
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def _render_context_window(data: dict, colors: ColorManager, config: Config) -> None:
|
|
58
|
-
cw = data.get("context_window", {})
|
|
59
|
-
print(f"{colors.bold}Context Window{colors.reset}")
|
|
60
|
-
total_size = cw.get("context_window_size", 0)
|
|
61
|
-
print(
|
|
62
|
-
f" window_size: "
|
|
63
|
-
f"{format_tokens(total_size, config.token_detail) if total_size else _fv(colors, None)}"
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
total_in = cw.get("total_input_tokens")
|
|
67
|
-
total_out = cw.get("total_output_tokens")
|
|
68
|
-
print(
|
|
69
|
-
f" total_input: "
|
|
70
|
-
f"{format_tokens(total_in, config.token_detail) if total_in else _fv(colors, total_in)}"
|
|
71
|
-
)
|
|
72
|
-
print(
|
|
73
|
-
f" total_output: "
|
|
74
|
-
f"{format_tokens(total_out, config.token_detail) if total_out else _fv(colors, total_out)}"
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
used_pct = cw.get("used_percentage")
|
|
78
|
-
remaining_pct = cw.get("remaining_percentage")
|
|
79
|
-
print(f" used_pct: {_fv(colors, used_pct)}")
|
|
80
|
-
print(f" remaining_pct: {_fv(colors, remaining_pct)}")
|
|
81
|
-
|
|
82
|
-
cu = cw.get("current_usage")
|
|
83
|
-
if cu:
|
|
84
|
-
_render_current_usage(cu, total_size, colors, config)
|
|
85
|
-
else:
|
|
86
|
-
print(f" current_usage: {colors.dim}(absent — no API call yet this session){colors.reset}")
|
|
87
|
-
print()
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def _render_current_usage(cu: dict, total_size: int, colors: ColorManager, config: Config) -> None:
|
|
91
|
-
input_tok = cu.get("input_tokens", 0)
|
|
92
|
-
output_tok = cu.get("output_tokens", 0)
|
|
93
|
-
cache_create = cu.get("cache_creation_input_tokens", 0)
|
|
94
|
-
cache_read = cu.get("cache_read_input_tokens", 0)
|
|
95
|
-
used_tokens = input_tok + cache_create + cache_read
|
|
96
|
-
|
|
97
|
-
print(f"\n {colors.bold}Current Usage{colors.reset}")
|
|
98
|
-
print(f" input_tokens: {format_tokens(input_tok, config.token_detail)}")
|
|
99
|
-
print(f" output_tokens: {format_tokens(output_tok, config.token_detail)}")
|
|
100
|
-
print(f" cache_creation_tokens: {format_tokens(cache_create, config.token_detail)}")
|
|
101
|
-
print(f" cache_read_tokens: {format_tokens(cache_read, config.token_detail)}")
|
|
102
|
-
|
|
103
|
-
print(f"\n {colors.bold}Derived Values{colors.reset}")
|
|
104
|
-
print(
|
|
105
|
-
f" context_used (in+cache): "
|
|
106
|
-
f"{colors.cyan}{format_tokens(used_tokens, config.token_detail)}{colors.reset}"
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
if total_size > 0:
|
|
110
|
-
free = total_size - used_tokens
|
|
111
|
-
free_pct = (free * 100.0) / total_size
|
|
112
|
-
color = _pct_color(colors, free_pct)
|
|
113
|
-
print(
|
|
114
|
-
f" free_tokens: "
|
|
115
|
-
f"{color}{format_tokens(max(0, free), config.token_detail)} ({free_pct:.1f}%){colors.reset}"
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
if config.autocompact:
|
|
119
|
-
ac_buffer = int(total_size * 0.225)
|
|
120
|
-
effective_free = max(0, free - ac_buffer)
|
|
121
|
-
eff_pct = (effective_free * 100.0) / total_size
|
|
122
|
-
eff_color = _pct_color(colors, eff_pct)
|
|
123
|
-
print(
|
|
124
|
-
f" autocompact_buffer: "
|
|
125
|
-
f"{colors.dim}{format_tokens(ac_buffer, config.token_detail)}{colors.reset}"
|
|
126
|
-
)
|
|
127
|
-
print(
|
|
128
|
-
f" effective_free (w/ AC): "
|
|
129
|
-
f"{eff_color}{format_tokens(effective_free, config.token_detail)}"
|
|
130
|
-
f" ({eff_pct:.1f}%){colors.reset}"
|
|
131
|
-
)
|
|
132
|
-
else:
|
|
133
|
-
print(f" autocompact: {colors.dim}disabled{colors.reset}")
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def _render_cost(data: dict, colors: ColorManager) -> None:
|
|
137
|
-
cost = data.get("cost", {})
|
|
138
|
-
if not cost:
|
|
139
|
-
return
|
|
140
|
-
print(f"{colors.bold}Cost{colors.reset}")
|
|
141
|
-
cost_usd = cost.get("total_cost_usd")
|
|
142
|
-
print(
|
|
143
|
-
f" total_cost_usd: {f'${cost_usd:.4f}' if cost_usd is not None else _fv(colors, None)}"
|
|
144
|
-
)
|
|
145
|
-
print(f" total_duration_ms: {_fv(colors, cost.get('total_duration_ms'))}")
|
|
146
|
-
print(f" lines_added: {_fv(colors, cost.get('total_lines_added'))}")
|
|
147
|
-
print(f" lines_removed: {_fv(colors, cost.get('total_lines_removed'))}")
|
|
148
|
-
print()
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def _render_session(data: dict, colors: ColorManager) -> None:
|
|
152
|
-
print(f"{colors.bold}Session{colors.reset}")
|
|
153
|
-
print(f" session_id: {_fv(colors, data.get('session_id'))}")
|
|
154
|
-
print(f" version: {_fv(colors, data.get('version'))}")
|
|
155
|
-
print(f" transcript_path: {_fv(colors, data.get('transcript_path'))}")
|
|
156
|
-
print(f" exceeds_200k: {_fv(colors, data.get('exceeds_200k_tokens'))}")
|
|
157
|
-
print()
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def _render_extensions(data: dict, colors: ColorManager) -> None:
|
|
161
|
-
vim = data.get("vim")
|
|
162
|
-
agent = data.get("agent")
|
|
163
|
-
output_style = data.get("output_style")
|
|
164
|
-
if vim is None and agent is None and output_style is None:
|
|
165
|
-
return
|
|
166
|
-
print(f"{colors.bold}Extensions{colors.reset}")
|
|
167
|
-
if vim is not None:
|
|
168
|
-
vim_mode = vim.get("mode") if isinstance(vim, dict) else vim
|
|
169
|
-
print(f" vim_mode: {_fv(colors, vim_mode)}")
|
|
170
|
-
if agent is not None:
|
|
171
|
-
agent_name = agent.get("name") if isinstance(agent, dict) else agent
|
|
172
|
-
print(f" agent: {_fv(colors, agent_name)}")
|
|
173
|
-
if output_style is not None:
|
|
174
|
-
style_name = output_style.get("name") if isinstance(output_style, dict) else output_style
|
|
175
|
-
print(f" output_style: {_fv(colors, style_name)}")
|
|
176
|
-
print()
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def _render_config(config: Config, colors: ColorManager) -> None:
|
|
180
|
-
print(
|
|
181
|
-
f"{colors.bold}Active Config{colors.reset} "
|
|
182
|
-
f"{colors.dim}(~/.claude/statusline.conf){colors.reset}"
|
|
183
|
-
)
|
|
184
|
-
for k, v in config.to_dict().items():
|
|
185
|
-
if k == "color_overrides":
|
|
186
|
-
if v:
|
|
187
|
-
print(f" {k}:")
|
|
188
|
-
for slot, ansi_code in v.items():
|
|
189
|
-
print(f" {slot}: {ansi_code}████{colors.reset}")
|
|
190
|
-
continue
|
|
191
|
-
print(f" {k}: {v}")
|
|
192
|
-
print()
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def _render_raw_json(data: dict, colors: ColorManager) -> None:
|
|
196
|
-
print(f"{colors.bold}Raw JSON{colors.reset}")
|
|
197
|
-
print(f"{colors.dim}{json.dumps(data, indent=2)}{colors.reset}")
|
|
198
|
-
print()
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def run_explain(data: dict, no_color: bool = False) -> None:
|
|
202
|
-
"""Print a diagnostic dump of the Claude Code session JSON.
|
|
203
|
-
|
|
204
|
-
Args:
|
|
205
|
-
data: Parsed JSON dict from stdin.
|
|
206
|
-
no_color: If True, suppress ANSI color codes.
|
|
207
|
-
"""
|
|
208
|
-
config = Config.load()
|
|
209
|
-
|
|
210
|
-
# Respect --no-color flag and non-TTY output
|
|
211
|
-
color_enabled = not no_color and sys.stdout.isatty()
|
|
212
|
-
colors = ColorManager(enabled=color_enabled, overrides=config.color_overrides)
|
|
213
|
-
|
|
214
|
-
print(f"\n{colors.bold}cc-context-stats explain{colors.reset}")
|
|
215
|
-
print(f"{colors.dim}{'─' * 60}{colors.reset}")
|
|
216
|
-
print(
|
|
217
|
-
f"{colors.dim}Shows how cc-context-stats interprets Claude Code's JSON context."
|
|
218
|
-
f"{colors.reset}\n"
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
_render_model(data, colors)
|
|
222
|
-
_render_workspace(data, colors)
|
|
223
|
-
_render_context_window(data, colors, config)
|
|
224
|
-
_render_cost(data, colors)
|
|
225
|
-
_render_session(data, colors)
|
|
226
|
-
_render_extensions(data, colors)
|
|
227
|
-
_render_config(config, colors)
|
|
228
|
-
_render_raw_json(data, colors)
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""CLI entry point for claude-statusline command.
|
|
3
|
-
|
|
4
|
-
Usage: Copy to ~/.claude/statusline.py and make executable, or install via pip.
|
|
5
|
-
|
|
6
|
-
Configuration:
|
|
7
|
-
Create/edit ~/.claude/statusline.conf and set:
|
|
8
|
-
|
|
9
|
-
autocompact=true (when autocompact is enabled in Claude Code - default)
|
|
10
|
-
autocompact=false (when you disable autocompact via /config in Claude Code)
|
|
11
|
-
|
|
12
|
-
token_detail=true (show exact token count like 64,000 - default)
|
|
13
|
-
token_detail=false (show abbreviated tokens like 64.0k)
|
|
14
|
-
|
|
15
|
-
show_delta=true (show token delta since last refresh like [+2,500] - default)
|
|
16
|
-
show_delta=false (disable delta display - saves file I/O on every refresh)
|
|
17
|
-
|
|
18
|
-
show_session=true (show session_id in status line - default)
|
|
19
|
-
show_session=false (hide session_id from status line)
|
|
20
|
-
|
|
21
|
-
When AC is enabled, 22.5% of context window is reserved for autocompact buffer.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
from __future__ import annotations
|
|
25
|
-
|
|
26
|
-
import json
|
|
27
|
-
import sys
|
|
28
|
-
|
|
29
|
-
from claude_statusline.core.colors import ColorManager
|
|
30
|
-
from claude_statusline.core.config import Config
|
|
31
|
-
from claude_statusline.core.git import get_git_info
|
|
32
|
-
from claude_statusline.core.state import StateEntry, StateFile
|
|
33
|
-
from claude_statusline.formatters.layout import fit_to_width, get_terminal_width
|
|
34
|
-
from claude_statusline.formatters.time import get_current_timestamp
|
|
35
|
-
from claude_statusline.formatters.tokens import calculate_context_usage, format_tokens
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def main() -> None:
|
|
39
|
-
"""Main entry point for claude-statusline CLI."""
|
|
40
|
-
try:
|
|
41
|
-
data = json.load(sys.stdin)
|
|
42
|
-
except json.JSONDecodeError:
|
|
43
|
-
print("[Claude] ~")
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
# Extract data
|
|
47
|
-
cwd = data.get("workspace", {}).get("current_dir", "~")
|
|
48
|
-
project_dir = data.get("workspace", {}).get("project_dir", cwd)
|
|
49
|
-
model = data.get("model", {}).get("display_name", "Claude")
|
|
50
|
-
dir_name = cwd.rsplit("/", 1)[-1] if "/" in cwd else cwd or "~"
|
|
51
|
-
|
|
52
|
-
# Read settings from config file
|
|
53
|
-
config = Config.load()
|
|
54
|
-
|
|
55
|
-
# Build color manager with any user overrides
|
|
56
|
-
colors = ColorManager(enabled=True, overrides=config.color_overrides)
|
|
57
|
-
|
|
58
|
-
# Git info (pass color manager for configurable branch/change colors)
|
|
59
|
-
git_info = get_git_info(project_dir, color_manager=colors)
|
|
60
|
-
|
|
61
|
-
# Extract session_id once for reuse
|
|
62
|
-
session_id = data.get("session_id")
|
|
63
|
-
|
|
64
|
-
# Context window calculation
|
|
65
|
-
context_info = ""
|
|
66
|
-
ac_info = ""
|
|
67
|
-
delta_info = ""
|
|
68
|
-
mi_info = ""
|
|
69
|
-
session_info = ""
|
|
70
|
-
|
|
71
|
-
total_size = data.get("context_window", {}).get("context_window_size", 0)
|
|
72
|
-
current_usage = data.get("context_window", {}).get("current_usage")
|
|
73
|
-
total_input_tokens = data.get("context_window", {}).get("total_input_tokens", 0)
|
|
74
|
-
total_output_tokens = data.get("context_window", {}).get("total_output_tokens", 0)
|
|
75
|
-
cost_usd = data.get("cost", {}).get("total_cost_usd", 0)
|
|
76
|
-
lines_added = data.get("cost", {}).get("total_lines_added", 0)
|
|
77
|
-
lines_removed = data.get("cost", {}).get("total_lines_removed", 0)
|
|
78
|
-
model_id = data.get("model", {}).get("id", "")
|
|
79
|
-
workspace_project_dir = data.get("workspace", {}).get("project_dir", "")
|
|
80
|
-
|
|
81
|
-
if total_size > 0 and current_usage:
|
|
82
|
-
# Get tokens from current_usage (includes cache)
|
|
83
|
-
input_tokens = current_usage.get("input_tokens", 0)
|
|
84
|
-
cache_creation = current_usage.get("cache_creation_input_tokens", 0)
|
|
85
|
-
cache_read = current_usage.get("cache_read_input_tokens", 0)
|
|
86
|
-
|
|
87
|
-
# Total used from current request
|
|
88
|
-
used_tokens = input_tokens + cache_creation + cache_read
|
|
89
|
-
|
|
90
|
-
# Calculate context usage
|
|
91
|
-
free_tokens, free_pct, autocompact_buffer = calculate_context_usage(
|
|
92
|
-
used_tokens,
|
|
93
|
-
total_size,
|
|
94
|
-
config.autocompact,
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
if config.autocompact:
|
|
98
|
-
buffer_k = autocompact_buffer // 1000
|
|
99
|
-
ac_info = f" {colors.dim}[AC:{buffer_k}k]{colors.reset}"
|
|
100
|
-
else:
|
|
101
|
-
ac_info = f" {colors.dim}[AC:off]{colors.reset}"
|
|
102
|
-
|
|
103
|
-
# Format tokens based on token_detail setting
|
|
104
|
-
free_display = format_tokens(free_tokens, config.token_detail)
|
|
105
|
-
|
|
106
|
-
# Color based on free percentage
|
|
107
|
-
free_pct_int = int(free_pct)
|
|
108
|
-
if free_pct_int > 50:
|
|
109
|
-
ctx_color = colors.green
|
|
110
|
-
elif free_pct_int > 25:
|
|
111
|
-
ctx_color = colors.yellow
|
|
112
|
-
else:
|
|
113
|
-
ctx_color = colors.red
|
|
114
|
-
|
|
115
|
-
context_info = f" | {ctx_color}{free_display} ({free_pct:.1f}%){colors.reset}"
|
|
116
|
-
|
|
117
|
-
# Read previous entry if needed for delta OR MI
|
|
118
|
-
if config.show_delta or config.show_mi:
|
|
119
|
-
state_file = StateFile(session_id)
|
|
120
|
-
prev_entry = state_file.read_last_entry()
|
|
121
|
-
|
|
122
|
-
prev_tokens = prev_entry.current_used_tokens if prev_entry else 0
|
|
123
|
-
has_prev = prev_entry is not None
|
|
124
|
-
|
|
125
|
-
# Build current entry
|
|
126
|
-
cur_input_tokens = current_usage.get("input_tokens", 0)
|
|
127
|
-
cur_output_tokens = current_usage.get("output_tokens", 0)
|
|
128
|
-
|
|
129
|
-
entry = StateEntry(
|
|
130
|
-
timestamp=get_current_timestamp(),
|
|
131
|
-
total_input_tokens=total_input_tokens,
|
|
132
|
-
total_output_tokens=total_output_tokens,
|
|
133
|
-
current_input_tokens=cur_input_tokens,
|
|
134
|
-
current_output_tokens=cur_output_tokens,
|
|
135
|
-
cache_creation=cache_creation,
|
|
136
|
-
cache_read=cache_read,
|
|
137
|
-
cost_usd=cost_usd,
|
|
138
|
-
lines_added=lines_added,
|
|
139
|
-
lines_removed=lines_removed,
|
|
140
|
-
session_id=session_id or "",
|
|
141
|
-
model_id=model_id,
|
|
142
|
-
workspace_project_dir=workspace_project_dir,
|
|
143
|
-
context_window_size=total_size,
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
# Calculate and display token delta if enabled
|
|
147
|
-
if config.show_delta:
|
|
148
|
-
delta = used_tokens - prev_tokens
|
|
149
|
-
if has_prev and delta > 0:
|
|
150
|
-
delta_display = format_tokens(delta, config.token_detail)
|
|
151
|
-
delta_info = f" {colors.dim}[+{delta_display}]{colors.reset}"
|
|
152
|
-
|
|
153
|
-
# Calculate and display MI score if enabled
|
|
154
|
-
if config.show_mi:
|
|
155
|
-
from claude_statusline.graphs.intelligence import (
|
|
156
|
-
calculate_intelligence,
|
|
157
|
-
format_mi_score,
|
|
158
|
-
get_mi_color,
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
mi_score = calculate_intelligence(
|
|
162
|
-
entry, prev_entry, total_size, config.mi_curve_beta
|
|
163
|
-
)
|
|
164
|
-
mi_color_name = get_mi_color(mi_score.mi)
|
|
165
|
-
mi_color = getattr(colors, mi_color_name)
|
|
166
|
-
mi_info = f" {mi_color}MI:{format_mi_score(mi_score.mi)}{colors.reset}"
|
|
167
|
-
|
|
168
|
-
# Only append if context usage changed (avoid duplicates from multiple refreshes)
|
|
169
|
-
if not has_prev or used_tokens != prev_tokens:
|
|
170
|
-
state_file.append_entry(entry)
|
|
171
|
-
|
|
172
|
-
# Display session_id if enabled
|
|
173
|
-
if config.show_session and session_id:
|
|
174
|
-
session_info = f" {colors.dim}{session_id}{colors.reset}"
|
|
175
|
-
|
|
176
|
-
# Output: [Model] directory | branch [changes] | XXk free (XX%) [+delta] [AC] [session_id]
|
|
177
|
-
base = f"{colors.dim}[{model}]{colors.reset} {colors.blue}{dir_name}{colors.reset}"
|
|
178
|
-
max_width = get_terminal_width()
|
|
179
|
-
parts = [base, git_info, context_info, delta_info, mi_info, ac_info, session_info]
|
|
180
|
-
print(fit_to_width(parts, max_width))
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if __name__ == "__main__":
|
|
184
|
-
main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Core functionality for claude-statusline."""
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
"""ANSI color constants and utilities."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import re
|
|
6
|
-
|
|
7
|
-
# ANSI color codes (defaults)
|
|
8
|
-
BLUE = "\033[0;34m"
|
|
9
|
-
MAGENTA = "\033[0;35m"
|
|
10
|
-
CYAN = "\033[0;36m"
|
|
11
|
-
GREEN = "\033[0;32m"
|
|
12
|
-
YELLOW = "\033[0;33m"
|
|
13
|
-
RED = "\033[0;31m"
|
|
14
|
-
BOLD = "\033[1m"
|
|
15
|
-
DIM = "\033[2m"
|
|
16
|
-
RESET = "\033[0m"
|
|
17
|
-
|
|
18
|
-
# Mapping from color names to ANSI codes
|
|
19
|
-
COLOR_NAMES: dict[str, str] = {
|
|
20
|
-
"black": "\033[0;30m",
|
|
21
|
-
"red": "\033[0;31m",
|
|
22
|
-
"green": "\033[0;32m",
|
|
23
|
-
"yellow": "\033[0;33m",
|
|
24
|
-
"blue": "\033[0;34m",
|
|
25
|
-
"magenta": "\033[0;35m",
|
|
26
|
-
"cyan": "\033[0;36m",
|
|
27
|
-
"white": "\033[0;37m",
|
|
28
|
-
"bright_black": "\033[0;90m",
|
|
29
|
-
"bright_red": "\033[0;91m",
|
|
30
|
-
"bright_green": "\033[0;92m",
|
|
31
|
-
"bright_yellow": "\033[0;93m",
|
|
32
|
-
"bright_blue": "\033[0;94m",
|
|
33
|
-
"bright_magenta": "\033[0;95m",
|
|
34
|
-
"bright_cyan": "\033[0;96m",
|
|
35
|
-
"bright_white": "\033[0;97m",
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
_HEX_RE = re.compile(r"^#([0-9a-fA-F]{6})$")
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def parse_color(value: str) -> str | None:
|
|
42
|
-
"""Parse a color value into an ANSI escape code.
|
|
43
|
-
|
|
44
|
-
Accepts:
|
|
45
|
-
- Named colors: "red", "green", "bright_cyan", etc.
|
|
46
|
-
- Hex colors: "#ff5733" (converted to 24-bit ANSI)
|
|
47
|
-
|
|
48
|
-
Returns:
|
|
49
|
-
ANSI escape code string, or None if the value is not recognized.
|
|
50
|
-
"""
|
|
51
|
-
value = value.strip().lower()
|
|
52
|
-
if not value:
|
|
53
|
-
return None
|
|
54
|
-
|
|
55
|
-
# Named color
|
|
56
|
-
if value in COLOR_NAMES:
|
|
57
|
-
return COLOR_NAMES[value]
|
|
58
|
-
|
|
59
|
-
# Hex color (#rrggbb)
|
|
60
|
-
m = _HEX_RE.match(value)
|
|
61
|
-
if m:
|
|
62
|
-
hex_str = m.group(1)
|
|
63
|
-
r = int(hex_str[0:2], 16)
|
|
64
|
-
g = int(hex_str[2:4], 16)
|
|
65
|
-
b = int(hex_str[4:6], 16)
|
|
66
|
-
return f"\033[38;2;{r};{g};{b}m"
|
|
67
|
-
|
|
68
|
-
return None
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class ColorManager:
|
|
72
|
-
"""Manage color output based on terminal capabilities.
|
|
73
|
-
|
|
74
|
-
Supports custom color overrides via a dict of {slot_name: ansi_code}.
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
def __init__(
|
|
78
|
-
self,
|
|
79
|
-
enabled: bool = True,
|
|
80
|
-
overrides: dict[str, str] | None = None,
|
|
81
|
-
) -> None:
|
|
82
|
-
self.enabled = enabled
|
|
83
|
-
self._overrides = overrides or {}
|
|
84
|
-
|
|
85
|
-
def _get(self, slot: str, default: str) -> str:
|
|
86
|
-
if not self.enabled:
|
|
87
|
-
return ""
|
|
88
|
-
return self._overrides.get(slot, default)
|
|
89
|
-
|
|
90
|
-
@property
|
|
91
|
-
def blue(self) -> str:
|
|
92
|
-
return self._get("blue", BLUE)
|
|
93
|
-
|
|
94
|
-
@property
|
|
95
|
-
def magenta(self) -> str:
|
|
96
|
-
return self._get("magenta", MAGENTA)
|
|
97
|
-
|
|
98
|
-
@property
|
|
99
|
-
def cyan(self) -> str:
|
|
100
|
-
return self._get("cyan", CYAN)
|
|
101
|
-
|
|
102
|
-
@property
|
|
103
|
-
def green(self) -> str:
|
|
104
|
-
return self._get("green", GREEN)
|
|
105
|
-
|
|
106
|
-
@property
|
|
107
|
-
def yellow(self) -> str:
|
|
108
|
-
return self._get("yellow", YELLOW)
|
|
109
|
-
|
|
110
|
-
@property
|
|
111
|
-
def red(self) -> str:
|
|
112
|
-
return self._get("red", RED)
|
|
113
|
-
|
|
114
|
-
@property
|
|
115
|
-
def bold(self) -> str:
|
|
116
|
-
return BOLD if self.enabled else ""
|
|
117
|
-
|
|
118
|
-
@property
|
|
119
|
-
def dim(self) -> str:
|
|
120
|
-
return DIM if self.enabled else ""
|
|
121
|
-
|
|
122
|
-
@property
|
|
123
|
-
def reset(self) -> str:
|
|
124
|
-
return RESET if self.enabled else ""
|