MCPInstall

paseo-plugin

Build and manage trusted local Paseo plugins. Use when the user asks to create, edit, install, reload, enable, disable, remove, or troubleshoot a Paseo plugin; add lifecycle hooks; transform agent configuration, environment, MCP servers, or workspace creation; automate permissions or turn follow-ups; add a screen, sidebar header or footer item, workspace panel, or file panel; add Command Center items or slash commands; add composer pills or attachment sources; transform, render, or append agent timeline items; contribute a theme; use Paseo from plugin code; or add plugin RPCs.

Installs
1.4K
Last 24h
+21
Trending rank
#5646
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/getpaseo/paseo --skill paseo-plugin
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads paseo-plugin automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Paseo plugins

Build or manage the requested plugin directly. Use the current public docs to catch contract changes, but keep working from this skill if the network is unavailable.

User's request: $ARGUMENTS

Check current documentation

Fetch https://paseo.sh/llms.txt first. Select and fetch the current plugin Markdown pages from that index before changing a plugin:

Use the deployed docs when they disagree with this skill. Do not send the user away to read them instead of completing the work.

In the Paseo repository, use public-docs/plugins/reference.md for the checkout's API, including unreleased changes. Use docs/plugins.md for maintainer guidance. Complete contracts belong in the public docs; this skill indexes the references and examples.

What a plugin can contribute

Pick the contribution that matches the request. Each row names the registration, when it fits, and where the full contract lives. Most plugins combine several: a slash command that calls an RPC, which appends a timeline row, which a renderer draws.

ContributionRegistrationUse it whenReference
Screen and sidebar itemaddScreen + addSidebarHeaderItem/addSidebarFooterItemA full screen of plugin UI, or sidebar header or footer rows that open a screen or popoverreference.md → Screens and sidebar items
Workspace paneladdWorkspacePanelUI that lives as a tab beside agents, terminals, files, and diffs; locations: ["explorer"] for the Explorerreference.md → Workspace panels
File paneladdFilePanelView or edit a matching file inside the file tab through its file handlereference.md → File panels; plugin-examples/pdf-panel, plugin-examples/markdown-panel
Command Center itemaddCommandCenterItemA global, workspace, or agent action reachable from ⌘Kreference.md → Command Center items
Client slash commandaddSlashCommandA /command args in the composer that runs plugin code instead of prompting the agentreference.md → Slash commands
Composer pilladdComposerPillA per-agent button in the composer track bar next to Tasks and Subagentsreference.md → Composer pills
Timeline transformeraddTimelineTransformer + addTimelineRendererReplace, explode, or hide a built-in timeline item, including while it streamsreference.md → Timeline items; plugin-examples/timeline-items, plugin-examples/inline-thinking
Timeline rowpaseo.agents.ref(id).timeline.append(...)Push a plugin-owned row into an agent timeline from a server handler and update it laterreference.md → Append a timeline row from the daemon
Attachment sourceclient.addAttachmentSource + server.handleLet the user attach a searchable external resource, such as an issue, to a promptreference.md → Add a composer attachment source; plugin-examples/linear
ThemeaddThemeA light or dark palette under Settings → Appearancereference.md → Contribute a theme; plugin-examples/catppuccin
Plugin RPCdefineRpc + server.handle + useRpcDaemon-side work that is not a normal Paseo operation: vendor APIs, credentials, local filesreference.md → Add plugin-specific backend behavior
Lifecycle eventsserver.onObserve agent/workspace lifecycle, inspect ended turns, and answer permission requestsLifecycle hooks
Creation and launch hooksserver.beforeChange agent config, provider options, MCP servers, environment, or workspace isolation before the operationBefore hooks
Paseo SDKusePaseo() / handler { paseo }Normal Paseo operations: workspaces, agents, providers, configreference.md → Use the Paseo SDK
Lifecycle taskExample
Log all eleven hookslifecycle-logger
Follow-ups, permissions, environment, provider switching, worktreeslifecycle-actions
Inject MCP servers and change Codex sandbox/approval optionsagent-configuration

Create the project

Use an absolute path on the daemon machine. init writes files but does not install packages.

paseo plugin init /absolute/path/to/my-plugin
cd /absolute/path/to/my-plugin
npm install

