One more na update
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:
gem install na
Run in iTerm
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.
Like or share this post Twitter.
* * *
BrettTerpstra.com is supported by readers like you. Click here if you'd like to help out.
Find Brett on Mastodon, Bluesky, GitHub, and everywhere else.