# 小蜗工具插件开发完整指南

本文档是小蜗工具插件开发的完整参考，包含插件结构、配置文件、前端SDK、后端SDK的所有内容。阅读本文档后，你可以立即开始开发小蜗插件。

---

## 一、插件目录结构

### 1.1 整体结构

```
plugins/
└── my-plugin/                 # 插件根目录（目录名即插件 ID）
    ├── manifest.json          # 插件配置文件（必需）
    ├── data/                  # 数据目录（可选）
    ├── frontend/              # 前端目录
    └── backend/               # 后端目录（可选）
```

### 1.2 前端目录结构

```
frontend/
├── dist/                      # 构建输出目录
│   └── index.html             # 打包后的单文件（生产模式入口）
├── src/                       # 源代码目录
│   ├── App.vue                # 主组件（Vue 项目）
│   ├── main.js                # 入口文件
│   └── components/            # 组件目录
├── public/                    # 静态资源
│   └── logo.png               # 图标等
├── index.html                 # HTML 模板
├── package.json               # 依赖配置
└── vite.config.js             # Vite 构建配置
```

**说明：**
- `dist/index.html` 是生产模式的入口文件，需打包成单个 HTML 文件
- 开发模式下直接连接 Vite 开发服务器（如 `http://localhost:5173`）

### 1.3 后端目录结构（Python）

```
backend/
├── main.py                    # 入口文件
├── handlers.py                # 接口函数（@sdk.handler）
└── utils/
    ├── __init__.py            # 包初始化
    ├── xiaowo_sdk.py          # SDK 核心
    └── common.py              # 公共函数
```

**各文件职责：**

| 文件 | 职责 |
|------|------|
| `main.py` | 入口文件，导入 handlers 并启动 `sdk.run()` |
| `handlers.py` | 存放所有 `@sdk.handler` 装饰的接口函数 |
| `utils/xiaowo_sdk.py` | SDK 核心实现，提供 `sdk` 实例 |
| `utils/common.py` | 公共函数、常用工具函数 |

### 1.4 数据目录

```
data/
├── config.json                # 插件运行时配置
└── cache/                     # 缓存文件（可选）
```

`data/` 目录用于存放插件运行时产生的数据，与代码文件分离。

---

## 二、插件配置文件 manifest.json

### 2.1 完整配置示例

```json
{
  "id": "asr",
  "title": "AI语音识别",
  "version": "1.0.0",
  "description": "识别视频和音频的语音内容",
  "author": "小蜗工具",
  "icon": "frontend/public/logo.png",
  "dev_mode": false,
  "frontend": {
    "entry": "frontend/dist/index.html",
    "width": 1280,
    "height": 900,
    "resizable": true,
    "maximizable": true,
    "decorations": false
  },
  "backend": {
    "type": "python",
    "runtime": "",
    "use_local_runtime": true,
    "fallback_runtime": true,
    "entry": "backend/main.py",
    "args": [],
    "env": {
      "PYTHONIOENCODING": "utf-8"
    },
    "pip": {
      "numpy": "1.23.5",
      "pydub": null
    }
  },
  "dev": {
    "frontend": {
      "entry": "http://localhost:5173"
    },
    "backend": {
      "runtime": "D:\\python\\python.exe",
      "use_local_runtime": false,
      "entry": "backend/main.py",
      "args": ["--dev"],
      "env": {
        "PYTHONIOENCODING": "utf-8"
      }
    }
  },
  "extmodels": {
    "check_files": [
      "python310\\python.exe",
      "__users__\\.cache\\huggingface\\hub\\models--xxx\\model.pt",
      "__models__\\whisper\\model.bin"
    ],
    "download_url": "https://example.com/download"
  }
}
```

### 2.2 基础字段

| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `id` | string | 是 | 插件唯一标识，用于目录名和窗口标签 |
| `title` | string | 是 | 插件显示名称 |
| `version` | string | 是 | 版本号（建议使用语义化版本） |
| `description` | string | 否 | 插件描述 |
| `author` | string | 否 | 作者信息 |
| `icon` | string | 否 | 图标路径（相对于插件目录） |
| `dev_mode` | boolean | 否 | 开发模式开关，默认 false |

### 2.3 frontend 前端配置

| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `entry` | string | 是 | 入口文件路径（相对于插件目录） |
| `width` | number | 否 | 窗口宽度，默认 1200 |
| `height` | number | 否 | 窗口高度，默认 800 |
| `resizable` | boolean | 否 | 是否可调整大小，默认 true |
| `maximizable` | boolean | 否 | 是否允许最大化，默认 true |
| `decorations` | boolean | 否 | 是否显示系统窗口边框，默认 false |

**说明：**
- `decorations: false` 时使用小蜗自定义标题栏
- `entry` 生产模式通常为 `frontend/dist/index.html`

### 2.4 backend 后端配置

| 字段 | 类型 | 必需 | 说明 |
|------|------|------|------|
| `type` | string | 是 | 后端类型：`python` / `node` / `exe` |
| `entry` | string | 是 | 入口文件路径（相对于插件目录） |
| `runtime` | string | 否 | 运行时路径（绝对或相对路径） |
| `use_local_runtime` | boolean | 否 | 是否使用插件目录内的运行时（仅python） |
| `fallback_runtime` | boolean | 否 | 本地运行时找不到时是否回退到系统运行时（仅python） |
| `args` | string[] | 否 | 启动参数 |
| `env` | object | 否 | 环境变量 |
| `pip` | object | 否 | pip 依赖配置（仅 python 类型有效，不要配置大型包如 torch） |

**Python 运行时查找优先级：**

1. `use_local_runtime: true` → 在插件目录查找 `python.exe`
2. 找不到且 `fallback_runtime: true` → 继续下一步
3. `runtime` 配置 → 使用指定路径
4. 小蜗内置 Python → 使用应用配置的 Python
5. 系统 `python` → 回退到系统环境变量

**pip 依赖配置示例：**

```json
"pip": {
  "numpy": "1.23.5",
  "pydub": null,
  "requests": null
}
```

- `null` 表示不限制版本，安装最新版
- 字符串值表示指定版本
- 插件启动时自动检查并安装缺失依赖
- **重要**：不要配置 torch 等大型包，建议做成独立的 python 环境和模型包一起打包成 7z 压缩包

### 2.5 dev 开发模式配置

当 `dev_mode: true` 时，使用 `dev` 配置覆盖 `frontend` 和 `backend`：

```json
"dev": {
  "frontend": {
    "entry": "http://localhost:5173"
  },
  "backend": {
    "runtime": "D:\\python\\python.exe",
    "use_local_runtime": false,
    "entry": "backend/main.py",
    "args": ["--dev"]
  }
}
```

**开发模式优势：**
- 前端连接本地开发服务器，支持热重载
- 后端可传入调试参数
- 可指定开发环境专用的 Python 路径

### 2.6 extmodels 扩展模型包配置

用于配置插件依赖的外部模型包或大型资源文件：

```json
"extmodels": {
  "check_files": [
    "python310\\python.exe",
    "__users__\\.cache\\huggingface\\hub\\models--xxx\\model.pt",
    "__models__\\whisper\\model.bin"
  ],
  "download_url": "https://example.com/download"
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| `check_files` | string[] | 需要检测的文件路径列表 |
| `download_url` | string | 模型包下载页面地址 |

**路径占位符：**

| 占位符 | 说明 |
|--------|------|
| `__users__` | 用户主目录（如 `C:\Users\xxx`） |
| `__models__` | 公共模型目录（`extlibs/models`），多个插件可共享的模型存放位置 |
| 无占位符 | 相对于插件的 backend 目录 |

**前端触发检查：**

配置好 `extmodels` 后，需要在前端调用 `callBackend` 时传入 `check_extmodel: true` 参数来触发检查。通常在需要使用模型的请求中添加：

```typescript
// 示例：在获取配置时检查模型是否已安装
const config = await PluginSDK.callBackend("function", {
  check_extmodel: true,
})
```

当 `check_extmodel: true` 时，系统会在执行后端方法前自动检查 `check_files` 中的文件是否存在。如果缺失，会自动弹出下载引导窗口并返回错误提示，后端方法不会被执行。

建议在插件初始化或首次使用模型的请求中添加此参数，无需在每个请求中都携带。

### 2.7 最小配置示例

**纯前端插件（无后端）：**

```json
{
  "id": "my-tool",
  "title": "我的工具",
  "version": "1.0.0",
  "frontend": {
    "entry": "frontend/dist/index.html"
  }
}
```

**带 Python 后端的插件：**

```json
{
  "id": "my-tool",
  "title": "我的工具",
  "version": "1.0.0",
  "frontend": {
    "entry": "frontend/dist/index.html"
  },
  "backend": {
    "type": "python",
    "use_local_runtime": true,
    "fallback_runtime": true,
    "entry": "backend/main.py",
    "env": {
      "PYTHONIOENCODING": "utf-8"
    }
  }
}
```

---

## 三、前端 SDK（xiaowo-sdk）

### 3.1 概述

xiaowo-sdk 已集成在插件模板中，无需单独安装。直接导入即可使用：

```javascript
import { PluginSDK } from "xiaowo-sdk"
```

### 3.2 快速开始

```javascript
import { PluginSDK } from "xiaowo-sdk"

// 等待 SDK 准备好
await PluginSDK.waitReady()

// 调用后端方法
const result = await PluginSDK.callBackend('hello', { name: '小蜗' })
```

### 3.3 后端通信 API

| 方法 | 说明 |
|------|------|
| `waitReady()` | 等待 SDK 初始化完成 |
| `callBackend(method, params)` | 调用后端方法 |
| `getBackendState()` | 获取后端状态 |
| `onBackendEvent(eventName, handler)` | 监听后端推送的事件 |
| `offBackendEvent(eventName)` | 取消监听后端事件 |
| `onBackendReady(handler)` | 监听后端就绪事件 |
| `onBackendExit(handler)` | 监听后端进程退出事件 |
| `restartBackend()` | 重启后端进程 |

**使用示例：**

```javascript
// 调用后端方法
const result = await PluginSDK.callBackend('hello', { name: '小蜗' })

// 监听后端事件
PluginSDK.onBackendEvent('progress', (data) => {
  console.log('进度:', data.percent)
})

// 监听后端就绪（返回 unlisten 函数）
const unlistenReady = PluginSDK.onBackendReady(() => {
  console.log('后端已就绪')
})

// 监听后端退出（返回 unlisten 函数）
const unlistenExit = PluginSDK.onBackendExit((info) => {
  console.warn('后端已退出:', info)
})

// 取消监听
unlistenReady()
unlistenExit()
```

### 3.4 窗口控制 API

| 方法 | 说明 |
|------|------|
| `close()` | 关闭窗口 |
| `minimize()` | 最小化窗口 |
| `toggleMaximize()` | 切换最大化状态 |
| `showWindow()` | 显示窗口 |

```javascript
PluginSDK.minimize()
PluginSDK.toggleMaximize()
PluginSDK.close()
```

### 3.5 插件信息 API

| 方法 | 说明 |
|------|------|
| `getInfo()` | 获取插件信息（manifest 配置） |
| `createDesktopShortcut(name)` | 创建桌面快捷方式 |

```javascript
const info = await PluginSDK.getInfo()
console.log(info.manifest.title)   // 插件标题
console.log(info.manifest.version) // 插件版本

await PluginSDK.createDesktopShortcut("我的插件")
```

### 3.6 文件拖放 API

| 方法 | 说明 |
|------|------|
| `onFileDrop(handler)` | 监听文件拖放 |
| `onFileDropHover(handler)` | 监听文件悬停 |
| `onFileDropCancelled(handler)` | 监听拖放取消 |

```javascript
PluginSDK.onFileDrop((files) => {
  console.log('拖入文件:', files)
})
```

### 3.7 文件路径转换 API

| 方法 | 说明 |
|------|------|
| `convertFileSrc(filePath)` | 将本地文件路径转换为可加载的 URL |

```javascript
const url = PluginSDK.convertFileSrc('D:\\music\\test.mp3')
// 可用于 audio/video/img 标签
```

### 3.8 对话框 API

| 方法 | 说明 |
|------|------|
| `dialog.open(options)` | 打开文件选择对话框 |
| `dialog.save(options)` | 打开保存文件对话框 |


### 3.9 剪贴板 API

| 方法 | 说明 |
|------|------|
| `clipboard.readText()` | 读取剪贴板文本 |
| `clipboard.writeText(text)` | 写入剪贴板文本 |

### 3.10 路径操作 API

| 方法 | 说明 |
|------|------|
| `path.appDataDir()` | 应用数据目录 |
| `path.homeDir()` | 用户主目录 |
| `path.desktopDir()` | 桌面目录 |
| `path.downloadDir()` | 下载目录 |
| `path.tempDir()` | 临时目录 |
| `path.join(...paths)` | 拼接路径 |
| `path.basename(p)` | 获取文件名 |
| `path.dirname(p)` | 获取目录名 |
| `path.extname(p)` | 获取扩展名 |

### 3.11 Shell API

| 方法 | 说明 |
|------|------|
| `shell.open(path)` | 使用系统默认程序打开文件或 URL |

### 3.12 文件管理 API

`PluginSDK.fs` 提供了一组前端可直接调用的文件管理接口，适合处理插件自己的输出文件、缓存文件、导出文件等场景。

| 方法 | 说明 |
|------|------|
| `fs.createFile(path, overwrite = false)` | 创建文件，`overwrite` 为 `true` 时允许覆盖已有文件 |
| `fs.createDir(path, recursive = true)` | 创建目录，默认递归创建 |
| `fs.writeTextFile(path, content, append = false)` | 写入文本文件，可选追加写入 |
| `fs.writeBase64File(path, base64Data, append = false)` | 将 Base64 内容写入文件，可用于图片、音频等二进制内容 |
| `fs.readTextFile(path)` | 读取文本文件内容 |
| `fs.removeFile(path)` | 删除文件 |
| `fs.removeDir(path, recursive = true)` | 删除目录，默认递归删除 |
| `fs.getInfo(path)` | 获取路径信息 |
| `fs.exists(path)` | 判断路径是否存在 |
| `fs.isFile(path)` | 判断路径是否为文件 |
| `fs.isDir(path)` | 判断路径是否为目录 |
| `fs.copyFile(src, dest)` | 复制文件 |

**使用示例：**

```javascript
import { PluginSDK } from 'xiaowo-sdk'

await PluginSDK.waitReady()

const outputDir = await PluginSDK.path.join(await PluginSDK.path.appDataDir(), 'my-plugin')
await PluginSDK.fs.createDir(outputDir)

const outputFile = await PluginSDK.path.join(outputDir, 'result.txt')
await PluginSDK.fs.writeTextFile(outputFile, '处理完成')

const exists = await PluginSDK.fs.exists(outputFile)
const content = await PluginSDK.fs.readTextFile(outputFile)
const info = await PluginSDK.fs.getInfo(outputFile)

console.log(exists, content, info)
```

**写入 Base64 文件示例：**

```javascript
const imagePath = await PluginSDK.path.join(outputDir, 'cover.png')
await PluginSDK.fs.writeBase64File(imagePath, base64Image)
```

**说明：**
- 这些接口都是异步方法，调用前建议先执行 `await PluginSDK.waitReady()`
- `path` 建议优先通过 `PluginSDK.path.join()` 拼接，避免手写分隔符
- 适合管理插件运行时文件，不建议直接依赖硬编码的绝对路径
- 如果前端只是为了在页面中预览本地文件，可配合 `convertFileSrc()` 一起使用

### 3.13 Vue 完整示例

```vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { PluginSDK } from 'xiaowo-sdk'

const result = ref('')
const progress = ref(0)
const backendReady = ref(false)

let unlistenReady
let unlistenExit

onMounted(async () => {
  await PluginSDK.waitReady()

  // 监听后端就绪
  unlistenReady = PluginSDK.onBackendReady(() => {
    backendReady.value = true
  })

  // 监听后端退出
  unlistenExit = PluginSDK.onBackendExit((info) => {
    console.warn('后端已退出:', info)
    backendReady.value = false
  })

  // 监听进度事件
  PluginSDK.onBackendEvent('progress', (data) => {
    progress.value = data.percent
  })
})

onUnmounted(() => {
  unlistenReady?.()
  unlistenExit?.()
  PluginSDK.offBackendEvent('progress')
})

async function callHello() {
  const res = await PluginSDK.callBackend('hello', { name: 'Vue' })
  result.value = res.message
}
</script>

<template>
  <div v-if="!backendReady">后端加载中...</div>
  <div v-else>
    <button @click="callHello">调用后端</button>
    <p>{{ result }}</p>
    <p>进度: {{ progress }}%</p>
  </div>
</template>
```

### 3.14 React 完整示例

```tsx
import { useState, useEffect } from 'react'
import { PluginSDK } from 'xiaowo-sdk'

