@dai_ming/plugin-deliverables 1.0.4 → 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 CHANGED
@@ -1,214 +1,141 @@
1
1
  # @dai_ming/plugin-deliverables
2
2
 
3
- OpenClaw 交付物插件 AI Agent 将生成的文件(文章、HTML 页面、多文件游戏/网站、图片等)自动上传到 OSS,并在会话消息中回显可点击的预览/下载链接。
3
+ OpenClaw 交付物插件。安装后会把交付物 MCP、skill、AGENTS 规则和 `openclaw.json` 配置一次性落到运行目录里,让 Agent 默认把生成文件上传成可访问的交付物链接。
4
4
 
5
- ---
6
-
7
- ## 插件包含什么
5
+ ## 包内文件
8
6
 
9
7
  | 文件 | 作用 |
10
8
  |------|------|
11
- | `mcp-servers/deliverables.js` | MCP Server 脚本:实现 `upload_deliverable` 工具,通过 HTTP 调用 claw-gateway API 上传文件 |
12
- | `skills/deliverables/SKILL.md` | OpenClaw Skill:强制文档产出走 `upload_deliverable`,规范写入路径和参数 |
13
- | `agents-rules/deliverables.md` | AGENTS.md 规则块:URL 回显规则 + 自动上传规则(硬约束,注入到每个 workspace 的 AGENTS.md) |
14
- | `openclaw-plugin.json` | 插件清单:声明 MCP server、skill、AGENTS 规则、openclaw.json 配置段 |
15
-
16
- ---
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 和运行时配置 |
17
14
 
18
- ## 安装方式
15
+ ## 推荐安装方式
19
16
 
20
- ### 方式一:通过 claw-gateway Helm 部署(推荐)
17
+ ### 方式一:claw-gateway Helm 部署
21
18
 
22
- 只需在 Helm values 里把插件加入 `installPlugins` 列表,gateway 和 Helm initContainer 会自动完成所有配置:
19
+ 把插件加入 `installPlugins`:
23
20
 
24
21
  ```yaml
25
- # values.yaml(或 claw-gateway 管理界面的 Helm 参数)
26
22
  installPlugins:
27
- - "@dai_ming/plugin-deliverables@1.0.4"
23
+ - "@dai_ming/plugin-deliverables@1.0.5"
28
24
  ```
29
25
 
30
- initContainer 执行顺序:
31
- 1. **Phase 3**:`npm pack @dai_ming/plugin-deliverables@1.0.4` 下载 tarball → 解压到 `/data/extensions-extra/plugin-deliverables/`
32
- 2. **Phase 3b**:在插件目录执行 `npm install --omit=dev`(本插件无运行时依赖,此步骤跳过)
33
- 3. **Phase 3e**:读取 `openclaw-plugin.json` 清单,自动:
34
- - 复制 `mcp-servers/deliverables.js` → `/data/mcp-servers/deliverables.js`
35
- - 安装 `skills/deliverables/SKILL.md` → 所有 workspace 的 `skills/deliverables/SKILL.md`
36
- - 注入 `agents-rules/deliverables.md` 内容到所有 workspace 的 `AGENTS.md`(幂等,按 marker 替换)
37
-
38
- > **env 变量**:MCP Server 需要以下变量(claw-gateway 在生成 `mcp.servers` 配置时会自动注入):
39
- >
40
- > | 变量 | 说明 | 默认值 |
41
- > |------|------|--------|
42
- > | `CLAW_GATEWAY_URL` | gateway 内部地址 | `http://claw-gateway:8080` |
43
- > | `CLAW_GATEWAY_PUBLIC_URL` | gateway 公网地址(用于生成预览链接) | 同上 |
44
- > | `CLAW_GATEWAY_API_KEY` | API Key | `api-key-1` |
26
+ 现有 chart 会在 init 阶段完成安装。
45
27
 
