@dai_ming/plugin-deliverables 1.0.3 → 1.0.5
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 +81 -155
- package/install.js +463 -0
- package/openclaw-plugin.json +4 -3
- package/package.json +5 -2
- package/openclaw.plugin.json +0 -34
package/README.md
CHANGED
|
@@ -1,215 +1,141 @@
|
|
|
1
1
|
# @dai_ming/plugin-deliverables
|
|
2
2
|
|
|
3
|
-
OpenClaw
|
|
3
|
+
OpenClaw 交付物插件。安装后会把交付物 MCP、skill、AGENTS 规则和 `openclaw.json` 配置一次性落到运行目录里,让 Agent 默认把生成文件上传成可访问的交付物链接。
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## 插件包含什么
|
|
5
|
+
## 包内文件
|
|
8
6
|
|
|
9
7
|
| 文件 | 作用 |
|
|
10
8
|
|------|------|
|
|
11
|
-
| `
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
14
|
-
| `
|
|
15
|
-
| `openclaw-plugin.json` |
|
|
16
|
-
|
|
17
|
-
---
|
|
9
|
+
| `install.js` | 安装器:负责复制插件、注册 MCP、安装 skill、注入 AGENTS 规则、补齐 `plugins.allow`、清 session |
|
|
10
|
+
| `mcp-servers/deliverables.js` | MCP Server,暴露 `upload_deliverable` |
|
|
11
|
+
| `skills/deliverables/SKILL.md` | 约束模型优先走交付物上传,并统一写到 `output/` |
|
|
12
|
+
| `agents-rules/deliverables.md` | 注入到 `AGENTS.md` 的强约束规则 |
|
|
13
|
+
| `openclaw-plugin.json` | 插件清单,声明 MCP、skill、rules 和运行时配置 |
|
|
18
14
|
|
|
19
|
-
##
|
|
15
|
+
## 推荐安装方式
|
|
20
16
|
|
|
21
|
-
###
|
|
17
|
+
### 方式一:claw-gateway Helm 部署
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
把插件加入 `installPlugins`:
|
|
24
20
|
|
|
25
21
|
```yaml
|
|
26
|
-
# values.yaml(或 claw-gateway 管理界面的 Helm 参数)
|
|
27
22
|
installPlugins:
|
|
28
|
-
- "@dai_ming/plugin-deliverables@1.0.
|
|
23
|
+
- "@dai_ming/plugin-deliverables@1.0.5"
|
|
29
24
|
```
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
1. **Phase 3**:`npm pack @dai_ming/plugin-deliverables@1.0.3` 下载 tarball → 解压到 `/data/extensions-extra/plugin-deliverables/`
|
|
33
|
-
2. **Phase 3b**:在插件目录执行 `npm install --omit=dev`(本插件无运行时依赖,此步骤跳过)
|
|
34
|
-
3. **Phase 3e**:读取 `openclaw-plugin.json` 清单,自动:
|
|
35
|
-
- 复制 `mcp-servers/deliverables.js` → `/data/mcp-servers/deliverables.js`
|
|
36
|
-
- 安装 `skills/deliverables/SKILL.md` → 所有 workspace 的 `skills/deliverables/SKILL.md`
|
|
37
|
-
- 注入 `agents-rules/deliverables.md` 内容到所有 workspace 的 `AGENTS.md`(幂等,按 marker 替换)
|
|
38
|
-
|
|
39
|
-
> **env 变量**:MCP Server 需要以下变量(claw-gateway 在生成 `mcp.servers` 配置时会自动注入):
|
|
40
|
-
>
|
|
41
|
-
> | 变量 | 说明 | 默认值 |
|
|
42
|
-
> |------|------|--------|
|
|
43
|
-
> | `CLAW_GATEWAY_URL` | gateway 内部地址 | `http://claw-gateway:8080` |
|
|
44
|
-
> | `CLAW_GATEWAY_PUBLIC_URL` | gateway 公网地址(用于生成预览链接) | 同上 |
|
|
45
|
-
> | `CLAW_GATEWAY_API_KEY` | API Key | `api-key-1` |
|
|
26
|
+
现有 chart 会在 init 阶段完成安装。
|
|
46
27
|
|
|
47
|
-
###
|
|
28
|
+
### 方式二:npm 安装后执行安装脚本
|
|
48
29
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
**Step 1 — 下载并解压插件**
|
|
30
|
+
这就是给运行中 pod / 自定义镜像准备的最小落地方式。
|
|
52
31
|
|
|
53
32
|
```bash
|
|
54
33
|
npm config set registry https://registry.npmmirror.com
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
tar xzf openclaw-plugin-deliverables-*.tgz -C ~/.openclaw/extensions-extra/plugin-deliverables --strip-components=1
|
|
34
|
+
npm install @dai_ming/plugin-deliverables@1.0.5
|
|
35
|
+
node node_modules/@dai_ming/plugin-deliverables/install.js
|
|
58
36
|
```
|
|
59
37
|
|
|
60
|
-
|
|
38
|
+
也可以直接用 bin:
|
|
61
39
|
|
|
62
40
|
```bash
|
|
63
|
-
|
|
64
|
-
cp ~/.openclaw/extensions-extra/plugin-deliverables/mcp-servers/deliverables.js \
|
|
65
|
-
~/.openclaw/mcp-servers/deliverables.js
|
|
41
|
+
npx openclaw-deliverables-install
|
|
66
42
|
```
|
|
67
43
|
|
|
68
|
-
|
|
44
|
+
如果 OpenClaw home 不在默认位置,可以显式传入:
|
|
69
45
|
|
|
70
46
|
```bash
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
cp ~/.openclaw/extensions-extra/plugin-deliverables/skills/deliverables/SKILL.md \
|
|
74
|
-
"$WORKSPACE/skills/deliverables/SKILL.md"
|
|
47
|
+
node node_modules/@dai_ming/plugin-deliverables/install.js \
|
|
48
|
+
--home /home/node/.openclaw
|
|
75
49
|
```
|
|
76
50
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
AGENTS="$WORKSPACE/AGENTS.md"
|
|
81
|
-
RULES=~/.openclaw/extensions-extra/plugin-deliverables/agents-rules/deliverables.md
|
|
82
|
-
|
|
83
|
-
if grep -q "DELIVERABLE_LINK_RULES_START" "$AGENTS" 2>/dev/null; then
|
|
84
|
-
# 已有旧规则,用 Node.js 按 marker 替换
|
|
85
|
-
node -e "
|
|
86
|
-
var fs=require('fs');
|
|
87
|
-
var a=fs.readFileSync('$AGENTS','utf8');
|
|
88
|
-
var r=fs.readFileSync('$RULES','utf8').trim();
|
|
89
|
-
var re=/<!--\s*DELIVERABLE_LINK_RULES_START\s*-->[\s\S]*?<!--\s*DELIVERABLE_AUTO_UPLOAD_RULES_END\s*-->/g;
|
|
90
|
-
fs.writeFileSync('$AGENTS', re.test(a) ? a.replace(re,r) : a+'\n\n'+r+'\n');
|
|
91
|
-
"
|
|
92
|
-
else
|
|
93
|
-
# 首次注入
|
|
94
|
-
printf '\n\n' >> "$AGENTS"
|
|
95
|
-
cat "$RULES" >> "$AGENTS"
|
|
96
|
-
fi
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
**Step 5 — 配置 openclaw.json**
|
|
100
|
-
|
|
101
|
-
在 `~/.openclaw/openclaw.json` 的 `mcp.servers` 下添加:
|
|
102
|
-
|
|
103
|
-
```json
|
|
104
|
-
{
|
|
105
|
-
"mcp": {
|
|
106
|
-
"servers": {
|
|
107
|
-
"deliverables": {
|
|
108
|
-
"command": "node",
|
|
109
|
-
"args": ["/home/node/.openclaw/mcp-servers/deliverables.js"],
|
|
110
|
-
"env": {
|
|
111
|
-
"CLAW_GATEWAY_URL": "http://<your-gateway-host>:8080",
|
|
112
|
-
"CLAW_GATEWAY_PUBLIC_URL": "https://<public-domain>",
|
|
113
|
-
"CLAW_GATEWAY_API_KEY": "<your-api-key>"
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
},
|
|
118
|
-
"plugins": {
|
|
119
|
-
"entries": {
|
|
120
|
-
"deliverables": { "enabled": true }
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
## AGENTS.md 规则说明
|
|
51
|
+
## `install.js` 会做什么
|
|
129
52
|
|
|
130
|
-
|
|
53
|
+
执行一次脚本后,会自动完成这些动作:
|
|
131
54
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
55
|
+
1. 复制插件到:
|
|
56
|
+
- `~/.openclaw/extensions/plugin-deliverables`
|
|
57
|
+
- `~/.openclaw/extensions-extra/plugin-deliverables`
|
|
58
|
+
2. 复制 `deliverables.js` 到 `~/.openclaw/mcp-servers/`
|
|
59
|
+
3. 把 `SKILL.md` 写入所有已发现 workspace 和 agent skill 目录
|
|
60
|
+
4. 把交付物规则幂等注入到实际使用中的 `AGENTS.md`
|
|
61
|
+
5. 在各 workspace 下补出 `output/` 目录
|
|
62
|
+
6. 更新 `~/.openclaw/openclaw.json`
|
|
63
|
+
- 注册 `mcp.servers.deliverables`
|
|
64
|
+
- 启用 `skills.entries.deliverables`
|
|
65
|
+
- 启用 `plugins.entries.plugin-deliverables`
|
|
66
|
+
- 把 `plugin-deliverables` 加入 `plugins.allow`
|
|
67
|
+
7. 清理 session,让下一条消息重新读取 prompt / skill / tool 配置
|
|
68
|
+
8. 如果 agent 或全局存在 `tools.allow` 白名单,自动补上 `deliverables__upload_deliverable`
|
|
136
69
|
|
|
137
|
-
|
|
70
|
+
## 是否需要重启 Pod
|
|
138
71
|
|
|
139
|
-
|
|
72
|
+
不需要重启 Pod。
|
|
140
73
|
|
|
141
|
-
|
|
74
|
+
原因:
|
|
75
|
+
- 安装脚本会直接改 `~/.openclaw/openclaw.json`
|
|
76
|
+
- `plugins.allow` 变化会触发 OpenClaw 的 watcher,进程级重载大约 15 秒
|
|
77
|
+
- session 也会被清掉,所以下一条消息会重新读取最新的 `AGENTS.md` / skill
|
|
142
78
|
|
|
143
|
-
|
|
79
|
+
建议做法:
|
|
144
80
|
|
|
145
|
-
|
|
146
|
-
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
## MCP Server 说明
|
|
81
|
+
```bash
|
|
82
|
+
node node_modules/@dai_ming/plugin-deliverables/install.js
|
|
83
|
+
sleep 20
|
|
84
|
+
```
|
|
153
85
|
|
|
154
|
-
|
|
86
|
+
然后再发一条新的用户消息测试。
|
|
155
87
|
|
|
156
|
-
|
|
88
|
+
## 常用参数
|
|
157
89
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
| `type` | enum | ✅ | `article` / `game` / `zip` / `image` / `video` / `ppt` / `link` |
|
|
162
|
-
| `file_name` | string | ✅ | 用户可见文件名(含扩展名)或目录名 |
|
|
163
|
-
| `group_id` | string | — | 群聊 ID |
|
|
164
|
-
| `user_id` | string | — | 用户 ID |
|
|
165
|
-
| `content_text` | string | — | 文本内容(单文件场景) |
|
|
166
|
-
| `content_base64` | string | — | Base64 编码的二进制内容(单文件场景) |
|
|
167
|
-
| `files` | array | — | 多文件列表(游戏/站点场景,优先使用) |
|
|
90
|
+
```bash
|
|
91
|
+
node install.js --help
|
|
92
|
+
```
|
|
168
93
|
|
|
169
|
-
|
|
94
|
+
| 参数 | 说明 |
|
|
95
|
+
|------|------|
|
|
96
|
+
| `--home <dir>` | 指定 OpenClaw home,默认 `~/.openclaw` |
|
|
97
|
+
| `--plugin-root <dir>` | 指定插件源码目录,默认当前包目录 |
|
|
98
|
+
| `--no-clear-sessions` | 不清 session |
|
|
99
|
+
| `--dry-run` | 只打印计划,不落盘 |
|
|
170
100
|
|
|
171
|
-
|
|
101
|
+
## 返回结果
|
|
172
102
|
|
|
173
|
-
|
|
103
|
+
安装脚本成功后会输出一段 JSON 摘要,包含:
|
|
174
104
|
|
|
175
|
-
|
|
105
|
+
- 目标 OpenClaw home
|
|
106
|
+
- 写入的 extension 目录
|
|
107
|
+
- 写入的 MCP 文件
|
|
108
|
+
- 修改的 `AGENTS.md`
|
|
109
|
+
- 清理的 session 数量
|
|
176
110
|
|
|
177
|
-
|
|
111
|
+
## 发布
|
|
178
112
|
|
|
179
113
|
```bash
|
|
180
|
-
# 1. 修改 packages/plugin-deliverables/package.json 中的 version
|
|
181
|
-
# 2. 同步更新 packages/plugin-deliverables/mcp-servers/deliverables.js(如有变更)
|
|
182
|
-
# 3. 打包并发布到 npmmirror 兼容的私有或公开 registry
|
|
183
114
|
cd packages/plugin-deliverables
|
|
184
115
|
npm publish --registry https://registry.npmjs.org --access public
|
|
185
116
|
```
|
|
186
117
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
| claw-gateway 版本 | plugin 版本 |
|
|
190
|
-
|-------------------|-------------|
|
|
191
|
-
| 当前 | 1.0.3 |
|
|
192
|
-
|
|
193
|
-
---
|
|
118
|
+
## 排障
|
|
194
119
|
|
|
195
|
-
|
|
120
|
+
### Agent 没看到工具
|
|
196
121
|
|
|
197
|
-
|
|
122
|
+
重点检查:
|
|
198
123
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
124
|
+
1. `~/.openclaw/openclaw.json` 里是否有 `mcp.servers.deliverables`
|
|
125
|
+
2. `plugins.allow` 是否含 `plugin-deliverables`
|
|
126
|
+
3. `~/.openclaw/mcp-servers/deliverables.js` 是否存在
|
|
127
|
+
4. 当前 workspace 的 `AGENTS.md` 是否已经注入 deliverables 规则
|
|
203
128
|
|
|
204
|
-
|
|
129
|
+
### 上传返回 401 / 404
|
|
205
130
|
|
|
206
|
-
|
|
131
|
+
优先检查容器环境变量:
|
|
207
132
|
|
|
208
133
|
```bash
|
|
209
|
-
|
|
134
|
+
env | grep -E 'CLAW_GATEWAY|OPENCLAW_GATEWAY|botID'
|
|
210
135
|
```
|
|
211
136
|
|
|
212
|
-
|
|
137
|
+
以及直接请求 gateway:
|
|
213
138
|
|
|
214
|
-
|
|
215
|
-
|
|
139
|
+
```bash
|
|
140
|
+
curl -H "X-API-Key: $CLAW_GATEWAY_API_KEY" "$CLAW_GATEWAY_URL/healthz"
|
|
141
|
+
```
|
package/install.js
ADDED
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var fs = require("fs");
|
|
5
|
+
var os = require("os");
|
|
6
|
+
var path = require("path");
|
|
7
|
+
|
|
8
|
+
var LEGACY_DELIVERABLE_TOOL_NAMES = {
|
|
9
|
+
"upload_deliverable": true,
|
|
10
|
+
"list_deliverables": true,
|
|
11
|
+
"renew_deliverable": true,
|
|
12
|
+
"deliverables__upload_deliverable": true,
|
|
13
|
+
"deliverables__list_deliverables": true,
|
|
14
|
+
"deliverables__renew_deliverable": true
|
|
15
|
+
};
|
|
16
|
+
var DELIVERABLES_UPLOAD_TOOL = "deliverables__upload_deliverable";
|
|
17
|
+
|
|
18
|
+
function usage() {
|
|
19
|
+
console.log([
|
|
20
|
+
"Usage:",
|
|
21
|
+
" node install.js [--home <openclaw-home>] [--plugin-root <dir>] [--no-clear-sessions] [--dry-run]",
|
|
22
|
+
"",
|
|
23
|
+
"Defaults:",
|
|
24
|
+
" --home " + path.join(os.homedir(), ".openclaw"),
|
|
25
|
+
" --plugin-root current package directory"
|
|
26
|
+
].join("\n"));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseArgs(argv) {
|
|
30
|
+
var args = {
|
|
31
|
+
home: path.join(os.homedir(), ".openclaw"),
|
|
32
|
+
pluginRoot: __dirname,
|
|
33
|
+
clearSessions: true,
|
|
34
|
+
dryRun: false
|
|
35
|
+
};
|
|
36
|
+
for (var i = 0; i < argv.length; i++) {
|
|
37
|
+
var cur = argv[i];
|
|
38
|
+
if (cur === "--home") {
|
|
39
|
+
i += 1;
|
|
40
|
+
if (i >= argv.length) throw new Error("--home requires a value");
|
|
41
|
+
args.home = path.resolve(argv[i]);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (cur === "--plugin-root") {
|
|
45
|
+
i += 1;
|
|
46
|
+
if (i >= argv.length) throw new Error("--plugin-root requires a value");
|
|
47
|
+
args.pluginRoot = path.resolve(argv[i]);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (cur === "--no-clear-sessions") {
|
|
51
|
+
args.clearSessions = false;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (cur === "--dry-run") {
|
|
55
|
+
args.dryRun = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (cur === "--help" || cur === "-h") {
|
|
59
|
+
usage();
|
|
60
|
+
process.exit(0);
|
|
61
|
+
}
|
|
62
|
+
throw new Error("unknown argument: " + cur);
|
|
63
|
+
}
|
|
64
|
+
return args;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isPlainObject(val) {
|
|
68
|
+
return !!val && typeof val === "object" && !Array.isArray(val);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function deepCopy(val) {
|
|
72
|
+
if (val === undefined) return undefined;
|
|
73
|
+
return JSON.parse(JSON.stringify(val));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveEnv(val) {
|
|
77
|
+
if (typeof val !== "string") return val;
|
|
78
|
+
return val.replace(/\$\{([^}]+)\}/g, function(_, key) {
|
|
79
|
+
return process.env[key] || "";
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function resolveEnvDeep(val) {
|
|
84
|
+
if (Array.isArray(val)) return val.map(resolveEnvDeep);
|
|
85
|
+
if (isPlainObject(val)) {
|
|
86
|
+
var out = {};
|
|
87
|
+
Object.keys(val).forEach(function(key) {
|
|
88
|
+
out[key] = resolveEnvDeep(val[key]);
|
|
89
|
+
});
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
return resolveEnv(val);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function normalizeRuntimeName(name) {
|
|
96
|
+
if (typeof name !== "string") return "";
|
|
97
|
+
var out = name.trim();
|
|
98
|
+
if (!out) return "";
|
|
99
|
+
if (out.charAt(0) === "@") {
|
|
100
|
+
var slash = out.indexOf("/");
|
|
101
|
+
out = slash >= 0 ? out.slice(slash + 1) : out.slice(1);
|
|
102
|
+
}
|
|
103
|
+
var at = out.indexOf("@");
|
|
104
|
+
if (at >= 0) out = out.slice(0, at);
|
|
105
|
+
return out.trim();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function ensureDir(dir, dryRun) {
|
|
109
|
+
if (dryRun) return;
|
|
110
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function readJSON(filePath, fallback) {
|
|
114
|
+
try {
|
|
115
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
116
|
+
} catch (err) {
|
|
117
|
+
return deepCopy(fallback);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function writeJSON(filePath, value, dryRun) {
|
|
122
|
+
if (dryRun) return;
|
|
123
|
+
ensureDir(path.dirname(filePath), dryRun);
|
|
124
|
+
fs.writeFileSync(filePath, JSON.stringify(value, null, 2) + "\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function samePath(a, b) {
|
|
128
|
+
try {
|
|
129
|
+
return fs.realpathSync(a) === fs.realpathSync(b);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function copyRecursive(src, dst, dryRun) {
|
|
136
|
+
var stat = fs.lstatSync(src);
|
|
137
|
+
if (stat.isSymbolicLink()) {
|
|
138
|
+
var target = fs.realpathSync(src);
|
|
139
|
+
copyRecursive(target, dst, dryRun);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (stat.isDirectory()) {
|
|
143
|
+
ensureDir(dst, dryRun);
|
|
144
|
+
fs.readdirSync(src).forEach(function(entry) {
|
|
145
|
+
if (entry === "node_modules" || entry === ".git") return;
|
|
146
|
+
copyRecursive(path.join(src, entry), path.join(dst, entry), dryRun);
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
ensureDir(path.dirname(dst), dryRun);
|
|
151
|
+
if (!dryRun) {
|
|
152
|
+
fs.copyFileSync(src, dst);
|
|
153
|
+
if (path.extname(dst) === ".js") {
|
|
154
|
+
fs.chmodSync(dst, 0o755);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function replaceDirectory(src, dst, dryRun) {
|
|
160
|
+
if (samePath(src, dst)) return false;
|
|
161
|
+
if (!dryRun) fs.rmSync(dst, { recursive: true, force: true });
|
|
162
|
+
copyRecursive(src, dst, dryRun);
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function normalizePluginEntryMap(entriesCfg, pluginId) {
|
|
167
|
+
if (!isPlainObject(entriesCfg)) return {};
|
|
168
|
+
var merged = {};
|
|
169
|
+
Object.keys(entriesCfg).forEach(function(name) {
|
|
170
|
+
var item = entriesCfg[name];
|
|
171
|
+
if (!isPlainObject(item)) return;
|
|
172
|
+
Object.keys(item).forEach(function(key) {
|
|
173
|
+
merged[key] = deepCopy(item[key]);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
if (merged.enabled === undefined) merged.enabled = true;
|
|
177
|
+
var out = {};
|
|
178
|
+
out[pluginId] = merged;
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function collectWorkspaceRoots(home, cfg) {
|
|
183
|
+
var roots = {};
|
|
184
|
+
function add(dir) {
|
|
185
|
+
if (!dir || typeof dir !== "string") return;
|
|
186
|
+
roots[path.resolve(dir)] = true;
|
|
187
|
+
}
|
|
188
|
+
add(path.join(home, "workspace"));
|
|
189
|
+
try {
|
|
190
|
+
fs.readdirSync(home).forEach(function(entry) {
|
|
191
|
+
if (entry === "workspace" || entry.indexOf("workspace-") === 0) {
|
|
192
|
+
add(path.join(home, entry));
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
} catch (err) {}
|
|
196
|
+
if (cfg && cfg.agents && Array.isArray(cfg.agents.list)) {
|
|
197
|
+
cfg.agents.list.forEach(function(agent) {
|
|
198
|
+
if (agent && typeof agent.workspace === "string" && agent.workspace.trim()) {
|
|
199
|
+
add(agent.workspace.trim());
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return Object.keys(roots);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function collectAgentIDs(home, cfg) {
|
|
207
|
+
var ids = {};
|
|
208
|
+
function add(id) {
|
|
209
|
+
if (!id || typeof id !== "string") return;
|
|
210
|
+
ids[id.trim()] = true;
|
|
211
|
+
}
|
|
212
|
+
add("main");
|
|
213
|
+
try {
|
|
214
|
+
fs.readdirSync(path.join(home, "agents")).forEach(function(entry) {
|
|
215
|
+
if (entry && entry !== "." && entry !== "..") add(entry);
|
|
216
|
+
});
|
|
217
|
+
} catch (err) {}
|
|
218
|
+
if (cfg && cfg.agents && Array.isArray(cfg.agents.list)) {
|
|
219
|
+
cfg.agents.list.forEach(function(agent) {
|
|
220
|
+
if (agent && agent.id) add(String(agent.id));
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
return Object.keys(ids);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function writeText(filePath, content, dryRun) {
|
|
227
|
+
if (dryRun) return;
|
|
228
|
+
ensureDir(path.dirname(filePath), dryRun);
|
|
229
|
+
fs.writeFileSync(filePath, content);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function installSkills(pluginRoot, manifest, home, workspaceRoots, agentIDs, dryRun) {
|
|
233
|
+
var installed = [];
|
|
234
|
+
if (!isPlainObject(manifest.skills)) return installed;
|
|
235
|
+
Object.keys(manifest.skills).forEach(function(skillName) {
|
|
236
|
+
var relFile = manifest.skills[skillName];
|
|
237
|
+
var src = path.join(pluginRoot, relFile);
|
|
238
|
+
var content = fs.readFileSync(src, "utf8");
|
|
239
|
+
workspaceRoots.forEach(function(wsRoot) {
|
|
240
|
+
var skillFile = path.join(wsRoot, "skills", skillName, "SKILL.md");
|
|
241
|
+
writeText(skillFile, content, dryRun);
|
|
242
|
+
ensureDir(path.join(wsRoot, "output"), dryRun);
|
|
243
|
+
installed.push(skillFile);
|
|
244
|
+
});
|
|
245
|
+
agentIDs.forEach(function(agentID) {
|
|
246
|
+
var skillFile = path.join(home, "agents", agentID, "skills", skillName, "SKILL.md");
|
|
247
|
+
writeText(skillFile, content, dryRun);
|
|
248
|
+
installed.push(skillFile);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
return installed;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function injectRules(pluginRoot, manifest, workspaceRoots, dryRun) {
|
|
255
|
+
if (!manifest.agents_rules || !manifest.agents_rules.file) return [];
|
|
256
|
+
var startMarker = manifest.agents_rules.start_marker || "";
|
|
257
|
+
var endMarker = manifest.agents_rules.end_marker || "";
|
|
258
|
+
var rules = fs.readFileSync(path.join(pluginRoot, manifest.agents_rules.file), "utf8").trim() + "\n";
|
|
259
|
+
var changed = [];
|
|
260
|
+
workspaceRoots.forEach(function(wsRoot) {
|
|
261
|
+
var agentsFile = path.join(wsRoot, "AGENTS.md");
|
|
262
|
+
var existing = "";
|
|
263
|
+
try {
|
|
264
|
+
existing = fs.readFileSync(agentsFile, "utf8");
|
|
265
|
+
} catch (err) {}
|
|
266
|
+
var next = existing;
|
|
267
|
+
if (startMarker && endMarker && existing.indexOf(startMarker) >= 0) {
|
|
268
|
+
var re = new RegExp("<!--\\s*" + escapeRegExp(startMarker) + "\\s*-->[\\s\\S]*?<!--\\s*" + escapeRegExp(endMarker) + "\\s*-->", "g");
|
|
269
|
+
next = existing.replace(re, rules.trim());
|
|
270
|
+
if (next !== "" && next.charAt(next.length - 1) !== "\n") next += "\n";
|
|
271
|
+
} else if (existing) {
|
|
272
|
+
next = existing.replace(/\s*$/, "") + "\n\n" + rules;
|
|
273
|
+
} else {
|
|
274
|
+
next = rules;
|
|
275
|
+
}
|
|
276
|
+
ensureDir(path.join(wsRoot, "output"), dryRun);
|
|
277
|
+
if (next !== existing) {
|
|
278
|
+
writeText(agentsFile, next, dryRun);
|
|
279
|
+
changed.push(agentsFile);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
return changed;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function escapeRegExp(text) {
|
|
286
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function sanitizeToolList(items, isAllow) {
|
|
290
|
+
if (!Array.isArray(items)) return items;
|
|
291
|
+
var out = [];
|
|
292
|
+
var seen = {};
|
|
293
|
+
var hasWildcard = false;
|
|
294
|
+
items.forEach(function(raw) {
|
|
295
|
+
var item = String(raw || "").trim();
|
|
296
|
+
if (!item) return;
|
|
297
|
+
if (item === "*") hasWildcard = true;
|
|
298
|
+
if (LEGACY_DELIVERABLE_TOOL_NAMES[item]) return;
|
|
299
|
+
if (!seen[item]) {
|
|
300
|
+
out.push(item);
|
|
301
|
+
seen[item] = true;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
if (isAllow && !hasWildcard && !seen[DELIVERABLES_UPLOAD_TOOL]) {
|
|
305
|
+
out.push(DELIVERABLES_UPLOAD_TOOL);
|
|
306
|
+
}
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function sanitizeToolConfig(toolsCfg) {
|
|
311
|
+
if (!isPlainObject(toolsCfg)) return;
|
|
312
|
+
if (Array.isArray(toolsCfg.allow)) toolsCfg.allow = sanitizeToolList(toolsCfg.allow, true);
|
|
313
|
+
if (Array.isArray(toolsCfg.deny)) toolsCfg.deny = sanitizeToolList(toolsCfg.deny, false);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function applyPluginConfig(cfg, manifest, home) {
|
|
317
|
+
if (!isPlainObject(cfg)) cfg = {};
|
|
318
|
+
var pluginId = normalizeRuntimeName(manifest.name || "plugin-deliverables");
|
|
319
|
+
var mcpRoot = path.join(home, "mcp-servers");
|
|
320
|
+
|
|
321
|
+
cfg.plugins = isPlainObject(cfg.plugins) ? cfg.plugins : {};
|
|
322
|
+
cfg.plugins.allow = Array.isArray(cfg.plugins.allow) ? cfg.plugins.allow.slice() : [];
|
|
323
|
+
if (cfg.plugins.allow.indexOf(pluginId) === -1) cfg.plugins.allow.push(pluginId);
|
|
324
|
+
cfg.plugins.entries = isPlainObject(cfg.plugins.entries) ? cfg.plugins.entries : {};
|
|
325
|
+
|
|
326
|
+
var manifestPluginCfg = isPlainObject(manifest.openclaw_config) ? resolveEnvDeep(manifest.openclaw_config) : {};
|
|
327
|
+
var desiredEntries = {};
|
|
328
|
+
if (isPlainObject(manifestPluginCfg.plugins) && isPlainObject(manifestPluginCfg.plugins.entries)) {
|
|
329
|
+
desiredEntries = normalizePluginEntryMap(manifestPluginCfg.plugins.entries, pluginId);
|
|
330
|
+
}
|
|
331
|
+
var desiredPluginEntry = desiredEntries[pluginId] || { enabled: true };
|
|
332
|
+
if (desiredPluginEntry.enabled === undefined) desiredPluginEntry.enabled = true;
|
|
333
|
+
var currentEntry = isPlainObject(cfg.plugins.entries[pluginId]) ? cfg.plugins.entries[pluginId] : {};
|
|
334
|
+
cfg.plugins.entries[pluginId] = Object.assign({}, currentEntry, desiredPluginEntry);
|
|
335
|
+
|
|
336
|
+
cfg.skills = isPlainObject(cfg.skills) ? cfg.skills : {};
|
|
337
|
+
cfg.skills.entries = isPlainObject(cfg.skills.entries) ? cfg.skills.entries : {};
|
|
338
|
+
if (isPlainObject(manifest.skills)) {
|
|
339
|
+
Object.keys(manifest.skills).forEach(function(skillName) {
|
|
340
|
+
var currentSkill = isPlainObject(cfg.skills.entries[skillName]) ? cfg.skills.entries[skillName] : {};
|
|
341
|
+
cfg.skills.entries[skillName] = Object.assign({}, currentSkill, { enabled: true });
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
cfg.mcp = isPlainObject(cfg.mcp) ? cfg.mcp : {};
|
|
346
|
+
cfg.mcp.servers = isPlainObject(cfg.mcp.servers) ? cfg.mcp.servers : {};
|
|
347
|
+
if (isPlainObject(manifest.mcp_servers)) {
|
|
348
|
+
Object.keys(manifest.mcp_servers).forEach(function(serverName) {
|
|
349
|
+
var server = manifest.mcp_servers[serverName];
|
|
350
|
+
if (!isPlainObject(server)) return;
|
|
351
|
+
var generated = {
|
|
352
|
+
command: typeof server.command === "string" && server.command.trim() ? resolveEnv(server.command) : "node"
|
|
353
|
+
};
|
|
354
|
+
if (Array.isArray(server.args) && server.args.length) {
|
|
355
|
+
generated.args = resolveEnvDeep(server.args);
|
|
356
|
+
} else if (typeof server.script === "string" && server.script.trim()) {
|
|
357
|
+
generated.args = [path.join(mcpRoot, path.basename(server.script))];
|
|
358
|
+
}
|
|
359
|
+
if (isPlainObject(server.env)) generated.env = resolveEnvDeep(server.env);
|
|
360
|
+
if (typeof server.cwd === "string" && server.cwd.trim()) generated.cwd = resolveEnv(server.cwd);
|
|
361
|
+
if (typeof server.transport === "string" && server.transport.trim()) generated.transport = resolveEnv(server.transport);
|
|
362
|
+
var currentServer = isPlainObject(cfg.mcp.servers[serverName]) ? cfg.mcp.servers[serverName] : {};
|
|
363
|
+
cfg.mcp.servers[serverName] = Object.assign({}, currentServer, generated);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
sanitizeToolConfig(cfg.tools);
|
|
368
|
+
if (cfg.agents && Array.isArray(cfg.agents.list)) {
|
|
369
|
+
cfg.agents.list.forEach(function(agent) {
|
|
370
|
+
if (isPlainObject(agent)) sanitizeToolConfig(agent.tools);
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return cfg;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function installMcpServers(pluginRoot, manifest, home, dryRun) {
|
|
378
|
+
var copied = [];
|
|
379
|
+
if (!isPlainObject(manifest.mcp_servers)) return copied;
|
|
380
|
+
Object.keys(manifest.mcp_servers).forEach(function(serverName) {
|
|
381
|
+
var server = manifest.mcp_servers[serverName];
|
|
382
|
+
if (!isPlainObject(server) || typeof server.script !== "string" || !server.script.trim()) return;
|
|
383
|
+
var src = path.join(pluginRoot, server.script);
|
|
384
|
+
var dst = path.join(home, "mcp-servers", path.basename(server.script));
|
|
385
|
+
ensureDir(path.dirname(dst), dryRun);
|
|
386
|
+
if (!dryRun) {
|
|
387
|
+
fs.copyFileSync(src, dst);
|
|
388
|
+
fs.chmodSync(dst, 0o755);
|
|
389
|
+
}
|
|
390
|
+
copied.push(dst);
|
|
391
|
+
});
|
|
392
|
+
return copied;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function clearSessions(home, dryRun) {
|
|
396
|
+
var cleared = [];
|
|
397
|
+
var agentsRoot = path.join(home, "agents");
|
|
398
|
+
try {
|
|
399
|
+
fs.readdirSync(agentsRoot).forEach(function(agentID) {
|
|
400
|
+
var sessionDir = path.join(agentsRoot, agentID, "sessions");
|
|
401
|
+
if (!fs.existsSync(sessionDir)) return;
|
|
402
|
+
fs.readdirSync(sessionDir).forEach(function(entry) {
|
|
403
|
+
if (!/\.jsonl$/.test(entry) && entry !== "sessions.json") return;
|
|
404
|
+
var full = path.join(sessionDir, entry);
|
|
405
|
+
if (!dryRun) fs.rmSync(full, { force: true });
|
|
406
|
+
cleared.push(full);
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
} catch (err) {}
|
|
410
|
+
return cleared;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function main() {
|
|
414
|
+
var args = parseArgs(process.argv.slice(2));
|
|
415
|
+
var pluginRoot = path.resolve(args.pluginRoot);
|
|
416
|
+
var home = path.resolve(args.home);
|
|
417
|
+
var manifestPath = path.join(pluginRoot, "openclaw-plugin.json");
|
|
418
|
+
if (!fs.existsSync(manifestPath)) {
|
|
419
|
+
throw new Error("missing openclaw-plugin.json at " + manifestPath);
|
|
420
|
+
}
|
|
421
|
+
var manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
422
|
+
var pluginName = normalizeRuntimeName(manifest.name || "plugin-deliverables");
|
|
423
|
+
var openclawPath = path.join(home, "openclaw.json");
|
|
424
|
+
var currentConfig = readJSON(openclawPath, {});
|
|
425
|
+
var workspaceRoots = collectWorkspaceRoots(home, currentConfig);
|
|
426
|
+
var agentIDs = collectAgentIDs(home, currentConfig);
|
|
427
|
+
var extensionsDir = path.join(home, "extensions", pluginName);
|
|
428
|
+
var extensionsExtraDir = path.join(home, "extensions-extra", pluginName);
|
|
429
|
+
|
|
430
|
+
ensureDir(home, args.dryRun);
|
|
431
|
+
replaceDirectory(pluginRoot, extensionsDir, args.dryRun);
|
|
432
|
+
replaceDirectory(pluginRoot, extensionsExtraDir, args.dryRun);
|
|
433
|
+
var mcpFiles = installMcpServers(pluginRoot, manifest, home, args.dryRun);
|
|
434
|
+
var skillFiles = installSkills(pluginRoot, manifest, home, workspaceRoots, agentIDs, args.dryRun);
|
|
435
|
+
var agentFiles = injectRules(pluginRoot, manifest, workspaceRoots, args.dryRun);
|
|
436
|
+
var nextConfig = applyPluginConfig(currentConfig, manifest, home);
|
|
437
|
+
writeJSON(openclawPath, nextConfig, args.dryRun);
|
|
438
|
+
var clearedSessions = args.clearSessions ? clearSessions(home, args.dryRun) : [];
|
|
439
|
+
|
|
440
|
+
var summary = {
|
|
441
|
+
plugin: pluginName,
|
|
442
|
+
version: manifest.version || "",
|
|
443
|
+
openclaw_home: home,
|
|
444
|
+
dry_run: args.dryRun,
|
|
445
|
+
copied_extensions: [extensionsDir, extensionsExtraDir],
|
|
446
|
+
mcp_servers: mcpFiles,
|
|
447
|
+
skill_files: skillFiles.length,
|
|
448
|
+
agents_files: agentFiles,
|
|
449
|
+
cleared_sessions: clearedSessions.length,
|
|
450
|
+
openclaw_json: openclawPath
|
|
451
|
+
};
|
|
452
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
453
|
+
if (!args.dryRun) {
|
|
454
|
+
console.log("Install complete. Wait about 15s for OpenClaw to reload plugins.allow, then send a new message to verify.");
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
main();
|
|
460
|
+
} catch (err) {
|
|
461
|
+
console.error("[plugin-deliverables] install failed:", err && err.stack ? err.stack : err);
|
|
462
|
+
process.exit(1);
|
|
463
|
+
}
|
package/openclaw-plugin.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "plugin-deliverables",
|
|
3
2
|
"name": "plugin-deliverables",
|
|
4
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
5
4
|
"npm_package": "@dai_ming/plugin-deliverables",
|
|
6
5
|
"description": "Deliverables plugin: MCP server + skill + AGENTS rules for AI-generated file uploads",
|
|
7
6
|
"mcp_servers": {
|
|
@@ -27,7 +26,9 @@
|
|
|
27
26
|
"openclaw_config": {
|
|
28
27
|
"plugins": {
|
|
29
28
|
"entries": {
|
|
30
|
-
"plugin-deliverables": {
|
|
29
|
+
"plugin-deliverables": {
|
|
30
|
+
"enabled": true
|
|
31
|
+
}
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dai_ming/plugin-deliverables",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "OpenClaw deliverables plugin — upload AI-generated files to OSS and return shareable preview/download links",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openclaw",
|
|
@@ -8,9 +8,12 @@
|
|
|
8
8
|
"deliverables",
|
|
9
9
|
"mcp"
|
|
10
10
|
],
|
|
11
|
+
"bin": {
|
|
12
|
+
"openclaw-deliverables-install": "./install.js"
|
|
13
|
+
},
|
|
11
14
|
"license": "MIT",
|
|
12
15
|
"files": [
|
|
13
|
-
"
|
|
16
|
+
"install.js",
|
|
14
17
|
"openclaw-plugin.json",
|
|
15
18
|
"mcp-servers/",
|
|
16
19
|
"skills/",
|
package/openclaw.plugin.json
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "plugin-deliverables",
|
|
3
|
-
"name": "plugin-deliverables",
|
|
4
|
-
"version": "1.0.3",
|
|
5
|
-
"npm_package": "@dai_ming/plugin-deliverables",
|
|
6
|
-
"description": "Deliverables plugin: MCP server + skill + AGENTS rules for AI-generated file uploads",
|
|
7
|
-
"mcp_servers": {
|
|
8
|
-
"deliverables": {
|
|
9
|
-
"script": "mcp-servers/deliverables.js",
|
|
10
|
-
"command": "node",
|
|
11
|
-
"env": {
|
|
12
|
-
"CLAW_GATEWAY_URL": "${CLAW_GATEWAY_URL}",
|
|
13
|
-
"CLAW_GATEWAY_PUBLIC_URL": "${CLAW_GATEWAY_PUBLIC_URL}",
|
|
14
|
-
"CLAW_GATEWAY_API_KEY": "${CLAW_GATEWAY_API_KEY}",
|
|
15
|
-
"OPENCLAW_GATEWAY_API_KEY": "${OPENCLAW_GATEWAY_API_KEY}"
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"skills": {
|
|
20
|
-
"deliverables": "skills/deliverables/SKILL.md"
|
|
21
|
-
},
|
|
22
|
-
"agents_rules": {
|
|
23
|
-
"file": "agents-rules/deliverables.md",
|
|
24
|
-
"start_marker": "DELIVERABLE_LINK_RULES_START",
|
|
25
|
-
"end_marker": "DELIVERABLE_AUTO_UPLOAD_RULES_END"
|
|
26
|
-
},
|
|
27
|
-
"openclaw_config": {
|
|
28
|
-
"plugins": {
|
|
29
|
-
"entries": {
|
|
30
|
-
"plugin-deliverables": { "enabled": true }
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|