Your todos, in markdown, done fast.
A fast, single-binary CLI todo manager focused on developer experience. Features vim-style navigation, an interactive TUI, and scriptable commandsβall stored in plain markdown you can version control.
- β‘ Fast - Single binary (4MB), instant startup, 30-40x faster than alternatives
- π Markdown-native - Todos live in
todo.md, version control friendly - β¨οΈ Vim-style navigation -
j/k, relative jumps (5j), number keys - π₯οΈ Interactive TUI - Toggle, create, edit, delete, undo, move, copy
- π― Command Palette - Helix-style
:commands with fuzzy search - π Read-Only Mode - Prevent auto-save, check/uncheck all, filter done
- π§ Scriptable -
list,add,toggle,edit,deletecommands - π Smart Conflict Handling - Atomic saves, external-change detection, visual conflict diffs
- π Version History - Automatic snapshots with visual diffs and safe restore
- π Per-File Configuration - YAML frontmatter for file-specific settings
- π Recent Files - Jump to recently opened files with cursor position restoration
- π Cross-platform - macOS, Linux, Windows
brew install niklas-heer/tap/tdxcurl -fsSL https://niklas-heer.github.io/tdx/install.sh | bashDownload the latest binary for your platform from Releases:
tdx-darwin-arm64- macOS Apple Silicontdx-darwin-amd64- macOS Inteltdx-linux-amd64- Linux x64tdx-linux-arm64- Linux ARM64tdx-windows-amd64.exe- Windows x64
Requires Go 1.26.4+ and Mask:
git clone https://github.com/niklas-heer/tdx.git
cd tdx
mask build
mask install# Try it without installing
nix run github:niklas-heer/tdx
# Install to profile
nix profile install github:niklas-heer/tdxLaunch the interactive todo manager:
tdxKeyboard Shortcuts:
| Key | Action |
|---|---|
j / k |
Move down / up |
gg |
Go to first item |
G |
Go to last item |
Space / Enter |
Toggle completion |
n |
New todo after cursor |
N |
New todo at end of file |
e |
Edit todo |
d |
Delete todo |
c |
Copy to clipboard |
m |
Move mode |
Tab |
Indent (nest under previous) |
Shift+Tab |
Outdent (move up one level) |
/ |
Fuzzy search |
t |
Tag filter |
p |
Priority filter |
D |
Due date filter |
r |
Recent files |
: |
Command palette |
u |
Undo |
? |
Help menu |
Esc |
Quit |
Cmd+V / Ctrl+Y |
Paste (in edit mode) |
Command Palette (:):
Press : to open the command palette with fuzzy search. Available commands:
| Command | Description |
|---|---|
check-all |
Mark all todos as complete |
uncheck-all |
Mark all todos as incomplete |
sort-done |
Sort todos by completion (incomplete first) |
sort-priority |
Sort todos by priority (p1 first, then p2, etc.) |
sort-due |
Sort todos by due date (earliest first) |
filter-done |
Toggle showing/hiding completed todos |
filter-due |
Toggle showing only todos with due dates |
filter-overdue |
Toggle showing only overdue todos |
filter-today |
Toggle showing only todos due today |
filter-week |
Toggle showing only todos due this week |
clear-done |
Delete all completed todos |
read-only |
Toggle read-only mode (changes not saved) |
save |
Save current state to file |
force-save |
Force save even if file was modified externally |
reload |
Reload file from disk (discards unsaved changes) |
versions |
Browse, compare, and restore file version history |
wrap |
Toggle word wrap for long lines |
line-numbers |
Toggle relative line numbers |
set-max-visible |
Set max visible items for this session |
show-headings |
Toggle displaying markdown headings between tasks |
Read-Only Mode:
Start tdx with -r or --read-only flag for workflows where you don't want changes saved automatically:
tdx -r checklist.mdUse :save to manually save when ready, or :read-only to turn auto-save back on.
Vim-style navigation:
5j- Move down 5 lines3k- Move up 3 linesgg- Jump to first itemG- Jump to last item
Fuzzy Search:
Press / to enter search mode. Type to filter todos with live highlighting. Press Enter to select or Esc to cancel.
Nested Tasks:
Organize your todos hierarchically using Tab and Shift+Tab:
- [ ] Main project
- [ ] Subtask 1
- [ ] Subtask 2
- [ ] Sub-subtask
- [ ] Another task- Press
Tabto indent a task under its previous sibling - Press
Shift+Tabto outdent (move up one level) - Deleting a parent task promotes its children to the parent's level
- New tasks (
n) are created at the same nesting level as the cursor
Tags & Filtering:
Add hashtags to your todos for organization:
- [ ] Fix authentication #urgent #backend
- [ ] Update docs #docs
- [ ] Add dark mode #feature #frontendPress t to open tag filter mode:
- Navigate with
β/βorj/k - Toggle tags with
SpaceorEnter - Clear all filters with
c - Press
Escwhen done
Active tag filters are shown in the status bar. Todos are automatically filtered to show only matching items.
Priorities:
Add priority markers to your todos using !p1, !p2, !p3, etc.:
- [ ] Fix critical security bug !p1
- [ ] Update dependencies !p2
- [ ] Write documentation !p3
- [ ] Refactor code !p2
- [ ] Add nice-to-have featurePriority levels:
!p1- Critical/Urgent (displayed in red)!p2- High priority (displayed in orange)!p3- Medium priority (displayed in yellow)!p4+- Lower priorities (displayed dimmed)
Use the :sort-priority command to sort todos by priority (p1 first, then p2, etc.). Tasks without a priority marker are placed at the end. You can combine priorities with tags: Fix bug !p1 #backend #urgent
Priority Filtering:
Press p to open priority filter mode:
- Navigate with
β/βorj/k - Toggle priorities with
SpaceorEnter - Clear all filters with
c - Press
Escwhen done
Active priority filters are shown in the status bar (e.g., β‘ p1 p2). You can combine priority and tag filters to narrow down your view.
Due Dates:
Add due dates to your todos using @due(YYYY-MM-DD):
- [ ] Submit quarterly report @due(2025-12-01)
- [ ] Review pull request @due(2025-11-30) #code-review
- [ ] Fix critical bug !p1 @due(2025-11-29) #urgent
- [ ] Plan team meeting @due(2025-12-15)Due date display colors based on urgency:
- Overdue - Red (past the due date)
- Due today - Orange
- Due soon - Yellow (within 3 days)
- Future - Dimmed
Use the :sort-due command to sort todos by due date (earliest first). Tasks without a due date are placed at the end. You can combine due dates with priorities and tags.
Due Date Filtering:
Press D (capital D) to open due date filter mode:
- Overdue - Show only overdue tasks
- Today - Show tasks due today
- This Week - Show tasks due within 7 days
- Has Due Date - Show all tasks with any due date
Navigate with β/β or j/k, select with Space or Enter, clear with c, and press Esc when done.
Active due date filters are shown in the status bar (e.g., π
overdue). You can combine due date filters with priority and tag filters.
# List all todos
tdx list
# Add a new todo
tdx add "Buy milk"
# Toggle completion (1-based index)
tdx toggle 1
# Edit a todo
tdx edit 2 "Updated text"
# Delete a todo
tdx delete 3
# Open most recent file
tdx last
# Use custom file
tdx ~/notes/work.md list
tdx project.md add "Task"tdx automatically tracks recently opened files and restores your cursor position when you reopen them.
TUI Mode:
Press r in the TUI to open the recent files overlay:
- Type to filter files by path (fuzzy search)
- Navigate with
β/βorj/k - Press
Enterto open a file - Press
Escorrto close
CLI Commands:
# Open the most recently used file
tdx last
# List recently opened files (sorted by frequency and recency)
tdx recent
# Open a specific recent file by number
tdx recent 1
# Clear recent files history
tdx recent clearFeatures:
- Smart Sorting: Files are ranked by both frequency (how often you open them) and recency (when you last accessed them)
- Cursor Restoration: When you reopen a file, tdx automatically restores your cursor to the last position
- Change Detection: If the file content has changed since your last visit, the cursor resets to the first item for safety
- Configurable Limit: Set maximum recent files in your config (default: 20)
Configuration:
In ~/.config/tdx/config.toml:
[recent]
max_files = 20 # Maximum number of recent files to trackRecent files are stored in ~/.config/tdx/recent.json and include:
- File path
- Last access time
- Access count (frequency)
- Last cursor position
- Content hash (for change detection)
tdx automatically stores content-addressed snapshots when a file is opened or successfully changed. Open the command palette and run :versions to compare the current file with earlier versions and restore one safely.
- Navigate versions with
β/βorj/k - Scroll the diff with
PgUp/PgDn - Press
Enter, theny, to confirm a restore - Press
Escto close without changing the file
By default, tdx retains the latest 100 versions per file. Configure the limit in ~/.config/tdx/config.toml; set it to 0 for unlimited history:
[versioning]
max_versions = 100Todos are stored in todo.md using standard Markdown:
# Todos
- [x] Completed task
- [ ] Incomplete task
- [ ] Another taskOther Markdown content is preserved, including bare URLs, email addresses, links, inline HTML, emphasis, and code.
tdx supports three levels of configuration with the following priority:
Priority Order: CLI flags > Frontmatter > Global config > Defaults
Create ~/.config/tdx/config.toml (or $XDG_CONFIG_HOME/tdx/config.toml) to set defaults:
[theme]
name = "tokyo-night"
[display]
check_symbol = "β"
select_marker = "β"
[defaults]
file = "todo.md" # default file (use ~/path for central file)
max_visible = 0 # 0 = unlimited
word_wrap = true
show_headings = false
read_only = false
filter_done = false
[recent]
max_files = 20
[versioning]
max_versions = 100 # 0 = unlimitedYou only need to include the settings you want to change from the defaults.
Available options:
| Section | Option | Type | Default | Description |
|---|---|---|---|---|
[theme] |
name |
string | "tokyo-night" | Theme to use |
[display] |
check_symbol |
string | "β" | Symbol for completed items |
[display] |
select_marker |
string | "β" | Symbol for selected item |
[defaults] |
file |
string | "todo.md" | Default file path (use ~/path for central file) |
[defaults] |
max_visible |
number | 0 | Limit visible tasks (0 = unlimited) |
[defaults] |
word_wrap |
boolean | true | Enable word wrapping for long lines |
[defaults] |
show_headings |
boolean | false | Show markdown headings between tasks |
[defaults] |
read_only |
boolean | false | Prevent all edits (view-only mode) |
[defaults] |
filter_done |
boolean | false | Hide completed tasks by default |
[recent] |
max_files |
number | 20 | Maximum recent files to track |
[versioning] |
max_versions |
number | 100 | Versions retained per file (0 = unlimited) |
Add YAML frontmatter to customize behavior for specific files:
---
read-only: false
max-visible: 10
show-headings: true
---
# Todos
- [ ] Task oneExamples:
Read-only checklist:
---
read-only: true
---
# Shopping List
- [ ] MilkProject tracker with headings:
---
show-headings: true
max-visible: 15
filter-done: true
---
# Project Tasks
## Backend
- [ ] API endpoints
## Frontend
- [ ] UI componentsSettings are applied in this order (highest to lowest priority):
- CLI flags -
tdx -r --show-headings todo.md - Frontmatter - YAML at top of individual todo files
- Global config -
~/.config/tdx/config.toml - Defaults - Built-in defaults (word_wrap: true, others: false/0)
Example:
# config.toml sets word_wrap = false
# Frontmatter sets read-only: true
# CLI flag: --show-headings
# Result: word_wrap=false, read_only=true, show_headings=true
tdx --show-headings todo.mdtdx uses Goldmark (Go's industry-standard Markdown parser) with a custom serializer to provide robust, format-preserving todo management:
Why AST over regex?
- β‘ Performance - Parse once, manipulate efficiently in memory
- π― Precision - Surgical updates to specific nodes without side effects
- π Format Preservation - Maintains your markdown structure, spacing, and formatting
- π Reliability - Correctly handles edge cases (nested lists, code blocks, links, etc.)
- π·οΈ Rich Features - Enables advanced features like tag extraction, heading tracking
How it works:
Read File β Goldmark Parser β AST (in-memory tree)
β
Manipulate nodes
(toggle, add, delete, swap)
β
Custom Serializer β Write File
Implementation Details:
-
Parser (
internal/markdown/ast.go:29)- Uses Goldmark with tables, strikethrough, and task-list extensions
- Keeps presentation-only linkification disabled so bare URLs remain exact source text
- Parses markdown into an Abstract Syntax Tree
- Each todo becomes a
TaskCheckBoxnode within aListItem - Preserves source bytes with segment pointers for text nodes
-
AST Operations (
internal/markdown/ast.go)ExtractTodos()- Walk AST and collect all task list itemsExtractHeadings()- Find headings and their positions relative to todosToggleTodo()- Flip checkbox state in the ASTUpdateTodoText()- Replace editable inline content while preserving list structureDeleteTodo()- Remove list item node from parentAddTodo()- Create new list item with checkbox and text nodesSwapTodos()- Reorder list items (handles adjacent and cross-section swaps)
-
Custom Serializer (
internal/markdown/serializer.go:12)- Recursively walks the modified AST
- Reconstructs markdown with proper formatting
- Built custom because Goldmark's renderer had formatting issues
- Handles: headings, lists, checkboxes, code blocks, links, emphasis, strikethrough, etc.
- Preserves spacing and blank lines
Key Benefits:
- β Non-destructive - Your markdown formatting, comments, and structure stay intact
- β Complex markdown - Handles nested lists, code blocks, links, emphasis seamlessly
- β Durable writes - Atomic replace with flushes and conflict detection
- β Predictable - AST guarantees correct parsing and serialization
- β Tag support - HashtagExtraction built into AST traversal
- β Heading-aware - Knows which todos belong under which headings
tdx is built for speed with several key optimizations:
- Search debouncing - Search operations are debounced (50ms) to avoid scoring all todos on every keystroke
- Heading caching - Heading positions are cached and only recomputed when todos change
- Zero-allocation navigation - Finding next/previous visible items allocates no memory (~8ns)
- Unified input handling - TUI and piped input share the same handlers, reducing code and bugs
Benchmark results (Apple M4):
FuzzyScore (exact match): 5.6ns/op 0 allocs
FuzzyScore (fuzzy match): 33.4ns/op 0 allocs
Cached headings: 1.0ns/op 0 allocs
Search 100 todos: 9.8Β΅s/op 114 allocs
Navigation (visible todo): 8.0ns/op 0 allocs
tdx/
βββ .dagger/ # Portable CI and release pipeline (Go)
βββ maskfile.md # Documented development tasks
βββ cmd/tdx/ # Main application
β βββ main.go # Entry point, CLI routing
β βββ config.go # Build-time configuration
β βββ userconfig.go # User configuration (themes, settings)
β βββ *_test.go # Comprehensive test suite
βββ internal/
β βββ markdown/ # AST-based markdown engine
β β βββ parser.go # Markdown β AST
β β βββ ast.go # AST data structures
β β βββ serializer.go # AST β Markdown
β βββ tui/ # Terminal UI (Bubble Tea)
β β βββ model.go # Application state
β β βββ update.go # Event handling
β β βββ view.go # Rendering
β β βββ commands.go # Command palette
β β βββ render.go # Display logic
β β βββ *_test.go # Unit & benchmark tests
β βββ cmd/ # CLI command handlers
β β βββ cli.go # List, add, toggle, etc.
β βββ config/ # Configuration handling
β β βββ config.go # Legacy YAML config (deprecated)
β β βββ recent.go # Recent files tracking
β βββ util/ # Utilities
β βββ text.go # Text processing, fuzzy search
β βββ clipboard.go # Clipboard operations
β βββ text_test.go # Unit & benchmark tests
βββ scripts/ # Development & release tools
- Go 1.26.4+
- Mask (command runner)
- Dagger 0.21.7 and a Docker-compatible container runtime for the portable CI pipeline
# Build binary
mask build
# Build for all platforms
mask build-all
# Install to /usr/local/bin
mask installmask build # Build binary
mask build-all # Build all release targets with Dagger
mask install # Install to PATH
mask tui # Run TUI
mask list # List todos
mask add "X" # Add todo
mask toggle 1 # Toggle todo
mask check # Run go vet
mask fmt # Format code
mask ci # Run the same portable checks used by GitHub CI
mask ci-lint # Run the pinned linter through Dagger
mask ci-test # Run race-enabled tests through Dagger
mask ci-workflows # Validate GitHub workflow syntax locally
mask clean # Clean artifactsThe Dagger pipeline is pinned in dagger.json and implements CI in Go. GitHub still runs native macOS and Windows filesystem tests because those platform semantics cannot be reproduced by Linux containers.
Press : to open the command palette and select theme to open the theme picker:
- Navigate with
β/βorj/kto preview themes in real-time - Press
Enterto apply and save the theme - Press
Escto cancel and restore the previous theme
The selected theme is automatically saved to your config file.
Set your theme in ~/.config/tdx/config.toml:
[theme]
name = "tokyo-night" # or any builtin/custom themeSee the Global Configuration section for all available settings.
tokyo-night(default)catppuccin-latte(light)catppuccin-frappecatppuccin-macchiatocatppuccin-mochadraculagithub-darkgruvbox-darkmonokainordone-darkrose-pinesolarized-dark
Create your own themes by adding .toml files to ~/.config/tdx/themes/:
# ~/.config/tdx/themes/my-theme.toml
[theme]
name = "my-theme"
author = "Your Name"
[colors]
# Core colors
Base = "#c0caf5" # default foreground
Dim = "#565f89" # muted text
Accent = "#7aa2f7" # highlights, selections
Success = "#9ece6a" # completed items, matches
Warning = "#e0af68" # move mode
Important = "#bb9af7" # checked items
AlertError = "#f7768e" # errors
# Tags (hashtags like #urgent)
Tag = "#e0af68"
# Priorities (!p1, !p2, !p3+)
PriorityHigh = "#f7768e" # !p1 - critical
PriorityMedium = "#bb9af7" # !p2 - high
PriorityLow = "#565f89" # !p3+ - medium/low
# Due dates (@due(YYYY-MM-DD))
DueUrgent = "#7dcfff" # overdue or due today
DueSoon = "#7aa2f7" # due within 3 days
DueFuture = "#565f89" # due laterCustom themes appear in the theme picker alongside builtin themes. You can also override builtin themes by creating a file with the same theme name.
Note: The Tag, Priority*, and Due* colors are optional. If omitted, sensible defaults are used based on the core colors.
tdx ~/notes/work.md # Use specific file
tdx project.md add "Task" # All commands workBuild metadata in tdx.toml:
version = "0.6.0"
description = "your todos, in markdown, done fast"MIT - see LICENSE
