Files
claudeplus/hooks/session-end-log/session-end-log.py
Zhang Jiahao 23c7037b57 Add session-end-log hook with per-session token tally
既有 Stop 钩子 token-stats-hook 记的是跨会话累计快照, 无法区分"这次会话
用了多少"; session-end-log 在 SessionEnd 时按 session_id 解析本次 transcript
做单会话统计, 产出一行可回溯的索引写入 ~/.claude/log.md, 方便事后盘点
哪场会话烧了哪些 token。Python 写零依赖, Ubuntu 自带 python3 免构建。
2026-04-21 00:58:00 +08:00

76 lines
2.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""SessionEnd hook: append one metadata + token-usage line to ~/.claude/log.md."""
import json, sys, pathlib
from datetime import datetime
try:
data = json.load(sys.stdin)
except Exception:
data = {}
ts = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
cwd = data.get("cwd", "?")
reason = data.get("reason", "?")
sid = data.get("session_id") or "?"
sid_short = sid[:8]
def find_transcript():
tp = data.get("transcript_path")
if tp and pathlib.Path(tp).exists():
return pathlib.Path(tp)
if cwd == "?" or sid == "?":
return None
slug = cwd.replace("/", "-")
candidate = pathlib.Path.home() / ".claude" / "projects" / slug / f"{sid}.jsonl"
return candidate if candidate.exists() else None
def sum_tokens(path):
totals = {"input": 0, "output": 0, "cache_read": 0, "cache_write": 0}
try:
with path.open() as f:
for line in f:
try:
rec = json.loads(line)
except Exception:
continue
if rec.get("type") != "assistant":
continue
u = rec.get("message", {}).get("usage") or {}
totals["input"] += u.get("input_tokens", 0)
totals["output"] += u.get("output_tokens", 0)
totals["cache_read"] += u.get("cache_read_input_tokens", 0)
totals["cache_write"] += u.get("cache_creation_input_tokens", 0)
except Exception:
pass
return totals
def fmt(n):
if n >= 1_000_000:
return f"{n/1_000_000:.1f}M"
if n >= 1_000:
return f"{n/1_000:.1f}k"
return str(n)
tpath = find_transcript()
if tpath:
t = sum_tokens(tpath)
total = sum(t.values())
tok_str = (
f"tokens=total:{fmt(total)} "
f"in:{fmt(t['input'])} out:{fmt(t['output'])} "
f"cache_r:{fmt(t['cache_read'])} cache_w:{fmt(t['cache_write'])}"
)
else:
tok_str = "tokens=unavailable"
line = f"- {ts} | session={sid_short} | cwd={cwd} | reason={reason} | {tok_str}\n"
log = pathlib.Path.home() / ".claude" / "log.md"
if not log.exists():
log.write_text("# Session Log\n\n")
with log.open("a") as f:
f.write(line)