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:
+101
@@ -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()
|
||||
Reference in New Issue
Block a user