What an extension package is

An extension is a directory with a package.json manifest, one or more TypeScript factory modules, and whatever capability folders you want to ship alongside them. The runtime piece — what pi.registerTool, pi.registerCommand, and pi.on actually run — is the factory module the manifest points at. Everything else in the directory (skills, hooks, custom tools, prompt templates, an mcp.json, themes) is picked up by omp’s existing discovery surfaces the moment the package is on the load path. The manifest is the only file that has to exist; the rest is convention.

This page is about packaging. For the individual surfaces, see Skills, Hooks, Custom tools, and MCP.

The manifest

omp reads one field from package.json: omp.extensions. It is an array of entry paths, each resolved relative to the package root. Each path is a .ts or .js module that default-exports a factory taking ExtensionAPI.

{
  "name": "my-extension",
  "version": "0.1.0",
  "omp": {
    "extensions": ["./src/main.ts"]
  }
}

A package may declare several entries — useful when one bundle wants to keep, say, the safety hook separate from the productivity tools:

{
  "omp": {
    "extensions": ["./src/safety.ts", "./src/tools.ts"]
  }
}

The legacy key pi.extensions is still accepted with the same shape; new packages should use omp.extensions.

Directory layout

omp discovers capabilities by directory name, not by manifest field. If you ship the conventional folders next to your factory, they are loaded as if the user had placed them under ~/.omp/agent/ themselves.

my-extension/
  package.json          ← omp.extensions manifest
  src/
    main.ts             ← extension factory (registers tools, commands, events)
  skills/
    my-skill/
      SKILL.md          ← on-demand playbook
  hooks/
    pre/
      block-rm.ts       ← legacy HookAPI module
  tools/
    my-tool/
      index.ts          ← custom tool factory
  prompts/
    review.md           ← prompt template
  mcp.json              ← additional MCP servers
  themes/
    midnight.json       ← theme
  README.md

Sub-discovery rules match the standalone surfaces: skills are one directory deep under skills/, hooks under hooks/pre/ and hooks/post/, custom tools at tools/<name>/index.ts. The factory in src/main.ts runs in addition to (not instead of) those.

A complete example

A minimal package that registers one tool, one slash command, and ships a skill:

{
  "name": "@acme/notes",
  "version": "1.0.0",
  "description": "Notes search tool plus a writing-style skill",
  "omp": {
    "extensions": ["./src/main.ts"]
  }
}
// src/main.ts
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent"
 
export default function notes(pi: ExtensionAPI) {
  const { z } = pi.zod
 
  pi.registerCommand("notes", {})
}
description: "Open today's note",
handler: async (_args, ctx) => ctx.ui.notify("Opened notes", "info"),

});

pi.registerTool({ name: “search_notes”, label: “Search Notes”, description: “Full-text search through project notes”, parameters: z.object({ query: z.string() }), async execute(_id, params) { return { content: [{ type: “text”, text: Searched: ${params.query} }], details: { query: params.query }, }; }, }); }



`skills/notes-style/SKILL.md` and the rest of the conventional folders need no wiring — drop them in and omp finds them when the package is on the load path.


## Test it locally


Three ways to load a package while developing it. All three are equivalent from the runtime's point of view; pick whichever matches how you want to iterate.



- **Point settings at the directory.** Add the absolute path to `extensions` in `~/.omp/agent/config.yml`:


```yaml
extensions:
  - /path/to/my-extension
  • One-shot via the CLI. omp --extension ./my-extension loads the package for a single session. --hook is the same flag under a different name.

  • Install as a plugin. omp install ./my-extension (or -l ./my-extension for project scope) symlinks the directory into the plugin set and watches it for changes — the right choice when you want to dogfood it alongside your real toolset. See Plugins.

Confirm what loaded with omp -p '/extensions'. Run with --log-level debug to see the per-surface load lines.

Ship it through a marketplace

A marketplace is a Git repository with a .claude-plugin/marketplace.json catalog listing one or more packages. The catalog is where versioning and metadata live — name, version, author, category, tags, homepage — not the package itself. A minimal entry pointing at a sibling directory in the same repo:

{
  "name": "acme-plugins",
  "owner": { "name": "Acme Corp" },
  "plugins": [
    {
      "name": "notes",
      "version": "1.0.0",
      "category": "productivity",
      "source": "./plugins/notes"
    }
  ]
}

Push the repo; users add it with omp marketplace add owner/repo and install with omp install notes@acme-plugins. Pin versions by tagging the marketplace repo and updating the catalog’s version field — installs respect the pin. See Marketplace for the full catalog schema and source types (Git URL, GitHub shorthand, git-subdir, npm).

  • Marketplace — catalog schema and publishing workflow.

  • Plugins — install, scope, and update mechanics.

  • Skills — playbooks bundled under skills/.

  • Hooks — event interceptors bundled under hooks/.\n

Ask Docs

AI assistant to help answer questions about the documentation. Answers are read-only and cite docs/source.

Hi! How can I help you with the documentation today? Answers are read-only and cite docs/source.

Ctrl+Enter to send