Metadata-Version: 2.4
Name: stundu
Version: 0.4.0
Summary: Time tracking library with billing calculations
License: MIT
Project-URL: Homepage, https://gitlab.com/mrtmednis/stundu
Project-URL: Repository, https://gitlab.com/mrtmednis/stundu
Project-URL: Issues, https://gitlab.com/mrtmednis/stundu/-/issues
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mathjson-solver==1.18.0
Requires-Dist: tals>=0.1.0
Requires-Dist: jinja2==3.1.6
Provides-Extra: cli
Requires-Dist: typer==0.19.2; extra == "cli"
Requires-Dist: tabulate==0.9.0; extra == "cli"
Requires-Dist: python-i18n==0.3.9; extra == "cli"
Requires-Dist: babel==2.17.0; extra == "cli"
Provides-Extra: pdf
Requires-Dist: weasyprint==65.1; extra == "pdf"
Provides-Extra: dev
Requires-Dist: pytest==8.3.5; extra == "dev"
Dynamic: license-file

# Stundu

[![codecov](https://codecov.io/gl/mrtmednis/stundu/graph/badge.svg)](https://codecov.io/gl/mrtmednis/stundu)

> **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](#installation)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Expression-Based Rates](#expression-based-rates)
- [Localization](#localization)
- [Commands](#commands)
- [Supported Importers](#supported-importers)
- [Data Storage](#data-storage)
- [Documentation](#documentation)
- [Contributing](#contributing)
- [License](#license)

## Installation

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

| Method | Best for |
|--------|----------|
| [Snap](#snap) | End users on Linux who want the CLI |
| [pip](#pip) | Developers who want to use the core library |
| [From source](#from-source) | Contributing or hacking on the project |

### Snap

The easiest way to install the CLI on Linux:

```bash
sudo snap install stundu
```

Or visit [snapcraft.io/stundu](https://snapcraft.io/stundu).

### pip

Install the core library (no CLI dependencies):

```bash
pip install stundu
```

Install with the CLI included:

```bash
pip install stundu[cli]
```

Install with PDF invoice support:

```bash
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:

```bash
cd stundu
```

2. Create a virtual environment:

```bash
python3 -m venv venv
```

3. Activate the virtual environment:

```bash
# Linux/macOS
source venv/bin/activate

# Windows
venv\Scripts\activate
```

4. Install dependencies:

```bash
pip install -r requirements.txt
```

## Quick Start

```bash
python stundu.py start "Acme Corp" "Development"
```
```
Started tracking: Acme Corp, Development
Started at: 09:00
```

```bash
python stundu.py status
```
```
Currently tracking: Acme Corp, Development
Started at: 09:00
Elapsed: 2h 15m
```

```bash
python stundu.py stop
```
```
Stopped tracking: Acme Corp, Development
Duration: 3h 0m
```

```bash
python stundu.py log
```
```
@    Start    End      Duration    Tags                    Status
--   -------  -------  ----------  ----------------------  --------
@1   09:00    12:00    3h 0m       Acme Corp, Development
```

```bash
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](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`:

```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:

```bash
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):
```json
{
  "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](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`:

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

Date and time formatting automatically adapts to your locale using [Babel](https://babel.pocoo.org/):

| 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](https://timewarrior.net/) 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](docs/examples.md) - Detailed usage examples
- [Concepts](docs/concepts.md) - Tags, rate types, MathJSON expressions

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and contribution guidelines.

## License

MIT
