Skip to content

pillarsdotnet/timesheet

Repository files navigation

Timesheet

Copyright (c) 2025 Robert August Vincent II pillarsdotnet@gmail.com Co-author: Cursor-AI and GitHub Copilot.

CLI for tracking work start/stop and reporting time by activity and by day of week.

Motivation

In the 90's, I had a boss who required me to turn in a detailed weekly timesheet listing exactly how much time I spent on each task, assigned or unassigned. As a borderline austistic, the idea of fudging or guessing at such a report was deeply troubling. So I self-assigned a task to write quick-and-dirty program that pops up every five minutes and asks what I've been doing. I called it "bugme".

My present position has similar reporting requirements, so I have recreated that old program with improvements. I took the opportunity to simultaneously scratch two itches: AI and the Rust Programming Language. So I used an AI agent almost exclusively to write the program code, both in its original form as a set of Korn Shell scripts, and in its current form as a Rust program.

One of these days, when I find the time, I'll read through the code and try to figure out how it works. For now I'm just glad that it does.

Requirements

  • Timesheet data file: ~/Documents/timesheet.log (edit DEFAULT_TIMESHEET in src/main.rs and rebuild to change)

  • macOS: no extra dependencies (reminder dialogs use built-in AppleScript/AppKit).

  • Linux (KDE/Ubuntu/etc.): the reminder prompt uses, in order of preference:

    1. A single-click chooser built with Python 3 + PyQt (python3 plus python3-pyqt6 or python3-pyqt5). This is the preferred experience: each entry acts on a single click with no OK/Cancel buttons (Qt, native on Wayland). Install e.g. sudo apt install python3-pyqt6.
    2. A fallback list dialog via kdialog (KDE/Plasma) or zenity (GNOME/other) when PyQt is unavailable — a select-then-OK list. Install whichever matches your desktop, e.g. sudo apt install kdialog or sudo apt install zenity.

    notify-send (from libnotify-bin) is used for the "reminders stopped" notification, and systemd --user for ts autostart. With no chooser available at all, reminders fall back to recording a STOP at each interval and ts start defaults to misc/unspecified instead of prompting.

Data format

The log file contains one entry per line:

  • START|unix_epoch|activity
  • STOP|unix_epoch

Start/stop pairs are matched in LIFO order (each STOP pairs with the most recent START). The report uses these pairs to compute duration and attribute time to activity and day of week.

ts command

The ts command takes a required subcommand as its first argument. Full documentation: ts help or ts manpage.

Subcommands (alphabetical):

Subcommand Description
alias Interactively replace activity text in START entries from the current week. Matches the search text literally first; if nothing matches and the search text is a valid regex, falls back to regex search-and-replace.
autostart Register ts start on login and ts stop on logout/shutdown (macOS: LaunchAgents + logout hook; Linux: systemd user units + a system-level logout hook). Optional first argument: interval (e.g. 5s, 3m) to set reminder interval and start the daemon in this session. Without interval: starts the daemon if needed and shows the current reminder interval. Use ts autostart uninstall to remove.
edit Open the timesheet log ($HOME/Documents/timesheet.log) in your editor, taken from $EDITOR (then $VISUAL, else vi).
help Show the manual page in a pager (groff -man -Tascii | less).
install Copy the binary (and on macOS the embedded icon as ts-icon.svg) to a directory on PATH. Optional: ts install [install_dir] [repo_path]. Works without the source repo on macOS (icon is embedded).
interval Set or show the reminder daemon interval (e.g. 3, 3m, 100s, 1h30m). With an argument, sets the interval and restarts the daemon.
list Plaintext report: % time per activity, hours per day of week; optional file/extension, date, or negative rotated-log index (e.g. ts list 2/19, ts list 260220, ts list -1) to select a log. If work in progress, shows current task and duration.
manpage Output the Unix manual page in groff format to stdout.
rebuild Build from source and install into the directory of the running binary. Optional directory argument; see ts help.
uninstall Stop the reminder daemon, remove autostart hooks, optionally remove timesheet log files, then remove ts-icon.svg and the ts binary from the install directory.
rename Same as alias.
reminder Alias for interval.
restart Alias for interval (with no argument, reports current interval and restarts the daemon).
rotate Rename timesheet.log to timesheet.YYMMDD using the earliest entry's date; if last entry is START, appends a STOP no later than one reminder interval after that entry first. If a file for that date already exists, appends to it.
start Record work start now. With no activity: shows the reminder dialog to pick/enter an activity (macOS, or Linux with kdialog/zenity installed); otherwise defaults to misc/unspecified. Starts the reminder daemon if not already running.
started Record a work start at a past time. Args: ts started <start_time> [activity...]. Time formats: e.g. YYYY-MM-DD HH:MM, HH:MM, or GNU date -d style.
stop Record work stop at now or at an optional stop time. If the last entry is already STOP and no time is given, nothing happens; if a time is given, the last STOP is amended. If the last entry is START, appends the new STOP. When a stop is recorded, stops the reminder daemon and shows a dialog that reminders have been stopped (skipped during logout/shutdown).
stopped Alias for stop.
tail Latest ten log entries with timestamps in local time; START lines show duration. Consecutive STARTs with the same activity are collapsed, then last 10 shown. Optional file/extension or date match to select a log.
timeoff Show the stop-work time for an 8 h/day average. Requires only a START entry (work in progress); no completed session on the current day is required. If the log is empty or the last entry is STOP, appends a START first.

