# 插件精灵脚本开发说明

> 本文是插件精灵的公开开发契约，面向脚本开发者和协助生成脚本的 AI。为兼容既有脚本，JavaScript 全局对象和元数据前缀继续使用 `SellerFeeds` 与 `@sellerfeeds-*`。平台实测差异与故障排查请同时参考《插件精灵脚本开发知识库》。

## 1. 先理解运行模型

插件精灵是 Chrome MV3 用户脚本运行器。脚本负责业务逻辑，插件负责安装、权限申请、任务调度、常用配置传递、任务历史和可选的 MCP 接入。

- 官方脚本和社区脚本使用同一套运行接口。
- 每个脚本可以声明一个或多个 Tool，但应优先把一个完整业务动作设计成一个 Tool。
- 手动、批量、定时和 MCP 调用最终都会进入同一个 `SellerFeeds.onTask` 处理器。
- 脚本自行决定外部 API 调用、字段映射、追加或覆盖、去重、报告及链接返回方式。
- 插件不会替脚本保存目标网站 Cookie，也不会替脚本实现某个平台的业务写入规则。

## 2. 最小可运行脚本

```javascript
// ==UserScript==
// @name         示例商品查询
// @namespace    https://example.com/sellerfeeds
// @version      1.0.0
// @description  根据商品编号查询商品信息
// @match        https://www.example.com/*
// @run-at       document-idle
// @grant        none
// @sellerfeeds-execution-mode page
// @sellerfeeds-tool {"name":"example_query_product","title":"查询商品","description":"当用户需要查询 Example 网站中的单个商品信息时使用。读取商品标题与价格，不修改网站数据；需要用户已登录 Example。","inputSchema":{"type":"object","properties":{"productId":{"type":"string","title":"商品编号","description":"Example 网站中的商品编号，不能为空。","minLength":1}},"required":["productId"],"additionalProperties":false},"outputSchema":{"type":"object","properties":{"productId":{"type":"string","description":"商品编号。"},"title":{"type":"string","description":"商品标题。"},"price":{"type":"number","description":"商品价格；网页未返回时省略该字段。"}},"required":["productId","title"]}}
// ==/UserScript==

SellerFeeds.onTask(async ({ taskId, tool, input, variables, source }) => {
  if (tool !== 'example_query_product') throw new Error(`不支持的 Tool：${tool}`);
  if (!input.productId) throw new Error('请填写商品编号');

  return {
    message: '查询完成',
    data: {
      productId: input.productId,
      title: '网页返回的真实标题',
    },
  };
});
```

`@sellerfeeds-tool` 和 `@sellerfeeds-variable` 的 JSON 必须各自写在一行中；文档中的格式化 JSON 只用于讲解。

## 3. 元数据字段

### 3.1 标准 UserScript 字段

| 字段 | 要求 |
|---|---|
| `@name` | 必填，脚本显示名称；与 `@namespace` 一起决定脚本身份。 |
| `@namespace` | 建议使用稳定且属于开发者的命名空间；不要在更新时修改。 |
| `@version` | 建议使用 `x.y.z` 数字版本；只有更高版本才会被识别为更新。 |
| `@description` | 清楚说明脚本用途，会显示在安装预览和脚本库中。 |
| `@author` | 可选，开发者名称。 |
| `@match` | 必填至少一条，仅支持明确的 HTTP/HTTPS 范围，禁止 `<all_urls>`。 |
| `@exclude-match` / `@include` / `@exclude` | 可选，用于进一步修正注入范围。 |
| `@run-at` | 可选，常用值为 `document-idle`。 |
| `@grant` | 建议固定为 `none`，通过 SellerFeeds API 使用运行能力。 |
| `@noframes` | 可选，声明后不注入 iframe。 |
| `@downloadURL` / `@updateURL` | HTTPS `.user.js` 地址；通过插件精灵脚本市场发布时由平台生成。 |
| `@homepageURL` / `@supportURL` | 可选，项目主页和支持地址。 |

### 3.2 插件精灵扩展字段（`@sellerfeeds-*`）

| 字段 | 要求 |
|---|---|
| `@sellerfeeds-execution-mode` | `page`（默认）或 `offscreen`；旧的 `background` 已废弃。 |
| `@sellerfeeds-entry-url` | `page` 模式可选；任务没有现成页面时自动打开的 HTTPS 地址，支持 `{input.字段}` 占位符。 |
| `@sellerfeeds-world` / `@inject-into` | `content`（默认隔离世界）或 `main/page`（MAIN 世界）。MAIN 世界能访问页面 JS，但网站也能观察和影响脚本。 |
| `@connect` | `offscreen` 模式可声明额外 API 域，例如 `https://api.example.com/*`。可写多条。 |
| `@sellerfeeds-tool` | Tool 的单行 JSON 声明，可写多条。 |
| `@sellerfeeds-variable` | “常用配置”的可选表单模板，单行 JSON，可写多条。 |
| `@sellerfeeds-official` | 插件精灵官方分发专用；社区脚本不得声明。 |