export default function App() {
  const [result, setResult] = useState('')
  const [progress, setProgress] = useState(0)
  const [backendReady, setBackendReady] = useState(false)

  useEffect(() => {
    let unlistenReady: (() => void) | undefined
    let unlistenExit: (() => void) | undefined

    PluginSDK.waitReady().then(() => {
      // 监听后端就绪
      unlistenReady = PluginSDK.onBackendReady(() => {
        setBackendReady(true)
      })

      // 监听后端退出
      unlistenExit = PluginSDK.onBackendExit((info) => {
        console.warn('后端已退出:', info)
        setBackendReady(false)
      })

      // 监听进度事件
      PluginSDK.onBackendEvent('progress', (data) => {
        setProgress(data.percent)
      })
    })

    return () => {
      unlistenReady?.()
      unlistenExit?.()
      PluginSDK.offBackendEvent('progress')
    }
  }, [])

  async function callHello() {
    const res = await PluginSDK.callBackend('hello', { name: 'React' })
    setResult(res.message)
  }

  if (!backendReady) {
    return <div>后端加载中...</div>
  }

  return (
    <div>
      <button onClick={callHello}>调用后端</button>
      <p>{result}</p>
      <p>进度: {progress}%</p>
    </div>
  )
}
```

---

## 四、后端 SDK（Python）

### 4.1 概述

小蜗插件后端支持多种语言：

| 类型 | 说明 |
|------|------|
| `python` | 推荐，提供现成 SDK，小蜗内置 Python 运行时 |
| `node` | 需自行实现通信协议 |
| `exe` | 任意语言编译的可执行文件，需自行实现通信协议 |

**通信方式**：前后端通过 **stdin/stdout** 进行 JSON-RPC 协议通信。

### 4.2 Python SDK 文件结构

```
backend/
├── main.py              # 入口文件
├── handlers.py          # 接口函数
└── utils/
    ├── __init__.py
    └── xiaowo_sdk.py    # SDK 核心
```

### 4.3 入口文件 main.py

```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from handlers import *

if __name__ == "__main__":
    sdk.run()
```

### 4.4 接口函数 handlers.py

```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from utils.xiaowo_sdk import sdk

@sdk.handler("hello")
def handle_hello(params):
    name = params.get("name", "World")
    return {"message": f"Hello, {name}!"}
```

### 4.5 SDK 属性

| 属性 | 类型 | 说明 |
|------|------|------|
| `sdk.plugin_id` | str | 插件唯一标识 |
| `sdk.plugin_dir` | str | 插件目录路径 |
| `sdk.plugin_data` | dict | 插件扩展配置（从环境变量 PLUGIN_DATA 解析） |

### 4.6 SDK 方法

#### @sdk.handler(method)

注册接口处理函数的装饰器。

```python
@sdk.handler("get_status")
def handle_get_status(params):
    return {"status": "running"}
```

#### sdk.send_event(event_name, data)

主动推送事件到前端。

```python
sdk.send_event("progress", {"percent": 50})
```

#### sdk.logerr(message)

输出日志到 stderr。

```python
sdk.logerr("发生错误")
```

#### sdk.run()

启动主循环，监听前端请求。仅在 `main.py` 中调用。

### 4.7 完整示例：长任务进度推送

```python
import time
from utils.xiaowo_sdk import sdk

@sdk.handler("long_task")
def handle_long_task(params):
    total = params.get("total", 5)

    for i in range(total):
        time.sleep(1)
        percent = int((i + 1) / total * 100)
        sdk.send_event("progress", {
            "current": i + 1,
            "total": total,
            "percent": percent
        })

    return {"success": True}
```

### 4.8 完整示例：获取插件信息

```python
@sdk.handler("get_info")
def handle_get_info(params):
    return {
        "plugin_id": sdk.plugin_id,
        "plugin_dir": sdk.plugin_dir
    }
```

### 4.9 注意事项（Python）

1. **编码问题**：建议在 `manifest.json` 中设置环境变量 `PYTHONIOENCODING: utf-8`
2. **返回值**：handler 函数必须返回可 JSON 序列化的对象

### 4.10 第三方包依赖管理

如果需要使用第三方包（非 Python 标准库），必须先在插件的 `manifest.json` 中声明依赖：

```json
{
  "backend": {
    "pip": {
      "requests": null,
      "numpy": "1.23.5"
    }
  }
}
```

- `null` 表示不限制版本
- 字符串值表示指定版本
- 插件启动时会自动检查并安装缺失的依赖
- 限配置小包，大型包 例如：torch等，请参考 [第二节 extmodels 扩展模型包配置](#2.6%20extmodels%20扩展模型包配置)

### 4.11 代码组织规范

**handlers.py 文件职责**：只存放 `@sdk.handler` 装饰的接口函数，保持文件简洁。

```python
# handlers.py - 只放接口函数
from utils.xiaowo_sdk import sdk
from utils.common import process_data

@sdk.handler("process")
def handle_process(params):
    result = process_data(params.get("data"))
    return {"result": result}
