| name | plugin-develop |
| description | Implement Zhin.js plugin features: commands, AI tools, cron jobs, middleware, and console pages. Use when asked to add a command, register a tool, wire cron, extend middleware, or build a console page entry. Triggers: 插件开发, add command, addTool, 加命令, 写插件功能. |
| keywords | ["plugin","command","tool","cron","console page","插件开发","加命令"] |
| tags | ["zhin","plugin","development"] |
Plugin Develop
在已有 Zhin 插件包内实现功能增量(命令、工具、定时任务、中间件、控制台页),不重写整个插件结构。
何时使用
- 用户要在插件里「加一个命令 / 工具 / 定时任务 / 控制台页」
- 插件骨架已存在(
plugin-init 或 zhin new 已跑过)
- 需要大改目录或拆模块 → 改用
zhin-plugin-refactoring 或仓库内 zhin-plugin-standard-development
工作流
第 1 步:定位入口与范围
- 找到插件入口:
plugins/<name>/src/index.ts 或项目内 src/plugins/<name>/
- 确认改动类型(只选一种主路径,其余作附带):
- 命令 →
MessageCommand + addCommand
- AI 工具 →
plugin.addTool() / addToolOnly()
- 定时任务 →
addCron(new Cron(...))
- 中间件 →
addMiddleware()
- 控制台页 →
useContext('web', ...) + client/
- 输入:用户需求、目标插件名、平台约束(如有)
- 输出:明确的文件清单(要改哪些
src/ / client/ 文件)
第 2 步:选最小 API 实现
| 能力 | API | 前置条件 |
|---|
| 聊天命令 | addCommand(new MessageCommand('...').action(...)) | command 服务 |
| AI 工具 | plugin.addTool({ name, execute, ... }) | Agent 已启用 |
| 定时任务 | addCron(new Cron(expr, fn)) | cron 服务 |
| 消息过滤 | addMiddleware(fn) | 无 |
| HTTP 路由 | useContext('router', ...) | @zhin.js/host-router |
| 控制台页 | useContext('web', ...) + web.addEntry() | @zhin.js/host-api |
usePlugin() 仅在模块顶层调用
- TS 本地导入使用
.js 扩展名
- 平台差异放在 adapter 检查或 tool
description,不要写进通用 SKILL 正文
第 3 步:装配与清理
useContext() 注册监听器、定时器、路由时,回调必须返回清理函数
- 业务逻辑放在
commands/、services/、tools/,入口只做装配
- 出站消息必须走
Message.$reply / Adapter.sendMessage 链,禁止直调平台 Bot
第 4 步:验证
pnpm --filter <plugin-package> build
pnpm --filter <plugin-package> test
报告:改了哪些文件、如何触发验证(命令文本 / 工具名 / 访问路径)。
第 5 步:输出格式(必须包含)
## 改动摘要
- 插件:`<package-name>`
- 类型:命令 | 工具 | cron | 中间件 | 控制台页
## 修改文件
- `src/...` — 作用
## 验证
- 命令:`pnpm --filter <pkg> build && pnpm --filter <pkg> test`
- 手测:Sandbox 发 `命令原文` / 工具名 `tool_name`
## 风险
- (仅列与本次改动相关的未测项)
代码片段(按需复制改)
命令(src/commands/greet.ts 或入口):
import { MessageCommand } from 'zhin.js'
plugin.addCommand(
new MessageCommand('greet <name:text>')
.desc('问候用户')
.action(async (_message, result) => `Hello, ${result.params.name}!`),
)
AI 工具:
plugin.addTool({
name: 'my_tool',
description: '一句话说明副作用与输入',
parameters: {
type: 'object',
properties: { q: { type: 'string', description: '查询词' } },
required: ['q'],
},
execute: async ({ q }) => { },
})
定时任务(需 cron 服务):
import { Cron } from 'zhin.js'
plugin.addCron(
new Cron('0 9 * * *', async () => {
plugin.logger.info('daily job')
}),
)
useContext 清理:
useContext('database', (db) => {
const timer = setInterval(() => {}, 60_000)
return () => clearInterval(timer)
})
Satori 卡片(src/cards/*.tsx,文件顶 /** @jsxImportSource @zhin.js/satori */):
import { Card, CardHeader, StatChip, Row, wrapCardHtml, DEFAULT_CARD_THEME } from '@zhin.js/satori'
export function buildWelcomeCard(name: string): string {
const body = (
<Card>
<CardHeader title={`你好,${name}`} />
<Row gap={10}>
<StatChip label="状态" value="OK" accent={DEFAULT_CARD_THEME.accentMem} />
</Row>
</Card>
)
return wrapCardHtml(body, DEFAULT_CARD_THEME.canvas)
}
命令里出站(纯 HTML;装 html-renderer 可自动转 PNG):
import { segment } from 'zhin.js'
import { buildWelcomeCard } from './cards/welcome-card.js'
plugin.addCommand(
new MessageCommand('welcome-card <name:text>')
.action((_, result) =>
segment.html({ html: buildWelcomeCard(result.params.name), width: 540 }),
),
)
| JSX 用途 | jsxImportSource | 产出 |
|---|
| IM 消息 / 组件 | zhin.js | MessageComponent |
| Satori 卡片 | @zhin.js/satori(文件顶注释) | HTML 字符串 |
失败与兜底
| 触发条件 | 一线处理 | 仍失败 |
|---|
useContext 回调不执行 | 确认依赖 Context 的插件已 start();检查 plugins 列表是否包含 host-router/host-api | 查启动日志里 Context provided 顺序 |
addCron 无效果 | 确认配置里启用了 cron 服务 | 在入口临时 logger.info 验证 Cron 是否注册 |
| 命令不匹配 | 检查 MessageCommand 模板与 result.params 类型 | 用 tests/commands.test.ts 单独测 action |
tsc 报导入错误 | 互导路径加 .js;exports.development 指向 src/ | 对照同仓库官方插件 package.json |
| 控制台页 404 | 确认 @zhin.js/host-api 已加载且 web.addEntry 在 web Context 就绪后调用 | 查 /entries 是否列出该 entry |
🔴 CHECKPOINT · 范围确认
在改超过 3 个文件或涉及数据库/HTTP/控制台之前,向用户确认:功能列表、是否接受拆 commands/ / services/、是否需配套测试。
不要做什么
- 不要在
async 函数或回调里调用 usePlugin()
- 不要从
@zhin.js/core 直接 import 框架 API(用户项目用 zhin.js)
- 不要把适配器协议、Endpoint 生命周期写进普通插件(交给 adapter 层)
- 不要绕过发送链
renderSendMessage → before.sendMessage → 平台发送
- 不要为「整洁」一次性拆出大量空目录;无复杂度时保持单文件
延伸阅读
| 文档 | 路径 |
|---|
| 工具与技能 | docs/advanced/tools-skills.md |
| 插件规范 | .github/instructions/zhin-plugin.instructions.md |
| Monorepo 深度开发 | .github/skills/zhin-plugin-standard-development/SKILL.md |
| 最小可跑示例 | examples/minimal-bot/ |