Skip to main content

Stundu

codecov

Note: This project was previously published as billable on the Snap Store and GitLab. The name was too generic and heavily overused across the web, making the project hard to find. The old billable snap is deprecated — please install stundu instead.

A command-line time tracking application with built-in billing calculations. Track work entries with timestamps and tags, calculate durations, compute billing based on configurable hourly rates with support for afterhours and holiday/weekend modifiers, generate professional invoices, and import data from other time trackers.

Table of Contents

Installation

There are three ways to get Stundu depending on your use case:

Method Best for
Snap End users on Linux who want the CLI
pip Developers who want to use the core library
From source Contributing or hacking on the project

Snap

The easiest way to install the CLI on Linux:

sudo snap install stundu

Or visit snapcraft.io/stundu.

pip

Install the core library (no CLI dependencies):

pip install stundu

Install with the CLI included:

pip install stundu[cli]

Install with PDF invoice support:

pip install stundu[cli,pdf]

The core library exposes the billing engine, data models, storage backend, and importers/exporters. The stundu command requires the [cli] extra.

From Source

Prerequisites

  • Python 3.11 or higher

Setup

  1. Clone the repository and navigate to the project directory:
cd stundu
  1. Create a virtual environment:
python3 -m venv venv
  1. Activate the virtual environment:
# Linux/macOS
source venv/bin/activate

# Windows
venv\Scripts\activate
  1. Install dependencies:
pip install -r requirements.txt

Quick Start

python stundu.py start "Acme Corp" "Development"
Started tracking: Acme Corp, Development
Started at: 09:00
python stundu.py status
Currently tracking: Acme Corp, Development
Started at: 09:00
Elapsed: 2h 15m
python stundu.py stop
Stopped tracking: Acme Corp, Development
Duration: 3h 0m
python stundu.py log
@    Start    End      Duration    Tags                    Status
--   -------  -------  ----------  ----------------------  --------
@1   09:00    12:00    3h 0m       Acme Corp, Development
python stundu.py configure rate "Acme Corp" 50 EUR
python stundu.py report
Report for 'Acme Corp' (Jan 1, 2026 to Jan 31, 2026)
────────────────────────────────────────────────────────────
╭──────────────┬───────┬───────┬──────────┬─────────────┬────────────╮
│ Date         │ Start │ End   │ Duration │ Tags        │ Cost       │
├──────────────┼───────┼───────┼──────────┼─────────────┼────────────┤
│ Jan 15, 2026 │ 09:00 │ 12:00 │ 3h 0m    │ Development │ 150.00 EUR │
│              │ 14:00 │ 18:00 │ 4h 0m    │ Development │ 200.00 EUR │
│              │ 18:00 │ 20:00 │ 2h 0m    │ Development │ 150.00 EUR │
├──────────────┼───────┼───────┼──────────┼─────────────┼────────────┤
│ Jan 18, 2026 │ 10:00 │ 14:00 │ 4h 0m    │ Development │ 400.00 EUR │
╰──────────────┴───────┴───────┴──────────┴─────────────┴────────────╯

────────────────────────────────────────────────────────────
Total time: 13h 0m

Total:
  900.00 EUR

See docs/examples.md for more detailed examples.

Configuration

First Run

On first run, Stundu displays a welcome message and prompts you to run the setup wizard:

Welcome to Stundu!
This appears to be your first run.
Run 'stundu.py setup' to configure.

Run stundu.py setup to create a configuration file interactively.

Config File

The default config file location is ~/.config/stundu/config.json:

{
  "locale": "lv",
  "style": "rounded_grid",
  "data_file": "/path/to/my/timesheet.csv",
  "formats": {
    "date": "short",
    "time": "short",
    "datetime": "medium"
  }
}

Run configure without arguments to see which config file is in use and what values are active.

Table Style

The style key controls how tables are rendered in report, log, and rates. Set it with:

python stundu.py configure style rounded_grid

Available styles (run configure style --help for the full list):

Tier Styles Divider
ASCII plain, simple, grid, github, psql, pipe, presto, pretty -
Light simple_grid, rounded_grid, outline, simple_outline, rounded_outline
Heavy heavy_grid, double_grid, fancy_grid, heavy_outline, double_outline, fancy_outline