The generated project contains:

my-plugin/
  paseo-plugin.json
  OVERVIEW.md
  package.json
  tsconfig.json
  index.client.tsx
  index.server.ts
  client/greeting.tsx
  server/greeting.ts
  shared/greeting.ts

The manifest supplies the default install ID and supported Paseo versions:

{ "id": "my-plugin", "requirements": { "paseo": ">=0.8.0" } }

Keep requirements.paseo correct whenever creating or editing a plugin. init uses >= followed by the CLI version. Raise the minimum when adopting newer APIs; add an upper bound when a later Paseo release is incompatible. Use npm semver ranges and explicitly include beta versions when targeting betas. Missing requirements mean <0.8.0; complete the 0.8 entry migration before adding >=0.8.0. Verify compatibility with both the daemon and the app running client contributions. See requirements.

Each runtime has its own optional entry. A plugin must have at least one. Both entries accept .ts or .tsx; use .tsx when an entry imports components.

PathRuntime
index.client.tsx and client/App
index.server.ts and server/Daemon subprocess
shared/Both

Do not put any other code modules in the plugin root.

A client import of server/, a server import of client/, and every node: import reachable from client code is a compile error. A relative import to another code file in the plugin root is also a compile error; move it into client/, server/, or shared/. Shared modules contain Zod contracts and plain values; they do not import Node or React Native runtime APIs.

Default-export one contribution function from each entry and return cleanup:

// index.client.tsx
import type { PluginClientContext } from "@getpaseo/plugin/client";

export default function contribute(client: PluginClientContext) {
  // Register components and client callbacks here.
  return () => {};
}
// index.server.ts
import type { PluginServerContext } from "@getpaseo/plugin/server";

export default function contribute(server: PluginServerContext) {
  // Register daemon-side RPC handlers here.
  return () => {};
}

Cleanup can be async. Use it for timers, watchers, sockets, subscriptions, and other resources created by plugin code. Every client add* method returns an idempotent remover. Paseo calls the entry cleanup first, removes registrations that remain, rejects pending RPCs, closes the plugin session, and stops the subprocess when the plugin stops.

Add a workspace panel

Workspace panels live beside agents, terminals, files, and diffs. Plugins run on desktop and mobile, and Paseo has multiple themes. Every Text must take its color from theme.colors. Use layout.compact for padding and stacking. Unstyled text is black and fails in dark themes.

import {
  type PluginClientContext,
  type PluginWorkspacePanelProps,
  useWorkspace,
} from "@getpaseo/plugin/client";
import { useMemo } from "react";
import { Text, View } from "react-native";

function Overview({ theme, layout, workspaceId }: PluginWorkspacePanelProps) {
  const name = useWorkspace(workspaceId, (workspace) => workspace.name);
  const styles = useMemo(
    () => ({
      screen: {
        flex: 1,
        padding: layout.compact ? 16 : 24,
        gap: layout.compact ? 8 : 12,
        backgroundColor: theme.colors.surface0,
      },
      title: { color: theme.colors.foreground, fontSize: layout.compact ? 20 : 24 },
    }),
    [theme, layout.compact],
  );
  return (
    <View style={styles.screen}>
      <Text style={styles.title}>{name}</Text>
    </View>
  );
}

export default function contribute(client: PluginClientContext) {
  client.addWorkspacePanel({
    id: "overview",
    title: "Workspace overview",
    icon: "PanelsTopLeft",
    context: "workspace",
    Component: Overview,
  });
  client.addCommandCenterItem({
    id: "open-overview",
    title: "Open workspace overview",
    icon: "PanelsTopLeft",
    context: "workspace",
    onSelect({ openPanel }) {
      openPanel("overview");
    },
  });
  return () => {};
}

Use useWorkspace(id, selector) and useAgent(id, selector). Selectors are required and their results use shallow equality. Never select the whole snapshot or add an RPC to discover the active workspace or agent. Command callbacks receive the selected host's paseo, typed rpc(contract, input), openScreen({ screenId, params? }), and contextual openPanel(id) capabilities.

Add a screen and a sidebar item

