- Add subparsers for 'summary' and 'csv' replacing the --summary flag - Extract _add_shared_args(), _resolve_date(), _resolve_rows(), and _resolve_project_map() as shared helpers used by both subcommands - Both subcommands support -o/--output to write to a file instead of stdout - Update AGENTS.md with new subcommand usage examples
4.7 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
The CLI has two subcommands: summary and csv. Both accept the same arguments.
# Human-readable summary for today (from Joplin)
uv run timesheets summary --joplin
# Human-readable summary for a specific day
uv run timesheets summary 2026-05-22 --joplin
uv run timesheets summary yesterday --joplin
uv run timesheets summary 3 days ago --joplin
# Export today's entries as CSV to stdout
uv run timesheets csv --joplin
# Export to a file
uv run timesheets csv --joplin -o output.csv
# Use a local markdown file instead of Joplin
uv run timesheets summary --input timesheet.md
uv run timesheets csv --input timesheet.md -o output.csv
# Read from stdin
cat timesheet.md | uv run timesheets csv --input -
# Specify a day (positional, accepts YYYY-MM-DD, MM-DD, or DD-MM; - or / separator)
uv run timesheets csv 2026-05-22 --input input.md
uv run timesheets csv 05-22 --input input.md
# Use a specific project map file
uv run timesheets csv --input input.md --map /path/to/project_map.json
# Fetch entries for a specific day from Joplin
uv run timesheets csv 2026-05-22 --joplin --token your_token
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.