46
- ### 方式二:手动安装(不使用 claw-gateway 自动部署)
28
+ ### 方式二:npm 安装后执行安装脚本
47
29
 
48
- 适用于自行管理 OpenClaw 容器的场景。
49
-
50
- **Step 1 — 下载并解压插件**
30
+ 这就是给运行中 pod / 自定义镜像准备的最小落地方式。
51
31
 
52
32
  ```bash
53
33
  npm config set registry https://registry.npmmirror.com
54
- mkdir -p ~/.openclaw/extensions-extra/plugin-deliverables
55
- cd /tmp && npm pack @dai_ming/plugin-deliverables
56
- 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
57
36
  ```
58
37
 
59
- **Step 2 — 复制 MCP Server 脚本**
38
+ 也可以直接用 bin:
60
39
 
61
40
  ```bash
62
- mkdir -p ~/.openclaw/mcp-servers
63
- cp ~/.openclaw/extensions-extra/plugin-deliverables/mcp-servers/deliverables.js \
64
- ~/.openclaw/mcp-servers/deliverables.js
41
+ npx openclaw-deliverables-install
65
42
  ```
66
43
 
67
- **Step 3 安装 Skill**
44
+ 如果 OpenClaw home 不在默认位置,可以显式传入:
68
45
 
69
46
  ```bash
70
- WORKSPACE=~/.openclaw/workspace # 替换为实际 workspace 路径
71
- mkdir -p "$WORKSPACE/skills/deliverables"
72
- cp ~/.openclaw/extensions-extra/plugin-deliverables/skills/deliverables/SKILL.md \
73
- "$WORKSPACE/skills/deliverables/SKILL.md"
47
+ node node_modules/@dai_ming/plugin-deliverables/install.js \
48
+ --home /home/node/.openclaw
74
49
  ```
75
50
 
76
- **Step 4 — 注入 AGENTS.md 规则**
77
-
78
- ```bash
79
- AGENTS="$WORKSPACE/AGENTS.md"
80
- RULES=~/.openclaw/extensions-extra/plugin-deliverables/agents-rules/deliverables.md
81
-
82
- if grep -q "DELIVERABLE_LINK_RULES_START" "$AGENTS" 2>/dev/null; then
83
- # 已有旧规则,用 Node.js 按 marker 替换
84
- node -e "
85
- var fs=require('fs');
86
- var a=fs.readFileSync('$AGENTS','utf8');
87
- var r=fs.readFileSync('$RULES','utf8').trim();
88
- var re=/<!--\s*DELIVERABLE_LINK_RULES_START\s*-->[\s\S]*?<!--\s*DELIVERABLE_AUTO_UPLOAD_RULES_END\s*-->/g;
89
- fs.writeFileSync('$AGENTS', re.test(a) ? a.replace(re,r) : a+'\n\n'+r+'\n');
90
- "
91
- else
92
- # 首次注入
93
- printf '\n\n' >> "$AGENTS"
94
- cat "$RULES" >> "$AGENTS"
95
- fi
96
- ```
97
-
98
- **Step 5 — 配置 openclaw.json**
99
-
100
- 在 `~/.openclaw/openclaw.json` 的 `mcp.servers` 下添加:
101
-
102
- ```json
103
- {
104
- "mcp": {
105
- "servers": {
106
- "deliverables": {
107
- "command": "node",
108
- "args": ["/home/node/.openclaw/mcp-servers/deliverables.js"],
109
- "env": {
110
- "CLAW_GATEWAY_URL": "http://<your-gateway-host>:8080",
111
- "CLAW_GATEWAY_PUBLIC_URL": "https://<public-domain>",
112
- "CLAW_GATEWAY_API_KEY": "<your-api-key>"
113
- }
114
- }
115
- }
116
- },
117
- "plugins": {
118
- "entries": {
119
- "deliverables": { "enabled": true }
120
- }
121
- }
122
- }
123
- ```
124
-
125
- ---
126
-
127
- ## AGENTS.md 规则说明
51
+ ## `install.js` 会做什么
128
52
 