Plugin screens use React Native primitives and work across desktop, browser, iOS, and Android. A sidebar item, in the header or the footer, is a component: render one or more SidebarRows from @getpaseo/plugin/client/ui, with SidebarSeparator between groups, and call openScreen or openPopover from them. A screen's title is a string or a function of its params; its component receives the params it was opened with. Color text from theme.colors and pad from layout.compact.

import type {
  PluginClientContext,
  PluginScreenProps,
  PluginSidebarItemProps,
} from "@getpaseo/plugin/client";
import { SidebarRow } from "@getpaseo/plugin/client/ui";
import { useMemo, useState } from "react";
import { Pressable, Text, View } from "react-native";

function Counter({ theme, layout }: PluginScreenProps) {
  const [count, setCount] = useState(0);
  const styles = useMemo(
    () => ({
      screen: {
        flex: 1,
        padding: layout.compact ? 16 : 24,
        gap: 16,
        backgroundColor: theme.colors.surface0,
      },
      count: { color: theme.colors.foreground, fontSize: layout.compact ? 36 : 48 },
      button: { padding: 14, borderRadius: 10, backgroundColor: theme.colors.accent },
      buttonText: { color: theme.colors.accentForeground, textAlign: "center" as const },
    }),
    [theme, layout.compact],
  );
  return (
    <View style={styles.screen}>
      <Text style={styles.count}>{count}</Text>
      <Pressable
        accessibilityRole="button"
        accessibilityLabel={`Increment counter, currently ${count}`}
        onPress={() => setCount((value) => value + 1)}
        style={styles.button}
      >
        <Text style={styles.buttonText}>Count me in</Text>
      </Pressable>
    </View>
  );
}

function CounterItem({ currentScreen, openScreen }: PluginSidebarItemProps) {
  return (
    <SidebarRow
      icon="ListPlus"
      active={currentScreen?.screenId === "main"}
      onPress={() => openScreen({ screenId: "main" })}
    />
  );
}

export default function contribute(client: PluginClientContext) {
  client.addScreen({ id: "main", title: "Counter", Component: Counter });
  client.addSidebarHeaderItem({ id: "main", title: "Counter", Component: CounterItem });
  return () => {};
}

Icons are Lucide icon names, inline SVG ({ svg }), or components; see Icons. theme is a typed PluginTheme on every screen and panel. Primary text uses theme.colors.foreground; labels use theme.colors.foregroundMuted; the root view uses theme.colors.surface0. layout.compact is true on mobile and narrow windows. Paseo owns the route, header, host picker, close action, error boundary, and per-installation query client.

Before writing imports, classify each module as shared, client, or server. Follow the SDK import boundaries, including transitive and type dependencies. The root is shared-only; hooks and client contexts belong to @getpaseo/plugin/client, server contexts to /server, and host UI to /client/ui. Install dependencies locally for typechecking; Paseo supplies host runtime modules. JSX uses the automatic runtime. Do not import /client/host from plugin code.

Works on mobile

Before reporting a plugin done:

  • Use React Native primitives only: View, Text, Pressable, ScrollView, and TextInput.
  • Do not use HTML elements, className, CSS strings, or onClick.
  • Do not put "DOM" in tsconfig.json or use /// <reference lib="dom" />. Put DOM globals only in client/web.ts, declare only what that module uses, gate every export on Platform.OS === "web", and provide the native alternative or a no-op.
  • Take colors from theme.colors.
  • Check the compact layout.

Run this audit on client/:

rg -n "document\.|window\.|localStorage|navigator\.|<[a-z]+[ >]|className=|onClick=" client/

A hit outside client/web.ts is a bug.

Choose the correct API

Use the existing Paseo SDK for normal Paseo operations. Use plugin RPC only for plugin-specific backend behavior.

Call Paseo from a surface

usePaseo() borrows the selected host's current connection. Never create another client inside a surface. It is the plugin's one client in every surface: release your subscriptions in your cleanup; plugin teardown ends the rest.

import { usePaseo } from "@getpaseo/plugin/client";

function PullRequestAction() {
  const paseo = usePaseo();

  async function createReviewWorkspace() {
    const workspace = await paseo.workspaces.create({
      title: "Review PR 42",
      source: {
        kind: "worktree",
        cwd: "/absolute/path/to/repository",
        action: "checkout",
        checkoutSource: { kind: "change_request", forge: "github", number: 42 },
      },
    });
    await workspace.agents.create({
      config: { provider: "codex/gpt-5.5" },
      prompt: "Review PR #42.",
    });
  }

  // Wire createReviewWorkspace to a Pressable.
  return null;
}

