@geohar/pi-plan
Pi extension: a plan sidebar. Consumes `plan-item` snapshots (from the cribsheet plan source) and renders the plan — dep-ordered plans and (later) the live agent fleet — as a TUI widget.
Package details
Install @geohar/pi-plan from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@geohar/pi-plan- Package
@geohar/pi-plan- Version
0.3.4- Published
- Sep 9, 2026
- Downloads
- 1,061/mo · 207/wk
- Author
- georgeharker
- License
- MIT
- Types
- extension
- Size
- 113.9 KB
- Dependencies
- 0 dependencies · 0 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
@geohar/pi-plan
A plan sidebar for the pi coding agent. It renders your cribsheet plan and the live subagent fleet as a persistent TUI widget, wave-ordered by dependency so the work you can pick up now is up top.
What it does
pi-plan accumulates plan events off pi's in-process bus and renders them, alongside the
live subagent fleet it reads directly:
- Wave ordering — each item's wave is the longest chain of unsatisfied deps above
it, so wave 0 is everything that's free to start now; each wave is a set that can be
worked in parallel. Blocked items show a dim
⋯N(N unsatisfied deps). - Dep-aware — a dep is satisfied by kind: a note never blocks, a design decision blocks only while tainted, a plan item blocks until done (mirrors cribsheet semantics).
- Actionable first — plan items you can start are highlighted; design/note context is dimmed and trails; done items sink to the bottom.
- Live subagents — the running subagent fleet is read straight off the
subagents:*bus and shown as a separate Agents group (toggle with/plan agents).
The crib plan is emitted by the companion source in
@geohar/pi-cribsheet, but pi-plan is a
generic consumer — any source can drive it by publishing the plan bus
protocol below, with no coupling to cribsheet.
Plan bus protocol
pi-plan listens on two pi.events channels, split by operation. A source publishes
a full snapshot to replace its slice, or part-by-part updates to patch it. State is kept
per source namespace (ns), so multiple sources coexist in one view.
Channels
plan:snapshot — replace all items for a namespace:
{ "ns": "cribsheet", "seq": 7, "items": [ /* PlanItem[] */ ] }
plan:update — patch a namespace part-by-part:
{ "ns": "mytool", "seq": 8, "upsert": [ /* PlanItem[] */ ], "remove": ["id-1", "id-2"] }
PlanItem
Rich fields are optional — a simple source can emit just id + title:
{
id: string // stable, unique within the ns
title: string // display text
status?: string // "done" sinks to the bottom; "in-progress"/"active" is highlighted
deps?: string[] // ids (same ns) this item depends on; omit for a flat list
kind?: string // "plan" | "design" | "note" (default "plan")
tainted?: boolean // design-kind only: blocks its dependents while true
}
Semantics
nsattributes the source;pi-planaccumulates a per-nsmap.snapshotreplaces the whole map for thatns;updateupserts items byidand deletes theremoveids.seq(optional, monotonic perns) drops out-of-order deliveries.- Dep satisfaction is kind-aware: a
notenever blocks; adesignblocks only whiletainted; aplanblocks until itsstatusis done. - Wave = the longest chain of unsatisfied deps above an item; wave 0 is actionable now.
- Pick the channel that fits: a source that has the whole list emits
plan:snapshot; a source that produces items piecemeal emitsplan:update.
Minimal emit (any pi extension, in-process):
pi.events.emit("plan:snapshot", {
ns: "mytool",
items: [{ id: "a", title: "do a thing", status: "todo" }],
})
The same protocol is bridged to ACP by
@geohar/pi-acp(it re-emits these events over RPC and maps them to an ACPplan), so a source that speaks it surfaces in both the pi TUI and ACP clients.
Install
pi install npm:@geohar/pi-plan
Run pi from a repo whose cribsheet project has plan items; the widget primes on session start and repaints when you edit the plan or the subagent fleet changes.
Control it with /plan
pi widgets are render-only, so the widget is driven by a slash command:
| command | effect |
|---|---|
/plan |
toggle expanded ⇄ collapsed |
/plan expand / /plan collapse / /plan hide / /plan show |
set state |
/plan agents |
toggle the subagent group |
/plan show agents / /plan hide agents |
set the subagent group |
/plan filter done |
include/exclude done items |
/plan filter context |
include/exclude design/note context |
/plan lines <n> |
set the expanded row budget for the session (/plan lines reports it) |
Collapsed is a one-line summary (▸ Plan N ready · M blocked · K agents /plan to expand); expanded is the wave-ordered tree followed by the Agents group.
Settings
pi-plan reads <PI_CODING_AGENT_DIR>/extensions/pi-plan.json (defaults are written on
first run):
{
"placement": "aboveEditor", // aboveEditor | belowEditor
"defaultState": "expanded", // expanded | collapsed | hidden
"showDone": false, // include done items by default
"showContext": false, // include design/note context by default
"showAgents": true, // show the live subagent group by default
"maxRows": 18 // row budget before the expanded view caps ("… N more")
}
/plan overrides these for the session; the file sets the defaults.
Development
npm install
npm run build # tsc → dist/
npm test # tsc && node --test
The extension entry is ./src/index.ts (declared in package.json pi.extensions),
loaded directly from source via jiti — no build step is needed for local loading. Point
a local checkout at pi with a bare path in settings.json packages[] (there is no
local: scheme): "/abs/path/to/pi-plan".
License
MIT © George Harker