Skip to content

Plugin Guide

Pi-tree is built on the Pi SDK — a minimalist AI agent framework. Pi-tree plugins extend Pi SDK's primitives with source-type-specific behavior:

Pi SDK conceptWhat it doesPi SDK docsPi-tree adds
ExtensionTypeScript module that registers AI toolsExtensionsdefinePiTreeExtension() for access to pi-tree services
SkillMarkdown file with AI behavior instructionsSkillsSource-type-scoped skills bundled in plugins
ToolFunction the AI can call during a sessionExtensions → ToolsSame API, registered via extensions

Pi-tree adds three concepts on top:

  • Session profiles — YAML files that wire skills + extensions together for a (sourceType, mode) pair
  • Source type manifestpiTree field in package.json that declares UI config, routes, and content panels
  • Plugin SDK services — typed access to pi-tree's sources, sessions, users, and registry from within extensions

Three Levels of Customization

LevelWhat you createRestart needed?Use case
Custom skillsA SKILL.md fileNoChange AI behavior for existing source types
Custom profilesA .yml fileYesNew session modes for existing source types
Full pluginA package with package.jsonYesNew source type with tools, skills, routes, UI

Custom Skills (No Code)

Skills are Pi SDK's skill files — markdown instructions that shape how the AI behaves. Drop one into $DATA_PATH/skills/ and it takes effect on the next session — no restart, no build step.

Create a new skill

Create $DATA_PATH/skills/my-skill/SKILL.md:

markdown
---
name: my-skill
description: One-line summary of what this skill does
---

# My Custom Skill

You are a specialized reading assistant. When the user asks you to...

## Guidelines

- Always cite specific sections or page numbers
- Use structured formatting with headers and bullet points
- Ask clarifying questions before diving deep

Override a built-in skill

To change how book reading works, create a skill with the same name as the built-in one:

bash
# Override the interactive-reading skill
mkdir -p "$DATA_PATH/skills/interactive-reading"
cp packages/plugin-book/skills/interactive-reading/SKILL.md \
   "$DATA_PATH/skills/interactive-reading/SKILL.md"

# Edit to taste

User skills win on name collision — the original is completely replaced.

Custom Session Profiles

Profiles are a pi-tree concept (not from Pi SDK). They wire skills and extensions together for a specific (sourceType, mode) pair. They're YAML files in $DATA_PATH/profiles/.

Create a new session mode

Create $DATA_PATH/profiles/book.analysis.yml:

yaml
name: book.analysis
label: Deep Analysis
description: Detailed analytical reading with structured note-taking
source_type: book

skills:
  - book-analysis
  - book-outline

extensions:
  - book

exclude_tools: [bash, edit]

This adds a "Deep Analysis" mode to the session picker for all book sources.

Profile fields

FieldRequiredDescription
nameYesUnique key, typically sourceType.mode
labelYesShown in session picker UI
descriptionNoOne-line description
source_typeNoLimits this mode to sources of this type
skillsYesList of skill names to load
extensionsNoList of extension names (use ["*"] for all)
exclude_toolsNoTools to block (default: [bash, edit])
modelNoOverride the default model for this mode

Full Plugin

A full plugin is a Pi SDK package (Pi Packages) enhanced with a pi-tree manifest. It provides a new source type with its own tools, skills, profiles, routes, and UI components.

Plugin structure

packages/plugin-example/
├── package.json          # Pi package config + piTree manifest
├── index.ts              # Pi extension: registers AI tools
├── skills/
│   └── example-reading/
│       └── SKILL.md      # Pi skill: AI behavior instructions
├── profiles/
│   └── example.reading.yml  # Pi-tree profile: wires skills + extensions
├── routes.ts             # Pi-tree: HTTP API routes (optional)
└── ui/
    └── ContentPanel.tsx  # Pi-tree: right-panel UI component (optional)

The manifest (package.json)

A plugin's package.json has two sections:

  • pi — standard Pi SDK config (extensions and skills paths)
  • piTree — pi-tree-specific config (source type, routes, UI)
json
{
  "name": "pi-tree-example",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "piTree": {
    "sourceType": {
      "key": "example",
      "label": "Example",
      "icon": "puzzle",
      "sessionModes": ["reading", "custom"],
      "defaultMode": "reading",
      "autoStartMode": "reading",
      "hasProcessing": false,
      "searchPlaceholder": "Search examples...",
      "chatPlaceholder": "Ask about this example…",
      "addSource": {
        "subtitle": "Add an example source",
        "fields": [
          { "key": "title", "label": "Title", "required": true },
          { "key": "url", "label": "URL", "placeholder": "https://..." }
        ]
      },
      "cardSubtitle": "{author}"
    },
    "routes": "./routes.ts",
    "routePrefix": "/api/example",
    "ui": {
      "contentPanel": "./ui/ContentPanel.tsx"
    }
  },
  "pi": {
    "extensions": ["./index.ts"],
    "skills": ["./skills"]
  },
  "dependencies": {
    "@pi-tree/plugin-sdk": "*",
    "typebox": "^1.2.6"
  }
}

Pi SDK fields (pi)

These follow Pi SDK conventions — see Pi Packages for details:

FieldDescription
pi.extensionsPaths to extension modules that register tools
pi.skillsPaths to skill directories

Pi-tree fields (piTree)

These are pi-tree additions — they tell the server how to wire the plugin into the app:

FieldDescription
piTree.sourceType.keyUnique source type identifier (e.g. "podcast")
piTree.sourceType.labelHuman-readable name shown in UI
piTree.sourceType.iconLucide icon name
piTree.sourceType.sessionModesAvailable session modes
piTree.sourceType.addSourceConfig for the "Add Source" modal form
piTree.sourceType.systemContextTemplate for AI system prompt (supports {sourceId}, {userId} placeholders)
piTree.routesPath to HTTP routes module
piTree.routePrefixURL prefix for plugin routes
piTree.ui.contentPanelPath to right-panel React component

Extension with pi-tree services

A standard Pi extension registers tools via pi.registerTool() (docs). Pi-tree's definePiTreeExtension() wraps this to inject typed services:

typescript
import { definePiTreeExtension } from "@pi-tree/plugin-sdk";
import { Type } from "typebox";

export default definePiTreeExtension((pi, services) => {
  // pi — standard Pi SDK ExtensionAPI
  // services — pi-tree services (sources, sessions, users, etc.)

  pi.registerTool({
    name: "create_example_source",
    label: "Create Example Source",
    description: "Create a new source in the library.",
    parameters: Type.Object({
      title: Type.String({ description: "Source title" }),
    }),
    async execute(_toolCallId, params) {
      const source = services.sources.create({
        id: slugify(params.title),
        type: "example",
        title: params.title,
        source: "system",
        status: "ready",
      });

      return {
        content: [{ type: "text", text: `Created: ${source.title}` }],
        details: undefined,
      };
    },
  });
});

If your extension doesn't need pi-tree services, use a plain Pi extension:

typescript
// Standard Pi SDK extension — no pi-tree dependency
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
  pi.registerTool({ /* ... */ });
}

Available pi-tree services

These are injected by definePiTreeExtension() — they're the pi-tree layer on top of Pi SDK:

ServiceMethodsUse case
services.sourceslist(), get(id), create(), update()Manage sources in the library
services.sessionslistForSource(), create(), getById()Manage user sessions
services.usersget(id), ensureExists(id)User identity
services.registrygetProfiles(), getSourceTypes()Profile introspection
services.getPluginDataDir(name)Scoped data directory for your plugin
services.dataPathRoot data directory

HTTP routes (optional)

Plugins can declare HTTP routes — this is a pi-tree feature (not from Pi SDK):

typescript
// routes.ts
import { Hono } from "hono";
import type { PluginRouteContext, PluginSetupResult } from "@pi-tree/plugin-sdk";

export function setup(ctx: PluginRouteContext): PluginSetupResult {
  const app = new Hono();

  app.get("/items", (c) => {
    // ctx.dataDir — your plugin's data directory
    // ctx.sources — source service for CRUD
    return c.json({ items: [] });
  });

  return {
    routes: app,
    cleanup: () => {
      // Called on server shutdown — close DBs, stop timers, etc.
    },
  };
}

The server mounts these at the declared routePrefix (e.g. /api/example/items).

Right-panel UI (optional)

Plugins can provide a React component for the right sidebar — another pi-tree addition:

tsx
// ui/ContentPanel.tsx
import type { ContentPanelProps } from "@pi-tree/ui";

export default function ContentPanel({ sourceId, onSendMessage }: ContentPanelProps) {
  return (
    <div className="my-plugin-panel">
      <h3>Example Panel</h3>
      <p>Source: {sourceId}</p>
      <button onClick={() => onSendMessage?.("Summarize this")}>
        Quick Summary
      </button>
    </div>
  );
}

The panel appears as a tab in the right sidebar alongside Dictionary.

Existing Plugins

For real-world examples, see the built-in plugins (ordered by complexity):

PluginComplexityPi SDK partsPi-tree additions
plugin-paperSimple1 extension (3 tools), 1 skillManifest only, no routes/UI
plugin-youtubeMedium1 extension (2 tools), 1 skillRoutes + content panel (video player)
plugin-newsFull1 extension (5 tools), 1 skillRoutes + own SQLite DB + feed dashboard
plugin-bookFull1 extension (1 tool), 3 skillsFile parsers + processing pipeline

Start with plugin-paper as a template — it's the simplest end-to-end plugin.

Released under the AGPL-3.0 License.