odoo-timesheets/AGENTS.md
Jef Roosens 6915d8d764
refactor(cli): split into summary and csv subcommands
- 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
2026-06-02 09:31:06 +02:00

167 lines
4.7 KiB
Markdown

# 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`](https://docs.astral.sh/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.
```sh
# 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`):
```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.
```sh
# 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.py``tests/test_parser.py`).
2. **Run the full test suite before finishing** and confirm it passes with no
failures:
```sh
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.