@cemoody/pi-crust-ext-todoist
Interactive, mobile-friendly Todoist widget for pi-crust — the LLM drives your task list via tools while you edit/complete tasks inline in the sidebar.
Package details
Install @cemoody/pi-crust-ext-todoist from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@cemoody/pi-crust-ext-todoist- Package
@cemoody/pi-crust-ext-todoist- Version
0.1.1- Published
- Jun 19, 2026
- Downloads
- not available
- Author
- chrisemoodynpm
- License
- MIT
- Types
- extension
- Size
- 70.4 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./pi.mjs"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@cemoody/pi-crust-ext-todoist
An interactive, mobile-friendly Todoist widget for pi-crust. The LLM drives your task list with tools; you edit, complete, and reschedule tasks inline in the sidebar. Both surfaces operate on the same Todoist account, so changes the agent makes show up immediately in the widget and vice-versa.
Without a
TODOIST_API_TOKENthe extension runs against a fully-interactive in-memory mock (create / edit / complete / reopen / delete all work) so you can try it offline. Set the token to talk to your real account.
Three surfaces, one client
This package wires three pi-crust/pi entry points to a single shared Todoist
REST client (todoist-client.mjs):
| File | Manifest key | Role |
|---|---|---|
pi.mjs |
pi.extensions |
LLM-facing tools — the agent driving surface |
server.mjs |
piCrust.extension |
Registers the Todoist sidebar activity + /api/ext/todoist/* REST routes |
web.mjs |
piCrust.web |
The interactive React widget mounted in the sidebar |
The widget calls the server routes; the agent tools call Todoist directly. Both go through the same client, so there's no state to keep in sync — Todoist (or the mock store) is the single source of truth.
The UI paradigm
A deliberately focused subset of Todoist rather than a full clone — what you actually want inline in a session:
- Project chips across the top scope the list (
All+ each project, with live open-task counts). Horizontally scrollable on phones. - A sticky quick-add bar creates tasks into the active project (Inbox by default). Enter or the Add button.
- Each task row is a large tap target:
- a round complete checkbox colored by priority (P1 red → P4 grey) with a satisfying strike-and-remove animation,
- the title is tap-to-edit inline (Enter saves, Esc cancels),
- a due-date chip (overdue = red, today = green, otherwise a friendly date),
- project dot (in the
Allview) and @label pills, - row actions to cycle priority and delete (hover-revealed on desktop, always visible on phones).
- Every mutation is optimistic and reconciles against the server response.
It collapses to a single comfortable column with 44px+ tap targets under 640px, and reuses the host design tokens so it matches the rest of pi-crust.
Agent tools
| Tool | Description |
|---|---|
todoist_list |
List active tasks (by projectId or a Todoist filter query like today, overdue, @work & p1) |
todoist_projects |
List projects with ids |
todoist_add |
Create a task (content, dueString natural language, priority 1–4, labels, projectId) |
todoist_update |
Edit a task (content, dueString, clearDue, priority, labels, projectId) |
todoist_complete |
Complete (close) a task |
todoist_reopen |
Reopen a completed task |
todoist_delete |
Delete a task |
Priority follows the Todoist REST convention: 4 = P1 (urgent) … 1 = P4.
Inline tool-call cards
Every agent tool call renders a compact Todoist summary inline in the
conversation timeline (not just a line of text). The tools attach a
details.piRemoteControlArtifact of kind markdown to their result;
pi-crust renders it natively in the timeline. We use markdown (not HTML) on
purpose: HTML artifacts are wrapped in a bordered figure and a bordered,
320px-min sandboxed iframe, which double-boxes a small task list, leaves dead
whitespace, and can't run scripts anyway. Markdown renders organically in a
single box with the host's own typography.
When the agent runs todoist_list, todoist_add, todoist_complete, or
todoist_update, you see the affected list right in the chat — a just-added task
is bolded, completed tasks are struck through, and a status line
(Added · Work, Completed · 3 still open) heads the card.
The inline card is a read-only snapshot. pi-crust sandboxes artifact iframes (
sandbox="", no scripts), so inline items can't be tapped to mutate. The interactive surface is the sidebar panel.
Editing in the sidebar (interactive)
The sidebar Todoist panel is where you work with tasks directly:
- Tap the round check → completes the task (with strike animation).
- Tap the title → inline text editor; Enter saves, Esc cancels.
- Tap the priority flag → cycles P1→P4 (
todoist_update priority). - Tap the due chip (or the faint
📅 Dueon undated tasks) → a natural-language due editor (“tomorrow 9am”, “next monday”); clear it to remove the date. - Quick-add at the top; trash to delete.
All mutations are optimistic and proxied to the /api/ext/todoist/* routes.
Tests
Built test-first (vitest + jsdom + Testing Library):
npm test
Covers the inline-artifact builder (test/artifact.test.mjs — markdown shape,
highlight, status, due labels), the agent tools' artifact wiring
(test/pi-tools.test.mjs), and the sidebar widget interactions
(test/widget.test.mjs — complete, title edit, priority cycle, due-date
editing, quick-add).
Connecting your account
Two ways to provide a Todoist personal API token (Todoist → Settings → Integrations → Developer → API token):
- In-app (recommended) — open the Todoist panel and use the Connect your
Todoist account banner. The token is saved via
prc.storageto…/todoist.jsonand survives restarts. A Disconnect link clears it. - Env var — set
TODOIST_API_TOKEN(handy for headless/server/CI).
Token resolution precedence: an in-app (stored) token wins; otherwise the
env var; otherwise the interactive in-memory demo store. The connection
status endpoint returns only a masked hint (…cdef) — the raw token is never
sent to the browser.
| Env var | Purpose |
|---|---|
TODOIST_API_TOKEN |
Optional fallback token. In-app token takes precedence; when neither is set the in-memory mock is used. |
Config routes: GET /api/ext/todoist/config, POST /api/ext/todoist/token
{token}, POST /api/ext/todoist/token/delete. Full surface + edge cases:
docs/SPEC-token-config.md.
Publishing
Ships an allow-listed tarball (9 files; verify with npm pack --dry-run).
prepublishOnly runs the test suite. To publish:
npm version patch # or minor/major
npm publish --access public
# or: push a vX.Y.Z tag and let .github/workflows/publish.yml do it (needs NPM_TOKEN)
Until published, install from a path or git URL:
PI_CRUST_EXTENSIONS=/path/to/pi-crust-ext-todoist npx pi-crust
# or add the path/git url to packages[] in your pi-crust settings.json
Install
Pi-crust discovers any installed package whose package.json carries a
piRemoteControl / piCrust field; pi discovers the pi.extensions entry.
TODOIST_API_TOKEN=xxxx PI_CRUST_EXTENSIONS=/path/to/pi-crust-ext-todoist npx pi-crust
REST surface (used by the widget)
GET /api/ext/todoist/state?projectId= -> { projects, tasks, live }
POST /api/ext/todoist/tasks { content, projectId? } -> { task }
POST /api/ext/todoist/tasks/:id/update { content?, priority?, dueString?, ... } -> { task }
POST /api/ext/todoist/tasks/:id/close -> { ok }
POST /api/ext/todoist/tasks/:id/reopen -> { ok }
POST /api/ext/todoist/tasks/:id/delete -> { ok }
Preview
node preview/shoot.mjs renders the widget against an in-memory mock and writes
preview/desktop.png + preview/mobile.png (requires playwright-core).
License
MIT.