This week I pushed a set of focused improvements to NA that make interactive workflows a lot smoother and the codebase a little more robust — including first‑class time tracking.
If you run na update without arguments you’ll now get an interactive menu that helps you pick the file(s) and actions to operate on, and I’ve added a flexible plugin architecture and time tracking features.
TL;DR
na update (no args) now launches an interactive, consistent selection flow for files and actions.
- The update submenu supports multi-select, edit, move, and direct action modes more reliably.
- Fixed many edge cases: nil-safe string helpers, clearer YARD docs, and better tests for helper code.
- Time tracking
- Plugin architecture
- Under-the-hood improvements to file selection, action movement, and tagging logic.
New: Time tracking
- Add start/finish times right from the CLI:
--started TIME on add, complete/finish, and update
--end TIME (alias --finished) on add, complete/finish, and update
--duration DURATION to backfill a start time from the finish
- Natural language and shorthand supported everywhere:
30m ago, -2h, 2h30m, 2:30 ago, yesterday 5pm
- Durations aren’t stored; they’re computed from
@started(...) and @done(...) when displayed.
- Display enhancements in
next/tagged:
--times shows per‑action durations and a grand total (implies --done)
--human switches durations to friendly text
--only_timed filters to actions with both @started and @done (implies --times --done)
--only_times outputs only the totals (no action list; implies --times --done)
--json_times emits a JSON object with timed actions, per‑tag totals, and overall totals (implies --times --done)
- Per‑tag totals are shown as a Markdown table with aligned columns and a footer row for the grand total
- Duration color is theme‑configurable via a
duration key (default {y})
Example:
na add --started "30 minutes ago" "Investigate bug"
na complete --finished now --duration 2h30m "Investigate bug"
na next --times --human
na next --only_times
na tagged bug --json_times | jq
New: Plugin architecture
You can now extend NA with small scripts placed in ~/.local/share/na/plugins. Each plugin is a script with a shebang; NA feeds it actions on STDIN and reads transformed actions from STDOUT.
- Run:
na plugin run NAME
- Shortcut:
na plugin NAME (defaults to run, but doesn’t accept run‑flags)
- From update:
na update --plugin NAME (also appears in the interactive menu)
- From display:
na next --plugin NAME (STDOUT‑only transform; no file writes)
- IO formats:
json, yaml, csv, text (--input/--output/--divider supported)
- Plugins may modify
text, note, tags, and parents; NA applies moves if parents change
- Optional ACTIONs supported in plugin output:
UPDATE (default), DELETE, COMPLETE/FINISH, RESTORE/UNFINISH, ARCHIVE, ADD_TAG, DELETE_TAG/REMOVE_TAG, MOVE
Plugin subcommands
na plugin new NAME (alias n): Create a plugin in ~/.local/share/na/plugins. If no extension is provided, NA prompts for a language (or pass --language rb|py|/usr/bin/env bash). Shebang and extension are inferred.
na plugin edit NAME: Open a plugin in your system editor. If no name is given, you’ll be prompted to pick from available plugins (enabled and disabled).
na plugin run NAME (alias x): Execute a plugin against selected actions. With no arguments, NA prompts for the plugin and actions. Supports --input, --output, --divider, plus usual filters like --file, --depth, --search, --tagged, --done.
na plugin enable NAME (alias e): Move a plugin from plugins_disabled into plugins.
na plugin disable NAME (alias d): Move a plugin from plugins into plugins_disabled.
na plugin ls (alias list): List plugins. Use --type enabled|disabled (or e|d) to output plain names only.
Quick examples:
# Transform a filtered list without writing files
na tagged bug --plugin AddFoo --output text
# Modify selected actions in place
na update --plugin AddFoo --input json --output json
On first run NA creates the plugins folder with a README and two sample plugins (Add Foo.py, Add Bar.sh) you can use as starting points.
Interactive na update flow
Run na update with no arguments and you’ll see a consistent selection flow:
- Choose one or more todo files (fuzzy search / fzf / gum used when available).
- Pick which actions to update (multi-selection supported).
- Choose an operation: edit, move, add tag, remove tag, mark done, delete.
Examples:
$ na update
Select files: (interactive list)
Select actions (multi):
[x] 23 % Inbox/Work : - Fix X
[x] 45 % Inbox/Personal : - Call Y
Choose operation: (edit / move / done / delete / tag)
The menu now behaves consistently whether you pick a single file or multiple files; if you choose multi-select the update command applies as you’d expect to the set of chosen actions.
There’s a direct action mode when you know the file and action: na update PATH -l 23 still works as before. The interactive flow only kicks in when no explicit target is provided.
Notable fixes
A lot of the work was small but important:
- Nil-safe string helpers:
trunc_middle, highlight_filename, and friends were guarded against nil inputs so tests and UI code don’t explode when a file is missing or the database contains a stray blank line.
- Action move/edit correctness: moving an action to a different project now updates parent indexes and project line numbers properly, avoiding off-by-one bugs that could leave the file in a strange state.
select_file and fuzzy matching: the fuzzy and database-driven file selection was made more robust — the code handles directories that have a file.na or file/file.na pattern and falls back to a clear error instead of failing silently.
- YARD docs: cleaned up a number of
@!method directives and added top-level @example blocks for the main classes and helpers so the docs are friendlier and generate without warnings.
- Tests: added and fixed unit tests for
Array, Hash, and String helpers. TTY screen and color-related test stubs were improved for reliability on CI.
- New tests for time features: JSON output, totals‑only output, timed‑only filtering.
Try it
You can update to the latest version with:
That should give you v1.2.85 or higher.
If you’re on a recent development build or want to try the updates locally:
# From the gem checkout
bundle exec bin/na update
# Or after building the gem and installing
na update
If you run into anything odd, please open an issue with the command you ran and a short description of what you expected vs what happened. Small, reproducible steps are the fastest way to a fix.
If you hit an error and want to include a backtrace, run the command with debug enabled and paste the output:
GLI_DEBUG=true na [COMMAND]
Other updates
I last wrote about 1.2.80. Here are a changes since then:
CHANGED
- Default theme/templates include
%line; users with custom theme may need to regenerate to see line numbers
- Update command interactive menu lists available plugins
- README: add detailed Plugins section with examples and schema
- CHANGELOG: add 1.2.88 entry for plugins feature
- Completed plugin subcommand sections with aliases and context
- Plugins section updated with management workflow and notes
- Sample plugins can be regenerated with –generate-examples when deleted
- Shortcut
na plugin NAME works without run command flags (use na plugin run NAME for flags)
NEW
- Display line numbers with actions across
na next and selection
- Support
PATH:LINE targeting in na update and na edit
- Multi-action editing in external editor using
# ------ PATH:LINE markers; notes supported beneath each action
- Add –started flag on add/complete(update) to set @started
- Add –finished alias to –end on add/complete/update to set @done
- Add –duration (add/complete/update); backfills @started from –end
- Add –times (next/tagged) to show per-action durations and totals
- Add –human (next/tagged) for human-friendly duration format
- Add –only_timed (next/tagged) to show only items with @started/@done
- Add –json_times to next/tagged (JSON of timed actions, tags, total)
- Add –only_times to next/tagged (show only totals, no action list)
- Plugin system with scripts in ~/.local/share/na/plugins
- Na plugin command to run plugins on selected actions
- –plugin/–input/–output/–divider flags on update/next/tagged/find
- Plugin IO supports json, yaml, csv, and text formats
- ACTION support in plugin IO (UPDATE/DELETE/COMPLETE/RESTORE/ARCHIVE/ADD_TAG/DELETE_TAG/MOVE)
- Auto-create plugins dir with README and sample plugins (Add Foo.py, Add Bar.sh)
- Display commands can pipe through plugins (STDOUT-only, no writes)
- Add –json_times to next/tagged (JSON of timed actions, tags, total)
- Add –only_times to next/tagged (show only totals, no action list)
- Added plugin enable/disable and flow tests (update/move behaviors)
- Document plugin subcommands (new/edit/run/enable/disable)
--generate-examples flag regenerates sample plugins and README
na plugin NAME shortcut defaults to na plugin run NAME
IMPROVED
- –times and –only_timed imply –done for next/tagged
- Duration annotations render with theme colors in output
- Natural-language dates for @started/@done normalized automatically
- Support shorthand: 2h30m, 30m ago, -2:30, 2:05 ago
- –only_timed, –times, and –json_times imply –done automatically
- Per-tag duration totals rendered as aligned Markdown table with footer
- Duration color configurable via theme key
duration (default {y})
- Duration/time docs and output clarifications in README
- Internal plugin README with metadata, IO, and examples
- –only_timed, –times, and –json_times imply –done automatically
- Per-tag duration totals rendered as aligned Markdown table with footer
- Duration color configurable via theme key
duration (default {y})
na plugin run prompts for plugin and action selection when no filters
- README: brief descriptions for
plugin subcommands
- README plugin command docs with brief descriptions
- Sample plugins only generated once (tracked via .samples_generated flag)
FIXED
- Incorrect colorization (unexpected bright green) in action output
- Search highlighting no longer corrupts ANSI color codes or numbers in escape sequences
- Multi-action editor now only includes the specifically selected lines (no duplicates)
- Delete in update menu removes the correct lines
- Multi-select updates process bottom-to-top to avoid line shifts
- String wrapping now wraps at requested widths (e.g., 60 cols) and indents
- Plugin update path writing duplicate actions; now in-place by line
- Plugin apply finds action via target_line, avoids Symbol->Integer error
- String#wrap indentation and width handling for multi-line wrapping
- Lint/DuplicateBranch in plugin parse_actions
- Style/MapToHash in apply_plugin_result
- String wrapping now wraps at requested widths (e.g., 60 cols) and indents
- Plugin-applied updates now modify in place (no duplicate actions)
- Suppressed Y/N prompts during plugin operations and non-interactive runs
--only_timed filtering (case-insensitive tag keys) in output
- Stabilized wrapping by removing test override of String#wrap
Thanks for playing with it and for the helpful feedback you’ve been sending. Check out the NA project page for more info.