docs: 渐进式插件教程(L0→L5)+ DESIGN.md 视觉规范 + plugin-template 脚手架
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
# plugin-template · 插件脚手架
|
||||
|
||||
复制本目录到插件位置(改目录名 = 改前先改 manifest.id),改掉 `id`/`name`/`description` 即完成最小定制。
|
||||
|
||||
## 目录
|
||||
|
||||
```
|
||||
manifest.json 清单(id/name/version/minContract 必改)
|
||||
main.mjs 主进程侧:Agent 能力 + 设置声明 + 日志埋点(范例齐备)
|
||||
src/renderer.tsx 宿主内 React 组件源码(JSX)
|
||||
shims/ 构建期把 react 别名到宿主单例(勿改)
|
||||
```
|
||||
|
||||
## 构建渲染层组件
|
||||
|
||||
```sh
|
||||
esbuild src/renderer.tsx --bundle --format=esm --outfile=renderer.mjs \
|
||||
--jsx=automatic \
|
||||
--alias:react=./shims/react.js --alias:react/jsx-runtime=./shims/jsx-runtime.js
|
||||
```
|
||||
|
||||
(esbuild 可用 `pnpm dlx esbuild@<版本>` 或任意 ≥0.17 版本;`--alias` 需 ≥0.17。)
|
||||
不需要 UI 时:删除 `entry.renderer` 与 `src/`、`shims/`,只保留 main.mjs。
|
||||
|
||||
## 放置位置(三选一,按优先级)
|
||||
|
||||
1. `userData/plugins/<id>/`(应用托管目录)
|
||||
2. `<工作区>/.pi/extensions/<id>/`(项目级)
|
||||
3. `~/.pi/agent/extensions/<id>/`(全局)
|
||||
|
||||
第三方装载仅开发版开启(`ASO_EXTERNAL_PLUGINS=1` 豁免)。
|
||||
|
||||
## 验证
|
||||
|
||||
1. 重启或开关重载 → 设置 → 插件 → 对应作用域出现插件
|
||||
2. 日志 `userData/logs/main-*.log` 有 `plugin activated pluginId=my-plugin`
|
||||
3. 对 Agent 说"ping 一下我的插件" → 触发 `my_plugin_ping`
|
||||
4. 面板/侧栏出现 React 组件,点击 +1 正常(hooks 生效)
|
||||
|
||||
## 下一步
|
||||
|
||||
- 教程索引:`docs/plugins/README.md`(L0→L5)
|
||||
- 视觉规范:`docs/plugins/DESIGN.md`(提发布前过一遍自检清单)
|
||||
- 契约全集:`docs/plugins.md`
|
||||
@@ -0,0 +1,43 @@
|
||||
// 主进程侧:Agent 能力(pi 工具/命令/事件)+ 设置声明 + 内联 UI。
|
||||
// 日志规范见 docs/plugins/06-self-diagnosis.md:激活时 info 一条能力清单,一切 catch 记 error。
|
||||
import { definePlugin } from '@aso/plugin-sdk'
|
||||
|
||||
export default definePlugin((api) => {
|
||||
const log = api.log
|
||||
log.info('activated', { manifest: api.manifest.version })
|
||||
|
||||
// ---- 设置(用户可配置;值为宿主持久化) ----
|
||||
api.ui.registerSettings({
|
||||
items: [
|
||||
{ type: 'string', key: 'greeting', label: '问候语', default: 'Hello', placeholder: 'Hello' },
|
||||
{ type: 'boolean', key: 'verbose', label: '详细日志', default: false }
|
||||
]
|
||||
})
|
||||
|
||||
let values = api.ui.getSettingValues()
|
||||
api.ui.onSettingsChange((next) => {
|
||||
values = next
|
||||
log.info('settings changed', { greeting: next.greeting })
|
||||
})
|
||||
api.ui.onSettingsAction((key) => {
|
||||
log.info('settings action', { key })
|
||||
})
|
||||
|
||||
// ---- Agent 工具示例 ----
|
||||
api.pi.registerTool({
|
||||
name: 'my_plugin_ping',
|
||||
description: '返回问候语(示例工具:把这段换成真正的能力)',
|
||||
parameters: {},
|
||||
async execute() {
|
||||
log.debug('ping called', { verbose: values.verbose })
|
||||
return { output: `${values.greeting ?? 'Hello'} from my-plugin` }
|
||||
}
|
||||
})
|
||||
|
||||
// ---- 面板/视图组件在 renderer.mjs(JSX 构建见 README);无 UI 需求可整段去掉 ----
|
||||
log.info('ready', { hasRenderer: true })
|
||||
|
||||
return () => {
|
||||
log.info('disposed')
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"id": "my-plugin",
|
||||
"name": "我的插件",
|
||||
"version": "0.1.0",
|
||||
"minContract": "0.2.0",
|
||||
"description": "TODO:一句话描述插件做什么",
|
||||
"order": 50,
|
||||
"panel": true,
|
||||
"icon": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"/></svg>",
|
||||
"entry": {
|
||||
"main": "main.mjs",
|
||||
"renderer": "renderer.mjs"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// 构建期别名目标:react/jsx-runtime → 宿主全局单例(esbuild --jsx=automatic)
|
||||
export const { jsx, jsxs, Fragment } = window.__ASO_PLUGIN_HOST__.jsxRuntime
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// 构建期别名目标:react → 宿主全局单例(双 React 实例会导致 hooks 失效)
|
||||
const R = window.__ASO_PLUGIN_HOST__.react
|
||||
export default R
|
||||
export const {
|
||||
useState,
|
||||
useEffect,
|
||||
useRef,
|
||||
useMemo,
|
||||
useCallback,
|
||||
createContext,
|
||||
useContext,
|
||||
Fragment,
|
||||
memo,
|
||||
forwardRef,
|
||||
createElement
|
||||
} = R
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 渲染层组件源码(JSX)。构建:
|
||||
* esbuild src/renderer.tsx --bundle --format=esm --outfile=renderer.mjs \
|
||||
* --jsx=automatic --alias:react=./shims/react.js --alias:react/jsx-runtime=./shims/jsx-runtime.js
|
||||
* 产物 renderer.mjs 是自包含单文件,放回插件目录即被宿主装载。
|
||||
* 样式类名只用 DESIGN.md 里的宿主词汇。
|
||||
*/
|
||||
import { useState } from 'react'
|
||||
|
||||
export default function mount(ctx) {
|
||||
function Panel() {
|
||||
const [count, setCount] = useState(0)
|
||||
return (
|
||||
<div className="flex h-full flex-col gap-2 p-3">
|
||||
<div className="text-[12.5px] text-ink">我的插件面板</div>
|
||||
<div className="text-[12px] text-ink-3">计数:{count}</div>
|
||||
<button
|
||||
className="self-start rounded-control border border-line-strong px-2.5 py-1 text-[11.5px] text-ink hover:bg-hover"
|
||||
onClick={() => setCount((c) => c + 1)}
|
||||
>
|
||||
+1
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
ctx.registerPanel(Panel)
|
||||
ctx.registerView(Panel)
|
||||
}
|
||||
Reference in New Issue
Block a user