129
- 插件向每个 workspace 的 `AGENTS.md` 注入两段硬约束规则:
53
+ 执行一次脚本后,会自动完成这些动作:
130
54
 
131
- | 规则段 | Marker | 作用 |
132
- |--------|--------|------|
133
- | URL 回显规则 | `DELIVERABLE_LINK_RULES_START` … `DELIVERABLE_LINK_RULES_END` | 强制 Agent 上传后在消息中输出可点击的 Markdown 链接 |
134
- | 自动上传规则 | `DELIVERABLE_AUTO_UPLOAD_RULES_START` `DELIVERABLE_AUTO_UPLOAD_RULES_END` | 当用户要求生成文件时默认自动上传,写入 `output/` 目录 |
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`
135
69
 
136
- 规则使用 HTML 注释 Marker 包裹,initContainer 和 Guardian 进程每次同步时都会幂等替换,不会产生重复块。
70
+ ## 是否需要重启 Pod
137
71
 
138
- ---
72
+ 不需要重启 Pod。
139
73
 
140
- ## Skill 说明
74
+ 原因:
75
+ - 安装脚本会直接改 `~/.openclaw/openclaw.json`
76
+ - `plugins.allow` 变化会触发 OpenClaw 的 watcher,进程级重载大约 15 秒
77
+ - session 也会被清掉,所以下一条消息会重新读取最新的 `AGENTS.md` / skill
141
78
 
142
- `skills/deliverables/SKILL.md` 是 OpenClaw Skill,被注入到 workspace 后由 Agent 在工具调用前读取。它规定:
79
+ 建议做法:
143
80
 
144
- - 始终使用会话中实际暴露的工具名(`deliverables__upload_deliverable`)
145
- - 生成文件统一写入 `output/` 子目录
146
- - 多文件(游戏/网站)优先用 `type=game` + `files[]`,不要提前打 zip
147
- - 上传成功后回复必须附带 Markdown 格式的预览/下载链接
148
-
149
- ---
150
-
151
- ## MCP Server 说明
81
+ ```bash
82
+ node node_modules/@dai_ming/plugin-deliverables/install.js
83
+ sleep 20
84
+ ```
152
85
 
153
- `mcp-servers/deliverables.js` 是一个符合 [MCP 协议(2024-11-05)](https://modelcontextprotocol.io/) 的 Node.js stdio server,暴露单个工具:
86
+ 然后再发一条新的用户消息测试。
154
87
 
155
- ### `upload_deliverable`
88
+ ## 常用参数
156
89
 
157
- | 参数 | 类型 | 必填 | 说明 |
158
- |------|------|------|------|
159
- | `resource_id` | string | ✅ | 当前会话唯一 ID |
160
- | `type` | enum | ✅ | `article` / `game` / `zip` / `image` / `video` / `ppt` / `link` |
161
- | `file_name` | string | ✅ | 用户可见文件名(含扩展名)或目录名 |
162
- | `group_id` | string | — | 群聊 ID |
163
- | `user_id` | string | — | 用户 ID |
164
- | `content_text` | string | — | 文本内容(单文件场景) |
165
- | `content_base64` | string | — | Base64 编码的二进制内容(单文件场景) |
166
- | `files` | array | — | 多文件列表(游戏/站点场景,优先使用) |
90
+ ```bash
91
+ node install.js --help
92
+ ```
167
93
 
168
- 返回值包含 `preview_url`、`download_url`、`reply_markdown`(可直接附在回复消息中)。
94
+ | 参数 | 说明 |
95
+ |------|------|
96
+ | `--home <dir>` | 指定 OpenClaw home,默认 `~/.openclaw` |
97
+ | `--plugin-root <dir>` | 指定插件源码目录,默认当前包目录 |
98
+ | `--no-clear-sessions` | 不清 session |
99
+ | `--dry-run` | 只打印计划,不落盘 |
169
100
 
170
- ---
101
+ ## 返回结果
171
102
 
172
- ## 版本与发布
103
+ 安装脚本成功后会输出一段 JSON 摘要,包含:
173
104
 
174
- ### claw-gateway 仓库内发布
105
+ - 目标 OpenClaw home
106
+ - 写入的 extension 目录
107
+ - 写入的 MCP 文件
108
+ - 修改的 `AGENTS.md`
109
+ - 清理的 session 数量
175
110
 
176
- 插件源码位于 `packages/plugin-deliverables/` 目录,与 claw-gateway 主仓库同步维护。更新流程:
111
+ ## 发布
177
112
 
178
113
  ```bash