Reminder daemon

  • ts start starts the reminder daemon if it is not already running. With no activity, ts start shows the reminder chooser immediately to pick/enter an activity (macOS via AppleScript/AppKit; Linux via the PyQt single-click chooser, falling back to kdialog/zenity). While this foreground chooser is open no daemon runs, so it cannot pop a second window; a fresh daemon starts once you pick. The daemon prompts “What are you working on?” at the configured interval.
  • Chooser (Linux, PyQt): a single click acts immediately — Stop Work records a STOP and stops reminders; an activity records a START for it and closes the window; Enter new activity… opens an input box where a non-empty entry (press Enter) records that activity and closes everything, while a blank entry returns you to the list.
  • ts stop (when it records a stop) stops the reminder daemon and shows a dialog that reminders have been stopped (skipped during logout/shutdown).
  • ts interval or ts restart [duration] sets or shows the interval and restarts the daemon.
  • Reminder behavior: If the reminder times out without a mouse click, a STOP is recorded at the time the reminder appeared, capped to no more than one reminder interval after the latest log entry, and the existing reminder window is brought to the front (no new prompt is launched). If the reminder or “Enter new activity” dialog is dismissed without choosing (e.g. closed, Escape), it re-shows immediately. The “Enter new activity” text dialog has no timeout. At logout/shutdown the open session is stopped: on macOS the daemon records STOP when launchd sends it SIGTERM (same one-interval cap); on Linux the systemd session unit’s ExecStop runs ts stop instead, and the daemon stays silent on SIGTERM (systemd may signal it during ordinary teardown, so a STOP there would be spurious).
  • Automatic STOP cap: Whenever a STOP is added automatically (a missed shutdown reconciled at the next ts start/ts autostart, closing the previous session before a new START, a reminder timeout, or ts rotate), its timestamp is capped to no more than one reminder interval after the latest log entry — the interval is how often you’re prompted (default 5 minutes; see ts interval). So forgetting to stop never records work all night: the session ends at most one interval after your last logged activity.
  • ts autostart [interval] (macOS/Linux) registers ts start at login and ts stop at logout/shutdown. An optional interval (e.g. 5s, 3m) sets the reminder interval and starts the daemon in this session so the reminder appears soon. Without interval: starts the daemon if needed and shows the current reminder interval. Startup skips a new START if the last log entry is a STOP less than 60 seconds old, and if startup finds a non-STOP event more than 5 minutes old it backfills a STOP one reminder interval after that event before recording the new START. It also installs a logout hook as a second guarantee that STOP is recorded at logout/shutdown: on macOS via com.apple.loginwindow LogoutHook, on Linux via a system-level systemd unit (ts-logout-<uid>.service) whose ExecStop runs ts stop before shutdown.target. Installing the hook needs administrator access, so ts autostart prints the sudo command and offers to run it; if you decline, run the printed command yourself. Once the hook is present, later runs skip it. ts autostart uninstall offers to remove it (also via sudo).

Install

From the repository directory:

cargo build --release && ./target/release/ts install

To install into a specific directory (e.g. ~/bin): ts install ~/bin. Or copy manually:

cp target/release/ts ~/bin/ts
chmod +x ~/bin/ts

The binary uses $HOME/Documents/timesheet.log by default.

Build from source

Build with Rust installed:

cargo build --release

The binary is produced at target/release/ts (or target/debug/ts for cargo build). See INSTALL.md for full instructions.

To set up the full toolchain (Rust components, git hooks) and run the checks, see CONTRIBUTING.md.

Commit messages

The CI lint workflow checks commit messages with commitlint (Conventional Commits). Use a leading type and optional scope, e.g. feat(macos): add dock icon or fix: record STOP on shutdown. See .commitlintrc.yaml and Conventional Commits.

Documentation

Rustdoc-compatible comments are in the Rust source. Generate and open the docs with:

cargo doc --no-deps --open

Output is under target/doc/ts/.

For command-line usage, run ts help or ts manpage.

About

Simple rust program for maintaining a weekly timesheet

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages