A PowerShell port of the todo.txt CLI
(todo.sh). It implements the same actions, options, configuration and on-disk
todo.txt format, and is validated for
byte-for-byte output and file parity against the original todo.sh.
Where it pays off, the implementation leans on .NET BCL types
(System.IO.File, System.Text.StringBuilder,
System.Collections.Generic.List<T>, System.Text.RegularExpressions.Regex)
instead of slower pure-PowerShell idioms.
- Cross-platform: PowerShell 7+ on Linux, macOS and Windows.
- No external dependencies at runtime.
- Optional extras: in-progress tracking, lifecycle date tags, and git syncing.
- A full Pester 5 test suite (130 tests).
- PowerShell 7.0+ (
pwsh) - Pester 5+ — for running the tests only.
The interactive installer fetches the project, creates your todo directory,
writes a config file, and (optionally) registers a todo command in your
PowerShell profile:
# Interactive
iwr https://raw.githubusercontent.com/citizen-123/todo.txt-powershell/main/install.ps1 | iex
# Non-interactive (accept defaults). A piped script can't take parameters, so
# set the env var first:
$env:TODO_INSTALL_DEFAULT = '1'; iwr <url> | iex
# Or pass flags via a script block:
& ([scriptblock]::Create((iwr <url>).Content)) -DefaultLocal checkout: ./install.ps1 (interactive) or ./install.ps1 -Default.
Useful flags: -InstallDir, -TodoDir, -Force. The installer prompts for git
tracking, in-progress tracking, date tags, the profile alias, and tab
completion; -Default skips the prompts (git/in-progress/date-tags off, alias
and completion registered).
The installer can register a tab-completer that completes action names, and
+project / @context tokens drawn from your live todo.txt. To enable it
manually, add this to your PowerShell profile:
Import-Module /path/to/todo.txt-powershell/src/TodoTxt.psd1
Register-TodoArgumentCompleter -CommandName todoThe completed command (todo) must expose its arguments via a
ValueFromRemainingArguments parameter named TodoArgs (the installer's
generated function does this); Invoke-Todo is also completed automatically.
# Run directly via the wrapper script:
./todo.ps1 add "buy milk +groceries @store"
./todo.ps1 add "(A) call mom @phone"
./todo.ps1 ls
./todo.ps1 do 1
./todo.ps1 ls
# Or import the module and use the cmdlet:
Import-Module ./src/TodoTxt.psd1
Invoke-Todo -Arguments 'add', 'write the report +work'
Invoke-Todo -Arguments 'ls'By default tasks live in ~/.todo/todo.txt (override with TODO_DIR, a config
file, or the -d option). Completed tasks are archived to done.txt.
# PowerShell profile
Set-Alias todo (Resolve-Path ./todo.ps1)
# or on Linux/macOS, symlink it onto your PATH:
# ln -s "$PWD/todo.ps1" ~/.local/bin/todoadd|a "THING I NEED TO DO +project @context"
agenda|due [N] [TERM...]
addm "MULTIPLE\nTASKS"
addto DEST "TEXT TO ADD"
append|app NR "TEXT TO APPEND"
archive
command [ACTIONS]
deduplicate
del|rm NR [TERM]
depri|dp NR [NR ...]
done|do NR [NR ...]
help [ACTION...]
list|ls [TERM...]
listall|lsa [TERM...]
listaddons
listcon|lsc [TERM...]
listfile|lf [SRC [TERM...]]
listpri|lsp [PRIORITIES] [TERM...]
listproj|lsprj [TERM...]
move|mv NR DEST [SRC]
prepend|prep NR "TEXT TO PREPEND"
pri|p NR PRIORITY [NR PRIORITY ...]
replace NR "UPDATED TODO"
report
shorthelp
start|ip NR [NR ...] (requires TODOTXT_IN_PROGRESS)
| Option | Meaning |
|---|---|
-@ / -@@ |
Hide / show context names in list output |
-+ / -++ |
Hide / show project names in list output |
-c |
Color mode |
-p |
Plain mode (no color) |
-P / -PP |
Hide / show priority labels in list output |
-d CONFIG_FILE |
Use an alternate configuration file |
-f |
Force (no confirmation / interactive input) |
-h |
Short help (same as shorthelp) |
-a / -A |
Disable / enable auto-archive on completion |
-n / -N |
Don't preserve / preserve line numbers on deletion |
-t / -T |
Enable / disable prepending the creation date on add |
-v / -vv |
Verbose / extra-verbose |
-V |
Version |
-x |
Disable the final filter |
Run ./todo.ps1 help for the full reference.
Configuration is layered, lowest to highest precedence:
- Built-in defaults
- Environment variables (
TODO_DIR,TODOTXT_*, …) - A configuration file
- Command-line options
Configuration files use the same export VAR=value syntax as the original
todo.cfg (including $VAR expansion and the standard ANSI color names), so
existing configurations are largely compatible. See
todo.cfg.example.
Config file search order (first existing one wins, unless -d /
TODOTXT_CFG_FILE is given):
$HOME/.todo/config
$HOME/todo.cfg
$HOME/.todo.cfg
${XDG_CONFIG_HOME:-$HOME/.config}/todo/config
TODO_DIR, TODO_FILE, DONE_FILE, REPORT_FILE, TODO_ACTIONS_DIR,
TODOTXT_CFG_FILE, TODOTXT_AUTO_ARCHIVE, TODOTXT_DATE_ON_ADD,
TODOTXT_PRIORITY_ON_ADD, TODOTXT_PRESERVE_LINE_NUMBERS, TODOTXT_PLAIN,
TODOTXT_FORCE, TODOTXT_VERBOSE, TODOTXT_DISABLE_FILTER,
TODOTXT_DEFAULT_ACTION, TODOTXT_SOURCEVAR, TODOTXT_SIGIL_BEFORE_PATTERN,
TODOTXT_SIGIL_VALID_PATTERN, TODOTXT_SIGIL_AFTER_PATTERN,
TODOTXT_DATE_TAGS, TODOTXT_IN_PROGRESS, TODOTXT_RECURRENCE,
TODOTXT_HIDE_FUTURE_TASKS, TODOTXT_GIT, TODOTXT_GIT_REMOTE.
These build on the standard todo.txt key:value tag conventions:
-
due:YYYY-MM-DD— a due date. Theagendaaction (aliasdue) lists undone tasks that have adue:tag, sorted by date:todo agenda # tasks due today or overdue todo agenda 7 # add tasks due within the next 7 days todo agenda 7 @work
-
t:YYYY-MM-DD— a threshold ("hide until") date. SetTODOTXT_HIDE_FUTURE_TASKS=1to drop tasks whoset:date is still in the future fromls/lsa/listpri(done tasks are never hidden). Off by default. -
rec:<n><d|w|m|y>— recurrence. WithTODOTXT_RECURRENCE=1, completing arec:-tagged task spawns its next occurrence withdue:advanced by the interval (andt:shifted to preserve its lead time). A leading+(rec:+1m) is strict — it advances from the task's owndue:date rather than from today. Off by default.todo add "(B) pay rent rec:+1m due:2026-06-01" todo do 1 # completes it AND adds: (B) pay rent rec:+1m due:2026-07-01A malformed
rec:interval is reported and the task is completed without respawning.
Two optional, independent extensions (both off by default):
-
TODOTXT_DATE_TAGS=1appends akey:valuetag to tasks:added:<date>onadd, andcompleted:<date>ondo. -
TODOTXT_IN_PROGRESS=1enables thestart(aliasip) action, which marks a task in-progress by prefixing anistatus marker (analogous to thexdone marker) and appendingstarted:<date>. The task's priority is preserved:todo add "(A) write the report" # (A) write the report todo start 1 # i (A) write the report started:2026-06-02 todo do 1 # x 2026-06-02 write the report started:... completed:...A leading
x/istatus marker is transparent to priority, sorting and coloring, so an in-progress(A)task still sorts among its priority peers andpri/deprikeep working.archivemoves only completed (x) tasks;itasks stay put. In-progress tasks get their own color (COLOR_INPROGRESS).
Set TODOTXT_GIT=1 to version your todo directory automatically. After any
file-changing action the CLI runs git add/git commit in TODO_DIR, and
git push when TODOTXT_GIT_REMOTE is set. The repository is auto-initialized
(and the remote added) on first use. Read-only actions (ls, listall, …) never
commit. If git is missing or a git step fails, the command still succeeds but a
warning is printed and the exit code is 1 — your tasks are never lost to a git
error.
Add-ons live in the actions directory (default $TODO_DIR/actions, or
$TODO_ACTIONS_DIR) and mirror todo.sh: a script whose name matches an action
overrides the built-in of the same name; any other name adds a brand-new
command. PowerShell (.ps1) and native executables are supported, and receive
the standard TODO_DIR, TODO_FILE, DONE_FILE, REPORT_FILE and TODO_SH
environment variables (restored afterwards, so add-ons never pollute your
session). TODO_SH points at the real wrapper, so an add-on can re-invoke a
built-in:
# actions/hello.ps1 — a new command
param() "hi, you have $((Get-Content $env:TODO_FILE).Count) tasks"
# actions/do.ps1 — override `do`, then call the built-in via the escape hatch
param() & $env:TODO_SH command do @argsUse command <action> to force the built-in even when an override exists.
- Search
TERMs use .NET regular expressions (case-insensitive) rather than POSIXgrepBRE. del NR TERMmatches theTERMliterally (sodel 1 +projectworks), matching the practical behaviour of the original's BRE.- The default
TODO_DIRis~/.todo; the script runs without a config file. TODOTXT_SORT_COMMAND/TODOTXT_FINAL_FILTER(arbitrary shell pipelines) are not supported; list sorting is implemented natively (ordinal, case-insensitive, matchingLC_COLLATE=C sort -f -k2).- In-progress (
i) tasks are sorted by their underlying priority/text (the marker is skipped in the sort key), so an in-progress(A)task still sorts among its priority peers. Completed (x) tasks still cluster last, exactly as in todo.sh. - Adds the
start/ipaction, theadded:/started:/completed:date tags, and git tracking — all opt-in (see above) and absent from upstreamtodo.sh.
todo.ps1 # CLI entry point (thin wrapper)
install.ps1 # interactive installer (iwr | iex)
src/TodoTxt.psd1 # module manifest
src/TodoTxt.psm1 # implementation
tests/TodoTxt.Tests.ps1 # Pester suite
tests/Install.Tests.ps1 # installer helper tests
tests/Invoke-Tests.ps1 # test runner (-CI adds NUnit + coverage)
tests/Invoke-Lint.ps1 # PSScriptAnalyzer runner
PSScriptAnalyzerSettings.psd1 # lint configuration
todo.cfg.example # sample configuration
CHANGELOG.md # release notes
# Install Pester if needed:
Install-Module Pester -Scope CurrentUser -Force -SkipPublisherCheck
# Run the suite:
pwsh -File tests/Invoke-Tests.ps1
# CI mode (also writes tests/testresults.xml + JaCoCo tests/coverage.xml and
# prints a coverage percentage):
pwsh -File tests/Invoke-Tests.ps1 -CI
# Lint (installs PSScriptAnalyzer on first run with -Install):
pwsh -File tests/Invoke-Lint.ps1 -InstallMIT — see LICENSE.