脚本源码最大 2MB。安装和更新地址必须使用 HTTPS，并以 `.user.js` 结尾。

## 4. 选择执行模式

| 对比项 | `page` | `offscreen` |
|---|---|---|
| 适合场景 | 读取或操作目标网页真实 DOM、页面 JS 状态、页面导航 | 纯 HTTP 请求与响应解析，不需要目标页 DOM |
| 执行位置 | 匹配网页的 USER_SCRIPT 或 MAIN 世界 | 插件的隔离 sandbox |
| 没有打开目标页时 | 插件按入口地址静默打开后台标签页 | 不打开标签页，直接在后台沙箱运行 |
| 网络访问 | 页面环境网络能力 | 使用 `SellerFeeds.fetch` 由扩展代发 |
| Cookie | 使用目标网页当前登录态 | 代发请求使用浏览器当前登录态 |
| DOM | 目标网站真实 DOM | 只有沙箱自身 DOM，可用 `DOMParser` 解析 HTML 字符串 |

选择原则：必须操作网页就用 `page`；只做“请求 + 解析”就用 `offscreen`。

### 4.1 page 模式入口

```javascript
// @sellerfeeds-execution-mode page
// @sellerfeeds-entry-url https://www.example.com/product/{input.productId}
```

占位符只能读取 `input` 顶层字段。缺少可选字段时，运行器会尝试回退到 `@match` 可推导出的首页，因此脚本仍需自行检查当前页面和输入。

### 4.2 offscreen 模式网络域

```javascript
// @match   https://www.example.com/*
// @connect https://api.example.com/*
// @sellerfeeds-execution-mode offscreen
```

`@match` 表示目标网站及基础授权范围；`@connect` 只增加允许请求的 API 域，不会把脚本注入该域。初始 URL 和每一次重定向都必须处于脚本声明且用户已授权的范围内。

## 5. SellerFeeds 运行时 API

### 5.1 接收任务

```javascript
SellerFeeds.onTask(async ({ taskId, tool, input, variables, source }) => {
  // source: manual | batch | scheduled | mcp
  return { message: '完成', data: {} };
});
```

| 字段 | 含义 |
|---|---|
| `taskId` | 本次任务的唯一编号。 |
| `tool` | 被调用的 Tool `name`。一个脚本有多个 Tool 时必须据此分派。 |
| `input` | 已按 `inputSchema` 生成并校验的任务参数。 |
| `variables` | 用户为本次任务选择的完整“常用配置”，未选择时为 `{}`。 |
| `source` | `manual`、`batch`、`scheduled` 或 `mcp`。 |

处理器返回值必须可被 JSON 序列化。抛出 Error 会使任务失败，错误信息会进入任务历史和 MCP 报告。

### 5.2 后台网络请求

`offscreen` 脚本通过以下接口请求目标网站或 `@connect` 域：

```javascript
const response = await SellerFeeds.fetch(url, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ keyword: input.keyword }),
});

if (!response.ok) throw new Error(`请求失败：HTTP ${response.status}`);
const result = await response.json();
```

限制如下：

- `body` 只支持字符串，JSON 对象必须先 `JSON.stringify`。
- `headers` 使用普通对象。
- 沙箱内不能直接使用 `chrome.*`。
- 桥接请求不能真正中止；需要超时提示时可使用 `Promise.race`，并忽略迟到结果。
- 不要在日志、报告或错误消息中输出 Cookie、Token 或完整常用配置。

### 5.3 日志与进度

```javascript
SellerFeeds.log('开始查询', { productId: input.productId });
SellerFeeds.emit('task-progress', { current: 20, total: 100 });
```

日志只写必要的诊断信息，不要记录凭据和完整响应中的隐私数据。

## 6. Tool 与输入 Schema

### 6.1 Tool 声明结构

```json
{
  "name": "amazon_collect_search_results",
  "title": "采集亚马逊搜索结果",
  "description": "当用户需要分析亚马逊关键词搜索结果时使用。读取自然位与广告位，返回商品列表和位置类型；需要浏览器已登录目标站点，只读取数据，不修改店铺。",
  "inputSchema": {
    "type": "object",
    "properties": {},
    "required": [],
    "additionalProperties": false
  },
  "outputSchema": {
    "type": "object",
    "properties": {}
  }
}
```

### 6.2 MCP 发布硬性要求

脚本能在本地运行，不代表它会自动成为 MCP Tool。安装后默认不发布到 MCP；只有用户开启全局 MCP，并在脚本中心单独打开该脚本的 MCP 开关，AI 才能看到它。

