@gamaraan/todos-tool
v0.2.3
Published
pi coding-agent extension: an Oh My Pi (OMP)-style phased todo tool with reminders, /todo configuration, HUD, and optional desktop notification requests.
Maintainers
Readme
@gamaraan/todos-tool — OMP-style todos for pi
A pi coding-agent
extension that ports the Oh My Pi (OMP) todo tool, tracker, and /todo
command to pi as a self-contained extension package: a phased todo list the
agent plans and updates itself, with completion reminders, an eager first-turn
prelude, a /todo slash command, a persistent HUD widget, and optional
best-effort desktop notification requests.
Todo 2/5 done
I. Foundation 1/2
✓ Scaffold crate
○ Wire workspace
II. Auth 1/3
○ Port credential storeFeatures
The todo tool (for the agent): init | start | done | rm | drop | block |
unblock | append | view.
- Phased lists (
initwithlist: [{phase, items}]or a flatteneditems: [...]), tasks referenced by exact content, never IDs. - The earliest still-open task auto-promotes to
in_progressafter every completion; out-of-order completion is handled (completed tasks never revert). block/unblockfor work waiting on external input — blocked tasks are excluded from stop-time reminders and carry an optionalreasonthat survives the Markdown round-trip.- Strict batch semantics: a failing batch is discarded wholesale, so a model retry never hits "already exists" for ops that partially landed.
- Missing
opis inferred for unambiguous shapes ({list:...}→init,{phase, items}→append, bareitemson an empty list →init) via the tool'sprepareArgumentsshim. - Custom TUI rendering: roman-numeral phase headers, progress counters, status-colored checkboxes, strikethrough completions, and a walking collapsed viewport that keeps the just-completed task visible while collapsing untouched phases.
The tracker (session behavior):
- Eager prelude — with
todo.eager: "preferred" | "always", the first turn of a session injects a hidden reminder asking the model to lay out a phased plan with a singleinitcall before working. Guards: skipped for questions/exclamations, when todos already exist, or when the tool is not active. pi's extension API cannot force atool_choice, so"always"injects a MUST-call reminder instead (see Differences from OMP). - Mid-run nudge — after 12 successful mutating tool results (bash/edit/ write/eval) with incomplete todos, a hidden steer message asks the agent to mark finished tasks done (≤2 per prompt cycle).
- Bundled
todo-disciplineskill — while the tool is enabled, the extension contributes a load-on-demand skill (via pi'sresources_discover) whose description sits in every model's system prompt: it mandates a phasedinitbefore any 3+ step request and marking each individual taskdonethe moment that task finishes — never retro-batched at the end of a phase or session. Load it explicitly with/skill:todo-discipline. - Completion reminder — when the agent settles with incomplete todos and
isn't waiting for user input, a reminder listing the remaining items is
injected and a fresh turn is triggered (
todo.reminders,todo.remindersMax, default 3). Reminders pause until the agent makes progress (any tool result) and never fire while the assistant is awaiting your answer.
The /todo command (for you):
/todo Show current todos
/todo edit Edit todos in the built-in editor
/todo copy Copy todos as Markdown (OSC 52 clipboard)
/todo export [<path>] Write todos to a file (default: TODO.md)
/todo import [<path>] Replace todos from a file
/todo append [<phase>] <task...> Append a task (phase fuzzy-matched/created)
/todo start <task> Mark a task in_progress (fuzzy match)
/todo done/drop [<task|phase>] Mark completed / abandoned
/todo rm [<task|phase>] Remove task/phase/allManual edits persist as user_todo_edit custom entries (they win over tool
results when newer) and inject a hidden reminder telling the model what
changed — including explicit "do NOT recreate" directives after removals.
Outside the TUI, /todo edit falls back to $VISUAL/$EDITOR on a temp
file.
The HUD widget — a compact per-phase checklist with progress
(2/5 done), rendered above the editor and kept in sync with every tool
result, manual edit, and session reload.
Install
The published package is @gamaraan/todos-tool:
pi install npm:@gamaraan/todos-toolPin a release with pi install npm:@gamaraan/[email protected]. From GitHub:
pi install git:github.com/gamaraan/todos-tool-pi-extension. Manual: copy
src/index.ts (plus the src/ modules it imports) into
~/.pi/agent/extensions/, or add the path to the extensions array in
~/.pi/agent/settings.json. Restart pi (or start a new session) after
installing.
Configure
The extension cannot extend pi's built-in settings schema, so it reads a small JSON file from the host agent dir, optionally overridden per project:
| File | Scope |
| --- | --- |
| ~/.pi/agent/todo.json | Global |
| <cwd>/.pi/todo.json | Project (only when the project is trusted) |
{
"enabled": true,
"reminders": true,
"remindersMax": 3,
"eager": "default"
}| Key | Default | Notes |
| --- | --- | --- |
| enabled | true | Gates the tool and all tracker behaviors. When false the todo tool is removed from the active tool set at session start. A global false is a floor — project config cannot re-enable it. |
| reminders | true | Stop-time incomplete-todo reminders. |
| remindersMax | 3 | Max reminder attempts per prompt cycle. |
| eager | "default" | "default" = no prelude, "preferred" = soft reminder, "always" = MUST-call reminder on the first turn. |
Invalid values and unknown keys are ignored with a warning. Configure these
settings interactively with /todos-configure; it saves the global JSON file and
reloads the extension when finished. At startup, precedence is CLI flag >
environment variable > project JSON > global JSON > built-in default:
| Setting | CLI flag | Environment variable |
| --- | --- | --- |
| Enabled | --todo-enabled on/off | PI_TODO_ENABLED |
| Reminders | --todo-reminders on/off | PI_TODO_REMINDERS |
| Reminder limit | --todo-reminders-max N | PI_TODO_REMINDERS_MAX |
| Eager mode | --todo-eager default/preferred/always | PI_TODO_EAGER |
Optional desktop notifications
In TUI mode, a successful transition from active work to completed or
blocked emits a named desktop-notify:request EventBus request that includes
the transitioned task names:
{
title: "Todo completed",
body: "Completed: Ship it",
type: "todo-completed",
urgency: "normal",
sound: "info"
}Blocked transitions use a "Todo blocked" title, a body such as
"Blocked: Need input", type "todo-blocked", and sound "warning".
Multiple transitions of the same kind are listed in one body. Blocker reasons
are not included. Requests are emitted only on terminals advertising OSC
9/99-capable focus handling (Kitty, Ghostty, WezTerm, iTerm2, or Warp), so the
terminal can suppress the toast while the pi tab is focused. Print, JSON, RPC,
replay, read-only, failed, repeated, and unsupported-terminal paths remain
silent. @gamaraan/desktop-notify is optional: no dependency or installation
is required, and without a listener the todo extension continues normally.
The desktop-notify extension's own settings control delivery when it is loaded.
Use /todos-configure for the successive interactive settings dialogues. The
wizard writes todo.json and reloads the extension after saving; startup flags
and environment variables still override the saved values for that process.
How it works
- Persistence is the tool result itself. Every successful
todoresult carriesdetails.phases; on session start, rewind (session_tree), and compaction, the extension replays the branch (getLatestTodoPhasesFromEntries) and takes the newest snapshot — the latestuser_todo_editcustom entry, else the latest successfultodotoolResult. Branching and rewinding always show the todo state correct for that point in history. - Errors are thrown. pi signals tool failures by throwing; the model receives the omp-style summary text (errors + full current list) and the previous state stays intact.
- Reminders re-enter the loop via
pi.sendMessage(..., { triggerTurn: true })from theagent_settledhandler; mid-run nudges usedeliverAs: "steer".
Compatibility
Pi: supported range 0.84.x (the API surface this extension uses —
ToolDefinition.prepareArguments, before_agent_start message injection,
agent_settled, sendMessage with triggerTurn/deliverAs — is current
for 0.84.0+). CI runs the unit suite and typecheck against the pinned
published packages.
OMP: not supported, by design — OMP ships its own native todo tool, so
loading this extension there would register a duplicate tool name. The port
target is pi only.
Differences from OMP
Faithful port, with these deliberate adaptations:
| OMP | This extension |
| --- | --- |
| todo.eager: "always" forces a tool_choice | pi extensions cannot force tool choices; "always" injects a MUST-call reminder (models virtually always comply) |
| Sticky HUD header at the top of the chat | HUD widget above the editor (ctx.ui.setWidget) |
| Strikethrough reveal animation driven by the spinner frame | Completed tasks strike through immediately (pi render options carry no frame counter) |
| Settings via OMP's settings schema (todo.*) | todo.json config files (global + trusted project), /todos-configure, CLI flags, and environment overrides |
| Desktop notification integration | Optional named EventBus desktop-notify:request payloads with transitioned task names, gated to OSC 9/99-capable TUI terminals |
| $EDITOR for /todo edit | Built-in pi editor dialog in the TUI; $EDITOR fallback outside it |
| Plan-mode pause, subagent reconciliation, eager task prelude | Out of scope (pi has no core plan mode / subagents); the guarded hooks are omitted |
| <system-reminder> as a developer message | Same text as a hidden custom message (pi converts these to user-role in context — the only injection mechanism extensions have) |
| System prompts (prompts/system/*.md) shipped in-core | A bundled todo-discipline skill contributed via resources_discover when the tool is enabled (extensions cannot edit the system prompt builder; skills are pi's extension-facing equivalent) |
Develop
bun install
bun run typecheck # tsc --noEmit against the published pi 0.84.1 types
bun test # 149 unit tests across state/markdown/persistence/format/render/tracker/command/config/smoke
bun run verify:package # npm pack --dry-runThe extension imports @earendil-works/pi-coding-agent, @earendil-works/pi-tui,
and @earendil-works/pi-ai (peer dependencies, provided by your pi
installation). The src/ modules are loaded by pi's extension loader (jiti)
directly as TypeScript — no build step. Desktop notifications use only pi's
EventBus and do not add a desktop-notify dependency.
See AGENTS.md for the development guide — architecture,
conventions, and the pre-release manual smoke checklist.
License
MIT — see LICENSE. This project ports code from Oh My Pi (MIT, © Can Bölük) which is itself a fork of pi (MIT, © Mario Zechner); both copyright notices are retained.