179
- # 1. 修改 packages/plugin-deliverables/package.json 中的 version
180
- # 2. 同步更新 packages/plugin-deliverables/mcp-servers/deliverables.js(如有变更)
181
- # 3. 打包并发布到 npmmirror 兼容的私有或公开 registry
182
114
  cd packages/plugin-deliverables
183
115
  npm publish --registry https://registry.npmjs.org --access public
184
116
  ```
185
117
 
186
- ### 版本对齐建议
187
-
188
- | claw-gateway 版本 | plugin 版本 |
189
- |-------------------|-------------|
190
- | 当前 | 1.0.4 |
191
-
192
- ---
118
+ ## 排障
193
119
 
194
- ## 常见问题
120
+ ### Agent 没看到工具
195
121
 
196
- **Q: 插件安装后 Agent 没有 `upload_deliverable` 工具怎么办?**
122
+ 重点检查:
197
123
 
198
- 检查以下几点:
199
- 1. `openclaw.json` `mcp.servers.deliverables` 是否存在
200
- 2. MCP Server 是否在运行:`kubectl exec <pod> -- ls /home/node/.openclaw/mcp-servers/`
201
- 3. Agent 的 `tools.allow` 是否包含 `deliverables__upload_deliverable`
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 规则
202
128
 
203
- **Q: 上传后没有返回链接怎么办?**
129
+ ### 上传返回 401 / 404
204
130
 
205
- 确认 `CLAW_GATEWAY_URL` 和 `CLAW_GATEWAY_API_KEY` 注入正确,可在容器内测试:
131
+ 优先检查容器环境变量:
206
132
 
207
133
  ```bash
208
- curl -H "X-API-Key: $CLAW_GATEWAY_API_KEY" $CLAW_GATEWAY_URL/healthz
134
+ env | grep -E 'CLAW_GATEWAY|OPENCLAW_GATEWAY|botID'
209
135
  ```
210
136
 
211
- **Q: claw-gateway 已经内置了 deliverables,会和插件冲突吗?**
137
+ 以及直接请求 gateway
212
138
 
213
- 不会冲突。claw-gateway 通过 ConfigMap 注入的 MCP 脚本、skill、AGENTS 规则与插件内容完全一致。
214
- Phase 3e(插件注入)在前,ConfigMap 阶段(Phase 4/4c/4d)在后;ConfigMap 的内容会覆盖插件安装的内容,两者互为备份。
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
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugin-deliverables",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "npm_package": "@dai_ming/plugin-deliverables",
5
5
  "description": "Deliverables plugin: MCP server + skill + AGENTS rules for AI-generated file uploads",
6
6
  "mcp_servers": {
@@ -26,7 +26,9 @@
26
26
  "openclaw_config": {
27
27
  "plugins": {
28
28
  "entries": {
29
- "plugin-deliverables": {}
29
+ "plugin-deliverables": {
30
+ "enabled": true
31
+ }
30
32
  }
31
33
  }
32
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dai_ming/plugin-deliverables",
3
- "version": "1.0.4",
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,8 +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": [
16
+ "install.js",
13
17
  "openclaw-plugin.json",
14
18
  "mcp-servers/",
15
19
  "skills/",