@coda-console/plugin-check 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -15
- package/check/coda.mjs +10 -5
- package/check/manifest.mjs +10 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,29 +14,45 @@ plugins/grok/ Grok:转发运行机上 Grok CLI 的 ACP,并适配
|
|
|
14
14
|
plugins/opencode/ OpenCode:在运行机的 opencode acp 与 Coda 之间转发,送入会话指令
|
|
15
15
|
plugins/codex-accounts/ Codex 多账号:管理登录、自动续期与账号选择,接管 Codex 额度面板
|
|
16
16
|
plugins/cursor/ Cursor:在运行机的 cursor-agent acp 与 Coda 之间转发,送入会话指令
|
|
17
|
+
plugins/magpie/ Magpie:把本机的 Magpie 网关作为模型服务提供给 Pi 等插件,安装时装好 Magpie 并让网关常驻
|
|
17
18
|
plugins/review/ PR 生命流程:会话开的 PR 先跑项目的检查、再交叉评审,你批准后合并、部署与发布;也是写 PR 流程的样例
|
|
18
19
|
templates/agent/ 最小的 Agent 插件:手写 ACP 的回声 Agent,演示会话指令、插话、Agent 活动与自主轮次
|
|
19
20
|
templates/panel/ 最小的面板插件:一张卡片、一条进度条、一个按钮
|
|
20
21
|
bin/、check/ 契约检查(仓库根的 package.json 就是它,@coda-console/plugin-check)
|
|
21
22
|
test/ 检查包的测试;test/plugins/ 是各插件的测试(插件目录里只放运行要用的文件)
|
|
22
23
|
docs/agent-protocol.md Coda 接入协议正文的公开副本
|
|
23
|
-
.coda/workflows/ 本仓库自己的流程:sync-coda
|
|
24
|
+
.coda/workflows/ 本仓库自己的流程:sync-coda 等新版本发到 npm,再给 Coda 开升级依赖的 PR
|
|
25
|
+
.claude-plugin/ 官方市场的索引 marketplace.json,见「官方市场」
|
|
24
26
|
```
|
|
25
27
|
|
|
28
|
+
## 官方市场
|
|
29
|
+
|
|
30
|
+
`.claude-plugin/marketplace.json` 是 Coda 的官方市场(Coda 的 docs/plugins.md「市场」):格式照搬 Claude Code 的
|
|
31
|
+
[marketplace.json](https://code.claude.com/docs/en/plugins/marketplace-reference),Coda 新装时就登记了它,插件页的「商店」从这里列出可以装的插件。
|
|
32
|
+
它列不随 Coda 发布、维护者推荐的插件:本仓库里的(`./plugins/<id>`),以及别的公开仓库或 npm 包。随 Coda 发布的那几个在 Coda 自带的市场里,这里不重复列。
|
|
33
|
+
|
|
34
|
+
- **加一个插件**就是在 `plugins` 里加一个条目:`name` 是插件 ID,写 `displayName`、`description`、`category`、`tags` 与 `source`。
|
|
35
|
+
来源可以是 `./plugins/<id>`、`{ "source": "github", "repo": "owner/repo" }`、`{ "source": "git-subdir", "url": "owner/repo", "path": "<目录>" }`
|
|
36
|
+
或 `{ "source": "npm", "package": "<包名>" }`;Coda 不装 `archive`、`command` 来源,也不认版本范围。
|
|
37
|
+
- **分类**只用这几个,Coda 的插件页按它分组:`Agent`、`模型与账号`、`工作流程`、`机器与资源`、`Skills 与工具`。
|
|
38
|
+
- **本仓库里的插件**条目要写 `version`,与它清单里的一样:Coda 按条目的版本标「可更新」。升插件版本时一起改。
|
|
39
|
+
- `npm test` 里的 `test/check-marketplace.test.mjs` 用 `scripts/check-marketplace.mjs` 核对上面几条(CI 跑 `npm test`),本地也可以直接运行这个脚本。
|
|
40
|
+
|
|
26
41
|
## Coda 自带的插件
|
|
27
42
|
|
|
28
43
|
`codex`、`claude`、`kimi`、`grok`、`opencode`、`codex-accounts`、`review` 是 Coda 内置的插件,源码从此在这里维护。
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- **改它们先在这里提 PR
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
合进 main 后它们发布为 npm 包 `@coda-console/<id>`([npm 发布](#npm-发布)),Coda 后端的 `package.json` 把它们写成钉住版本号的依赖,
|
|
45
|
+
随后端的 `npm ci` 装好,新机器首次启动就带着它们。
|
|
46
|
+
|
|
47
|
+
- **改它们先在这里提 PR、合并**,发布流程发出新版本后,再让 Coda 把依赖升到那个版本(Coda 的 docs/development.md「coda-plugins 维护的内置插件」)。
|
|
48
|
+
升依赖由本仓库的部署流程 `sync-coda`(`.coda/workflows/sync-coda.bpmn`、`scripts/sync-coda.mjs`)做:合并时勾选「部署」,它在合并提交上先等
|
|
49
|
+
`publish.yml` 跑完、npm 读得到这个提交的版本(发布失败就带着那次运行的地址失败),再把 Coda 用到的包升到这些版本(只升不降)、照抄插件的 `overrides`,
|
|
50
|
+
推到 Coda 的分支 `sync/coda-plugins` 并开 PR(开着的就只更新),PR 由开这边 PR 的会话负责、走 Coda 的 PR 流程;Coda 已是这些版本就什么都不做。
|
|
51
|
+
也可以单独发起,同步默认分支的最新提交。
|
|
36
52
|
- **改了哪个插件就升哪个插件的 `version`**(`plugins/<id>/` 下任何文件,含 `package-lock.json`;只改 `test/` 不用):Coda 的插件页与遥测按版本认插件。
|
|
37
53
|
CI 在 PR 上运行 `node scripts/check-versions.mjs origin/<目标分支>` 核对;Dependabot 升 CLI、SDK 的 PR 也要补一个升版本的提交。
|
|
38
|
-
- Coda
|
|
39
|
-
- 测的是插件自己代码的用例在 `test/plugins/<id>/`;把 Coda 与入口合在一起测的(启动 Coda 后端、写进 Coda 的存储)留在 Coda
|
|
54
|
+
- Coda 的「检查更新」可以从 npm 装同一个包的新版本,盖过 Coda 带的版本;只有 `@coda-console/<id>` 这个包能用这些名字。
|
|
55
|
+
- 测的是插件自己代码的用例在 `test/plugins/<id>/`;把 Coda 与入口合在一起测的(启动 Coda 后端、写进 Coda 的存储)留在 Coda,对它装的 npm 包跑。
|
|
40
56
|
`review` 是一个流程,要 Coda 的流程引擎才跑得起来,它的用例都在 Coda。
|
|
41
57
|
|
|
42
58
|
| 插件 | npm 依赖(与 Coda 后端同版本) | 运行机上另要 |
|
|
@@ -66,6 +82,33 @@ CLI 不在或运行不了就用它自己的安装程序安装,低于插件要
|
|
|
66
82
|
Grok 1.0.52(审批模式通知核对过的版本)、OpenCode 1.0.151(`opencode acp` 第一个报当前模型、恢复的会话能接着跑的版本:之前 `session/load` 不登记会话,下一轮报 Session not found);Cursor 没有已知的下限,能运行就行(它自己会更新)。
|
|
67
83
|
Codex 与 Claude 的 CLI、SDK 是插件的 npm 依赖,不需要设置动作。
|
|
68
84
|
|
|
85
|
+
### 模型服务
|
|
86
|
+
|
|
87
|
+
Claude 与 Codex 各有一项设置「模型服务」(`claude.modelProvider`、`codex.modelProvider`,`format: coda-model-provider`),
|
|
88
|
+
选一个别的插件提供的[模型服务](https://github.com/coda-console/coda/blob/main/docs/plugins.md#模型服务),如 Magpie。
|
|
89
|
+
只经会话进程的启动参数与按会话的覆盖生效,不改运行机上的 `~/.claude/settings.json`、`~/.codex/config.toml`;不选、或提供它的插件卸载了,与原来一样。
|
|
90
|
+
|
|
91
|
+
- **接口**:Claude 只接 `anthropic-messages` 的服务,Codex 只接 `openai-responses` 的(Codex 0.162 已不接受 `wire_api = "chat"`)。
|
|
92
|
+
选了别的接口的服务,入口只在标准错误记一行,不列它的模型。
|
|
93
|
+
- **模型**:会话打开时从服务的「地址/models」列出模型,问法同 Coda 的「检查可用模型」(Claude 带 `x-api-key`,Codex 带 `Authorization: Bearer`),
|
|
94
|
+
排在 Agent 自己的模型后面:ID 是 `<服务>/<模型>`(如 `magpie/anthropic/deepseek/deepseek-v4-pro`),名字是「<模型> (<服务标题>)」,没有推理强度可选。
|
|
95
|
+
列不出来时只有 Agent 自己的模型。
|
|
96
|
+
- **何时换**:选模型只是记下(Coda 读模型目录时会把每个模型都选一遍,不该因此启动什么);下一轮开始前,所选模型的服务与会话正在用的不同,才把会话换过去。
|
|
97
|
+
- **Claude**:以 `resume` 新起一个 SDK 会话(还没说过话的会话用同一个会话 ID 新起),flag settings 的 `env` 设 `ANTHROPIC_BASE_URL`、
|
|
98
|
+
`ANTHROPIC_AUTH_TOKEN`(服务的 Key,没有 Key 写 `none`)与 `ANTHROPIC_MODEL`、`ANTHROPIC_DEFAULT_{OPUS,SONNET,HAIKU}_MODEL`、
|
|
99
|
+
`CLAUDE_CODE_SUBAGENT_MODEL`(子 Agent、标题等后台调用也用这个模型)。运行机自己通往 API 的办法一样都不带过去:`ANTHROPIC_API_KEY`、
|
|
100
|
+
`CLAUDE_CODE_OAUTH_TOKEN`、`ANTHROPIC_CUSTOM_HEADERS`、云服务商开关等置空,`apiKeyHelper` 置空(不然 API Key、helper 给的 Key 会作为 `x-api-key` 发给服务)。
|
|
101
|
+
flag settings 高于运行机与项目的 settings,它们 `env` 里的同名变量盖不过它;换回 Claude 自己的模型时新起的会话不带这些,运行机的配置照旧。
|
|
102
|
+
换回 Claude 自己的模型同样新起一个,不带这些;审批模式与推理强度沿用。还有后台任务、子 Agent 或定时唤醒没结束时,换到另一边的模型被拒绝(-32602),因为它们随原来的进程结束。
|
|
103
|
+
新起的那个起不来(SDK 当场报错、初始化失败或超时),这一轮失败,会话随即结束(同 SDK 会话自己结束),Coda 下次再打开它。
|
|
104
|
+
- **Codex**:Codex 一个线程只绑一个模型 provider,所以照 codex-acp 处理 ACP `providers/set`、`providers/disable` 的办法:重启 app-server,
|
|
105
|
+
把服务作为它 OpenAI 槽位的 gateway(`model_providers.custom-gateway`,`wire_api = "responses"`,Key 放在 `Authorization` 头),再 resume 会话的线程;
|
|
106
|
+
换回 Codex 自己的模型时撤掉 gateway 再 resume。resume 带着会话打开时的 Coda 会话指令与 MCP 服务器,app-server 里的后台命令按 codex-acp 原有的做法报为停止。
|
|
107
|
+
换的过程中失败(重启的 app-server 起不来、resume 失败)时这一轮失败,下一轮从重启起重新换(会话在哪个 provider 上由入口自己记:
|
|
108
|
+
codex-acp 在 resume 之前就记下了 gateway,新 app-server 起不来时还留着已关闭的旧客户端,都不能拿来当换成了)。
|
|
109
|
+
一句话都还没说的线程 Codex 没有写下来,resume 不了:先以第一条消息(前 80 字)为名 `thread/name/set` 把它写下,这也就成了会话标题(不再由 Codex 生成)。
|
|
110
|
+
Codex 没有服务的模型的元数据,它为此发的「Model metadata … not found」与恢复时的「recorded with model …」提示不进会话。
|
|
111
|
+
|
|
69
112
|
### Grok 审批模式
|
|
70
113
|
|
|
71
114
|
Grok 插件通过 ACP 提供原来的「自动审批」「全部放行」两个选项;新会话未选择时沿用运行机配置。
|
|
@@ -88,7 +131,7 @@ Codex 用普通 ACP 选择型配置项 `service-tier`(「速度」)报告速
|
|
|
88
131
|
|
|
89
132
|
`codex-accounts` 2.0.0 自己管理账号,不需要预装账号管理器或 Python。安装时 Coda 安装它的 npm 依赖和
|
|
90
133
|
`codex` 插件;本版接管额度、登录入口和续期通知的配套入口是 `codex` 1.1.0。已有安装升级时两个插件一起更新,
|
|
91
|
-
Coda
|
|
134
|
+
Coda 升级依赖时也一起升这两个包。
|
|
92
135
|
|
|
93
136
|
- 首次使用会把运行机 `$CODEX_HOME/auth.json`(默认 `~/.codex/auth.json`)里的 ChatGPT 登录导入插件,作为第一个
|
|
94
137
|
普通受控账号并默认选用。原文件保留;之后的凭据、续期与选择都由插件维护,原登录文件后续变化不会替换受控账号。
|
|
@@ -139,15 +182,15 @@ Coda 按这个通用类别显示「额度不足」,不触发服务故障重试
|
|
|
139
182
|
Codex 会话、面板与登录用的 Codex CLI 就是 `codex` 插件依赖的 `@openai/codex`;Claude 的是 `claude` 插件依赖的
|
|
140
183
|
`@anthropic-ai/claude-agent-sdk`。插件按包名从自己的依赖里找它们,Coda 不另给路径。升级就是改这两个版本:
|
|
141
184
|
Dependabot(`.github/dependabot.yml`)每周为它们各开一个 PR,连同 `package-lock.json` 一起改;合并后在 Coda「设置 › 插件」
|
|
142
|
-
|
|
143
|
-
|
|
185
|
+
对该插件点「检查更新」;合并时勾选「部署」,`sync-coda` 给 Coda 开升级依赖的 PR。升 `@openai/codex` 时 codex-acp 经 `overrides` 跟着用新版本,
|
|
186
|
+
Coda 根上的同一条 overrides 由 `sync-coda` 一起改(npm 只认根的,Coda 的测试会核对);合并前用真实会话跑一轮。
|
|
144
187
|
|
|
145
188
|
## 在 Coda 里安装
|
|
146
189
|
|
|
147
190
|
「设置 › 插件 › 从仓库添加…」:
|
|
148
191
|
|
|
149
192
|
1. **仓库**填 `coda-console/coda-plugins`(或 `https://github.com/coda-console/coda-plugins`);**版本**留空是默认分支,也可以填分支、标签或完整的提交号。
|
|
150
|
-
2. **读取**:Coda 拉取那个提交,把仓库根与两层以内 `package.json` 带 `contributes` 的目录各列成一个插件——这个仓库里是 `plugins/`
|
|
193
|
+
2. **读取**:Coda 拉取那个提交,把仓库根与两层以内 `package.json` 带 `contributes` 的目录各列成一个插件——这个仓库里是 `plugins/` 下的七个与两个模板。Coda 内置的那几个只能从 npm 上自己的包单独更新;在 Coda 里先卸载它,才能从这里装同名的。
|
|
151
194
|
3. 选一个,确认里逐条列出它会运行的命令(Agent 的 `command`、面板的命令与动作),确认就是批准:这些命令以你的账户在空间里每台机器上运行,Coda 不做隔离,只装你信任的来源。
|
|
152
195
|
|
|
153
196
|
**Coda 只认提交**:安装时钉在读取到的那个提交上,每台机器各自取同一个提交。「检查更新」用记下的仓库与版本再读一次,提交变了才有更新;
|
|
@@ -309,7 +352,7 @@ CI 先装各插件的依赖再跑全部测试,`plugins/*` 只查清单:真
|
|
|
309
352
|
|
|
310
353
|
MIT,见 [LICENSE](LICENSE)。各插件的品牌图形归各自权利人所有,来源写在插件的 `icons/README.md`。
|
|
311
354
|
|
|
312
|
-
`plugins/codex/agent/` 里的 `relay.mjs`、`titles.mjs`、`network.mjs`、`process-exit.mjs`、`options.mjs`、`subagents.mjs`、`instructions.mjs`、`steering.mjs` 在加载时给锁定版本的
|
|
355
|
+
`plugins/codex/agent/` 里的 `relay.mjs`、`titles.mjs`、`network.mjs`、`process-exit.mjs`、`options.mjs`、`subagents.mjs`、`instructions.mjs`、`steering.mjs`、`providers.mjs` 在加载时给锁定版本的
|
|
313
356
|
[codex-acp](https://github.com/agentclientprotocol/codex-acp)(Apache-2.0,Copyright 2025 JetBrains s.r.o.)打补丁,补丁里含 codex-acp 构建产物的原文片段。
|
|
314
357
|
这些片段按 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 使用,许可全文也随 npm 包装在 `node_modules/@agentclientprotocol/codex-acp/LICENSE`;
|
|
315
358
|
codex-acp 本身作为 npm 依赖安装,不在本仓库里。
|
package/check/coda.mjs
CHANGED
|
@@ -100,6 +100,11 @@ export const AGENT_ID = /^[a-z][a-z0-9-]{0,39}$/;
|
|
|
100
100
|
export const PANEL_ID = /^[a-z][a-z0-9-]{0,63}$/;
|
|
101
101
|
export const POINT = /^[a-z][a-z0-9-]{0,39}$/;
|
|
102
102
|
export const SETTING_KEY = /^[a-z][a-z0-9-]*(\.[a-z][A-Za-z0-9]*)+$/;
|
|
103
|
+
// The string formats a setting may have, beside `coda-agent` and `coda-agent-match` (backend/src/components/plugins/plugin-rules.mjs
|
|
104
|
+
// STRING_FORMATS): a duration, a repository, a directory of the machine, a model service another plugin provides
|
|
105
|
+
// (`<插件>/<服务>`, MODEL_PROVIDER), and the address of one.
|
|
106
|
+
export const STRING_FORMATS = ['duration', 'coda-repo', 'coda-directory', 'coda-model-provider', 'coda-model-url'];
|
|
107
|
+
export const MODEL_PROVIDER = /^[a-z][a-z0-9-]{0,39}\/[a-z][a-z0-9-]{0,39}$/;
|
|
103
108
|
export const CAPABILITY = /^[a-z][a-z0-9-]{0,63}$/;
|
|
104
109
|
// What a plugin may contribute (backend/src/components/plugins/plugin-rules.mjs CONTRIBUTES); anything else is refused.
|
|
105
110
|
export const CONTRIBUTES = ['configuration', 'capabilities', 'instructions', 'panels', 'agents', 'extensionPoints', 'extensions', 'groups', 'events', 'commands', 'gitCredentials',
|
|
@@ -136,11 +141,11 @@ export const DEPTH = 2;
|
|
|
136
141
|
// `npm ci` in the plugin's version directory.
|
|
137
142
|
export const NPM = ['dependencies', 'optionalDependencies'];
|
|
138
143
|
export const NPM_LOCK = 'package-lock.json';
|
|
139
|
-
// Those
|
|
140
|
-
// version
|
|
141
|
-
//
|
|
142
|
-
export const BUILTIN_HERE = ['claude', 'codex', 'codex-accounts', 'grok', 'kimi', 'opencode'];
|
|
143
|
-
export const
|
|
144
|
+
// Those kept in `plugins/<id>` here that Coda ships as npm dependencies, `@coda-console/<id>` (docs/plugins.md「内置插件的来源」):
|
|
145
|
+
// a version of the same package may be installed over Coda's. Other sources may use the same ID once the built-in plugin is
|
|
146
|
+
// uninstalled; only the target backend can check its installed state (builtinProblem).
|
|
147
|
+
export const BUILTIN_HERE = ['claude', 'codex', 'codex-accounts', 'grok', 'kimi', 'opencode', 'review'];
|
|
148
|
+
export const BUILTIN_SCOPE = '@coda-console/';
|
|
144
149
|
// An agent, a concurrency group and a command are one plugin's (catalogProblem, claims). Those the built-in plugins declare, by
|
|
145
150
|
// plugin; and `claude`, the agent ID sessions from before #1069 still store (docs/plugins.md「Agent」).
|
|
146
151
|
export const BUILTIN_AGENTS = { 'claude-sdk': 'claude', codex: 'codex', grok: 'grok', kimi: 'kimi', opencode: 'opencode', claude: 'claude' };
|
package/check/manifest.mjs
CHANGED
|
@@ -7,9 +7,9 @@ import { execFileSync } from 'node:child_process';
|
|
|
7
7
|
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from 'node:fs';
|
|
8
8
|
import { basename, isAbsolute, join, normalize, relative, sep } from 'node:path';
|
|
9
9
|
import { inflateSync } from 'node:zlib';
|
|
10
|
-
import { AGENT_ID, AGENT_LIMITS, BUILTIN_AGENTS, BUILTIN_COMMANDS, BUILTIN_GROUPS, BUILTIN_HERE,
|
|
10
|
+
import { AGENT_ID, AGENT_LIMITS, BUILTIN_AGENTS, BUILTIN_COMMANDS, BUILTIN_GROUPS, BUILTIN_HERE, BUILTIN_SCOPE, CAPABILITY,
|
|
11
11
|
CODA_COMMANDS, CODA_EVENT_DOMAINS, COMMAND, COMMAND_LIMIT, BUILTIN_ONLY, CONTRIBUTES, EVENT, EVENT_LIMIT, DEPTH, ICON_LIMIT, INSTRUCTIONS_LIMIT, NPM, NPM_LOCK, PANEL_ID, PANEL_LIMITS, PLUGIN_ID, POINT,
|
|
12
|
-
SEGMENT, SERVICE, SETTING_KEY, SETUP_KEYS, SIZE_LIMIT } from './coda.mjs';
|
|
12
|
+
MODEL_PROVIDER, SEGMENT, SERVICE, SETTING_KEY, SETUP_KEYS, SIZE_LIMIT, STRING_FORMATS } from './coda.mjs';
|
|
13
13
|
|
|
14
14
|
class Invalid extends Error {}
|
|
15
15
|
const check = (condition, message) => { if (!condition) throw new Invalid(message); };
|
|
@@ -52,9 +52,9 @@ function schema(value, where, entry = false) {
|
|
|
52
52
|
const agent = value.format === 'coda-agent';
|
|
53
53
|
check(['boolean', 'integer', 'number', 'string', 'object', 'array'].includes(value.type), `${where} 的 type 不受支持:${value.type}`);
|
|
54
54
|
check((value.items === undefined && value.uniqueItems === undefined) || value.type === 'array', `${where}:只有多选(type: array)才写 items 与 uniqueItems`);
|
|
55
|
-
check(value.format === undefined || agent || value.format === 'coda-agent-match' || value.format
|
|
55
|
+
check(value.format === undefined || agent || value.format === 'coda-agent-match' || STRING_FORMATS.includes(value.format), `${where} 的 format 不受支持:${value.format}`);
|
|
56
56
|
check(!agent || value.type === 'object', `${where}:coda-agent 是对象`);
|
|
57
|
-
check(value.format
|
|
57
|
+
check(!STRING_FORMATS.includes(value.format) || value.type === 'string', `${where}:${value.format} 是文本`);
|
|
58
58
|
check(value.enum === undefined || (Array.isArray(value.enum) && value.enum.length && ['string', 'integer', 'number'].includes(value.type)), `${where} 的 enum 无效`);
|
|
59
59
|
const out = { type: value.type, format: value.format, enum: value.enum, minimum: value.minimum, maximum: value.maximum };
|
|
60
60
|
if (value.type === 'array') {
|
|
@@ -106,6 +106,9 @@ function checkValue(spec, value, where) {
|
|
|
106
106
|
} else if (spec.type === 'string') {
|
|
107
107
|
check(typeof value === 'string' && value.length <= 20000 && !value.includes('\0'), `${where}须为文本`);
|
|
108
108
|
check(spec.format !== 'duration' || isDuration(value), `${where}须为 ISO 8601 时长,如 P30D、PT6H`);
|
|
109
|
+
// A repository's address Coda reads as it installs from one; here only the directory and the model service.
|
|
110
|
+
check(spec.format !== 'coda-directory' || value === '' || value.startsWith('/'), `${where}须为绝对目录`);
|
|
111
|
+
check(spec.format !== 'coda-model-provider' || value === '' || MODEL_PROVIDER.test(value), `${where}须为「插件/服务」,如 cliproxy/cliproxy`);
|
|
109
112
|
} else if (spec.type === 'object') {
|
|
110
113
|
object(value, where);
|
|
111
114
|
for (const [k, v] of Object.entries(value)) {
|
|
@@ -227,11 +230,6 @@ function files(dir) {
|
|
|
227
230
|
walk('');
|
|
228
231
|
return { top: null, entries };
|
|
229
232
|
}
|
|
230
|
-
// The addresses the repository at `top` is fetched from.
|
|
231
|
-
function remotes(top) {
|
|
232
|
-
try { return execFileSync('git', ['-C', top, 'remote', '-v'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).split('\n').map(l => l.split(/\s+/)[1]).filter(Boolean); }
|
|
233
|
-
catch { return []; }
|
|
234
|
-
}
|
|
235
233
|
// Why npm could not install from the lock file next to the manifest (`npm ci` refuses a lock that does not match; Coda's
|
|
236
234
|
// manifestProblem refuses it first), or null.
|
|
237
235
|
function lockProblem(dir, manifest) {
|
|
@@ -266,13 +264,13 @@ export function checkManifest(dir, say) {
|
|
|
266
264
|
const { top, entries } = files(dir);
|
|
267
265
|
// Git names the top by its real path; so is the directory measured.
|
|
268
266
|
const path = top ? relative(top, realpathSync(dir)).split(sep).filter(Boolean) : null;
|
|
269
|
-
// A built-in plugin
|
|
267
|
+
// A built-in plugin as its package is named: Coda ships a version of that package, and a newer one may stand in for it.
|
|
270
268
|
const declaredId = manifest.id !== undefined ? manifest.id : manifest.name;
|
|
271
|
-
const own = BUILTIN_HERE.includes(declaredId) &&
|
|
269
|
+
const own = BUILTIN_HERE.includes(declaredId) && manifest.name === `${BUILTIN_SCOPE}${declaredId}`;
|
|
272
270
|
const id = part('manifest.name', () => {
|
|
273
271
|
check(typeof declaredId === 'string' && PLUGIN_ID.test(declaredId), `package.json 的 ${manifest.id !== undefined ? 'id' : 'name'} 只能用小写字母、数字和「-」,以字母开头,至多 40 个字符`);
|
|
274
272
|
return declaredId;
|
|
275
|
-
}, name => `package.json 能读,插件 ID ${name}${own ? `(Coda 内置插件 ${name}
|
|
273
|
+
}, name => `package.json 能读,插件 ID ${name}${own ? `(Coda 内置插件 ${name}:Coda 带的是 npm 上 ${manifest.name} 的一个版本,这个包的新版本可以盖过它)` : ''}`);
|
|
276
274
|
const contributes = part('manifest.contributes', () => {
|
|
277
275
|
check(manifest.contributes && typeof manifest.contributes === 'object' && !Array.isArray(manifest.contributes),
|
|
278
276
|
'contributes 须为对象(Coda 只把带 contributes 的目录当插件)');
|