Skip to main content
Every track CLI command is a thin wrapper around track.core — the same module you can import in your own Python code. Because all the business logic lives on the server, the library stays lightweight: it is pure stdlib, makes plain HTTP calls to the /api/* endpoints, and hydrates the JSON responses into typed dataclasses. You can drive it from a Jupyter notebook, a cron script, a Slack bot, or any Python automation pipeline without touching the CLI at all.

Requirements and installation

track.core requires Python 3.11 or later. The easiest way to install it is through the project’s init.sh script, which creates a virtualenv and symlinks the track binary:
git clone https://github.com/rwwarren/project-tracker
cd project-tracker
./init.sh
export PATH="$HOME/.local/bin:$PATH"
After that, track.core is importable from the same virtualenv (or from any environment where you have installed the package directly).

Configuration

The library reads credentials from the same place the CLI does — no separate setup needed if you have already run track config:
  • ~/.track/config.toml — written by track config. Contains api_url and api_key.
  • Environment variablesTRACK_API_KEY and TRACK_API_URL take precedence over the file and are useful in CI, containers, or serverless functions.
The default API URL is https://projects.wrixton.xyz. You only need to set TRACK_API_URL if you are pointing at a self-hosted deployment.
export TRACK_API_KEY="your-api-key-here"
# TRACK_API_URL is optional; defaults to https://projects.wrixton.xyz

The Item dataclass

Every function that returns data gives you back Item instances (or lists of them). The available fields are:
FieldTypeDescription
idstrUnique item ID, e.g. t-1f3a, s-7bdb, g-c4a1
kindstrOne of todo, chore, task, gift, shopping, grocery
titlestrThe item’s display name
statusstrtodo, doing, done, or canceled
priorityint1 (highest) to 5 (lowest); default 3
duedate | NoneEffective due date
tagslist[str]Free-form tag list
notesstr | NoneMarkdown notes body
everystr | NoneRecurrence string, e.g. "weekly", "3 months"
last_donedate | NoneFor chores: when it was last completed
projectstr | NoneProject slug (tasks only)
featurestr | NoneFeature ID (tasks only)
for_personstr | NoneGift recipient name
occasionstr | NoneGift occasion, e.g. "Birthday"
budgetfloat | NoneBudget in your local currency (gifts and shopping)
urlstr | NoneReference URL (gifts and shopping)
estimate_minutesint | NoneTime estimate in minutes

Key functions

Adding items

core.add_todo(title, *, due, priority, tags, notes, every, estimate_minutes)
Creates a one-off to-do. priority defaults to 3. Pass an every string ("weekly", "3 months", etc.) to make it recurring — on completion the server spawns the next occurrence automatically.
core.add_chore(title, every, *, priority, tags, notes, last_done, estimate_minutes)
Creates a recurring chore. Unlike todos, chores are never marked done — their effective due date rolls forward as last_done + every. The every argument is required.
core.add_task(project_slug, title, *, due, priority, tags, notes, feature_id, every, estimate_minutes)
Adds a task inside a project. Supply feature_id to attach it to a specific feature within that project.
core.add_gift(title, for_person, *, occasion, due, budget, url, priority, tags, notes, every)
Creates a gift item. for_person is required. Gifts have a two-stage lifecycle: mark_bought moves status to doing (bought, not yet given), and mark_done moves it to done (given).
core.add_shopping(title, *, due, budget, url, priority, tags, notes, every)
Creates a one-off shopping item. Gifts and shopping share the s- ID prefix.
core.add_grocery(title, *, due, priority, tags, notes, every)
Creates a grocery item. Groceries get the g- prefix and appear in their own list. Use tags to capture the store (e.g. tags=["costco"]).

Completing items

core.mark_done(item_id, when=None)   # → Item
Marks any item done. For a gift, this means “given”. For a recurring item, the server spawns the next occurrence. Pass a date as when to record a completion date other than today.
core.mark_bought(item_id)  # → Item
Moves a gift or shopping item to doing (bought, not yet given/used). For gifts this is the intermediate step before mark_done.

Reading data

core.today_view()          # → list[Item]
Returns every item that needs attention today: anything due on or before today, any high-priority item (priority ≤ 2), and anything already in progress (status == "doing"). Results are sorted by priority and due date, matching the CLI’s track today output.
core.load_shopping()       # → list[Item]
Returns all active shopping-kind items.

Managing projects

core.update_project(slug, status="done")
Updates a project’s title, status, or notes. Pass only the fields you want to change. Setting status="done" archives the project.
core.create_project(title, *, slug, notes, kind)
Creates a new project. slug is auto-generated from the title if omitted. kind defaults to "standard"; use "engineering" to unlock features and requirements.

Example

from track import core
from datetime import date

core.add_gift("Ceramic mug set", for_person="Sister", occasion="Birthday",
              budget=80, due=date(2026, 6, 15))
core.add_shopping("New running shoes", budget=150)
core.mark_bought("s-7bdb")   # gift -> bought (doing)
core.mark_done("s-7bdb")     # gift -> given (done)

core.load_shopping()         # list[Item], mixed gift + shopping
core.today_view()            # everything that needs attention today
IDs like "s-7bdb" are prefix-matched on the server, so you can pass a shorter prefix (e.g. "s-7b") as long as it is unambiguous. A KeyError is raised if the prefix matches zero or more than one item.

Use cases

from track import core

def post_daily_summary(slack_client, channel):
    items = core.today_view()
    lines = [f"• [{i.kind}] {i.title}" for i in items[:10]]
    slack_client.chat_postMessage(
        channel=channel,
        text=f"*Today ({len(items)} items)*\n" + "\n".join(lines),
    )
All add_* functions validate the every recurrence string before sending the request, so invalid patterns like "every week" raise a ValueError locally rather than returning a server error. Valid forms include "daily", "weekly", "biweekly", "monthly", "quarterly", "yearly", and "<N> <unit>" (e.g. "3 months", "2 weeks").