16 KiB
summary, read_when
| summary | read_when | ||
|---|---|---|---|
| Clawdbot plugins/extensions: discovery, config, and safety |
|
Plugins (Extensions)
Quick start (new to plugins?)
A plugin is just a small code module that extends Clawdbot 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 Clawdbot yet (or you want to keep optional features out of your main install).
Fast path:
- See what’s already loaded:
clawdbot plugins list
- Install an official plugin (example: Voice Call):
clawdbot plugins install @clawdbot/voice-call
- Restart the Gateway, then configure under
plugins.entries.<id>.config.
See Voice Call for a concrete example plugin.
Available plugins (official)
- Microsoft Teams is plugin-only as of 2026.1.15; install
@clawdbot/msteamsif you use Teams. - Memory (Core) — bundled memory search plugin (enabled by default via
plugins.slots.memory) - Memory (LanceDB) — bundled long-term memory plugin (auto-recall/capture; set
plugins.slots.memory = "memory-lancedb") - Voice Call —
@clawdbot/voice-call - Zalo Personal —
@clawdbot/zalouser - Matrix —
@clawdbot/matrix - Zalo —
@clawdbot/zalo - Microsoft Teams —
@clawdbot/msteams - Google Antigravity OAuth (provider auth) — bundled as
google-antigravity-auth(disabled by default) - Gemini CLI OAuth (provider auth) — bundled as
google-gemini-cli-auth(disabled by default) - Qwen OAuth (provider auth) — bundled as
qwen-portal-auth(disabled by default) - Copilot Proxy (provider auth) — local VS Code Copilot Proxy bridge; distinct from built-in
github-copilotdevice login (bundled, disabled by default)
Clawdbot 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:
- Gateway RPC methods
- Gateway HTTP handlers
- Agent tools
- CLI commands
- Background services
- Optional config validation
Plugins run in‑process with the Gateway, so treat them as trusted code. Tool authoring guide: Plugin agent tools.
Discovery & precedence
Clawdbot scans, in order:
- Config paths
plugins.load.paths(file or directory)
- Workspace extensions
<workspace>/.clawdbot/extensions/*.ts<workspace>/.clawdbot/extensions/*/index.ts
- Global extensions
~/.clawdbot/extensions/*.ts~/.clawdbot/extensions/*/index.ts
- Bundled extensions (shipped with Clawdbot, disabled by default)
<clawdbot>/extensions/*
Bundled plugins must be enabled explicitly via plugins.entries.<id>.enabled
or clawdbot plugins enable <id>. Installed plugins are enabled by default,
but can be disabled the same way.
Each plugin must include a clawdbot.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 clawdbot.extensions:
{
"name": "my-pack",
"clawdbot": {
"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).
Channel catalog metadata
Channel plugins can advertise onboarding metadata via clawdbot.channel and
install hints via clawdbot.install. This keeps the core catalog data-free.
Example:
{
"name": "@clawdbot/nextcloud-talk",
"clawdbot": {
"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": "@clawdbot/nextcloud-talk",
"localPath": "extensions/nextcloud-talk",
"defaultChoice": "npm"
}
}
}
Plugin IDs
Default plugin ids:
- Package packs:
package.jsonname - Standalone file: file base name (
~/.../voice-call.ts→voice-call)
If a plugin exports id, Clawdbot 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:
enabled: master toggle (default: true)allow: allowlist (optional)deny: denylist (optional; deny wins)load.paths: extra plugin files/dirsentries.<id>: per‑plugin toggles + config
Config changes require a gateway restart.
Validation rules (strict):
- Unknown plugin ids in
entries,allow,deny, orslotsare errors. - Unknown
channels.<id>keys are errors unless a plugin manifest declares the channel id. - Plugin config is validated using the JSON Schema embedded in
clawdbot.plugin.json(configSchema). - If a plugin is disabled, its config is preserved and a warning is emitted.
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.
Clawdbot augments uiHints at runtime based on discovered plugins:
- Adds per-plugin labels for
plugins.entries.<id>/.enabled/.config - Merges optional plugin-provided config field hints under:
plugins.entries.<id>.config.<field>
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
clawdbot plugins list
clawdbot plugins info <id>
clawdbot plugins install <path> # copy a local file/dir into ~/.clawdbot/extensions/<id>
clawdbot plugins install ./extensions/voice-call # relative path ok
clawdbot plugins install ./plugin.tgz # install from a local tarball
clawdbot plugins install ./plugin.zip # install from a local zip
clawdbot plugins install -l ./extensions/voice-call # link (no copy) for dev
clawdbot plugins install @clawdbot/voice-call # install from npm
clawdbot plugins update <id>
clawdbot plugins update --all
clawdbot plugins enable <id>
clawdbot plugins disable <id>
clawdbot plugins doctor
plugins update only works for npm installs tracked under plugins.installs.
Plugins may also register their own top‑level commands (example: clawdbot voicecall).
Plugin API (overview)
Plugins export either:
- A function:
(api) => { ... } - An object:
{ id, name, configSchema, register(api) { ... } }
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 "clawdbot/plugin-sdk";
export default function register(api) {
registerPluginHooksFromDir(api, "./hooks");
}
Notes:
- Hook directories follow the normal hook structure (
HOOK.md+handler.ts). - Hook eligibility rules still apply (OS/bins/env/config requirements).
- Plugin-managed hooks show up in
clawdbot hooks listwithplugin:<id>. - You cannot enable/disable plugin-managed hooks via
clawdbot hooks; enable/disable the plugin instead.
Provider plugins (model auth)
Plugins can register model provider auth flows so users can run OAuth or API-key setup inside Clawdbot (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:
clawdbot models auth login --provider <id> [--method <id>]
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:
runreceives aProviderAuthContextwithprompter,runtime,openUrl, andoauth.createVpsAwareHandlershelpers.- Return
configPatchwhen you need to add default models or provider config. - Return
defaultModelso--set-defaultcan update agent defaults.
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:
- Put config under
channels.<id>(notplugins.entries). meta.labelis used for labels in CLI/UI lists.meta.aliasesadds alternate ids for normalization and CLI inputs.
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/*.
- Pick an id + config shape
- All channel config lives under
channels.<id>. - Prefer
channels.<id>.accounts.<accountId>for multi‑account setups.
- Define the channel metadata
meta.label,meta.selectionLabel,meta.docsPath,meta.blurbcontrol CLI/UI lists.meta.docsPathshould point at a docs page like/channels/<id>.
- Implement the required adapters
config.listAccountIds+config.resolveAccountcapabilities(chat types, media, threads, etc.)outbound.deliveryMode+outbound.sendText(for basic send)
- Add optional adapters as needed
setup(wizard),security(DM policy),status(health/diagnostics)gateway(start/stop/login),mentions,threading,streamingactions(message actions),commands(native command behavior)
- Register the channel in your plugin
api.registerChannel({ 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 background services
export default function (api) {
api.registerService({
id: "my-service",
start: () => api.logger.info("ready"),
stop: () => api.logger.info("bye"),
});
}
Naming conventions
- Gateway methods:
pluginId.action(example:voicecall.status) - Tools:
snake_case(example:voice_call) - CLI commands: kebab or camel, but avoid clashing with core commands
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:
- Main package:
clawdbot(this repo) - Plugins: separate npm packages under
@clawdbot/*(example:@clawdbot/voice-call)
Publishing contract:
- Plugin
package.jsonmust includeclawdbot.extensionswith one or more entry files. - Entry files can be
.jsor.ts(jiti loads TS at runtime). clawdbot plugins install <npm-spec>usesnpm pack, extracts into~/.clawdbot/extensions/<id>/, and enables it in config.- Config key stability: scoped packages are normalized to the unscoped id for
plugins.entries.*.
Example plugin: Voice Call
This repo includes a voice‑call plugin (Twilio or log fallback):
- Source:
extensions/voice-call - Skill:
skills/voice-call - CLI:
clawdbot voicecall start|status - Tool:
voice_call - RPC:
voicecall.start,voicecall.status - Config (twilio):
provider: "twilio"+twilio.accountSid/authToken/from(optionalstatusCallbackUrl,twimlUrl) - Config (dev):
provider: "log"(no network)
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:
- Only install plugins you trust.
- Prefer
plugins.allowallowlists. - Restart the Gateway after changes.
Testing plugins
Plugins can (and should) ship tests:
- In-repo plugins can keep Vitest tests under
src/**(example:src/plugins/voice-call.plugin.test.ts). - Separately published plugins should run their own CI (lint/build/test) and validate
clawdbot.extensionspoints at the built entrypoint (dist/index.js).