Skip to content

arkan/td

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

td

td is a personal Rust task manager for an Obsidian vault. It reads and writes a Markdown TODO.md file using the obsidian-tasks checkbox format, while adding stable task IDs, recurrence handling, backlog promotion/demotion, and an optional terminal UI.

The binary is named td. The Cargo package is currently named td-cli.

Features

  • Add tasks to active tasks or backlog.
  • List actionable, upcoming, backlog, completed, or all active tasks.
  • Mark tasks as done and archive them automatically.
  • Create the next occurrence automatically for recurring tasks.
  • Promote backlog items to active tasks.
  • Demote active tasks back to the backlog.
  • Match tasks by stable ID prefix or by description substring.
  • Migrate legacy task files by assigning missing IDs.
  • Merge a legacy ## Later section into ## Backlog.
  • Move checked tasks from active sections into ## Archive.
  • Use a terminal UI with keyboard navigation and inline add/edit forms.
  • Ship an agent skill at skills/td/SKILL.md so agents use the CLI instead of editing TODO.md directly.

Data model

td expects a Markdown file with these sections:

# TODO

## Tasks

- [ ] Active task

## Backlog

- [ ] Deferred task

## Archive

- [x] Completed task ✅ 2026-05-08

The canonical sections are:

Section Meaning
## Tasks Active tasks. Default list mode shows only actionable tasks here.
## Backlog Deferred tasks and migrated Later entries.
## Archive Completed tasks.

Each task is a Markdown checkbox:

- [ ] Pay nursery #project/admin #home ⏫ 🔁 every month on the 1st 📅 2026-05-01 <!-- tid:a1b2c3d4 -->

Supported task metadata:

Metadata Format Notes
Stable ID <!-- tid:a1b2c3d4 --> 8 hexadecimal characters. Hidden by Obsidian preview.
Priority , 🔼, 🔽 High, medium, low.
Due date 📅 YYYY-MM-DD Parsed as a date without timezone.
Recurrence 🔁 every month Stored in canonical text form.
Project #project/<slug> Created from --project; spaces become -, value is lowercased.
Tags #tag Any non-project tags.
Completion date ✅ YYYY-MM-DD Added when a task is archived.

Installation

With Nix

This repository exposes a Nix flake with a default package, app, and development shell. Use this path when Nix is available; it avoids manual archive downloads and gives reproducible builds.

Prerequisites:

  • Nix with flakes enabled.
  • Network access to GitHub when installing from the public repository.

Run from the local checkout:

nix run . -- --help
nix run . -- list

Build from the local checkout:

nix build
./result/bin/td --help

Install from the local checkout:

nix profile install .
td --help

Install from the public GitHub repository, using the current default branch:

nix profile install github:arkan/td
td --help

Install a pinned release tag:

nix profile install github:arkan/td/v0.1.1
td --help

Run without installing from the public GitHub repository:

nix run github:arkan/td -- --help

Run a pinned release tag without installing:

nix run github:arkan/td/v0.1.1 -- --help

Open a development shell with Rust tooling:

nix develop

From a GitHub Release

Download the archive for your platform from the latest GitHub Release, verify its checksum, extract it, and put the td binary on your PATH.

Prerequisites:

  • GitHub CLI (gh).
  • tar.
  • sha256sum on Linux, or shasum on macOS.

Choose the target for your platform:

Platform Target
Linux x86_64 x86_64-unknown-linux-gnu
macOS Intel x86_64-apple-darwin
macOS Apple Silicon aarch64-apple-darwin
Windows x86_64 x86_64-pc-windows-msvc

Linux example:

version="v0.1.1"
target="x86_64-unknown-linux-gnu"
gh release download "${version}" \
  --repo arkan/td \
  --pattern "td-${version}-${target}.tar.gz*" \
  --dir .
if command -v sha256sum >/dev/null 2>&1; then
  sha256sum -c "td-${version}-${target}.tar.gz.sha256"
else
  shasum -a 256 -c "td-${version}-${target}.tar.gz.sha256"
fi
tar -xzf "td-${version}-${target}.tar.gz"
install -m 755 "td-${version}-${target}/td" "$HOME/.local/bin/td"
td --help

macOS Intel uses the same commands with this target:

target="x86_64-apple-darwin"

macOS Apple Silicon uses the same commands with this target:

target="aarch64-apple-darwin"

Windows PowerShell:

$version = "v0.1.1"
$target = "x86_64-pc-windows-msvc"

