@zshuangmu/agenthub 0.1.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/LICENSE +21 -0
- package/README.md +176 -0
- package/package.json +41 -0
- package/src/api-server.js +195 -0
- package/src/cli.js +343 -0
- package/src/commands/api.js +8 -0
- package/src/commands/info.js +6 -0
- package/src/commands/install.js +10 -0
- package/src/commands/list.js +91 -0
- package/src/commands/pack.js +151 -0
- package/src/commands/publish-remote.js +9 -0
- package/src/commands/publish.js +7 -0
- package/src/commands/rollback.js +64 -0
- package/src/commands/search.js +7 -0
- package/src/commands/serve.js +9 -0
- package/src/commands/stats.js +90 -0
- package/src/commands/update.js +68 -0
- package/src/commands/versions.js +63 -0
- package/src/commands/web.js +8 -0
- package/src/index.js +14 -0
- package/src/lib/bundle-transfer.js +58 -0
- package/src/lib/database.js +244 -0
- package/src/lib/download-stats.js +77 -0
- package/src/lib/fs-utils.js +46 -0
- package/src/lib/html.js +1730 -0
- package/src/lib/http.js +24 -0
- package/src/lib/install.js +14 -0
- package/src/lib/manifest.js +123 -0
- package/src/lib/openclaw-config.js +40 -0
- package/src/lib/registry.js +64 -0
- package/src/lib/security-scanner.js +233 -0
- package/src/lib/skill-md.js +17 -0
- package/src/server.js +158 -0
- package/src/web-server.js +138 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentHub CLI
|
|
3
|
+
* AI Agent 打包与分发平台
|
|
4
|
+
*
|
|
5
|
+
* 根据 PRD v1.1 规范实现
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
infoCommand,
|
|
10
|
+
installCommand,
|
|
11
|
+
packCommand,
|
|
12
|
+
publishCommand,
|
|
13
|
+
publishRemoteCommand,
|
|
14
|
+
serveCommand,
|
|
15
|
+
searchCommand,
|
|
16
|
+
apiCommand,
|
|
17
|
+
webCommand,
|
|
18
|
+
} from "./index.js";
|
|
19
|
+
|
|
20
|
+
const VERSION = "0.1.0";
|
|
21
|
+
|
|
22
|
+
function printHelp() {
|
|
23
|
+
console.log(`
|
|
24
|
+
AgentHub v${VERSION} - AI Agent 打包与分发平台
|
|
25
|
+
|
|
26
|
+
用法:
|
|
27
|
+
agenthub <command> [options]
|
|
28
|
+
|
|
29
|
+
命令:
|
|
30
|
+
pack 打包 OpenClaw 工作区为 Agent Bundle
|
|
31
|
+
publish 发布 Bundle 到本地 Registry
|
|
32
|
+
publish-remote 发布 Bundle 到远程服务器
|
|
33
|
+
install 安装 Agent 到目标工作区
|
|
34
|
+
search 搜索 Registry 中的 Agent
|
|
35
|
+
info 查看 Agent 详情
|
|
36
|
+
serve 启动 Web + API 服务
|
|
37
|
+
|
|
38
|
+
选项:
|
|
39
|
+
--help, -h 显示帮助信息
|
|
40
|
+
--version, -v 显示版本号
|
|
41
|
+
|
|
42
|
+
详细帮助:
|
|
43
|
+
agenthub <command> --help
|
|
44
|
+
|
|
45
|
+
示例:
|
|
46
|
+
# 打包 Agent
|
|
47
|
+
agenthub pack --workspace ./my-workspace --config openclaw.json --output ./bundles
|
|
48
|
+
|
|
49
|
+
# 发布 Agent
|
|
50
|
+
agenthub publish ./bundles/my-agent.agent --registry ./.registry
|
|
51
|
+
|
|
52
|
+
# 安装 Agent
|
|
53
|
+
agenthub install my-agent --registry ./.registry --target-workspace ./workspace
|
|
54
|
+
|
|
55
|
+
# 搜索 Agent
|
|
56
|
+
agenthub search "code review" --registry ./.registry
|
|
57
|
+
|
|
58
|
+
# 启动完整服务(前端+后端)
|
|
59
|
+
agenthub serve --registry ./.registry --port 3000
|
|
60
|
+
|
|
61
|
+
`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printCommandHelp(command) {
|
|
65
|
+
const helps = {
|
|
66
|
+
pack: `
|
|
67
|
+
agenthub pack - 打包 Agent
|
|
68
|
+
|
|
69
|
+
用法:
|
|
70
|
+
agenthub pack --workspace <dir> --config <file> --output <dir>
|
|
71
|
+
|
|
72
|
+
选项:
|
|
73
|
+
--workspace <dir> OpenClaw 工作区目录 (必需)
|
|
74
|
+
--config <file> openclaw.json 配置文件路径 (必需)
|
|
75
|
+
--output <dir> 输出目录 (默认: ./bundles)
|
|
76
|
+
--tags <tags> 标签,逗号分隔
|
|
77
|
+
--category <cat> 分类
|
|
78
|
+
|
|
79
|
+
示例:
|
|
80
|
+
agenthub pack --workspace ./my-agent --config ./openclaw.json
|
|
81
|
+
`,
|
|
82
|
+
publish: `
|
|
83
|
+
agenthub publish - 发布 Agent
|
|
84
|
+
|
|
85
|
+
用法:
|
|
86
|
+
agenthub publish <bundle-dir> --registry <dir>
|
|
87
|
+
|
|
88
|
+
选项:
|
|
89
|
+
--registry <dir> Registry 目录 (必需)
|
|
90
|
+
|
|
91
|
+
示例:
|
|
92
|
+
agenthub publish ./bundles/my-agent.agent --registry ./.registry
|
|
93
|
+
`,
|
|
94
|
+
install: `
|
|
95
|
+
agenthub install - 安装 Agent
|
|
96
|
+
|
|
97
|
+
用法:
|
|
98
|
+
agenthub install <agent-slug>[:version] --registry <dir> --target-workspace <dir>
|
|
99
|
+
|
|
100
|
+
选项:
|
|
101
|
+
--registry <dir> Registry 目录 (必需)
|
|
102
|
+
--target-workspace <dir> 目标工作区目录 (必需)
|
|
103
|
+
--force 强制覆盖
|
|
104
|
+
|
|
105
|
+
示例:
|
|
106
|
+
agenthub install code-review-assistant --registry ./.registry --target-workspace ./workspace
|
|
107
|
+
agenthub install my-agent:1.0.0 --registry ./.registry --target-workspace ./workspace
|
|
108
|
+
`,
|
|
109
|
+
search: `
|
|
110
|
+
agenthub search - 搜索 Agent
|
|
111
|
+
|
|
112
|
+
用法:
|
|
113
|
+
agenthub search <query> --registry <dir>
|
|
114
|
+
|
|
115
|
+
选项:
|
|
116
|
+
--registry <dir> Registry 目录 (必需)
|
|
117
|
+
|
|
118
|
+
示例:
|
|
119
|
+
agenthub search "code review" --registry ./.registry
|
|
120
|
+
agenthub search "" --registry ./.registry # 列出所有
|
|
121
|
+
`,
|
|
122
|
+
serve: `
|
|
123
|
+
agenthub serve - 启动完整服务(前端+后端)
|
|
124
|
+
|
|
125
|
+
用法:
|
|
126
|
+
agenthub serve --registry <dir> --port <port>
|
|
127
|
+
|
|
128
|
+
选项:
|
|
129
|
+
--registry <dir> Registry 目录 (必需)
|
|
130
|
+
--port <port> 前端端口号 (默认: 3000)
|
|
131
|
+
--api-port <port> 后端 API 端口号 (默认: 3001)
|
|
132
|
+
|
|
133
|
+
示例:
|
|
134
|
+
agenthub serve --registry ./.registry --port 3000
|
|
135
|
+
`,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
console.log(helps[command] || `未知命令: ${command}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function parseArgs(argv) {
|
|
142
|
+
const positionals = [];
|
|
143
|
+
const options = {};
|
|
144
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
145
|
+
const token = argv[index];
|
|
146
|
+
if (token === "--help" || token === "-h") {
|
|
147
|
+
options.help = true;
|
|
148
|
+
} else if (token === "--version" || token === "-v") {
|
|
149
|
+
options.version = true;
|
|
150
|
+
} else if (token.startsWith("--")) {
|
|
151
|
+
const rawKey = token.slice(2);
|
|
152
|
+
const camelKey = rawKey.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
153
|
+
const value = argv[index + 1];
|
|
154
|
+
if (value && !value.startsWith("-")) {
|
|
155
|
+
options[rawKey] = value;
|
|
156
|
+
options[camelKey] = value;
|
|
157
|
+
index += 1;
|
|
158
|
+
} else {
|
|
159
|
+
options[rawKey] = true;
|
|
160
|
+
options[camelKey] = true;
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
positionals.push(token);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return { positionals, options };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function main() {
|
|
170
|
+
const { positionals, options } = parseArgs(process.argv.slice(2));
|
|
171
|
+
const [command, ...rest] = positionals;
|
|
172
|
+
|
|
173
|
+
// 全局选项
|
|
174
|
+
if (options.version) {
|
|
175
|
+
console.log(`AgentHub v${VERSION}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!command) {
|
|
180
|
+
printHelp();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 命令帮助
|
|
185
|
+
if (options.help) {
|
|
186
|
+
printCommandHelp(command);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 命令路由
|
|
191
|
+
try {
|
|
192
|
+
switch (command) {
|
|
193
|
+
case "pack": {
|
|
194
|
+
if (!options.workspace || !options.config) {
|
|
195
|
+
console.error("错误: --workspace 和 --config 是必需的");
|
|
196
|
+
console.log("\n运行 'agenthub pack --help' 查看帮助");
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const result = await packCommand(options);
|
|
201
|
+
console.log(`✓ 打包完成: ${result.bundleDir}`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
case "publish": {
|
|
206
|
+
if (!rest[0]) {
|
|
207
|
+
console.error("错误: 需要指定 bundle 目录");
|
|
208
|
+
process.exitCode = 1;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const result = await publishCommand(rest[0], options);
|
|
212
|
+
console.log(`✓ 已发布 ${result.slug}@${result.version}`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
case "publish-remote": {
|
|
217
|
+
if (!rest[0]) {
|
|
218
|
+
console.error("错误: 需要指定 bundle 目录");
|
|
219
|
+
process.exitCode = 1;
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const result = await publishRemoteCommand(rest[0], options);
|
|
223
|
+
console.log(`✓ 已发布到远程 ${result.slug}@${result.version}`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
case "search": {
|
|
228
|
+
const results = await searchCommand(rest[0] || "", options);
|
|
229
|
+
if (results.length === 0) {
|
|
230
|
+
console.log("未找到匹配的 Agent");
|
|
231
|
+
} else {
|
|
232
|
+
console.log(`\n找到 ${results.length} 个 Agent:\n`);
|
|
233
|
+
for (const entry of results) {
|
|
234
|
+
console.log(` ${entry.slug}@${entry.version} - ${entry.description || ""}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
case "info": {
|
|
241
|
+
if (!rest[0]) {
|
|
242
|
+
console.error("错误: 需要指定 agent slug");
|
|
243
|
+
process.exitCode = 1;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const result = await infoCommand(rest[0], options);
|
|
247
|
+
console.log(`\n📦 ${result.name} (${result.slug}@${result.version})`);
|
|
248
|
+
console.log(` ${result.description}`);
|
|
249
|
+
console.log(` Runtime: ${result.runtime?.type || "openclaw"} ${result.runtime?.version || ""}`);
|
|
250
|
+
const mem = result.includes?.memory || {};
|
|
251
|
+
if (mem.count > 0) {
|
|
252
|
+
console.log(` Memory: ${mem.count} 条 (public: ${mem.public}, portable: ${mem.portable})`);
|
|
253
|
+
}
|
|
254
|
+
console.log(`\n 安装命令: agenthub install ${result.slug}`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
case "install": {
|
|
259
|
+
if (!rest[0]) {
|
|
260
|
+
console.error("错误: 需要指定 agent slug");
|
|
261
|
+
process.exitCode = 1;
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
console.log(`\n📥 正在安装 ${rest[0]}...\n`);
|
|
265
|
+
const result = await installCommand(rest[0], options);
|
|
266
|
+
console.log(`✓ 已安装 ${result.manifest.slug}@${result.manifest.version}`);
|
|
267
|
+
console.log(` 位置: ${options.targetWorkspace || "当前目录"}`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
case "serve": {
|
|
272
|
+
const result = await serveCommand(options);
|
|
273
|
+
console.log(`Server listening at ${result.baseUrl}`);
|
|
274
|
+
console.log(`\n🌐 AgentHub 服务已启动`);
|
|
275
|
+
console.log(` 地址: ${result.baseUrl}`);
|
|
276
|
+
console.log(` API: ${result.baseUrl}/api/agents`);
|
|
277
|
+
console.log(`\n按 Ctrl+C 停止服务\n`);
|
|
278
|
+
|
|
279
|
+
const shutdown = async () => {
|
|
280
|
+
process.off("SIGINT", shutdown);
|
|
281
|
+
process.off("SIGTERM", shutdown);
|
|
282
|
+
await result.close();
|
|
283
|
+
process.exit(0);
|
|
284
|
+
};
|
|
285
|
+
process.on("SIGINT", shutdown);
|
|
286
|
+
process.on("SIGTERM", shutdown);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
case "api": {
|
|
291
|
+
const port = options.port || "3001";
|
|
292
|
+
const result = await apiCommand({ ...options, port });
|
|
293
|
+
console.log(`Server listening at ${result.baseUrl}`);
|
|
294
|
+
console.log(`\n🔧 API 服务已启动`);
|
|
295
|
+
console.log(` 地址: ${result.baseUrl}`);
|
|
296
|
+
console.log(` 端点: ${result.baseUrl}/api/agents`);
|
|
297
|
+
console.log(` 统计: ${result.baseUrl}/api/stats`);
|
|
298
|
+
console.log(`\n按 Ctrl+C 停止服务\n`);
|
|
299
|
+
|
|
300
|
+
const shutdown = async () => {
|
|
301
|
+
process.off("SIGINT", shutdown);
|
|
302
|
+
process.off("SIGTERM", shutdown);
|
|
303
|
+
await result.close();
|
|
304
|
+
process.exit(0);
|
|
305
|
+
};
|
|
306
|
+
process.on("SIGINT", shutdown);
|
|
307
|
+
process.on("SIGTERM", shutdown);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
case "web": {
|
|
312
|
+
const port = options.port || "3000";
|
|
313
|
+
const apiBase = options.apiBase || "http://127.0.0.1:3001";
|
|
314
|
+
const result = await webCommand({ port, apiBase });
|
|
315
|
+
console.log(`Server listening at ${result.baseUrl}`);
|
|
316
|
+
console.log(`\n🌐 Web 服务已启动`);
|
|
317
|
+
console.log(` 地址: ${result.baseUrl}`);
|
|
318
|
+
console.log(` API: ${apiBase}`);
|
|
319
|
+
console.log(`\n按 Ctrl+C 停止服务\n`);
|
|
320
|
+
|
|
321
|
+
const shutdown = async () => {
|
|
322
|
+
process.off("SIGINT", shutdown);
|
|
323
|
+
process.off("SIGTERM", shutdown);
|
|
324
|
+
await result.close();
|
|
325
|
+
process.exit(0);
|
|
326
|
+
};
|
|
327
|
+
process.on("SIGINT", shutdown);
|
|
328
|
+
process.on("SIGTERM", shutdown);
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
default:
|
|
333
|
+
console.error(`未知命令: ${command}`);
|
|
334
|
+
console.log("\n运行 'agenthub --help' 查看可用命令");
|
|
335
|
+
process.exitCode = 1;
|
|
336
|
+
}
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error(`\n❌ 错误: ${error.message}`);
|
|
339
|
+
process.exitCode = 1;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
main();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createApiServer } from "../api-server.js";
|
|
2
|
+
|
|
3
|
+
export async function apiCommand(options) {
|
|
4
|
+
const registry = options.registry || "./.registry";
|
|
5
|
+
const port = parseInt(options.port || "3001", 10);
|
|
6
|
+
|
|
7
|
+
return createApiServer({ registryDir: registry, port });
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { installBundle } from "../lib/install.js";
|
|
3
|
+
|
|
4
|
+
export async function installCommand(agentSpec, options) {
|
|
5
|
+
return installBundle({
|
|
6
|
+
registryDir: path.resolve(options.registry),
|
|
7
|
+
agentSpec,
|
|
8
|
+
targetWorkspace: path.resolve(options.targetWorkspace),
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Command
|
|
3
|
+
* 列出已安装的 Agent
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { stat, readdir, readFile } from "node:fs/promises";
|
|
8
|
+
|
|
9
|
+
async function pathExists(targetPath) {
|
|
10
|
+
try {
|
|
11
|
+
await stat(targetPath);
|
|
12
|
+
return true;
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function readJson(filePath) {
|
|
19
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function listCommand(options = {}) {
|
|
23
|
+
// 检查多个可能的安装位置
|
|
24
|
+
const locations = [];
|
|
25
|
+
|
|
26
|
+
// 1. 当前目录的 .agenthub
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
const cwdAgenthub = path.join(cwd, ".agenthub");
|
|
29
|
+
if (await pathExists(cwdAgenthub)) {
|
|
30
|
+
const installs = await findInstallsInDir(cwd);
|
|
31
|
+
locations.push(...installs.map((i) => ({ ...i, location: cwd })));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 2. 如果指定了 target-workspace
|
|
35
|
+
if (options.targetWorkspace) {
|
|
36
|
+
const targetPath = path.resolve(options.targetWorkspace);
|
|
37
|
+
const installs = await findInstallsInDir(targetPath);
|
|
38
|
+
locations.push(...installs.map((i) => ({ ...i, location: targetPath })));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return locations;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function findInstallsInDir(dirPath) {
|
|
45
|
+
const installs = [];
|
|
46
|
+
const agenthubDir = path.join(dirPath, ".agenthub");
|
|
47
|
+
const installRecordPath = path.join(agenthubDir, "install.json");
|
|
48
|
+
|
|
49
|
+
if (await pathExists(installRecordPath)) {
|
|
50
|
+
try {
|
|
51
|
+
const record = await readJson(installRecordPath);
|
|
52
|
+
installs.push({
|
|
53
|
+
slug: record.slug,
|
|
54
|
+
version: record.version,
|
|
55
|
+
installedAt: record.installedAt || record.updatedAt,
|
|
56
|
+
rolledBackAt: record.rolledBackAt,
|
|
57
|
+
});
|
|
58
|
+
} catch {
|
|
59
|
+
// 无效的安装记录
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return installs;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function formatListOutput(installs) {
|
|
67
|
+
if (installs.length === 0) {
|
|
68
|
+
return "\n📭 暂无已安装的 Agent\n\n安装命令: agenthub install <agent-slug>\n";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const lines = [];
|
|
72
|
+
lines.push("\n📦 已安装的 Agent:\n");
|
|
73
|
+
lines.push("─".repeat(60));
|
|
74
|
+
|
|
75
|
+
for (const install of installs) {
|
|
76
|
+
const status = install.rolledBackAt ? "↩️ " : "✓ ";
|
|
77
|
+
lines.push(` ${status} ${install.slug}@${install.version}`);
|
|
78
|
+
if (install.location) {
|
|
79
|
+
lines.push(` 位置: ${install.location}`);
|
|
80
|
+
}
|
|
81
|
+
if (install.installedAt) {
|
|
82
|
+
lines.push(` 安装时间: ${install.installedAt}`);
|
|
83
|
+
}
|
|
84
|
+
lines.push("");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
lines.push("─".repeat(60));
|
|
88
|
+
lines.push(`\n共 ${installs.length} 个已安装的 Agent\n`);
|
|
89
|
+
|
|
90
|
+
return lines.join("\n");
|
|
91
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pack Command
|
|
3
|
+
* 打包 OpenClaw 工作区为 Agent Bundle
|
|
4
|
+
*
|
|
5
|
+
* 根据 PRD v1.1 Bundle 结构规范实现
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
10
|
+
import { countFiles, copyDir, ensureDir, readJson, writeJson, pathExists } from "../lib/fs-utils.js";
|
|
11
|
+
import { createManifest, validateManifest, generateBundleId } from "../lib/manifest.js";
|
|
12
|
+
import { extractOpenClawTemplate } from "../lib/openclaw-config.js";
|
|
13
|
+
|
|
14
|
+
const WORKSPACE_CORE_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "IDENTITY.md", "TOOLS.md", "HEARTBEAT.md", "BOOTSTRAP.md"];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 扫描工作区文件
|
|
18
|
+
*/
|
|
19
|
+
async function scanWorkspace(workspacePath) {
|
|
20
|
+
const files = {
|
|
21
|
+
core: [],
|
|
22
|
+
skills: [],
|
|
23
|
+
prompts: [],
|
|
24
|
+
canvas: false,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
for (const filename of WORKSPACE_CORE_FILES) {
|
|
28
|
+
const filePath = path.join(workspacePath, filename);
|
|
29
|
+
if (await pathExists(filePath)) {
|
|
30
|
+
files.core.push(filename);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 检查 skills 目录
|
|
35
|
+
const skillsPath = path.join(workspacePath, "skills");
|
|
36
|
+
if (await pathExists(skillsPath)) {
|
|
37
|
+
const entries = await readdir(skillsPath, { withFileTypes: true });
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
if (entry.isDirectory()) {
|
|
40
|
+
files.skills.push(entry.name);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 检查 PROMPTS 目录
|
|
46
|
+
const promptsPath = path.join(workspacePath, "PROMPTS");
|
|
47
|
+
if (await pathExists(promptsPath)) {
|
|
48
|
+
const entries = await readdir(promptsPath, { withFileTypes: true });
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (entry.isFile() && entry.name.endsWith(".txt")) {
|
|
51
|
+
files.prompts.push(entry.name);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 检查 canvas 目录
|
|
57
|
+
const canvasPath = path.join(workspacePath, "canvas");
|
|
58
|
+
files.canvas = await pathExists(canvasPath);
|
|
59
|
+
|
|
60
|
+
return files;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Pack 命令主函数
|
|
65
|
+
*/
|
|
66
|
+
export async function packCommand(options) {
|
|
67
|
+
const workspace = path.resolve(options.workspace || process.cwd());
|
|
68
|
+
const output = path.resolve(options.output ?? "./bundles");
|
|
69
|
+
const configPath = options.config ? path.resolve(options.config) : null;
|
|
70
|
+
|
|
71
|
+
// 从工作区名称生成 slug
|
|
72
|
+
const slug = path.basename(workspace).toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
73
|
+
const bundleDir = path.join(output, `${slug}-1.0.0.agent`);
|
|
74
|
+
|
|
75
|
+
// 1. 扫描工作区
|
|
76
|
+
const workspaceFiles = await scanWorkspace(workspace);
|
|
77
|
+
|
|
78
|
+
// 2. 统计 Memory 分层
|
|
79
|
+
const memoryCounts = {
|
|
80
|
+
public: await countFiles(path.join(workspace, "memory", "public")),
|
|
81
|
+
portable: await countFiles(path.join(workspace, "memory", "portable")),
|
|
82
|
+
private: await countFiles(path.join(workspace, "memory", "private")),
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// 3. 读取并处理 OpenClaw 配置
|
|
86
|
+
let template = {};
|
|
87
|
+
if (configPath && (await pathExists(configPath))) {
|
|
88
|
+
const config = await readJson(configPath);
|
|
89
|
+
template = extractOpenClawTemplate(config);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 4. 生成 MANIFEST
|
|
93
|
+
const manifest = createManifest({
|
|
94
|
+
slug,
|
|
95
|
+
name: path.basename(workspace),
|
|
96
|
+
memoryCounts,
|
|
97
|
+
openclawTemplate: template,
|
|
98
|
+
skills: workspaceFiles.skills,
|
|
99
|
+
prompts: workspaceFiles.prompts,
|
|
100
|
+
tags: options.tags ? options.tags.split(",").map((t) => t.trim()) : [],
|
|
101
|
+
category: options.category,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// 5. 验证 MANIFEST
|
|
105
|
+
const validation = validateManifest(manifest);
|
|
106
|
+
if (!validation.valid) {
|
|
107
|
+
throw new Error(`Invalid manifest: ${validation.errors.join(", ")}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 6. 创建 Bundle 目录结构
|
|
111
|
+
await ensureDir(bundleDir);
|
|
112
|
+
|
|
113
|
+
// 6.1 复制 WORKSPACE
|
|
114
|
+
await copyDir(workspace, path.join(bundleDir, "WORKSPACE"));
|
|
115
|
+
|
|
116
|
+
// 6.2 写入 OPENCLAW.template.json
|
|
117
|
+
await writeJson(path.join(bundleDir, "OPENCLAW.template.json"), template);
|
|
118
|
+
|
|
119
|
+
// 6.3 写入 MANIFEST.json
|
|
120
|
+
await writeJson(path.join(bundleDir, "MANIFEST.json"), manifest);
|
|
121
|
+
|
|
122
|
+
// 6.4 生成 README.md
|
|
123
|
+
const readme = `# ${manifest.name}
|
|
124
|
+
|
|
125
|
+
${manifest.description}
|
|
126
|
+
|
|
127
|
+
## 安装
|
|
128
|
+
|
|
129
|
+
\`\`\`bash
|
|
130
|
+
agenthub install ${manifest.slug}
|
|
131
|
+
\`\`\`
|
|
132
|
+
|
|
133
|
+
## 包含内容
|
|
134
|
+
|
|
135
|
+
- **记忆**: ${manifest.includes.memory.count} 条
|
|
136
|
+
- **技能**: ${manifest.includes.skills.join(", ") || "无"}
|
|
137
|
+
- **提示词**: ${manifest.includes.prompts} 个
|
|
138
|
+
|
|
139
|
+
## 要求
|
|
140
|
+
|
|
141
|
+
- 运行时: OpenClaw ${manifest.runtime.version}
|
|
142
|
+
`;
|
|
143
|
+
await writeFile(path.join(bundleDir, "README.md"), readme, "utf8");
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
bundleDir,
|
|
147
|
+
manifest,
|
|
148
|
+
validation,
|
|
149
|
+
bundleId: generateBundleId(manifest.slug, manifest.version),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { publishRemoteBundle } from "../lib/bundle-transfer.js";
|
|
3
|
+
|
|
4
|
+
export async function publishRemoteCommand(bundleDir, options) {
|
|
5
|
+
return publishRemoteBundle({
|
|
6
|
+
bundleDir: path.resolve(bundleDir),
|
|
7
|
+
serverUrl: options.server,
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rollback Command
|
|
3
|
+
* 回滚 Agent 到指定版本
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { pathExists, readJson, writeJson } from "../lib/fs-utils.js";
|
|
8
|
+
import { installBundle } from "../lib/install.js";
|
|
9
|
+
import { versionsCommand } from "./versions.js";
|
|
10
|
+
|
|
11
|
+
export async function rollbackCommand(agentSpec, options) {
|
|
12
|
+
const registryDir = path.resolve(options.registry);
|
|
13
|
+
const targetWorkspace = options.targetWorkspace ? path.resolve(options.targetWorkspace) : null;
|
|
14
|
+
const targetVersion = options.to;
|
|
15
|
+
|
|
16
|
+
if (!targetVersion) {
|
|
17
|
+
throw new Error("请指定回滚版本: --to <version>");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const [slug] = agentSpec.split(":");
|
|
21
|
+
|
|
22
|
+
// 验证目标版本存在
|
|
23
|
+
const versions = await versionsCommand(slug, { registry: registryDir });
|
|
24
|
+
const versionExists = versions.some((v) => v.version === targetVersion);
|
|
25
|
+
if (!versionExists) {
|
|
26
|
+
throw new Error(`版本 ${targetVersion} 不存在`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 获取当前版本
|
|
30
|
+
let currentVersion = null;
|
|
31
|
+
if (targetWorkspace) {
|
|
32
|
+
const installRecordPath = path.join(targetWorkspace, ".agenthub", "install.json");
|
|
33
|
+
if (await pathExists(installRecordPath)) {
|
|
34
|
+
const record = await readJson(installRecordPath);
|
|
35
|
+
currentVersion = record.version;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 执行回滚
|
|
40
|
+
const result = await installBundle({
|
|
41
|
+
registryDir,
|
|
42
|
+
agentSpec: `${slug}:${targetVersion}`,
|
|
43
|
+
targetWorkspace,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// 记录回滚
|
|
47
|
+
if (targetWorkspace) {
|
|
48
|
+
const installRecordPath = path.join(targetWorkspace, ".agenthub", "install.json");
|
|
49
|
+
await writeJson(installRecordPath, {
|
|
50
|
+
slug,
|
|
51
|
+
version: targetVersion,
|
|
52
|
+
rolledBackAt: new Date().toISOString(),
|
|
53
|
+
rolledBackFrom: currentVersion,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
rolledBack: true,
|
|
59
|
+
message: `已回滚 ${slug} 从 ${currentVersion || "未知"} 到 ${targetVersion}`,
|
|
60
|
+
currentVersion: targetVersion,
|
|
61
|
+
previousVersion: currentVersion,
|
|
62
|
+
manifest: result.manifest,
|
|
63
|
+
};
|
|
64
|
+
}
|