- 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
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 flagJOPLIN_TOKENenvironment 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
-
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 toparser.py→tests/test_parser.py). -
Run the full test suite before finishing and confirm it passes with no failures:
uv run pytest --cov -
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.
-
cli.pyis intentionally excluded from unit tests — it is thin glue code. All logic worth testing belongs in the other modules. -
Joplin integration tests in
test_joplin.pymust mockClientApi— do not require a live Joplin instance.