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:
+129
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Install the Zed Hours Tracker git hook and config for the current project."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_project_root() -> Path:
|
||||
"""Return the git project root, falling back to the script's project directory."""
|
||||
try:
|
||||
root = subprocess.check_output(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
text=True,
|
||||
stderr=subprocess.DEVNULL,
|
||||
).strip()
|
||||
return Path(root).resolve()
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
pass
|
||||
# Fallback 1: directory containing this script's grandparent (<project>/tools/time_tracking/install.py)
|
||||
script_project = Path(__file__).resolve().parents[2]
|
||||
if (script_project / ".git").exists() or (script_project / ".zed-hours").exists():
|
||||
return script_project
|
||||
# Fallback 2: current working directory
|
||||
return Path.cwd().resolve()
|
||||
|
||||
|
||||
def ensure_log_dir(project_root: Path) -> Path:
|
||||
log_dir = project_root / ".zed-hours"
|
||||
log_dir.mkdir(parents=True, exist_ok=True)
|
||||
return log_dir
|
||||
|
||||
|
||||
def write_config(log_dir: Path, project_root: Path) -> Path:
|
||||
config = {
|
||||
"project_root": str(project_root),
|
||||
"log_dir": str(log_dir.relative_to(project_root)),
|
||||
"heartbeat_interval_seconds": 60,
|
||||
"idle_threshold_seconds": 300,
|
||||
"max_inactive_gap_seconds": 600,
|
||||
}
|
||||
config_path = log_dir / "config.json"
|
||||
if not config_path.exists():
|
||||
config_path.write_text(json.dumps(config, indent=2) + "\n", encoding="utf-8")
|
||||
print(f"Created config: {config_path}")
|
||||
else:
|
||||
print(f"Config already exists: {config_path}")
|
||||
return config_path
|
||||
|
||||
|
||||
def install_git_hook(project_root: Path) -> None:
|
||||
hook_path = project_root / ".git" / "hooks" / "post-commit"
|
||||
if not hook_path.parent.exists():
|
||||
print("No .git directory found; skipping git hook install.")
|
||||
return
|
||||
|
||||
script_dir = project_root / "tools" / "time_tracking"
|
||||
log_commit_script = script_dir / "log_commit.py"
|
||||
|
||||
hook_content = f"""#!/bin/sh
|
||||
# Zed Hours Tracker - post-commit hook
|
||||
python3 "{log_commit_script}" "$@"
|
||||
"""
|
||||
if hook_path.exists():
|
||||
existing = hook_path.read_text(encoding="utf-8")
|
||||
if "Zed Hours Tracker" in existing:
|
||||
print(f"Hook already installed: {hook_path}")
|
||||
return
|
||||
# Append to existing hook
|
||||
hook_path.write_text(existing.rstrip("\n") + "\n\n" + hook_content, encoding="utf-8")
|
||||
else:
|
||||
hook_path.write_text(hook_content, encoding="utf-8")
|
||||
|
||||
# Make executable
|
||||
hook_path.chmod(hook_path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
||||
print(f"Installed git hook: {hook_path}")
|
||||
|
||||
|
||||
def write_systemd_service(project_root: Path, script_dir: Path) -> None:
|
||||
"""Write a sample systemd user service file for background tracking."""
|
||||
systemd_dir = Path.home() / ".config" / "systemd" / "user"
|
||||
service_name = f"zed-hours-{project_root.name}.service"
|
||||
service_path = systemd_dir / service_name
|
||||
|
||||
tracker_script = script_dir / "zed_tracker.py"
|
||||
|
||||
service_content = f"""[Unit]
|
||||
Description=Zed Hours Tracker for {project_root.name}
|
||||
After=graphical-session.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
WorkingDirectory={project_root}
|
||||
ExecStart=/usr/bin/python3 {tracker_script} --project-root {project_root}
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
Environment="PYTHONUNBUFFERED=1"
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
"""
|
||||
systemd_dir.mkdir(parents=True, exist_ok=True)
|
||||
service_path.write_text(service_content, encoding="utf-8")
|
||||
print(f"Wrote systemd user service: {service_path}")
|
||||
print(f"Enable with: systemctl --user daemon-reload && systemctl --user enable --now {service_name}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
project_root = get_project_root()
|
||||
script_dir = Path(__file__).resolve().parent
|
||||
|
||||
log_dir = ensure_log_dir(project_root)
|
||||
write_config(log_dir, project_root)
|
||||
install_git_hook(project_root)
|
||||
write_systemd_service(project_root, script_dir)
|
||||
|
||||
print(f"\nZed Hours Tracker installed for project: {project_root}")
|
||||
print(f"Data directory: {log_dir}")
|
||||
print("\nNext steps:")
|
||||
print(" 1. Start the tracker: python3 tools/time_tracking/zed_tracker.py")
|
||||
print(" 2. Or enable the systemd service shown above.")
|
||||
print(" 3. Make a commit to test the git hook.")
|
||||
print(" 4. Generate a report: python3 tools/time_tracking/generate_report.py --today")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user