odoo-timesheets/AGENTS.md
Jef Roosens 29698b1241
feat(config): add TOML config file support
- Add config.py with load_config(), find_default_config(), get_token(),
  and get_map_path()
- Auto-discover timesheets.toml in cwd; override with --config flag
- Priority: CLI flag > config file > env var / cwd default
- Add timesheets.example.toml as a committed reference template
- Add timesheets.toml to .gitignore to prevent accidental secret leakage
- Document config file format in AGENTS.md
2026-06-02 09:31:04 +02:00

4.9 KiB

Timesheets — Agent Guide

Project overview

A Python CLI tool that parses markdown pipe-delimited timesheet tables and exports them to CSV for import into Odoo (or similar tools). It also supports a human-readable summary view and can fetch notes directly from Joplin.

Package layout

timesheets/
├── pyproject.toml                  # package metadata, entry point, dev dependencies
├── AGENTS.md
└── src/timesheets/
    ├── cli.py        # argument parsing, main() entry point
    ├── parser.py     # markdown table parsing, aggregation, date filtering
    ├── projects.py   # project_map.json loading and key resolution
    ├── output.py     # CSV writing and summary printing
    ├── config.py     # TOML config file loading and key extraction
    ├── joplin.py     # Joplin API integration (notebook traversal, note fetching)
    └── utils.py      # shared low-level helpers (duration parsing, formatting, etc.)

Tests live in tests/, one file per source module:

tests/
├── test_utils.py
├── test_parser.py
├── test_projects.py
├── test_config.py
├── test_output.py
└── test_joplin.py

Package manager — uv

All dependency management and script execution is done via uv. Do not use pip or python directly.

Task Command
Install / sync dependencies uv sync
Add a runtime dependency uv add <package>
Add a dev-only dependency uv add --dev <package>
Run the CLI uv run timesheets <args>
Run any Python script uv run python <script>

CLI usage

# Print CSV to stdout (date defaults to today)
uv run timesheets --input input.md

# Specify a day (positional, accepts YYYY-MM-DD, MM-DD, or DD-MM; - or / separator)
uv run timesheets 2026-05-22 --input input.md
uv run timesheets 05-22 --input input.md

# Write CSV to a file
uv run timesheets --input input.md -o output.csv

# Override the day (accepts YYYY-MM-DD, MM-DD, or DD-MM; - or / separator)
uv run timesheets --input input.md --day 2026-05-22
uv run timesheets --input input.md --day 05-22

# Use a specific project map file
uv run timesheets --input input.md --map /path/to/project_map.json

# Print a human-readable summary instead of CSV
uv run timesheets --input input.md --summary

# Read from stdin
cat input.md | uv run timesheets --input -

# Fetch today's entries from Joplin (token via env var)
JOPLIN_TOKEN=your_token uv run timesheets --joplin

# Fetch entries for a specific day from Joplin
uv run timesheets 2026-05-22 --joplin --token your_token

The --joplin flag and the file input argument are mutually exclusive. When --joplin is used, only entries matching the target day (positional arg, or today) are returned, filtered by the # ... YYYY-MM-DD day heading in the note.

The API token can be provided via:

  • --token <token> CLI flag
  • JOPLIN_TOKEN environment variable

project_map.json is auto-discovered in the current working directory if --map is not provided.


Config file

Create timesheets.toml in your working directory (or pass --config /path/to/file.toml):

[joplin]
token = "your_api_token_here"

[projects]
map = "/path/to/project_map.json"

Priority order for each value: CLI flag > config file > environment variable / default.


Joplin notebook structure

The --joplin flag expects the following notebook hierarchy in Joplin:

Work/
└── Timesheets/
    └── YYYY/
        └── YYYY - WNN/         ← notebook per week
            └── YYYY - WNN      ← note with the same title as the notebook

The note body contains one markdown table per day, each preceded by a heading of the form # <weekday> - YYYY-MM-DD.


Testing

The test suite uses pytest with pytest-cov for coverage reporting.

# Run all tests
uv run pytest

# Run with coverage report
uv run pytest --cov

# Run a specific test file
uv run pytest tests/test_parser.py

# Run a specific test
uv run pytest tests/test_parser.py::TestParseTable::test_empty_input

Rules for adding or changing functionality

  1. Always update or add tests when introducing new behaviour or modifying existing behaviour. Tests live in the tests/ file that corresponds to the module being changed (e.g. changes to parser.pytests/test_parser.py).

  2. Run the full test suite before finishing and confirm it passes with no failures:

    uv run pytest --cov
    
  3. Do not reduce coverage. Every new function or branch should have at least one test covering the happy path. Edge cases and error paths should be covered where the logic is non-trivial.

  4. cli.py is intentionally excluded from unit tests — it is thin glue code. All logic worth testing belongs in the other modules.

  5. Joplin integration tests in test_joplin.py must mock ClientApi — do not require a live Joplin instance.