The API covers workspaces, agents, providers, and daemon config. It omits connection lifecycle because Paseo owns the connection. Consult the current SDK reference for method details.

Add daemon-side behavior

Define one Zod contract in shared/, register its subprocess handler in index.server.ts, and call it from client code with useRpc():

// shared/greeting.ts
import { defineRpc } from "@getpaseo/plugin";
import { z } from "zod";

const greeting = defineRpc({
  name: "greeting.create",
  input: z.object({ name: z.string() }),
  output: z.object({ message: z.string() }),
});
// server/greeting.ts
import type { RpcInput } from "@getpaseo/plugin";
import { greeting } from "../shared/greeting";

export async function createGreeting({ name }: RpcInput<typeof greeting>) {
  return { message: `Hello, ${name}!` };
}
// index.server.ts
import type { PluginServerContext } from "@getpaseo/plugin/server";
import { createGreeting } from "./server/greeting";
import { greeting } from "./shared/greeting";

export default function contribute(server: PluginServerContext) {
  server.handle(greeting, createGreeting);
  return () => {};
}
// client/greeting.tsx
import { useRpc } from "@getpaseo/plugin/client";
import { greeting } from "../shared/greeting";

function Greeting() {
  const createGreeting = useRpc(greeting);
  // Use createGreeting({ name: "Ada" }) in a query, mutation, or event.
  return null;
}

Inputs and outputs are validated on both sides. Backend handlers receive the same PaseoApi as { paseo }; their IPC-backed daemon session lives exactly as long as the subprocess. Backend code can use Node APIs and installed dependencies. Keep credentials, filesystem access, shell commands, and vendor API calls in the handler rather than the client surface.

Use TanStack Query for async request state, caching, and mutations.

Debug daemon-side behavior

Backend contributions can use normal Node logging. console.log() writes to the plugin's stdout; console.error() writes to stderr. Paseo captures both streams without interfering with plugin IPC.

Inspect recent output after install, reload, an RPC failure, or a subprocess crash:

paseo plugin logs my-plugin
paseo plugin logs my-plugin --json
paseo plugin logs my-plugin --host <url>

The same tail is available from Settings → Plugins → Logs. It includes initialization, handler, cleanup, and final crash output. Reload, disable, and process failure retain the tail. Removing the plugin clears it; restarting the daemon clears the in-memory tail. Structured copies also go to the daemon log. Never log credentials or other secrets.

Add a composer attachment source

Define a search RPC and declarative source in shared/, handle it on the server, and register it on the client:

// shared/issues.ts
import { defineAttachmentSource, defineRpc } from "@getpaseo/plugin";
import { z } from "zod";

const searchIssues = defineRpc({
  name: "issues.search",
  input: z.object({ query: z.string() }),
  output: z.object({
    items: z.array(
      z.object({
        id: z.string(),
        identifier: z.string(),
        title: z.string(),
        subtitle: z.string().optional(),
        url: z.string().url(),
        text: z.string(),
        resourceType: z.string(),
      }),
    ),
  }),
});

const issues = defineAttachmentSource({
  id: "issues",
  title: "Acme issue",
  icon: "CircleDot",
  pickerTitle: "Attach Acme issue",
  searchPlaceholder: "Search by identifier or title",
  search: searchIssues,
});
// index.server.ts
import type { PluginServerContext } from "@getpaseo/plugin/server";
import { searchIssues } from "./shared/issues";

export default function contribute(server: PluginServerContext) {
  server.handle(searchIssues, ({ query }) => searchAcmeIssues(query));
  return () => {};
}
// index.client.tsx
import type { PluginClientContext } from "@getpaseo/plugin/client";
import { issues } from "./shared/issues";

export default function contribute(client: PluginClientContext) {
  client.addAttachmentSource(issues);
  return () => {};
}

Return complete text snapshots. Paseo owns the composer menu, picker, pills, drafts, and submission. Credentials and vendor calls stay in the daemon handler.

Add a client slash command

A slash command runs plugin code in the app when the user submits /name args. Nothing is sent to the agent. args is the raw text after the command name, trimmed; parse it in the plugin.

