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
package/pyproject.toml
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
3
|
-
build-backend = "hatchling.build"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "cc-context-stats"
|
|
7
|
-
version = "1.8.0"
|
|
8
|
-
description = "Monitor your Claude Code session context in real-time - track token usage and never run out of context"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
license = { text = "MIT" }
|
|
11
|
-
requires-python = ">=3.9"
|
|
12
|
-
authors = [
|
|
13
|
-
{ name = "Nguyen Luong", email = "luongnv89@gmail.com" }
|
|
14
|
-
]
|
|
15
|
-
keywords = [
|
|
16
|
-
"claude",
|
|
17
|
-
"claude-code",
|
|
18
|
-
"context",
|
|
19
|
-
"tokens",
|
|
20
|
-
"monitoring",
|
|
21
|
-
"statusline",
|
|
22
|
-
"terminal",
|
|
23
|
-
"cli",
|
|
24
|
-
"dashboard",
|
|
25
|
-
]
|
|
26
|
-
classifiers = [
|
|
27
|
-
"Development Status :: 4 - Beta",
|
|
28
|
-
"Environment :: Console",
|
|
29
|
-
"Intended Audience :: Developers",
|
|
30
|
-
"License :: OSI Approved :: MIT License",
|
|
31
|
-
"Operating System :: OS Independent",
|
|
32
|
-
"Programming Language :: Python :: 3",
|
|
33
|
-
"Programming Language :: Python :: 3.9",
|
|
34
|
-
"Programming Language :: Python :: 3.10",
|
|
35
|
-
"Programming Language :: Python :: 3.11",
|
|
36
|
-
"Programming Language :: Python :: 3.12",
|
|
37
|
-
"Programming Language :: Python :: 3.13",
|
|
38
|
-
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
39
|
-
"Topic :: Utilities",
|
|
40
|
-
]
|
|
41
|
-
dependencies = []
|
|
42
|
-
|
|
43
|
-
[project.scripts]
|
|
44
|
-
claude-statusline = "claude_statusline.cli.statusline:main"
|
|
45
|
-
context-stats = "claude_statusline.cli.context_stats:main"
|
|
46
|
-
|
|
47
|
-
[project.urls]
|
|
48
|
-
Homepage = "https://github.com/luongnv89/cc-context-stats"
|
|
49
|
-
Documentation = "https://github.com/luongnv89/cc-context-stats#readme"
|
|
50
|
-
Repository = "https://github.com/luongnv89/cc-context-stats"
|
|
51
|
-
Issues = "https://github.com/luongnv89/cc-context-stats/issues"
|
|
52
|
-
|
|
53
|
-
[project.optional-dependencies]
|
|
54
|
-
dev = [
|
|
55
|
-
"pytest>=7.4.0",
|
|
56
|
-
"pytest-cov>=4.1.0",
|
|
57
|
-
"ruff>=0.1.0",
|
|
58
|
-
"mypy>=1.7.0",
|
|
59
|
-
"build>=1.0.0",
|
|
60
|
-
"twine>=4.0.0",
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
[tool.hatch.build.targets.wheel]
|
|
64
|
-
packages = ["src/claude_statusline"]
|
|
65
|
-
|
|
66
|
-
[tool.hatch.build.targets.sdist]
|
|
67
|
-
include = [
|
|
68
|
-
"/src",
|
|
69
|
-
"/README.md",
|
|
70
|
-
"/LICENSE",
|
|
71
|
-
]
|
|
72
|
-
|
|
73
|
-
[tool.ruff]
|
|
74
|
-
target-version = "py39"
|
|
75
|
-
line-length = 100
|
|
76
|
-
src = ["src"]
|
|
77
|
-
|
|
78
|
-
[tool.ruff.lint]
|
|
79
|
-
select = [
|
|
80
|
-
"E", # pycodestyle errors
|
|
81
|
-
"W", # pycodestyle warnings
|
|
82
|
-
"F", # Pyflakes
|
|
83
|
-
"I", # isort
|
|
84
|
-
"B", # flake8-bugbear
|
|
85
|
-
"C4", # flake8-comprehensions
|
|
86
|
-
"UP", # pyupgrade
|
|
87
|
-
]
|
|
88
|
-
ignore = ["E501"] # line too long (handled by formatter)
|
|
89
|
-
|
|
90
|
-
[tool.ruff.format]
|
|
91
|
-
quote-style = "double"
|
|
92
|
-
indent-style = "space"
|
|
93
|
-
|
|
94
|
-
[tool.mypy]
|
|
95
|
-
python_version = "3.9"
|
|
96
|
-
warn_return_any = true
|
|
97
|
-
warn_unused_ignores = true
|
|
98
|
-
strict_optional = true
|
|
99
|
-
ignore_missing_imports = true
|
|
100
|
-
|
|
101
|
-
[tool.pytest.ini_options]
|
|
102
|
-
testpaths = ["tests/python"]
|
|
103
|
-
addopts = "-v --tb=short"
|
|
104
|
-
python_files = ["test_*.py"]
|
|
105
|
-
python_functions = ["test_*"]
|
|
106
|
-
|
|
107
|
-
[tool.coverage.run]
|
|
108
|
-
source = ["src/claude_statusline"]
|
|
109
|
-
omit = ["tests/*"]
|
|
110
|
-
|
|
111
|
-
[tool.coverage.report]
|
|
112
|
-
exclude_lines = [
|
|
113
|
-
"pragma: no cover",
|
|
114
|
-
"if __name__ == .__main__.:",
|
|
115
|
-
]
|
package/requirements-dev.txt
DELETED
|
@@ -1,438 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Full-featured status line with context window usage
|
|
3
|
-
# Usage: Copy to ~/.claude/statusline.sh and make executable
|
|
4
|
-
#
|
|
5
|
-
# Configuration:
|
|
6
|
-
# Create/edit ~/.claude/statusline.conf and set:
|
|
7
|
-
#
|
|
8
|
-
# autocompact=true (when autocompact is enabled in Claude Code - default)
|
|
9
|
-
# autocompact=false (when you disable autocompact via /config in Claude Code)
|
|
10
|
-
#
|
|
11
|
-
# token_detail=true (show exact token count like 64,000 - default)
|
|
12
|
-
# token_detail=false (show abbreviated tokens like 64.0k)
|
|
13
|
-
#
|
|
14
|
-
# show_delta=true (show token delta since last refresh like [+2,500] - default)
|
|
15
|
-
# show_delta=false (disable delta display - saves file I/O on every refresh)
|
|
16
|
-
#
|
|
17
|
-
# show_session=true (show session_id in status line - default)
|
|
18
|
-
# show_session=false (hide session_id from status line)
|
|
19
|
-
#
|
|
20
|
-
# When AC is enabled, 22.5% of context window is reserved for autocompact buffer.
|
|
21
|
-
#
|
|
22
|
-
# State file format (CSV):
|
|
23
|
-
# timestamp,total_input_tokens,total_output_tokens,current_usage_input_tokens,current_usage_output_tokens,current_usage_cache_creation,current_usage_cache_read,total_cost_usd,total_lines_added,total_lines_removed,session_id,model_id,workspace_project_dir
|
|
24
|
-
|
|
25
|
-
# Colors (defaults, overridable via config)
|
|
26
|
-
BLUE='\033[0;34m'
|
|
27
|
-
MAGENTA='\033[0;35m'
|
|
28
|
-
CYAN='\033[0;36m'
|
|
29
|
-
GREEN='\033[0;32m'
|
|
30
|
-
YELLOW='\033[0;33m'
|
|
31
|
-
RED='\033[0;31m'
|
|
32
|
-
DIM='\033[2m'
|
|
33
|
-
RESET='\033[0m'
|
|
34
|
-
|
|
35
|
-
# Named colors for config parsing
|
|
36
|
-
declare -A COLOR_NAMES=(
|
|
37
|
-
[black]='\033[0;30m' [red]='\033[0;31m' [green]='\033[0;32m'
|
|
38
|
-
[yellow]='\033[0;33m' [blue]='\033[0;34m' [magenta]='\033[0;35m'
|
|
39
|
-
[cyan]='\033[0;36m' [white]='\033[0;37m'
|
|
40
|
-
[bright_black]='\033[0;90m' [bright_red]='\033[0;91m' [bright_green]='\033[0;92m'
|
|
41
|
-
[bright_yellow]='\033[0;93m' [bright_blue]='\033[0;94m' [bright_magenta]='\033[0;95m'
|
|
42
|
-
[bright_cyan]='\033[0;96m' [bright_white]='\033[0;97m'
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
# Color config key to slot mapping
|
|
46
|
-
declare -A COLOR_KEYS=(
|
|
47
|
-
[color_green]=GREEN [color_yellow]=YELLOW [color_red]=RED
|
|
48
|
-
[color_blue]=BLUE [color_magenta]=MAGENTA [color_cyan]=CYAN
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
# Parse a color name or #rrggbb hex into an ANSI escape code
|
|
52
|
-
parse_color() {
|
|
53
|
-
local value
|
|
54
|
-
value=$(echo "$1" | tr '[:upper:]' '[:lower:]' | xargs)
|
|
55
|
-
if [[ -n "${COLOR_NAMES[$value]+x}" ]]; then
|
|
56
|
-
echo "${COLOR_NAMES[$value]}"
|
|
57
|
-
return
|
|
58
|
-
fi
|
|
59
|
-
if [[ "$value" =~ ^#[0-9a-f]{6}$ ]]; then
|
|
60
|
-
local r=$((16#${value:1:2}))
|
|
61
|
-
local g=$((16#${value:3:2}))
|
|
62
|
-
local b=$((16#${value:5:2}))
|
|
63
|
-
echo "\033[38;2;${r};${g};${b}m"
|
|
64
|
-
return
|
|
65
|
-
fi
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
# State file rotation constants
|
|
69
|
-
ROTATION_THRESHOLD=10000
|
|
70
|
-
ROTATION_KEEP=5000
|
|
71
|
-
|
|
72
|
-
# Rotate state file if it exceeds threshold
|
|
73
|
-
maybe_rotate_state_file() {
|
|
74
|
-
local state_file="$1"
|
|
75
|
-
[[ -f "$state_file" ]] || return
|
|
76
|
-
local line_count
|
|
77
|
-
line_count=$(wc -l < "$state_file" | tr -d ' ')
|
|
78
|
-
if [[ "$line_count" -gt "$ROTATION_THRESHOLD" ]]; then
|
|
79
|
-
local tmp_file="${state_file}.tmp.$$"
|
|
80
|
-
tail -n "$ROTATION_KEEP" "$state_file" > "$tmp_file" && mv "$tmp_file" "$state_file" || rm -f "$tmp_file"
|
|
81
|
-
fi
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
# Model Intelligence computation (uses awk for float math)
|
|
85
|
-
compute_mi() {
|
|
86
|
-
local used_tokens=$1 context_window=$2 cache_read_val=$3 total_context=$4
|
|
87
|
-
local delta_lines=$5 delta_output=$6 beta=$7
|
|
88
|
-
awk -v used="$used_tokens" -v cw="$context_window" -v cr="$cache_read_val" \
|
|
89
|
-
-v tc="$total_context" -v dl="$delta_lines" -v do_val="$delta_output" -v b="$beta" '
|
|
90
|
-
BEGIN {
|
|
91
|
-
if (cw == 0) { printf "1.00 1.000 1.000 0.500"; exit }
|
|
92
|
-
# CPS
|
|
93
|
-
u = used / cw
|
|
94
|
-
if (u <= 0) cps = 1.0
|
|
95
|
-
else { cps = 1.0 - (u ^ b); if (cps < 0) cps = 0.0 }
|
|
96
|
-
# ES
|
|
97
|
-
if (tc == 0) es = 1.0
|
|
98
|
-
else es = 0.3 + 0.7 * (cr / tc)
|
|
99
|
-
# PS
|
|
100
|
-
if (do_val == "" || do_val + 0 <= 0) ps = 0.5
|
|
101
|
-
else {
|
|
102
|
-
ratio = dl / do_val
|
|
103
|
-
normalized = ratio / 0.2
|
|
104
|
-
if (normalized > 1.0) normalized = 1.0
|
|
105
|
-
ps = 0.2 + 0.8 * normalized
|
|
106
|
-
}
|
|
107
|
-
mi = 0.60 * cps + 0.25 * es + 0.15 * ps
|
|
108
|
-
printf "%.2f %.3f %.3f %.3f", mi, cps, es, ps
|
|
109
|
-
}'
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
get_mi_color() {
|
|
113
|
-
local mi_val="$1"
|
|
114
|
-
awk -v mi="$mi_val" 'BEGIN {
|
|
115
|
-
if (mi + 0 > 0.65) print "green"
|
|
116
|
-
else if (mi + 0 > 0.35) print "yellow"
|
|
117
|
-
else print "red"
|
|
118
|
-
}'
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
# Read JSON input from stdin
|
|
122
|
-
input=$(cat)
|
|
123
|
-
|
|
124
|
-
# Extract information from JSON
|
|
125
|
-
cwd=$(echo "$input" | jq -r '.workspace.current_dir')
|
|
126
|
-
project_dir=$(echo "$input" | jq -r '.workspace.project_dir')
|
|
127
|
-
model=$(echo "$input" | jq -r '.model.display_name // "Claude"')
|
|
128
|
-
session_id=$(echo "$input" | jq -r '.session_id // empty')
|
|
129
|
-
dir_name=$(basename "$cwd")
|
|
130
|
-
|
|
131
|
-
# Git information (skip optional locks for performance)
|
|
132
|
-
git_info=""
|
|
133
|
-
if [[ -d "$project_dir/.git" ]]; then
|
|
134
|
-
git_branch=$(cd "$project_dir" 2>/dev/null && git --no-optional-locks rev-parse --abbrev-ref HEAD 2>/dev/null)
|
|
135
|
-
git_status_count=$(cd "$project_dir" 2>/dev/null && git --no-optional-locks status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
136
|
-
|
|
137
|
-
if [[ -n "$git_branch" ]]; then
|
|
138
|
-
if [[ "$git_status_count" != "0" ]]; then
|
|
139
|
-
git_info=" | ${MAGENTA}${git_branch}${RESET} ${CYAN}[${git_status_count}]${RESET}"
|
|
140
|
-
else
|
|
141
|
-
git_info=" | ${MAGENTA}${git_branch}${RESET}"
|
|
142
|
-
fi
|
|
143
|
-
fi
|
|
144
|
-
fi
|
|
145
|
-
|
|
146
|
-
# Read settings from ~/.claude/statusline.conf
|
|
147
|
-
# Sync this manually when you change settings in Claude Code via /config
|
|
148
|
-
autocompact_enabled=true
|
|
149
|
-
token_detail_enabled=true
|
|
150
|
-
show_delta_enabled=true
|
|
151
|
-
show_session_enabled=true
|
|
152
|
-
show_mi_enabled=true
|
|
153
|
-
mi_curve_beta=1.5
|
|
154
|
-
ac_info=""
|
|
155
|
-
delta_info=""
|
|
156
|
-
mi_info=""
|
|
157
|
-
session_info=""
|
|
158
|
-
|
|
159
|
-
# Create config file with defaults if it doesn't exist
|
|
160
|
-
if [[ ! -f ~/.claude/statusline.conf ]]; then
|
|
161
|
-
mkdir -p ~/.claude
|
|
162
|
-
cat >~/.claude/statusline.conf <<'EOF'
|
|
163
|
-
# Autocompact setting - sync with Claude Code's /config
|
|
164
|
-
autocompact=true
|
|
165
|
-
|
|
166
|
-
# Token display format
|
|
167
|
-
token_detail=true
|
|
168
|
-
|
|
169
|
-
# Show token delta since last refresh (adds file I/O on every refresh)
|
|
170
|
-
# Disable if you don't need it to reduce overhead
|
|
171
|
-
show_delta=true
|
|
172
|
-
|
|
173
|
-
# Show session_id in status line
|
|
174
|
-
show_session=true
|
|
175
|
-
|
|
176
|
-
# Model Intelligence (MI) score display
|
|
177
|
-
show_mi=true
|
|
178
|
-
|
|
179
|
-
# MI degradation curve shape (higher = steeper initial drop)
|
|
180
|
-
# mi_curve_beta=1.5
|
|
181
|
-
EOF
|
|
182
|
-
fi
|
|
183
|
-
|
|
184
|
-
if [[ -f ~/.claude/statusline.conf ]]; then
|
|
185
|
-
while IFS= read -r line; do
|
|
186
|
-
line=$(echo "$line" | xargs)
|
|
187
|
-
[[ -z "$line" || "$line" == \#* ]] && continue
|
|
188
|
-
[[ "$line" != *=* ]] && continue
|
|
189
|
-
key="${line%%=*}"
|
|
190
|
-
key=$(echo "$key" | xargs)
|
|
191
|
-
raw_value="${line#*=}"
|
|
192
|
-
raw_value=$(echo "$raw_value" | xargs)
|
|
193
|
-
value_lower=$(echo "$raw_value" | tr '[:upper:]' '[:lower:]')
|
|
194
|
-
case "$key" in
|
|
195
|
-
autocompact) [[ "$value_lower" == "false" ]] && autocompact_enabled=false ;;
|
|
196
|
-
token_detail) [[ "$value_lower" == "false" ]] && token_detail_enabled=false ;;
|
|
197
|
-
show_delta) [[ "$value_lower" == "false" ]] && show_delta_enabled=false ;;
|
|
198
|
-
show_session) [[ "$value_lower" == "false" ]] && show_session_enabled=false ;;
|
|
199
|
-
show_mi) [[ "$value_lower" == "false" ]] && show_mi_enabled=false ;;
|
|
200
|
-
mi_curve_beta) mi_curve_beta="$raw_value" ;;
|
|
201
|
-
color_*)
|
|
202
|
-
if [[ -n "${COLOR_KEYS[$key]+x}" ]]; then
|
|
203
|
-
local slot="${COLOR_KEYS[$key]}"
|
|
204
|
-
local ansi
|
|
205
|
-
ansi=$(parse_color "$raw_value")
|
|
206
|
-
if [[ -n "$ansi" ]]; then
|
|
207
|
-
eval "$slot='$ansi'"
|
|
208
|
-
fi
|
|
209
|
-
fi
|
|
210
|
-
;;
|
|
211
|
-
esac
|
|
212
|
-
done < ~/.claude/statusline.conf
|
|
213
|
-
fi
|
|
214
|
-
|
|
215
|
-
# Width-fitting helpers
|
|
216
|
-
visible_width() {
|
|
217
|
-
# Strip ANSI escape sequences (both literal \033 and actual ESC byte) and return string length
|
|
218
|
-
local stripped
|
|
219
|
-
stripped=$(printf '%s' "$1" | sed -e $'s/\033\[[0-9;]*m//g' -e 's/\\033\[[0-9;]*m//g')
|
|
220
|
-
printf '%s' "$stripped" | wc -m | tr -d ' '
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
get_terminal_width() {
|
|
224
|
-
# Return terminal width for fit_to_width truncation.
|
|
225
|
-
# When running inside Claude Code's statusline subprocess, neither $COLUMNS
|
|
226
|
-
# nor tput can detect the real terminal width (they always return 80).
|
|
227
|
-
# If COLUMNS is explicitly set, trust it. Otherwise use 200 as default
|
|
228
|
-
# so no parts are unnecessarily dropped; Claude Code handles overflow.
|
|
229
|
-
if [[ -n "$COLUMNS" ]]; then
|
|
230
|
-
echo "$COLUMNS"
|
|
231
|
-
else
|
|
232
|
-
local cols
|
|
233
|
-
cols=$(tput cols 2>/dev/null || echo 80)
|
|
234
|
-
if [[ "$cols" -eq 80 ]]; then
|
|
235
|
-
echo 200
|
|
236
|
-
else
|
|
237
|
-
echo "$cols"
|
|
238
|
-
fi
|
|
239
|
-
fi
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
fit_to_width() {
|
|
243
|
-
# Assemble parts into a single line that fits within max_width.
|
|
244
|
-
# Usage: fit_to_width max_width part1 part2 part3 ...
|
|
245
|
-
# First part (base) is always included. Subsequent parts are
|
|
246
|
-
# included only if adding them does not exceed max_width.
|
|
247
|
-
local max_width=$1
|
|
248
|
-
shift
|
|
249
|
-
local parts=("$@")
|
|
250
|
-
|
|
251
|
-
if [[ ${#parts[@]} -eq 0 ]]; then
|
|
252
|
-
echo ""
|
|
253
|
-
return
|
|
254
|
-
fi
|
|
255
|
-
|
|
256
|
-
local result="${parts[0]}"
|
|
257
|
-
local current_width
|
|
258
|
-
current_width=$(visible_width "$result")
|
|
259
|
-
|
|
260
|
-
for ((i = 1; i < ${#parts[@]}; i++)); do
|
|
261
|
-
local part="${parts[$i]}"
|
|
262
|
-
if [[ -z "$part" ]]; then
|
|
263
|
-
continue
|
|
264
|
-
fi
|
|
265
|
-
local part_width
|
|
266
|
-
part_width=$(visible_width "$part")
|
|
267
|
-
if (( current_width + part_width <= max_width )); then
|
|
268
|
-
result+="$part"
|
|
269
|
-
(( current_width += part_width ))
|
|
270
|
-
fi
|
|
271
|
-
done
|
|
272
|
-
|
|
273
|
-
echo -e "$result"
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
# Calculate context window - show remaining free space
|
|
277
|
-
context_info=""
|
|
278
|
-
total_size=$(echo "$input" | jq -r '.context_window.context_window_size // 0')
|
|
279
|
-
current_usage=$(echo "$input" | jq '.context_window.current_usage')
|
|
280
|
-
total_input_tokens=$(echo "$input" | jq -r '.context_window.total_input_tokens // 0')
|
|
281
|
-
total_output_tokens=$(echo "$input" | jq -r '.context_window.total_output_tokens // 0')
|
|
282
|
-
cost_usd=$(echo "$input" | jq -r '.cost.total_cost_usd // 0')
|
|
283
|
-
lines_added=$(echo "$input" | jq -r '.cost.total_lines_added // 0')
|
|
284
|
-
lines_removed=$(echo "$input" | jq -r '.cost.total_lines_removed // 0')
|
|
285
|
-
model_id=$(echo "$input" | jq -r '.model.id // ""')
|
|
286
|
-
workspace_project_dir=$(echo "$input" | jq -r '.workspace.project_dir // ""' | tr ',' '_')
|
|
287
|
-
|
|
288
|
-
if [[ "$total_size" -gt 0 && "$current_usage" != "null" ]]; then
|
|
289
|
-
# Get tokens from current_usage (includes cache)
|
|
290
|
-
input_tokens=$(echo "$current_usage" | jq -r '.input_tokens // 0')
|
|
291
|
-
cache_creation=$(echo "$current_usage" | jq -r '.cache_creation_input_tokens // 0')
|
|
292
|
-
cache_read=$(echo "$current_usage" | jq -r '.cache_read_input_tokens // 0')
|
|
293
|
-
|
|
294
|
-
# Total used from current request
|
|
295
|
-
used_tokens=$((input_tokens + cache_creation + cache_read))
|
|
296
|
-
|
|
297
|
-
# Calculate autocompact buffer (22.5% of context window = 45k for 200k)
|
|
298
|
-
autocompact_buffer=$((total_size * 225 / 1000))
|
|
299
|
-
|
|
300
|
-
# Free tokens calculation depends on autocompact setting
|
|
301
|
-
if [[ "$autocompact_enabled" == "true" ]]; then
|
|
302
|
-
# When AC enabled: subtract buffer to show actual usable space
|
|
303
|
-
free_tokens=$((total_size - used_tokens - autocompact_buffer))
|
|
304
|
-
buffer_k=$(awk "BEGIN {printf \"%.0f\", $autocompact_buffer / 1000}")
|
|
305
|
-
ac_info=" ${DIM}[AC:${buffer_k}k]${RESET}"
|
|
306
|
-
else
|
|
307
|
-
# When AC disabled: show full free space
|
|
308
|
-
free_tokens=$((total_size - used_tokens))
|
|
309
|
-
ac_info=" ${DIM}[AC:off]${RESET}"
|
|
310
|
-
fi
|
|
311
|
-
|
|
312
|
-
if [[ "$free_tokens" -lt 0 ]]; then
|
|
313
|
-
free_tokens=0
|
|
314
|
-
fi
|
|
315
|
-
|
|
316
|
-
# Calculate percentage with one decimal (relative to total size)
|
|
317
|
-
free_pct=$(awk "BEGIN {printf \"%.1f\", ($free_tokens * 100.0 / $total_size)}")
|
|
318
|
-
free_pct_int=${free_pct%.*}
|
|
319
|
-
|
|
320
|
-
# Format tokens based on token_detail setting
|
|
321
|
-
if [[ "$token_detail_enabled" == "true" ]]; then
|
|
322
|
-
# Use awk for portable comma formatting (works regardless of locale)
|
|
323
|
-
free_display=$(awk -v n="$free_tokens" 'BEGIN { printf "%\047d", n }')
|
|
324
|
-
else
|
|
325
|
-
free_display=$(awk "BEGIN {printf \"%.1fk\", $free_tokens / 1000}")
|
|
326
|
-
fi
|
|
327
|
-
|
|
328
|
-
# Color based on free percentage
|
|
329
|
-
if [[ "$free_pct_int" -gt 50 ]]; then
|
|
330
|
-
ctx_color="$GREEN"
|
|
331
|
-
elif [[ "$free_pct_int" -gt 25 ]]; then
|
|
332
|
-
ctx_color="$YELLOW"
|
|
333
|
-
else
|
|
334
|
-
ctx_color="$RED"
|
|
335
|
-
fi
|
|
336
|
-
|
|
337
|
-
context_info=" | ${ctx_color}${free_display} (${free_pct}%)${RESET}"
|
|
338
|
-
|
|
339
|
-
# Read previous entry if needed for delta OR MI
|
|
340
|
-
if [[ "$show_delta_enabled" == "true" || "$show_mi_enabled" == "true" ]]; then
|
|
341
|
-
# Use session_id for per-session state (avoids conflicts with parallel sessions)
|
|
342
|
-
state_dir=~/.claude/statusline
|
|
343
|
-
mkdir -p "$state_dir"
|
|
344
|
-
|
|
345
|
-
# Migrate old state files from ~/.claude/ to ~/.claude/statusline/ (one-time migration)
|
|
346
|
-
old_state_dir=~/.claude
|
|
347
|
-
for old_file in "$old_state_dir"/statusline*.state; do
|
|
348
|
-
if [[ -f "$old_file" ]]; then
|
|
349
|
-
new_file="${state_dir}/$(basename "$old_file")"
|
|
350
|
-
if [[ ! -f "$new_file" ]]; then
|
|
351
|
-
mv "$old_file" "$new_file" 2>/dev/null || true
|
|
352
|
-
else
|
|
353
|
-
rm -f "$old_file" 2>/dev/null || true
|
|
354
|
-
fi
|
|
355
|
-
fi
|
|
356
|
-
done
|
|
357
|
-
|
|
358
|
-
if [[ -n "$session_id" ]]; then
|
|
359
|
-
state_file=${state_dir}/statusline.${session_id}.state
|
|
360
|
-
else
|
|
361
|
-
state_file=${state_dir}/statusline.state
|
|
362
|
-
fi
|
|
363
|
-
has_prev=false
|
|
364
|
-
prev_tokens=0
|
|
365
|
-
prev_output_tokens=0
|
|
366
|
-
prev_lines_added=0
|
|
367
|
-
prev_lines_removed=0
|
|
368
|
-
if [[ -f "$state_file" ]]; then
|
|
369
|
-
has_prev=true
|
|
370
|
-
# Read last line and calculate previous state
|
|
371
|
-
# CSV: ts[0],in[1],out[2],cur_in[3],cur_out[4],cache_create[5],cache_read[6],
|
|
372
|
-
# cost[7],+lines[8],-lines[9],session[10],model[11],dir[12],size[13]
|
|
373
|
-
last_line=$(tail -1 "$state_file" 2>/dev/null)
|
|
374
|
-
if [[ -n "$last_line" ]]; then
|
|
375
|
-
prev_cur_in=$(echo "$last_line" | cut -d',' -f4)
|
|
376
|
-
prev_cache_create=$(echo "$last_line" | cut -d',' -f6)
|
|
377
|
-
prev_cache_read=$(echo "$last_line" | cut -d',' -f7)
|
|
378
|
-
prev_tokens=$(( ${prev_cur_in:-0} + ${prev_cache_create:-0} + ${prev_cache_read:-0} ))
|
|
379
|
-
prev_output_tokens=$(echo "$last_line" | cut -d',' -f3)
|
|
380
|
-
prev_lines_added=$(echo "$last_line" | cut -d',' -f9)
|
|
381
|
-
prev_lines_removed=$(echo "$last_line" | cut -d',' -f10)
|
|
382
|
-
fi
|
|
383
|
-
fi
|
|
384
|
-
|
|
385
|
-
# Calculate and display token delta if enabled
|
|
386
|
-
if [[ "$show_delta_enabled" == "true" ]]; then
|
|
387
|
-
delta=$((used_tokens - prev_tokens))
|
|
388
|
-
if [[ "$has_prev" == "true" && "$delta" -gt 0 ]]; then
|
|
389
|
-
if [[ "$token_detail_enabled" == "true" ]]; then
|
|
390
|
-
delta_display=$(awk -v n="$delta" 'BEGIN { printf "%\047d", n }')
|
|
391
|
-
else
|
|
392
|
-
delta_display=$(awk "BEGIN {printf \"%.1fk\", $delta / 1000}")
|
|
393
|
-
fi
|
|
394
|
-
delta_info=" ${DIM}[+${delta_display}]${RESET}"
|
|
395
|
-
fi
|
|
396
|
-
fi
|
|
397
|
-
|
|
398
|
-
# Calculate and display MI score if enabled
|
|
399
|
-
if [[ "$show_mi_enabled" == "true" ]]; then
|
|
400
|
-
if [[ "$has_prev" == "true" ]]; then
|
|
401
|
-
delta_la=$(( ${lines_added:-0} - ${prev_lines_added:-0} ))
|
|
402
|
-
delta_lr=$(( ${lines_removed:-0} - ${prev_lines_removed:-0} ))
|
|
403
|
-
mi_delta_lines=$(( delta_la + delta_lr ))
|
|
404
|
-
mi_delta_output=$(( ${total_output_tokens:-0} - ${prev_output_tokens:-0} ))
|
|
405
|
-
else
|
|
406
|
-
mi_delta_lines=0
|
|
407
|
-
mi_delta_output=""
|
|
408
|
-
fi
|
|
409
|
-
mi_result=$(compute_mi "$used_tokens" "$total_size" "$cache_read" "$used_tokens" "$mi_delta_lines" "$mi_delta_output" "$mi_curve_beta")
|
|
410
|
-
mi_val=$(echo "$mi_result" | cut -d' ' -f1)
|
|
411
|
-
mi_color_name=$(get_mi_color "$mi_val")
|
|
412
|
-
case "$mi_color_name" in
|
|
413
|
-
green) mi_color="$GREEN" ;;
|
|
414
|
-
yellow) mi_color="$YELLOW" ;;
|
|
415
|
-
red) mi_color="$RED" ;;
|
|
416
|
-
esac
|
|
417
|
-
mi_info=" ${mi_color}MI:${mi_val}${RESET}"
|
|
418
|
-
fi
|
|
419
|
-
|
|
420
|
-
# Only append if context usage changed (avoid duplicates from multiple refreshes)
|
|
421
|
-
cur_input_tokens=$(echo "$current_usage" | jq -r '.input_tokens // 0')
|
|
422
|
-
cur_output_tokens=$(echo "$current_usage" | jq -r '.output_tokens // 0')
|
|
423
|
-
if [[ "$has_prev" != "true" || "$used_tokens" != "$prev_tokens" ]]; then
|
|
424
|
-
echo "$(date +%s),$total_input_tokens,$total_output_tokens,$cur_input_tokens,$cur_output_tokens,$cache_creation,$cache_read,$cost_usd,$lines_added,$lines_removed,$session_id,$model_id,$workspace_project_dir,$total_size" >>"$state_file"
|
|
425
|
-
maybe_rotate_state_file "$state_file"
|
|
426
|
-
fi
|
|
427
|
-
fi
|
|
428
|
-
fi
|
|
429
|
-
|
|
430
|
-
# Display session_id if enabled
|
|
431
|
-
if [[ "$show_session_enabled" == "true" && -n "$session_id" ]]; then
|
|
432
|
-
session_info=" ${DIM}${session_id}${RESET}"
|
|
433
|
-
fi
|
|
434
|
-
|
|
435
|
-
# Output: [Model] directory | branch [changes] | XXk free (XX%) [+delta] [AC] [S:session_id]
|
|
436
|
-
base="${DIM}[${model}]${RESET} ${BLUE}${dir_name}${RESET}"
|
|
437
|
-
max_width=$(get_terminal_width)
|
|
438
|
-
fit_to_width "$max_width" "$base" "$git_info" "$context_info" "$delta_info" "$mi_info" "$ac_info" "$session_info"
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# Git-aware status line - shows model, directory, and git branch
|
|
3
|
-
# Usage: Copy to ~/.claude/statusline.sh and make executable
|
|
4
|
-
|
|
5
|
-
# Colors
|
|
6
|
-
BLUE='\033[0;34m'
|
|
7
|
-
MAGENTA='\033[0;35m'
|
|
8
|
-
CYAN='\033[0;36m'
|
|
9
|
-
RESET='\033[0m'
|
|
10
|
-
|
|
11
|
-
input=$(cat)
|
|
12
|
-
|
|
13
|
-
MODEL_DISPLAY=$(echo "$input" | jq -r '.model.display_name // "Claude"')
|
|
14
|
-
CURRENT_DIR=$(echo "$input" | jq -r '.workspace.current_dir // "~"')
|
|
15
|
-
DIR_NAME="${CURRENT_DIR##*/}"
|
|
16
|
-
|
|
17
|
-
# Width-fitting helpers
|
|
18
|
-
visible_width() {
|
|
19
|
-
local stripped
|
|
20
|
-
stripped=$(printf '%s' "$1" | sed -e $'s/\033\[[0-9;]*m//g' -e 's/\\033\[[0-9;]*m//g')
|
|
21
|
-
printf '%s' "$stripped" | wc -m | tr -d ' '
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
get_terminal_width() {
|
|
25
|
-
# When running inside Claude Code's statusline subprocess, $COLUMNS is not set
|
|
26
|
-
# and tput falls back to 80. If COLUMNS is set, trust it. Otherwise use 200
|
|
27
|
-
# so no parts are dropped; Claude Code handles overflow.
|
|
28
|
-
if [[ -n "$COLUMNS" ]]; then
|
|
29
|
-
echo "$COLUMNS"
|
|
30
|
-
else
|
|
31
|
-
local cols
|
|
32
|
-
cols=$(tput cols 2>/dev/null || echo 80)
|
|
33
|
-
if [[ "$cols" -eq 80 ]]; then
|
|
34
|
-
echo 200
|
|
35
|
-
else
|
|
36
|
-
echo "$cols"
|
|
37
|
-
fi
|
|
38
|
-
fi
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
fit_to_width() {
|
|
42
|
-
local max_width=$1
|
|
43
|
-
shift
|
|
44
|
-
local parts=("$@")
|
|
45
|
-
|
|
46
|
-
if [[ ${#parts[@]} -eq 0 ]]; then
|
|
47
|
-
echo ""
|
|
48
|
-
return
|
|
49
|
-
fi
|
|
50
|
-
|
|
51
|
-
local result="${parts[0]}"
|
|
52
|
-
local current_width
|
|
53
|
-
current_width=$(visible_width "$result")
|
|
54
|
-
|
|
55
|
-
for ((i = 1; i < ${#parts[@]}; i++)); do
|
|
56
|
-
local part="${parts[$i]}"
|
|
57
|
-
if [[ -z "$part" ]]; then
|
|
58
|
-
continue
|
|
59
|
-
fi
|
|
60
|
-
local part_width
|
|
61
|
-
part_width=$(visible_width "$part")
|
|
62
|
-
if (( current_width + part_width <= max_width )); then
|
|
63
|
-
result+="$part"
|
|
64
|
-
(( current_width += part_width ))
|
|
65
|
-
fi
|
|
66
|
-
done
|
|
67
|
-
|
|
68
|
-
echo -e "$result"
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
# Git branch detection
|
|
72
|
-
GIT_INFO=""
|
|
73
|
-
if git -C "$CURRENT_DIR" rev-parse --git-dir > /dev/null 2>&1; then
|
|
74
|
-
BRANCH=$(git -C "$CURRENT_DIR" branch --show-current 2>/dev/null)
|
|
75
|
-
if [ -n "$BRANCH" ]; then
|
|
76
|
-
# Count uncommitted changes
|
|
77
|
-
CHANGES=$(git -C "$CURRENT_DIR" status --porcelain 2>/dev/null | wc -l | tr -d ' ')
|
|
78
|
-
if [ "$CHANGES" -gt 0 ]; then
|
|
79
|
-
GIT_INFO=" | ${MAGENTA}${BRANCH}${RESET} ${CYAN}[${CHANGES}]${RESET}"
|
|
80
|
-
else
|
|
81
|
-
GIT_INFO=" | ${MAGENTA}${BRANCH}${RESET}"
|
|
82
|
-
fi
|
|
83
|
-
fi
|
|
84
|
-
fi
|
|
85
|
-
|
|
86
|
-
base="[${MODEL_DISPLAY}] ${BLUE}${DIR_NAME}${RESET}"
|
|
87
|
-
max_width=$(get_terminal_width)
|
|
88
|
-
fit_to_width "$max_width" "$base" "$GIT_INFO"
|