Add Zed Hours Tracker tooling

Introduces a local-first time tracker for Zed editor sessions, including
background activity tracking, automatic git commit logging, and markdown
report generation. Includes install script, systemd service template,
and setup instructions in README.
This commit is contained in:
2026-09-11 10:27:56 +02:00
commit bfff093dc6
5 changed files with 943 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Log a git commit event to .zed-hours/commits.jsonl.
This script is normally called by the git post-commit hook.
It can also be run manually for testing.
"""
import json
import os
import subprocess
from datetime import datetime, timezone
from pathlib import Path
def get_project_root() -> Path:
try:
root = subprocess.check_output(
["git", "rev-parse", "--show-toplevel"],
cwd=os.environ.get("GIT_DIR", "."),
text=True,
stderr=subprocess.DEVNULL,
).strip()
return Path(root).resolve()
except (subprocess.CalledProcessError, FileNotFoundError):
pass
# Fallback 1: script location (<project>/tools/time_tracking/)
script_project = Path(__file__).resolve().parents[2]
if (script_project / ".git").exists() or (script_project / ".zed-hours").exists():
return script_project
# Fallback 2: current directory
return Path.cwd().resolve()
def get_last_commit_info(project_root: Path) -> dict:
try:
log_format = "%H|%an|%ae|%s|%D"
out = subprocess.check_output(
["git", "log", "-1", f"--format={log_format}"],
cwd=project_root,
text=True,
stderr=subprocess.DEVNULL,
).strip()
sha, author, email, subject, refs = out.split("|", 4)
# Try to extract current branch
branch = "unknown"
try:
branch = subprocess.check_output(
["git", "rev-parse", "--abbrev-ref", "HEAD"],
cwd=project_root,
text=True,
stderr=subprocess.DEVNULL,
).strip()
except subprocess.CalledProcessError:
pass
return {
"sha": sha,
"author": author,
"email": email,
"subject": subject,
"branch": branch,
}
except subprocess.CalledProcessError as e:
return {
"sha": "unknown",
"author": "unknown",
"email": "unknown",
"subject": f"error: {e}",
"branch": "unknown",
}
def log_commit(project_root: Path | None = None) -> Path | None:
if project_root is None:
project_root = get_project_root()
log_dir = project_root / ".zed-hours"
log_dir.mkdir(parents=True, exist_ok=True)
commit_info = get_last_commit_info(project_root)
record = {
"type": "commit",
"timestamp": datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds"),
"repo": str(project_root),
**commit_info,
}
log_file = log_dir / "commits.jsonl"
with log_file.open("a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
return log_file
def main() -> None:
log_file = log_commit()
if log_file:
print(f"Logged commit to {log_file}")
if __name__ == "__main__":
main()