client.addSlashCommand({
  name: "review",
  description: "Run the review bot",
  argumentHint: "[scope]",
  context: "agent", // or "workspace" so drafts get it too
  async onSubmit({ args, agent, rpc, openPanel }) {
    await rpc(startReview, { agentId: agent.id, scope: args });
    openPanel("review");
  },
});

The callback receives the same context as the matching Command Center item plus args. Paseo owns the autocomplete row, input clearing, and the error toast; put pending UI in a pill or panel. Precedence is built-in client commands, then plugin commands, then provider commands; a lower-precedence collision is dropped. Commands do not run while the composer has attachments. Server-side slash commands do not exist.

Add a composer pill

A pill is a per-agent button in the composer track bar next to Tasks and Subagents. Add and remove pills from the client entry lifecycle. addComposerPill exists on PluginClientContext.

import type { PluginButtonRegistration, PluginClientContext } from "@getpaseo/plugin/client";

export function contributeClient(client: PluginClientContext) {
  const pills = new Map<string, PluginButtonRegistration>();
  const lifetime = new AbortController();
  const register = (agent: { id: string; workspaceId?: string | null }) => {
    if (lifetime.signal.aborted || !agent.workspaceId) return;
    const agentId = agent.id;
    const workspaceId = agent.workspaceId;
    pills.get(agentId)?.remove();
    pills.set(
      agentId,
      client.addComposerPill({
        id: "review",
        workspaceId,
        agentId,
        button: {
          title: "Open review",
          icon: "Scan",
          label: "Review",
          behavior: {
            kind: "action",
            onPress() {
              client.openPanel("review", { workspaceId, agentId });
            },
          },
        },
      }),
    );
  };
  const removeAll = () => {
    for (const pill of pills.values()) pill.remove();
    pills.clear();
  };
  // An owned list subscription delivers existing agents, then updates, and a fresh snapshot after a reconnect.
  void client.paseo.agents
    .list({ subscribe: {}, signal: lifetime.signal })
    .then(({ subscription }) => {
      subscription.subscribe({
        snapshot: ({ entries }) => {
          removeAll();
          for (const { agent } of entries) register(agent);
        },
        update: (message) => {
          if (message.type !== "agent_update") return;
          const update = message.payload;
          if (update.kind === "upsert") return register(update.agent);
          pills.get(update.agentId)?.remove();
          pills.delete(update.agentId);
        },
      });
      return undefined;
    })
    .catch((error) => {
      if (!lifetime.signal.aborted) console.error("Agent observation failed", error);
    });
  return () => {
    lifetime.abort();
    removeAll();
  };
}

Call contributeClient(client) from index.client.tsx, or move its body into that entry. The button descriptor sets the icon, label, and behavior; Paseo owns the pressable, chrome, pending state, error reporting, and placement. addComposerPill returns a registration with update() and remove(). remove() is idempotent, and Paseo removes every pill when the plugin, client entrypoint, or host connection is torn down.

Transform and render timeline items

Timeline transformers and renderers are client contributions. A transformer selects one built-in AgentTimelineItem.type, inspects the item, and returns zero or more versioned plugin items. undefined keeps the source item, items replaces it, [] removes it. A renderer draws one kind and version after validating data with its Zod schema.

client.addTimelineTransformer({
  id: "inline-thinking",
  query: { itemType: "reasoning" },
  transform: ({ item, phase }) => ({
    items: [
      { type: "plugin", kind: "inline-thinking", version: 1, data: { text: item.text, phase } },
    ],
  }),
});
client.addTimelineRenderer({
  kind: "inline-thinking",
  version: 1,
  schema: z.object({ text: z.string(), phase: z.enum(["streaming", "complete"]) }),
  Component: InlineThinking,
});

Transformers run while the render model is built, on fetched history and on every live update, so phase is "streaming" for a loading thought or running tool call. Identity comes from the source item, so a streaming item keeps its mounted component; set an output id when one source explodes into several items. Transformers must be synchronous and deterministic, data must be JSON, and a transformer that throws is logged and skipped. Render <StreamedText text={text} phase={phase} /> from @getpaseo/plugin/client/ui for paced assistant text; use its style prop to customize text appearance. plugin-examples/inline-thinking replaces the thinking row with inline text; plugin-examples/timeline-items replaces a todo tool call with a task card.

