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.
Files changed (26) hide show
  1. {token_tracker-0.3.5 → token_tracker-0.3.6}/PKG-INFO +1 -1
  2. {token_tracker-0.3.5 → token_tracker-0.3.6}/README.md +5 -5
  3. {token_tracker-0.3.5 → token_tracker-0.3.6}/pyproject.toml +1 -1
  4. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/hooks.py +54 -29
  5. {token_tracker-0.3.5 → token_tracker-0.3.6}/token_tracker.egg-info/PKG-INFO +1 -1
  6. {token_tracker-0.3.5 → token_tracker-0.3.6}/setup.cfg +0 -0
  7. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/__init__.py +0 -0
  8. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/adapters/__init__.py +0 -0
  9. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/adapters/claude.py +0 -0
  10. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/adapters/codex.py +0 -0
  11. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/adapters/rate_limits.py +0 -0
  12. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/adapters/registry.py +0 -0
  13. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/adapters/types.py +0 -0
  14. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/analyzer/__init__.py +0 -0
  15. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/analyzer/aggregator.py +0 -0
  16. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/analyzer/blocks.py +0 -0
  17. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/analyzer/cost.py +0 -0
  18. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/cli.py +0 -0
  19. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/i18n.py +0 -0
  20. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/ui/__init__.py +0 -0
  21. {token_tracker-0.3.5 → token_tracker-0.3.6}/src/ui/tables.py +0 -0
  22. {token_tracker-0.3.5 → token_tracker-0.3.6}/token_tracker.egg-info/SOURCES.txt +0 -0
  23. {token_tracker-0.3.5 → token_tracker-0.3.6}/token_tracker.egg-info/dependency_links.txt +0 -0
  24. {token_tracker-0.3.5 → token_tracker-0.3.6}/token_tracker.egg-info/entry_points.txt +0 -0
  25. {token_tracker-0.3.5 → token_tracker-0.3.6}/token_tracker.egg-info/requires.txt +0 -0
  26. {token_tracker-0.3.5 → token_tracker-0.3.6}/token_tracker.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: token-tracker
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: Track token usage across local AI agents (Claude Code, Codex)
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: rich>=13.7
@@ -12,7 +12,7 @@
12
12
 
13
13
  自动为 Claude Code 和 Codex 配置状态栏,`tt setup` 一键配置,脚本更新时自动升级。
14
14
 
15
- **Claude Code**:项目名、5h/7d 配额进度条、CTX 窗口占比、Token 用量、模型名
15
+ **Claude Code**:基于官方自定义 StatusLine 接口,数据完全来自本地 Claude,准确无任何推测
16
16
 
17
17
  ![Claude Code StatusLine](assets/screenshot-statusline-cc.png)
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 | `会话时长: 3h20m` | 当前会话已持续时间 |
32
- | 3 | `Opus 4.6 (1M context)/high/nofast` | 模型名 / thinking 级别 / 是否 fast 模式 |
31
+ | 3 | `Model: Opus 4.6/high/nofast` | 模型名 / thinking 级别 / 是否 fast 模式 |
32
+ | 3 | `Duration: 1h33m` | 当前会话已持续时间 |
33
33
 
34
34
  > 终端宽度不足时会自动降级:先隐藏重置倒计时,再将进度条简化为百分比数字。
35
35
 
36
- **Codex**:项目名、5h/7d 配额、上下文剩余、模型名
36
+ **Codex**:官方暂不支持自定义 StatusLine,使用官方默认样式,展示项目名、5h/7d 配额、上下文剩余、模型名
37
37
 
38
38
  ![Codex StatusLine](assets/screenshot-statusline-codex.png)
39
39
 
@@ -60,7 +60,7 @@
60
60
  ## 安装
61
61
 
62
62
  ```bash
63
- curl -sSL https://raw.githubusercontent.com/stormzhang/token-tracker/master/install.sh | bash
63
+ curl -sSL https://raw.githubusercontent.com/stormzhang/token-tracker/main/install.sh | bash
64
64
  ```
65
65
 
66
66
  或者通过 pip:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "token-tracker"
7
- version = "0.3.5"
7
+ version = "0.3.6"
8
8
  description = "Track token usage across local AI agents (Claude Code, Codex)"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [
@@ -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.5"
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.5"
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
- C = {
37
- "green": "\033[32m", "yellow": "\033[33m", "red": "\033[31m",
38
- "cyan": "\033[36m", "blue": "\033[34m", "magenta": "\033[35m",
39
- "peach": "\033[38;5;216m", "dim": "\033[2m", "reset": "\033[0m",
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["green"] if pct < 50 else C["yellow"] if pct < 80 else C["red"]
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['green']}{name}{C['reset']}({C['magenta']}{branch}{C['reset']})")
172
+ line1.append(f"{C['project']}{name}{C['reset']}({C['branch']}{branch}{C['reset']})")
149
173
  else:
150
- line1.append(f"{C['green']}{name}{C['reset']}")
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['dim']}({fmt_duration(remain)}){C['reset']}"
187
+ reset_str = f" \033[2m{C['label']}({fmt_duration(remain)}){C['reset']}"
164
188
  rl_parts.append((
165
- f"{C['blue']}{label}:{C['reset']}{progress_bar(pct, bar_w)}{reset_str}",
166
- f"{C['blue']}{label}:{C['reset']}{progress_bar(pct, bar_w)}",
167
- f"{C['blue']}{label}:{C['reset']}{pct:.0f}%",
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['blue']}{fmt_tokens(size)} Context:{C['reset']}{progress_bar(ctx['used_percentage'], bar_w)}",
175
- f"{C['blue']}{fmt_tokens(size)} CTX:{C['reset']}{ctx['used_percentage']:.0f}%",
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['dim']}(本轮: in {fmt_tokens(turn_in_total)}, out {fmt_tokens(turn_out)}){C['reset']}"
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['peach']}Tokens: in {fmt_tokens(total_in)}, out {fmt_tokens(total_out)}{turn_str}"
205
- tok_short = f"{C['peach']}Tokens: in {fmt_tokens(total_in)}, out {fmt_tokens(total_out)}{C['reset']}"
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['cyan']}Cached: {fmt_tokens(cache_read)}{C['reset']}")
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['magenta']}Cost: ${usd:.2f}{C['reset']}")
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: Duration + Model ---
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['dim']}{C['magenta']}{model_name}{C['reset']}")
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: token-tracker
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: Track token usage across local AI agents (Claude Code, Codex)
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: rich>=13.7
File without changes
File without changes
File without changes