Skip to content

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 timesheet autostart. With no chooser available at all, reminders fall back to closing the open session with a STOP at the next interval (one STOP, not one per interval) and timesheet start defaults to misc/unspecified instead of prompting.

    timesheet install also adds a per-user application-menu entry (~/.local/share/applications/timesheet.desktop) named "Timesheet" that runs timesheet start, so starting work is a point-and-click action from the Kubuntu/KDE launcher (or GNOME Activities, or any XDG menu) — the analog of the Windows Start Menu shortcut. The icon is installed into the hicolor theme as ~/.local/share/icons/hicolor/scalable/apps/timesheet.svg and referenced by name, so it survives the binary moving. timesheet uninstall removes both.

  • Windows: no extra dependencies to build or run; PowerShell (built in) drives the reminder chooser and shortcuts. The log lives at %USERPROFILE%\Documents\timesheet.log. timesheet start with no activity shows a full-screen chooser (PowerShell/WinForms) to pick or enter an activity, same as macOS/Linux. The reminder daemon runs in the background and re-prompts at the configured interval; timesheet stop/timesheet interval stop it (and any open chooser window) the same way as elsewhere. timesheet autostart registers a Startup-folder shortcut to run timesheet start at login (the per-user, no-admin-required analog of a macOS LaunchAgent or Linux systemd user unit); STOP at logoff/shutdown is recorded by the daemon's console control handler. Unlike macOS (LogoutHook) and Linux (systemd ExecStop plus a system-level logout-hook unit), there is no second, more-guaranteed mechanism available without admin rights, so this one path is best-effort — Windows does not guarantee it waits for the handler to finish the way launchd/systemd do. timesheet edit falls back to notepad (instead of vi) when $EDITOR/$VISUAL are unset. timesheet help has no groff/less to page through, so it renders the man page as plain text through more instead. timesheet install installs the binary as timesheet.exe (see Install) and creates a per-user Start Menu shortcut ("Start Timesheet") that runs timesheet.exe start, so starting work is also a point-and-click action.

  • timesheet pdf and timesheet email: PDF filling and message building are compiled in, so there is nothing to install for timesheet pdf. timesheet email hands the finished message to a sendmail(8)-compatible binary — /usr/sbin/sendmail on Linux and macOS, sendmail.exe found on PATH on Windows, unless sendmail: names another — so delivery needs a local MTA (Postfix, ssmtp, msmtp) or a wrapper script, configured there rather than here. See Supplying sendmail.exe on Windows.

Data format

The log file contains one entry per line. The timestamp is the first field, in strict ISO 8601 (RFC 3339) with microsecond precision and a local UTC offset:

  • ISO8601_timestamp|START|activity
  • ISO8601_timestamp|STOP

For example:

2026-08-03T08:00:00.000000-04:00|START|ST:Welcome session
2026-08-03T09:00:00.000000-04:00|STOP

The wall-clock time in the recorded offset is read back as local time without converting through UTC, so a log stays readable after a timezone change.

Start/stop pairs are matched in LIFO order (each STOP pairs with the most recent START). A START also closes any session still open before it, so consecutive STARTs each contribute their own interval. The report uses these pairs to compute duration and attribute time to activity and day of week.

Earlier versions wrote the kind first, as START|ISO8601_timestamp|activity and STOP|ISO8601_timestamp. timesheet migrate converts every timesheet.* file in the log directory to the current field order; lines already in it are left alone.

Configuration

Optional settings live in ~/.config/timesheet.yml (or $XDG_CONFIG_HOME/timesheet.yml; $TS_CONFIG overrides both, and a timesheet.yaml sibling is used if no .yml exists). The file does not exist by default, and every setting except the pdf/email template has a default, so no configuration is needed to track time.

Only a small YAML subset is understood: key: value pairs, # comments, optional quotes, indented nesting, and sequences (either - item lines or [a, b]). Unknown keys are ignored, and a value that can't be understood prints a warning on stderr and falls back to the default. Quote a value whose leading or trailing spaces matter, such as separator: "; ".

rotate — when a new timesheet week begins

timesheet rotates timesheet.log to timesheet.YYMMDD at the start of each week, so each rotated file holds exactly one work week. By default the week begins Sunday at 00:00 local time. If your employer's week runs Monday through Sunday — rotating at midnight between Sunday night and Monday morning — say so:

