token-tracker 0.2.3__tar.gz → 0.2.4__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.2.3 → token_tracker-0.2.4}/PKG-INFO +1 -1
  2. {token_tracker-0.2.3 → token_tracker-0.2.4}/pyproject.toml +1 -1
  3. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/cli.py +78 -13
  4. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/ui/tables.py +27 -8
  5. {token_tracker-0.2.3 → token_tracker-0.2.4}/token_tracker.egg-info/PKG-INFO +1 -1
  6. {token_tracker-0.2.3 → token_tracker-0.2.4}/README.md +0 -0
  7. {token_tracker-0.2.3 → token_tracker-0.2.4}/setup.cfg +0 -0
  8. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/__init__.py +0 -0
  9. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/adapters/__init__.py +0 -0
  10. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/adapters/claude.py +0 -0
  11. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/adapters/codex.py +0 -0
  12. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/adapters/rate_limits.py +0 -0
  13. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/adapters/registry.py +0 -0
  14. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/adapters/types.py +0 -0
  15. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/analyzer/__init__.py +0 -0
  16. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/analyzer/aggregator.py +0 -0
  17. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/analyzer/blocks.py +0 -0
  18. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/analyzer/cost.py +0 -0
  19. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/hooks.py +0 -0
  20. {token_tracker-0.2.3 → token_tracker-0.2.4}/src/ui/__init__.py +0 -0
  21. {token_tracker-0.2.3 → token_tracker-0.2.4}/token_tracker.egg-info/SOURCES.txt +0 -0
  22. {token_tracker-0.2.3 → token_tracker-0.2.4}/token_tracker.egg-info/dependency_links.txt +0 -0
  23. {token_tracker-0.2.3 → token_tracker-0.2.4}/token_tracker.egg-info/entry_points.txt +0 -0
  24. {token_tracker-0.2.3 → token_tracker-0.2.4}/token_tracker.egg-info/requires.txt +0 -0
  25. {token_tracker-0.2.3 → token_tracker-0.2.4}/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.2.3
3
+ Version: 0.2.4
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.2.3"
7
+ version = "0.2.4"
8
8
  description = "Track token usage across local AI agents (Claude Code, Codex)"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [
@@ -64,48 +64,96 @@ def _build_agent_data(agent_id: str, agent_name: str) -> dict | None:
64
64
  )
65
65
 
66
66
 
67
+ def _initial_agent_index(agents) -> int:
68
+ import os
69
+
70
+ preferred = None
71
+ if os.environ.get("CODEX_THREAD_ID") or os.environ.get("CODEX_SANDBOX"):
72
+ preferred = "codex"
73
+ elif os.environ.get("CLAUDE_CONFIG_DIR") or os.environ.get("CLAUDECODE"):
74
+ preferred = "claude-code"
75
+
76
+ if preferred:
77
+ for i, agent in enumerate(agents):
78
+ if agent.id == preferred:
79
+ return i
80
+ return 0
81
+
82
+
83
+ def _fit_screen(text: str, height: int, scroll_offset: int) -> tuple[str, int]:
84
+ lines = text.splitlines()
85
+ if not lines:
86
+ return "", 0
87
+ max_body = max(1, height - 1)
88
+ max_scroll = max(0, len(lines) - max_body)
89
+ scroll_offset = max(0, min(scroll_offset, max_scroll))
90
+ visible = lines[:1] + lines[1 + scroll_offset:1 + scroll_offset + max_body - 1]
91
+ return "\n".join(visible), max_scroll
92
+
93
+
67
94
  def _show_interactive_dashboard(agents):
68
95
  import tty
69
96
  import termios
97
+ import shutil
70
98
  from io import StringIO
71
99
  from rich.console import Console as RichConsole
72
100
  import src.ui.tables as _tables
73
101
 
74
102
  agent_names = [a.name for a in agents]
75
- current = 0
103
+ current = _initial_agent_index(agents)
104
+ scroll_offset = 0
76
105
  orig = _tables.console
77
106
 
78
- sys.stdout.write("\033[?1049h\033[?25l")
79
- sys.stdout.write("\033[H\033[J\033[2m加载数据...\033[0m")
80
- sys.stdout.flush()
81
- cache = {a.id: _build_agent_data(a.id, a.name) for a in agents}
107
+ sys.stdout.write("\033[?1049h\033[?7l\033[2J\033[3J\033[H\033[?25l")
108
+ cache = {}
82
109
 
83
110
  try:
84
111
  while True:
112
+ agent = agents[current]
113
+ if agent.id not in cache:
114
+ sys.stdout.write("\033[2J\033[3J\033[H\033[2m加载数据...\033[0m")
115
+ sys.stdout.flush()
116
+ cache[agent.id] = _build_agent_data(agent.id, agent.name)
117
+
118
+ size = shutil.get_terminal_size((80, 24))
119
+ width = size.columns
120
+ height = size.lines
121
+
85
122
  buf = StringIO()
86
123
  _tables.console = RichConsole(
87
- file=buf, width=orig.width, force_terminal=True,
124
+ file=buf, width=width, force_terminal=True,
88
125
  )
89
126
  render_tab_bar(agent_names, current)
90
- data = cache[agents[current].id]
127
+ data = cache[agent.id]
91
128
  if data:
92
- render_dashboard(**data)
129
+ render_dashboard(**data, session_limit=10, top_margin=False)
93
130
  else:
94
131
  _tables.console.print(f"[yellow]暂无数据[/yellow]")
95
132
  _tables.console = orig
96
133
 
97
- sys.stdout.write("\033[H\033[J" + buf.getvalue())
134
+ screen, max_scroll = _fit_screen(buf.getvalue(), height, scroll_offset)
135
+ sys.stdout.write("\033[2J\033[3J\033[H" + screen)
98
136
  sys.stdout.flush()
99
137
 
100
138
  key = _read_key(tty, termios)
101
139
  if key == "left":
102
140
  current = (current - 1) % len(agents)
141
+ scroll_offset = 0
103
142
  elif key == "right":
104
143
  current = (current + 1) % len(agents)
144
+ scroll_offset = 0
145
+ elif key == "up":
146
+ scroll_offset = max(0, scroll_offset - 1)
147
+ elif key == "down":
148
+ scroll_offset = min(max_scroll, scroll_offset + 1)
149
+ elif key == "page_up":
150
+ scroll_offset = max(0, scroll_offset - max(1, height - 3))
151
+ elif key == "page_down":
152
+ scroll_offset = min(max_scroll, scroll_offset + max(1, height - 3))
105
153
  elif key == "quit":
106
154
  break
107
155
  finally:
108
- sys.stdout.write("\033[?25h\033[?1049l")
156
+ sys.stdout.write("\033[?7h\033[?25h\033[?1049l")
109
157
  sys.stdout.flush()
110
158
  _tables.console = orig
111
159
 
@@ -128,11 +176,27 @@ def _read_key(tty, termios):
128
176
  return "left"
129
177
  if ch3 == b"C":
130
178
  return "right"
179
+ if ch3 == b"A":
180
+ return "up"
181
+ if ch3 == b"B":
182
+ return "down"
183
+ if ch3 in (b"5", b"6"):
184
+ if select.select([fd], [], [], 0.05)[0]:
185
+ _os.read(fd, 1)
186
+ return "page_up" if ch3 == b"5" else "page_down"
131
187
  return "other"
132
- if ch in (b"h", b"k"):
188
+ if ch == b"h":
133
189
  return "left"
134
- if ch in (b"l", b"j"):
190
+ if ch == b"l":
135
191
  return "right"
192
+ if ch == b"k":
193
+ return "up"
194
+ if ch == b"j":
195
+ return "down"
196
+ if ch == b"b":
197
+ return "page_up"
198
+ if ch == b"f":
199
+ return "page_down"
136
200
  if ch in (b"q", b"Q", b"\x03"):
137
201
  return "quit"
138
202
  return "other"
@@ -166,7 +230,8 @@ def main():
166
230
 
167
231
  agent_ids = {a.id for a in agents}
