Skip to content

Projects 2026 live

todo-harvest

Pulls tasks from Vikunja, Plane, Jira, Microsoft To Do and Notion into one local file with one schema, and pushes changes back to Vikunja and Plane.

Python

My tasks live in five places. Work is in Jira, the team board is in Plane, the family list is in Microsoft To Do, and notes with checkboxes are in Notion. The self-hosted Vikunja was supposed to replace them all, and it has not.

todo-harvest pulls all five into one file with one schema and resolves conflicts by timestamp. It then pushes the result back to the two services that accept writes, Vikunja and Plane. Every task gets the same fields: id, title, status, priority, dates, tags, category and url. The service's own payload is kept under raw.

Five trackers on the left, the local state file in the middle, and the one schema that is read and edited on the right

There is no daemon and no polling. pull, push and sync run only when you call them and do what their names say. inspect and export make no network calls at all.

A SQLite mapping gives each task a stable local id the first time it is pulled, and remembers which id the task has on each service. That is how a task created by a push is recognised on the next pull. For instances with their own vocabulary there are status_map, priority_map and, for Notion, field_map.

Local state is the source of truth. A pull never overwrites a newer local edit. A push never creates a task on a service where it has no mapping, unless you set a default project. It is written in Python, and the tokens go in a single config.yaml that is git-ignored.