token-tracker 0.3.1__tar.gz → 0.3.2__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 (25) hide show
  1. {token_tracker-0.3.1 → token_tracker-0.3.2}/PKG-INFO +1 -1
  2. {token_tracker-0.3.1 → token_tracker-0.3.2}/pyproject.toml +1 -1
  3. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/hooks.py +74 -17
  4. {token_tracker-0.3.1 → token_tracker-0.3.2}/token_tracker.egg-info/PKG-INFO +1 -1
  5. {token_tracker-0.3.1 → token_tracker-0.3.2}/README.md +0 -0
  6. {token_tracker-0.3.1 → token_tracker-0.3.2}/setup.cfg +0 -0
  7. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/__init__.py +0 -0
  8. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/adapters/__init__.py +0 -0
  9. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/adapters/claude.py +0 -0
  10. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/adapters/codex.py +0 -0
  11. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/adapters/rate_limits.py +0 -0
  12. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/adapters/registry.py +0 -0
  13. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/adapters/types.py +0 -0
  14. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/analyzer/__init__.py +0 -0
  15. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/analyzer/aggregator.py +0 -0
  16. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/analyzer/blocks.py +0 -0
  17. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/analyzer/cost.py +0 -0
  18. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/cli.py +0 -0
  19. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/ui/__init__.py +0 -0
  20. {token_tracker-0.3.1 → token_tracker-0.3.2}/src/ui/tables.py +0 -0
  21. {token_tracker-0.3.1 → token_tracker-0.3.2}/token_tracker.egg-info/SOURCES.txt +0 -0
  22. {token_tracker-0.3.1 → token_tracker-0.3.2}/token_tracker.egg-info/dependency_links.txt +0 -0
  23. {token_tracker-0.3.1 → token_tracker-0.3.2}/token_tracker.egg-info/entry_points.txt +0 -0
  24. {token_tracker-0.3.1 → token_tracker-0.3.2}/token_tracker.egg-info/requires.txt +0 -0
  25. {token_tracker-0.3.1 → token_tracker-0.3.2}/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.1
