dbx-plugin-skill 0.1.0 → 0.1.3
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
CHANGED
|
@@ -130,6 +130,8 @@ npm test # 端到端自检(CI 在 Node 18/20/22 上跑)
|
|
|
130
130
|
npm run verify:package # 校验 npm 打包内容与元数据
|
|
131
131
|
npm run upstream:check # 对比上游 DBX 契约是否变化
|
|
132
132
|
npm run upstream:update # 更新后重新基线化
|
|
133
|
+
npm run upstream:sync # 只同步清单中的上游文件/目录到 tmp/upstream
|
|
134
|
+
npm run upstream:report # 查看最近一次稀疏快照的提交与文件哈希
|
|
133
135
|
```
|
|
134
136
|
|
|
135
137
|
---
|
|
@@ -214,6 +216,24 @@ skill 大量记录 DBX 的**契约细节**(字段枚举、权限语法、协
|
|
|
214
216
|
- 维护者按 issue 里的步骤核对并更新 `skill/references/*.md`,然后 `npm run upstream:update` 重新基线化并提交。
|
|
215
217
|
- 只有 sha256 变化、事实无差异时,通常只是上游排版变动,确认后直接重新基线化即可。
|
|
216
218
|
|
|
219
|
+
### 按需引用上游源码
|
|
220
|
+
|
|
221
|
+
需要逐项核对实现、文档或 Workflow 时运行 `npm run upstream:sync`。它对 `t8y2/dbx` 使用
|
|
222
|
+
Git partial clone + sparse checkout,只下载 `tools/upstream-sources.json` 中列出的 schema、
|
|
223
|
+
插件开发文档和 SDK 子目录;`t8y2/dbx-store` 则使用 depth=1 全量浅克隆。内容保存到本地
|
|
224
|
+
`tmp/upstream/`,不会进入提交或 npm 包。重复执行只 fetch 两个仓库的最新 `main`,并生成带
|
|
225
|
+
commit 与 SHA-256 的快照报告。
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
npm run upstream:sync
|
|
229
|
+
npm run upstream:report
|
|
230
|
+
# 在 tmp/upstream/dbx 与 tmp/upstream/dbx-store 中核对后更新 skill/references/*.md
|
|
231
|
+
npm test
|
|
232
|
+
npm run upstream:update
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
需要扩大或缩小同步范围时,只修改 `tools/upstream-sources.json`,无需复制上游源码。
|
|
236
|
+
|
|
217
237
|
---
|
|
218
238
|
|
|
219
239
|
## 更新已安装的 skill
|
|
@@ -236,3 +256,10 @@ npm install --global dbx-plugin-skill@latest && dbx-plugin-skill install
|
|
|
236
256
|
- 官方商店仓库 `t8y2/dbx-store`:`CONTRIBUTING.md`、`schemas/plugin-candidate.schema.json`、`scripts/validate.mjs`、同步与签名 Workflow
|
|
237
257
|
|
|
238
258
|
> 插件上架 PR 提到 **`t8y2/dbx-store`**,不是 `t8y2/dbx`。普通插件源码留在你自己的仓库。
|
|
259
|
+
|
|
260
|
+
## 其他
|
|
261
|
+
|
|
262
|
+
- [dbx](https://github.com/t8y2/dbx):官方仓库
|
|
263
|
+
- [dbx-store](https://github.com/t8y2/dbx-store):应用商店仓库
|
|
264
|
+
- [awesome-dbx-plugins](https://github.com/eryajf/awesome-dbx-plugins):🦄 汇集优秀的 DBX 开源插件
|
|
265
|
+
- [dbx-plugin-skill](https://github.com/eryajf/dbx-plugin-skill):一个用于 DBX 插件开发 的 agent skill(技能包),覆盖从创建到上架官方商店的全链路
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dbx-plugin-skill",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "DBX 插件开发 skill:覆盖插件创建、开发、调试、打包、发布到 dbx-store 上架的全流程,安装到 DSH / Claude Code / agents 技能目录",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
"verify:package": "node tools/verify-package.mjs",
|
|
22
22
|
"upstream:check": "node tools/upstream-drift.mjs",
|
|
23
23
|
"upstream:update": "node tools/upstream-drift.mjs --update",
|
|
24
|
+
"upstream:sync": "node tools/upstream-sync.mjs",
|
|
25
|
+
"upstream:report": "node tools/upstream-sync.mjs --report-only",
|
|
24
26
|
"release:patch": "npm version patch",
|
|
25
27
|
"release:minor": "npm version minor",
|
|
26
28
|
"prepublishOnly": "npm test && node tools/verify-package.mjs"
|
package/skill/SKILL.md
CHANGED
|
@@ -16,7 +16,7 @@ description: DBX 插件开发全流程。Use when 创建、开发、调试、打
|
|
|
16
16
|
| --- | --- | --- |
|
|
17
17
|
| Manifest v1 | `manifest.json` | 插件身份、权限、入口、贡献点、国际化。运行时契约,**拒绝未声明字段** |
|
|
18
18
|
| 构建配置 | `dbx-plugin.toml` | 打包包含哪些目录、是否有原生后端、dev 构建命令。**不进入插件包** |
|
|
19
|
-
| Host API 1.x | `window.dbxPlugin` | 沙箱 UI 与 DBX
|
|
19
|
+
| Host API 1.x | `window.dbxPlugin` / Sidecar callback | 沙箱 UI 与 DBX 宿主通信;Host API 1.1 支持 `host/requestUserInput` |
|
|
20
20
|
| Sidecar Protocol v1 | stdin/stdout JSON-RPC | 可选原生后端与 DBX 通信 |
|
|
21
21
|
| 包格式 | `.dbxp`(ZIP 容器) | DBX 实际安装的东西 |
|
|
22
22
|
|
|
@@ -103,6 +103,8 @@ dbx-plugin create my-plugin \
|
|
|
103
103
|
- 前端入口由 `entrypoints.ui.entry` 指定,必须落在 `ui.root` 内(root 为 `ui` 时写 `ui/index.html`)。
|
|
104
104
|
- 所有 UI 必须构建为包内静态资源;**不要把 Vite dev server 或 CDN 地址写进发行包**。
|
|
105
105
|
- 需要原生能力(S3/SSH/系统凭据/长连接/高性能)才写 Sidecar。Sidecar 不是 OS 沙箱,以当前用户权限运行。
|
|
106
|
+
- 连接表单可用 `visible_when` / `required_when` 的 `all_of`、`any_of`、`not` 组合条件,以及 `picker` 提供本地文件选择;依赖这些能力时设置足够新的 `engines.dbx`。
|
|
107
|
+
- 若握手可能超过通用超时,连接表单可声明 `config` 字段 `connect_timeout_secs`;DBX 会用其解析值作为 `connection/test` 与 `connection/connect` 的 deadline。Sidecar 进程按插件共享、跨标签页复用,后端会话应按 `connection.id` 管理。
|
|
106
108
|
|
|
107
109
|
### 步骤 3:本地调试
|
|
108
110
|
|
|
@@ -49,13 +49,18 @@
|
|
|
49
49
|
插件自定义的连接类型标识(如 `ssh`、`example-files`)。**它不会给 DBX 内置数据库枚举添加成员**;保存后存放在 `plugin_connection_type`。
|
|
50
50
|
|
|
51
51
|
### `fields`
|
|
52
|
+
|
|
53
|
+
#### `connect_timeout_secs`(可选的内置字段键)
|
|
54
|
+
|
|
55
|
+
如果传输或握手可能超过 DBX 默认的通用超时,可声明一个 `binding: "config"` 且 `key: "connect_timeout_secs"` 的 `number` 字段。DBX 会把用户值(或声明的 `default`)写入类型化连接配置,并让 `connection/test`、`connection/connect` 的请求 deadline 使用这个解析后的值;已保存的 `external_config` 优先于默认值。声明后,连接对话框里的通用超时选项不再覆盖该值。
|
|
52
56
|
每个字段必须有 `key`(`identifier`)、`label`(非空)、`type`。
|
|
53
57
|
|
|
54
58
|
- `type` 枚举:`text`、`password`、`number`、`boolean`、`select`、`radio`、`textarea`。
|
|
55
59
|
- `select` / `radio` **必须**提供非空 `options`;每项 `{ "label": 非空, "value": 字符串 }`。其它类型**不允许**出现 `options`。
|
|
56
60
|
- 可选:`description`、`placeholder`、`required`(bool)、`default`(string|number|boolean|null,必须符合字段类型)、`binding`。
|
|
57
61
|
- 条件字段:
|
|
58
|
-
- `
|
|
62
|
+
- `picker`:仅用于 `text` / `password` / `textarea`,声明本地文件或目录选择;结构见下方。
|
|
63
|
+
- `visible_when` / `required_when`:叶子条件 `{ "field": "<ident>", "one_of": ["v1", "v2"] }`,也可用 `all_of`、`any_of`、`not` 递归组合(最多 8 层、64 个节点)。值按规范化字符串比较,`false` 与 `"false"` 等价。
|
|
59
64
|
- `binding: "port"` **必须** `type: "number"`。
|
|
60
65
|
- `binding` 为 `secret` / `name` / `host` / `username` / `password` / `database` 时,`type` 只能是 `text`、`password`、`select`、`radio`、`textarea`(不能是 `number`/`boolean`)。
|
|
61
66
|
|
|
@@ -71,6 +76,19 @@
|
|
|
71
76
|
|
|
72
77
|
> **绝对不要**把密码、Token、私钥放到 `config`、Workbench context、事件或日志里。非敏感的补充配置才用 `config`。
|
|
73
78
|
|
|
79
|
+
### `picker`(本地文件动作)
|
|
80
|
+
|
|
81
|
+
`text`、`password`、`textarea` 字段可以声明:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{ "key": "private_key_path", "label": "Private key", "type": "text", "binding": "config", "picker": { "kind": "file", "accept": [".pem", ".key"], "content_field": "private_key" } }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- `kind` 必填:`file` 或 `directory`;目录选择仅桌面端可用。
|
|
88
|
+
- 桌面端把绝对路径写入当前字段;浏览器端把文件内容写入 `content_field` 兄弟字段并清空路径字段。两种来源互斥。
|
|
89
|
+
- `accept` 最多 16 个唯一扩展名或 MIME 类型;浏览器上传上限 1 MiB。没有 `content_field` 的 picker 在浏览器端不可用。
|
|
90
|
+
- 依赖 `picker` 时应把 `engines.dbx` 设为包含该能力的版本。
|
|
91
|
+
|
|
74
92
|
插件连接的 `external_config` 会在新建、编辑、保存和重新连接流程中保留;升级插件时要显式迁移自己拥有的 `external_config`。
|
|
75
93
|
|
|
76
94
|
### `capabilities`
|
|
@@ -28,7 +28,7 @@ DBX 启动每个 Sidecar 后首先发送 `plugin/initialize`:
|
|
|
28
28
|
"id": 1,
|
|
29
29
|
"method": "plugin/initialize",
|
|
30
30
|
"params": {
|
|
31
|
-
"host": { "dbxVersion": "0.5.68", "hostApiVersion": "1.
|
|
31
|
+
"host": { "dbxVersion": "0.5.68", "hostApiVersion": "1.1.0", "features": ["host.requestUserInput"], "protocolVersions": [1] },
|
|
32
32
|
"plugin": { "id": "vendor.example", "version": "1.0.0" },
|
|
33
33
|
"permissions": ["host.events"]
|
|
34
34
|
}
|
|
@@ -133,7 +133,13 @@ kind: u8 | payload_length: u32 big-endian | payload
|
|
|
133
133
|
|
|
134
134
|
**绝对不要在事件、context 或错误消息中泄露 Secret。**
|
|
135
135
|
|
|
136
|
-
## 8.
|
|
136
|
+
## 8. Host API 1.1:请求用户输入
|
|
137
|
+
|
|
138
|
+
连接期间需要 MFA、主机密钥确认或账号选择时,Sidecar 可以在宿主已通告 `hostApiVersion >= 1.1.0` 或 `host.features` 含 `host.requestUserInput` 后发起 `host/requestUserInput`。插件发起的请求使用字符串 id;响应为 `{ "action": "submit", "value": "123456" }`、`{ "action": "cancel" }` 或 `{ "action": "timeout" }`。只有 `submit` 带值,其余必须失败关闭。`prompt` 最多 2000 字符,`timeoutSecs` 为 5–600(默认 300),同时最多 4 个未关闭提示。错误 `-32001` 表示无 UI,`-32601` 表示宿主不支持,`-32602` 表示参数非法;均应优雅降级。宿主在提示期间会暂停等待该提示的连接请求超时计时。
|
|
139
|
+
|
|
140
|
+
Rust SDK 提供 `dbx_plugin_sdk::host_client()`、`HostClient::supports("host/requestUserInput")` 与 `HostClient::request_user_input(...)`。
|
|
141
|
+
|
|
142
|
+
## 9. Rust SDK
|
|
137
143
|
|
|
138
144
|
`dbx-plugin-sdk`(协议 v1,支持 JSONL 与 framed)。
|
|
139
145
|
|
|
@@ -519,6 +519,7 @@ function checkConnectionProvider(contribution, at, declared, usage) {
|
|
|
519
519
|
"default",
|
|
520
520
|
"options",
|
|
521
521
|
"binding",
|
|
522
|
+
"picker",
|
|
522
523
|
"visible_when",
|
|
523
524
|
"required_when",
|
|
524
525
|
];
|
|
@@ -557,14 +558,53 @@ function checkConnectionProvider(contribution, at, declared, usage) {
|
|
|
557
558
|
}
|
|
558
559
|
}
|
|
559
560
|
}
|
|
561
|
+
if (field.picker !== undefined) {
|
|
562
|
+
const picker = field.picker;
|
|
563
|
+
if (!isPlainObject(picker)) {
|
|
564
|
+
error(`${fAt}.picker 必须是对象`);
|
|
565
|
+
} else {
|
|
566
|
+
const pickerAllowed = ["kind", "accept", "content_field"];
|
|
567
|
+
const extra = Object.keys(picker).filter((k) => !pickerAllowed.includes(k));
|
|
568
|
+
if (extra.length) error(`${fAt}.picker 含未知字段: ${extra.join(", ")}`);
|
|
569
|
+
if (!["text", "password", "textarea"].includes(field.type)) {
|
|
570
|
+
error(`${fAt}.picker 只能用于 text/password/textarea 字段`);
|
|
571
|
+
}
|
|
572
|
+
if (!["file", "directory"].includes(picker.kind)) {
|
|
573
|
+
error(`${fAt}.picker.kind 必须是 file 或 directory`);
|
|
574
|
+
}
|
|
575
|
+
if (picker.accept !== undefined) {
|
|
576
|
+
if (!Array.isArray(picker.accept) || picker.accept.length > 16 || new Set(picker.accept).size !== picker.accept.length || picker.accept.some((v) => typeof v !== "string" || !/^(\.[A-Za-z0-9]{1,16}|[A-Za-z0-9+*._-]{1,64}\/[A-Za-z0-9+*._-]{1,64})$/.test(v))) {
|
|
577
|
+
error(`${fAt}.picker.accept 必须是最多 16 个唯一的扩展名或 MIME 类型`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (picker.content_field !== undefined && (typeof picker.content_field !== "string" || !IDENTIFIER.test(picker.content_field))) {
|
|
581
|
+
error(`${fAt}.picker.content_field 必须是字段标识符`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
560
585
|
for (const conditionKey of ["visible_when", "required_when"]) {
|
|
561
586
|
const condition = field[conditionKey];
|
|
562
587
|
if (condition === undefined) continue;
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
error(`${
|
|
567
|
-
|
|
588
|
+
let conditionNodes = 0;
|
|
589
|
+
const validateCondition = (value, path, depth = 0) => {
|
|
590
|
+
conditionNodes += 1;
|
|
591
|
+
if (depth > 8 || conditionNodes > 64 || !isPlainObject(value)) { error(`${path} 条件嵌套无效、超过 8 层或超过 64 个节点`); return; }
|
|
592
|
+
const keys = Object.keys(value);
|
|
593
|
+
if (keys.length !== 1 && !(keys.length === 2 && keys.includes("field") && keys.includes("one_of"))) { error(`${path} 条件结构无效`); return; }
|
|
594
|
+
if ("field" in value || "one_of" in value) {
|
|
595
|
+
if (typeof value.field !== "string" || !IDENTIFIER.test(value.field) || !Array.isArray(value.one_of) || value.one_of.length === 0 || value.one_of.some((v) => !["string", "number", "boolean"].includes(typeof v))) error(`${path} 必须是 { field: 标识符, one_of: [字符串/数字/布尔值] }`);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const operator = keys[0];
|
|
599
|
+
if (operator === "not") { validateCondition(value.not, `${path}.not`, depth + 1); return; }
|
|
600
|
+
if (operator === "all_of" || operator === "any_of") {
|
|
601
|
+
if (!Array.isArray(value[operator]) || value[operator].length === 0) { error(`${path}.${operator} 必须是非空数组`); return; }
|
|
602
|
+
value[operator].forEach((child, i) => validateCondition(child, `${path}.${operator}[${i}]`, depth + 1));
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
error(`${path} 只支持 field/one_of、all_of、any_of、not`);
|
|
606
|
+
};
|
|
607
|
+
validateCondition(condition, `${fAt}.${conditionKey}`);
|
|
568
608
|
}
|
|
569
609
|
});
|
|
570
610
|
}
|