gh release download $version `
  --repo arkan/td `
  --pattern "td-$version-$target.tar.gz*" `
  --dir .

$archive = "td-$version-$target.tar.gz"
$expected = (Get-Content "$archive.sha256").Split(' ')[0].ToUpperInvariant()
$actual = (Get-FileHash $archive -Algorithm SHA256).Hash
if ($actual -ne $expected) { throw "Checksum mismatch" }

tar -xzf $archive
# Add the extracted td-$version-$target directory to PATH, then run:
td.exe --help

From source

Requirements:

  • Rust stable.
  • Cargo.

Install the binary from this repository:

cargo install --path .
td --help

Run without installing:

cargo run -- list
cargo run -- add "Pay nursery" -p high -d 2026-05-01

Configuration

td resolves the target todo file in this order:

  1. TODO_FILE environment variable.
  2. ~/.config/td/config.toml with a todo_file field.
  3. Fallback: <home>/TODO.md, where <home> comes from HOME or USERPROFILE.

Use a one-off file:

TODO_FILE=/path/to/TODO.md td list

Persistent config:

# ~/.config/td/config.toml
todo_file = "/absolute/path/to/TODO.md"

If the target file does not exist, commands that load it will fail with a read error. Create the file with the expected sections before using td.

CLI reference

td <command> [options]

Commands:

Command Purpose
td add <description> [flags] Add a task to ## Tasks or ## Backlog.
td list [mode] List tasks.
td done <id-or-text> Mark an active or backlog task as completed.
td promote <id-or-text> Move a backlog task to active tasks.
td demote <id-or-text> Move an active task to backlog.
td tui Open the interactive terminal UI.

td add

td add <description> [flags]

Flags:

Flag Values Meaning
-p, --priority high, medium, low, h, m, l Set priority.
-d, --due YYYY-MM-DD Set due date.
-r, --recur recurrence pattern Set recurrence.
-j, --project text Set #project/<slug>.
-t, --tag text Add a tag. Can be repeated.
-b, --backlog boolean Add to ## Backlog instead of ## Tasks.

Examples:

td add "Pay nursery"
td add "Pay nursery" -p high -d 2026-05-01 -r monthly:1 -j admin -t home
td add "Read Rust release notes" --backlog -t reading

Recurrence input

CLI --recur accepts compact recurrence patterns:

Input Stored as
daily 🔁 every day
weekly 🔁 every week
weekly:monday 🔁 every Monday
monthly 🔁 every month
monthly:1 🔁 every month on the 1st
monthly:28 🔁 every month on the 28th
yearly 🔁 every year
3-days 🔁 every 3 days
2-weeks 🔁 every 2 weeks
6-months 🔁 every 6 months
1-year 🔁 every 1 years

When a recurring task is completed:

  1. The current task is moved to ## Archive with ✅ YYYY-MM-DD.
  2. A new unchecked task is created in the original section.
  3. The new due date is computed from the previous due date when present, otherwise from today.
  4. The next due date is always strictly in the future.
  5. The new task receives a new stable ID.

td list

td list
td list --upcoming
td list --all
td list --backlog
td list --completed

Modes are mutually exclusive:

Mode Scope Filter
default ## Tasks Due today/past or no due date.
--upcoming ## Tasks Future due dates only.
--all ## Tasks All active tasks.
--backlog ## Backlog All backlog items.
--completed ## Archive All archived items.

Non-completed lists are sorted by priority descending, then due date descending. Completed tasks are sorted by completion date descending.

The output is a terminal table with priority, ID, description, and metadata. Set COLUMNS if you need deterministic table width in tests or scripts:

COLUMNS=100 td list --all

td done

td done <id-or-text>

The matcher first tries an ID prefix, then falls back to a case-insensitive substring search in task descriptions.

Examples:

td done a1b2
td done nursery

If several tasks match, td exits with an error and prints the matching IDs. Use a longer ID prefix or a more specific text query.

td done searches active tasks and backlog tasks. If the ID belongs to an archived task, it reports that the task is already archived.

Data safety

td is intentionally conservative when it would rewrite the todo file:

  • Every command takes an exclusive lock file under <home>/.config/td/locks/ for the full read-modify-write cycle.
  • td tui keeps the same lock for the whole interactive session.
  • Unknown Markdown sections or non-task content after the first known todo section are treated as unsupported content.
  • If unsupported content is present, td refuses to save instead of silently dropping it.

This means the file may contain frontmatter and introductory text before ## Tasks, but mutable content should stay inside the canonical task sections as task lines.

td promote

td promote <id-or-text>

Moves a task from ## Backlog to ## Tasks.

td list --backlog
td promote bbbb

td demote

td demote <id-or-text>

Moves a task from ## Tasks to ## Backlog.

td list --all
td demote aaaa

Terminal UI

Launch the TUI:

td tui

Tabs:

  • To-do: active actionable tasks.
  • Upcoming: active future tasks.
  • Backlog: deferred tasks.
  • Archive: completed tasks.

Keybindings:

Key Action
q, Esc Quit when no popup is open.
, Move selection.
Tab, Next tab.
Previous tab.
a Open add popup. Adds to backlog when the current tab is Backlog.
e Open edit popup for selected task.
d Mark selected task as done, except in Archive.
p Promote selected backlog task.
m Demote selected active/upcoming task.
k Move selected task up in its section.
j Move selected task down in its section.
o Open URLs found in the selected task description.

Popup keybindings:

Key Action
Esc Close popup without saving.
Enter Save add/edit form.
Tab Move to next field.
, Cycle priority when the priority field is selected.
Text input Insert text in text fields.
Backspace Delete last character in text fields.

The URL opener uses open on macOS and xdg-open on Linux. URL opening is not supported on Windows in the current implementation.

Automatic migrations

On load, td may rewrite the file to keep it canonical:

  • Tasks without <!-- tid:xxxxxxxx --> receive a generated ID.
  • Duplicate IDs are regenerated for later duplicates.
  • ## Later is treated as legacy backlog and rendered back as ## Backlog.
  • Checked tasks found in ## Tasks or ## Backlog are moved to ## Archive.
  • Archived tasks without ✅ YYYY-MM-DD receive today's completion date.

Writes are atomic: td writes to TODO.md.tmp, then renames it over the original file.

If td reports an unsupported-content rewrite refusal, move the reported Markdown lines outside the todo file or convert them to canonical task lines before retrying.

Agent skill

The repository includes the agent skill at:

skills/td/SKILL.md

The skill tells agents to use td for task mutations instead of editing the Markdown todo file directly. The skill assumes that the td binary is already available on PATH, so install the binary first with Nix, a GitHub Release download, or cargo install --path ..

Install the skill from the public GitHub repository with npx skills add:

npx --yes skills add https://github.com/arkan/td.git --skill td --agent claude-code -y

Install the skill from a local checkout:

npx --yes skills add . --skill td --agent claude-code -y

Install it globally instead of project-locally:

npx --yes skills add https://github.com/arkan/td.git --skill td --agent claude-code --global -y

Install it for every supported agent:

npx --yes skills add https://github.com/arkan/td.git --skill td --agent '*' -y

List the skills exposed by this repository without installing:

npx --yes skills add . --list

After installation, keep td on PATH and ensure TODO_FILE or ~/.config/td/config.toml points to the intended Markdown file.

Development

Useful commands:

cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-targets --all-features --locked
cargo run -- list

With a temporary todo file:

tmpdir="$(mktemp -d)"
cat > "${tmpdir}/TODO.md" <<'EOF'
# TODO

## Tasks

## Backlog

## Archive
EOF

TODO_FILE="${tmpdir}/TODO.md" cargo run -- add "Smoke task" -p high
TODO_FILE="${tmpdir}/TODO.md" cargo run -- list --all
TODO_FILE="${tmpdir}/TODO.md" cargo run -- done Smoke

Makefile shortcuts:

make build
make run
make install
make clean

Continuous integration

The CI workflow runs on pushes and pull requests to main on Ubuntu, macOS, and Windows:

  • cargo fmt --all -- --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --all-targets --all-features --locked

Releasing

  1. Update Cargo.toml version when needed.
  2. Run the local verification commands.
  3. Commit the release changes.
  4. Push main.
  5. Wait for CI to pass on main.
  6. Create and publish a GitHub Release whose tag matches Cargo.toml exactly (0.1.1v0.1.1).

Example:

version="v0.1.1"
cargo_version="$(cargo pkgid | sed 's/.*@//')"
test "${version}" = "v${cargo_version}"
git tag "${version}"
git push origin main "${version}"
gh run list --repo arkan/td --branch main --workflow CI --limit 1
gh release create "${version}" --verify-tag --title "${version}" --notes "Release ${version}"

The Release workflow starts when the GitHub Release is published. Before packaging, it verifies formatting, Clippy, tests, and that the release tag matches the Cargo package version. It then builds, smoke-tests, and uploads these assets:

Target Runner Asset
x86_64-unknown-linux-gnu ubuntu-22.04 td-<tag>-x86_64-unknown-linux-gnu.tar.gz
x86_64-apple-darwin macos-15-intel td-<tag>-x86_64-apple-darwin.tar.gz
aarch64-apple-darwin macos-15 td-<tag>-aarch64-apple-darwin.tar.gz
x86_64-pc-windows-msvc windows-2022 td-<tag>-x86_64-pc-windows-msvc.tar.gz

Each archive contains the binary, README.md, and LICENSE. Each archive also has a sibling .sha256 checksum asset.

Troubleshooting

Read errors

The configured todo file does not exist or is not readable. Check TODO_FILE, ~/.config/td/config.toml, and file permissions.

Invalid TOML config

The config file exists but is not valid TOML. Keep it minimal:

todo_file = "/absolute/path/to/TODO.md"

Invalid date

Dates must use YYYY-MM-DD.

Invalid recurrence

For CLI input, use compact recurrence patterns such as daily, weekly:monday, monthly:1, or 3-days.

Ambiguous ID or text

Your query matches multiple tasks. Run td list --all or td list --backlog, then use a longer ID prefix.

Unsupported content refusal

td detected Markdown that it cannot preserve safely during a rewrite. It refuses to save to avoid data loss. Move the reported lines outside TODO.md or convert them to canonical checkbox task lines under ## Tasks, ## Backlog, or ## Archive.

Release was created but no asset appears

Check the GitHub Actions Release workflow logs. The workflow only runs for published releases, not for draft releases that remain unpublished.

TUI does not render correctly

Use a modern terminal with Unicode support. The table and TUI use box drawing characters and emoji metadata.

About

Rust CLI/TUI task manager for Obsidian Markdown TODO files, with stable IDs, recurrence, backlog, and agent-friendly workflows.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages