Minimal todo CLI for the terminal. Pure Python, zero dependencies.
Project description
tsk
A small task manager for the terminal. One line to add a task, one file to keep them in, and nothing to configure.
uv tool install tsk-todo
Or try it without installing anything: uvx --from tsk-todo tsk
Why you might like it
There is nothing to set up. No config file, no theme to pick, no coefficients to tune. It works the way it works, and the first thing you do with it is add a task.
You can predict the order. Due date, then priority, then id. That is the entire rule. Nothing computes a score behind your back, so a task never jumps the queue for a reason you cannot see.
It repeats properly. Finish a daily task a week late and the next one is due tomorrow, not eight overdue copies waiting to be dismissed one at a time.
It stays out of the way. tsk add email bob@example.com about the invoice does exactly what you
meant. So does tsk add fix the -v flag. Nothing gets swallowed as metadata.
You can leave whenever. Your tasks are one JSON file you can read, diff and commit. Stop using
tsk tomorrow and they are still sitting there in a format anything can open.
Install
Needs Python 3.11 or newer, and nothing else. There are no runtime dependencies.
uv tool install tsk-todo
The command is tsk. The package is tsk-todo, because
tsk on PyPI belongs to something else. pip install tsk-todo works too, and from a clone it is
uv tool install .
The idea
You type what you mean and tsk works out which parts are metadata:
tsk add renew the passport @admin +boring due:eom p:h
@admin is the project, +boring is a tag, due:eom is the end of this month, p:h is high
priority. Everything left over is the task itself.
Only due:, p: and every: are read as attributes, so ordinary text stays ordinary:
$ tsk add email bob@example.com about the invoice
$ tsk add fix the -v flag
$ tsk add ticket: replace the boiler
None of those pick up a project, a tag or an attribute. If it is not a form tsk knows, it is
part of the description.
Commands
tsk list pending tasks
tsk add <text> [attrs] add a task
tsk done <id>... complete tasks
tsk rm <id>... delete tasks
tsk edit <id> <attrs> change a task
tsk note <id> <text> attach a note
tsk show <id> show one task in full
tsk list [filters] list tasks
tsk 3 on its own is shorthand for tsk show 3.
done and rm take several ids at once: tsk done 2 5 9.
Attributes
Use these when adding or editing.
@project set the project
+tag add a tag
-tag remove a tag, when editing
due:<when> set a due date
p:h|m|l set priority, high medium or low
every:<how> reschedule on completion
Priority also accepts the full word, so p:high and p:h are the same.
Editing changes only what you name. Everything else is left alone.
$ tsk edit 3 due:friday +urgent
updated 3: read the book
To clear something, give it no value:
$ tsk edit 3 due: p: every:
A bare @ clears the project. A leading - removes a tag.
Filters
tsk +garden tagged garden
tsk -work not tagged work
tsk @home in the home project
tsk overdue past due
tsk due:friday due on or before friday
tsk done completed tasks
tsk all everything
tsk plants search descriptions and notes
Filters combine, and list is optional:
$ tsk @home +garden overdue
$ tsk list @work due:eow
Bare words search. tsk list dentist finds anything with dentist in the description or in a note.
Dates
Anywhere a date is wanted:
today, tomorrow, yesterday
monday .. sunday, or mon .. sun
3d, 2w, 1m, 1y
eow, eom, eoy
2026-08-01
Weekday names mean the next one coming. On a Thursday, due:thursday is a week away, not today.
Month and year offsets land on a real date. 1m from the 31st of January is the 28th of February,
not the 3rd of March.
Due dates are plain calendar dates. due:friday means Friday wherever you happen to be, and it
does not drift when you cross a timezone.
Repeats
every:daily
every:weekdays
every:weekly
every:monthly
every:yearly
A repeating task needs a due date. When you complete it, the next one is created for you:
$ tsk done 1
done 1: water the plants
repeats as 5, due in 1 week
The next date is worked out from the old due date, then rolled forward until it is in the future. Finish a daily task five days late and the next one is due tomorrow, not last week. You never come back to a pile of overdue copies.
Notes do not carry over. The completed task keeps them.
Notes
$ tsk note 3 the ferns look worse than the rest
noted on 3: water the plants
Tasks with notes are marked with a * in the list. tsk show 3 prints them.
Ordering
Tasks sort by due date, then priority, then id. That is the whole rule. Nothing is scored or weighted behind your back, so the order is always one you can predict and explain.
Undated tasks sort last. They are not urgent, they are just there.
Your data
One JSON file, at ~/.local/share/tsk/tasks.json unless you say otherwise:
{
"version": 1,
"issued": 7,
"tasks": [
{
"id": 2,
"description": "fix the login bug",
"status": "pending",
"created": "2026-07-16T14:49:48+00:00",
"project": "work",
"tags": ["urgent"],
"due": "2026-07-15",
"priority": "high"
}
]
}
Set TSK_DATA to put it somewhere else, or XDG_DATA_HOME to move the lot. Keep it in a git repo
if you want history. Tags are written in a fixed order and absent fields are left out, so diffs
stay small.
Writes are atomic. The file is written beside itself and then moved into place, so an interrupted write cannot leave you with half a file. Existing permissions are preserved.
Ids are never reused. Once id 3 has been handed out, no later task gets it, even if you delete task 3 the same day. The id you read a minute ago is still the task you thought it was.
Colour
On when writing to a terminal, off when piped. Set NO_COLOR to turn it off entirely.
Overdue is red, due today is yellow, projects are cyan, tags are magenta.
What it does not do
There is no urgency score, no dependency graph, no user defined attributes, no hooks, no custom report definitions and no sync. Each of those either failed the question "would I miss this" or costs far more than it returns for a tool this size.
If you want a task on the calendar, due: is where it goes. If you want to find it later, tag it.
That is most of task management.
Development
uv sync
uv run pytest
uv run ruff check .
uv run ruff format .
uv run mypy
The suite runs in well under a second. It is worth keeping it that way.
Licence
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tsk_todo-0.1.2.tar.gz.
File metadata
- Download URL: tsk_todo-0.1.2.tar.gz
- Upload date:
- Size: 12.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4bec97faca3743547e04b409a59012ba51031677a27b8391926a001e7890ffa8
|
|
| MD5 |
3a2aed0128b726da567bbc5883dd1892
|
|
| BLAKE2b-256 |
411a5e02b70e1f09dafbb0b46d8f45da736c7382c29bffd42555050dba681298
|
File details
Details for the file tsk_todo-0.1.2-py3-none-any.whl.
File metadata
- Download URL: tsk_todo-0.1.2-py3-none-any.whl
- Upload date:
- Size: 15.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2df44a31207bf892bedc2a1723c172667ce9bb93d0542b5db056eadd84baf6ba
|
|
| MD5 |
365da0f18ab4d9eae2a379aba4da5126
|
|
| BLAKE2b-256 |
80cd0250e0cbdcc2a16af7edaccf5a115539a291abef867ca656ae2815704afc
|