lightclawbot 1.0.7 → 1.0.9-beta.0
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 +47 -213
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/src/channel.d.ts +1 -1
- package/dist/src/channel.d.ts.map +1 -1
- package/dist/src/channel.js +1 -1
- package/dist/src/channel.js.map +1 -1
- package/dist/src/config.d.ts +16 -6
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +18 -5
- package/dist/src/config.js.map +1 -1
- package/dist/src/download-tool.d.ts +2 -2
- package/dist/src/download-tool.d.ts.map +1 -1
- package/dist/src/download-tool.js +9 -9
- package/dist/src/download-tool.js.map +1 -1
- package/dist/src/file-storage.d.ts +9 -9
- package/dist/src/file-storage.d.ts.map +1 -1
- package/dist/src/file-storage.js +10 -10
- package/dist/src/file-storage.js.map +1 -1
- package/dist/src/format-urls.d.ts +5 -5
- package/dist/src/format-urls.js +8 -8
- package/dist/src/format-urls.js.map +1 -1
- package/dist/src/gateway.d.ts.map +1 -1
- package/dist/src/gateway.js +18 -14
- package/dist/src/gateway.js.map +1 -1
- package/dist/src/inbound.d.ts.map +1 -1
- package/dist/src/inbound.js +20 -21
- package/dist/src/inbound.js.map +1 -1
- package/dist/src/outbound.d.ts +1 -1
- package/dist/src/outbound.d.ts.map +1 -1
- package/dist/src/outbound.js +15 -3
- package/dist/src/outbound.js.map +1 -1
- package/dist/src/runtime.d.ts +1 -1
- package/dist/src/runtime.d.ts.map +1 -1
- package/dist/src/socket/handlers.d.ts +26 -0
- package/dist/src/socket/handlers.d.ts.map +1 -0
- package/dist/src/socket/handlers.js +130 -0
- package/dist/src/socket/handlers.js.map +1 -0
- package/dist/src/socket/index.d.ts +11 -0
- package/dist/src/socket/index.d.ts.map +1 -0
- package/dist/src/socket/index.js +9 -0
- package/dist/src/socket/index.js.map +1 -0
- package/dist/src/socket/registry.d.ts +59 -0
- package/dist/src/socket/registry.d.ts.map +1 -0
- package/dist/src/socket/registry.js +125 -0
- package/dist/src/socket/registry.js.map +1 -0
- package/dist/src/socket/reliable-emitter.d.ts +79 -0
- package/dist/src/socket/reliable-emitter.d.ts.map +1 -0
- package/dist/src/socket/reliable-emitter.js +217 -0
- package/dist/src/socket/reliable-emitter.js.map +1 -0
- package/dist/src/types.d.ts +7 -2
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/upload-tool.d.ts +2 -2
- package/dist/src/upload-tool.d.ts.map +1 -1
- package/dist/src/upload-tool.js +3 -3
- package/dist/src/upload-tool.js.map +1 -1
- package/node_modules/socket.io-parser/build/cjs/index.d.ts +14 -4
- package/node_modules/socket.io-parser/build/cjs/index.js +14 -6
- package/node_modules/socket.io-parser/build/esm/index.d.ts +14 -4
- package/node_modules/socket.io-parser/build/esm/index.js +14 -6
- package/node_modules/socket.io-parser/build/esm-debug/index.d.ts +14 -4
- package/node_modules/socket.io-parser/build/esm-debug/index.js +14 -6
- package/node_modules/socket.io-parser/package.json +1 -1
- package/package.json +6 -11
- package/skills/lightclaw-media/SKILL.md +22 -102
- package/dist/public/data/scripts/manifest.json +0 -11
- package/dist/public/data/scripts/upgrade.211d7e4c.sh +0 -266
- package/dist/public/data/scripts/upgrade.sh +0 -266
- package/dist/src/session-history.d.ts +0 -88
- package/dist/src/session-history.d.ts.map +0 -1
- package/dist/src/session-history.js +0 -598
- package/dist/src/session-history.js.map +0 -1
- package/dist/src/streaming/delta-tracker.d.ts +0 -34
- package/dist/src/streaming/delta-tracker.d.ts.map +0 -1
- package/dist/src/streaming/delta-tracker.js +0 -145
- package/dist/src/streaming/delta-tracker.js.map +0 -1
- package/dist/src/streaming/index.d.ts +0 -12
- package/dist/src/streaming/index.d.ts.map +0 -1
- package/dist/src/streaming/index.js +0 -13
- package/dist/src/streaming/index.js.map +0 -1
- package/dist/src/streaming/stream-reply-sink.d.ts +0 -59
- package/dist/src/streaming/stream-reply-sink.d.ts.map +0 -1
- package/dist/src/streaming/stream-reply-sink.js +0 -293
- package/dist/src/streaming/stream-reply-sink.js.map +0 -1
- package/dist/src/streaming/types.d.ts +0 -45
- package/dist/src/streaming/types.d.ts.map +0 -1
- package/dist/src/streaming/types.js +0 -7
- package/dist/src/streaming/types.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lightclawbot-media
|
|
3
|
-
description: LightClawBot 文件收发能力。用户发来的文件自动下载并保存,AI 生成的文件通过 lightclaw_upload_file 上传后以标准 Markdown
|
|
3
|
+
description: LightClawBot 文件收发能力。用户发来的文件自动下载并保存,AI 生成的文件通过 lightclaw_upload_file 上传后以标准 Markdown 链接返回给用户。禁止使用其他存储工具。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# LightClawBot 文件上传与下载
|
|
@@ -11,23 +11,21 @@ description: LightClawBot 文件收发能力。用户发来的文件自动下载
|
|
|
11
11
|
|
|
12
12
|
## ⛔ 最重要的规则(读三遍)
|
|
13
13
|
|
|
14
|
-
> 1. **上传文件必须使用 `lightclaw_upload_file`
|
|
15
|
-
> 2. **获取文件下载链接必须使用 `lightclaw_get_file_url` 工具!**
|
|
14
|
+
> 1. **上传文件必须使用 `lightclaw_upload_file` 工具,禁止使用其他任何存储工具!**
|
|
16
15
|
> 3. **返回文件给用户时,必须使用标准 Markdown 链接格式:`[文件名](下载链接)`**
|
|
17
16
|
> - ❌ 错误:`下载链接: https://xxx`
|
|
18
17
|
> - ❌ 错误:`📎 文件下载链接: https://xxx`
|
|
19
|
-
> - ✅ 正确:`[report.pdf](https://lightai.
|
|
18
|
+
> - ✅ 正确:`[report.pdf](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/report.pdf)`
|
|
20
19
|
|
|
21
20
|
---
|
|
22
21
|
|
|
23
22
|
## 🔧 可用工具
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
本技能使用以下两个专属工具,**不要使用其他任何文件/存储工具**:
|
|
26
25
|
|
|
27
26
|
| 工具名 | 用途 | 何时使用 |
|
|
28
27
|
|--------|------|----------|
|
|
29
28
|
| `lightclaw_upload_file` | 上传本地文件到云端,获取公网下载链接 | AI 生成了文件需要分享给用户时 |
|
|
30
|
-
| `lightclaw_get_file_url` | 获取已上传文件的下载链接 / 下载文件到本地 / 上传单文件并获取链接 | 需要查询或操作已有云端文件时 |
|
|
31
29
|
|
|
32
30
|
---
|
|
33
31
|
|
|
@@ -79,59 +77,6 @@ description: LightClawBot 文件收发能力。用户发来的文件自动下载
|
|
|
79
77
|
|
|
80
78
|
---
|
|
81
79
|
|
|
82
|
-
## 📥 获取文件链接 / 下载文件(lightclaw_get_file_url)
|
|
83
|
-
|
|
84
|
-
### 功能
|
|
85
|
-
|
|
86
|
-
提供三种操作:
|
|
87
|
-
- `get_url`:获取已上传到云端的文件的公网下载链接
|
|
88
|
-
- `download_to_local`:将云端文件下载到本地目录
|
|
89
|
-
- `upload_and_get_url`:上传单个本地文件并返回公网下载链接
|
|
90
|
-
|
|
91
|
-
### 参数
|
|
92
|
-
|
|
93
|
-
```json
|
|
94
|
-
{
|
|
95
|
-
"action": "get_url",
|
|
96
|
-
"filePath": "2026-03-15/report.pdf"
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
| 参数 | 类型 | 必填 | 说明 |
|
|
101
|
-
|------|------|------|------|
|
|
102
|
-
| `action` | string | ✅ | `"get_url"` / `"download_to_local"` / `"upload_and_get_url"` |
|
|
103
|
-
| `filePath` | string | ✅ | `get_url`/`download_to_local` 时为云端文件路径;`upload_and_get_url` 时为本地绝对路径 |
|
|
104
|
-
| `localDir` | string | ❌ | 仅 `download_to_local` 使用,指定本地保存目录,默认当前工作目录 |
|
|
105
|
-
|
|
106
|
-
### 各 action 使用示例
|
|
107
|
-
|
|
108
|
-
**获取下载链接**:
|
|
109
|
-
```json
|
|
110
|
-
{
|
|
111
|
-
"action": "get_url",
|
|
112
|
-
"filePath": "2026-03-15/report.pdf"
|
|
113
|
-
}
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
**下载文件到本地**:
|
|
117
|
-
```json
|
|
118
|
-
{
|
|
119
|
-
"action": "download_to_local",
|
|
120
|
-
"filePath": "2026-03-15/report.pdf",
|
|
121
|
-
"localDir": "/tmp/downloads"
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
**上传单文件并获取链接**:
|
|
126
|
-
```json
|
|
127
|
-
{
|
|
128
|
-
"action": "upload_and_get_url",
|
|
129
|
-
"filePath": "/Users/xxx/.openclaw/workspace/output.csv"
|
|
130
|
-
}
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
80
|
## 📋 返回文件给用户的格式(最重要)
|
|
136
81
|
|
|
137
82
|
### 🚨🚨🚨 必须使用标准 Markdown 链接格式
|
|
@@ -149,7 +94,7 @@ description: LightClawBot 文件收发能力。用户发来的文件自动下载
|
|
|
149
94
|
```
|
|
150
95
|
好的,报告已生成,请点击下载:
|
|
151
96
|
|
|
152
|
-
[report.pdf](https://lightai.
|
|
97
|
+
[report.pdf](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/report.pdf)
|
|
153
98
|
```
|
|
154
99
|
|
|
155
100
|
**多个文件**:
|
|
@@ -157,9 +102,9 @@ description: LightClawBot 文件收发能力。用户发来的文件自动下载
|
|
|
157
102
|
```
|
|
158
103
|
所有文件已准备好:
|
|
159
104
|
|
|
160
|
-
- [数据分析报告.pdf](https://lightai.
|
|
161
|
-
- [原始数据.xlsx](https://lightai.
|
|
162
|
-
- [趋势图.png](https://lightai.
|
|
105
|
+
- [数据分析报告.pdf](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/数据分析报告.pdf)
|
|
106
|
+
- [原始数据.xlsx](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/原始数据.xlsx)
|
|
107
|
+
- [趋势图.png](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/趋势图.png)
|
|
163
108
|
```
|
|
164
109
|
|
|
165
110
|
**附带说明文字**:
|
|
@@ -167,7 +112,7 @@ description: LightClawBot 文件收发能力。用户发来的文件自动下载
|
|
|
167
112
|
```
|
|
168
113
|
✅ 周报已生成完毕!
|
|
169
114
|
|
|
170
|
-
[本周工作周报.pdf](https://lightai.
|
|
115
|
+
[本周工作周报.pdf](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/本周工作周报.pdf)
|
|
171
116
|
|
|
172
117
|
报告包含了本周的进度汇总和下周计划。
|
|
173
118
|
```
|
|
@@ -176,22 +121,22 @@ description: LightClawBot 文件收发能力。用户发来的文件自动下载
|
|
|
176
121
|
|
|
177
122
|
❌ **裸链接**:
|
|
178
123
|
```
|
|
179
|
-
下载链接: https://lightai.
|
|
124
|
+
下载链接: https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/report.pdf
|
|
180
125
|
```
|
|
181
126
|
|
|
182
127
|
❌ **带 emoji 前缀的裸链接**:
|
|
183
128
|
```
|
|
184
|
-
📎 文件下载链接: https://lightai.
|
|
129
|
+
📎 文件下载链接: https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/report.pdf
|
|
185
130
|
```
|
|
186
131
|
|
|
187
132
|
❌ **使用代码块包裹链接**:
|
|
188
133
|
```
|
|
189
|
-
`https://lightai.
|
|
134
|
+
`https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/report.pdf`
|
|
190
135
|
```
|
|
191
136
|
|
|
192
137
|
❌ **只返回路径,没有文件名**:
|
|
193
138
|
```
|
|
194
|
-
[下载](https://lightai.
|
|
139
|
+
[下载](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/report.pdf)
|
|
195
140
|
```
|
|
196
141
|
|
|
197
142
|
---
|
|
@@ -204,7 +149,7 @@ description: LightClawBot 文件收发能力。用户发来的文件自动下载
|
|
|
204
149
|
|------|------|
|
|
205
150
|
| `name` | 文件名 |
|
|
206
151
|
| `mimeType` | 文件 MIME 类型 |
|
|
207
|
-
| `url` |
|
|
152
|
+
| `url` | 文件的公网下载链接 |
|
|
208
153
|
|
|
209
154
|
本地文件路径在上下文的 `MediaPath` / `MediaPaths` 字段中。
|
|
210
155
|
|
|
@@ -232,7 +177,7 @@ description: LightClawBot 文件收发能力。用户发来的文件自动下载
|
|
|
232
177
|
```
|
|
233
178
|
✅ 报告已生成:
|
|
234
179
|
|
|
235
|
-
[数据分析报告.pdf](https://lightai.
|
|
180
|
+
[数据分析报告.pdf](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/数据分析报告.pdf)
|
|
236
181
|
```
|
|
237
182
|
|
|
238
183
|
### 场景 2:用户要求处理发来的文件
|
|
@@ -247,7 +192,7 @@ description: LightClawBot 文件收发能力。用户发来的文件自动下载
|
|
|
247
192
|
```
|
|
248
193
|
✅ 已将 Excel 转为 PDF:
|
|
249
194
|
|
|
250
|
-
[data.pdf](https://lightai.
|
|
195
|
+
[data.pdf](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/data.pdf)
|
|
251
196
|
```
|
|
252
197
|
|
|
253
198
|
### 场景 3:用户要求获取之前上传的文件
|
|
@@ -255,15 +200,11 @@ description: LightClawBot 文件收发能力。用户发来的文件自动下载
|
|
|
255
200
|
**用户**:我昨天上传的报告还能下载吗?
|
|
256
201
|
|
|
257
202
|
**AI 执行步骤**:
|
|
258
|
-
1.
|
|
259
|
-
```json
|
|
260
|
-
{ "action": "get_url", "filePath": "2026-03-14/report.pdf" }
|
|
261
|
-
```
|
|
262
|
-
2. 以 Markdown 格式回复:
|
|
203
|
+
1. 以 Markdown 格式回复:
|
|
263
204
|
```
|
|
264
205
|
可以的,这是下载链接:
|
|
265
206
|
|
|
266
|
-
[report.pdf](https://lightai.
|
|
207
|
+
[report.pdf](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-14/report.pdf)
|
|
267
208
|
```
|
|
268
209
|
|
|
269
210
|
### 场景 4:批量上传多个文件
|
|
@@ -286,32 +227,14 @@ description: LightClawBot 文件收发能力。用户发来的文件自动下载
|
|
|
286
227
|
```
|
|
287
228
|
✅ 三张图表已生成:
|
|
288
229
|
|
|
289
|
-
- [chart1.png](https://lightai.
|
|
290
|
-
- [chart2.png](https://lightai.
|
|
291
|
-
- [chart3.png](https://lightai.
|
|
230
|
+
- [chart1.png](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/chart1.png)
|
|
231
|
+
- [chart2.png](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/chart2.png)
|
|
232
|
+
- [chart3.png](https://lightai.cloud.tencent.com/drive/preview?filePath=2026-03-15/chart3.png)
|
|
292
233
|
```
|
|
293
234
|
|
|
294
|
-
### 场景 5:下载云端文件到本地处理
|
|
295
|
-
|
|
296
|
-
**用户**:帮我把之前上传的 CSV 数据做个汇总
|
|
297
|
-
|
|
298
|
-
**AI 执行步骤**:
|
|
299
|
-
1. 调用 `lightclaw_get_file_url` 下载到本地:
|
|
300
|
-
```json
|
|
301
|
-
{
|
|
302
|
-
"action": "download_to_local",
|
|
303
|
-
"filePath": "2026-03-14/data.csv",
|
|
304
|
-
"localDir": "/tmp/workspace"
|
|
305
|
-
}
|
|
306
|
-
```
|
|
307
|
-
2. 读取本地文件进行数据处理
|
|
308
|
-
3. 生成汇总报告,上传并以 Markdown 链接返回
|
|
309
|
-
|
|
310
|
-
---
|
|
311
|
-
|
|
312
235
|
## 🚫 禁止事项
|
|
313
236
|
|
|
314
|
-
1. **❌
|
|
237
|
+
1. **❌ 禁止使用其他存储工具**:所有文件操作必须通过 `lightclaw_upload_file`
|
|
315
238
|
2. **❌ 禁止返回裸链接**:必须使用 `[文件名](链接)` 格式
|
|
316
239
|
3. **❌ 禁止说"无法发送文件"**:你有能力上传和分享任何本地文件
|
|
317
240
|
4. **❌ 禁止使用相对路径**:工具参数中的文件路径必须是绝对路径
|
|
@@ -324,7 +247,4 @@ description: LightClawBot 文件收发能力。用户发来的文件自动下载
|
|
|
324
247
|
| 场景 | 工具 | 回复格式 |
|
|
325
248
|
|------|------|----------|
|
|
326
249
|
| 上传生成的文件 | `lightclaw_upload_file` | `[文件名](下载链接)` |
|
|
327
|
-
| 获取已上传文件链接 | `lightclaw_get_file_url`(action: get_url) | `[文件名](下载链接)` |
|
|
328
|
-
| 下载云端文件到本地 | `lightclaw_get_file_url`(action: download_to_local) | 告知下载完成和本地路径 |
|
|
329
|
-
| 上传单文件获取链接 | `lightclaw_get_file_url`(action: upload_and_get_url) | `[文件名](下载链接)` |
|
|
330
250
|
| 接收用户文件 | 自动处理,查看上下文 Attachments | 确认收到并告知文件信息 |
|
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -e
|
|
3
|
-
|
|
4
|
-
# ========== lightclawbot 插件快速安装/升级脚本 ==========
|
|
5
|
-
# 用途:跳过 openclaw plugins install 的慢路径,直接安装/升级 lightclawbot 插件
|
|
6
|
-
# 前提:openclaw 已安装且可用,pnpm 或 npm 已安装(不依赖 jq,JSON 操作统一用 node)
|
|
7
|
-
# 支持:首次安装 / 重装 / 升级,自动保留已有的 channels 配置
|
|
8
|
-
#
|
|
9
|
-
# 输出规范:stdout 只输出一行 RESULT:{...} JSON,供前端解析
|
|
10
|
-
# - 成功:RESULT:{"status":"ok","version":"x.y.z"}
|
|
11
|
-
# - 失败:RESULT:{"status":"error","reason":"..."}
|
|
12
|
-
#
|
|
13
|
-
# 使用方式:
|
|
14
|
-
# 本地执行: bash scripts/upgrade.sh
|
|
15
|
-
# CDN 执行: bash <(curl -fsSL https://your-cdn.com/upgrade.sh)
|
|
16
|
-
# 传入参数: APIKEY="sk-xxx" bash <(curl -fsSL https://your-cdn.com/upgrade.sh)
|
|
17
|
-
|
|
18
|
-
# ========== 工具函数 ==========
|
|
19
|
-
|
|
20
|
-
result_exit() {
|
|
21
|
-
local reason="$1"
|
|
22
|
-
echo "RESULT:{\"status\":\"error\",\"reason\":\"${reason}\"}"
|
|
23
|
-
exit 1
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
result_ok() {
|
|
27
|
-
echo "RESULT:{\"status\":\"ok\",\"version\":\"${1}\"}"
|
|
28
|
-
exit 0
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
# ========== 配置 ==========
|
|
32
|
-
|
|
33
|
-
id="lightclawbot"
|
|
34
|
-
spec="lightclawbot"
|
|
35
|
-
cfg="$HOME/.openclaw/openclaw.json"
|
|
36
|
-
plugin_dir="$HOME/.openclaw/extensions/${id}"
|
|
37
|
-
|
|
38
|
-
# npm 源:使用腾讯镜像源(可通过 NPM_REGISTRY 环境变量覆盖)
|
|
39
|
-
NPM_REGISTRY="${NPM_REGISTRY:-https://mirrors.tencent.com/npm/}"
|
|
40
|
-
# fallback 源:首选失败时兜底(可通过 NPM_FALLBACK 环境变量覆盖)
|
|
41
|
-
NPM_FALLBACK="${NPM_FALLBACK:-https://registry.npmjs.org}"
|
|
42
|
-
|
|
43
|
-
# API Key:优先读取环境变量,为空则保留配置文件中已有的值
|
|
44
|
-
# 用法:APIKEY="sk-xxx" bash <(curl -fsSL https://your-cdn.com/upgrade.sh)
|
|
45
|
-
APIKEY="${APIKEY:-}"
|
|
46
|
-
|
|
47
|
-
# ========== 环境加载 ==========
|
|
48
|
-
# bash xxx.sh 启动的是非交互式 shell,不会自动 source ~/.bashrc,
|
|
49
|
-
# 如果用户通过 nvm 安装 node,脚本中的 node/pnpm/openclaw 都找不到。
|
|
50
|
-
|
|
51
|
-
export NVM_DIR="$HOME/.nvm"
|
|
52
|
-
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
|
53
|
-
|
|
54
|
-
if [ -z "$PNPM_HOME" ] && [ -d "$HOME/.local/share/pnpm" ]; then
|
|
55
|
-
export PNPM_HOME="$HOME/.local/share/pnpm"
|
|
56
|
-
export PATH="$PNPM_HOME:$PATH"
|
|
57
|
-
fi
|
|
58
|
-
|
|
59
|
-
[ ! -t 1 ] && export CI=true
|
|
60
|
-
|
|
61
|
-
# ========== 前置检查 ==========
|
|
62
|
-
|
|
63
|
-
# 1) 探测包管理器:优先 pnpm,fallback npm,都没有则报错
|
|
64
|
-
if command -v pnpm &>/dev/null; then
|
|
65
|
-
PKG_MGR="pnpm"
|
|
66
|
-
elif command -v npm &>/dev/null; then
|
|
67
|
-
PKG_MGR="npm"
|
|
68
|
-
else
|
|
69
|
-
result_exit "npm_not_found"
|
|
70
|
-
fi
|
|
71
|
-
|
|
72
|
-
# 2) 检查 openclaw 是否可用
|
|
73
|
-
if ! openclaw -v &>/dev/null; then
|
|
74
|
-
result_exit "openclaw_not_available"
|
|
75
|
-
fi
|
|
76
|
-
|
|
77
|
-
# 3) 设置 npm/pnpm 源(确保后续 pack/install 都走镜像源)
|
|
78
|
-
npm config set registry "$NPM_REGISTRY" 2>/dev/null || true
|
|
79
|
-
[ "$PKG_MGR" = "pnpm" ] && pnpm config set registry "$NPM_REGISTRY" 2>/dev/null || true
|
|
80
|
-
|
|
81
|
-
# 4) 检查 APIKEY
|
|
82
|
-
if [ -z "$APIKEY" ]; then
|
|
83
|
-
result_exit "apikey_empty"
|
|
84
|
-
fi
|
|
85
|
-
|
|
86
|
-
# ========== 安装 / 升级 lightclawbot ==========
|
|
87
|
-
|
|
88
|
-
# ---------- 检查是否已安装,获取本地版本 ----------
|
|
89
|
-
local_version=""
|
|
90
|
-
if [ -d "$plugin_dir" ] && [ -f "$plugin_dir/package.json" ]; then
|
|
91
|
-
local_version=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync(process.argv[1],'utf8')).version||'')}catch(e){console.log('')}" "$plugin_dir/package.json" 2>/dev/null || true)
|
|
92
|
-
fi
|
|
93
|
-
|
|
94
|
-
# ---------- 查询远程最新版本 ----------
|
|
95
|
-
remote_version=$(npm view "$spec" version 2>/dev/null || true)
|
|
96
|
-
|
|
97
|
-
if [ -z "$remote_version" ]; then
|
|
98
|
-
remote_version=$(npm view "$spec" version --registry "$NPM_FALLBACK" 2>/dev/null || true)
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
# ---------- 判断安装模式 ----------
|
|
102
|
-
install_mode="install"
|
|
103
|
-
if [ -n "$local_version" ]; then
|
|
104
|
-
if [ -n "$remote_version" ] && [ "$local_version" = "$remote_version" ]; then
|
|
105
|
-
install_mode="up-to-date"
|
|
106
|
-
else
|
|
107
|
-
install_mode="upgrade"
|
|
108
|
-
fi
|
|
109
|
-
fi
|
|
110
|
-
|
|
111
|
-
if [ "$install_mode" != "up-to-date" ]; then
|
|
112
|
-
# ---------- 清理旧版本 ----------
|
|
113
|
-
rm -rf "$plugin_dir"
|
|
114
|
-
|
|
115
|
-
# ---------- npm pack 下载 tgz ----------
|
|
116
|
-
tmp_dir=$(mktemp -d /tmp/openclaw-plugin-XXXXXX)
|
|
117
|
-
|
|
118
|
-
tgz_file=$(cd "$tmp_dir" && npm pack "$spec" 2>/dev/null | tail -n1)
|
|
119
|
-
if [ -z "$tgz_file" ] || [ ! -f "$tmp_dir/$tgz_file" ]; then
|
|
120
|
-
tgz_file=$(cd "$tmp_dir" && npm pack "$spec" --registry "$NPM_FALLBACK" 2>/dev/null | tail -n1)
|
|
121
|
-
fi
|
|
122
|
-
if [ -z "$tgz_file" ] || [ ! -f "$tmp_dir/$tgz_file" ]; then
|
|
123
|
-
rm -rf "$tmp_dir"
|
|
124
|
-
result_exit "download_failed"
|
|
125
|
-
fi
|
|
126
|
-
|
|
127
|
-
# ---------- 解压到插件目录 ----------
|
|
128
|
-
mkdir -p "$plugin_dir"
|
|
129
|
-
tar -xzf "$tmp_dir/$tgz_file" -C "$plugin_dir" --strip-components=1
|
|
130
|
-
rm -rf "$tmp_dir"
|
|
131
|
-
|
|
132
|
-
# ---------- 安装生产依赖 ----------
|
|
133
|
-
# lightclawbot 声明了 bundledDependencies,npm pack 产出的 tgz 已包含所有依赖
|
|
134
|
-
has_unbundled_deps=$(node -e '
|
|
135
|
-
const pkg = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
|
|
136
|
-
const deps = Object.keys(pkg.dependencies || {});
|
|
137
|
-
const bundled = pkg.bundledDependencies || pkg.bundleDependencies || [];
|
|
138
|
-
console.log(deps.length > 0 && bundled.length < deps.length ? "true" : "false");
|
|
139
|
-
' "$plugin_dir/package.json")
|
|
140
|
-
|
|
141
|
-
if [ "$has_unbundled_deps" = true ]; then
|
|
142
|
-
(cd "$plugin_dir" && CI=true $PKG_MGR install --prod) >/dev/null 2>&1
|
|
143
|
-
fi
|
|
144
|
-
fi
|
|
145
|
-
|
|
146
|
-
# ========== 更新配置文件 ==========
|
|
147
|
-
|
|
148
|
-
# 确保配置文件存在
|
|
149
|
-
if [ ! -f "$cfg" ]; then
|
|
150
|
-
mkdir -p "$(dirname "$cfg")"
|
|
151
|
-
echo '{}' > "$cfg"
|
|
152
|
-
fi
|
|
153
|
-
|
|
154
|
-
# 校验配置文件是合法 JSON(用户可能手动编辑弄坏了格式)
|
|
155
|
-
if ! node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))" "$cfg" 2>/dev/null; then
|
|
156
|
-
result_exit "config_json_invalid"
|
|
157
|
-
fi
|
|
158
|
-
|
|
159
|
-
# ---------- 单次 node 调用完成所有配置更新 ----------
|
|
160
|
-
# 一次启动 V8,通过环境变量安全传参,
|
|
161
|
-
# 顺序完成 plugins.entries / installs / deny / allow / channels 全部配置修改,
|
|
162
|
-
# 减少文件 I/O 次数(只读一次 + 写一次)。
|
|
163
|
-
|
|
164
|
-
version=$(UPGRADE_CFG="$cfg" UPGRADE_ID="$id" UPGRADE_SPEC="$spec" \
|
|
165
|
-
UPGRADE_PLUGIN_DIR="$plugin_dir" UPGRADE_APIKEY="$APIKEY" \
|
|
166
|
-
node -e '
|
|
167
|
-
const fs = require("fs");
|
|
168
|
-
const cfgPath = process.env.UPGRADE_CFG;
|
|
169
|
-
const pluginId = process.env.UPGRADE_ID;
|
|
170
|
-
const spec = process.env.UPGRADE_SPEC;
|
|
171
|
-
const pluginDir = process.env.UPGRADE_PLUGIN_DIR;
|
|
172
|
-
const envApiKey = process.env.UPGRADE_APIKEY || "";
|
|
173
|
-
|
|
174
|
-
function fail(reason) {
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// 读取配置
|
|
179
|
-
let cfg;
|
|
180
|
-
try {
|
|
181
|
-
cfg = JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
182
|
-
} catch (e) {
|
|
183
|
-
fail("config_read_failed");
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// 读取插件版本
|
|
187
|
-
let version = "unknown";
|
|
188
|
-
try {
|
|
189
|
-
const pkg = JSON.parse(fs.readFileSync(pluginDir + "/package.json", "utf8"));
|
|
190
|
-
version = pkg.version || "unknown";
|
|
191
|
-
} catch (e) {}
|
|
192
|
-
|
|
193
|
-
// ---------- 1) 确保 plugins 基础结构存在 & 全局插件系统启用 ----------
|
|
194
|
-
if (!cfg.plugins) cfg.plugins = {};
|
|
195
|
-
// 确保全局插件系统生效(plugins.enabled 默认 true,仅显式 false 时需修复)
|
|
196
|
-
if (cfg.plugins.enabled === false) {
|
|
197
|
-
cfg.plugins.enabled = true;
|
|
198
|
-
}
|
|
199
|
-
if (!cfg.plugins.entries) cfg.plugins.entries = {};
|
|
200
|
-
if (!cfg.plugins.installs) cfg.plugins.installs = {};
|
|
201
|
-
|
|
202
|
-
// plugins.entries:启用插件
|
|
203
|
-
cfg.plugins.entries[pluginId] = { enabled: true };
|
|
204
|
-
|
|
205
|
-
// plugins.installs:安装记录
|
|
206
|
-
cfg.plugins.installs[pluginId] = {
|
|
207
|
-
source: "npm",
|
|
208
|
-
spec: spec,
|
|
209
|
-
installPath: pluginDir,
|
|
210
|
-
version: version,
|
|
211
|
-
installedAt: new Date().toISOString().replace(/\.\d{3}Z$/, ".000Z")
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
// ---------- 2) 黑名单修复 ----------
|
|
215
|
-
if (Array.isArray(cfg.plugins.deny)) {
|
|
216
|
-
const idx = cfg.plugins.deny.indexOf(pluginId);
|
|
217
|
-
if (idx !== -1) {
|
|
218
|
-
cfg.plugins.deny.splice(idx, 1);
|
|
219
|
-
if (cfg.plugins.deny.length === 0) {
|
|
220
|
-
delete cfg.plugins.deny;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// ---------- 3) 白名单添加 ----------
|
|
226
|
-
if (Array.isArray(cfg.plugins.allow)) {
|
|
227
|
-
if (!cfg.plugins.allow.includes(pluginId)) {
|
|
228
|
-
cfg.plugins.allow.push(pluginId);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// ---------- 4) channel 配置:智能合并 ----------
|
|
233
|
-
if (!cfg.channels) cfg.channels = {};
|
|
234
|
-
const ch = cfg.channels[pluginId] || {};
|
|
235
|
-
|
|
236
|
-
// 迁移旧的 apiKey -> apiKeys
|
|
237
|
-
let apiKeys = Array.isArray(ch.apiKeys) ? [...ch.apiKeys] : [];
|
|
238
|
-
if (ch.apiKey && typeof ch.apiKey === "string") {
|
|
239
|
-
if (!apiKeys.includes(ch.apiKey)) {
|
|
240
|
-
apiKeys.push(ch.apiKey);
|
|
241
|
-
}
|
|
242
|
-
delete ch.apiKey;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// 如果通过环境变量传入了 APIKEY,确保它在 apiKeys 数组中(去重)
|
|
246
|
-
if (envApiKey && !apiKeys.includes(envApiKey)) {
|
|
247
|
-
apiKeys.push(envApiKey);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
ch.enabled = true;
|
|
251
|
-
ch.apiKeys = apiKeys;
|
|
252
|
-
cfg.channels[pluginId] = ch;
|
|
253
|
-
|
|
254
|
-
// ---------- 5) 写回配置文件 ----------
|
|
255
|
-
fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
256
|
-
|
|
257
|
-
// 输出版本号到 stdout,供 shell 接收
|
|
258
|
-
process.stdout.write(version);
|
|
259
|
-
') || result_exit "config_update_failed"
|
|
260
|
-
|
|
261
|
-
# ========== 重启 Gateway ==========
|
|
262
|
-
|
|
263
|
-
openclaw gateway restart >/dev/null 2>&1 || true
|
|
264
|
-
|
|
265
|
-
# stdout 只输出这一行 RESULT,供前端解析
|
|
266
|
-
result_ok "$version"
|