@tadasant/pi-plugins

AIR plugin support for the Pi coding agent: resolve an AIR plugin and activate the skills, hooks, and MCP servers it bundles inside a Pi session

Packages

Package details

extension

Install @tadasant/pi-plugins from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@tadasant/pi-plugins
Package
@tadasant/pi-plugins
Version
0.2.0
Published
Sep 6, 2026
Downloads
205/mo · 25/wk
Author
tadasant
License
MIT
Types
extension
Size
164.6 KB
Dependencies
1 dependency · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./extensions/plugins.ts",
    "node_modules/@tadasant/pi-hooks/extensions/hooks.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

@tadasant/pi-plugins

AIR plugin support for the Pi coding agent. Resolve an AIR plugin and activate the skills, hooks, and MCP servers it bundles inside a real Pi session.

pi install npm:pi-mcp-adapter      # required peer
pi install npm:@tadasant/pi-plugins

What an AIR plugin is, and why Pi needs this

AIR is a vendor-neutral framework for AI artifacts. It defines six artifact types — skills, references, MCP servers, plugins, roots, and hooks — and a plugin is the compositional one: a manifest that bundles other artifacts by ID rather than a directory of content.

// plugins.json — a thin registry
{
  "code-quality": {
    "description": "Linting, formatting, and coding-standards skills",
    "path": "./code-quality",
    "default_in_roots": ["*"]
  }
}
// code-quality/.plugin/plugin.json — the body
{
  "title": "Code Quality Suite",
  "version": "1.2.0",
  "skills": ["lint-fix", "format-check"],
  "mcp_servers": ["eslint-server"],
  "hooks": ["lint-pre-commit"]
}

Pi cannot consume any of that. Pi has its own package format for distributing Pi extensions, which is how this package reaches you — but an AIR plugin is a different artifact type from a different ecosystem, and nothing in Pi reads it. This package is the Pi adapter for AIR, the same role @pulsemcp/air-adapter-opencode plays for OpenCode.

What it activates

AIR artifact How it reaches Pi
skills Contributed through Pi's own resources_discover event as skillPaths. An AIR skill is a directory containing SKILL.md, which is exactly what Pi discovers, so they load like any other skill.
hooks Each HOOK.json is translated into a @tadasant/pi-hooks definition and dispatched by that engine. This package bundles it, so there is no second install and no second hook path.
MCP servers Translated into the .pi/mcp.json that pi-mcp-adapter reads, so that adapter starts and supervises them.

Configuration

Point the adapter at an AIR config. Discovery, in order:

Location Notes
$PI_PLUGINS_CONFIG Explicit path; replaces discovery entirely
./air.json In Pi's working directory
./.air/air.json

Which plugins activate follows AIR's own rule — membership is declared on the artifact via default_in_roots, where "*" means every root:

Variable Effect
PI_PLUGINS Comma-separated plugin IDs. Overrides default_in_roots entirely.
PI_PLUGINS_ROOT Activates plugins naming this root in default_in_roots.

Run /plugins inside Pi to see what resolved, and /plugins reload after editing.

Event mapping

AIR's lifecycle vocabulary is agent-agnostic and broader than Pi's surface:

AIR event Pi event
session_start session_start
session_end session_shutdown
pre_tool_call tool_call (can block)
post_tool_call tool_result
user_prompt_submit user_prompt (can block)
stop agent_settled

Claude Code's PascalCase spellings (SessionStart, PreToolUse, …) are accepted as identity mappings, matching AIR's own behaviour.

pre_commit, post_commit, subagent_stop, notification, and pre_compact are not activated. Pi has no git-commit lifecycle, no subagent concept, and no extension-visible notification event, and pi-hooks does not currently expose compaction. A hook using one of those loads with a named warning rather than silently never firing — visible on stderr and in /plugins.

An AIR matcher becomes a pi-hooks matcher scoped to the event: tool name or input.command on the tool events, prompt text on user_prompt_submit. Matching is case-insensitive and Claude Code's tool names (Bash, Edit, Write, …) are aliased onto Pi's, since this bridge accepts Claude's event spellings and will therefore be handed Claude-authored hooks.

command runs through a shell when the hook declares no args — AIR defines it as "Shell command to execute", so foo && bar is valid — and as an argv pair when args are present. Either way it runs from the hook's own directory, so a relative ./notify.sh resolves. timeout_seconds becomes timeoutMs. env values and the merged x-config get ${VAR} interpolation and reach the script as ordinary environment variables (AIR_HOOK_CONFIG, plus AIR_HOOK_ID) — never disk.

A hook's non-zero exit blocks the event where Pi allows blocking, which is what makes an AIR guardrail a guardrail.

Required peers

Supporting AIR plugins means supporting what a plugin bundles. Skills, Pi already does natively. The other two come from extensions this package composes with rather than reimplements:

Peer How it is required Why
@tadasant/pi-hooks Bundled — shipped inside this package and listed in pi.extensions. 17 KB, and Pi requires a pi package to bundle another whose extension it references by path. You never install it separately.
pi-mcp-adapter A declared peer you install: pi install npm:pi-mcp-adapter It carries native keychain binaries for every platform; vendoring it would put a ~36 MB tarball on npm for something most Pi users already have.

So a complete install is two commands:

pi install npm:pi-mcp-adapter      # required peer: runs the MCP servers plugins bundle
pi install npm:@tadasant/pi-plugins

If the adapter is missing, this package says so and keeps going. The servers are still written to .pi/mcp.json — they start the moment you install the adapter — and startup logs plus /plugins state plainly that it is not installed. A plugin that is silently half-activated is the failure this avoids.

How the MCP handoff works

pi-mcp-adapter loads its config when its extension factory runs, not on session_start. So this package resolves plugins and writes .pi/mcp.json in its own factory, before the adapter's runs.

Writes are conservative. Servers this package owns are tagged with an x-pi-plugins provenance key, so:

  • hand-written entries are never modified — and names claimed by the other configs the adapter merges first (~/.config/mcp/mcp.json, the Pi global config, and <cwd>/.mcp.json) are reserved too, since its merge is a shallow later-wins spread that would otherwise blend our entry into someone else's;
  • a plugin's server whose natural name is already taken is written under its qualified name instead, and the rename is reported — rather than dropped or overwritten;
  • servers written for a plugin that is no longer active are removed on the next run;
  • a malformed .pi/mcp.json is left completely alone.

${VAR} interpolation is applied to command, args, env, url, headers, and the OAuth block, matching AIR's own secrets handling.

Scope

Local (filesystem) catalogs only. AIR's remote catalog providers (github://…) are a separate extension surface; a catalog, plugin, hook, or skill path that looks like a provider URI produces a named warning telling you to clone it locally, rather than being silently skipped.

Composition

Plugins composing plugins works as AIR specifies: children expand depth-first, a parent's direct declarations win over inherited ones, IDs are deduplicated, and circular references are rejected by name at resolution time.

Degradation is deliberate throughout. A missing manifest, an unresolvable artifact ID, a skill directory that does not exist, or one plugin that fails to resolve produces a warning naming the offender and leaves everything else working.

Security

An AIR plugin's hooks execute arbitrary commands with your permissions, and its skills can instruct the model to do anything. Read a catalog before you point Pi at it, the way you would read a shell script from someone else.

License

MIT