The default is simple. The Quick Start section shows an example report rendered with rounded_grid.

Date/Time Format Options

The formats section controls how dates and times are displayed. You can use:

Preset styles (locale-aware):

  • short - Compact format (e.g., "1/15/26" in English, "15.01.26" in German)
  • medium - Default format (e.g., "Jan 15, 2026")
  • long - Full month name (e.g., "January 15, 2026")
  • full - Complete format (e.g., "Thursday, January 15, 2026")

Custom patterns (using Unicode CLDR format):

{
  "formats": {
    "date": "dd.MM.yyyy",
    "time": "HH:mm",
    "datetime": "yyyy-MM-dd HH:mm"
  }
}

Environment Variables

Variable Description
STUNDU_CONFIG Custom config file path
STUNDU_DATA Custom data file path

CLI Options

Option Description
--lang, -L Override language (e.g., --lang=de)
--data, -d Override data file path for this command

Precedence

Data file location (highest to lowest priority):

  1. --data CLI option
  2. STUNDU_DATA environment variable
  3. data_file in config file
  4. Default: stundu_data.csv in current directory

Config file location:

  1. STUNDU_CONFIG environment variable
  2. Default: ~/.config/stundu/config.json

Expression-Based Rates

Beyond fixed hourly rates, Billable supports MathJSON expressions as the hourly_rate. This lets you implement billing models where the effective rate depends on how much time has already been billed — retainers, volume discounts, loyalty pricing, and more. Entries that cross a rate-change threshold mid-session are automatically split at the exact minute the threshold is reached.

Expression-based rates are set by editing the data file directly (they are an advanced feature):

SET_HOURLY_RATE "Client" <expression> <currency>

First Hour Free, Then 50 EUR/h (Monthly Retainer)

SET_HOURLY_RATE "Acme Corp" ["If",[["Less",["Sum","[Acme Corp][0M:+1M][hours]"],1],0],50] EUR

The expression sums all logged hours for "Acme Corp" in the current calendar month. While the total is under 1 hour the rate is 0; after that it's 50 EUR/h. The counter resets automatically at the start of each month.

Volume Discount — Full Rate for First 20 h, Lower Rate After

SET_HOURLY_RATE "Acme Corp" ["If",[["Less",["Sum","[Acme Corp][0M:+1M][hours]"],20],80],60] EUR

First 20 hours each month: 80 EUR/h. Everything beyond: 60 EUR/h.

Loyalty Rate — Cheaper After 100 Lifetime Hours

SET_HOURLY_RATE "Acme Corp" ["If",[["Less",["Sum","[Acme Corp][:-1][hours]"],100],75],50] EUR

[:-1] sums all entries before the current one. The rate starts at 75 EUR/h and drops to 50 EUR/h once the client has accumulated 100 hours of lifetime work.

See docs/concepts.md for the full reference, including the entry reference syntax, available fields, and how expression-based rates compose with afterhours and holiday modifiers.

Localization

The CLI supports multiple languages with full localization of messages and date/time formats. The language is detected automatically from your system locale, or you can override it with --lang:

python stundu.py --lang=de status
python stundu.py --lang=es log

Date and time formatting automatically adapts to your locale using Babel:

Locale Date Example Time
en Jan 15, 2026 2:30 PM
de 15.01.2026 14:30
fr 15 janv. 2026 14:30
lv 2026. gada 15. janv. 14:30

Available Translations

Code Language
en English
de German
es Spanish
et Estonian
fr French
it Italian
lt Lithuanian
lv Latvian
pl Polish
pt Portuguese
uk Ukrainian

Commands

Time Tracking

Command Description
start <tags...> Start tracking time with one or more tags
stop Stop the currently active entry
status Show the currently active entry
continue Resume tracking with the same tags as the last entry
cancel Discard the currently active entry without saving

Viewing Entries

Command Description
log Show all time entries in a table
report Show billing report (defaults to current month)
rates Show configured rates for all tags

Report options:

  • --tag, -t - Filter by tag
  • --month, -m - Current month (default when no filter is given)
  • --last-month, -l - Previous month
  • --from - Start date filter (YYYY-MM-DD)
  • --to - End date filter (YYYY-MM-DD)

When no date filter is specified, the report shows the current month. If there are no entries in the current month, it falls back to all entries from the last day that has data.

Configuration

All configuration commands are grouped under the configure command:

Command Description
configure Show active config sources and available subcommands
configure rate <tag> <amount> <currency> Set hourly rate for a tag
configure workday <hours> [tag] Set workday hours (e.g., 9:00-18:00)
configure afterhours <tag> <multiplier> Set afterhours rate multiplier
configure holiday <tag> <multiplier> Set holiday/weekend rate multiplier
configure week-start <0-6> Set week start day for calendar expressions (0=Mon, 6=Sun)
configure style <name> Set table display style

Personal information (for invoices):

Command Description
configure my-name <name> Set your name
configure my-address <address> Set your address
configure my-email <email> Set your email
configure my-vat <vat> Set your VAT number
configure my-bank <bank> Set your bank name
configure my-bank-account <account> Set your bank account number
configure my-bic <bic> Set your bank BIC/SWIFT code
configure my-note <note> Set a note to appear on invoices

Client information (for invoices):

Command Description
configure client-address <tag> <address> Set client address
configure client-vat <tag> <vat> Set client VAT number
configure client-note <tag> <note> Set client-specific note

Entry Management

Command Description
modify <@N> [options] Modify an existing entry
delete <@N or tag> Delete entries by reference or tag

Invoicing

Command Description
invoice <tag> [options] Generate invoice for a tag

Invoice options:

  • --output, -o - Output file path (default: invoice_<tag>_<date>.html)
  • --format, -f - Output format: html or pdf (default: html)
  • --from - Start date filter (YYYY-MM-DD)
  • --to - End date filter (YYYY-MM-DD)

Importing Data

Command Description
import <source> <file> [options] Import time entries from external tools

Import options:

  • --dry-run - Preview import without saving
  • --yes, -y - Skip confirmation prompt

Other

Command Description
setup Run the setup wizard to configure stundu

Supported Importers

Stundu can import data from other time tracking tools:

Source Description
timewarrior Timewarrior data files

Data Storage

All data is stored in a single CSV file (stundu_data.csv) containing:

  • Time entries with start/end timestamps and tags
  • Rate configuration commands (SET_HOURLY_RATE, SET_WORKDAY, SET_WEEK_START, etc.)
  • Personal and client information (SET_MY_*, SET_CLIENT_*)

This makes it easy to backup, version control, or transfer your time tracking data.

Documentation

  • Examples - Detailed usage examples
  • Concepts - Tags, rate types, MathJSON expressions

Contributing

See CONTRIBUTING.md for development setup, testing, and contribution guidelines.

License

MIT

Release files for stundu 0.5.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for stundu 0.5.5
File Size Uploaded
stundu-0.5.5.tar.gz 537.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for stundu 0.5.5
File Interpreter ABI Platform
stundu-0.5.5-py3-none-any.whl Python 3 none any Details

Total release size: 624.9 kB

Release files / stundu-0.5.5.tar.gz

Download URL stundu-0.5.5.tar.gz
Size 537.6 kB
Tags Source
SHA-256 checksum
How to use checksums
a2ba417a1255b0bddb5c2f2fd2a576a2a480a88217e6340513cbb9a37315d6eb
BLAKE2b-256 checksum
How to use checksums
f39ce712927fd279a6cc7744eed64458fd0a05a2d36a1b45f9e11fe65c760ba4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release files / stundu-0.5.5-py3-none-any.whl

Download URL stundu-0.5.5-py3-none-any.whl
Size 87.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7362be03f33ee38dad6f9c5083f1e5506ef1f13fac41169a8c29cc91ff522121
BLAKE2b-256 checksum
How to use checksums
5af482e0a1dbbee76d139a3790f1cb3ff2788bd8b0589e56e9ecbf2a7db95042
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release history Release notifications | RSS feed

0.5.6

2 release files

This release

0.5.5 This release

2 release files

0.5.3

2 release files

0.5.2

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page