# ~/.config/timesheet.yml
rotate:
  day: monday
  time: "00:00"
  • day — weekday name or three-letter abbreviation, any case (monday, Mon, SUNDAY). Default: sunday.
  • timeHH:MM, HH:MM:SS, a bare hour, or a 12-hour time with a meridiem (5pm, 5:30 PM), in local time. Default: 00:00.

A scalar shorthand works too: rotate: monday, or rotate: "fri 17:00" for a week that turns over Friday at 5 pm.

The rotation boundary is checked by timesheet start, timesheet stop, timesheet started, timesheet timeoff and the reminder daemon: if the log's last entry falls before the most recent boundary, the log is rotated before the new entry is recorded. timesheet rotate run by hand always rotates, whatever the boundary. The same boundary defines "this week" for timesheet alias, and the week that timesheet pdf and timesheet email report.

Settings for timesheet pdf and timesheet email

Each of these may be written at the top level, or under prefixes:PREFIX so that it applies only when that prefix is in use. A per-prefix value beats the top-level one, and a command-line option beats both — which is how one log can serve several jobs, each tagging its activities (ST:Setup Jira) and keeping its own name, template, addresses and field map.

Setting Meaning
name Full name as it should appear on the timesheet. Required.
prefix Default for --prefix. When absent and exactly one prefix is listed under prefixes:, that one is used.
template Default for --template: path to the form-fillable PDF. No built-in default.
output Default for --output. When absent, timesheet pdf writes to stdout.
activity, separator, zero Defaults for --activity, --separator and --zero.
to, cc Default recipients; each is either one address or a sequence of them.
from, reply Default sender and Reply-To addresses.
subject, body Message templates, taking the same placeholders as output plus {total_hours}.
min_font_size, max_font_size Shrink-to-fit range in points (default 5 and 10).
fields Maps each timesheet slot to a form-field name. Defaults suit the stock form; anything listed here replaces only the slots it names.
sendmail Path to the sendmail(8)-compatible binary the message is piped to. Default /usr/sbin/sendmail, or sendmail.exe resolved against PATH on Windows. A leading ~/ is expanded.

The slots that fields maps are contractor_name, week_start_month/_day/_year, week_end_month/_day/_year, <weekday>_hours and <weekday>_activities for each of the seven weekdays, and total_hours. The field names of a different form can be listed with mutool show form.pdf form | grep Name:.

# ~/.config/timesheet.yml
name: "Jane Contractor"
from: "jane@example.com"
prefixes:
  ST:
    template: "~/Documents/timesheet-fillable.pdf"
    output: "timesheet_Jane_{week_start}-{week_end}.pdf"
    separator: "; "
    zero: ""
    reply: "jane@employer.example"
    to: "timesheets@employer.example"

timesheet email does not speak SMTP itself. It runs <binary> -i -f <from> <recipient>..., writes the message to its standard input, and repeats whatever the binary prints to standard error — as a warning when it succeeds, as the error when it does not. Relay, credentials and queueing therefore live in the MTA's own configuration, not here.

sendmail is scoped like every other setting, so one job can be sent by a wrapper that mails through a different account while the rest use the system binary:

prefixes:
  ST:
    sendmail: "~/.local/bin/sendmail-employer"

Anything that reads a message on stdin and takes sendmail's arguments will do. A wrapper that sends through an account other than the one in from will usually rewrite the From header, so set reply to the address you actually read.

Supplying sendmail.exe on Windows

Windows ships no MTA and has no conventional path for one, so the default there is the bare name sendmail.exe, resolved against PATH. msmtp supplies a working one: it is an SMTP client with a sendmail-compatible interface, so it takes -f and the recipients as operands, accepts and ignores -i, and returns sendmail's exit codes — which is exactly what timesheet email drives. Its own TLS and its own account configuration (msmtprc) replace the relay settings this program used to carry. Two ways to get it:

  • Portable build — unzip msmtp_windows_portable_<version>.zip from SourceForge; it is a 64-bit build with no installer.
  • MSYS2pacman -S mingw-w64-x86_64-msmtp, which puts msmtp.exe in the mingw64 bin directory.

Then either copy msmtp.exe to sendmail.exe somewhere on PATH, or skip the renaming and point the setting at it:

sendmail: "C:/msys64/mingw64/bin/msmtp.exe"

Configure the relay and credentials in msmtp's own msmtprc (~/.msmtprc; see its manual for where it looks on Windows), not here.

