2026-09-14 13:14:12 +02:00
2026-09-11 10:27:56 +02:00
2026-09-11 10:27:56 +02:00
2026-09-14 13:14:12 +02:00
2026-09-14 13:14:12 +02:00
2026-09-11 10:27:56 +02:00

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

  1. Detects when you are working in Zed on this project by monitoring the active window title (Wayland) and filesystem activity.
  2. Writes heartbeats to .zed-hours/heartbeats.jsonl:
    • timestamp
    • project path
    • focused file (if detectable)
    • idle status
  3. Logs git commits automatically via post-commit hook to .zed-hours/commits.jsonl.
  4. 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-commit git 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

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)'

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:

  1. Hyprland: hyprctl activewindow
  2. Sway: swaymsg -t get_tree
  3. GNOME / Mutter: D-Bus GetActiveWindow via gdbus
  4. KDE / KWin: D-Bus via qdbus
  5. Fallback: monitor filesystem activity inside the project root using inotify or 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.py with ZED_HOURS_SERVER_URL, ZED_HOURS_SERVER_TOKEN, and ZED_HOURS_DEVICE_ID configured.
  • 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.
S
Description
No description provided
Readme 47 KiB
Languages
Python 100%