3
+ Version: 0.3.2
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "token-tracker"
7
- version = "0.3.1"
7
+ version = "0.3.2"
8
8
  description = "Track token usage across local AI agents (Claude Code, Codex)"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [
@@ -10,7 +10,7 @@ CLAUDE_SETTINGS = os.path.expanduser("~/.claude/settings.json")
10
10
  HOOK_SCRIPT_PATH = os.path.expanduser("~/.claude/tt-statusline.py")
11
11
  CODEX_CONFIG = os.path.expanduser("~/.codex/config.toml")
12
12
  CODEX_BACKUP = os.path.expanduser("~/.codex/tt-backup.json")
13
- HOOK_VERSION = "1.2"
13
+ HOOK_VERSION = "1.4"
14
14
  _BACKUP_KEY = "tokenTracker"
15
15
  _PREV_SL_KEY = "previousStatusLine"
16
16
  _SL_REGEX = re.compile(r'status_line\s*=\s*\[.*?\]', re.DOTALL)
@@ -25,12 +25,12 @@ CODEX_STATUS_LINE = [
25
25
 
26
26
  HOOK_SCRIPT = r'''#!/usr/bin/env python3
27
27
  """Claude Code statusLine — 状态栏显示 + 数据持久化到 tt-status.json"""
28
- __version__ = "1.2"
29
- import json, os, subprocess, sys, tempfile
28
+ __version__ = "1.4"
29
+ import json, os, re, subprocess, sys, tempfile
30
30
  from datetime import datetime, timezone
31
31
 
32
32
  STATUS_FILE = os.path.expanduser("~/.claude/tt-status.json")
33
- BAR = ("█", "░", 8)
33
+ ANSI_RE = re.compile(r'\033\[[0-9;]*m')
34
34
  C = {
35
35
  "green": "\033[32m", "yellow": "\033[33m", "red": "\033[31m",
36
36
  "cyan": "\033[36m", "blue": "\033[34m", "magenta": "\033[35m",
@@ -38,6 +38,24 @@ C = {
38
38
  }
39
39
 
40
40
 
41
+ def vlen(s):
42
+ return len(ANSI_RE.sub("", s))
43
+
44
+
45
+ def get_width():
46
+ try:
47
+ return max(1, os.get_terminal_size(2).columns - 4)
48
+ except Exception:
49
+ pass
50
+ import fcntl, struct, termios
51
+ try:
52
+ with open('/dev/tty', 'r') as tty:
53
+ res = fcntl.ioctl(tty, termios.TIOCGWINSZ, b'\x00' * 8)
54
+ return max(1, struct.unpack('hh', res[:4])[1] - 4)
55
+ except Exception:
56
+ return 116
57
+
58
+
41
59
  def color_by_pct(pct):
42
60
  return C["green"] if pct < 50 else C["yellow"] if pct < 80 else C["red"]
43
61
 
@@ -48,13 +66,13 @@ def fmt_tokens(n):
48
66
  return str(n)
49
67
 
50
68
 
51
- def progress_bar(value):
52
- filled_char, empty_char, width = BAR
69
+ def progress_bar(value, bar_width=8):
70
+ filled_char, empty_char = "█", "░"
53
71
  if value is None:
54
- return empty_char * width + " n/a"
72
+ return empty_char * bar_width + " n/a"
55
73
  pct = max(0.0, min(100.0, float(value)))
56
- filled = round(pct / 100 * width)
57
- return f"{color_by_pct(pct)}{filled_char * filled}{C['reset']}{empty_char * (width - filled)} {pct:.0f}%"
74
+ filled = round(pct / 100 * bar_width)
75
+ return f"{color_by_pct(pct)}{filled_char * filled}{C['reset']}{empty_char * (bar_width - filled)} {pct:.0f}%"
58
76
 
59
77
 
60
78
  def fmt_duration(seconds):
@@ -108,7 +126,9 @@ def save_data(data, now):
108
126
 
109
127
 
110
128
  def render(data, now):
129
+ W = get_width()
111
130
  ctx = data.get("context_window") or {}
131
+ bar_w = 8 if W >= 100 else 6 if W >= 60 else 4
112
132
 
113
133
  # --- Line 1: Project | 5h | 7d | CTX ---
114
134
  line1 = []
@@ -123,6 +143,7 @@ def render(data, now):
123
143
  line1.append(f"{C['green']}{name}{C['reset']}")
124
144
 
125
145
  rl = data.get("rate_limits") or {}
146
+ rl_parts = []
126
147
  for key, label in [("five_hour", "5h"), ("seven_day", "7d")]:
127
148
  entry = rl.get(key) or {}
128
149
  pct = entry.get("used_percentage")
@@ -133,11 +154,35 @@ def render(data, now):
133
154
  remain = int(resets_at) - int(now.timestamp())
134
155
  if remain > 0:
135
156
  reset_str = f" {C['dim']}({fmt_duration(remain)}){C['reset']}"
136
- line1.append(f"{C['blue']}{label}:{C['reset']}{progress_bar(pct)}{reset_str}")
157
+ rl_parts.append((
158
+ f"{C['blue']}{label}:{C['reset']}{progress_bar(pct, bar_w)}{reset_str}",
159
+ f"{C['blue']}{label}:{C['reset']}{progress_bar(pct, bar_w)}",
160
+ f"{C['blue']}{label}:{C['reset']}{pct:.0f}%",
161
+ ))
137
162
 
163
+ ctx_parts = []
138
164
  if ctx.get("used_percentage") is not None:
139
165
  size = ctx.get("context_window_size", 0)
140
- line1.append(f"{C['blue']}{fmt_tokens(size)} Context:{C['reset']}{progress_bar(ctx['used_percentage'])}")
166
+ ctx_parts = [
167
+ f"{C['blue']}{fmt_tokens(size)} Context:{C['reset']}{progress_bar(ctx['used_percentage'], bar_w)}",
168
+ f"{C['blue']}{fmt_tokens(size)} CTX:{C['reset']}{ctx['used_percentage']:.0f}%",
169
+ ]
170
+
171
+ # 尝试完整版(带进度条+reset time)
172
+ full = line1 + [p[0] for p in rl_parts] + (ctx_parts[:1] if ctx_parts else [])
173
+ candidate = " | ".join(full)
174
+ if vlen(candidate) <= W:
175
+ line1 = full
176
+ else:
177
+ # 去掉 reset time
178
+ no_reset = line1 + [p[1] for p in rl_parts] + (ctx_parts[:1] if ctx_parts else [])
179
+ candidate = " | ".join(no_reset)
180
+ if vlen(candidate) <= W:
181
+ line1 = no_reset
182
+ else:
183
+ # 去掉进度条,只留百分比
184
+ minimal = line1 + [p[2] for p in rl_parts] + (ctx_parts[1:2] if ctx_parts else [])
185
+ line1 = minimal
141
186
 
142
187
  # --- Line 2: Tokens + Cache + Cost ---
143
188
  line2 = []
@@ -147,11 +192,11 @@ def render(data, now):
147
192
  curr_usage = (ctx.get("current_usage") or {})
148
193
  turn_in_total = curr_usage.get("input_tokens", 0) + curr_usage.get("cache_creation_input_tokens", 0)
149
194
  turn_out = curr_usage.get("output_tokens", 0)
195
+ turn_str = f" {C['dim']}(本轮: in {fmt_tokens(turn_in_total)}, out {fmt_tokens(turn_out)}){C['reset']}"
150
196
  if total_in or total_out:
151
- tok = f"{C['peach']}Tokens: in {fmt_tokens(total_in)}, out {fmt_tokens(total_out)}"
152
- tok += f" {C['dim']}(本轮: in {fmt_tokens(turn_in_total)}, out {fmt_tokens(turn_out)})"
153
- tok += C['reset']
154
- line2.append(tok)
197
+ tok_full = f"{C['peach']}Tokens: in {fmt_tokens(total_in)}, out {fmt_tokens(total_out)}{turn_str}"
198
+ tok_short = f"{C['peach']}Tokens: in {fmt_tokens(total_in)}, out {fmt_tokens(total_out)}{C['reset']}"
199
+ line2.append(tok_full)
155
200
  cache_read = curr_usage.get("cache_read_input_tokens", 0)
156
201
  if cache_read > 0:
157
202
  line2.append(f"{C['cyan']}Cached: {fmt_tokens(cache_read)}{C['reset']}")
@@ -161,12 +206,20 @@ def render(data, now):
161
206
  if usd is not None:
162
207
  line2.append(f"{C['magenta']}Cost: ${usd:.2f}{C['reset']}")
163
208
 
209
+ # 宽度不够时隐藏本轮数据
210
+ if vlen(" | ".join(line2)) > W and (total_in or total_out):
211
+ line2[0] = tok_short
212
+ if vlen(" | ".join(line2)) > W:
213
+ line2 = line2[1:]
214
+
164
215
  # --- Line 3: Duration + Model ---
165
216
  line3 = []
166
217
 
167
218
  duration_ms = cost.get("total_duration_ms")
219
+ duration_part = ""
168
220
  if duration_ms and duration_ms > 0:
169
- line3.append(f"{C['dim']}{C['magenta']}会话时长: {fmt_duration(duration_ms / 1000)}{C['reset']}")
221
+ duration_part = f"{C['dim']}{C['magenta']}会话时长: {fmt_duration(duration_ms / 1000)}{C['reset']}"
222
+ line3.append(duration_part)
170
223
 
171
224
  model_name = (data.get("model") or {}).get("display_name", "")
172
225
  if model_name:
@@ -177,6 +230,10 @@ def render(data, now):
177
230
  model_name += f"/{'fast' if fast else 'nofast'}"
178
231
  line3.append(f"{C['dim']}{C['magenta']}{model_name}{C['reset']}")
179
232
 
233
+ # 宽度不够时隐藏会话时长
234
+ if vlen(" | ".join(line3)) > W and duration_part:
235
+ line3 = [p for p in line3 if p != duration_part]
236
+
180
237
  output = [" | ".join(line) for line in (line1, line2, line3) if line]
181
238
  if output:
182
239
  print("\n".join(output))
@@ -301,7 +358,7 @@ def _setup_claude() -> None:
301
358
  console.print(f"[yellow]检测到已有 statusLine,备份后替换[/yellow]")
302
359
  settings.setdefault(_BACKUP_KEY, {})[_PREV_SL_KEY] = existing
303
360
 
304
- settings["statusLine"] = {"type": "command", "command": HOOK_SCRIPT_PATH}
361
+ settings["statusLine"] = {"type": "command", "command": f"python3 {HOOK_SCRIPT_PATH}"}
305
362
 
306
363
  with open(CLAUDE_SETTINGS, "w", encoding="utf-8") as f:
307
364
  json.dump(settings, f, indent=2, ensure_ascii=False)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: token-tracker
3
- Version: 0.3.1
3
+ Version: 0.3.2
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