XAMPP's bundled "fake sendmail" (sendmail.exe by Byron Jones, and the sendmail-tls1-2 fork) is the other binary of that name in circulation. It is documented as emulating sendmail's -t option — recipients taken from the headers — whereas timesheet email passes the envelope as operands, so check that it actually delivers to everyone before relying on it.

A .cmd wrapper also works if you would rather delegate: sendmail: can name a batch file that calls wsl.exe -e /usr/sbin/sendmail "$@" or a PowerShell script.

Filling and sending the timesheet

timesheet pdf aggregates one week and fills a form-fillable PDF with it; timesheet email does the same and mails the result as an attachment.

timesheet pdf > timesheet.pdf          # the week just worked, to stdout
timesheet pdf -o ~/Documents           # into a directory, using the configured file name
timesheet pdf -1                       # the most recently rotated week
timesheet pdf 260727                   # the week containing 2026-07-27
timesheet email                        # fill and send in one step

The optional week argument takes the same forms as timesheet list: a log file path, log for the current log, a negative rotated-log index (-1 is the most recently rotated), or a date (YYYYMMDD, YYMMDD, M/D). With no argument, the week in progress is reported on its final day and the most recently completed week on any other day — so a run late on the last day of the week, or at any time in the days after it, both report the week just worked.

Hours are credited to the day each session started on, exactly as timesheet list accounts for them, and the printed total is the sum of the day figures as rounded, so the column adds up on paper. Every log is read, so a week that straddles a rotation still reports in full.

--prefix ST reports only activities beginning ST: and strips that tag, so an entry logged as ST:Setup Jira is reported as Setup Jira. Entries without the tag belong to another job and are left out entirely — their hours as well as their descriptions. An empty prefix (-p "") reports every entry unchanged, while still reading the settings of the prefix the configuration would otherwise have selected — so the template and addresses need not be restated.

timesheet list takes -p/--prefix too, filtering and stripping the same way, so timesheet list -p ST previews on screen the hours timesheet pdf -p ST will report. It reads no configuration, so the option is the only thing that filters: with no --prefix, timesheet list reports every activity as it always has, and an empty -p "" does the same.

--output accepts {date}, {week_start}, {week_end}, {name} and {prefix}, an existing directory (which receives the configured file name), or - for stdout. Writing a PDF to a terminal is refused.

Text is shrunk to fit its cell and, in the activity columns, wrapped; a description that cannot fit even at min_font_size warns on stderr and is clipped. Appearance streams are generated rather than left to the viewer, so the filled text shows up in every reader, when printed, and to text extractors.

If a send fails, the finished PDF is kept on disk rather than discarded, so the message can be retried without rebuilding a week that may have moved on.

timesheet command

The timesheet command takes a required subcommand as its first argument. Full documentation: timesheet help or timesheet 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 timesheet start on login and timesheet stop on logout/shutdown (macOS: LaunchAgents + logout hook; Linux: systemd user units + a system-level logout hook; Windows: a Startup-folder shortcut, with STOP at logoff/shutdown handled by the daemon's best-effort console control handler — no admin-required second guarantee exists there). 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 timesheet autostart uninstall to remove.
edit Open the timesheet log ($HOME/Documents/timesheet.log) in your editor, taken from $EDITOR (then $VISUAL) — but only when run interactively (a real tty on both stdin and stdout). Otherwise (e.g. launched from a GUI menu entry, where those variables usually aren't set anyway) it opens via the OS default instead: the program associated with .txt files on Windows, open on macOS, xdg-open on Linux. vi is the last-resort fallback for an interactive terminal with neither variable set.
email Fill the timesheet PDF as pdf does and mail it as an attachment. Takes every pdf option, except that -t means --to here (the template is --template or -T), plus -c/--cc, -f/--from and -r/--reply. See Filling and sending the timesheet.
help Show the manual page in a pager (groff -man -Tascii | less; on Windows, rendered as plain text and paged through more).
install Copy the binary (and on macOS the embedded icon as ts-icon.svg) to a directory on PATH. Optional: timesheet install [install_dir] [repo_path]. Works without the source repo on macOS (icon is embedded). On Windows, installs as timesheet.exe (not ts.exe — see Install) and also creates a "Start Timesheet" Start Menu shortcut that runs timesheet.exe start. On Linux, creates a "Timesheet" application-menu entry that runs timesheet start. If the reminder daemon is running from the target binary (locking it open), stops it without logging a STOP, copies the new binary into place, then restarts the daemon from it — the open session carries through the reinstall untouched.
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. timesheet list 2/19, timesheet list 260220, timesheet list -1) to select a log. If work in progress, shows current task and duration. -p/--prefix PREFIX reports only one job's activities, as for pdf.
manpage Output the Unix manual page in groff format to stdout.
pdf Fill a form-fillable PDF template with one week of the timesheet and write it to a file or to stdout. Optional file/extension, date, or negative rotated-log index selects the week, exactly as for list. Options: -p/--prefix, -o/--output, -t/--template, -a/--activity, -s/--separator, -z/--zero. See Filling and sending the timesheet.
prefix Prepend <prefix>: to this week's activities matching a pattern. timesheet prefix foo bar is equivalent to timesheet alias bar foo:bar, prompting per match just like alias.
rebuild Build from source and install into the directory of the running binary. Optional directory argument; see timesheet help.
uninstall Stop the reminder daemon, remove autostart hooks, optionally remove timesheet log files, then remove ts-icon.svg and the timesheet 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. Happens automatically at the start of each week — see Configuration.
start Record work start now. With no activity: shows the reminder dialog to pick/enter an activity (macOS; Windows; Linux with kdialog/zenity installed); otherwise defaults to misc/unspecified. If a session is already open, a STOP is added only when that START is more than one reminder interval old (capped to one interval after it, leaving the time you were away unbilled) — otherwise the new START closes the previous session on its own, since pairs match in LIFO order. Starts the reminder daemon if not already running.
started Record a work start at a past time. Args: timesheet started <start_time> [activity...]. See Time formats.
stop Record work stop at now or at an optional stop time. If the last entry is already STOP and no time is given, the log is left alone; if a time is given, the last STOP is amended. If the last entry is START, appends the new STOP. Always stops the reminder daemon and closes any prompt it has on screen, even when the log needed no change, 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.

Time formats

timesheet started <start_time> and timesheet stop [stop_time] accept the same forms. A time with no date means today; a date with no time means midnight that day. Quote any argument containing a space.

Form Examples
ISO 8601 2026-08-06T07:00:00-04:00
Date and time "2026-08-06 07:00", "08/06/2026 7:00 PM", "8/6 7am"
24-hour time 07:00, 07:00:30, 7
12-hour time 7am, 7 AM, 7pm, 7:30pm, "12:15:30 p.m."
Date only 2026-08-06, 08/06/2026, 8/6

A bare hour is 24-hour (19 is 7 pm), so a bare 12 is noon while 12am is midnight. The meridiem is case-insensitive and may be written am/pm, a.m./p.m., or a/p. MM/DD without a year means the current year.

Reminder daemon

  • timesheet start starts the reminder daemon if it is not already running. With no activity, timesheet 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; Windows via PowerShell/WinForms). 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): the window covers the full screen and stays on top, with the choices in a centered panel. 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.
  • timesheet stop always stops the reminder daemon and shows a dialog that reminders have been stopped (skipped during logout/shutdown). This happens even when the log itself needs no new entry — after an unanswered reminder, which records its own STOP, timesheet stop would otherwise have nothing to write and would leave the daemon running to prompt again one interval later. A prompt that is still on screen is closed along with the daemon: on macOS/Linux the daemon runs in its own process group, so it is signalled as a group; on Windows the daemon and any chooser window it spawned are members of the same Job Object, terminated together the same way. A daemon that timesheet stop could not signal (a stray that no longer owns the PID file) notices the file is gone within half a second and exits instead of prompting again, closing its own prompt if one is up.
  • timesheet interval or timesheet restart [duration] sets or shows the interval and restarts the daemon.
  • Reminder behavior: If a reminder goes unanswered for one reminder interval, a STOP is recorded at the time the reminder appeared — not when the interval expired. That timestamp is used exactly, without the one-interval cap: the reminder appears one interval after your previous entry, so it already marks the last moment you were known to be working. The reminder is then left on screen rather than dismissed (macOS also brings it back to the front). When you get back to your desk and pick an activity, that START is recorded at your return time, so the time you were away sits between the STOP and the new START — unbilled — and your return is logged accurately. No second STOP is added while work is already stopped, so leaving the screen unattended records a single STOP rather than one per interval. The reminder window covers the full screen and stays on top (macOS, Linux, and Windows), so a mouse action in progress when it appears cannot accidentally hide it. 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 (capped to one interval after the latest entry); on Linux the systemd session unit’s ExecStop runs timesheet stop instead, and the daemon stays silent on SIGTERM (systemd may signal it during ordinary teardown, so a STOP there would be spurious); on Windows the daemon records STOP from a console control handler on CTRL_LOGOFF_EVENT/CTRL_SHUTDOWN_EVENT, best-effort since Windows does not guarantee it waits for the handler to finish.
  • Automatic STOP cap: Whenever a STOP is added automatically (a missed shutdown reconciled at the next timesheet start/timesheet autostart, closing the previous session before a new START, or timesheet 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 timesheet interval). So forgetting to stop never records work all night: the session ends at most one interval after your last logged activity. An unanswered reminder is the one exception, and needs no cap: its STOP is stamped at the moment the reminder appeared, which is already one interval after the previous entry.
  • timesheet autostart [interval] registers timesheet start at login and timesheet stop at logout/shutdown. On Linux the login unit (ts-autostart-start.service) is wanted by and ordered after graphical-session.target, so the chooser waits for the desktop session instead of racing it — a unit wanted by default.target starts seconds before the compositor exists, and the chooser then has no display to open on. 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. On macOS/Linux 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 timesheet stop before shutdown.target. Installing the hook needs administrator access, so timesheet 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. On Windows, login registration is a Startup-folder shortcut (no admin needed) and there is no second guarantee available without admin rights, so the daemon's console control handler is the only STOP-at-logoff mechanism there. timesheet autostart uninstall offers to remove it (via sudo on macOS/Linux).

