pi-note
Persistent pinote tasks, Markdown handoffs, and task status for Pi
Package details
Install pi-note from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-note- Package
pi-note- Version
0.37.0- Published
- Nov 7, 2026
- Downloads
- 5,377/mo · 1,960/wk
- Author
- kvidzibo
- License
- MIT
- Types
- extension
- Size
- 353 KB
- Dependencies
- 3 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./remote.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-note
Shared Pinote tasks and Markdown handoffs in Pi. A dedicated extension connects directly to an authenticated MCP Streamable HTTP service. No MCP gateway, VM-local Python installation, or network-mounted database is needed.
Host Pi ── Pinote extension ──┐
├─ HTTPS → Pinote service → notes.db
VM Pi ──── Pinote extension ──┘
Tasks, tags, progress and handoff fields are shared. Selection and pending suggestions belong to each Pi conversation/branch, not the HTTP connection. Other tools continue running wherever Pi and their services run.
Install
Requires Pi 1.4.0+ and Node.js 22.19+. The service additionally needs Python 3.11+ and note 0.12.0+. Install and start the Pinote service first, then on each client:
pi install npm:pi-note
# Or, from a checkout:
pi install ./pi
Set the endpoint and use the same private bearer token as the service before
starting Pi. Both read PINOTE_TOKEN, or a private owner-only PINOTE_TOKEN_FILE
(default <Pi agent directory>/pinote-token):
export PINOTE_URL=https://notes.example.net/mcp
# Load PINOTE_TOKEN from your private environment/secret manager; never commit it.
pi
The token must contain at least 32 non-whitespace characters. The default endpoint
is http://127.0.0.1:8765/mcp. Non-loopback HTTP requires the explicit
PINOTE_ALLOW_HTTP=1 opt-in and a trusted private VPN; prefer HTTPS. Endpoint URLs
cannot contain credentials, query strings or fragments. Do not register this
service again through Pi's built-in MCP client: the extension owns the connection,
state routing, tools and UI.
Use
Open /pinote. Select or create a task, preview it,
clear the selection, complete it, respond to suggestions, or edit shared settings.
Selecting an existing task starts it immediately and returns focus to the prompt,
with no extra confirmation. Creation and settings changes still require confirmation.
Nothing submits a model prompt automatically; inserting Continue's draft requires
confirmation and preserves an existing draft.
Menus use Pi's native components and configured selection keys. Escape goes
back; Ctrl+R refreshes. Settings with fewer than five choices cycle inline.
Fullscreen has a native clickable footer: ▤ opens Pinote; clicking a selected
title previews it and double-clicking completes it. A suggested title requires a
double-click to accept. Regular mode shows status; /pinote provides all actions.
No Kitty action handler is needed for this extension.
Accepting a suggestion, selecting a different task, or creating and selecting one
renames the current Kitty tab to [tag] task title (tag omitted when unset),
using the footer's first-line title rather than the GUI launcher's shortened name.
Requires local Kitty with remote control enabled, kitty on PATH, and
KITTY_LISTEN_ON / KITTY_WINDOW_ID; SSH and other terminals are unchanged.
Rename failures warn without undoing selection. Completion and deselection leave
the tab name unchanged; reload/resume and external task edits do not rename it.
Completion keeps the conversation and editor, except for dedicated Start in Kitty…
sessions (see below). With an empty editor, Ctrl+D
(or your configured exit key) offers Cancel, Exit without completing, or Complete
task and exit. /new offers Cancel, Start new session without completing, or
Complete task and start new session. With no selected task, /new proceeds normally.
Completion requires idle Pi and a current displayed revision; failures keep the
current session open. /quit exits without completing.
Reload/resume restores the active branch's selection. Starting a new session without completing retains the selection; completing starts it unselected. Unrelated fresh launches start unselected. Changing endpoints starts a separate selection namespace. Unsaved Pi sessions cannot guarantee selection persistence.
The GTK app's Start in Kitty… action (note 0.13.0+) chooses a working directory
and opens a named tab with this task selected, without submitting a prompt.
In note 0.16.0+, it uses the tag's default folder when available, otherwise keeps
the directory chooser. Set or clear defaults in Manage tags….
With note 0.15.0+ and pi-note 0.32.0+, successful selection prefills an empty Pi editor
with is task clear?. Configure gui.kitty_start_prompt in the
GUI config, or set it to "" to disable; existing
drafts are never replaced. The service needs pi-note 0.31.0+.
The one-shot PINOTE_LAUNCH_TASK environment handoff carries the task ID, displayed
revision and optional editor draft, never task text or tokens. The draft stays
client-local and is never sent to the service or persisted as launch state.
Only a fresh session can consume it. The service must share the GUI's database;
missing/stale revisions fail closed. Resume, reload and /new never replay the
handoff or replace another selection. Pi keeps its normal endpoint/token settings.
With pi-note 0.33.4+, completing the launched task from the menu, footer or
Complete task and exit exits Pi directly, so Kitty closes its dedicated tab
(or the standalone window's last tab). Pinote itself never runs Git commands;
optional local completion hooks can briefly delay closing (see below).
This ownership survives /reload, but not /new, resume, fork, tree navigation
or process restart. Completing a different task or an externally completed task
does not close Pi; failed/uncertain completion stays open.
Local completion event (pi-note 0.35.0+)
Trusted sibling extensions can listen on pi.events for pinote:task-completed:
pi.events.on('pinote:task-completed', (event) => {
const { taskId, signal, waitUntil } = event as {
taskId: number;
signal: AbortSignal;
waitUntil(work: Promise<unknown>): void;
};
waitUntil(Promise.resolve().then(() => doLocalWork(taskId, signal))); // Register before any await.
});
This local event fires exactly once only after this client's acknowledged Done
action clears a valid selected task, across menu, footer and exit/new-session paths.
taskId is the original positive safe-integer task ID, not the cleared selection.
External completions, notifications, clear-selection actions and failed/stale Done
attempts do not emit it. Pinote does not submit a model prompt for this event.
Pi's event bus is synchronous fire-and-forget: returning an async handler's promise
is insufficient. Call waitUntil during dispatch; late registrations are ignored.
Put potentially throwing work inside the registered promise, as above. Exceptions
thrown by the listener itself before registration are handled by Pi's own logger,
which may print their raw text; Pinote cannot intercept those bus errors.
All registered work is awaited outside Pinote's serial queue (so listeners may use
pinote:query-current) before Done returns or a launched session auto-exits.
Without registered work, completion proceeds immediately. Ordinary Done still
keeps the conversation open.
There is one fixed 25-second total deadline, shared by all listeners. The signal
aborts on session lifetime cancellation, deadline, or hook settlement; listeners
must honor it and avoid blocking synchronous work. Timers cannot preempt JavaScript
that blocks the event loop. On runtime teardown, signal.reason carries Pi's
shutdown reason (quit, reload, new, resume, or fork). A listener with its
own awaited shutdown handler may finish bounded work on quit; other cancellation
still stops its work. Hook rejection, timeout or cancellation never undo an
acknowledged completion, invalidate its state, or retry remote writes. Registered
promise rejections and timeouts produce only a generic warning when UI is available;
Pinote does not display their error or response text. This is a trusted local extension API, not remote task
instructions or authorization to run models or external actions.
Agent tools and context
The extension registers pinote_search, pinote_get_task, pinote_get_current,
pinote_update_current, pinote_fields, pinote_tags, and pinote_propose.
Default guidance suggests tasks only for substantial work requiring changes, not
questions, explanations, research, read-only inspection, or trivial edits.
For new tasks, pinote_propose requires a nonblank, single-line title of
1–60 Unicode characters, with optional multiline description and tag.
The service joins title and description with a blank line; the combined text must
fit 4,000 characters. Invalid input is rejected, never truncated. To suggest an
existing task, pass only task_id.
Suggestions do not create/start/select tasks until accepted in the UI. Reads by ID
do not change selection. Updates require the task's expected_updated_at revision;
concurrent edits are rejected rather than overwritten.
Fresh selected-task context and service guidance are read before every agent run. Task text and handoff fields are untrusted data, not authorization. Failed reads explicitly report unavailable context; they never reuse a cached task or claim no task is selected. Tool results and injected context can reach your configured model: do not put secrets in tasks.
External CLI/GTK/client edits refresh connected views through notifications.
After a network failure, use /pinote or a tool call to reconnect. Mutations are
never automatically replayed: a failed write may already have committed, so
read fresh state before retrying. Server restart loses HTTP sessions, not notes.
There is no offline write queue or database replication.
Shared settings
Settings live in pi-note.json on the service machine, under its
PI_CODING_AGENT_DIR (default ~/.pi/agent). The Pinote app edits prompts, injected
agent guidance and footer definitions with confirmation and revision checks.
A malformed configuration fails closed; fix it on the server rather than replacing
it with defaults. Task fields such as PR, Worktree and Next remain task data,
separate from global display settings.
Migration
Since pi-note 0.36.0, new-task proposals use title and optional description
instead of text, in both remote and local modes. Update clients and service
together, then /reload Pi. Stored tasks and pending suggestions are unchanged;
task_id proposals and the local-only pinote_add tool are unchanged.
Since pi-note 0.33.0, /pinote is the only slash command in both modes.
The /pi-note alias is removed; update saved shortcuts to /pinote.
The npm package name and pi-note.json configuration filename are unchanged.
- Back up the service machine's Pinote data; keep
notes.dblocal to that machine. - Start the standalone service against that same database and settings directory.
- Remove Pinote's service entry and its unused package entry from the gateway configuration; preserve all other services/packages.
- Remove the old gateway-dependent
exit-confirm.tscompanion and any old localindex.tsextension from Pi's load list. Load this package once. - Finish active gateway jobs before reloading Pi. Configure each VM client with
the remote endpoint and token, then select its task in
/pinote.
Existing gateway selection entries are not imported; notes and handoff fields are unchanged. Remote-safe Ctrl+D handling is built into this extension; the obsolete gateway companion is removed. The existing local-only CLI extension remains available explicitly for on-device use, but must not be loaded alongside the HTTP extension.
Development and releases
From the repository root:
uv sync --locked
npm --prefix pi ci --ignore-scripts
npm --prefix pi test
(cd pi && npm pack --dry-run)
Tests use the checkout's own .venv/bin/note, real HTTP/stdio servers and temporary
storage. UI validation must use Xvfb, a private D-Bus session and temporary app data.
The npm and Python packages are versioned independently. Bump npm with
npm version patch|minor|major --no-git-tag-version inside pi/ and commit its
manifest and lockfile together. GitHub CI tests main, then publishes new npm
versions through OIDC trusted publishing; registry failures fail the workflow.
The trusted publisher is kvidzibo/pinote, workflow publish.yml, no environment.
Installation never edits personal Pi, shell, systemd or desktop configuration.