token-tracker 0.3.5__tar.gz → 0.3.6__tar.gz
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.
- {token_tracker-0.3.5 → token_tracker-0.3.6}/PKG-INFO +1 -1
- {token_tracker-0.3.5 → token_tracker-0.3.6}/README.md +5 -5
- {token_tracker-0.3.5 → token_tracker-0.3.6}/pyproject.toml +1 -1
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/hooks.py +54 -29
- {token_tracker-0.3.5 → token_tracker-0.3.6}/token_tracker.egg-info/PKG-INFO +1 -1
- {token_tracker-0.3.5 → token_tracker-0.3.6}/setup.cfg +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/__init__.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/adapters/__init__.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/adapters/claude.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/adapters/codex.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/adapters/rate_limits.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/adapters/registry.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/adapters/types.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/analyzer/__init__.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/analyzer/aggregator.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/analyzer/blocks.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/analyzer/cost.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/cli.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/i18n.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/ui/__init__.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/src/ui/tables.py +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/token_tracker.egg-info/SOURCES.txt +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/token_tracker.egg-info/dependency_links.txt +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/token_tracker.egg-info/entry_points.txt +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/token_tracker.egg-info/requires.txt +0 -0
- {token_tracker-0.3.5 → token_tracker-0.3.6}/token_tracker.egg-info/top_level.txt +0 -0
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
自动为 Claude Code 和 Codex 配置状态栏,`tt setup` 一键配置,脚本更新时自动升级。
|
|
14
14
|
|
|
15
|
-
**Claude Code
|
|
15
|
+
**Claude Code**:基于官方自定义 StatusLine 接口,数据完全来自本地 Claude,准确无任何推测
|
|
16
16
|
|
|
17
17
|

