A lightweight desktop popup that shows up twice a day and asks what you worked on. Entries go into a local CSV you can open in Excel or Google Sheets.
Two scheduled triggers fire on weekdays: one at 10am and one at 3:30pm (cron on Linux/WSL, Task Scheduler on Windows). Each one opens a small tkinter window with a prompt and a row-based input table (project, description, hours). The afternoon window also shows what you logged earlier that day, and those earlier rows are editable in place (blank a row out entirely to delete it).
Clicking "Log it" validates the hours fields (a typo like 2h flags the field red instead of landing in the CSV) and appends your entries to worklog.csv. Clicking "Skip" closes the window and writes nothing. "Snooze 30m" hides the popup and brings it back half an hour later, for the triggers that land mid-meeting. Closing the window with the title-bar X asks before discarding anything you typed. Keyboard-wise: Enter logs, Escape closes (with the same discard check), and Tab out of the last hours field starts a fresh row.
A lock file prevents duplicate windows if a scheduled trigger fires while one is already open, and every scheduled run writes a line to tracker.log next to tracker.py (popup shown, skipped because one was already open, or crashed with the traceback), so a popup that never appeared is diagnosable instead of a mystery.
We run the app as a plain script under Python, not as a bundled .exe. An unsigned, self-scheduling binary trips Windows Defender's high-severity "persistence" detection, so a signed, sanctioned interpreter running our own script is the safer setup on a managed machine. See docs/windows-guide.md for the full walkthrough.
-
Install Python (the Microsoft Store or a company software portal both work, no admin needed).
-
Put
tracker.pyandscheduler.pyin a folder you own, for exampleC:\Users\you\WorkDayTracker. The log, prefs, and assets all live next totracker.py. -
Optional: create an
assetssubfolder and droptsheet_categories.csvin it to enable the project autocomplete. -
Open a terminal in that folder and run:
python tracker.py --installThis creates two per-user scheduled tasks (no admin prompt). They launch
pythonw.exeagainsttracker.py, so no console window flashes when a popup fires. The tasks are registered with missed-run catch-up (StartWhenAvailable) and are allowed to start on battery, so a 10:00 that passes while the laptop is asleep, locked before login, or undocked fires as soon as it can instead of being silently dropped. Those two settings are the fix for popups that "randomly" fail to appear; theschtasksdefaults skip both cases without logging anything.
To remove the schedule later, run python tracker.py --uninstall and delete the folder.
python3 tracker.py --installThis writes two cron entries for weekdays at the times in prefs.json, with the session's DISPLAY/WAYLAND_DISPLAY baked into each line (cron strips the GUI environment, so without this the popup has nowhere to draw and dies silently). Installing from inside a virtualenv is safe: the cron line uses the system python3, not the venv interpreter.
Note: on WSL the cron popups only fire while WSL itself is running, since WSL2 shuts down when its last shell closes. Keep a terminal open across the check-in times, or use the native Windows setup above, which survives sleep and reboots. WSL remains fine for development (python3 tracker.py --now works in a desktop session).
Test it
python3 tracker.py --nowThe --now flag skips the lock file check and opens the window immediately.
Project list
Put a CSV in assets/tsheet_categories.csv. The app reads project names from the first column named category (case-insensitive), or falls back to the first column if no such header exists. The names show up in an autocomplete dropdown on the project field, and the dropdown filters live as you type.
Preferences (prefs.json, or the Settings button in the popup)
| Key | Default | What it does |
|---|---|---|
morning_time |
"10:00" |
Morning check-in trigger (24h HH:MM) |
afternoon_time |
"15:30" |
Afternoon check-in trigger, also the cutover for the greeting |
chime |
true |
Play assets/fairy_dust.wav when the popup opens (toggle in Settings) |
daily_target_hours |
8 |
Target shown next to the running total, e.g. 6.5h of 8h |
Entries are written to worklog.csv in the same directory. It is created automatically on first submit if it does not exist yet.
| Column | Format | Notes |
|---|---|---|
date |
YYYY-MM-DD |
Always today's date at submit time |
project |
string | From the project dropdown or typed in |
description |
string | Free text |
hours |
decimal | Can be blank |
logged_at |
YYYY-MM-DD HH:MM:SS |
Exact timestamp when "Log it" was clicked |
Rows with all three fields blank are skipped. Partial rows (at least one field filled) are written with empty cells for the missing fields.
work_day_tracker/
tracker.py # app logic and UI
scheduler.py # cron (Linux) / Task Scheduler (Windows) integration
worklog.csv # your log (auto-created, not committed)
prefs.json # popup times and options (auto-created, not committed)
tracker.log # what each scheduled run did (auto-created, not committed)
assets/
tsheet_categories.csv # project name list (optional)
fairy_dust.wav # popup chime (optional, toggle in Settings)