```

**utils 包职责**：
- 具体功能实现放在 `utils/` 目录下新建的文件中
- 常用函数、公共函数统一放到 `utils/common.py` 中

```
backend/
├── main.py
├── handlers.py          # 只放 @sdk.handler 装饰的函数
└── utils/
    ├── __init__.py
    ├── xiaowo_sdk.py    # SDK 核心
    ├── common.py        # 常用函数、公共函数
    └── video_utils.py   # 视频处理相关功能（示例）
```

---

## 五、通信协议（其他语言）

如果使用 Node.js、Go、Rust 等其他语言开发后端，需要自行实现 JSON-RPC 通信协议。

### 5.1 协议概述

- **输入**：从 stdin 读取 JSON 请求（每行一个）
- **输出**：向 stdout 写入 JSON 响应（需包裹特殊标记）
- **日志**：调试信息输出到 stderr（不影响通信）

### 5.2 消息格式

**请求格式**（从 stdin 读取）：

```json
{"id": 1, "method": "hello", "params": {"name": "World"}}
```

| 字段 | 说明 |
|------|------|
| `id` | 请求 ID，响应时需原样返回 |
| `method` | 方法名 |
| `params` | 参数对象 |

**响应格式**（写入 stdout）：

```
<<PLUGIN_MSG_START>>{"id": 1, "result": {"message": "Hello!"}}<<PLUGIN_MSG_END>>
```

**重要**：响应必须用 `<<PLUGIN_MSG_START>>` 和 `<<PLUGIN_MSG_END>>` 包裹。

**事件格式**（主动推送，id 为 0）：

```
<<PLUGIN_MSG_START>>{"id": 0, "result": {"event": "progress", "data": {"percent": 50}}}<<PLUGIN_MSG_END>>
```

### 5.3 后端就绪信号

后端启动后**必须**发送就绪信号，否则前端调用会失败：

```
<<PLUGIN_MSG_START>>{"id": 0, "result": {"event": "__backend_ready__", "data": {"status": "ready"}}}<<PLUGIN_MSG_END>>
```

### 5.4 Node.js 示例

```javascript
const readline = require('readline')

const MSG_START = '<<PLUGIN_MSG_START>>'
const MSG_END = '<<PLUGIN_MSG_END>>'

// 发送响应
function sendResponse(id, result) {
  const msg = JSON.stringify({ id, result })
  process.stdout.write(`${MSG_START}${msg}${MSG_END}`)
}

// 发送事件
function sendEvent(event, data) {
  sendResponse(0, { event, data })
}

// 处理请求
function handleRequest(request) {
  const { id, method, params } = request

  if (method === 'hello') {
    const name = params.name || 'World'
    sendResponse(id, { message: `Hello, ${name}!` })
  }
}

// 主循环
const rl = readline.createInterface({ input: process.stdin })

rl.on('line', (line) => {
  try {
    const request = JSON.parse(line)
    handleRequest(request)
  } catch (e) {
    console.error('解析错误:', e.message)
  }
})

// 发送就绪信号
sendEvent('__backend_ready__', { status: 'ready' })
```

---

## 六、快速开发指南

### 6.1 创建新插件步骤

1. 在 `plugins/` 目录下创建插件文件夹（如 `my-plugin`）
2. 创建 `manifest.json` 配置文件
3. 创建 `frontend/` 目录，开发前端界面
4. （可选）创建 `backend/` 目录，开发后端逻辑
5. 前端打包：`npm run build`（生成 `dist/index.html`）
6. 测试插件

### 6.2 开发模式调试

1. 设置 `manifest.json` 中 `dev_mode: true`
2. 配置 `dev.frontend.entry` 为本地开发服务器地址
3. 启动前端开发服务器：`npm run dev`
4. 在小蜗中打开插件，享受热重载

### 6.3 前后端通信流程

```
前端                              后端
  |                                 |
  |-- callBackend('hello', {})  -->|
  |                                 |-- @sdk.handler("hello") 处理
  |<-- 返回 {"message": "Hi!"}  ---|
  |                                 |
  |                                 |-- sdk.send_event("progress", {})
  |<-- onBackendEvent 接收事件  ---|
```

### 6.4 常见开发模式

**纯前端插件**：只需前端，无需后端，适合简单工具

**前后端插件**：前端 + Python 后端，适合需要复杂计算或系统操作的场景

**带模型包插件**：前后端 + 外部模型包，适合 AI 相关插件