168
232
 
169
- console.print(f"[dim]检测到: {', '.join(a.name + ' ✓' for a in agents)}[/dim]")
233
+ if command != "dashboard":
234
+ console.print(f"[dim]检测到: {', '.join(a.name + ' ✓' for a in agents)}[/dim]")
170
235
 
171
236
  if not is_setup():
172
237
  setup(auto=True)
@@ -184,14 +184,21 @@ def _render_week_section(lines: Text, week: WeeklyStats,
184
184
  def render_tab_bar(agent_names: list[str], current: int) -> None:
185
185
  line = Text()
186
186
  line.append(" ")
187
+ compact = console.width < 72
187
188
  for i, name in enumerate(agent_names):
188
189
  if i > 0:
189
190
  line.append(" │ ", style=_S.dim)
191
+ label = AGENT_SHORT.get("claude-code" if name == "Claude Code" else name.lower(), name)
192
+ if compact and name == "Claude Code":
193
+ label = "CC"
194
+ elif compact:
195
+ label = name[:8]
190
196
  if i == current:
191
- line.append(f" {name} ", style="bold reverse")
197
+ line.append(f" {label} ", style="bold reverse")
192
198
  else:
193
- line.append(f" {name} ", style=_S.dim)
194
- line.append(" ← → 切换 q / ESC 退出", style=_S.dim)
199
+ line.append(f" {label} ", style=_S.dim)
200
+ help_text = " ←→ jk q/ESC退出" if compact else " ← → 切换 ↑ ↓ 滚动 q / ESC 退出"
201
+ line.append(help_text, style=_S.dim)
195
202
  console.print(line)
196
203
 
197
204
 
@@ -200,9 +207,11 @@ def _project_short(project: str) -> str:
200
207
 
201
208
 
202
209
  def _render_header(agents: list[str], total_tokens: int, total_cost: float,
203
- total_sessions: int, total_messages: int, days: int) -> None:
210
+ total_sessions: int, total_messages: int, days: int,
211
+ top_margin: bool = True) -> None:
204
212
  agent_text = " ".join(f"[{_S.good}]●[/{_S.good}] {a}" for a in agents)
205
- console.print()
213
+ if top_margin:
214
+ console.print()
206
215
  console.print(Panel(
207
216
  f"[bold]Token Tracker[/bold] {agent_text}",
208
217
  border_style="blue",
@@ -262,6 +271,8 @@ def render_dashboard(
262
271
  rate_limits: RateLimits | None = None,
263
272
  p90: P90Limits | None = None,
264
273
  agents: list[str] | None = None,
274
+ session_limit: int = 10,
275
+ top_margin: bool = True,
265
276
  ) -> None:
266
277
  if not daily_stats:
267
278
  console.print(f"[{_S.warn}]暂无数据[/{_S.warn}]")
@@ -272,7 +283,15 @@ def render_dashboard(
272
283
  total_msgs = sum(s.message_count for s in daily_stats)
273
284
  total_sessions = sum(s.session_count for s in daily_stats)
274
285
 
275
- _render_header(agents or ["Claude Code"], total_tokens, total_cost, total_sessions, total_msgs, len(daily_stats))
286
+ _render_header(
287
+ agents or ["Claude Code"],
288
+ total_tokens,
289
+ total_cost,
290
+ total_sessions,
291
+ total_msgs,
292
+ len(daily_stats),
293
+ top_margin=top_margin,
294
+ )
276
295
 
277
296
  # --- 本月概览 ---
278
297
  if monthly_stats:
@@ -300,8 +319,8 @@ def render_dashboard(
300
319
  _render_idle_panel(rate_limits, cur_week, last_week)
301
320
 
302
321
  # --- 最近十条会话 ---
303
- if sessions:
304
- _render_recent_sessions(sessions[:10])
322
+ if sessions and session_limit > 0:
323
+ _render_recent_sessions(sessions[:session_limit])
305
324
 
306
325
  console.print()
307
326
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: token-tracker
3
- Version: 0.2.3
3
+ Version: 0.2.4
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