@zerodawn/td
v0.5.0
Published
Minimal todo capture + view with @tags and per-window filter
Maintainers
Readme
@zerodawn/td
Minimal todo CLI with @tags and per-tmux-window filter.
Install
npm install -g @zerodawn/tdFor zsh completion:
mkdir -p ~/.local/share/zsh/completions
ln -sf "$(npm root -g)/@zerodawn/td/_td" ~/.local/share/zsh/completions/_tdFirst run
On first use, td asks where your todos should live.
It stores that location and reuses it later.
No default suggestion is imposed; you choose the path yourself.
Filter model
Default filter is all. Filters are per-tmux-window.
- plain
td ls/td viewshows everything inall td filter @tagfocuses one tagtd filter untaggedshows only items without tagstd filter clearreturns toall
Active / inactive (per list)
Within a list filter (@work, @private, @backlog, untagged, or any
@tag), items split into two ordered sets:
- active — your working set; what you see by default
- inactive — voorraad; only shown on request
A list keeps independent active/inactive state, so the same item with
@work @backlog can be active in @work and inactive in @backlog.
Until you reorder or activate within a list, all matching items render as
active (legacy behaviour). The first td activate/deactivate/up/... in
a list materializes a per-list entry in todos.order.json. From then on,
plain captures (td, td w, td p, td b) land in inactive of that
list; use the wa/pa/ba/a shortcuts to capture-and-activate in one
step.
In all, active/inactive does not apply.
Commands
# capture
td "text @tags" capture new item (lands in inactive once the list has state)
td w text capture with @work
td p text capture with @private
td b text capture with @backlog
td wa text capture with @work and activate
td pa text capture with @private and activate
td ba text capture with @backlog and activate
td a text capture untagged and activate
# view
td ls list active items in current filter
td ls active same, explicit
td ls inactive list inactive items in current filter
td ls --all active, blank line, inactive (continuous numbering)
td view sidebar-format render (active only)
# state (current list filter; not allowed in 'all')
td activate <id|nr> move to bottom of active
td deactivate <id|nr> move to top of inactive
td a <nr> shorthand for 'activate <nr>' (numeric-only arg)
# reorder within active (current list filter; not allowed in 'all')
td up <id|nr> 1 step up
td down <id|nr> 1 step down
td top <id|nr> to top of active
td bottom <id|nr> to bottom of active
td mv <id|nr> <pos> to 1-based position in active
# edit / remove
td rm <id|nr> delete item by timestamp prefix or list number
td untag <id|nr> <tag> remove one tag from an item
# filter
td filter @tag set filter for current tmux window
td filter show current window filter
td filter clear reset current window filter to all
td filter all show all items
td filter untagged show only untagged items
# location
td location show current todo location
td location set /path set todo location and create it if needed
td location migrate /path move todos.md (and sidecar) to a new location
td help show this summaryNumeric positions in reorder/state commands refer to the combined
ls --all view (active first, then inactive). Reorder commands (up,
down, top, bottom, mv) error if the target is not in active.
Examples
td location set /data/notes/todos
td "review pipeline" # untagged, inactive after first activate
td "fix worktree @pi @urgent"
td wa urgent prod bug # @work + active
td w boring chore # @work, inactive
td filter @work
td ls # urgent prod bug
td ls --all
# 1 14:03 urgent prod bug @work
#
# 2 14:05 boring chore @work
td activate 2 # promote boring chore to active
td up 2 # reorder within active
td deactivate 1 # demote urgent prod bug to inactive
td rm 1 # remove by list number (cleans sidecar too)
td untag 1 @work # also drops id from @work sidecar
td filter clear
td location migrate /data/archive/todos # moves todos.md + todos.order.jsonPer-window filter
td filter ... writes to /tmp/td/window-filters/<window-id>.
Ephemeral — resets on reboot (intentional; windows are also ephemeral).
td view and td ls automatically read the filter for the current window.
The tmux-sidebar script calls td view from within the sidebar pane,
which is in the same window as the active context.
File format
One item per line in todos.md:
- 2026-05-20T13:42:17 fix gitflow worktree @pi @urgent
- 2026-05-20T14:01:55 review pipelineSafe to edit in vim. Do not change the - TIMESTAMP prefix format or td rm
will not find the item.
Ordering and active/inactive state live in a sidecar todos.order.json next
to todos.md:
{
"@work": {
"active": ["2026-05-20T14:03:11"],
"inactive": ["2026-05-20T14:05:02"]
},
"@private": { "active": [], "inactive": [] },
"untagged": { "active": [], "inactive": [] }
}Safe to delete: removing the sidecar reverts every list to legacy "all-as-active in capture-order". Stale ids referring to removed items are ignored on render and pruned on next write.
Removing items and tags
td rm and td untag accept either:
- a list number from the current filter view
- a full timestamp
- a unique timestamp prefix
Numeric positions are filter-aware: in a list filter (@work,
@private, @backlog, untagged, or any @tag), the number refers to the
ls --all view of that filter (active first, then inactive). In all,
the number refers to capture-order across todos.md.
Examples:
td filter @work
td ls --all
# 1 14:01 fix auth
# 2 14:03 ship release
#
# 3 14:05 refactor
td rm 3 # removes "refactor" (3rd in @work view)
td untag 1 @work # drops @work tag from "fix auth"
td filter all
td rm 2026-05-20T14:05 # by timestamp prefix — works in any filterConfig
Saved in:
$TD_CONFIG_FILE, or$XDG_CONFIG_HOME/td/config.json, or~/.config/td/config.json
Example:
{
"todosDir": "/data/notes/todos"
}Local development
npm install
npm run smoke
npm publish --dry-runPublishing
npm login
npm publish --access publicKnown limitations
- IDs are second-based ISO timestamps. If multiple captures happen in the same second,
tdwaits until the next second to keep IDs unique. td location migraterefuses to overwrite a non-empty targettodos.md.
