构建可浏览的 Corsair 插件目录:corsair-explorer 独立服务全解析
corsair-explorer 是 Corsair 仓库中一个独立、可独立部署的 Express 服务,它把仓库内所有 @corsair-dev/* 插件及其定义的 api、webhook、db 三类操作,静态化为一份公开、可浏览、可检索的 JSON 目录(catalog)。本文将从构建管线、目录数据结构、REST API、运行配置到源码实现逐层展开,读完你将掌握如何重新生成目录、本地运行/生产部署这个服务,并通过 REST 或编程接口把插件能力开放给任何客户端。
一、为什么需要 explorer:把"未安装的插件"也变成可发现资产
在 Corsair 的生态里,插件(plugin)是连接用户第三方应用(如 Slack、Gmail、GitHub)能力的核心单元,每个插件会声明若干 api(可调用的端点)、webhook(可订阅的事件)与 db(可同步的实体)。普通用户只有在本地安装某个插件后,才能看到它内部暴露了哪些能力——这显然限制了营销站、文档站对插件生态的展示。
explorer/README.md 给出的解法是:用一个小型独立 Express 服务器,公开一个可浏览的全量插件目录,让用户在没有本地安装任何插件的情况下,也能探索所有插件及其全部操作。它被设计为部署在稳定的 URL(例如 api.corsair.dev)之后,供营销站点直接指向,从而让"浏览插件"这件事与本地运行时彻底解耦。
从目录结构上看,explorer/ 拥有自己的 pnpm-workspace.yaml(空文件)、package.json 与 lockfile,不属于 monorepo workspace 包,因此可以独立容器化或移动到任何位置,不会拖带上仓库其余部分。它与 monorepo 的唯一连接点,是负责产出 data/catalog.json 与 data/plugins/*.json 的生成脚本。
二、工作原理:从插件源码到静态 JSON 的两段式管线
整个 explorer 的运作可以拆成"构建期"与"运行期"两个完全解耦的阶段:
[构建期] scripts/build-explorer-catalog.ts
├─ 扫描 packages/* 下每个 @corsair-dev/* 插件
├─ 动态 import 插件工厂,执行 introspectPluginForDocs()
└─ 写出 explorer/data/catalog.json + explorer/data/plugins/<id>.json
[运行期] src/server.ts(Express)
├─ 启动时读取 catalog.json 索引
├─ 按需懒加载 data/plugins/<id>.json
└─ 通过 REST /v1/* 对外提供 JSON
2.1 构建期:introspectPluginForDocs 的静态化
scripts/build-explorer-catalog.ts 是整条管线的入口。它会遍历 packages/* 下的所有插件包(跳过 corsair、cli、mcp、ui、studio 等非集成插件目录),动态导入每个插件工厂,调用 Corsair 核心的 introspectPluginForDocs(定义在 packages/corsair/core/inspect/index.ts)对其做文档级自省,然后把结果序列化为两类产物:
explorer/data/catalog.json—— 目录元数据、插件摘要列表(summary)与一份扁平化的搜索索引;explorer/data/plugins/<id>.json—— 每个插件的完整记录(含全部 api / webhook / db 详情)。
值得注意的是,脚本头部注释明确说明它刻意维护了一份"小而自包含"的发现逻辑副本(与 scripts/generate-plugin-docs.ts 同源但可独立演进),插件的展示文案(displayName、description)优先读取 packages/<plugin>/plugin-docs.yaml,缺失时回退到各包的 package.json。
2.2 运行期:只依赖 JSON,不依赖 corsair
运行期服务 explorer/src/server.ts 启动时只做一件事:读取 catalog.json 索引,之后对 data/plugins/*.json 按请求懒加载。这意味着 explorer 对 corsair 核心没有任何运行时依赖——它只是一个读取 JSON 的静态数据服务,天然轻量、易部署、易缓存。
2.3 重新生成目录
每当插件有增删改,从仓库根目录执行:
pnpm build:explorer-catalog
生成的 explorer/data/catalog.json 与 explorer/data/plugins/ 应随部署一并提交(commit)。在 package.json 中也能看到这一脚本(build:explorer-catalog)与 explorer 目录的数据文件是配套提交的。
三、目录数据结构:index + 每插件文件,并兼容 v1 单体格式
3.1 catalogVersion 2(当前格式)
explorer/src/types.ts 明确定义了 PluginCatalogIndex(catalogVersion 恒为 2)的骨架:
| 字段 | 类型 | 说明 |
|---|---|---|
generatedAt |
string |
目录构建时间的 ISO 时间戳 |
corsairVersion |
string |
构建时 corsair 包版本 |
catalogVersion |
2 |
供消费方做破坏性变更防护的 schema 版本 |
plugins |
PluginSummary[] |
每插件一行摘要(不含操作详情) |
search |
CatalogSearchEntry[] |
扁平化搜索索引 |
从实际的 explorer/data/catalog.json 可以看到真实数据形态,例如第一个插件:
{
"generatedAt": "2026-09-08T17:15:47.532Z",
"corsairVersion": "0.1.128",
"catalogVersion": 2,
"plugins": [
{
"id": "ably",
"displayName": "Ably",
"description": "Realtime messaging platform for pub/sub, chat, and live data streaming at scale.",
"npmPackageName": "@corsair-dev/ably",
"authTypes": ["api_key"],
"defaultAuthType": "api_key",
"counts": { "api": 26, "webhooks": 0, "db": 0 }
}
]
}
3.2 PluginEntry:插件的完整记录
每个 data/plugins/<id>.json 对应一个 PluginEntry,它在 PluginSummary 基础上追加四个数组:
auth: PluginAuthFields[]—— 每种受支持认证类型的字段元数据,按oauth_2 | api_key | bot_token三种认证类型组织,并区分存储在集成层(shared/provider)的integrationFields与账户层(per-tenant)的accountFields;api: DocsApiEndpoint[]—— 每个端点含path、shortPath(如messages.post)、可选description、riskLevel(read | write | destructive)、irreversible、input/output的 schema 描述;webhooks: DocsWebhook[]—— 含path、shortPath、payloadschema、responseType与usageExample;db: DocsDbEntity[]—— 可同步实体,含entityName与可过滤字段filters(每个字段声明string | number | boolean | date类型及其可用操作符)。
3.3 兼容 v1 单体格式
explorer/src/catalog.ts 的 loadCatalog 在读取文件后会做形状判定:若是 catalogVersion === 2 的 index,则进入"索引 + 按插件文件懒加载"模式;若是 catalogVersion === 1 的遗留单体 PluginCatalog(所有插件塞在一个文件里),则通过 catalogFromLegacyMonolith 自动转换成 index 形态并预载全部插件,向上兼容老数据。resolveCatalogPath 还会在目标路径不存在时回退尝试同目录下的 plugins.json,进一步兼容旧布局。
四、安装、运行与生产部署
4.1 一次性安装(在 explorer 目录内)
cd explorer
pnpm install
这会生成 explorer/node_modules 与 explorer/pnpm-lock.yaml,与 monorepo 完全隔离。服务仅有一个运行时依赖 express ^4.21.0(见 explorer/package.json),开发期使用 tsx 直接跑 TypeScript。
4.2 本地开发
pnpm dev
# → http://localhost:4319
dev 脚本是 tsx watch src/cli.ts,支持热重载。启动成功后,explorer/src/cli.ts 会打印一行摘要,例如:
[corsair:explorer] listening on http://0.0.0.0:4319 — 285 plugins, generated 2026-09-08T17:15:47.532Z
(实际插件数量以你本地生成的 catalog.json 为准。)
4.3 环境变量
| 变量 | 默认值 | 用途 |
|---|---|---|
PORT |
4319 |
绑定端口 |
HOST |
0.0.0.0 |
绑定主机 |
EXPLORER_CATALOG_PATH |
打包内置的 data/catalog.json |
覆盖目录索引位置 |
EXPLORER_CORS_ORIGIN |
* |
CORS 允许的 origin 头 |
cli.ts 中的解析逻辑为 Number.parseInt(process.env.PORT ?? '', 10) || 4319,即非法或缺失的端口值都会安全回退到默认端口。EXPLORER_CORS_ORIGIN 默认不设置,交由 createServer 的默认值 * 兜底。
4.4 生产构建与启动
pnpm build
pnpm start
build 脚本是 tsup && tsc -p tsconfig.build.json,产出 dist/;start 为 node dist/cli.js。explorer/package.json 同时声明了 bin 字段(corsair-explorer),并保证发布产物包含 dist、data、icons 三个目录。
五、REST API 全览
所有路由均返回 JSON 且默认美化输出(app.set('json spaces', 2),见 explorer/src/server.ts)。完整路由表如下:
| 路由 | 说明 |
|---|---|
GET / |
服务自描述:名称、描述与全部端点列表 |
GET /health |
存活探针,返回 { ok: true } |
GET /v1/meta |
目录元数据:generatedAt、corsairVersion、catalogVersion、pluginCount |
GET /v1/plugins |
摘要列表(不含操作详情) |
GET /v1/plugins/:id |
插件完整记录 |
GET /v1/plugins/:id/api |
某插件全部 API 端点 |
GET /v1/plugins/:id/api/:shortPath |
按 shortPath(如 messages.post)取单个端点 |
GET /v1/plugins/:id/webhooks |
某插件全部 webhook |
GET /v1/plugins/:id/webhooks/:shortPath |
按 shortPath 取单个 webhook |
GET /v1/plugins/:id/db |
某插件全部同步 db 实体 |
GET /v1/plugins/:id/db/:entity |
按实体名取单个实体 |
GET /v1/search?q=<query> |
跨插件 id、显示名、描述与端点 shortPath 的子串匹配搜索 |
5.1 统一响应头与缓存失效
中间件为每个响应统一附加两个头(explorer/src/server.ts):
X-Catalog-Generated-At—— 目录生成时间;X-Corsair-Version—— 构建目录时的 corsair 版本。
客户端可以据此判断目录是否已重建,从而主动失效本地缓存。
5.2 错误语义与通配符细节
- 插件不存在统一返回
404,错误体形如{ error: 'plugin_not_found', message: 'No plugin "xxx" in the catalog.' }; - 端点/webhook/实体不存在时,
404响应体不仅给出error与message,还会附带available字段列出该插件实际可用的 shortPath/实体名,方便客户端自纠错; - 单个端点路由使用 Express 通配符
*(而非:shortPath)实现,正是为了保住messages.post这类带点的 shortPath;源码中的getWildcardParam会从req.params<a href="https://link.gitcode.com/i/93c39410840317dc2e48650a4a30257e" target="_blank">'0']取出通配符匹配值([explorer/src/server.ts); - 未带
q参数的搜索请求返回400(missing_query)。
5.3 搜索接口
/v1/search?q=<query> 返回 { query, results },其中 results 分三组:命中的插件摘要、命中的 api 端点(带 pluginId)、命中的 webhook(带 pluginId)。搜索是大小写不敏感的子串匹配,且索引在构建期就已生成好:buildSearchIndex 为每个 api 端点拼接 shortPath + path + description + riskLevel 为 haystack,为每个 webhook 拼接 shortPath + path + description,全部小写化后写入 catalog.json 的 search 数组(explorer/src/catalog.ts)。运行期只需遍历索引做 includes 判断,命中后再按需加载对应插件文件补齐详情,因此搜索响应非常快。
六、编程式集成:作为库嵌入你的服务
除了独立 CLI 运行,explorer 还以库的形式导出,可在任意 Express 应用中直接挂载(见 explorer/README.md 的 Programmatic use 一节,导出实现在 explorer/src/index.ts):
import { createServer, loadCatalog } from 'corsair-explorer';
const catalog = loadCatalog();
const app = createServer({ catalog });
app.listen(4319);
createServer 接受 { catalog, corsOrigin? } 两个选项(explorer/src/server.ts),其中 corsOrigin 默认 *——源码注释明确说明原因是目录只含公开的插件元数据、不含任何密钥,因此允许任何浏览器客户端访问是安全的。这一设计让 explorer 既能独立部署,也能被测试或与其他中间件组合挂载。
七、源码级原理:懒加载、缓存与数据校验
explorer/src/catalog.ts 中的 Catalog 类是运行期的核心,值得关注的实现细节有三点:
- 按需懒加载 + 内存缓存:
loadPlugin(id)先查pluginCache(Map<string, PluginEntry>),未命中再读data/plugins/<id>.json并解析、校验、缓存(explorer/src/catalog.ts)。对于 v1 遗留单体数据,则通过preloadedPlugins一次性预载。 - 严格形状校验:加载的插件文件必须通过
isPluginEntry校验(id、displayName、npmPackageName 为字符串,auth/api/webhooks/db 为数组,counts 为对象),解析失败或形状不符都会抛出带[corsair:explorer]前缀、含文件路径的明确错误;索引读取失败时错误信息会提示Did you run pnpm build:explorer-catalog?,引导用户先构建数据。 - 摘要与详情的分层:
listSummaries()直接返回索引中的PluginSummary[](只含 id、displayName、description、npmPackageName、authTypes、defaultAuthType、counts),列表接口不触发任何插件文件加载;只有getPlugin/findApiEndpoint/findWebhook/findDbEntity这类需要详情的调用才触发懒加载。分层带来的直接收益是/v1/plugins与/v1/meta即使插件数量再多也只需一次索引读盘。
八、把 explorer 放进你的部署
综合以上,一套完整的落地路径是:
- 在仓库根目录执行
pnpm build:explorer-catalog,把生成的explorer/data/catalog.json与explorer/data/plugins/提交入库; - 在
explorer/内执行pnpm install(独立 lockfile,不污染 monorepo); - 生产环境执行
pnpm build && pnpm start,用PORT/HOST控制监听地址,EXPLORER_CORS_ORIGIN收敛跨域来源; - 把它放在稳定 URL(如
api.corsair.dev)之后,让营销站点/文档站通过/v1/*展示未安装插件的全部 api、webhook 与 db 能力; - 利用
X-Catalog-Generated-At响应头做客户端缓存失效,插件更新后重新跑一次生成脚本即可。
由于运行期完全不依赖 corsair 核心,只消费静态 JSON,explorer 可以放心地容器化、水平扩展甚至迁移到 CDN 之后——它本质上是把"插件生态"变成了一份任何客户端都能消费的公开数据面。
延伸阅读:插件文档生成主脚本见 scripts/generate-plugin-docs.ts;文档自省能力定义在 packages/corsair/core/inspect/index.ts;插件目录数据实样见 explorer/data/catalog.json。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.24 K638- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python670
SlideSCIPPT插件,支持素材库、AI助手、一键添加图片标题,复制粘贴位置、一键图片对齐、一键插入Markdown(加粗、超链接等行内样式、代码块、LaTeX等块级样式)、便捷导出图片!C#230
hello-agents📚 《从零开始构建智能体》——从零开始的智能体原理与实践教程Python52874
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go22545
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java36351