|
|
18
18
|
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
| 2 | `(本轮: in 1, out 15)` | 当前对话轮次的 Token 用量 |
|
|
29
29
|
| 2 | `Cached: 204k` | 当前轮次命中的 Prompt Cache Token 数 |
|
|
30
30
|
| 2 | `Cost: $35.51` | 本次会话等效成本(按官方定价计算) |
|
|
31
|
-
| 3 |
|
|
32
|
-
| 3 | `
|
|
31
|
+
| 3 | `Model: Opus 4.6/high/nofast` | 模型名 / thinking 级别 / 是否 fast 模式 |
|
|
32
|
+
| 3 | `Duration: 1h33m` | 当前会话已持续时间 |
|
|
33
33
|
|
|
34
34
|
> 终端宽度不足时会自动降级:先隐藏重置倒计时,再将进度条简化为百分比数字。
|
|
35
35
|
|
|
36
|
-
**Codex
|
|
36
|
+
**Codex**:官方暂不支持自定义 StatusLine,使用官方默认样式,展示项目名、5h/7d 配额、上下文剩余、模型名
|
|
37
37
|
|
|
38
38
|

|
|
39
39
|
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
## 安装
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
curl -sSL https://raw.githubusercontent.com/stormzhang/token-tracker/
|
|
63
|
+
curl -sSL https://raw.githubusercontent.com/stormzhang/token-tracker/main/install.sh | bash
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
或者通过 pip:
|
|
@@ -12,7 +12,7 @@ CLAUDE_SETTINGS = os.path.expanduser("~/.claude/settings.json")
|
|
|
12
12
|
HOOK_SCRIPT_PATH = os.path.expanduser("~/.claude/tt-statusline.py")
|
|
13
13
|
CODEX_CONFIG = os.path.expanduser("~/.codex/config.toml")
|
|
14
14
|
CODEX_BACKUP = os.path.expanduser("~/.codex/tt-backup.json")
|
|
15
|
-
HOOK_VERSION = "1.
|
|
15
|
+
HOOK_VERSION = "1.6"
|
|
16
16
|
_BACKUP_KEY = "tokenTracker"
|
|
17
17
|
_PREV_SL_KEY = "previousStatusLine"
|
|
18
18
|
_SL_REGEX = re.compile(r'status_line\s*=\s*\[.*?\]', re.DOTALL)
|
|
@@ -27,17 +27,41 @@ CODEX_STATUS_LINE = [
|
|
|
27
27
|
|
|
28
28
|
HOOK_SCRIPT = r'''#!/usr/bin/env python3
|
|
29
29
|
"""Claude Code statusLine — 状态栏显示 + 数据持久化到 tt-status.json"""
|
|
30
|
-
__version__ = "1.
|
|
30
|
+
__version__ = "1.6"
|
|
31
31
|
import json, os, re, subprocess, sys, tempfile
|
|
32
32
|
from datetime import datetime, timezone
|
|
33
33
|
|
|
34
34
|
STATUS_FILE = os.path.expanduser("~/.claude/tt-status.json")
|
|
35
35
|
ANSI_RE = re.compile(r'\033\[[0-9;]*m')
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
36
|
+
THEME = "mocha"
|
|
37
|
+
THEMES = {
|
|
38
|
+
"default": {
|
|
39
|
+
"project": "\033[32m", "branch": "\033[35m", "label": "\033[34m",
|
|
40
|
+
"bar_ok": "\033[32m", "bar_warn": "\033[33m", "bar_danger": "\033[31m",
|
|
41
|
+
"tokens": "\033[33m", "duration": "\033[2;35m", "model": "\033[2;35m",
|
|
42
|
+
"reset": "\033[0m",
|
|
43
|
+
},
|
|
44
|
+
"mocha": {
|
|
45
|
+
"project": "\033[38;5;120m", "branch": "\033[38;5;211m", "label": "\033[38;5;218m",
|
|
46
|
+
"bar_ok": "\033[38;5;151m", "bar_warn": "\033[38;5;229m", "bar_danger": "\033[38;5;217m",
|
|
47
|
+
"tokens": "\033[38;5;223m", "duration": "\033[38;5;111m", "model": "\033[38;5;111m",
|
|
48
|
+
"reset": "\033[0m",
|
|
49
|
+
},
|
|
50
|
+
"dracula": {
|
|
51
|
+
"project": "\033[38;5;84m", "branch": "\033[38;5;75m", "label": "\033[38;5;212m",
|
|
52
|
+
"bar_ok": "\033[38;5;151m", "bar_warn": "\033[38;5;229m", "bar_danger": "\033[38;5;217m",
|
|
53
|
+
"tokens": "\033[38;5;215m", "duration": "\033[38;5;117m", "model": "\033[38;5;117m",
|
|
54
|
+
"reset": "\033[0m",
|
|
55
|
+
},
|
|
40
56
|
}
|
|
57
|
+
def _supports_256color():
|
|
58
|
+
if os.environ.get("COLORTERM", "") in ("truecolor", "24bit"):
|
|
59
|
+
return True
|
|
60
|
+
if "256color" in os.environ.get("TERM", ""):
|
|
61
|
+
return True
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
C = THEMES.get(THEME, THEMES["default"]) if _supports_256color() or THEME == "default" else THEMES["default"]
|
|
41
65
|
|
|
42
66
|
if sys.platform == "win32":
|
|
43
67
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
@@ -64,7 +88,7 @@ def get_width():
|
|
|
64
88
|
|
|
65
89
|
|
|
66
90
|
def color_by_pct(pct):
|
|
67
|
-
return C["
|
|
91
|
+
return C["bar_ok"] if pct < 50 else C["bar_warn"] if pct < 80 else C["bar_danger"]
|
|
68
92
|
|
|
69
93
|
|
|
70
94
|
def fmt_tokens(n):
|
|
@@ -79,7 +103,7 @@ def progress_bar(value, bar_width=8):
|
|
|
79
103
|
return empty_char * bar_width + " n/a"
|
|
80
104
|
pct = max(0.0, min(100.0, float(value)))
|
|
81
105
|
filled = round(pct / 100 * bar_width)
|
|
82
|
-
return f"{color_by_pct(pct)}{filled_char * filled}{C['reset']}{empty_char * (bar_width - filled)} {pct:.0f}%"
|
|
106
|
+
return f"{color_by_pct(pct)}{filled_char * filled}{C['reset']}{empty_char * (bar_width - filled)} {C['label']}{pct:.0f}%{C['reset']}"
|
|
83
107
|
|
|
84
108
|
|
|
85
109
|
def fmt_duration(seconds):
|
|
@@ -145,9 +169,9 @@ def render(data, now):
|
|
|
145
169
|
name = os.path.basename(project)
|
|
146
170
|
branch = git_branch(project)
|
|
147
171
|
if branch:
|
|
148
|
-
line1.append(f"{C['
|
|
172
|
+
line1.append(f"{C['project']}{name}{C['reset']}({C['branch']}{branch}{C['reset']})")
|
|
149
173
|
else:
|
|
150
|
-
line1.append(f"{C['
|
|
174
|
+
line1.append(f"{C['project']}{name}{C['reset']}")
|
|
151
175
|
|
|
152
176
|
rl = data.get("rate_limits") or {}
|
|
153
177
|
rl_parts = []
|
|
@@ -160,19 +184,19 @@ def render(data, now):
|
|
|
160
184
|
if resets_at:
|
|
161
185
|
remain = int(resets_at) - int(now.timestamp())
|
|
162
186
|
if remain > 0:
|
|
163
|
-
reset_str = f" {C['
|
|
187
|
+
reset_str = f" \033[2m{C['label']}({fmt_duration(remain)}){C['reset']}"
|
|
164
188
|
rl_parts.append((
|
|
165
|
-
f"{C['
|
|
166
|
-
f"{C['
|
|
167
|
-
f"{C['
|
|
189
|
+
f"{C['label']}{label}:{C['reset']}{progress_bar(pct, bar_w)}{reset_str}",
|
|
190
|
+
f"{C['label']}{label}:{C['reset']}{progress_bar(pct, bar_w)}",
|
|
191
|
+
f"{C['label']}{label}:{pct:.0f}%{C['reset']}",
|
|
168
192
|
))
|
|
169
193
|
|
|
170
194
|
ctx_parts = []
|
|
171
195
|
if ctx.get("used_percentage") is not None:
|
|
172
196
|
size = ctx.get("context_window_size", 0)
|
|
173
197
|
ctx_parts = [
|
|
174
|
-
f"{C['
|
|
175
|
-
f"{C['
|
|
198
|
+
f"{C['label']}{fmt_tokens(size)} Context:{C['reset']}{progress_bar(ctx['used_percentage'], bar_w)}",
|
|
199
|
+
f"{C['label']}{fmt_tokens(size)} CTX:{ctx['used_percentage']:.0f}%{C['reset']}",
|
|
176
200
|
]
|
|
177
201
|
|
|
178
202
|
# 尝试完整版(带进度条+reset time)
|
|
@@ -199,19 +223,19 @@ def render(data, now):
|
|
|
199
223
|
curr_usage = (ctx.get("current_usage") or {})
|
|
200
224
|
turn_in_total = curr_usage.get("input_tokens", 0) + curr_usage.get("cache_creation_input_tokens", 0)
|
|
201
225
|
turn_out = curr_usage.get("output_tokens", 0)
|
|
202
|
-
turn_str = f" {C['
|
|
226
|
+
turn_str = f" \033[2m{C['tokens']}(本轮: in {fmt_tokens(turn_in_total)}, out {fmt_tokens(turn_out)}){C['reset']}"
|
|
203
227
|
if total_in or total_out:
|
|
204
|
-
tok_full = f"{C['
|
|
205
|
-
tok_short = f"{C['
|
|
228
|
+
tok_full = f"{C['tokens']}Tokens: in {fmt_tokens(total_in)}, out {fmt_tokens(total_out)}{turn_str}"
|
|
229
|
+
tok_short = f"{C['tokens']}Tokens: in {fmt_tokens(total_in)}, out {fmt_tokens(total_out)}{C['reset']}"
|
|
206
230
|
line2.append(tok_full)
|
|
207
231
|
cache_read = curr_usage.get("cache_read_input_tokens", 0)
|
|
208
232
|
if cache_read > 0:
|
|
209
|
-
line2.append(f"{C['
|
|
233
|
+
line2.append(f"{C['tokens']}Cached: {fmt_tokens(cache_read)}{C['reset']}")
|
|
210
234
|
|
|
211
235
|
cost = data.get("cost") or {}
|
|
212
236
|
usd = cost.get("total_cost_usd")
|
|
213
237
|
if usd is not None:
|
|
214
|
-
line2.append(f"{C['
|
|
238
|
+
line2.append(f"{C['tokens']}Cost: ${usd:.2f}{C['reset']}")
|
|
215
239
|
|
|
216
240
|
# 宽度不够时隐藏本轮数据
|
|
217
241
|
if vlen(" | ".join(line2)) > W and (total_in or total_out):
|
|
@@ -219,23 +243,24 @@ def render(data, now):
|
|
|
219
243
|
if vlen(" | ".join(line2)) > W:
|
|
220
244
|
line2 = line2[1:]
|
|
221
245
|
|
|
222
|
-
# --- Line 3:
|
|
246
|
+
# --- Line 3: Model | Duration ---
|
|
223
247
|
line3 = []
|
|
224
248
|
|
|
225
|
-
duration_ms = cost.get("total_duration_ms")
|
|
226
|
-
duration_part = ""
|
|
227
|
-
if duration_ms and duration_ms > 0:
|
|
228
|
-
duration_part = f"{C['dim']}{C['magenta']}会话时长: {fmt_duration(duration_ms / 1000)}{C['reset']}"
|
|
229
|
-
line3.append(duration_part)
|
|
230
|
-
|
|
231
249
|
model_name = (data.get("model") or {}).get("display_name", "")
|
|
232
250
|
if model_name:
|
|
251
|
+
model_name = re.sub(r'\s*\(.*?\)', '', model_name)
|
|
233
252
|
effort = (data.get("effort") or {}).get("level", "")
|
|
234
253
|
if effort:
|
|
235
254
|
model_name += f"/{effort}"
|
|
236
255
|
fast = data.get("fast_mode")
|
|
237
256
|
model_name += f"/{'fast' if fast else 'nofast'}"
|
|
238
|
-
line3.append(f"{C['
|
|
257
|
+
line3.append(f"{C['model']}Model: {model_name}{C['reset']}")
|
|
258
|
+
|
|
259
|
+
duration_ms = cost.get("total_duration_ms")
|
|
260
|
+
duration_part = ""
|
|
261
|
+
if duration_ms and duration_ms > 0:
|
|
262
|
+
duration_part = f"{C['duration']}Duration: {fmt_duration(duration_ms / 1000)}{C['reset']}"
|
|
263
|
+
line3.append(duration_part)
|
|
239
264
|
|
|
240
265
|
# 宽度不够时隐藏会话时长
|
|
241
266
|
if vlen(" | ".join(line3)) > W and duration_part:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|