Zed Hours Tracker
A lightweight, local-first, background time tracker for Zed editor sessions, with automatic git commit logging. No cloud, no accounts, no manual timers.
Works on Arch Linux + Wayland and is designed to be portable to other projects.
What it does
- Detects when you are working in Zed on this project by monitoring the active window title (Wayland) and filesystem activity.
- Writes heartbeats to
.zed-hours/heartbeats.jsonl:- timestamp
- project path
- focused file (if detectable)
- idle status
- Logs git commits automatically via
post-commithook to.zed-hours/commits.jsonl. - Generates markdown reports combining heartbeats + commits into daily summaries.
Files
| File | Purpose |
|---|---|
install.py |
Install git hook and create .zed-hours/ data directory |
log_commit.py |
Called by git hook; do not run directly usually |
zed_tracker.py |
Background tracker for Zed activity |
generate_report.py |
Generate a markdown hours report |
sync.py |
Opt-in incremental sync of local logs to a Zed Hours server |
README.md |
This file |
Installation
Run from the project root:
python3 tools/time_tracking/install.py
This will:
- create
.zed-hours/in the project root; - install a
post-commitgit hook that logs commits; - create a default config file.
Running the tracker
The tracker is intentionally a simple script you run in the background.
Option A: One-off test
python3 tools/time_tracking/zed_tracker.py --project-root $(pwd)
Press Ctrl+C to stop.
Option B: Zed tasks (run from the editor)
This project includes Zed tasks so you can trigger tracker actions with the task runner shortcut (alt+shift+t or ctrl+shift+t depending on your keymap). The default working directory for Zed tasks is the project root, so the commands use relative paths.
| Task | Action |
|---|---|
Zed Hours: Install tracker |
Install git hook, config, and systemd service |
Zed Hours: Start tracker (foreground) |
Start the tracker in a Zed terminal panel |
Zed Hours: Generate report (today) |
Generate today's report |
Zed Hours: Generate report (yesterday) |
Generate yesterday's report |
Zed Hours: Generate report (last 14 days) |
Generate a 14-day report |
Zed Hours: Open latest report |
Print path to latest report |
Zed Hours: Status |
Show heartbeat/commit/report counts |
Option C: Background via systemd user service (recommended for daily use)
A sample service file is generated by install.py at:
~/.config/systemd/user/zed-hours-gem360.service
Enable it:
systemctl --user daemon-reload
systemctl --user enable --now zed-hours-gem360.service
systemctl --user status zed-hours-gem360.service
Logs:
journalctl --user -u zed-hours-gem360.service -f
To stop:
systemctl --user stop zed-hours-gem360.service
Option C: tmux / screen
tmux new -s zed-hours -d 'python3 tools/time_tracking/zed_tracker.py --project-root $(pwd)'
Automatic sync (systemd user timer)
sync_all.py syncs every configured project in one run. Configuration lives
in ~/.config/zed-hours-sync/:
| File | Purpose |
|---|---|
env |
ZED_HOURS_SERVER_URL, ZED_HOURS_SERVER_TOKEN, ZED_HOURS_DEVICE_ID (keep mode 600) |
projects.txt |
One project per line: PATH [SLUG]; slug defaults to the directory name |
A systemd user pair drives it:
zed-hours-sync.service(oneshot; runssync_all.pywith the env file)zed-hours-sync.timer(every 15 minutes,Persistent=trueto catch up after sleep)
Useful commands:
systemctl --user start zed-hours-sync.service # run a sync now
systemctl --user list-timers zed-hours-sync.timer # next scheduled run
journalctl --user -u zed-hours-sync.service -f # live logs
Generating a report
python3 tools/time_tracking/generate_report.py --from 2026-08-03 --to yesterday
Output is written to .zed-hours/reports/YYYY-MM-DD_to_YYYY-MM-DD.md.
You can also run:
python3 tools/time_tracking/generate_report.py --today
Optional server sync
The tracker remains local-first. To opt in to sending the locally recorded heartbeats and commits to a compatible server, set these environment variables:
export ZED_HOURS_SERVER_URL="https://hours.example.com"
export ZED_HOURS_SERVER_TOKEN="your-bearer-token"
export ZED_HOURS_DEVICE_ID="laptop-1"
Then run from the tracked project root:
python3 tools/time_tracking/sync.py
The command posts heartbeats to /api/v1/sync/zed-heartbeats and commits to /api/v1/sync/zed-commits. It sends device_id, project_slug, and records in batches. The slug defaults to the project root directory name and can be overridden without exposing the absolute local project path:
python3 tools/time_tracking/sync.py \
--project-root /path/to/project \
--project-slug customer-portal
Progress is recorded locally in .zed-hours/sync-state.json. Retrying after a failed request is safe: each record has a deterministic, device-specific source_id, allowing the server to treat duplicate submissions as idempotent.
How activity detection works on Wayland
Wayland does not expose a global active-window API for security reasons. The tracker tries several methods, in order:
- Hyprland:
hyprctl activewindow - Sway:
swaymsg -t get_tree - GNOME / Mutter: D-Bus
GetActiveWindowviagdbus - KDE / KWin: D-Bus via
qdbus - Fallback: monitor filesystem activity inside the project root using
inotifyor polling recent file access/modification times.
If all window-detection methods fail, the fallback still gives you a reliable signal that someone is actively editing files in this project.
Reusing in another project
Copy the tools/time_tracking/ directory to the new project and run:
python3 tools/time_tracking/install.py
The scripts detect the project root automatically from the current working directory.
Data format
.zed-hours/heartbeats.jsonl
{"timestamp": "2026-08-26T09:15:00", "project": "/home/fegger/Code/ixsol/gem360-git", "file": "README.md", "active": true, "source": "hyprctl"}
.zed-hours/commits.jsonl
{"type": "commit", "timestamp": "2026-08-26T09:20:00", "repo": "/home/fegger/Code/ixsol/gem360-git", "branch": "va_module", "sha": "abc12345", "message": "[ADD] feature X", "author": "Florian Egger"}
.zed-hours/config.json
{
"project_root": "/home/fegger/Code/ixsol/gem360-git",
"heartbeat_interval_seconds": 60,
"idle_threshold_seconds": 300,
"log_dir": ".zed-hours"
}
Notes
- This tool is intentionally simple and local. It is not a replacement for a commercial time tracker, but it is a reliable fallback when you forget to log hours.
- The tracker does not send data anywhere unless you explicitly run
sync.pywithZED_HOURS_SERVER_URL,ZED_HOURS_SERVER_TOKEN, andZED_HOURS_DEVICE_IDconfigured. - Heartbeat granularity is one minute by default. Tune it in
config.json. - Because of Wayland security, window detection may not give the exact filename. The filesystem fallback compensates for this.