coderClaw

Plugins (Extensions)

Quick start (new to plugins?)

A plugin is just a small code module that extends CoderClaw with extra features (commands, tools, and Gateway RPC).

Most of the time, you’ll use plugins when you want a feature that’s not built into core CoderClaw yet (or you want to keep optional features out of your main install).

Fast path:

  1. See what’s already loaded:
coderclaw plugins list
  1. Install an official plugin (example: Voice Call):
coderclaw plugins install @coderclaw/voice-call

Npm specs are registry-only (package name + optional version/tag). Git/URL/file specs are rejected.

  1. Restart the Gateway, then configure under plugins.entries.<id>.config.

See Voice Call for a concrete example plugin. Looking for third-party listings? See Community plugins.

Available plugins (official)

CoderClaw plugins are TypeScript modules loaded at runtime via jiti. Config validation does not execute plugin code; it uses the plugin manifest and JSON Schema instead. See Plugin manifest.

Plugins can register:

Plugins run in‑process with the Gateway, so treat them as trusted code. Tool authoring guide: Plugin agent tools.

Runtime helpers

Plugins can access selected core helpers via api.runtime. For telephony TTS:

const result = await api.runtime.tts.textToSpeechTelephony({
  text: "Hello from CoderClaw",
  cfg: api.config,
});

Notes:

Discovery & precedence

CoderClaw scans, in order:

  1. Config paths
  1. Workspace extensions
  1. Global extensions
  1. Bundled extensions (shipped with CoderClaw, disabled by default)

Bundled plugins must be enabled explicitly via plugins.entries.<id>.enabled or coderclaw plugins enable <id>. Installed plugins are enabled by default, but can be disabled the same way.

Each plugin must include a coderclaw.plugin.json file in its root. If a path points at a file, the plugin root is the file’s directory and must contain the manifest.

If multiple plugins resolve to the same id, the first match in the order above wins and lower-precedence copies are ignored.

Package packs

A plugin directory may include a package.json with coderclaw.extensions:

{
  "name": "my-pack",
  "coderclaw": {
    "extensions": ["./src/safety.ts", "./src/tools.ts"]
  }
}

Each entry becomes a plugin. If the pack lists multiple extensions, the plugin id becomes name/<fileBase>.

If your plugin imports npm deps, install them in that directory so node_modules is available (npm install / pnpm install).

Security note: coderclaw plugins install installs plugin dependencies with npm install --ignore-scripts (no lifecycle scripts). Keep plugin dependency trees “pure JS/TS” and avoid packages that require postinstall builds.

Channel catalog metadata

Channel plugins can advertise onboarding metadata via coderclaw.channel and install hints via coderclaw.install. This keeps the core catalog data-free.

Example:

{
  "name": "@coderclaw/nextcloud-talk",
  "coderclaw": {
    "extensions": ["./index.ts"],
    "channel": {
      "id": "nextcloud-talk",
      "label": "Nextcloud Talk",
      "selectionLabel": "Nextcloud Talk (self-hosted)",
      "docsPath": "/channels/nextcloud-talk",
      "docsLabel": "nextcloud-talk",
      "blurb": "Self-hosted chat via Nextcloud Talk webhook bots.",
      "order": 65,
      "aliases": ["nc-talk", "nc"]
    },
    "install": {
      "npmSpec": "@coderclaw/nextcloud-talk",
      "localPath": "extensions/nextcloud-talk",
      "defaultChoice": "npm"
    }
  }
}

CoderClaw can also merge external channel catalogs (for example, an MPM registry export). Drop a JSON file at one of:

Or point CODERCLAW_PLUGIN_CATALOG_PATHS (or CODERCLAW_MPM_CATALOG_PATHS) at one or more JSON files (comma/semicolon/PATH-delimited). Each file should contain { "entries": [ { "name": "@scope/pkg", "coderclaw": { "channel": {...}, "install": {...} } } ] }.

Plugin IDs

Default plugin ids:

If a plugin exports id, CoderClaw uses it but warns when it doesn’t match the configured id.

Config

{
  plugins: {
    enabled: true,
    allow: ["voice-call"],
    deny: ["untrusted-plugin"],
    load: { paths: ["~/Projects/oss/voice-call-extension"] },
    entries: {
      "voice-call": { enabled: true, config: { provider: "twilio" } },
    },
  },
}

Fields:

Config changes require a gateway restart.

Validation rules (strict):

Plugin slots (exclusive categories)

Some plugin categories are exclusive (only one active at a time). Use plugins.slots to select which plugin owns the slot:

{
  plugins: {
    slots: {
      memory: "memory-core", // or "none" to disable memory plugins
    },
  },
}

If multiple plugins declare kind: "memory", only the selected one loads. Others are disabled with diagnostics.

Control UI (schema + labels)

The Control UI uses config.schema (JSON Schema + uiHints) to render better forms.

CoderClaw augments uiHints at runtime based on discovered plugins:

If you want your plugin config fields to show good labels/placeholders (and mark secrets as sensitive), provide uiHints alongside your JSON Schema in the plugin manifest.

Example:

{
  "id": "my-plugin",
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "apiKey": { "type": "string" },
      "region": { "type": "string" }
    }
  },
  "uiHints": {
    "apiKey": { "label": "API Key", "sensitive": true },
    "region": { "label": "Region", "placeholder": "us-east-1" }
  }
}

CLI

coderclaw plugins list
coderclaw plugins info <id>
coderclaw plugins install <path>                 # copy a local file/dir into ~/.coderclaw/extensions/<id>
coderclaw plugins install ./extensions/voice-call # relative path ok
coderclaw plugins install ./plugin.tgz           # install from a local tarball
coderclaw plugins install ./plugin.zip           # install from a local zip
coderclaw plugins install -l ./extensions/voice-call # link (no copy) for dev
coderclaw plugins install @coderclaw/voice-call # install from npm
coderclaw plugins update <id>
coderclaw plugins update --all
coderclaw plugins enable <id>
coderclaw plugins disable <id>
coderclaw plugins doctor

plugins update only works for npm installs tracked under plugins.installs.

Plugins may also register their own top‑level commands (example: coderclaw voicecall).

Plugin API (overview)

Plugins export either:

Plugin hooks

Plugins can ship hooks and register them at runtime. This lets a plugin bundle event-driven automation without a separate hook pack install.

Example

import { registerPluginHooksFromDir } from "coderclaw/plugin-sdk";

export default function register(api) {
  registerPluginHooksFromDir(api, "./hooks");
}

Notes:

Provider plugins (model auth)

Plugins can register model provider auth flows so users can run OAuth or API-key setup inside CoderClaw (no external scripts needed).

Register a provider via api.registerProvider(...). Each provider exposes one or more auth methods (OAuth, API key, device code, etc.). These methods power:

Example:

api.registerProvider({
  id: "acme",
  label: "AcmeAI",
  auth: [
    {
      id: "oauth",
      label: "OAuth",
      kind: "oauth",
      run: async (ctx) => {
        // Run OAuth flow and return auth profiles.
        return {
          profiles: [
            {
              profileId: "acme:default",
              credential: {
                type: "oauth",
                provider: "acme",
                access: "...",
                refresh: "...",
                expires: Date.now() + 3600 * 1000,
              },
            },
          ],
          defaultModel: "acme/opus-1",
        };
      },
    },
  ],
});

Notes:

Register a messaging channel

Plugins can register channel plugins that behave like built‑in channels (WhatsApp, Telegram, etc.). Channel config lives under channels.<id> and is validated by your channel plugin code.

const myChannel = {
  id: "acmechat",
  meta: {
    id: "acmechat",
    label: "AcmeChat",
    selectionLabel: "AcmeChat (API)",
    docsPath: "/channels/acmechat",
    blurb: "demo channel plugin.",
    aliases: ["acme"],
  },
  capabilities: { chatTypes: ["direct"] },
  config: {
    listAccountIds: (cfg) => Object.keys(cfg.channels?.acmechat?.accounts ?? {}),
    resolveAccount: (cfg, accountId) =>
      cfg.channels?.acmechat?.accounts?.[accountId ?? "default"] ?? {
        accountId,
      },
  },
  outbound: {
    deliveryMode: "direct",
    sendText: async () => ({ ok: true }),
  },
};

export default function (api) {
  api.registerChannel({ plugin: myChannel });
}

Notes:

Write a new messaging channel (step‑by‑step)

Use this when you want a new chat surface (a “messaging channel”), not a model provider. Model provider docs live under /providers/*.

  1. Pick an id + config shape
  1. Define the channel metadata
  1. Implement the required adapters
  1. Add optional adapters as needed
  1. Register the channel in your plugin

Minimal config example:

{
  channels: {
    acmechat: {
      accounts: {
        default: { token: "ACME_TOKEN", enabled: true },
      },
    },
  },
}

Minimal channel plugin (outbound‑only):

const plugin = {
  id: "acmechat",
  meta: {
    id: "acmechat",
    label: "AcmeChat",
    selectionLabel: "AcmeChat (API)",
    docsPath: "/channels/acmechat",
    blurb: "AcmeChat messaging channel.",
    aliases: ["acme"],
  },
  capabilities: { chatTypes: ["direct"] },
  config: {
    listAccountIds: (cfg) => Object.keys(cfg.channels?.acmechat?.accounts ?? {}),
    resolveAccount: (cfg, accountId) =>
      cfg.channels?.acmechat?.accounts?.[accountId ?? "default"] ?? {
        accountId,
      },
  },
  outbound: {
    deliveryMode: "direct",
    sendText: async ({ text }) => {
      // deliver `text` to your channel here
      return { ok: true };
    },
  },
};

export default function (api) {
  api.registerChannel({ plugin });
}

Load the plugin (extensions dir or plugins.load.paths), restart the gateway, then configure channels.<id> in your config.

Agent tools

See the dedicated guide: Plugin agent tools.

Register a gateway RPC method

export default function (api) {
  api.registerGatewayMethod("myplugin.status", ({ respond }) => {
    respond(true, { ok: true });
  });
}

Register CLI commands

export default function (api) {
  api.registerCli(
    ({ program }) => {
      program.command("mycmd").action(() => {
        console.log("Hello");
      });
    },
    { commands: ["mycmd"] },
  );
}

Register auto-reply commands

Plugins can register custom slash commands that execute without invoking the AI agent. This is useful for toggle commands, status checks, or quick actions that don’t need LLM processing.

export default function (api) {
  api.registerCommand({
    name: "mystatus",
    description: "Show plugin status",
    handler: (ctx) => ({
      text: `Plugin is running! Channel: ${ctx.channel}`,
    }),
  });
}

Command handler context:

Command options:

Example with authorization and arguments:

api.registerCommand({
  name: "setmode",
  description: "Set plugin mode",
  acceptsArgs: true,
  requireAuth: true,
  handler: async (ctx) => {
    const mode = ctx.args?.trim() || "default";
    await saveMode(mode);
    return { text: `Mode set to: ${mode}` };
  },
});

Notes:

Register background services

export default function (api) {
  api.registerService({
    id: "my-service",
    start: () => api.logger.info("ready"),
    stop: () => api.logger.info("bye"),
  });
}

Naming conventions

Skills

Plugins can ship a skill in the repo (skills/<name>/SKILL.md). Enable it with plugins.entries.<id>.enabled (or other config gates) and ensure it’s present in your workspace/managed skills locations.

Distribution (npm)

Recommended packaging:

Publishing contract:

Example plugin: Voice Call

This repo includes a voice‑call plugin (Twilio or log fallback):

See Voice Call and extensions/voice-call/README.md for setup and usage.

Safety notes

Plugins run in-process with the Gateway. Treat them as trusted code:

Testing plugins

Plugins can (and should) ship tests: