mirror of
https://git.openapi.site/https://github.com/desirecore/registry.git
synced 2026-09-05 16:03:36 +08:00
feat: 添加 Kimi WebBridge 受控目录条目 (#2)
This commit is contained in:
27
.github/workflows/validate.yml
vendored
Normal file
27
.github/workflows/validate.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Validate Registry
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
steps:
|
||||||
|
- name: Check out registry
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 20
|
||||||
|
cache: npm
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
- name: Validate registry
|
||||||
|
run: npm test
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
84
README.md
84
README.md
@@ -1,16 +1,22 @@
|
|||||||
# DesireCore Registry
|
# DesireCore Registry
|
||||||
|
|
||||||
DesireCore 官方注册表仓库,包含所有可安装的应用、MCP 服务和 HTTP API 服务。
|
DesireCore 官方注册表仓库,包含可安装的应用、MCP 服务、HTTP API 服务,以及只用于发现和合规披露的第三方外部集成。
|
||||||
|
|
||||||
DesireCore 客户端启动时会克隆此仓库,并定期同步更新。用户在应用商店中看到的所有条目均来自此仓库。
|
DesireCore 客户端启动时会克隆此仓库,并定期同步更新。用户在应用商店中看到的所有条目均来自此仓库。
|
||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
|
|
||||||
```
|
```text
|
||||||
.
|
.
|
||||||
├── README.md # 本文件
|
├── README.md # 本文件
|
||||||
├── SCHEMA_VERSION # 数据格式版本号(当前 3.0.0)
|
├── SCHEMA_VERSION # 数据格式版本号(当前 4.0.0)
|
||||||
├── manifest.json # 仓库元数据(版本、统计、维护者)
|
├── manifest.json # 仓库元数据(版本、统计、维护者)
|
||||||
|
├── package.json # Registry 校验入口
|
||||||
|
├── schemas/
|
||||||
|
│ └── registry-entry.schema.json # Draft-07 严格判别 Schema
|
||||||
|
├── scripts/
|
||||||
|
│ ├── validate-registry.mjs # 仓库语义校验
|
||||||
|
│ └── validate-registry.test.mjs # 失败关闭回归测试
|
||||||
├── entries/ # 🔑 所有注册表条目(统一格式)
|
├── entries/ # 🔑 所有注册表条目(统一格式)
|
||||||
│ ├── n8n/ # 示例:Docker 应用
|
│ ├── n8n/ # 示例:Docker 应用
|
||||||
│ │ ├── manifest.json # 条目元数据(必需)
|
│ │ ├── manifest.json # 条目元数据(必需)
|
||||||
@@ -20,9 +26,11 @@ DesireCore 客户端启动时会克隆此仓库,并定期同步更新。用户
|
|||||||
│ │ ├── manifest.json
|
│ │ ├── manifest.json
|
||||||
│ │ ├── install.md
|
│ │ ├── install.md
|
||||||
│ │ └── usage.md
|
│ │ └── usage.md
|
||||||
│ └── baidu-map/ # 示例:HTTP API(无需安装)
|
│ ├── baidu-map/ # 示例:HTTP API(无需安装)
|
||||||
│ ├── manifest.json
|
│ │ ├── manifest.json
|
||||||
│ └── usage.md
|
│ │ └── usage.md
|
||||||
|
│ └── kimi-webbridge/ # external-integration:只允许 manifest.json
|
||||||
|
│ └── manifest.json
|
||||||
├── models/ # 模型能力描述符与分类
|
├── models/ # 模型能力描述符与分类
|
||||||
│ ├── descriptors.json
|
│ ├── descriptors.json
|
||||||
│ └── categories.json
|
│ └── categories.json
|
||||||
@@ -45,7 +53,7 @@ DesireCore 客户端启动时会克隆此仓库,并定期同步更新。用户
|
|||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| `id` | string | ✅ | 唯一标识,与目录名一致 |
|
| `id` | string | ✅ | 唯一标识,与目录名一致 |
|
||||||
| `name` | string | ✅ | 显示名称 |
|
| `name` | string | ✅ | 显示名称 |
|
||||||
| `type` | string | ✅ | 条目类型:`docker-app` / `mcp` / `http-api` |
|
| `type` | string | ✅ | 条目类型:`docker-app` / `mcp` / `http-api` / `external-integration` |
|
||||||
| `version` | string | ✅ | 语义版本号 |
|
| `version` | string | ✅ | 语义版本号 |
|
||||||
| `description` | string | ✅ | 一行功能摘要 |
|
| `description` | string | ✅ | 一行功能摘要 |
|
||||||
| `author` | string | | 作者或组织 |
|
| `author` | string | | 作者或组织 |
|
||||||
@@ -102,26 +110,46 @@ MCP `connection.transport` 取值:`stdio` / `streamable-http` / `sse`
|
|||||||
| `sourceAppId` | string | 关联的应用 ID |
|
| `sourceAppId` | string | 关联的应用 ID |
|
||||||
| `sourceAppName` | string | 关联的应用名称 |
|
| `sourceAppName` | string | 关联的应用名称 |
|
||||||
|
|
||||||
|
**第三方外部集成专属字段(`type: "external-integration"`):**
|
||||||
|
|
||||||
|
Schema v4 的 external integration 是严格、失败关闭的目录指针,不是 `StoreApp`、安装事实、连接或 Browser Provider。当前只准入经过审核的 `kimi-webbridge` ID,并固定以下治理语义:
|
||||||
|
|
||||||
|
- `stewardship: "pointer"`
|
||||||
|
- `availability: "listing-only"`
|
||||||
|
- `redistribution: "source-pointer-only"`
|
||||||
|
- `branding.relationship: "independent-listing"`
|
||||||
|
- `branding.nameUsage: "nominative"`
|
||||||
|
- `branding.logoStatus: "not-used"`
|
||||||
|
- `admission.status: "blocked"`
|
||||||
|
|
||||||
|
条目还必须结构化披露上游维护者、完整官方 URL、浏览器扩展 ID、物理组件、扩展权限、独立 Profile 建议、WebBridge 本地链路与 DesireCore 模型 Provider 的不同数据边界、阻塞准入原因,以及固定版本 daemon 的 SHA-256 审核记录。
|
||||||
|
|
||||||
|
external integration 目录必须恰好包含一个常规文件 `manifest.json`。额外文件、目录、符号链接、设备节点、FIFO、Socket 或二进制制品全部被拒绝;因此它没有可被 Agent 当成安装指令执行的自由文本面。`sourceId` 和 `hasInstall` 仅由客户端从可信来源和目录事实注入,禁止写入上游 manifest。
|
||||||
|
|
||||||
### install.md(可选)
|
### install.md(可选)
|
||||||
|
|
||||||
自然语言安装说明,供 DesireCore Agent 读取并执行安装流程。
|
自然语言安装说明,供 DesireCore Agent 读取并执行安装流程。
|
||||||
|
|
||||||
内容应包含:
|
内容应包含:
|
||||||
|
|
||||||
- 环境要求(Node.js 版本、Python 等)
|
- 环境要求(Node.js 版本、Python 等)
|
||||||
- 安装步骤(可直接执行的命令)
|
- 安装步骤(可直接执行的命令)
|
||||||
- 验证方式
|
- 验证方式
|
||||||
|
|
||||||
**不需要 install.md 的情况**:纯 HTTP API 服务(无需在本地安装)、通过关联应用附带安装的服务(如 dify-mcp 随 Dify 一起可用)。
|
**不需要 install.md 的情况**:纯 HTTP API 服务(无需在本地安装)、通过关联应用附带安装的服务(如 dify-mcp 随 Dify 一起可用)。`external-integration` 明确禁止 `install.md`。
|
||||||
|
|
||||||
### usage.md(可选)
|
### usage.md(可选)
|
||||||
|
|
||||||
使用说明,描述安装后如何连接和使用此服务。
|
使用说明,描述安装后如何连接和使用此服务。
|
||||||
|
|
||||||
内容应包含:
|
内容应包含:
|
||||||
|
|
||||||
- 连接配置(transport、command、URL 等)
|
- 连接配置(transport、command、URL 等)
|
||||||
- 配置示例(JSON 格式,可直接使用)
|
- 配置示例(JSON 格式,可直接使用)
|
||||||
- 注意事项
|
- 注意事项
|
||||||
|
|
||||||
|
`external-integration` 不允许 `usage.md` 或其他附加文件;所有用户可见披露必须是经过 Schema 约束的 manifest 字段。
|
||||||
|
|
||||||
## 添加新条目
|
## 添加新条目
|
||||||
|
|
||||||
### 添加 Docker 应用
|
### 添加 Docker 应用
|
||||||
@@ -144,6 +172,7 @@ cat > entries/my-app/manifest.json << 'EOF'
|
|||||||
"iconLetter": "M",
|
"iconLetter": "M",
|
||||||
"platformSupport": ["macos", "windows", "linux"],
|
"platformSupport": ["macos", "windows", "linux"],
|
||||||
"fullDesc": "详细描述...",
|
"fullDesc": "详细描述...",
|
||||||
|
"shortDesc": "简短描述...",
|
||||||
"install": {
|
"install": {
|
||||||
"method": "docker",
|
"method": "docker",
|
||||||
"requirements": {
|
"requirements": {
|
||||||
@@ -231,6 +260,7 @@ cat > entries/my-api/manifest.json << 'EOF'
|
|||||||
"description": "一行功能描述",
|
"description": "一行功能描述",
|
||||||
"tags": ["tag1"],
|
"tags": ["tag1"],
|
||||||
"icon": "globe",
|
"icon": "globe",
|
||||||
|
"platformSupport": ["macos", "windows", "linux"],
|
||||||
"endpoint": "https://api.example.com/v1",
|
"endpoint": "https://api.example.com/v1",
|
||||||
"capabilities": ["capability_1"]
|
"capabilities": ["capability_1"]
|
||||||
}
|
}
|
||||||
@@ -239,17 +269,24 @@ EOF
|
|||||||
|
|
||||||
HTTP API 通常不需要 install.md,只需 usage.md 说明如何调用。
|
HTTP API 通常不需要 install.md,只需 usage.md 说明如何调用。
|
||||||
|
|
||||||
|
### 添加第三方外部集成
|
||||||
|
|
||||||
|
external integration 不是开放的自助条目类型。新增 ID、URL、扩展 ID、组件或制品审核事实需要先修改严格 Schema、校验器与 DesireCore 客户端契约,并经过安全与合规 review;未知 ID 会失败关闭。请以 [`entries/kimi-webbridge/manifest.json`](entries/kimi-webbridge/manifest.json) 为唯一当前示例。
|
||||||
|
|
||||||
## 修改现有条目
|
## 修改现有条目
|
||||||
|
|
||||||
1. 编辑 `entries/<id>/manifest.json` 中的字段
|
1. 编辑 `entries/<id>/manifest.json` 中的字段
|
||||||
2. 如有安装/使用流程变更,同步更新 `install.md` / `usage.md`
|
2. 如有安装/使用流程变更,同步更新 `install.md` / `usage.md`;external integration 不适用
|
||||||
3. **务必更新 `version` 字段**(客户端通过版本号判断是否有更新)
|
3. **务必更新 `version` 字段**(客户端通过版本号判断是否有更新)
|
||||||
4. 提交并创建 PR
|
4. 新增或删除条目时同步更新根 `manifest.json#stats`
|
||||||
|
5. 执行 `npm ci && npm test`
|
||||||
|
6. 提交并创建 PR
|
||||||
|
|
||||||
## 版本规范
|
## 版本规范
|
||||||
|
|
||||||
- `SCHEMA_VERSION`:数据格式版本,格式不兼容时递增主版本号
|
- `SCHEMA_VERSION`:数据格式版本,格式不兼容时递增主版本号
|
||||||
- `manifest.json#version`:仓库元数据版本
|
- `manifest.json#version`:仓库元数据版本,必须等于 `SCHEMA_VERSION`
|
||||||
|
- `manifest.json#dataVersion`:仓库数据版本,必须等于 `SCHEMA_VERSION`
|
||||||
- `entries/<id>/manifest.json#version`:条目自身版本
|
- `entries/<id>/manifest.json#version`:条目自身版本
|
||||||
|
|
||||||
**Schema 版本历史:**
|
**Schema 版本历史:**
|
||||||
@@ -258,7 +295,17 @@ HTTP API 通常不需要 install.md,只需 usage.md 说明如何调用。
|
|||||||
|------|------|
|
|------|------|
|
||||||
| 1.0.0 | 初始格式 — 单文件 JSON 数组 |
|
| 1.0.0 | 初始格式 — 单文件 JSON 数组 |
|
||||||
| 2.0.0 | 分散式目录 — apps/mcp/services 三目录,每个条目 `<id>/index.json` |
|
| 2.0.0 | 分散式目录 — apps/mcp/services 三目录,每个条目 `<id>/index.json` |
|
||||||
| 3.0.0 | **当前** — 统一 entries/ 目录,manifest.json + install.md + usage.md |
|
| 3.0.0 | 统一 entries/ 目录,manifest.json + install.md + usage.md |
|
||||||
|
| 4.0.0 | **当前** — Draft-07 严格判别 Schema、仓库校验和 listing-only external integration |
|
||||||
|
|
||||||
|
## 校验
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm ci
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
校验包含 JSON Schema、目录与 ID、全局唯一性、根版本、统计、来源注入字段、external 单文件布局、固定 Kimi ID、完整官方 URL、扩展 ID、组件/权限/准入集合、真实日历日期和不可变供应链审核记录。
|
||||||
|
|
||||||
## 同步机制
|
## 同步机制
|
||||||
|
|
||||||
@@ -269,16 +316,21 @@ DesireCore 客户端的同步流程:
|
|||||||
3. 有新 commit 时 `git pull` 并重建本地索引
|
3. 有新 commit 时 `git pull` 并重建本地索引
|
||||||
4. 离线时使用本地缓存或内置 fallback 数据
|
4. 离线时使用本地缓存或内置 fallback 数据
|
||||||
|
|
||||||
客户端读取 `entries/` 目录下所有 `manifest.json`,按 `type` 字段分类为应用、MCP 服务和 HTTP 服务展示在商店中。`install.md` 和 `usage.md` 供 AI Agent 执行安装和配置时使用。
|
客户端读取 `entries/` 目录下所有 `manifest.json`,按 `type` 字段分类为应用、MCP 服务、HTTP 服务和第三方外部集成展示在商店中。旧三类条目的 `install.md` 和 `usage.md` 可供 AI Agent 执行安装和配置时使用;external integration 只有结构化 manifest,且 listing-only 条目不会被派生为 Docker 应用、installed-entry、ready 连接或 Browser Provider。
|
||||||
|
|
||||||
|
主仓库的 `npm run sync-registry` 在打包前执行本 checkout 的 `scripts/validate-registry.mjs`。validator 缺失或失败时同步必须失败关闭,不能生成新的 `defaults/registry.zip`。
|
||||||
|
|
||||||
## 贡献指南
|
## 贡献指南
|
||||||
|
|
||||||
1. Fork 本仓库
|
1. Fork 本仓库
|
||||||
2. 在 `entries/` 下创建以 ID 命名的子目录
|
2. 在 `entries/` 下创建以 ID 命名的子目录
|
||||||
3. 按上述格式添加 `manifest.json`,按需添加 `install.md` 和 `usage.md`
|
3. 按上述格式添加 `manifest.json`,旧三类按需添加 `install.md` 和 `usage.md`
|
||||||
4. 更新根目录 `manifest.json` 中的 `stats` 统计
|
4. 更新根目录 `manifest.json` 中的 `stats` 统计
|
||||||
5. 提交 PR 并描述变更内容
|
5. 执行 `npm ci && npm test`
|
||||||
6. 等待审核合并
|
6. 提交 PR 并描述变更内容、来源和验证结果
|
||||||
|
7. 等待审核合并
|
||||||
|
|
||||||
|
external integration 需要额外安全、供应链、商标和隐私 review,不接受绕过严格 Schema 的未知 ID 或自由文本安装说明。
|
||||||
|
|
||||||
## 镜像
|
## 镜像
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
3.0.0
|
4.0.0
|
||||||
|
|||||||
175
entries/kimi-webbridge/manifest.json
Normal file
175
entries/kimi-webbridge/manifest.json
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
{
|
||||||
|
"id": "kimi-webbridge",
|
||||||
|
"name": "Kimi WebBridge",
|
||||||
|
"type": "external-integration",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Moonshot AI",
|
||||||
|
"description": "连接本机 Chrome 或 Edge 的第三方浏览器桥接集成目录入口",
|
||||||
|
"category": "tools",
|
||||||
|
"shortDesc": "第三方浏览器桥接集成,仅提供官方来源和合规披露",
|
||||||
|
"fullDesc": "Kimi WebBridge 由上游浏览器扩展和本地桥接服务组成,可让兼容的本地 Agent 使用现有 Chrome 或 Edge 会话执行导航、点击、填写、截图和内容提取。本条目由 DesireCore 独立收录,仅提供官方来源、权限和数据边界披露;当前不提供安装、连接或 DesireCore 原生执行能力。",
|
||||||
|
"tags": [
|
||||||
|
"browser",
|
||||||
|
"chrome",
|
||||||
|
"edge",
|
||||||
|
"webbridge"
|
||||||
|
],
|
||||||
|
"icon": "monitor",
|
||||||
|
"platformSupport": [
|
||||||
|
"macos",
|
||||||
|
"windows"
|
||||||
|
],
|
||||||
|
"requiredClientVersion": "10.0.132",
|
||||||
|
"stewardship": "pointer",
|
||||||
|
"availability": "listing-only",
|
||||||
|
"redistribution": "source-pointer-only",
|
||||||
|
"branding": {
|
||||||
|
"relationship": "independent-listing",
|
||||||
|
"nameUsage": "nominative",
|
||||||
|
"logoStatus": "not-used"
|
||||||
|
},
|
||||||
|
"listingMaintainer": {
|
||||||
|
"name": "DesireCore Registry",
|
||||||
|
"url": "https://github.com/desirecore/registry",
|
||||||
|
"verified": true
|
||||||
|
},
|
||||||
|
"upstreamMaintainer": {
|
||||||
|
"name": "Moonshot AI",
|
||||||
|
"url": "https://www.kimi.ai/",
|
||||||
|
"verified": true
|
||||||
|
},
|
||||||
|
"integration": {
|
||||||
|
"kind": "local-extension-bridge",
|
||||||
|
"adapterId": "kimi-webbridge",
|
||||||
|
"executionAvailability": "blocked",
|
||||||
|
"installMode": "upstream-guided"
|
||||||
|
},
|
||||||
|
"officialLinks": [
|
||||||
|
{
|
||||||
|
"kind": "product",
|
||||||
|
"label": "Kimi WebBridge 官方产品页",
|
||||||
|
"url": "https://www.kimi.ai/products/kimi-webbridge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "documentation",
|
||||||
|
"label": "Kimi WebBridge 官方帮助",
|
||||||
|
"url": "https://www.kimi.ai/help/kimi-webbridge/kimi-webbridge-introduction"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "chrome-web-store",
|
||||||
|
"label": "Chrome Web Store 官方条目",
|
||||||
|
"extensionId": "fldmhceldgbpfpkbgopacenieobmligc",
|
||||||
|
"url": "https://chromewebstore.google.com/detail/kimi-webbridge/fldmhceldgbpfpkbgopacenieobmligc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "edge-add-ons",
|
||||||
|
"label": "Microsoft Edge Add-ons 官方条目",
|
||||||
|
"extensionId": "bnlffdbcfnanfbknnlaflhlhkocccckg",
|
||||||
|
"url": "https://microsoftedge.microsoft.com/addons/detail/kimi-webbridge/bnlffdbcfnanfbknnlaflhlhkocccckg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "privacy",
|
||||||
|
"label": "Kimi 隐私政策",
|
||||||
|
"url": "https://www.kimi.ai/user/agreement/userPrivacy?version=v2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "terms",
|
||||||
|
"label": "Kimi 服务条款",
|
||||||
|
"url": "https://www.kimi.ai/user/agreement/modelUse?version=v2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"kind": "browser-extension",
|
||||||
|
"name": "Kimi WebBridge browser extension",
|
||||||
|
"description": "在 Chrome 或 Edge 中执行浏览器操作并与本地桥接服务通信的上游扩展",
|
||||||
|
"distribution": "official-upstream-only",
|
||||||
|
"browsers": [
|
||||||
|
"chrome",
|
||||||
|
"edge"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kind": "local-daemon",
|
||||||
|
"name": "Kimi WebBridge local service",
|
||||||
|
"description": "在用户设备上接收 Agent 指令并协调浏览器扩展的上游本地服务",
|
||||||
|
"distribution": "official-upstream-only",
|
||||||
|
"platforms": [
|
||||||
|
"macos",
|
||||||
|
"windows"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"permissions": {
|
||||||
|
"browserExtension": {
|
||||||
|
"apiPermissions": [
|
||||||
|
"tabs",
|
||||||
|
"activeTab",
|
||||||
|
"debugger",
|
||||||
|
"storage",
|
||||||
|
"alarms",
|
||||||
|
"tabGroups",
|
||||||
|
"windows"
|
||||||
|
],
|
||||||
|
"hostPermissions": [
|
||||||
|
"<all_urls>"
|
||||||
|
],
|
||||||
|
"riskLevel": "high"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profileRecommendation": "dedicated-browser-profile",
|
||||||
|
"dataBoundaries": {
|
||||||
|
"webBridgeLocalExecution": "上游声明 WebBridge 的桥接执行、登录状态和页面处理发生在用户设备本地;该声明仅描述 WebBridge 自身链路。",
|
||||||
|
"desireCoreModelContext": "当页面文本、截图或操作结果进入 DesireCore Agent 上下文时,内容仍可能按用户选择的模型与数据驻留配置发送给模型 Provider;这不属于上游本地执行声明的覆盖范围。",
|
||||||
|
"modelProviderDisclosureRequired": true
|
||||||
|
},
|
||||||
|
"admission": {
|
||||||
|
"status": "blocked",
|
||||||
|
"code": "native-integration-not-admitted",
|
||||||
|
"reason": "DesireCore 尚未获得足以把该第三方桥接提升为受治理浏览器 Provider 的稳定协议、授权和确定性治理信号,因此当前仅展示目录信息。",
|
||||||
|
"missingPrerequisites": [
|
||||||
|
"stable-auditable-protocol",
|
||||||
|
"integration-authorization",
|
||||||
|
"deterministic-command-receipts",
|
||||||
|
"user-interaction-pause-signal"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"compliance": {
|
||||||
|
"reviewedAt": "2026-08-30",
|
||||||
|
"reviewedBy": "DesireCore Registry maintainers",
|
||||||
|
"upstreamEndorsed": false,
|
||||||
|
"relationshipDisclosure": "independent-listing-not-endorsed",
|
||||||
|
"artifactReviews": [
|
||||||
|
{
|
||||||
|
"component": "local-daemon",
|
||||||
|
"platform": "macos",
|
||||||
|
"architecture": "arm64",
|
||||||
|
"version": "1.11.6",
|
||||||
|
"url": "https://cdn.kimi.com/webbridge/v1.11.6/releases/kimi-webbridge-darwin-arm64",
|
||||||
|
"sha256": "46fe401eaa5669c6d9915b4d7ea734cd3fb968c877362644212234241d0c9930",
|
||||||
|
"reviewedAt": "2026-08-30",
|
||||||
|
"reviewedBy": "DesireCore Registry maintainers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "local-daemon",
|
||||||
|
"platform": "macos",
|
||||||
|
"architecture": "x64",
|
||||||
|
"version": "1.11.6",
|
||||||
|
"url": "https://cdn.kimi.com/webbridge/v1.11.6/releases/kimi-webbridge-darwin-amd64",
|
||||||
|
"sha256": "edd1b1265445410ef5e8684788955c7283f9c47d9121f53159fa7e5a4641ec54",
|
||||||
|
"reviewedAt": "2026-08-30",
|
||||||
|
"reviewedBy": "DesireCore Registry maintainers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"component": "local-daemon",
|
||||||
|
"platform": "windows",
|
||||||
|
"architecture": "x64",
|
||||||
|
"version": "1.11.6",
|
||||||
|
"url": "https://cdn.kimi.com/webbridge/v1.11.6/releases/kimi-webbridge-windows-amd64.exe",
|
||||||
|
"sha256": "e085991e0127c3872d8cbf2ec11078a1efba3c534f6229e42af0dde45ce90520",
|
||||||
|
"reviewedAt": "2026-08-30",
|
||||||
|
"reviewedBy": "DesireCore Registry maintainers"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"id": "desirecore-registry-manifest",
|
"id": "desirecore-registry-manifest",
|
||||||
"version": "3.0.0",
|
"version": "4.0.0",
|
||||||
"name": "DesireCore Registry",
|
"name": "DesireCore Registry",
|
||||||
"description": "DesireCore 官方注册表 — 应用、MCP 服务和 HTTP API 的统一仓库",
|
"description": "DesireCore 官方注册表 — Docker 应用、MCP 服务、HTTP API 和第三方外部集成的统一目录",
|
||||||
"maintainer": "DesireCore Team",
|
"maintainer": "DesireCore Team",
|
||||||
"repository": "https://github.com/desirecore/registry",
|
"repository": "https://github.com/desirecore/registry",
|
||||||
"lastUpdated": "2026-07-29",
|
"lastUpdated": "2026-08-30",
|
||||||
"stats": {
|
"stats": {
|
||||||
"totalEntries": 21,
|
"totalEntries": 22,
|
||||||
"dockerApps": 8,
|
"dockerApps": 8,
|
||||||
"mcpServices": 8,
|
"mcpServices": 8,
|
||||||
"httpApis": 5
|
"httpApis": 5,
|
||||||
|
"externalIntegrations": 1
|
||||||
},
|
},
|
||||||
"dataVersion": "3.0.0"
|
"dataVersion": "4.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
76
package-lock.json
generated
Normal file
76
package-lock.json
generated
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
{
|
||||||
|
"name": "@desirecore/registry",
|
||||||
|
"version": "4.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "@desirecore/registry",
|
||||||
|
"version": "4.0.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"ajv": "8.20.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ajv": {
|
||||||
|
"version": "8.20.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||||
|
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-deep-equal": "^3.1.3",
|
||||||
|
"fast-uri": "^3.0.1",
|
||||||
|
"json-schema-traverse": "^1.0.0",
|
||||||
|
"require-from-string": "^2.0.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fast-deep-equal": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/fast-uri": {
|
||||||
|
"version": "3.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.6.tgz",
|
||||||
|
"integrity": "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/fastify"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fastify"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
|
"node_modules/json-schema-traverse": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/require-from-string": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
package.json
Normal file
17
package.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "@desirecore/registry",
|
||||||
|
"version": "4.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Validated catalog data for the DesireCore Registry",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"validate": "node scripts/validate-registry.mjs",
|
||||||
|
"test": "node --test scripts/validate-registry.test.mjs && npm run validate"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"ajv": "8.20.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
}
|
||||||
|
}
|
||||||
552
schemas/registry-entry.schema.json
Normal file
552
schemas/registry-entry.schema.json
Normal file
@@ -0,0 +1,552 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
|
"$id": "https://github.com/desirecore/registry/schemas/registry-entry.schema.json",
|
||||||
|
"title": "DesireCore Registry Entry",
|
||||||
|
"description": "A strictly discriminated DesireCore Registry catalog entry. External integrations are discovery-only pointers and cannot carry installation or connection semantics.",
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "#/definitions/dockerApp" },
|
||||||
|
{ "$ref": "#/definitions/mcp" },
|
||||||
|
{ "$ref": "#/definitions/httpApi" },
|
||||||
|
{ "$ref": "#/definitions/externalIntegration" }
|
||||||
|
],
|
||||||
|
"definitions": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
||||||
|
"description": "Stable entry identifier; must equal the containing directory name."
|
||||||
|
},
|
||||||
|
"nonEmptyString": {
|
||||||
|
"type": "string",
|
||||||
|
"minLength": 1
|
||||||
|
},
|
||||||
|
"version": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+(?:\\.[0-9]+){1,3}(?:[-+][0-9A-Za-z.-]+)?$",
|
||||||
|
"description": "Entry or client version in dotted numeric form."
|
||||||
|
},
|
||||||
|
"httpsUrl": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^https://[^\\s]+$",
|
||||||
|
"description": "An HTTPS URL without embedded whitespace."
|
||||||
|
},
|
||||||
|
"platformSupport": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["macos", "windows", "linux"]
|
||||||
|
},
|
||||||
|
"description": "Desktop platforms on which the entry is relevant."
|
||||||
|
},
|
||||||
|
"tags": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"description": "Human-readable discovery and search tags."
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[a-z0-9]+(?:_[a-z0-9]+)*$"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"environment": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": { "type": "string" }
|
||||||
|
},
|
||||||
|
"mcpInstall": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["method", "command", "args"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"method": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"packageName": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"command": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"args": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" }
|
||||||
|
},
|
||||||
|
"postInstall": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/nonEmptyString" }
|
||||||
|
},
|
||||||
|
"env": { "$ref": "#/definitions/environment" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"connection": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["transport"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"transport": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["stdio", "streamable-http", "sse"]
|
||||||
|
},
|
||||||
|
"command": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"args": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" }
|
||||||
|
},
|
||||||
|
"url": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"env": { "$ref": "#/definitions/environment" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dockerApp": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id", "name", "type", "version", "author", "description", "tags", "icon",
|
||||||
|
"iconLetter", "platformSupport", "category", "fullDesc", "shortDesc", "install"
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": { "$ref": "#/definitions/id" },
|
||||||
|
"name": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"type": { "const": "docker-app" },
|
||||||
|
"version": { "$ref": "#/definitions/version" },
|
||||||
|
"author": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"description": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"tags": { "$ref": "#/definitions/tags" },
|
||||||
|
"icon": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"iconLetter": { "type": "string", "minLength": 1, "maxLength": 2 },
|
||||||
|
"platformSupport": { "$ref": "#/definitions/platformSupport" },
|
||||||
|
"category": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["ai-platform", "chat", "workflow", "rag", "tools"]
|
||||||
|
},
|
||||||
|
"fullDesc": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"shortDesc": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"stars": { "type": "integer", "minimum": 0 },
|
||||||
|
"githubUrl": { "$ref": "#/definitions/httpsUrl" },
|
||||||
|
"install": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["method", "requirements", "configNeeded"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"method": { "type": "string", "enum": ["docker", "docker-compose"] },
|
||||||
|
"requirements": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["docker", "minMemory", "minDisk", "ports"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"docker": { "const": true },
|
||||||
|
"minMemory": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"minDisk": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"ports": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": { "type": "integer", "minimum": 1, "maximum": 65535 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configNeeded": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "$ref": "#/definitions/nonEmptyString" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exposes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["type", "port", "path", "name"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"type": { "const": "http-api" },
|
||||||
|
"port": { "type": "integer", "minimum": 1, "maximum": 65535 },
|
||||||
|
"path": { "type": "string", "pattern": "^/" },
|
||||||
|
"name": { "$ref": "#/definitions/nonEmptyString" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mcp": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id", "name", "type", "version", "author", "description", "tags", "icon",
|
||||||
|
"platformSupport", "capabilities", "connection"
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": { "$ref": "#/definitions/id" },
|
||||||
|
"name": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"type": { "const": "mcp" },
|
||||||
|
"version": { "$ref": "#/definitions/version" },
|
||||||
|
"author": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"description": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"tags": { "$ref": "#/definitions/tags" },
|
||||||
|
"icon": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"platformSupport": { "$ref": "#/definitions/platformSupport" },
|
||||||
|
"capabilities": { "$ref": "#/definitions/capabilities" },
|
||||||
|
"toolCount": { "type": "integer", "minimum": 0 },
|
||||||
|
"install": { "$ref": "#/definitions/mcpInstall" },
|
||||||
|
"connection": { "$ref": "#/definitions/connection" },
|
||||||
|
"sourceAppId": { "$ref": "#/definitions/id" },
|
||||||
|
"sourceAppName": { "$ref": "#/definitions/nonEmptyString" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"httpApi": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id", "name", "type", "version", "author", "description", "tags", "icon",
|
||||||
|
"platformSupport", "endpoint", "capabilities"
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": { "$ref": "#/definitions/id" },
|
||||||
|
"name": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"type": { "const": "http-api" },
|
||||||
|
"version": { "$ref": "#/definitions/version" },
|
||||||
|
"author": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"description": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"tags": { "$ref": "#/definitions/tags" },
|
||||||
|
"icon": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"platformSupport": { "$ref": "#/definitions/platformSupport" },
|
||||||
|
"endpoint": { "type": "string", "pattern": "^https?://[^\\s]+$" },
|
||||||
|
"capabilities": { "$ref": "#/definitions/capabilities" },
|
||||||
|
"sourceAppId": { "$ref": "#/definitions/id" },
|
||||||
|
"sourceAppName": { "$ref": "#/definitions/nonEmptyString" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"maintainer": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["name", "url", "verified"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"allOf": [{ "$ref": "#/definitions/nonEmptyString" }],
|
||||||
|
"description": "Human-readable maintainer identity."
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"allOf": [{ "$ref": "#/definitions/httpsUrl" }],
|
||||||
|
"description": "Public source used to verify the maintainer identity."
|
||||||
|
},
|
||||||
|
"verified": {
|
||||||
|
"const": true,
|
||||||
|
"description": "The Registry maintainer verified this identity against the public source."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"officialLink": {
|
||||||
|
"description": "A Kimi WebBridge upstream URL pinned by kind, hostname, path, and extension ID where applicable.",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["kind", "label", "url"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "product" },
|
||||||
|
"label": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"url": { "const": "https://www.kimi.ai/products/kimi-webbridge" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["kind", "label", "url"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "documentation" },
|
||||||
|
"label": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"url": { "const": "https://www.kimi.ai/help/kimi-webbridge/kimi-webbridge-introduction" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["kind", "label", "extensionId", "url"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "chrome-web-store" },
|
||||||
|
"label": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"extensionId": {
|
||||||
|
"const": "fldmhceldgbpfpkbgopacenieobmligc",
|
||||||
|
"description": "The audited Chrome Web Store extension identifier."
|
||||||
|
},
|
||||||
|
"url": { "const": "https://chromewebstore.google.com/detail/kimi-webbridge/fldmhceldgbpfpkbgopacenieobmligc" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["kind", "label", "extensionId", "url"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "edge-add-ons" },
|
||||||
|
"label": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"extensionId": {
|
||||||
|
"const": "bnlffdbcfnanfbknnlaflhlhkocccckg",
|
||||||
|
"description": "The audited Microsoft Edge Add-ons extension identifier."
|
||||||
|
},
|
||||||
|
"url": { "const": "https://microsoftedge.microsoft.com/addons/detail/kimi-webbridge/bnlffdbcfnanfbknnlaflhlhkocccckg" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["kind", "label", "url"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "privacy" },
|
||||||
|
"label": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"url": { "const": "https://www.kimi.ai/user/agreement/userPrivacy?version=v2" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"required": ["kind", "label", "url"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "terms" },
|
||||||
|
"label": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"url": { "const": "https://www.kimi.ai/user/agreement/modelUse?version=v2" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserExtensionComponent": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["kind", "name", "description", "distribution", "browsers"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "browser-extension" },
|
||||||
|
"name": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"description": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"distribution": {
|
||||||
|
"const": "official-upstream-only",
|
||||||
|
"description": "DesireCore does not mirror or repackage this third-party component."
|
||||||
|
},
|
||||||
|
"browsers": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 2,
|
||||||
|
"maxItems": 2,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": { "type": "string", "enum": ["chrome", "edge"] },
|
||||||
|
"allOf": [{ "contains": { "const": "chrome" } }, { "contains": { "const": "edge" } }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"localDaemonComponent": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["kind", "name", "description", "distribution", "platforms"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "local-daemon" },
|
||||||
|
"name": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"description": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"distribution": {
|
||||||
|
"const": "official-upstream-only",
|
||||||
|
"description": "DesireCore does not mirror or repackage this third-party component."
|
||||||
|
},
|
||||||
|
"platforms": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 2,
|
||||||
|
"maxItems": 3,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": { "type": "string", "enum": ["macos", "windows", "linux"] },
|
||||||
|
"allOf": [{ "contains": { "const": "macos" } }, { "contains": { "const": "windows" } }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externalComponent": {
|
||||||
|
"description": "One strictly discriminated Kimi WebBridge physical component.",
|
||||||
|
"oneOf": [
|
||||||
|
{ "$ref": "#/definitions/browserExtensionComponent" },
|
||||||
|
{ "$ref": "#/definitions/localDaemonComponent" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"artifactReview": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["component", "platform", "architecture", "version", "url", "sha256", "reviewedAt", "reviewedBy"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"component": { "const": "local-daemon" },
|
||||||
|
"platform": { "type": "string", "enum": ["macos", "windows", "linux"] },
|
||||||
|
"architecture": { "type": "string", "enum": ["x64", "arm64"] },
|
||||||
|
"version": { "$ref": "#/definitions/version" },
|
||||||
|
"url": { "$ref": "#/definitions/httpsUrl" },
|
||||||
|
"sha256": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
|
||||||
|
"reviewedAt": { "type": "string", "format": "date" },
|
||||||
|
"reviewedBy": { "$ref": "#/definitions/nonEmptyString" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externalIntegration": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"id", "name", "type", "version", "author", "description", "category", "shortDesc",
|
||||||
|
"fullDesc", "tags", "icon", "platformSupport", "requiredClientVersion", "stewardship",
|
||||||
|
"availability", "redistribution", "branding", "listingMaintainer", "upstreamMaintainer",
|
||||||
|
"integration", "officialLinks", "components", "permissions", "profileRecommendation",
|
||||||
|
"dataBoundaries", "admission", "compliance"
|
||||||
|
],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"const": "kimi-webbridge",
|
||||||
|
"description": "The only external-integration ID admitted by Registry Schema v4."
|
||||||
|
},
|
||||||
|
"name": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"type": { "const": "external-integration" },
|
||||||
|
"version": { "$ref": "#/definitions/version" },
|
||||||
|
"author": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"description": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"category": { "const": "tools" },
|
||||||
|
"shortDesc": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"fullDesc": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"tags": { "$ref": "#/definitions/tags" },
|
||||||
|
"icon": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["monitor", "puzzle", "globe"],
|
||||||
|
"description": "A neutral DesireCore icon name; upstream logos are not permitted in this listing type."
|
||||||
|
},
|
||||||
|
"platformSupport": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 2,
|
||||||
|
"maxItems": 2,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": { "type": "string", "enum": ["macos", "windows"] },
|
||||||
|
"allOf": [{ "contains": { "const": "macos" } }, { "contains": { "const": "windows" } }],
|
||||||
|
"description": "The reviewed listing must support both macOS and Windows."
|
||||||
|
},
|
||||||
|
"requiredClientVersion": {
|
||||||
|
"allOf": [{ "$ref": "#/definitions/version" }],
|
||||||
|
"description": "Minimum DesireCore version that understands this strict listing-only entry."
|
||||||
|
},
|
||||||
|
"stewardship": { "const": "pointer" },
|
||||||
|
"availability": { "const": "listing-only" },
|
||||||
|
"redistribution": { "const": "source-pointer-only" },
|
||||||
|
"branding": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["relationship", "nameUsage", "logoStatus"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"relationship": { "const": "independent-listing" },
|
||||||
|
"nameUsage": { "const": "nominative" },
|
||||||
|
"logoStatus": { "const": "not-used" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"listingMaintainer": { "$ref": "#/definitions/maintainer" },
|
||||||
|
"upstreamMaintainer": { "$ref": "#/definitions/maintainer" },
|
||||||
|
"integration": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["kind", "adapterId", "executionAvailability", "installMode"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"kind": { "const": "local-extension-bridge" },
|
||||||
|
"adapterId": { "$ref": "#/definitions/id" },
|
||||||
|
"executionAvailability": { "const": "blocked" },
|
||||||
|
"installMode": { "const": "upstream-guided" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"officialLinks": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 6,
|
||||||
|
"maxItems": 6,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": { "$ref": "#/definitions/officialLink" },
|
||||||
|
"description": "One reviewed official source for each required link kind."
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 2,
|
||||||
|
"maxItems": 2,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": { "$ref": "#/definitions/externalComponent" },
|
||||||
|
"description": "The upstream browser extension and local daemon, neither redistributed by DesireCore."
|
||||||
|
},
|
||||||
|
"permissions": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["browserExtension"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"browserExtension": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["apiPermissions", "hostPermissions", "riskLevel"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"apiPermissions": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 7,
|
||||||
|
"maxItems": 7,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["tabs", "activeTab", "debugger", "storage", "alarms", "tabGroups", "windows"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hostPermissions": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"maxItems": 1,
|
||||||
|
"items": { "const": "<all_urls>" }
|
||||||
|
},
|
||||||
|
"riskLevel": { "const": "high" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profileRecommendation": { "const": "dedicated-browser-profile" },
|
||||||
|
"dataBoundaries": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["webBridgeLocalExecution", "desireCoreModelContext", "modelProviderDisclosureRequired"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"webBridgeLocalExecution": {
|
||||||
|
"allOf": [{ "$ref": "#/definitions/nonEmptyString" }],
|
||||||
|
"description": "A scoped statement of the upstream claim about the local WebBridge execution path."
|
||||||
|
},
|
||||||
|
"desireCoreModelContext": {
|
||||||
|
"allOf": [{ "$ref": "#/definitions/nonEmptyString" }],
|
||||||
|
"description": "A separate disclosure that content entering DesireCore context may be sent to the selected model provider."
|
||||||
|
},
|
||||||
|
"modelProviderDisclosureRequired": { "const": true }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"admission": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["status", "code", "reason", "missingPrerequisites"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"status": { "const": "blocked" },
|
||||||
|
"code": { "const": "native-integration-not-admitted" },
|
||||||
|
"reason": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"missingPrerequisites": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 4,
|
||||||
|
"maxItems": 4,
|
||||||
|
"uniqueItems": true,
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"stable-auditable-protocol",
|
||||||
|
"integration-authorization",
|
||||||
|
"deterministic-command-receipts",
|
||||||
|
"user-interaction-pause-signal"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compliance": {
|
||||||
|
"type": "object",
|
||||||
|
"required": ["reviewedAt", "reviewedBy", "upstreamEndorsed", "relationshipDisclosure", "artifactReviews"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"reviewedAt": { "type": "string", "format": "date" },
|
||||||
|
"reviewedBy": { "$ref": "#/definitions/nonEmptyString" },
|
||||||
|
"upstreamEndorsed": { "const": false },
|
||||||
|
"relationshipDisclosure": { "const": "independent-listing-not-endorsed" },
|
||||||
|
"artifactReviews": {
|
||||||
|
"type": "array",
|
||||||
|
"minItems": 1,
|
||||||
|
"items": { "$ref": "#/definitions/artifactReview" },
|
||||||
|
"description": "Immutable upstream artifact audit facts; these records do not admit installation or redistribution."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
381
scripts/validate-registry.mjs
Normal file
381
scripts/validate-registry.mjs
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
import { lstat, readFile, readdir } from 'node:fs/promises'
|
||||||
|
import { dirname, join, relative, resolve } from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import Ajv from 'ajv'
|
||||||
|
|
||||||
|
const scriptDirectory = dirname(fileURLToPath(import.meta.url))
|
||||||
|
const rootArgumentIndex = process.argv.indexOf('--root')
|
||||||
|
if (rootArgumentIndex >= 0 && !process.argv[rootArgumentIndex + 1]) {
|
||||||
|
throw new Error('--root requires a directory path')
|
||||||
|
}
|
||||||
|
const repositoryRoot = rootArgumentIndex >= 0
|
||||||
|
? resolve(process.argv[rootArgumentIndex + 1])
|
||||||
|
: resolve(scriptDirectory, '..')
|
||||||
|
const entriesRoot = join(repositoryRoot, 'entries')
|
||||||
|
const errors = []
|
||||||
|
|
||||||
|
const addError = (message) => errors.push(message)
|
||||||
|
|
||||||
|
const readText = async (path) => {
|
||||||
|
try {
|
||||||
|
return await readFile(path, 'utf8')
|
||||||
|
} catch (error) {
|
||||||
|
addError(`${relative(repositoryRoot, path)}: unable to read (${error.message})`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const readJson = async (path) => {
|
||||||
|
const text = await readText(path)
|
||||||
|
if (text === null) return null
|
||||||
|
try {
|
||||||
|
return JSON.parse(text)
|
||||||
|
} catch (error) {
|
||||||
|
addError(`${relative(repositoryRoot, path)}: invalid JSON (${error.message})`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatAjvErrors = (validationErrors = []) =>
|
||||||
|
validationErrors
|
||||||
|
.map((error) => `${error.instancePath || '/'} ${error.message}`)
|
||||||
|
.join('; ')
|
||||||
|
|
||||||
|
const sorted = (values) => [...values].sort()
|
||||||
|
const sameSet = (actual, expected) =>
|
||||||
|
JSON.stringify(sorted(actual)) === JSON.stringify(sorted(expected))
|
||||||
|
|
||||||
|
const containsKey = (value, prohibitedKey) => {
|
||||||
|
if (Array.isArray(value)) return value.some((item) => containsKey(item, prohibitedKey))
|
||||||
|
if (!value || typeof value !== 'object') return false
|
||||||
|
if (Object.hasOwn(value, prohibitedKey)) return true
|
||||||
|
return Object.values(value).some((item) => containsKey(item, prohibitedKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
const assertHttpsUrl = (rawUrl, context) => {
|
||||||
|
try {
|
||||||
|
const url = new URL(rawUrl)
|
||||||
|
if (url.protocol !== 'https:') addError(`${context}: URL must use HTTPS`)
|
||||||
|
if (url.username || url.password) addError(`${context}: URL must not contain credentials`)
|
||||||
|
return url
|
||||||
|
} catch {
|
||||||
|
addError(`${context}: invalid URL`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const requiredOfficialLinkKinds = [
|
||||||
|
'product',
|
||||||
|
'documentation',
|
||||||
|
'chrome-web-store',
|
||||||
|
'edge-add-ons',
|
||||||
|
'privacy',
|
||||||
|
'terms',
|
||||||
|
]
|
||||||
|
|
||||||
|
const admittedExternalIntegrationIds = new Set(['kimi-webbridge'])
|
||||||
|
|
||||||
|
const kimiOfficialLinkPolicy = {
|
||||||
|
product: {
|
||||||
|
url: 'https://www.kimi.ai/products/kimi-webbridge',
|
||||||
|
},
|
||||||
|
documentation: {
|
||||||
|
url: 'https://www.kimi.ai/help/kimi-webbridge/kimi-webbridge-introduction',
|
||||||
|
},
|
||||||
|
'chrome-web-store': {
|
||||||
|
url: 'https://chromewebstore.google.com/detail/kimi-webbridge/fldmhceldgbpfpkbgopacenieobmligc',
|
||||||
|
extensionId: 'fldmhceldgbpfpkbgopacenieobmligc',
|
||||||
|
},
|
||||||
|
'edge-add-ons': {
|
||||||
|
url: 'https://microsoftedge.microsoft.com/addons/detail/kimi-webbridge/bnlffdbcfnanfbknnlaflhlhkocccckg',
|
||||||
|
extensionId: 'bnlffdbcfnanfbknnlaflhlhkocccckg',
|
||||||
|
},
|
||||||
|
privacy: {
|
||||||
|
url: 'https://www.kimi.ai/user/agreement/userPrivacy?version=v2',
|
||||||
|
},
|
||||||
|
terms: {
|
||||||
|
url: 'https://www.kimi.ai/user/agreement/modelUse?version=v2',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const requiredKimiPermissions = [
|
||||||
|
'tabs',
|
||||||
|
'activeTab',
|
||||||
|
'debugger',
|
||||||
|
'storage',
|
||||||
|
'alarms',
|
||||||
|
'tabGroups',
|
||||||
|
'windows',
|
||||||
|
]
|
||||||
|
|
||||||
|
const requiredAdmissionPrerequisites = [
|
||||||
|
'stable-auditable-protocol',
|
||||||
|
'integration-authorization',
|
||||||
|
'deterministic-command-receipts',
|
||||||
|
'user-interaction-pause-signal',
|
||||||
|
]
|
||||||
|
|
||||||
|
const isValidCalendarDate = (value) => {
|
||||||
|
if (typeof value !== 'string') return false
|
||||||
|
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value)
|
||||||
|
if (!match) return false
|
||||||
|
const year = Number(match[1])
|
||||||
|
const month = Number(match[2])
|
||||||
|
const day = Number(match[3])
|
||||||
|
const date = new Date(Date.UTC(year, month - 1, day))
|
||||||
|
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day
|
||||||
|
}
|
||||||
|
|
||||||
|
const describeFilesystemEntry = (entry) => {
|
||||||
|
if (entry.isSymbolicLink()) return 'symbolic link'
|
||||||
|
if (entry.isDirectory()) return 'directory'
|
||||||
|
if (entry.isFile()) return 'file'
|
||||||
|
if (entry.isBlockDevice()) return 'block device'
|
||||||
|
if (entry.isCharacterDevice()) return 'character device'
|
||||||
|
if (entry.isFIFO()) return 'FIFO'
|
||||||
|
if (entry.isSocket()) return 'socket'
|
||||||
|
return 'unsupported filesystem entry'
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateExternalDirectoryLayout = async (entryDirectory, manifestId) => {
|
||||||
|
const prefix = `entries/${manifestId}`
|
||||||
|
const directoryEntries = await readdir(entryDirectory, { withFileTypes: true })
|
||||||
|
for (const entry of directoryEntries) {
|
||||||
|
if (entry.name !== 'manifest.json') {
|
||||||
|
addError(`${prefix}: external-integration allows only manifest.json; found ${describeFilesystemEntry(entry)} ${entry.name}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!entry.isFile() || entry.isSymbolicLink()) {
|
||||||
|
addError(`${prefix}/manifest.json: must be a regular file, not a ${describeFilesystemEntry(entry)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (directoryEntries.length !== 1 || directoryEntries[0]?.name !== 'manifest.json') {
|
||||||
|
addError(`${prefix}: external-integration directory must contain exactly one manifest.json regular file`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateExternalIntegration = async (entryDirectory, manifest) => {
|
||||||
|
const prefix = `entries/${manifest.id}`
|
||||||
|
await validateExternalDirectoryLayout(entryDirectory, manifest.id)
|
||||||
|
if (!admittedExternalIntegrationIds.has(manifest.id)) {
|
||||||
|
addError(`${prefix}/manifest.json: unknown external-integration id ${manifest.id}`)
|
||||||
|
}
|
||||||
|
const forbiddenFields = [
|
||||||
|
'install',
|
||||||
|
'entrypoints',
|
||||||
|
'exposes',
|
||||||
|
'endpoint',
|
||||||
|
'connection',
|
||||||
|
'auth',
|
||||||
|
'operations',
|
||||||
|
'source',
|
||||||
|
]
|
||||||
|
for (const field of forbiddenFields) {
|
||||||
|
if (Object.hasOwn(manifest, field)) addError(`${prefix}/manifest.json: forbidden field "${field}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkKinds = manifest.officialLinks?.map((link) => link.kind) ?? []
|
||||||
|
if (!sameSet(linkKinds, requiredOfficialLinkKinds)) {
|
||||||
|
addError(`${prefix}/manifest.json: officialLinks must contain each required kind exactly once`)
|
||||||
|
}
|
||||||
|
for (const link of manifest.officialLinks ?? []) {
|
||||||
|
const url = assertHttpsUrl(link.url, `${prefix}/manifest.json officialLinks.${link.kind}`)
|
||||||
|
const policy = kimiOfficialLinkPolicy[link.kind]
|
||||||
|
if (!policy || link.url !== policy.url) {
|
||||||
|
addError(`${prefix}/manifest.json officialLinks.${link.kind}: URL must exactly equal ${policy?.url ?? 'an admitted URL'}`)
|
||||||
|
}
|
||||||
|
if (policy?.extensionId) {
|
||||||
|
if (link.extensionId !== policy.extensionId) {
|
||||||
|
addError(`${prefix}/manifest.json officialLinks.${link.kind}: extensionId must equal ${policy.extensionId}`)
|
||||||
|
}
|
||||||
|
if (url && url.pathname.split('/').filter(Boolean).at(-1) !== link.extensionId) {
|
||||||
|
addError(`${prefix}/manifest.json officialLinks.${link.kind}: URL path must end with extensionId`)
|
||||||
|
}
|
||||||
|
} else if (Object.hasOwn(link, 'extensionId')) {
|
||||||
|
addError(`${prefix}/manifest.json officialLinks.${link.kind}: extensionId is allowed only for browser stores`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifest.id === 'kimi-webbridge') {
|
||||||
|
for (const [field, expectedHost] of [
|
||||||
|
['listingMaintainer', 'github.com'],
|
||||||
|
['upstreamMaintainer', 'www.kimi.ai'],
|
||||||
|
]) {
|
||||||
|
const url = assertHttpsUrl(manifest[field]?.url, `${prefix}/manifest.json ${field}.url`)
|
||||||
|
if (url && url.hostname !== expectedHost) {
|
||||||
|
addError(`${prefix}/manifest.json ${field}.url: hostname must be ${expectedHost}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifest.listingMaintainer?.url !== 'https://github.com/desirecore/registry' || manifest.listingMaintainer?.verified !== true) {
|
||||||
|
addError(`${prefix}/manifest.json: listingMaintainer must be the verified DesireCore Registry URL`)
|
||||||
|
}
|
||||||
|
if (manifest.upstreamMaintainer?.url !== 'https://www.kimi.ai/' || manifest.upstreamMaintainer?.verified !== true) {
|
||||||
|
addError(`${prefix}/manifest.json: upstreamMaintainer must be the verified Kimi URL`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const componentKinds = manifest.components?.map((component) => component.kind) ?? []
|
||||||
|
if (!sameSet(componentKinds, ['browser-extension', 'local-daemon'])) {
|
||||||
|
addError(`${prefix}/manifest.json: components must contain browser-extension and local-daemon exactly once`)
|
||||||
|
}
|
||||||
|
for (const component of manifest.components ?? []) {
|
||||||
|
if (component.kind === 'browser-extension') {
|
||||||
|
if (!sameSet(component.browsers ?? [], ['chrome', 'edge']) || Object.hasOwn(component, 'platforms')) {
|
||||||
|
addError(`${prefix}/manifest.json: browser-extension must declare only Chrome and Edge browsers`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (component.kind === 'local-daemon') {
|
||||||
|
if (!Array.isArray(component.platforms) ||
|
||||||
|
!component.platforms.includes('windows') ||
|
||||||
|
!component.platforms.includes('macos') ||
|
||||||
|
Object.hasOwn(component, 'browsers')) {
|
||||||
|
addError(`${prefix}/manifest.json: local-daemon must declare at least Windows and macOS and no browsers`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiPermissions = manifest.permissions?.browserExtension?.apiPermissions ?? []
|
||||||
|
if (!sameSet(apiPermissions, requiredKimiPermissions)) {
|
||||||
|
addError(`${prefix}/manifest.json: browser extension API permissions must match the audited Kimi permission set`)
|
||||||
|
}
|
||||||
|
const hostPermissions = manifest.permissions?.browserExtension?.hostPermissions ?? []
|
||||||
|
if (!sameSet(hostPermissions, ['<all_urls>'])) {
|
||||||
|
addError(`${prefix}/manifest.json: browser extension must disclose <all_urls>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sameSet(manifest.admission?.missingPrerequisites ?? [], requiredAdmissionPrerequisites)) {
|
||||||
|
addError(`${prefix}/manifest.json: blocked admission prerequisites are incomplete`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const artifactIdentities = new Set()
|
||||||
|
if (!isValidCalendarDate(manifest.compliance?.reviewedAt)) {
|
||||||
|
addError(`${prefix}/manifest.json compliance.reviewedAt: expected a real YYYY-MM-DD calendar date`)
|
||||||
|
}
|
||||||
|
for (const artifact of manifest.compliance?.artifactReviews ?? []) {
|
||||||
|
const artifactContext = `${prefix}/manifest.json artifact ${artifact.platform}/${artifact.architecture}`
|
||||||
|
const url = assertHttpsUrl(artifact.url, artifactContext)
|
||||||
|
if (url && url.hostname !== 'cdn.kimi.com') {
|
||||||
|
addError(`${artifactContext}: hostname must be cdn.kimi.com`)
|
||||||
|
}
|
||||||
|
if (url && !url.pathname.includes(`/v${artifact.version}/releases/`)) {
|
||||||
|
addError(`${artifactContext}: URL must pin the reviewed version in its release path`)
|
||||||
|
}
|
||||||
|
if (url && (url.search || url.hash)) {
|
||||||
|
addError(`${artifactContext}: URL must not contain query or fragment components`)
|
||||||
|
}
|
||||||
|
if (!isValidCalendarDate(artifact.reviewedAt)) {
|
||||||
|
addError(`${artifactContext} reviewedAt: expected a real YYYY-MM-DD calendar date`)
|
||||||
|
}
|
||||||
|
const identity = `${artifact.platform}/${artifact.architecture}`
|
||||||
|
if (artifactIdentities.has(identity)) addError(`${artifactContext}: duplicate platform/architecture review`)
|
||||||
|
artifactIdentities.add(identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaVersion = (await readText(join(repositoryRoot, 'SCHEMA_VERSION')))?.trim()
|
||||||
|
const rootManifest = await readJson(join(repositoryRoot, 'manifest.json'))
|
||||||
|
const entrySchema = await readJson(join(repositoryRoot, 'schemas', 'registry-entry.schema.json'))
|
||||||
|
|
||||||
|
if (!schemaVersion || !/^\d+\.\d+\.\d+$/.test(schemaVersion)) {
|
||||||
|
addError('SCHEMA_VERSION: expected a semantic version')
|
||||||
|
}
|
||||||
|
if (rootManifest) {
|
||||||
|
if (rootManifest.version !== schemaVersion) addError('manifest.json#version must equal SCHEMA_VERSION')
|
||||||
|
if (rootManifest.dataVersion !== schemaVersion) addError('manifest.json#dataVersion must equal SCHEMA_VERSION')
|
||||||
|
}
|
||||||
|
|
||||||
|
let validateEntry = null
|
||||||
|
if (entrySchema) {
|
||||||
|
try {
|
||||||
|
const ajv = new Ajv({ allErrors: true, strict: true, validateFormats: false })
|
||||||
|
validateEntry = ajv.compile(entrySchema)
|
||||||
|
} catch (error) {
|
||||||
|
addError(`schemas/registry-entry.schema.json: unable to compile (${error.message})`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const manifests = []
|
||||||
|
const seenIds = new Set()
|
||||||
|
for (const directoryEntry of await readdir(entriesRoot, { withFileTypes: true })) {
|
||||||
|
if (!directoryEntry.isDirectory()) {
|
||||||
|
addError(`entries/${directoryEntry.name}: entries root may contain directories only`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const entryDirectory = join(entriesRoot, directoryEntry.name)
|
||||||
|
const manifestPath = join(entryDirectory, 'manifest.json')
|
||||||
|
let manifestStat
|
||||||
|
try {
|
||||||
|
manifestStat = await lstat(manifestPath)
|
||||||
|
} catch (error) {
|
||||||
|
if (error.code === 'ENOENT') {
|
||||||
|
addError(`entries/${directoryEntry.name}: missing manifest.json`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
if (!manifestStat.isFile() || manifestStat.isSymbolicLink()) {
|
||||||
|
addError(`entries/${directoryEntry.name}/manifest.json: must be a regular file and not a symbolic link`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const manifest = await readJson(manifestPath)
|
||||||
|
if (!manifest) continue
|
||||||
|
manifests.push(manifest)
|
||||||
|
|
||||||
|
if (manifest.id !== directoryEntry.name) {
|
||||||
|
addError(`entries/${directoryEntry.name}/manifest.json: id must equal directory name`)
|
||||||
|
}
|
||||||
|
if (seenIds.has(manifest.id)) addError(`entries/${directoryEntry.name}/manifest.json: duplicate id ${manifest.id}`)
|
||||||
|
seenIds.add(manifest.id)
|
||||||
|
|
||||||
|
for (const injectedField of ['sourceId', 'hasInstall']) {
|
||||||
|
if (containsKey(manifest, injectedField)) {
|
||||||
|
addError(`entries/${directoryEntry.name}/manifest.json: ${injectedField} is client-injected and must not be authored`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validateEntry && !validateEntry(manifest)) {
|
||||||
|
addError(`entries/${directoryEntry.name}/manifest.json: ${formatAjvErrors(validateEntry.errors)}`)
|
||||||
|
}
|
||||||
|
if (manifest.type === 'external-integration') {
|
||||||
|
await validateExternalIntegration(entryDirectory, manifest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rootManifest) {
|
||||||
|
const expectedStats = {
|
||||||
|
totalEntries: manifests.length,
|
||||||
|
dockerApps: manifests.filter((entry) => entry.type === 'docker-app').length,
|
||||||
|
mcpServices: manifests.filter((entry) => entry.type === 'mcp').length,
|
||||||
|
httpApis: manifests.filter((entry) => entry.type === 'http-api').length,
|
||||||
|
externalIntegrations: manifests.filter((entry) => entry.type === 'external-integration').length,
|
||||||
|
}
|
||||||
|
for (const [field, expected] of Object.entries(expectedStats)) {
|
||||||
|
if (rootManifest.stats?.[field] !== expected) {
|
||||||
|
addError(`manifest.json#stats.${field}: expected ${expected}, received ${rootManifest.stats?.[field]}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const extraStats = Object.keys(rootManifest.stats ?? {}).filter((field) => !Object.hasOwn(expectedStats, field))
|
||||||
|
if (extraStats.length > 0) addError(`manifest.json#stats: unknown fields ${extraStats.join(', ')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const manifest of manifests) {
|
||||||
|
if (manifest.sourceAppId && !seenIds.has(manifest.sourceAppId)) {
|
||||||
|
addError(`entries/${manifest.id}/manifest.json: sourceAppId ${manifest.sourceAppId} does not exist`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length > 0) {
|
||||||
|
console.error(`Registry validation failed with ${errors.length} error(s):`)
|
||||||
|
for (const error of errors) console.error(`- ${error}`)
|
||||||
|
process.exitCode = 1
|
||||||
|
} else {
|
||||||
|
const counts = rootManifest.stats
|
||||||
|
console.log(
|
||||||
|
`Registry validation passed: ${counts.totalEntries} entries ` +
|
||||||
|
`(${counts.dockerApps} Docker, ${counts.mcpServices} MCP, ` +
|
||||||
|
`${counts.httpApis} HTTP API, ${counts.externalIntegrations} external integration).`,
|
||||||
|
)
|
||||||
|
}
|
||||||
259
scripts/validate-registry.test.mjs
Normal file
259
scripts/validate-registry.test.mjs
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { cp, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { dirname, join, resolve } from 'node:path'
|
||||||
|
import { spawnSync } from 'node:child_process'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import test from 'node:test'
|
||||||
|
|
||||||
|
const scriptDirectory = dirname(fileURLToPath(import.meta.url))
|
||||||
|
const repositoryRoot = resolve(scriptDirectory, '..')
|
||||||
|
const validatorPath = join(scriptDirectory, 'validate-registry.mjs')
|
||||||
|
|
||||||
|
const createFixture = async () => {
|
||||||
|
const root = await mkdtemp(join(tmpdir(), 'desirecore-registry-test-'))
|
||||||
|
await Promise.all([
|
||||||
|
cp(join(repositoryRoot, 'entries'), join(root, 'entries'), { recursive: true }),
|
||||||
|
cp(join(repositoryRoot, 'schemas'), join(root, 'schemas'), { recursive: true }),
|
||||||
|
cp(join(repositoryRoot, 'SCHEMA_VERSION'), join(root, 'SCHEMA_VERSION')),
|
||||||
|
cp(join(repositoryRoot, 'manifest.json'), join(root, 'manifest.json')),
|
||||||
|
])
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
const readJson = async (path) => JSON.parse(await readFile(path, 'utf8'))
|
||||||
|
const writeJson = async (path, value) => writeFile(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8')
|
||||||
|
|
||||||
|
const runValidator = (root) => spawnSync(
|
||||||
|
process.execPath,
|
||||||
|
[validatorPath, '--root', root],
|
||||||
|
{ cwd: repositoryRoot, encoding: 'utf8' },
|
||||||
|
)
|
||||||
|
|
||||||
|
const expectRejected = async (mutate, expectedMessage) => {
|
||||||
|
const root = await createFixture()
|
||||||
|
try {
|
||||||
|
await mutate(root)
|
||||||
|
const result = runValidator(root)
|
||||||
|
assert.equal(result.status, 1, `validator unexpectedly passed:\n${result.stdout}`)
|
||||||
|
assert.match(`${result.stdout}\n${result.stderr}`, expectedMessage)
|
||||||
|
} finally {
|
||||||
|
await rm(root, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test('accepts the checked-in Registry fixture', async () => {
|
||||||
|
const root = await createFixture()
|
||||||
|
try {
|
||||||
|
const result = runValidator(root)
|
||||||
|
assert.equal(result.status, 0, result.stderr)
|
||||||
|
assert.match(result.stdout, /Registry validation passed: 22 entries/)
|
||||||
|
} finally {
|
||||||
|
await rm(root, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects an entry whose directory and id differ', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.id = 'different-id'
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /id must equal directory name/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects client-injected sourceId in an upstream manifest', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.sourceId = 'untrusted-source'
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /sourceId is client-injected/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects stale root statistics', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.stats.externalIntegrations = 0
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /stats\.externalIntegrations: expected 1, received 0/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects an unknown external-integration id', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.id = 'unreviewed-bridge'
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /unknown external-integration id unreviewed-bridge/)
|
||||||
|
})
|
||||||
|
|
||||||
|
for (const kind of ['product', 'documentation', 'chrome-web-store', 'edge-add-ons', 'privacy', 'terms']) {
|
||||||
|
test(`rejects a modified ${kind} official URL`, async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
const link = manifest.officialLinks.find((item) => item.kind === kind)
|
||||||
|
link.url = `${link.url}/tampered`
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /URL must exactly equal/)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test('rejects a browser-store extension id that does not match its URL path', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
const link = manifest.officialLinks.find((item) => item.kind === 'chrome-web-store')
|
||||||
|
link.url = 'https://chromewebstore.google.com/detail/kimi-webbridge/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /URL path must end with extensionId/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects an incorrect structured extension id', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
const link = manifest.officialLinks.find((item) => item.kind === 'edge-addons')
|
||||||
|
?? manifest.officialLinks.find((item) => item.kind === 'edge-add-ons')
|
||||||
|
link.extensionId = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /extensionId must equal bnlffdbcfnanfbknnlaflhlhkocccckg/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects installation fields for listing-only integrations', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.install = { method: 'unsupported' }
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /forbidden field "install"/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects an unverified or unexpected listing maintainer', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.listingMaintainer.url = 'https://github.com/example/registry'
|
||||||
|
manifest.listingMaintainer.verified = false
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /listingMaintainer must be the verified DesireCore Registry URL/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects a non-tools external category', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.category = 'browser'
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /must be equal to constant/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects an unapproved external icon', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.icon = 'kimi-logo'
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /must be equal to one of the allowed values/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects extra text files in an external-integration directory', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
await writeFile(join(root, 'entries', 'kimi-webbridge', 'usage.md'), '# not allowed\n', 'utf8')
|
||||||
|
}, /allows only manifest\.json; found file usage\.md/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects extra directories in an external-integration directory', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
await mkdir(join(root, 'entries', 'kimi-webbridge', 'assets'))
|
||||||
|
}, /allows only manifest\.json; found directory assets/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects symbolic links in an external-integration directory', { skip: process.platform === 'win32' }, async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
await symlink('manifest.json', join(root, 'entries', 'kimi-webbridge', 'alias.json'))
|
||||||
|
}, /allows only manifest\.json; found symbolic link alias\.json/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects a symbolic-link manifest', { skip: process.platform === 'win32' }, async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const directory = join(root, 'entries', 'kimi-webbridge')
|
||||||
|
const manifestPath = join(directory, 'manifest.json')
|
||||||
|
const backupPath = join(directory, 'reviewed.json')
|
||||||
|
await cp(manifestPath, backupPath)
|
||||||
|
await rm(manifestPath)
|
||||||
|
await symlink('reviewed.json', manifestPath)
|
||||||
|
}, /manifest\.json: must be a regular file and not a symbolic link/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects binary files in an external-integration directory', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
await writeFile(join(root, 'entries', 'kimi-webbridge', 'payload.bin'), Buffer.from([0, 255, 1, 254]))
|
||||||
|
}, /allows only manifest\.json; found file payload\.bin/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects special filesystem entries in an external-integration directory', { skip: process.platform === 'win32' }, async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const fifoPath = join(root, 'entries', 'kimi-webbridge', 'control.fifo')
|
||||||
|
const result = spawnSync('mkfifo', [fifoPath], { encoding: 'utf8' })
|
||||||
|
assert.equal(result.status, 0, result.stderr)
|
||||||
|
}, /allows only manifest\.json; found FIFO control\.fifo/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects an impossible compliance review date', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.compliance.reviewedAt = '2026-02-30'
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /compliance\.reviewedAt: expected a real YYYY-MM-DD calendar date/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects an impossible artifact review date', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.compliance.artifactReviews[0].reviewedAt = '2026-13-01'
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /artifact macos\/arm64 reviewedAt: expected a real YYYY-MM-DD calendar date/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects an artifact URL that is not pinned to its reviewed version', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.compliance.artifactReviews[0].url =
|
||||||
|
'https://cdn.kimi.com/webbridge/unpinned/releases/kimi-webbridge-darwin-arm64'
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /URL must pin the reviewed version in its release path/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects query parameters on an immutable artifact URL', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.compliance.artifactReviews[0].url += '?redirect=1'
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /URL must not contain query or fragment components/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects unsupported universal artifact architecture', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
manifest.compliance.artifactReviews[0].architecture = 'universal'
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /must be equal to one of the allowed values/)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('rejects a local daemon that omits Windows', async () => {
|
||||||
|
await expectRejected(async (root) => {
|
||||||
|
const path = join(root, 'entries', 'kimi-webbridge', 'manifest.json')
|
||||||
|
const manifest = await readJson(path)
|
||||||
|
const daemon = manifest.components.find((item) => item.kind === 'local-daemon')
|
||||||
|
daemon.platforms = ['macos', 'linux']
|
||||||
|
await writeJson(path, manifest)
|
||||||
|
}, /local-daemon must declare at least Windows and macOS/)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user