A minimal cross-platform terminal task manager
- One binary for Linux, macOS and Windows.
- Due dates, priorities, dependencies between tasks and a phrase search.
- An interactive multi-line editor with undo, clipboard and crash-safe drafts.
- A mobile-first web UI, a static HTML export and sync over SSH or HTTPS.
- The database is a file in the format you pick: JSON, CSV, Markdown, todo.txt, NDJSON, iCalendar or SQLite. Or a remote one, or a git repository.
- Atomic saves, a backup on every save and commands that can run at the same time.
- Tab completion for bash, zsh, fish, nu and PowerShell.
cargo install --git https://github.com/tagirov/ruskThe binary lands in $HOME/.cargo/bin/rusk (Windows:
%USERPROFILE%\.cargo\bin\rusk.exe). Make sure that directory is on your
PATH.
yay -S rusknix run github:tagirov/rusk # try it without installing
nix profile install github:tagirov/rusk # bash, zsh and fish completions come with itgit clone https://github.com/tagirov/rusk && cd rusk
cargo build --releaseLinux/macOS:
sudo install -m 755 ./target/release/rusk /usr/local/binWindows:
copy .\target\release\rusk.exe "%USERPROFILE%\AppData\Local\Microsoft\WindowsApps\"rusk add Buy groceries # add a task
rusk add Finish the report -d 31-12-25 # with a due date
rusk # list the tasks (same as `rusk list`)
rusk mark 1 # done; run it again to undo
rusk edit 2 Finish the quarterly report # new text
rusk del 1 # asks for confirmation first
rusk --help # help; `rusk add --help` for one command-d / --date takes a date in one of these forms:
| Form | Examples | Notes |
|---|---|---|
| Absolute | 31-12-2025, 31/12/25, 1.3.25 |
day-month-year, with -, / or . between the parts. Leading zeros are optional. A two-digit year is 20xx |
| Month name | 30-apr-26, 1-September-2027 |
English, short or long |
| Words | today, tomorrow |
|
| Relative | 2w, 3q, 10d5w |
an offset from today: d days, w weeks, m months, q quarters (3 months), y years. Chain segments with no spaces |
| From the task's date | +2w |
edit only: an offset from the task's current due date (today if it has none) |
| Clear | _ |
removes the date of a task being edited |
A relative offset adds the months first and the days after. From the 31st, a
shorter month ends the date on its last day: 31-01 + 1m = 28-02. Years run
from 1000 to 9999.
rusk add Tax filing -d 30-apr-26
rusk add Follow up -d 2w
rusk edit 1 -d +2w
rusk edit 1 -d _In the interactive editor the date is the first word of the first line.
rusk # or `rusk list`
rusk list --compact # one line per task; `…` marks a task that goes on
rusk list --no-compact # full view for one run, even with `compact = true` in the config
rusk search omega # matches highlighted
rusk s buy milk # the words are one phrase
rusk s --id omega # only the ids, one per line (script-friendly)The compact view trims trailing punctuation and never wraps. A new task that takes the id of a deleted one is listed in that place. In a hand-edited file with an order of its own it goes at the end.
Search is forgiving in two ways and strict in one:
- Case is ignored:
ΟΔΟΣfindsοδος,STRASSEfindsStraße. - The amount of whitespace between the words is ignored. The list shows a run of spaces as one.
- Accents are not ignored:
cafedoes not findcafé.
rusk mark 1 # done; run it again to undo
rusk mark 1 --priority # priority on; run it again to remove itmark says what it changed: done, undone, priority or priority removed.
Priority survives done/undone toggles.
rusk edit 1 Complete the project documentation # new text in one shot
rusk edit 1 -d 1-1-25 # only the date
rusk edit 1 -- -x means exclude # text that starts with a dash
rusk edit 1 -- 42 # text made of numbers onlyEverything after -- is text, word for word. Before --, a word that starts
with a dash is an option, and -h prints the help. Without new text,
rusk edit 1 opens the interactive editor.
rusk add deploy --after 19,22 # shown as `deploy (19,22)` in the list
rusk edit 1 --after 19,22 # change the list (a set: the order does not matter)
rusk edit 1 --after _ # clear itA dependency is an ordering hint for agents and tooling: the task should be
done no earlier than the tasks it depends on. rusk mark is never blocked by
it.
A task text that starts with TEMP, INFO, FIXME or WIP gets that word
highlighted in the list. The set is configurable (keywords in the config;
an empty value disables it), and so is the color (the keyword theme key).
rusk add TEMP debug flag until the release
rusk add INFO deploy notes live on the wikirusk del 1 # asks for confirmation first
rusk del --done # all completed tasks
rusk del 1 --yes # no question asked: for scripts and pipes
rusk del --done -yThe question names the tasks that depend on the one being deleted. The
deletion then says which of them no longer do
(Task 3 no longer depends on 1.). Without a terminal to ask on (a script,
a pipe) rusk del refuses instead of guessing: --yes is how to delete
there.
mark, edit and del take one comma-separated list of ids: 1,2,3
(1, 2, 3 is read the same way).
rusk mark 1,2,3
rusk edit 1,2,3 Update status to completed
rusk del 1,2,3- Anything in the list that is not an id is an error. A repeated id counts once.
- For
markanddela second word after the list is an error (rusk mark 1 2). - For
editthe first word that is not glued to the list by a comma starts the new text:rusk edit 3 1,000 unitsedits task 3 only. - When everything after the list is numbers (
rusk edit 1,2 3 -d 2w) nothing changes, and rusk says so: a text of numbers goes after--.
The interactive multi-line editor supports selection, the system clipboard, undo/redo, word navigation, the mouse, crash-safe autosave and a colored date header on the first line. The full reference is EDITOR.md.
rusk add # create a task in the editor
rusk add -d 2w # with the first line pre-filled
rusk edit 1 # edit the text and the due date on the first line
rusk edit 1,2,3 # several tasks in one session| Command | Alias |
|---|---|
add |
a |
list |
l |
mark |
m |
edit |
e |
del |
d |
restore |
r |
gen |
g |
search |
s |
completions |
c |
| Flag | Short | Where |
|---|---|---|
--date |
-d |
add, edit |
--after |
-a |
add, edit |
--compact |
-c |
list (--no-compact has no short form) |
--priority |
-p |
mark |
--yes |
-y |
del |
--output |
-o |
gen |
--help |
-h |
everywhere |
--version |
-V |
rusk -V |
A mobile-first web frontend, themed from your configuration file and built from a single embedded template: there is no separate frontend to install. The full guide (authentication, deployment behind Caddy or nginx, the API, sync) is WEB.md.
rusk gen -o index.html # a self-contained read-only page with all tasks
rusk serve # the interactive UI at http://127.0.0.1:7272: full task editing from a phone
rusk serve --host 0.0.0.0 # requires web_token in the configrusk gen never writes over the database or a file rusk keeps beside it.
rusk serve stays up whatever clients do:
- A connection may wait a minute (
web_timeoutin the config, in seconds). A request body or an answer has to keep moving. - Up to 256 connections are open at once.
- A connection that waits longer is closed, with
408 Request Timeoutonce part of a request had come. web_timeout = 0turns these limits off.
The limits keep the server up. They do not make it fair: exposed beyond loopback or a VPN, put a reverse proxy in front (see WEB.md).
Sync works over SSH, or over HTTP(S) with a running rusk serve:
# in the config: sync_remote = user@vps:/srv/tasks/tasks.json
rusk sync # fast-forward in whichever direction changed
rusk sync push # upload local tasks
rusk sync pull --force # discard local changes in favor of the remoteConflicts are detected against what each side held after the last sync, so nothing is silently overwritten. A change made on either side right after a sync is a change for the next one. A first sync into an empty database simply fills it. Details in WEB.md.
The short version. The full reference, including what each format stores and how backups and concurrent writers work, is STORAGE.md.
By default rusk stores tasks in ./.rusk/tasks.json, relative to the current
directory, so every project has a task list of its own:
cd ~/projects/website && rusk add Fix responsive layout
cd ~/projects/api && rusk add Add authentication endpointThe RUSK_DB environment variable or the rusk_db key in the
configuration file point elsewhere (RUSK_DB wins):
export RUSK_DB="/path/to/your/db.json" # a file
export RUSK_DB="/path/to/your/project/" # a directory: tasks.json is created insideDebug builds (cargo run) ignore RUSK_DB and use a database of their own
under the temp directory; see STORAGE.md.
The last extension of the database file name picks the format (JSON by
default; notes.txt.json is JSON):
export RUSK_DB="$HOME/tasks/tasks.csv" # or rusk_db = ~/tasks/tasks.csv in the config| Extension | Format | Interop | Feature |
|---|---|---|---|
.json (or anything else) |
pretty JSON | the default | built-in |
.csv |
RFC 4180, id,text,date,done,priority,after schema |
LibreOffice / Excel / Google Sheets | built-in |
.md / .markdown |
GitHub-style task list | GitHub, Obsidian, any editor | fmt-markdown |
.txt |
todo.txt | todo.sh, Simpletask, … | fmt-todotxt |
.ndjson / .jsonl |
one JSON task per line | git diffs, grep, jq |
fmt-ndjson |
.ics |
iCalendar VTODO | Thunderbird, Nextcloud Tasks, Apple Reminders | fmt-ics |
.db / .sqlite / .sqlite3 |
SQLite | concurrent writers, SQL tooling | backend-sqlite |
All features except backend-sqlite (which bundles a C library) are enabled
by default. Distro builds can trim them (--no-default-features --features …).
Every format can be edited by hand or by another tool. Backups and atomic writes work the same in each. What each format stores, and the rules every file is held to when it is read, are in STORAGE.md.
The shape of rusk_db picks the storage backend:
rusk_db = https://tasks.example.com # the API of a running `rusk serve` (feature backend-http)
db_token = s3cret # its web_token, if set (or RUSK_DB_TOKEN)
rusk_db = alex@vps:/srv/tasks/tasks.md # a file over ssh (feature backend-ssh)
git_backend = true # commit every save of a local file database
# to a git repo in its directory (feature backend-git)- http(s) is a thin client. There is no local copy: every command reads
and writes through the server, and never overwrites what another client
stored in the meantime. It needs the network and the server up. For
offline-first use
rusk syncinstead. - ssh loads and saves the remote file per command over the system
ssh(keys, agent and~/.ssh/configapply). On flaky links preferrusk sync. - git_backend gives full history (
git log,git revert) beyond the single.backupcopy. Local file databases only. Uses the systemgit, 2.9 or newer.
Details in STORAGE.md.
- Every save first copies the previous database to a
.backupsibling (tasks.json.backup;backup = falseturns this off).rusk restorebrings it back, and keeps what it replaces as.before_restore. - Every file is replaced atomically: temp file, fsync, rename. A crash, a
full disk or a parallel
ruskleaves the old content or the new one, never a truncated file. - A database that cannot be read is never taken for an empty one. The command stops instead of saving zero tasks over your data.
- Commands running at the same time (two terminals, a cron job,
rusk serve) all take effect. Saves take turns on a lock, and every change is applied to the database as it is at the moment of the save. - A command that changes nothing writes nothing.
Details in STORAGE.md.
rusk creates a configuration file on first run: ~/.config/rusk/cfg on Linux,
other platforms in CONFIG.md. It holds the theme colors of every
element group, the default behavior and the web and sync settings. The full
reference is CONFIG.md.
# variables are supported: any unknown key defines one
accent = #ffa500
priority_marker = accent
# lowercase analogs of the environment variables (env always wins)
# rusk_db = ~/tasks/tasks.json
# no_color = false
# compact = true
# web & sync
# web_token = your-secret
# web_timeout = 60
# sync_remote = user@vps:/srv/tasks/tasks.json
Colors are ANSI names (red, cyan, bright_black, ...) or hex
(#d75f00). A broken line never prevents rusk from starting: it produces a
warning and the default is kept. RUSK_CONFIG=<path> overrides the file
location; an empty RUSK_CONFIG disables the config entirely.
rusk completions install zsh # bash, zsh, fish, nu, powershell; several at once
rusk completions show zsh # print the script for a manual installTab completes the commands and their aliases, the flags (--date,
--after, --done, …) and, after rusk edit <id>, the task's own text,
quoted so that running the line stores exactly that text. Manual installation
per shell, what exactly is completed and the Windows notes are in
completions/README.md.
export RUSK_NO_COLOR=1RUSK_NO_COLORset to any non-empty value disables ANSI colors in all output: the task list, dialogs, errors,--helpand argument errors.- The standard
NO_COLOR(see no-color.org) andno_color = truein the config do the same. An empty variable counts as not set. - Once colors are off this way, nothing turns them back on for the run, not
even
CLICOLOR_FORCE, which otherwise forces colors into a pipe. CLICOLOR=0turns colors off as well, and so doesTERM=dumbunlessCLICOLOR_FORCEis set.
- What
ruskprints goes to a pipe as well as to a terminal. - A reader that stops reading (
rusk list | head -2) ends the command quietly with exit code 0. Whatever the command changed is saved before anything is printed. An output that cannot be written (a full disk) is an error, exit code 1. - Questions are asked only on a terminal (standard input and output both).
rusk delwithout one refuses and asks for--yes. The editors (rusk add,rusk edit <id>) need a terminal too. rusk search --idprints the matching ids only, one per line, no colors.
| File | Covers |
|---|---|
| CONFIG.md | the configuration file: settings, theme, variables |
| EDITOR.md | the interactive editor: keys, drafts, the date header |
| WEB.md | the web UI, rusk serve, deployment, the API, rusk sync |
| STORAGE.md | database location, formats, remote backends, backups, concurrent writers |
| completions/README.md | shell completion, per shell |
| ARCHITECTURE.md | how the code is laid out |
