@gamaraan/todos-tool

pi coding-agent extension: an Oh My Pi (OMP)-style phased todo tool with reminders, /todo configuration, HUD, and optional desktop notification requests.

Packages

Package details

extension

Install @gamaraan/todos-tool from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@gamaraan/todos-tool
Package
@gamaraan/todos-tool
Version
0.3.0
Published
Sep 8, 2026
Downloads
1,152/mo · 318/wk
Author
gamaraan
License
MIT
Types
extension
Size
145.3 KB
Dependencies
0 dependencies · 4 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

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 store

Features

The todo tool (for the agent): init | start | done | rm | drop | block | unblock | append | view.

  • Phased lists (init with list: [{phase, items}] or a flattened items: [...]), tasks referenced by exact content, never IDs.
  • Auto-promote only fires when nothing is in progress: an existing in_progress task keeps its place, and completing a task with none in progress promotes the earliest still-open task. Out-of-order completion may move the pointer back to an earlier phase (completed tasks never revert).
  • block/unblock for work waiting on external input — blocked tasks are excluded from stop-time reminders and carry an optional reason that 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 op is inferred for unambiguous shapes ({list:...}init, {phase, items}append, bare items on an empty list → init) via the tool's prepareArguments shim.
  • 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 single init call before working. Guards: skipped for questions/exclamations, when todos already exist, or when the tool is not active. pi's extension API cannot force a tool_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-discipline skill — while the tool is enabled, the extension contributes a load-on-demand skill (via pi's resources_discover) whose description sits in every model's system prompt: it mandates a phased init before any 3+ step request and marking each individual task done the 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/all

Manual 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-tool

Pin a release with pi install npm:@gamaraan/todos-tool@0.2.1. 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 todo result carries details.phases; on session start, rewind (session_tree), and compaction, the extension replays the branch (getLatestTodoPhasesFromEntries) and takes the newest snapshot — the latest user_todo_edit custom entry, else the latest successful todo toolResult. 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 the agent_settled handler; mid-run nudges use deliverAs: "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
Branch replay casts persisted phases blindly Structurally validated (isTodoPhase, including the optional blocker type) first; corrupt snapshots are skipped, never crash session sync
Task content / phase names stored verbatim Whitespace runs (incl. newlines) collapsed at init/append input, same one-line guarantee as blocker reason; blank entries rejected
Blocker-comment parse binds the first <!-- blocker: (lazy) Binds the trailing comment the writer emits (greedy) AND delimiters inside the blocker reason are percent-escaped, so blockers containing --> or <!-- blocker: round-trip intact; legacy unescaped exports still parse
/todo edit external-editor temp file uses a predictable pid/timestamp name in tmp mkdtemp (0700) + 0600 file: no pre-created symlink hijack on multi-user machines; saves are aborted when the list changed while the editor was open (no silent overwrite of concurrent progress)
/todo export writes unconditionally Refuses symlink targets and refuses clobbering an existing file without an explicit confirm
Env/flag overrides fall back silently Invalid values warn (no silent enabled from a typo); whitespace-only values are ignored
Manual /todo append/import/edit accept blank/duplicate identities Shared identity validator rejects them (duplicates would be permanently unaddressable)
Todo snapshots merge "no snapshot" with "explicitly cleared" Replay returns undefined vs a valid []/todo never resurrects an explicitly cleared list
<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-run

The 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.