@zshuangmu/agenthub 0.1.6 → 0.1.8
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/package.json +1 -1
- package/src/api-server.js +29 -1
- package/src/cli.js +103 -83
- package/src/commands/info.js +12 -3
- package/src/commands/list.js +1 -14
- package/src/commands/rollback.js +12 -27
- package/src/commands/search.js +10 -3
- package/src/commands/stats.js +43 -28
- package/src/commands/update.js +7 -30
- package/src/commands/verify.js +94 -0
- package/src/commands/versions.js +25 -13
- package/src/index.js +2 -0
- package/src/lib/colors.js +60 -0
- package/src/lib/http.js +144 -0
- package/src/lib/install.js +5 -156
- package/src/lib/registry.js +32 -1
- package/src/lib/remote.js +11 -0
- package/src/lib/version-manager.js +77 -0
- package/src/server.js +3 -20
- package/src/web-server.js +8 -11
- package/src/lib/download-stats.js +0 -77
- package/src/lib/skill-md.js +0 -17
package/package.json
CHANGED
package/src/api-server.js
CHANGED
|
@@ -23,6 +23,30 @@ const RATE_LIMIT_MAX = parseInt(process.env.RATE_LIMIT_MAX || "100", 10); // 100
|
|
|
23
23
|
|
|
24
24
|
// 简单的速率限制器
|
|
25
25
|
const rateLimiter = new Map();
|
|
26
|
+
let cleanupInterval = null;
|
|
27
|
+
|
|
28
|
+
function startRateLimiterCleanup() {
|
|
29
|
+
if (cleanupInterval) return;
|
|
30
|
+
cleanupInterval = setInterval(() => {
|
|
31
|
+
const now = Date.now();
|
|
32
|
+
const windowStart = now - RATE_LIMIT_WINDOW;
|
|
33
|
+
for (const [ip, requests] of rateLimiter.entries()) {
|
|
34
|
+
const recentRequests = requests.filter(t => t > windowStart);
|
|
35
|
+
if (recentRequests.length === 0) {
|
|
36
|
+
rateLimiter.delete(ip);
|
|
37
|
+
} else if (recentRequests.length !== requests.length) {
|
|
38
|
+
rateLimiter.set(ip, recentRequests);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}, RATE_LIMIT_WINDOW);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function stopRateLimiterCleanup() {
|
|
45
|
+
if (cleanupInterval) {
|
|
46
|
+
clearInterval(cleanupInterval);
|
|
47
|
+
cleanupInterval = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
26
50
|
|
|
27
51
|
function checkRateLimit(ip) {
|
|
28
52
|
const now = Date.now();
|
|
@@ -203,6 +227,7 @@ export async function createApiServer({ registryDir, port = 3000 }) {
|
|
|
203
227
|
}
|
|
204
228
|
});
|
|
205
229
|
|
|
230
|
+
startRateLimiterCleanup();
|
|
206
231
|
await new Promise((resolve) => server.listen(port, "0.0.0.0", resolve));
|
|
207
232
|
const address = server.address();
|
|
208
233
|
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
@@ -211,6 +236,9 @@ export async function createApiServer({ registryDir, port = 3000 }) {
|
|
|
211
236
|
server,
|
|
212
237
|
port: actualPort,
|
|
213
238
|
baseUrl: `http://127.0.0.1:${actualPort}`,
|
|
214
|
-
close: () => new Promise((resolve, reject) =>
|
|
239
|
+
close: () => new Promise((resolve, reject) => {
|
|
240
|
+
stopRateLimiterCleanup();
|
|
241
|
+
server.close((error) => (error ? reject(error) : resolve()));
|
|
242
|
+
}),
|
|
215
243
|
};
|
|
216
244
|
}
|
package/src/cli.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import {
|
|
10
10
|
infoCommand,
|
|
11
11
|
installCommand,
|
|
12
|
+
verifyCommand,
|
|
13
|
+
formatVerifyOutput,
|
|
12
14
|
packCommand,
|
|
13
15
|
publishCommand,
|
|
14
16
|
publishRemoteCommand,
|
|
@@ -26,7 +28,26 @@ import {
|
|
|
26
28
|
formatVersionsOutput,
|
|
27
29
|
} from "./index.js";
|
|
28
30
|
|
|
29
|
-
|
|
31
|
+
import { success, error, warning, info as infoColor, highlight, muted, symbols } from "./lib/colors.js";
|
|
32
|
+
|
|
33
|
+
import { createRequire } from "node:module";
|
|
34
|
+
const require = createRequire(import.meta.url);
|
|
35
|
+
const VERSION = require("../package.json").version;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 验证必需参数,如果缺失则打印错误并设置退出码
|
|
39
|
+
* @param {string} arg - 要验证的参数
|
|
40
|
+
* @param {string} message - 错误消息
|
|
41
|
+
* @returns {boolean} 参数是否存在
|
|
42
|
+
*/
|
|
43
|
+
function requireArg(arg, message) {
|
|
44
|
+
if (!arg) {
|
|
45
|
+
console.error(error(message));
|
|
46
|
+
process.exitCode = 1;
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
30
51
|
|
|
31
52
|
function printHelp() {
|
|
32
53
|
console.log(`
|
|
@@ -43,6 +64,7 @@ AgentHub v${VERSION} - AI Agent 打包与分发平台
|
|
|
43
64
|
search 搜索 Registry 中的 Agent
|
|
44
65
|
info 查看 Agent 详情
|
|
45
66
|
list 列出当前目录/指定目录的已安装 Agent
|
|
67
|
+
verify 校验已安装 Agent 是否完整可用
|
|
46
68
|
versions 查看 Agent 版本历史
|
|
47
69
|
update 更新已安装 Agent 到最新版
|
|
48
70
|
rollback 回滚已安装 Agent 到指定版本
|
|
@@ -147,45 +169,66 @@ agenthub list - 列出已安装 Agent
|
|
|
147
169
|
示例:
|
|
148
170
|
agenthub list
|
|
149
171
|
agenthub list --target-workspace ./my-workspace
|
|
172
|
+
`,
|
|
173
|
+
verify: `
|
|
174
|
+
agenthub verify - 校验已安装 Agent
|
|
175
|
+
|
|
176
|
+
用法:
|
|
177
|
+
agenthub verify [agent-slug] [--registry <dir> | --server <url>] [--target-workspace <dir>]
|
|
178
|
+
|
|
179
|
+
选项:
|
|
180
|
+
--registry <dir> 本地 Registry 目录(与 --server 二选一)
|
|
181
|
+
--server <url> 远程服务器地址(默认: https://agenthub.cyou)
|
|
182
|
+
--target-workspace <dir> 目标工作区目录(默认当前目录)
|
|
183
|
+
|
|
184
|
+
示例:
|
|
185
|
+
agenthub verify workspace --registry ./.registry --target-workspace ./my-workspace
|
|
186
|
+
agenthub verify workspace --server https://agenthub.cyou --target-workspace ./my-workspace
|
|
150
187
|
`,
|
|
151
188
|
versions: `
|
|
152
189
|
agenthub versions - 查看版本历史
|
|
153
190
|
|
|
154
191
|
用法:
|
|
155
|
-
agenthub versions <agent-slug> --registry <dir>
|
|
192
|
+
agenthub versions <agent-slug> [--registry <dir> | --server <url>]
|
|
156
193
|
|
|
157
194
|
选项:
|
|
158
|
-
--registry <dir>
|
|
195
|
+
--registry <dir> 本地 Registry 目录(与 --server 二选一)
|
|
196
|
+
--server <url> 远程服务器地址(默认: https://agenthub.cyou)
|
|
159
197
|
|
|
160
198
|
示例:
|
|
161
199
|
agenthub versions workspace --registry ./.registry
|
|
200
|
+
agenthub versions workspace --server https://agenthub.cyou
|
|
162
201
|
`,
|
|
163
202
|
update: `
|
|
164
203
|
agenthub update - 更新 Agent 到最新版
|
|
165
204
|
|
|
166
205
|
用法:
|
|
167
|
-
agenthub update <agent-slug> --registry <dir> --target-workspace <dir>
|
|
206
|
+
agenthub update <agent-slug> [--registry <dir> | --server <url>] --target-workspace <dir>
|
|
168
207
|
|
|
169
208
|
选项:
|
|
170
|
-
--registry <dir> Registry
|
|
209
|
+
--registry <dir> 本地 Registry 目录(与 --server 二选一)
|
|
210
|
+
--server <url> 远程服务器地址(默认: https://agenthub.cyou)
|
|
171
211
|
--target-workspace <dir> 目标工作区目录 (必需)
|
|
172
212
|
|
|
173
213
|
示例:
|
|
174
214
|
agenthub update workspace --registry ./.registry --target-workspace ./my-workspace
|
|
215
|
+
agenthub update workspace --server https://agenthub.cyou --target-workspace ./my-workspace
|
|
175
216
|
`,
|
|
176
217
|
rollback: `
|
|
177
218
|
agenthub rollback - 回滚 Agent 到指定版本
|
|
178
219
|
|
|
179
220
|
用法:
|
|
180
|
-
agenthub rollback <agent-slug> --to <version> --registry <dir> --target-workspace <dir>
|
|
221
|
+
agenthub rollback <agent-slug> --to <version> [--registry <dir> | --server <url>] --target-workspace <dir>
|
|
181
222
|
|
|
182
223
|
选项:
|
|
183
224
|
--to <version> 目标版本 (必需)
|
|
184
|
-
--registry <dir> Registry
|
|
225
|
+
--registry <dir> 本地 Registry 目录(与 --server 二选一)
|
|
226
|
+
--server <url> 远程服务器地址(默认: https://agenthub.cyou)
|
|
185
227
|
--target-workspace <dir> 目标工作区目录 (必需)
|
|
186
228
|
|
|
187
229
|
示例:
|
|
188
230
|
agenthub rollback workspace --to 1.0.0 --registry ./.registry --target-workspace ./my-workspace
|
|
231
|
+
agenthub rollback workspace --to 1.0.0 --server https://agenthub.cyou --target-workspace ./my-workspace
|
|
189
232
|
`,
|
|
190
233
|
stats: `
|
|
191
234
|
agenthub stats - 查看 Agent 统计信息
|
|
@@ -272,79 +315,63 @@ async function main() {
|
|
|
272
315
|
switch (command) {
|
|
273
316
|
case "pack": {
|
|
274
317
|
if (!options.workspace || !options.config) {
|
|
275
|
-
console.error("错误: --workspace 和 --config 是必需的");
|
|
276
|
-
console.log("\n运行 'agenthub pack --help' 查看帮助");
|
|
318
|
+
console.error(error("错误: --workspace 和 --config 是必需的"));
|
|
319
|
+
console.log(muted("\n运行 'agenthub pack --help' 查看帮助"));
|
|
277
320
|
process.exitCode = 1;
|
|
278
321
|
return;
|
|
279
322
|
}
|
|
280
323
|
const result = await packCommand(options);
|
|
281
|
-
console.log(
|
|
324
|
+
console.log(success(`${symbols.success} 打包完成: ${result.bundleDir}`));
|
|
282
325
|
return;
|
|
283
326
|
}
|
|
284
327
|
|
|
285
328
|
case "publish": {
|
|
286
|
-
if (!rest[0])
|
|
287
|
-
console.error("错误: 需要指定 bundle 目录");
|
|
288
|
-
process.exitCode = 1;
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
329
|
+
if (!requireArg(rest[0], "错误: 需要指定 bundle 目录")) return;
|
|
291
330
|
const result = await publishCommand(rest[0], options);
|
|
292
|
-
console.log(
|
|
331
|
+
console.log(success(`${symbols.success} 已发布 ${highlight(`${result.slug}@${result.version}`)}`));
|
|
293
332
|
return;
|
|
294
333
|
}
|
|
295
334
|
|
|
296
335
|
case "publish-remote": {
|
|
297
|
-
if (!rest[0])
|
|
298
|
-
console.error("错误: 需要指定 bundle 目录");
|
|
299
|
-
process.exitCode = 1;
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
336
|
+
if (!requireArg(rest[0], "错误: 需要指定 bundle 目录")) return;
|
|
302
337
|
const result = await publishRemoteCommand(rest[0], options);
|
|
303
|
-
console.log(
|
|
338
|
+
console.log(success(`${symbols.success} 已发布到远程 ${highlight(`${result.slug}@${result.version}`)}`));
|
|
304
339
|
return;
|
|
305
340
|
}
|
|
306
341
|
|
|
307
342
|
case "search": {
|
|
308
343
|
const results = await searchCommand(rest[0] || "", options);
|
|
309
344
|
if (results.length === 0) {
|
|
310
|
-
console.log("未找到匹配的 Agent");
|
|
345
|
+
console.log(warning("未找到匹配的 Agent"));
|
|
311
346
|
} else {
|
|
312
|
-
console.log(`\n
|
|
347
|
+
console.log(`\n${infoColor(`找到 ${results.length} 个 Agent:`)}\n`);
|
|
313
348
|
for (const entry of results) {
|
|
314
|
-
console.log(` ${entry.slug}
|
|
349
|
+
console.log(` ${highlight(entry.slug)}${muted("@")}${entry.version} ${muted("-")} ${entry.description || ""}`);
|
|
315
350
|
}
|
|
316
351
|
}
|
|
317
352
|
return;
|
|
318
353
|
}
|
|
319
354
|
|
|
320
355
|
case "info": {
|
|
321
|
-
if (!rest[0])
|
|
322
|
-
console.error("错误: 需要指定 agent slug");
|
|
323
|
-
process.exitCode = 1;
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
356
|
+
if (!requireArg(rest[0], "错误: 需要指定 agent slug")) return;
|
|
326
357
|
const result = await infoCommand(rest[0], options);
|
|
327
|
-
console.log(`\n📦 ${result.name} (${result.slug}@${result.version})`);
|
|
358
|
+
console.log(`\n${highlight("📦")} ${result.name} (${result.slug}@${result.version})`);
|
|
328
359
|
console.log(` ${result.description}`);
|
|
329
|
-
console.log(` Runtime: ${result.runtime?.type || "openclaw"} ${result.runtime?.version || ""}`);
|
|
360
|
+
console.log(` ${muted("Runtime:")} ${result.runtime?.type || "openclaw"} ${result.runtime?.version || ""}`);
|
|
330
361
|
const mem = result.includes?.memory || {};
|
|
331
362
|
if (mem.count > 0) {
|
|
332
|
-
console.log(` Memory: ${mem.count} 条 (public: ${mem.public}, portable: ${mem.portable})`);
|
|
363
|
+
console.log(` ${muted("Memory:")} ${mem.count} 条 (public: ${mem.public}, portable: ${mem.portable})`);
|
|
333
364
|
}
|
|
334
|
-
console.log(`\n 安装命令: agenthub install ${result.slug}`);
|
|
365
|
+
console.log(`\n ${infoColor("安装命令:")} agenthub install ${result.slug}`);
|
|
335
366
|
return;
|
|
336
367
|
}
|
|
337
368
|
|
|
338
369
|
case "install": {
|
|
339
|
-
if (!rest[0])
|
|
340
|
-
|
|
341
|
-
process.exitCode = 1;
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
console.log(`\n📥 正在安装 ${rest[0]}...\n`);
|
|
370
|
+
if (!requireArg(rest[0], "错误: 需要指定 agent slug")) return;
|
|
371
|
+
console.log(`\n${infoColor("📥 正在安装")} ${highlight(rest[0])}...\n`);
|
|
345
372
|
const installResult = await installCommand(rest[0], options);
|
|
346
|
-
console.log(
|
|
347
|
-
console.log(` 位置: ${options.targetWorkspace || "当前目录"}`);
|
|
373
|
+
console.log(success(`${symbols.success} 已安装 ${highlight(`${installResult.manifest.slug}@${installResult.manifest.version}`)}`));
|
|
374
|
+
console.log(` ${muted("位置:")} ${options.targetWorkspace || "当前目录"}`);
|
|
348
375
|
return;
|
|
349
376
|
}
|
|
350
377
|
|
|
@@ -354,45 +381,38 @@ async function main() {
|
|
|
354
381
|
return;
|
|
355
382
|
}
|
|
356
383
|
|
|
357
|
-
case "
|
|
358
|
-
|
|
359
|
-
|
|
384
|
+
case "verify": {
|
|
385
|
+
const result = await verifyCommand(rest[0], options);
|
|
386
|
+
console.log(formatVerifyOutput(result));
|
|
387
|
+
if (!result.verified) {
|
|
360
388
|
process.exitCode = 1;
|
|
361
|
-
return;
|
|
362
389
|
}
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
case "versions": {
|
|
394
|
+
if (!requireArg(rest[0], "错误: 需要指定 agent slug")) return;
|
|
363
395
|
const versions = await versionsCommand(rest[0], options);
|
|
364
396
|
console.log(formatVersionsOutput(rest[0], versions));
|
|
365
397
|
return;
|
|
366
398
|
}
|
|
367
399
|
|
|
368
400
|
case "update": {
|
|
369
|
-
if (!rest[0])
|
|
370
|
-
console.error("错误: 需要指定 agent slug");
|
|
371
|
-
process.exitCode = 1;
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
401
|
+
if (!requireArg(rest[0], "错误: 需要指定 agent slug")) return;
|
|
374
402
|
const updateResult = await updateCommand(rest[0], options);
|
|
375
403
|
console.log(updateResult.message);
|
|
376
404
|
return;
|
|
377
405
|
}
|
|
378
406
|
|
|
379
407
|
case "rollback": {
|
|
380
|
-
if (!rest[0])
|
|
381
|
-
console.error("错误: 需要指定 agent slug");
|
|
382
|
-
process.exitCode = 1;
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
408
|
+
if (!requireArg(rest[0], "错误: 需要指定 agent slug")) return;
|
|
385
409
|
const rollbackResult = await rollbackCommand(rest[0], options);
|
|
386
410
|
console.log(rollbackResult.message);
|
|
387
411
|
return;
|
|
388
412
|
}
|
|
389
413
|
|
|
390
414
|
case "stats": {
|
|
391
|
-
if (!rest[0])
|
|
392
|
-
console.error("错误: 需要指定 agent slug");
|
|
393
|
-
process.exitCode = 1;
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
415
|
+
if (!requireArg(rest[0], "错误: 需要指定 agent slug")) return;
|
|
396
416
|
const stats = await statsCommand(rest[0], options);
|
|
397
417
|
console.log(formatStatsOutput(stats));
|
|
398
418
|
return;
|
|
@@ -400,11 +420,11 @@ async function main() {
|
|
|
400
420
|
|
|
401
421
|
case "serve": {
|
|
402
422
|
const result = await serveCommand(options);
|
|
403
|
-
console.log(`Server listening at ${result.baseUrl}`);
|
|
404
|
-
console.log(`\n🌐 AgentHub
|
|
405
|
-
console.log(` 地址: ${result.baseUrl}`);
|
|
406
|
-
console.log(` API: ${result.baseUrl}/api/agents`);
|
|
407
|
-
console.log(`\n按 Ctrl+C
|
|
423
|
+
console.log(success(`Server listening at ${result.baseUrl}`));
|
|
424
|
+
console.log(`\n${highlight("🌐 AgentHub 服务已启动")}`);
|
|
425
|
+
console.log(` ${muted("地址:")} ${result.baseUrl}`);
|
|
426
|
+
console.log(` ${muted("API:")} ${result.baseUrl}/api/agents`);
|
|
427
|
+
console.log(`\n${muted("按 Ctrl+C 停止服务")}\n`);
|
|
408
428
|
|
|
409
429
|
const shutdown = async () => {
|
|
410
430
|
process.off("SIGINT", shutdown);
|
|
@@ -420,12 +440,12 @@ async function main() {
|
|
|
420
440
|
case "api": {
|
|
421
441
|
const port = options.port || "3001";
|
|
422
442
|
const result = await apiCommand({ ...options, port });
|
|
423
|
-
console.log(`Server listening at ${result.baseUrl}`);
|
|
424
|
-
console.log(`\n🔧 API
|
|
425
|
-
console.log(` 地址: ${result.baseUrl}`);
|
|
426
|
-
console.log(` 端点: ${result.baseUrl}/api/agents`);
|
|
427
|
-
console.log(` 统计: ${result.baseUrl}/api/stats`);
|
|
428
|
-
console.log(`\n按 Ctrl+C
|
|
443
|
+
console.log(success(`Server listening at ${result.baseUrl}`));
|
|
444
|
+
console.log(`\n${highlight("🔧 API 服务已启动")}`);
|
|
445
|
+
console.log(` ${muted("地址:")} ${result.baseUrl}`);
|
|
446
|
+
console.log(` ${muted("端点:")} ${result.baseUrl}/api/agents`);
|
|
447
|
+
console.log(` ${muted("统计:")} ${result.baseUrl}/api/stats`);
|
|
448
|
+
console.log(`\n${muted("按 Ctrl+C 停止服务")}\n`);
|
|
429
449
|
|
|
430
450
|
const shutdown = async () => {
|
|
431
451
|
process.off("SIGINT", shutdown);
|
|
@@ -442,11 +462,11 @@ async function main() {
|
|
|
442
462
|
const port = options.port || "3000";
|
|
443
463
|
const apiBase = options.apiBase || "http://127.0.0.1:3001";
|
|
444
464
|
const result = await webCommand({ port, apiBase });
|
|
445
|
-
console.log(`Server listening at ${result.baseUrl}`);
|
|
446
|
-
console.log(`\n🌐 Web
|
|
447
|
-
console.log(` 地址: ${result.baseUrl}`);
|
|
448
|
-
console.log(` API: ${apiBase}`);
|
|
449
|
-
console.log(`\n按 Ctrl+C
|
|
465
|
+
console.log(success(`Server listening at ${result.baseUrl}`));
|
|
466
|
+
console.log(`\n${highlight("🌐 Web 服务已启动")}`);
|
|
467
|
+
console.log(` ${muted("地址:")} ${result.baseUrl}`);
|
|
468
|
+
console.log(` ${muted("API:")} ${apiBase}`);
|
|
469
|
+
console.log(`\n${muted("按 Ctrl+C 停止服务")}\n`);
|
|
450
470
|
|
|
451
471
|
const shutdown = async () => {
|
|
452
472
|
process.off("SIGINT", shutdown);
|
|
@@ -460,15 +480,15 @@ async function main() {
|
|
|
460
480
|
}
|
|
461
481
|
|
|
462
482
|
default:
|
|
463
|
-
console.error(`未知命令: ${command}`);
|
|
464
|
-
console.log("\n运行 'agenthub --help' 查看可用命令");
|
|
483
|
+
console.error(error(`未知命令: ${command}`));
|
|
484
|
+
console.log(muted("\n运行 'agenthub --help' 查看可用命令"));
|
|
465
485
|
process.exitCode = 1;
|
|
466
486
|
}
|
|
467
|
-
} catch (
|
|
487
|
+
} catch (err) {
|
|
468
488
|
// 提取更详细的错误信息
|
|
469
|
-
const causeMsg =
|
|
470
|
-
const detailMsg = causeMsg ? `${
|
|
471
|
-
console.error(`\n
|
|
489
|
+
const causeMsg = err.cause?.errors?.[0]?.message || err.cause?.message || "";
|
|
490
|
+
const detailMsg = causeMsg ? `${err.message}\n 原因: ${causeMsg}` : err.message;
|
|
491
|
+
console.error(`\n${error(`${symbols.error} 错误:`)} ${detailMsg}`);
|
|
472
492
|
process.exitCode = 1;
|
|
473
493
|
}
|
|
474
494
|
}
|
package/src/commands/info.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { readAgentInfo } from "../lib/registry.js";
|
|
2
|
+
import { readAgentInfo, parseSpec } from "../lib/registry.js";
|
|
3
|
+
import { fetchRemoteJson } from "../lib/remote.js";
|
|
3
4
|
|
|
4
|
-
export async function infoCommand(agentSpec, options) {
|
|
5
|
-
|
|
5
|
+
export async function infoCommand(agentSpec, options = {}) {
|
|
6
|
+
if (options.registry) {
|
|
7
|
+
return readAgentInfo(path.resolve(options.registry), agentSpec);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { slug, version } = parseSpec(agentSpec);
|
|
11
|
+
const params = new URLSearchParams();
|
|
12
|
+
if (version) params.set("version", version);
|
|
13
|
+
const qs = params.toString();
|
|
14
|
+
return fetchRemoteJson(`/api/agents/${slug}${qs ? `?${qs}` : ""}`, options);
|
|
6
15
|
}
|
package/src/commands/list.js
CHANGED
|
@@ -4,20 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import {
|
|
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
|
-
}
|
|
7
|
+
import { pathExists, readJson } from "../lib/fs-utils.js";
|
|
21
8
|
|
|
22
9
|
export async function listCommand(options = {}) {
|
|
23
10
|
// 检查多个可能的安装位置
|
package/src/commands/rollback.js
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import {
|
|
7
|
+
import { getCurrentVersion, updateInstallRecord, buildInstallOptions } from "../lib/version-manager.js";
|
|
8
8
|
import { installBundle } from "../lib/install.js";
|
|
9
|
+
import { parseSpec } from "../lib/registry.js";
|
|
9
10
|
import { versionsCommand } from "./versions.js";
|
|
10
11
|
|
|
11
|
-
export async function rollbackCommand(agentSpec, options) {
|
|
12
|
-
const registryDir = path.resolve(options.registry);
|
|
12
|
+
export async function rollbackCommand(agentSpec, options = {}) {
|
|
13
13
|
const targetWorkspace = options.targetWorkspace ? path.resolve(options.targetWorkspace) : null;
|
|
14
14
|
const targetVersion = options.to;
|
|
15
15
|
|
|
@@ -17,42 +17,27 @@ export async function rollbackCommand(agentSpec, options) {
|
|
|
17
17
|
throw new Error("请指定回滚版本: --to <version>");
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
const
|
|
20
|
+
const { slug } = parseSpec(agentSpec);
|
|
21
21
|
|
|
22
22
|
// 验证目标版本存在
|
|
23
|
-
const versions = await versionsCommand(slug,
|
|
23
|
+
const versions = await versionsCommand(slug, options);
|
|
24
24
|
const versionExists = versions.some((v) => v.version === targetVersion);
|
|
25
25
|
if (!versionExists) {
|
|
26
26
|
throw new Error(`版本 ${targetVersion} 不存在`);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
// 获取当前版本
|
|
30
|
-
|
|
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
|
-
}
|
|
30
|
+
const currentVersion = await getCurrentVersion(targetWorkspace);
|
|
38
31
|
|
|
39
32
|
// 执行回滚
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
agentSpec: `${slug}:${targetVersion}`,
|
|
43
|
-
targetWorkspace,
|
|
44
|
-
});
|
|
33
|
+
const installOptions = buildInstallOptions(slug, targetVersion, targetWorkspace, options);
|
|
34
|
+
const result = await installBundle(installOptions);
|
|
45
35
|
|
|
46
36
|
// 记录回滚
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
version: targetVersion,
|
|
52
|
-
rolledBackAt: new Date().toISOString(),
|
|
53
|
-
rolledBackFrom: currentVersion,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
37
|
+
await updateInstallRecord(targetWorkspace, slug, targetVersion, {
|
|
38
|
+
rolledBackAt: new Date().toISOString(),
|
|
39
|
+
rolledBackFrom: currentVersion,
|
|
40
|
+
});
|
|
56
41
|
|
|
57
42
|
return {
|
|
58
43
|
rolledBack: true,
|
package/src/commands/search.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { searchRegistry } from "../lib/registry.js";
|
|
3
|
+
import { fetchRemoteJson } from "../lib/remote.js";
|
|
3
4
|
|
|
4
|
-
export async function searchCommand(query, options) {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
export async function searchCommand(query, options = {}) {
|
|
6
|
+
if (options.registry) {
|
|
7
|
+
return searchRegistry(path.resolve(options.registry), query);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const params = new URLSearchParams();
|
|
11
|
+
if (query) params.set("q", query);
|
|
12
|
+
const result = await fetchRemoteJson(`/api/agents?${params.toString()}`, options);
|
|
13
|
+
return result.agents || [];
|
|
7
14
|
}
|
package/src/commands/stats.js
CHANGED
|
@@ -5,11 +5,49 @@
|
|
|
5
5
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { pathExists, readJson } from "../lib/fs-utils.js";
|
|
8
|
+
import { fetchRemoteJson } from "../lib/remote.js";
|
|
9
|
+
import { parseSpec, getLatestVersion } from "../lib/registry.js";
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* 构建统计返回对象的辅助函数
|
|
13
|
+
*/
|
|
14
|
+
function buildStatsResult(slug, manifest, agentEntries) {
|
|
15
|
+
const latestEntry = getLatestVersion(agentEntries);
|
|
16
|
+
return {
|
|
17
|
+
slug,
|
|
18
|
+
name: manifest.name,
|
|
19
|
+
latestVersion: latestEntry.version,
|
|
20
|
+
totalVersions: agentEntries.length,
|
|
21
|
+
description: manifest.description,
|
|
22
|
+
author: manifest.author,
|
|
23
|
+
runtime: manifest.runtime,
|
|
24
|
+
includes: manifest.includes,
|
|
25
|
+
requirements: manifest.requirements,
|
|
26
|
+
metadata: manifest.metadata,
|
|
27
|
+
downloads: manifest.downloads || 0,
|
|
28
|
+
stars: manifest.stats?.stars || 0,
|
|
29
|
+
rating: manifest.stats?.rating || null,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function statsCommand(agentSpec, options = {}) {
|
|
34
|
+
const { slug } = parseSpec(agentSpec);
|
|
35
|
+
|
|
36
|
+
if (!options.registry) {
|
|
37
|
+
const [manifest, agentsResult] = await Promise.all([
|
|
38
|
+
fetchRemoteJson(`/api/agents/${slug}`, options),
|
|
39
|
+
fetchRemoteJson(`/api/agents?q=${encodeURIComponent(slug)}`, options),
|
|
40
|
+
]);
|
|
12
41
|
|
|
42
|
+
const agentEntries = (agentsResult.agents || []).filter((entry) => entry.slug === slug);
|
|
43
|
+
if (agentEntries.length === 0) {
|
|
44
|
+
throw new Error(`Agent not found: ${slug}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return buildStatsResult(slug, manifest, agentEntries);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const registryDir = path.resolve(options.registry);
|
|
13
51
|
const indexPath = path.join(registryDir, "index.json");
|
|
14
52
|
if (!(await pathExists(indexPath))) {
|
|
15
53
|
throw new Error(`Agent not found: ${slug}`);
|
|
@@ -22,12 +60,7 @@ export async function statsCommand(agentSpec, options) {
|
|
|
22
60
|
throw new Error(`Agent not found: ${slug}`);
|
|
23
61
|
}
|
|
24
62
|
|
|
25
|
-
|
|
26
|
-
const latestEntry = agentEntries.sort((a, b) =>
|
|
27
|
-
b.version.localeCompare(a.version, undefined, { numeric: true })
|
|
28
|
-
)[0];
|
|
29
|
-
|
|
30
|
-
// 读取完整 MANIFEST
|
|
63
|
+
const latestEntry = getLatestVersion(agentEntries);
|
|
31
64
|
const manifestPath = path.join(
|
|
32
65
|
registryDir,
|
|
33
66
|
"agents",
|
|
@@ -37,25 +70,7 @@ export async function statsCommand(agentSpec, options) {
|
|
|
37
70
|
);
|
|
38
71
|
const manifest = await readJson(manifestPath);
|
|
39
72
|
|
|
40
|
-
|
|
41
|
-
const stats = {
|
|
42
|
-
slug,
|
|
43
|
-
name: manifest.name,
|
|
44
|
-
latestVersion: latestEntry.version,
|
|
45
|
-
totalVersions: agentEntries.length,
|
|
46
|
-
description: manifest.description,
|
|
47
|
-
author: manifest.author,
|
|
48
|
-
runtime: manifest.runtime,
|
|
49
|
-
includes: manifest.includes,
|
|
50
|
-
requirements: manifest.requirements,
|
|
51
|
-
metadata: manifest.metadata,
|
|
52
|
-
// 以下数据在实际系统中会从数据库获取
|
|
53
|
-
downloads: manifest.stats?.installs || 0,
|
|
54
|
-
stars: manifest.stats?.stars || 0,
|
|
55
|
-
rating: manifest.stats?.rating || null,
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
return stats;
|
|
73
|
+
return buildStatsResult(slug, manifest, agentEntries);
|
|
59
74
|
}
|
|
60
75
|
|
|
61
76
|
export function formatStatsOutput(stats) {
|