TypeScript SDK Guide

pm-cli ships a fully typed TypeScript SDK at @unbrained/pm-cli/sdk. This guide covers every capability type with working examples.

Installation

npm install @unbrained/pm-cli

The SDK is included with the CLI โ€” no separate package needed. Import from the sub-path:

import { defineExtension, type ExtensionApi } from "@unbrained/pm-cli/sdk";

Extension anatomy

Every extension is a module that exports a default value from defineExtension:

// index.ts
import { defineExtension, type ExtensionApi } from "@unbrained/pm-cli/sdk";

export default defineExtension({
  name: "my-ext",
  version: "0.1.0",
  activate(api: ExtensionApi) {
    // register capabilities here
  },
});

Paired with a manifest.json:

{
  "name": "my-ext",
  "version": "0.1.0",
  "entry": "index.js",
  "capabilities": ["commands"]
}

Note: entry points to the compiled JS output. Compile with tsc or esbuild before installing.

Capability 1: Custom commands

api.registerCommand({
  name: "greet",
  description: "Print a greeting.",
  intent: "verify extension activation",
  examples: ["pm greet", "pm greet --name Alice"],
  arguments: [
    { name: "subject", description: "Who to greet", optional: true },
  ],
  flags: [
    { long: "--name", value_name: "str", description: "Override greeting name" },
  ],
  run: async (ctx) => {
    const name = (ctx.options.name as string) ?? ctx.args[0] ?? "world";
    return { ok: true, message: `Hello, ${name}!` };
  },
});

The run function receives a CommandHandlerContext:

interface CommandHandlerContext {
  command: string;          // "greet"
  args: string[];           // positional args
  options: Record<string, unknown>;  // parsed flags
  global: GlobalOptions;    // --json, --quiet, --path, etc.
  pm_root: string;          // absolute path to .agents/pm/
}

Capability 2: Schema โ€” custom item types

api.registerItemTypes([
  {
    name: "Incident",
    folder: "incidents",
    aliases: ["incident", "inc"],
    required_create_fields: ["title", "description", "severity"],
    options: [
      { key: "severity", values: ["critical", "major", "minor"], required: true },
      { key: "service", values: ["api", "web", "worker"] },
    ],
  },
]);

api.registerItemFields([
  { name: "severity", type: "string" },
  { name: "service",  type: "string", optional: true },
]);

After registering:

pm create --type incident --title "DB down" --severity critical --service api

Capability 3: Schema โ€” custom flags

Inject additional flags into any built-in command:

api.registerFlags("create", [
  {
    long: "--severity",
    value_name: "level",
    description: "Incident severity: critical | major | minor",
  },
]);

Capability 4: Importers

api.registerImporter("json-fixtures", {
  description: "Import Incident fixtures from a JSON file.",
  run: async (config: Record<string, unknown>) => {
    const file = config.file as string;
    const fixtures = JSON.parse(await fs.readFile(file, "utf-8"));
    return fixtures.map((f: any) => ({
      type: "Incident",
      title: f.title,
      body: f.description ?? "",
      status: f.status === "closed" ? "closed" : "open",
      metadata: { severity: f.severity, service: f.service },
    }));
  },
});

Usage:

pm extension import json-fixtures --config file=./incidents.json

Capability 5: Lifecycle hooks

api.hooks.beforeCommand((ctx) => {
  if (ctx.command === "close" && process.env.PM_AUDIT) {
    console.error(`[audit] close attempt by ${ctx.global.author ?? "unknown"}`);
  }
});

api.hooks.afterCommand((ctx, result) => {
  if (ctx.command === "create") {
    console.error(`[audit] created: ${(result as any)?.id ?? "?"}`);
  }
});

api.hooks.onWrite((item) => {
  // Called after every item write โ€” good for webhooks
});

Capability 6: Renderers

Override TOON or JSON output for a specific format:

api.registerRenderer("toon", {
  render: (data: unknown) => {
    if (data && typeof data === "object" && "id" in data) {
      const item = data as Record<string, unknown>;
      return `id: ${item.id}\nstatus: ${item.status}\n`;
    }
    return JSON.stringify(data, null, 2);
  },
});

Capability 7: Preflight

Gate destructive operations:

api.registerPreflight({
  command: "close",
  run: async (ctx) => {
    const id = ctx.args[0];
    // Read the item to check its type and severity
    const item = ctx.item as Record<string, unknown> | undefined;
    if (item?.type === "Incident" && !item?.severity) {
      return {
        allow: false,
        reason: "Incidents must have a severity set before closing.",
      };
    }
    return { allow: true };
  },
});

Capability 8: Services

Override built-in services for formatting, error handling, etc.:

api.registerService("output_format", {
  override: (format: string) => {
    if (format === "toon" && process.env.PM_FORCE_JSON) return "json";
    return format;
  },
});

Capability 9: Search providers

Plug in a custom embedding / retrieval backend:

api.registerSearchProvider({
  name: "my-openai",
  embed: async (ctx) => {
    // Return a float32 embedding vector for ctx.text
    const resp = await fetch("https://api.openai.com/v1/embeddings", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ model: "text-embedding-3-small", input: ctx.text }),
    });
    const json = await resp.json() as { data: { embedding: number[] }[] };
    return json.data[0].embedding;
  },
  query: async (ctx) => {
    // Return scored hits: [{ id, score }]
    return { hits: [] };
  },
});

Complete reference extension

See the ts-sdk-starter example โ€” a single TypeScript file demonstrating all 9 capabilities, ready to install:

pm install github.com/unbraind/pm-cli-companion/examples/ts-sdk-starter --project

Extension manifest

All capability strings must be declared in manifest.json:

Capability Registration method
commands registerCommand
schema registerFlags, registerItemTypes, registerItemFields, registerMigration
importers registerImporter / registerExporter
hooks api.hooks.*
renderers registerRenderer
preflight registerPreflight
services registerService
search registerSearchProvider, registerVectorStoreAdapter
parser registerParser

Debugging

# Check extension health
pm extension doctor --detail deep --trace

# Activate with verbose output
PM_EXT_DEBUG=1 pm <any-command>

# Validate manifest
pm extension validate ./my-ext

TypeScript SDK Guide local
Report an issue