# DSH Plugin Hub 插件

[English](README.en.md) | 中文

把 [DSH Plugin Hub](https://dshpluginhub.dev) 的公开、只读搜索 API 接入 DeepSeek Harness。Agent 可以在对话中搜索已发布且通过验证的插件，查看排行榜，并取得安装命令、兼容性、社区指标和来源链接。

## 能做什么

- `dsh_plugin_search`：按任务、功能、插件名、仓库或作者搜索插件。
- `dsh_plugin_rankings`：查询日增长、Star 总榜、最新上架和最近活跃榜单。
- 支持中文和英文描述，以及分类、运行形态、安装来源、排序和分页筛选。
- Native 模式向模型返回紧凑、便于后续操作的文本；Code Mode 返回完整结构化结果。
- 校验公网 API 的每个返回字段，并暴露分页、数据更新时间和匿名限流信息。
- 支持 Harness 取消信号、可配置超时、429 重试信息和稳定的错误分类。

插件只调用公开的 `GET /api/v1/plugins/search`，不需要 API Key，不读取用户身份、收藏或私有数据，也不执行安装或其他写操作。安装命令只是结果数据，是否执行仍由 Harness 的 Shell、权限和审批策略控制。

## 安装与启动

从 npm 安装到 Web profile：

```sh
dsh plugin --profile web add @czj-git/dsh-plugin-hub
```

检查最终组合并启动：

```sh
dsh --profile web --dump-config
dsh --profile web
```

## 配置

Bundle 默认配置：

```yaml
- id: dsh-plugin-hub
  name: '@czj-git/dsh-plugin-hub'
  config:
    baseUrl: https://dshpluginhub.dev
    locale: en
    timeoutMs: 15000
    maxResults: 10
```

| 字段 | 类型 | 默认值 | 约束与行为 |
|---|---|---:|---|
| `baseUrl` | `string` | `https://dshpluginhub.dev` | API 服务地址；只接受 HTTP(S)，不能包含用户名、密码、查询参数或 URL 片段，末尾 `/` 会被移除。 |
| `locale` | `zh \| en` | `en` | 工具调用没有传入 `locale` 时使用的描述语言。 |
| `timeoutMs` | `integer` | `15000` | 每次 HTTP 请求的超时，范围为 1–120000 毫秒。 |
| `maxResults` | `integer` | `10` | 工具允许的最大 `limit`，范围为 1–50；同时也是每次调用的默认 `limit`。 |

后续 profile patch 覆盖配置时需要完整重述该行的全部 `config` 字段，因为 Cordis patch 替换整个配置对象而不是深度合并。

## 工具输入

### `dsh_plugin_search`

搜索已发布且通过验证的插件。`query` 会去除首尾空白并把连续空白合并成一个空格；规范化后为空会在发出网络请求前失败。

| 参数 | 必填 | 类型/可选值 | 默认值 | 说明 |
|---|---|---|---|---|
| `query` | 是 | `string` | — | 用户需求、功能、插件名、作者或仓库；规范化后必须非空，最长 100 个字符。 |
| `locale` | 否 | `zh`, `en` | 配置中的 `locale` | 返回本地化描述。 |
| `category` | 否 | 见“枚举值” | 全部分类 | 限定市场分类。 |
| `type` | 否 | `host`, `client`, `hybrid` | 全部形态 | 限定插件运行形态。 |
| `source` | 否 | `npm`, `github` | 全部来源 | 限定安装包来源。 |
| `sort` | 否 | `relevance`, `growth`, `stars`, `newest`, `active` | `relevance` | 控制稳定排序；非空关键词默认按相关性排序。 |
| `page` | 否 | `integer` | `1` | 页码，范围为 1–1000。 |
| `limit` | 否 | `integer` | 配置中的 `maxResults` | 本页条数，范围为 1–`maxResults`，且服务端上限为 50。 |

示例输入：

```json
{
  "query": "让纯文本 Agent 看懂截图",
  "locale": "zh",
  "category": "multimodal-creative",
  "type": "hybrid",
  "source": "npm",
  "sort": "relevance",
  "page": 1,
  "limit": 5
}
```

自然语言示例：

```text
帮我找一个能让纯文本 Agent 分析截图的 DeepSeek Harness 插件，优先返回 npm 包，并给出安装命令。
```

### `dsh_plugin_rankings`

使用同一个公开搜索端点的排序能力列出榜单。该工具没有关键词、`type` 或 `source` 参数。

| 参数 | 必填 | 类型/可选值 | 默认值 | 说明 |
|---|---|---|---|---|
| `ranking` | 是 | `growth`, `stars`, `newest`, `active` | — | 选择榜单口径。 |
| `locale` | 否 | `zh`, `en` | 配置中的 `locale` | 返回本地化描述。 |
| `category` | 否 | 见“枚举值” | 全部分类 | 只排列指定分类。 |
| `page` | 否 | `integer` | `1` | 页码，范围为 1–1000。 |
| `limit` | 否 | `integer` | 配置中的 `maxResults` | 本页条数，范围为 1–`maxResults`。 |

`ranking` 的含义：

| 值 | 榜单 | 排序依据 |
|---|---|---|
| `growth` | 日增长 | 两个最近成功快照之间的 GitHub Star 变化。无上一份快照时 `starsDelta1d` 为 `null`。 |
| `stars` | Star 总榜 | GitHub Stars 总数。 |
| `newest` | 最新上架 | DSH Plugin Hub 首次收录时间。 |
| `active` | 最近活跃 | GitHub 最近推送时间。 |

示例输入：

```json
{
  "ranking": "stars",
  "locale": "zh",
  "category": "coding-tools",
  "page": 1,
  "limit": 10
}
```

## 枚举值

分类 `category`：

| 值 | 含义 |
|---|---|
| `agent-workflow` | Agent 与工作流 |
| `coding-tools` | 编程与工具 |
| `models-data` | 模型与数据 |
| `ui-experience` | 界面与体验 |
| `integrations` | 连接与集成 |
| `security-governance` | 安全与治理 |
| `multimodal-creative` | 多模态与创作 |
| `observability-cost` | 监控与用量 |
| `other` | 其他 |

排序 `sort`：

- `relevance`：名称、仓库和作者匹配优先，其次是简介匹配；同级结果使用 Stars 和插件 ID 保持稳定顺序。
- `growth`、`stars`、`newest`、`active`：与排行榜工具中的同名口径一致。

## 工具输出

两个工具返回同一份结构化数据。Code Mode 可以读取全部字段；Native 模式使用下面的文本投影，避免把大量 JSON 填入模型上下文。

### 完整字段

| 路径 | 类型 | 说明 |
|---|---|---|
| `items` | `Plugin[]` | 当前页的插件。空结果为 `[]`，不是错误。 |
| `items[].id` | `string` | 插件的稳定公开 ID。 |
| `items[].slug` | `string` | `owner/repository` 形式的公开标识。 |
| `items[].name` | `string` | 插件显示名称。 |
| `items[].owner` | `string` | GitHub 仓库所有者。 |
| `items[].repo` | `string` | GitHub 仓库名。 |
| `items[].description` | `string` | 按 `locale` 返回的插件简介。 |
| `items[].type` | `host \| client \| hybrid` | 插件运行形态。 |
| `items[].category` | `string` | 市场分类枚举值。 |
| `items[].topics` | `string[]` | 仓库主题标签。 |
| `items[].language` | `string` | 仓库主要语言。 |
| `items[].license` | `string` | 许可证标识。 |
| `items[].package.name` | `string` | 包或安装目标名称。 |
| `items[].package.version` | `string` | 已验证版本。 |
| `items[].package.source` | `npm \| github` | 安装来源。 |
| `items[].package.sourceSpec` | `string` | 固定到已验证来源/版本的安装说明符。 |
| `items[].package.installCommand` | `string` | 可复制的 DSH 安装命令；返回命令不会自动执行。 |
| `items[].package.profile` | `string` | 建议安装的 Harness profile。 |
| `items[].compatibility.harnessVersion` | `string` | 验证时使用或要求的 Harness 版本。 |
| `items[].compatibility.verificationLevel` | `static-checked \| runtime-verified` | 静态检查或运行时验证级别。 |
| `items[].compatibility.smokeStatus` | `static-passed \| passed \| manual-step-required \| failed \| not-run` | Smoke 检查结果。 |
| `items[].compatibility.validatedAt` | `string` | 兼容性验证时间。 |
| `items[].metrics.stars` | `integer` | GitHub Stars。 |
| `items[].metrics.starsDelta1d` | `integer \| null` | 最近两个成功快照之间的 Star 变化；缺少基线时为 `null`。 |
| `items[].metrics.forks` | `integer` | GitHub Forks。 |
| `items[].metrics.openIssues` | `integer` | GitHub Open Issues。 |
| `items[].metrics.views` | `integer` | DSH Plugin Hub 公开浏览量。 |
| `items[].timestamps.listedAt` | `string` | 市场首次收录时间。 |
| `items[].timestamps.lastPushedAt` | `string` | GitHub 最近推送时间。 |
| `items[].timestamps.sourceUpdatedAt` | `string` | 来源数据最近更新时间。 |
| `items[].links.detail` | `string` | DSH Plugin Hub 详情页 URL。 |
| `items[].links.repository` | `string` | GitHub 仓库 URL。 |
| `pagination.page` | `integer` | 当前页码。 |
| `pagination.perPage` | `integer` | 服务端返回的每页条数。 |
| `pagination.total` | `integer` | 符合条件的插件总数。 |
| `pagination.totalPages` | `integer` | 总页数。 |
| `meta.apiVersion` | `v1` | 公网 API 版本。 |
| `meta.locale` | `zh \| en` | 实际使用的描述语言。 |
| `meta.query` | `string` | 服务端接收的规范化关键词；排行榜通常为空字符串。 |
| `meta.sort` | `string` | 实际使用的排序。 |
| `meta.dataUpdatedAt` | `string` | 此响应所基于的数据更新时间。 |
| `rateLimit.limit` | `integer \| null` | 响应头中的匿名额度上限；响应头缺失或无效时为 `null`。 |
| `rateLimit.remaining` | `integer \| null` | 响应头中的剩余额度；缺失或无效时为 `null`。 |
| `rateLimit.reset` | `string \| null` | `RateLimit-Reset` 响应头的原始值。 |
| `rateLimit.retryAfterSeconds` | `integer \| null` | `Retry-After` 的非负整数秒数；通常只在 429 时出现。 |

### 完整结构化输出示例

```json
{
  "items": [
    {
      "id": "123",
      "slug": "owner/plugin",
      "name": "plugin",
      "owner": "owner",
      "repo": "plugin",
      "description": "一个已通过验证的 DeepSeek Harness 插件。",
      "type": "host",
      "category": "coding-tools",
      "topics": ["dsh-plugin", "developer-tools"],
      "language": "TypeScript",
      "license": "MIT",
      "package": {
        "name": "dsh-plugin-example",
        "version": "1.0.0",
        "source": "npm",
        "sourceSpec": "dsh-plugin-example@1.0.0",
        "installCommand": "dsh plugin --profile web add dsh-plugin-example",
        "profile": "web"
      },
      "compatibility": {
        "harnessVersion": "0.1.0-rc.7",
        "verificationLevel": "runtime-verified",
        "smokeStatus": "passed",
        "validatedAt": "2026-08-27T00:00:00.000Z"
      },
      "metrics": {
        "stars": 42,
        "starsDelta1d": 3,
        "forks": 4,
        "openIssues": 1,
        "views": 20
      },
      "timestamps": {
        "listedAt": "2026-08-20T00:00:00.000Z",
        "lastPushedAt": "2026-08-26T00:00:00.000Z",
        "sourceUpdatedAt": "2026-08-27T00:00:00.000Z"
      },
      "links": {
        "detail": "https://dshpluginhub.dev/zh/plugins/owner/plugin",
        "repository": "https://github.com/owner/plugin"
      }
    }
  ],
  "pagination": {
    "page": 1,
    "perPage": 5,
    "total": 1,
    "totalPages": 1
  },
  "meta": {
    "apiVersion": "v1",
    "locale": "zh",
    "query": "截图分析",
    "sort": "relevance",
    "dataUpdatedAt": "2026-08-27T00:00:00.000Z"
  },
  "rateLimit": {
    "limit": 60,
    "remaining": 58,
    "reset": "42",
    "retryAfterSeconds": null
  }
}
```

### Native 模式文本示例

```text
Plugin search: 1 matches; showing 1 on page 1.
1. plugin (owner/plugin)
   一个已通过验证的 DeepSeek Harness 插件。
   coding-tools · host · 42 stars · 1d growth +3
   Install: dsh plugin --profile web add dsh-plugin-example
   Details: https://dshpluginhub.dev/zh/plugins/owner/plugin
   Repository: https://github.com/owner/plugin
Anonymous API quota remaining: 58/60.
```

空结果会返回有效的 `items: []`。Native 模式显示：

```text
Plugin search: no published, verified plugins matched. Try a broader query or remove a filter.
```

## 错误与取消

工具输入校验、网络访问、服务端错误和返回校验失败都会结束本次工具调用，不会把不完整数据伪装成成功结果。底层客户端抛出 `PluginHubApiError`，包含以下可编程字段：

| 字段 | 类型 | 说明 |
|---|---|---|
| `message` | `string` | 面向调用者的稳定错误说明。 |
| `code` | `string` | 本地错误码或服务端公开错误码。 |
| `status` | `integer \| null` | HTTP 状态码；请求未取得响应时为 `null`。 |
| `fields` | `Record<string, string>` | 服务端返回的字段级错误；没有时为空对象。 |
| `retryAfterSeconds` | `integer \| null` | 可重试等待秒数；只接受有效的非负整数响应头。 |

本地错误码：

| `code` | 触发条件 |
|---|---|
| `CANCELLED` | Harness 取消了正在进行的工具调用。 |
| `TIMEOUT` | 请求超过配置中的 `timeoutMs`。 |
| `NETWORK_ERROR` | DNS、TLS、连接或其他网络访问失败。 |
| `INVALID_RESPONSE` | 服务返回非 JSON，或成功响应不符合完整字段定义。 |
| `HTTP_<status>` | 非成功响应没有可识别的公开错误对象。 |

服务端错误码会原样保留，例如参数不合法的 `INVALID_QUERY` 或匿名额度耗尽的 `RATE_LIMITED`。429 响应同时读取 `Retry-After`。

程序化处理示例：

```ts
import { PluginHubApiError, PluginHubClient } from '@czj-git/dsh-plugin-hub/api'

const client = new PluginHubClient({
  baseUrl: 'https://dshpluginhub.dev',
  timeoutMs: 15_000,
})

try {
  const result = await client.search(
    { query: 'memory', locale: 'zh', page: 1, perPage: 5 },
    new AbortController().signal,
  )
  console.log(result.items)
} catch (error) {
  if (error instanceof PluginHubApiError) {
    console.error(error.code, error.status, error.retryAfterSeconds)
  }
}
```

## 开发与验证

```sh
npm install
npm run check
```

`npm run check` 依次运行严格类型检查、无网络单元测试和生产构建。GitHub 源安装会执行 `prepare` 生成 `dist/`；pnpm 10+ 会要求用户明确允许该构建脚本。若不希望授予安装期构建权限，请发布包含预构建 `dist/` 的 npm 包或 tarball。

## 限制

- 当前公开 API 只有搜索端点；排行榜工具通过同一端点的稳定排序参数实现。
- 插件详情正文、评论、收藏和用户数据不在公开 API 中，因此本插件不读取这些内容。
- 返回的安装命令不会自动执行，也不表示插件适用于所有 profile；调用者应检查 `package.profile` 和兼容性字段。
- 匿名额度由服务端控制；默认策略可能调整，工具只报告当前响应中的实际限流头。

## 许可证

MIT