Install

From the repository directory:

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

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

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

The binary uses $HOME/Documents/timesheet.log by default (%USERPROFILE%\Documents\timesheet.log on Windows, where timesheet install writes timesheet.exe — not ts.exe, since that name collides too easily with unrelated preinstalled tools — and no chmod step is needed).

Running the Windows build from WSL

If you invoke timesheet.exe from a WSL shell (e.g. a shell function that calls the interop path directly, since the per-user Windows install location isn't on WSL's PATH), WSL does not forward arbitrary environment variables across the interop boundary — only names listed in $WSLENV cross over. Without it, timesheet.exe never sees $EDITOR/$VISUAL, so timesheet edit falls back to the Windows .txt handler even when run from an interactive terminal. Add this to your shell profile:

export WSLENV="EDITOR/w:VISUAL/w${WSLENV:+:$WSLENV}"

The /w flag forwards the variable WSL-to-Windows only.

Build from source

Build with Rust installed:

cargo build --release

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

Building a Windows executable from WSL

To build a Windows executable from within Windows Subsystem for Linux (WSL), you need the MinGW compiler toolchain and the Rust Windows target:

  1. Install MinGW and dependencies:

    On Ubuntu/Debian-based WSL:

    sudo apt install mingw-w64 pkg-config

    On other distributions, install the equivalent MinGW package (e.g., mingw-w64 on Fedora/RHEL).

  2. Install the Windows Rust target (if not already installed):

    rustup target add x86_64-pc-windows-gnu
  3. Build for Windows:

    cargo build --release --target x86_64-pc-windows-gnu

    The Windows executable will be produced at target/x86_64-pc-windows-gnu/release/deps/timesheet-*.exe (where * is a hash).

  4. Access from Windows — the executable is accessible from Windows at a path like:

    \\wsl$\Ubuntu\home\<username>\src\github\pillarsdotnet\timesheet\target\x86_64-pc-windows-gnu\release\deps\timesheet-<hash>.exe
    

    Or copy it to a more convenient location:

    copy \\wsl$\Ubuntu\home\<username>\...\timesheet-<hash>.exe timesheet.exe
  5. Install on Windows — run the executable's install subcommand from PowerShell to self-install:

    From WSL, using PowerShell Core:

    pwsh -Command "& './target/x86_64-pc-windows-gnu/release/deps/timesheet-<hash>.exe' install"

    Or from Windows PowerShell after copying the binary:

    Copy-Item '\\wsl$\Ubuntu\home\<username>\...\timesheet-<hash>.exe' 'timesheet.exe'
    .\timesheet.exe install

    This installs the binary to %LOCALAPPDATA%\Programs\timesheet\timesheet.exe and creates a "Start Timesheet" shortcut in the Windows Start Menu.

This cross-compilation approach avoids the need to install a separate Rust/build environment on Windows itself — the toolchain remains in WSL while producing a native Windows binary.

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/timesheet/.

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

About

Simple rust program for maintaining a weekly timesheet

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages