Skeleton
Create a new package under your workspace (or as a standalone npm package):
// packages/plugin-greet/package.json
{
"name": "@acme/moxxy-plugin-greet",
"type": "module",
"main": "./dist/index.js",
"moxxy": {
"plugin": {
"entry": "./src/index.ts",
"kind": "tools"
}
},
"dependencies": { "@moxxy/sdk": "*" }
}// src/index.ts
import { definePlugin, defineTool, z } from '@moxxy/sdk';
export default definePlugin({
name: '@acme/moxxy-plugin-greet',
version: '0.0.0',
tools: [
defineTool({
name: 'greet',
description: 'Return a greeting for the given name.',
inputSchema: z.object({ name: z.string() }),
handler: ({ name }) => `Hello, ${name}!`,
}),
],
});That's it. Install it in any project that uses moxxy and it auto-discovers via package.json#moxxy.plugin.
Lifecycle hooks
export default definePlugin({
name: '@acme/moxxy-plugin-audit',
hooks: {
onInit: async (ctx) => { /* one-time setup */ },
onToolCall: async ({ call }) => {
if (call.name === 'Bash' && /rm -rf/.test(String(call.input.command))) {
return { action: 'deny', reason: 'destructive command blocked' };
}
return { action: 'allow' };
},
onEvent: async (event) => { /* observe — read only */ },
},
});Hook ordering follows plugin registration order. Use requirements to describe availability/readiness, not hook ordering. onToolCall short-circuits on first deny.
Requirements
Plugins and individual blocks can declare requirements so unavailable features fail closed and show clear diagnostics:
export default definePlugin({
name: '@acme/moxxy-plugin-reporting',
requirements: [
{
kind: 'plugin',
name: '@moxxy/plugin-memory',
state: 'registered',
hint: 'Enable @moxxy/plugin-memory.',
},
],
tools: [
defineTool({
name: 'report_from_memory',
description: 'Create a report from long-term memory.',
inputSchema: z.object({ topic: z.string() }),
requirements: [
{
kind: 'runtime',
name: 'auth:provider:openai-codex',
state: 'ready',
hint: 'Run `moxxy login openai-codex`.',
},
],
handler: async ({ topic }) => `Report for ${topic}`,
}),
],
});Plugin-level requirements are checked before registration; missing hard requirements skip the whole plugin. Tool requirements are checked before the handler runs. Providers, modes, compactors, and transcribers check requirements before activation.
See Requirements for the full contract, runtime facts, and diagnostics behavior.
Don't
- Don't import from
@moxxy/coreunless you're writing a channel. Plugins should depend only on@moxxy/sdk. The dependency-cruiser CI guard enforces the reverse (core can't import you), but importing core from a leaf plugin makes you tightly coupled to runtime internals. - Don't use
z.any()for tool inputs. The model sees your schema; loose schemas waste tokens and produce flaky tool calls. - Don't forget
ctx.signalin long-running handlers (Bash, network). Channels can abort sessions; tools must cooperate.