Append a timeline row from the daemon

A server handler can push a plugin-owned row into any agent timeline. The same renderer registration draws it.

server.handle(publishReview, async ({ agentId, verdict }, { paseo }) => {
  await paseo.agents.ref(agentId).timeline.append({
    type: "plugin",
    id: "review",
    kind: "review-result",
    version: 1,
    data: { verdict },
  });
  return {};
});

The daemon stamps pluginId from the plugin session, so only plugin code can call this. Re-appending with the same id replaces the earlier row live and on refetch, which is how a plugin updates a row. data is capped at 64 KiB serialized and rejected above that. Rows live in the daemon's in-memory timeline and survive scroll, refetch, and reconnect, but not a daemon restart. A row whose plugin is missing renders an unavailable placeholder. Hosts advertise support through server_info.features.pluginTimelineItems.

Contribute a theme

addTheme takes a small light or dark palette; Paseo expands it into the full token set. Every color is a hex string.

client.addTheme({
  id: "mocha",
  name: "Catppuccin Mocha",
  appearance: "dark",
  colors: {
    background: "#1e1e2e",
    foreground: "#cdd6f4",
    raised: "#313244",
    control: "#45475a",
    border: "#45475a",
    accent: "#cba6f7",
    mutedForeground: "#a6adc8",
    ring: "#6c7086",
  },
});

It appears under Settings → Appearance. A client that predates addTheme cannot evaluate the entry and reports client.addTheme is not a function; update the client. See plugin-examples/catppuccin.

Hosts and trust

Plugins are installed per daemon and are trusted, unsandboxed code. Backend code can access files, processes, credentials, and network services on the daemon machine. Client contributions run inside the Paseo app. Do not install a plugin the user has not authorized or source code you have not inspected.

Check the global switch before installing

Identify the target daemon and inspect its root pluginsEnabled value in config.json. For the local daemon, paseo daemon status --json reports its home; the file is <home>/config.json. Treat a missing field as false. Do not infer the global value from a plugin's disabled status, because an individual plugin can also be disabled.

If pluginsEnabled is already true, continue without asking the user to enable it.

If it is false or absent, stop and ask the user for explicit permission before editing or enabling anything. Include this warning in the request:

Plugins are trusted, unsandboxed code. Backend plugin code can access your daemon machine, including files, processes, credentials, and network services. Client plugin code runs inside the Paseo app. May I enable plugins on this daemon?

Do not continue unless the user agrees. After permission:

  1. Preserve the rest of config.json and set the root pluginsEnabled field to true.
  2. Run paseo reload --json against that daemon.
  3. Require pluginsEnabled in appliedPaths, or accept an empty appliedPaths only after re-reading the file and confirming the live plugin catalog is enabled.
  4. Run paseo plugin ls and verify the intended plugin reaches running after installation.

If the user asks to disable the global switch, set pluginsEnabled to false, run paseo reload --json, and verify configured plugins report disabled.

Do not edit a local config when the target is a remote daemon. Perform the edit on the daemon machine, or ask the user to use Settings → Plugins → Enable plugins. paseo reload --host <url> reloads the remote daemon's own file but does not edit it.

When the same screen or sidebar item exists on several connected hosts, Paseo shows it once. The screen header has a host picker; a sidebar item uses the host of the screen on display, else the host last picked in one of the plugin's screens. The selected host owns the bundle, SDK calls, RPCs, and query cache. An offline selected host does not fall through to another host. Attachment sources stay scoped to the composer's host.

Publishing a listing

Write OVERVIEW.md beside paseo-plugin.json for the plugin page inside Paseo. It helps someone decide whether to install your plugin; the install command is already at the top of that page. A README assumes a GitHub audience and carries installation instructions, technical details, and badges. Long, AI-generated READMEs make people read past that material to understand what a plugin does.

OVERVIEW.md is required to list your plugin in the registry. Commit it beside paseo-plugin.json in the source repository at the pinned commit. The registry resolves it relative to the manifest, under pluginPath for monorepos. Include it in the published npm package too; the scaffold's files list includes it. Replace the scaffold's guidance comment with useful facts before publishing.

The repository overview takes precedence over a registry import stopgap. Online validation fails when the pinned commit has no OVERVIEW.md, except for unchanged imported records that already carry plugins/<owner>/<slug>.md in the registry repository. Every version bump requires a repository overview and removes the stopgap in the same PR. A bump without the repository overview fails validation. README files do not supply the overview.

Author overviews and registry stopgaps follow the same content contract, in this order. Choose headings only when they help; length follows complexity. A theme needs one paragraph.

  1. Explain what the plugin is and does in plain terms first.
  2. Explain how it works only when that is not obvious.
  3. Explain setup when needed: settings, accounts, tokens, providers, external tools, or other plugins. Include applicable daemon version and operating system requirements. Setup guidance is allowed; installation instructions are not.
  4. Explain capabilities and settings worth understanding, what each option does, what the plugin reads or sends and where, permissions, and known limits.

Use sentence case and plain factual language, with no em dashes. Omit installation commands, badges, changelog, contributing or license sections, marketing, and unsupported claims. Avoid implementation filler such as empty cleanup functions, catalogs of theme-token fields, or lists of absent features. Keep only what helps someone choose the plugin.

If your plugin is an unchanged imported record, you can propose an author-written overview to replace the registry stopgap. Do not add import credit to your own OVERVIEW.md.

See Your listing page.

Typecheck and manage

When editing a plugin, typecheck its source before install or reload:

npm run typecheck
paseo plugin install /absolute/path/to/plugin
paseo plugin install /absolute/path/to/plugin --id another-runtime-id
paseo plugin install npm:@acme/paseo-review
paseo plugin install npm:@acme/paseo-review@1.2.0
paseo plugin install github:owner/repository
paseo plugin install github:owner/repository --ref main
paseo plugin update my-plugin
paseo plugin ls
paseo plugin reload my-plugin
paseo plugin logs my-plugin
paseo plugin disable my-plugin
paseo plugin enable my-plugin
paseo plugin remove my-plugin

For npm, ensure npm is on the daemon's PATH; use that host's registry configuration and credentials. Install selectors choose content once; they do not pin updates. install and add are aliases. Follow the publishing guide for Paseo's package contents and preparation requirements; standard npm publishing commands apply.

Use --host <url> when managing a daemon other than the CLI default. A Git source that must install or generate something declares build in paseo-plugin.json as a list of argv arrays; Paseo runs them without a shell on install and update and keeps the old version if one fails. Plugin source edits require paseo plugin reload; config changes to the global switch require paseo reload. A failed plugin reload stays failed; inspect paseo plugin ls for the load error and paseo plugin logs <id> for subprocess output, fix the source, typecheck, and reload again. remove keeps local source directories and deletes managed Git/npm installations.

Do not restart the daemon to load source changes. Restarting it can kill the agent performing the work.

For an old mixed entry, follow the standalone runtime-entry migration guide mechanically.

Verify the outcome

After a change:

  1. Run npm run typecheck.
  2. Install or reload the exact runtime ID.
  3. Run paseo plugin ls and require running with no error.
  4. Confirm the contribution on the intended host. Open the Command Center with ⌘K (macOS) or Ctrl+K (Windows/Linux). Type / in the composer for slash commands. For timeline work, run an agent turn that produces the source item and watch it while it streams, not only after it completes. For UI work, check a wide desktop window and a compact/mobile client, and switch theme to confirm text still uses foreground / foregroundMuted.
  5. Exercise the changed action or RPC, including its error state.

Common failures:

  • Missing sidebar item: wrong host, plugin not running, the item is hidden in Settings > Sidebar, or its component throws (a SidebarRow with an unknown Lucide name or unsafe SVG throws).
  • Unavailable client module: client bundles can use only the host-provided modules listed above.
  • RPC rejection: input or output failed its Zod schema, or the handler threw. Inspect paseo plugin logs <id> for handler output.
  • Plugin exits or reload fails: inspect paseo plugin ls for status and paseo plugin logs <id> for initialization, cleanup, or crash output.
  • Stale UI: source was edited without paseo plugin reload <id>.
  • Timeline item shows "Plugin timeline item unavailable": no renderer registered for that kind and version, the renderer schema rejected data, or the plugin is not running on that host.
  • Transformer has no effect: query.itemType does not match the source type, the transform returned undefined, or it threw and was skipped; check the app console for [Plugins] Timeline transformer failed.
  • Slash command not offered: name collides with a built-in or another plugin, the context is agent on a draft, or the composer has attachments.
  • Append rejected: the caller is not a plugin session, data exceeds 64 KiB, or the host predates features.pluginTimelineItems.

FAQ

What does the paseo-plugin skill do?

Build and manage trusted local Paseo plugins. Use when the user asks to create, edit, install, reload, enable, disable, remove, or troubleshoot a Paseo plugin; add lifecycle hooks; transform agent configuration, environment, MCP servers, or workspace creation; automate permissions or turn follow-ups; add a screen, sidebar header or footer item, workspace panel, or file panel; add Command Center items or slash commands; add composer pills or attachment sources; transform, render, or append agent timeline items; contribute a theme; use Paseo from plugin code; or add plugin RPCs.

How do I install paseo-plugin?

Run npx skills add https://github.com/getpaseo/paseo --skill paseo-plugin in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/paseo-plugin/ (personal) or .claude/skills/paseo-plugin/ (project).

Which AI agents can use paseo-plugin?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is paseo-plugin safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains paseo-plugin?

It is published by getpaseo in getpaseo/paseo. It has 1,394 recorded installs.

More from getpaseo/paseo

View all →

paseo

getpaseo/paseo

Paseo reference for managing hosts, projects, workspaces, workspace scripts, agents, schedules, and heartbeats.

5.3K installs

paseo-handoff

getpaseo/paseo

Hand off the current task to another agent with full context. Use when the user says "handoff", "hand off", "hand this to", or wants to pass work to another agent.

paseo-committee

getpaseo/paseo

Form a committee of two high-reasoning agents to step back, do root cause analysis, and produce a plan. Use when stuck, looping, tunnel-visioning, or facing a hard planning problem.

paseo-advisor

getpaseo/paseo

Spin up a single agent as an advisor — second opinion on the current task. Use when the user says "advisor", "second opinion", "what does X think", or wants an outside take without delegating the work itself.

paseo-loop

getpaseo/paseo

Run an agent loop until an exit condition is met. Use when the user says "loop", "babysit", "keep trying until", "check every X", "watch", or wants iterative autonomous execution.

2.6K installs

release-stable

getpaseo/paseo

Cut a stable release of Paseo (fresh patch or minor, or promote from beta). Use when the user says "release stable", "ship stable", "promote", "release:patch", "release:minor", "release:promote", or "/release-stable".

2.3K installs

Popular frontend & design skills

View category →

setup-matt-pocock-skills

mattpocock/skills

Configure this repo for the engineering skills: set up its issue tracker, triage label vocabulary, and domain doc layout. Run once before first use of the other engineering skills.

Frontend & Design1.1M installs

frontend-design

anthropics/skills

Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.

OfficialFrontend & Design1M installs

hyperframes-animation

heygen-com/hyperframes

All animation knowledge for HyperFrames — atomic motion rules, multi-phase scene blueprints, scene transitions, broader motion-design techniques, AND the seven runtime adapters (GSAP default, plus Lottie, Three.js, Anime.js, CSS keyframes, Web Animations API, TypeGPU). Use for any motion or animation task: pick 2-4 rules and compose, or load a blueprint, or look up runtime-specific API (e.g. GSAP eases / Lottie player / Three.js mixer). Also covers auditing an existing composition's choreography (animation map) and 24 named text-animation effects. HyperFrames-native: single paused timeline, seek-safe, deterministic.

prototype

mattpocock/skills

Build a throwaway prototype to answer a design question. Use when the user wants to sanity-check whether a state model or logic feels right, or explore what a UI should look like.

Frontend & Design999.2K installs

hyperframes-core

heygen-com/hyperframes

The HyperFrames composition contract — build one renderable project. Use for composition structure, the data- timing attributes, class="clip", tracks, sub-compositions, variables, framework-owned media playback, deterministic-render rules, and validation. Read before writing composition HTML.

Frontend & Design930.5K installs

vercel-react-best-practices

vercel-labs/agent-skills

React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.

OfficialFrontend & Design843.1K installs