Getting started

From nothing to a plugin running on an isolated dev daemon, reloading on every save, with a screenshot to prove it. About five minutes, most of it npm install.

You need Node 22.18 or newer and the Paseo desktop app (it ships the Paseo CLI and the compiler ppd check uses). The output below is real; home directories are shortened to ~ and temp directories to $TMPDIR.

1. Create a plugin

npx --package paseo-plugin-devkit ppd create ~/code/my-plugin
cd ~/code/my-plugin && npm install
created my-plugin (panel template) in ~/code/my-plugin
  base: paseo plugin init
  .gitignore
  OVERVIEW.md
  README.md
  client/hello-panel.tsx
  index.client.tsx
  index.server.ts
  package.json
  paseo-plugin.json
  server/greet.ts
  shared/rpc.ts
  test/greet.test.ts
  test/host-contract.test.ts
  tsconfig.json
  vitest.config.ts

next:
  cd ~/code/my-plugin && npm install
  npm run check && npm test
  npx ppd daemon up && npx ppd dev

The base comes from paseo plugin init, so the SDK version matches your Paseo. ppd create adds what init leaves out: tsconfig node types, an @types/node ~22 pin, a .gitignore, vitest with a host-contract suite, the npm scripts check, test, dev and typecheck, and a README. The panel template is a workspace panel called "Hello", an "Open Hello panel" Command Center item and one RPC, hello.greet.

--template agent-hook creates a plugin that reacts to agent turns instead: see the timeline renderer recipe.

2. Check it against the host

npm run check && npm test
PASS  manifest         my-plugin, requires paseo >=0.11.4
PASS  imports          client, server and shared boundaries hold
PASS  dependencies     no runtime packages
PASS  paseo-compile    compilePlugin of the Paseo 0.11.4 app (/Applications/Paseo.app) accepted the plugin
PASS  client-requires  client requires only host modules (@getpaseo/plugin, @getpaseo/plugin/client, react, react-native, react/jsx-runtime, zod)
PASS  server-requires  server requires only host modules and Node built-ins
PASS  server-eval      Paseo's evaluator ran the contribution in 0 ms; RPCs: hello.greet
PASS  client-eval      contributions: addWorkspacePanel, addCommandCenterItem
PASS  hermes-class     no class syntax in the client bundle
PASS  hermes-parse     hermesc compiles the client bundle
PASS  hermes-vm        Hermes VM evaluated the bundle and ran the contribution (addWorkspacePanel, addCommandCenterItem)
PASS  lucide-icons     2 icon name(s) valid against 6354 Lucide icons of the Paseo 0.11.4 app (/Applications/Paseo.app)
PASS  bundle-size      client 5 KiB (2 KiB gzip), budget 2.0 MiB; server 2 KiB (1 KiB gzip), eval 0 ms, budget 32.0 MiB

13 passed, 0 warnings, 0 failed, 0 skipped

npm test runs the unit tests plus test/host-contract.test.ts, which turns every check into a vitest test. What each line means, and what a failure looks like, is on Host-contract checks.

3. Start a dev daemon

npx ppd daemon up --workspace .
dev daemon "default" up
  url:   http://127.0.0.1:7800
  home:  $TMPDIR/ppd-default-2kCaQs
  pid:   84941
  paseo: 0.11.4
workspace created for ~/code/my-plugin

A detached Paseo daemon in a fresh home under $TMPDIR, on the first free port from 7800 to 7899, with plugins and the web UI on and relay, MCP and speech off. Your own Paseo keeps running on port 6767, untouched. --name runs several dev daemons side by side; install, dev, paseo, web and android take the same --name.

4. Run the dev loop

npx ppd dev
--- installing my-plugin into http://127.0.0.1:7800
my-plugin: running (installed) in 1865 ms
client: passes the app's registration rules
  log 2026-10-18T08:25:34.284Z  stdout    [paseo] Loading plugin
  log 2026-10-18T08:25:34.550Z  stdout    [paseo] Plugin ready
watching . (Ctrl+C to stop)

Edit server/greet.ts (here Hello, became Hi,) and the plugin reloads, the client is re-checked, and new plugin log lines stream in:

--- reloading: server/greet.ts
my-plugin: running (reloaded) in 1921 ms
client: passes the app's registration rules
  log 2026-10-18T08:25:43.954Z  stdout    [paseo] Stopping plugin
  log 2026-10-18T08:25:43.966Z  stdout    [paseo] Plugin stopped
  log 2026-10-18T08:25:43.966Z  stdout    [paseo] Loading plugin
  log 2026-10-18T08:25:44.247Z  stdout    [paseo] Plugin ready

ppd install does one install or reload without watching. Both report client-rejected when the app would drop your client: see Troubleshooting.

5. Look at it

In another terminal:

npx ppd web --command "Open Hello panel" --click Greet --shot out/hello.png
opened workspace my-plugin (wks_61e6e477b7e141dd)
screenshot: ~/code/my-plugin/out/hello.png
console errors: none
plugin evaluation errors: none
The Hello panel in the Paseo web UI after clicking Greet: 'Hi, Paseo! (from the plugin server on darwin) Call #1.'
The screenshot ppd web took: the panel, served by the dev daemon, after its RPC round trip with the edited greeting.

ppd web drives Chrome through Playwright: it opens the workspace, runs a Command Center item, clicks elements by their accessible name, and prints console errors and any plugin client the app failed to evaluate (that one exits 1). --viewport 390x844 shows the compact (phone) layout and --headed shows the browser, which stays open when there is no --shot. On a real phone: Testing on a phone.

6. Stop

npx ppd daemon down
stopped dev daemon "default" (pid 84941)
removed $TMPDIR/ppd-default-2kCaQs
Agents

The repo ships a Claude Code skill, skills/paseo-plugin-dev, that teaches an agent this loop and the safety rules. Copy it into ~/.claude/skills/ (all projects) or the plugin's .claude/skills/ and ask, for example, "verify my plugin on a dev daemon and screenshot the panel".

Add ppd to an existing plugin

npm install -D paseo-plugin-devkit
npx ppd check

Then make the checks part of your tests:

// test/host-contract.test.ts: one vitest test per check
import { fileURLToPath } from "node:url";
import { describeHostContract } from "paseo-plugin-devkit/vitest";

describeHostContract(fileURLToPath(new URL("..", import.meta.url)));

and copy examples/ci.yml into .github/workflows/. It runs ppd check (as a labeled approximation, since Linux runners have no Paseo app) and ppd registry precheck on every push, plus a weekly job that checks your plugin against Paseo main, so upstream changes break your CI before they break your users.