发布前必须满足：

1. `name` 匹配 `^[A-Za-z0-9_-]{1,128}$`，不能使用保留名 `get_report`。
2. `name` 应稳定且具有业务前缀，推荐“平台_动作_对象”，如 `amazon_collect_search_results`。
3. `description` 明确回答：何时使用、做什么、作用范围、返回什么、需要什么前提、是否有副作用。
4. `inputSchema` 根节点必须是 `{"type":"object"}`。
5. 每个顶层参数必须有准确的 `type` 和 `description`；数组还必须声明 `items.type`。
6. `required` 只能引用 `properties` 中存在的字段。
7. 同名 Tool 会冲突，发生冲突的 Tool 都不会发布。

建议为 Tool 和参数提供普通用户能看懂的 `title`，并声明稳定的 `outputSchema`。

### 6.3 Schema 设计要求

支持常用 JSON Schema 约束，包括 `type`、`enum`、`default`、`minimum`、`maximum`、`minLength`、`pattern`、`minItems`、`required` 和 `additionalProperties`。

- 字段必须对应网页或接口真实存在的输入。
- 枚举只列出真实选项，不虚构“自动”等页面没有的值。
- 数值使用 `number` 或 `integer`，不要让用户填写带单位的字符串。
- 描述中写清单位、格式、示例、范围以及字段间关系。
- Schema 负责基础校验；登录状态、跨字段关系和业务错误仍由脚本检查。
- 没有获取到的数据返回 `null` 或省略，不得使用 `0` 或空字符串伪造真实值。

## 7. 常用配置与配置模板

“常用配置”让普通用户保存一组经常重复填写的名称和值，例如表格地址、授权码、店铺编号或固定筛选条件。它是通用参数库，不绑定某个脚本或平台。

### 7.1 自动匹配规则

用户为任务选择一项常用配置后：

- 与 Tool `inputSchema` 同名、同路径的值会自动填入 `input`。
- 用户在任务表单中手动修改的值优先。
- 不在 Schema 中的内容不会混入 `input`，但脚本仍能从完整的 `variables` 中读取。
- 未选择常用配置时，`variables` 为 `{}`。
- 手动、批量和定时任务都可以选择常用配置；定时任务运行时读取该配置的最新值。

优先级为：用户手动填写 > 常用配置同名值 > Schema 默认值。

### 7.2 预制录入模板

脚本可声明一个或多个表单模板：

```javascript
// @sellerfeeds-variable {"title":"飞书多维表格配置","description":"填写表格写入所需信息","schema":{"type":"object","properties":{"baseToken":{"type":"string","title":"多维表格授权码"},"appToken":{"type":"string","title":"App Token"},"tableId":{"type":"string","title":"Table ID"},"baseUrl":{"type":"string","title":"表格链接"}},"required":["baseToken","appToken","tableId"]}}
```

安装脚本时，用户可以选择模板或“稍后设置”。选中后，安装完成会打开对应表单。模板只改善录入体验，不绑定或过滤常用配置，也不限制开发者增加任意字段。

### 7.3 在脚本中读取

```javascript
SellerFeeds.onTask(async ({ input, variables }) => {
  const { baseToken, appToken, tableId, baseUrl } = variables;
  if (!baseToken || !appToken || !tableId) {
    throw new Error('所选常用配置缺少授权码、App Token 或 Table ID');
  }

  // 追加、覆盖、更新、去重和字段映射均由脚本决定。
  return {
    message: '多维表格更新成功',
    links: baseUrl ? [{ title: '打开多维表格', url: baseUrl }] : [],
  };
});
```

常用配置可以在自动表单和 JSON 模式之间切换。脚本与常用配置分别由用户手动同步；保存或编辑本身不会自动上传。

## 8. 手动、批量与定时任务

同一个 Tool 不需要为三种任务模式编写三套代码。

- 手动任务：执行一次表单或 JSON 输入。
- 批量任务：选定一个批量字段，多行值会拆成多个任务；其余字段作为每项任务的公共输入。
- 定时任务：保存 Tool、输入、常用配置选择和时间规则，到点后自动调用。
- 运行时可通过 `source` 区分来源，但除非业务确实不同，不建议分叉核心逻辑。

脚本应保证重复执行的行为可预测。是否追加、覆盖、更新或去重必须由脚本根据业务要求明确实现，不要假设插件会代为处理。

## 9. 任务结果、报告和通知

脚本返回值可以同时包含结构化数据、摘要、链接和文件：

```javascript
return {
  message: '采集和写入完成',
  data: { total: records.length, records },
  links: [{ title: '打开结果表格', url: tableUrl }],
  files: [
    { name: '结果.md', format: 'markdown', content: '# 结果' }
  ],
};
```

结果规则：

- `message` 用于任务摘要和通知文字。
- `data` 是结构化结果，会进入任务报告和 MCP `public_report`。
- `links` 是用户可打开的 HTTPS 链接。
- `files` 是脚本主动返回的文件；链接和文件可以同时存在。
- 有文件就发送文件，有链接就发送链接，两者都有就一起发送。
- 只有链接时，插件不会擅自生成报告文件。
- 没有 `files` 不等于任务失败；报告形式由脚本结果和脚本声明共同决定。

大结果应主动精简。任务历史对超大数据可能只保留截断摘要；不要依赖任务历史保存无限量业务数据。

## 10. MCP 调用模型

MCP 业务 Tool 使用异步任务模型：

1. AI 调用脚本 Tool。
2. 服务返回 `task_id`。
3. 浏览器插件领取并执行任务。
4. AI 使用系统 Tool `get_report` 和 `task_id` 查询状态及结果。

因此 Tool 的描述和 Schema 必须足够清楚，让 AI 在不阅读源码的情况下判断是否应该调用、怎样填写参数以及会产生什么结果。

控制规则：

- 脚本安装后 MCP 默认关闭。
- 全局 MCP 开关和脚本级 MCP 开关必须同时开启。
- 脚本不能替用户开启 MCP。
- 停用脚本后，它的 Tool 会退出 MCP 清单。
- 脚本更新保留用户原来的 MCP 选择。
- MCP 来源任务不发送普通任务通知，结果由 MCP 客户端通过 `get_report` 获取。

## 11. 权限和安全边界

- `@match` 和 `@connect` 应遵循最小范围原则，不要申请无关域名。
- 安装或更新增加新域名时，必须由用户确认网站权限。
- 脚本只能读取用户主动为任务选择的那一项常用配置。
- 脚本拿不到插件精灵 License、服务端密钥或其他脚本的数据。
- 不得把 Cookie、Token、授权码或用户业务数据上传到声明用途之外的第三方。
- 不要把完整凭据写入源码、日志、异常信息、任务返回值或报告。
- 使用 MAIN 世界、`eval`、`new Function`、Cookie、本地存储或页面跳转等能力会产生风险提示，应仅在确有必要时使用。
- 未登录、Cookie 失效、验证码、Robot Check、HTTP 401/403/429、超时和平台业务错误都必须真实报告，不得伪造成功。

## 12. 安装、更新和身份

- 脚本身份由稳定的 `@namespace` 与 `@name` 共同确定；修改其中任意一个会被视为新脚本。
- 更新必须提高 `@version`，并保持脚本身份不变。
- 更新新增网站权限时，用户需要重新授权。
- 本地修改过源码的脚本不会被无提示覆盖。
- 卸载脚本后，运行器会回收不再被其他脚本使用的网站权限。
- 社区脚本安装前应让用户审阅源码、网站范围和风险提示。

## 13. 开发与测试清单

提交脚本前至少确认：

1. 元数据块完整，`@sellerfeeds-tool` 和 `@sellerfeeds-variable` 均为单行 JSON。
2. `@match`、`@connect` 和执行模式符合真实需求。
3. Tool 名称稳定、唯一，描述能让普通用户和 AI 判断使用场景。
4. 所有输入字段都有准确类型、标题、说明、单位、范围或示例。
5. `onTask` 校验必填项、跨字段规则、登录状态和业务响应码。
6. 正常结果、空结果、未登录、限流、验证码、超时和接口错误均经过测试。
7. 批量和定时运行不依赖当前活动页面，或已正确提供 `@sellerfeeds-entry-url`。
8. 没有在源码、日志和结果中泄露 Cookie 或常用配置。
9. 返回字段来自真实网页或接口；缺失值不伪造为 0。
10. 手动、批量、定时和 MCP 共用同一个稳定业务入口。

仓库开发时运行：

```bash
pnpm run verify
```

## 14. 如何把文档交给 AI

生成或修改脚本时，建议向 AI 同时提供：

1. 本开发说明 Markdown：`https://www.sellerfeeds.com/docs/development.md`
2. 实测知识库 Markdown：`https://www.sellerfeeds.com/docs/knowledge.md`
3. 具体业务需求、目标网站、输入字段、预期输出和是否允许写入外部系统。

可以直接使用以下提示词：

```text
请先完整阅读插件精灵脚本开发说明和知识库，再根据我的需求编写一个可安装的 .user.js。
必须遵守元数据、执行模式、常用配置、Tool Schema、MCP 描述、权限和结果契约。
对知识库没有确认的页面字段或接口行为，不要猜测，请明确列出需要我实测的信息。
我的需求是：……
```

知识库记录平台的实测事实；开发说明定义运行器契约。两份文档发生表述冲突时，以开发说明的运行器契约为准，以知识库中标注的最新实测结论解释目标平台行为。
