# 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: ```bash 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 ```bash 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: ```bash systemctl --user daemon-reload systemctl --user enable --now zed-hours-gem360.service systemctl --user status zed-hours-gem360.service ``` Logs: ```bash journalctl --user -u zed-hours-gem360.service -f ``` To stop: ```bash systemctl --user stop zed-hours-gem360.service ``` ### Option C: `tmux` / `screen` ```bash 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; runs `sync_all.py` with the env file) - `zed-hours-sync.timer` (every 15 minutes, `Persistent=true` to catch up after sleep) Useful commands: ```sh 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 ```bash 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: ```bash 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: ```bash 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: ```bash 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: ```bash 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: ```bash python3 tools/time_tracking/install.py ``` The scripts detect the project root automatically from the current working directory. --- ## Data format ### `.zed-hours/heartbeats.jsonl` ```json {"timestamp": "2026-08-26T09:15:00", "project": "/home/fegger/Code/ixsol/gem360-git", "file": "README.md", "active": true, "source": "hyprctl"} ``` ### `.zed-hours/commits.jsonl` ```json {"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` ```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.