@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zshuangmu/agenthub",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "AI Agent 打包与分发平台 - 一句话打包上传,一键下载获得能力",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
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) => server.close((error) => (error ? reject(error) : resolve()))),
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
- const VERSION = "0.1.3";
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> Registry 目录 (必需)
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(`✓ 打包完成: ${result.bundleDir}`);
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(`✓ 已发布 ${result.slug}@${result.version}`);
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(`✓ 已发布到远程 ${result.slug}@${result.version}`);
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找到 ${results.length} 个 Agent:\n`);
347
+ console.log(`\n${infoColor(`找到 ${results.length} 个 Agent:`)}\n`);
313
348
  for (const entry of results) {
314
- console.log(` ${entry.slug}@${entry.version} - ${entry.description || ""}`);
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
- console.error("错误: 需要指定 agent slug");
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(`✓ 已安装 ${installResult.manifest.slug}@${installResult.manifest.version}`);
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 "versions": {
358
- if (!rest[0]) {
359
- console.error("错误: 需要指定 agent slug");
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 停止服务\n`);
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 停止服务\n`);
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 停止服务\n`);
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 (error) {
487
+ } catch (err) {
468
488
  // 提取更详细的错误信息
469
- const causeMsg = error.cause?.errors?.[0]?.message || error.cause?.message || "";
470
- const detailMsg = causeMsg ? `${error.message}\n 原因: ${causeMsg}` : error.message;
471
- console.error(`\n 错误: ${detailMsg}`);
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
  }
@@ -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
- return readAgentInfo(path.resolve(options.registry), agentSpec);
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
  }
@@ -4,20 +4,7 @@
4
4
  */
5
5
 
6
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
- }
7
+ import { pathExists, readJson } from "../lib/fs-utils.js";
21
8
 
22
9
  export async function listCommand(options = {}) {
23
10
  // 检查多个可能的安装位置
@@ -4,12 +4,12 @@
4
4
  */
5
5
 
6
6
  import path from "node:path";
7
- import { pathExists, readJson, writeJson } from "../lib/fs-utils.js";
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 [slug] = agentSpec.split(":");
20
+ const { slug } = parseSpec(agentSpec);
21
21
 
22
22
  // 验证目标版本存在
23
- const versions = await versionsCommand(slug, { registry: registryDir });
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
- 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
- }
30
+ const currentVersion = await getCurrentVersion(targetWorkspace);
38
31
 
39
32
  // 执行回滚
40
- const result = await installBundle({
41
- registryDir,
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
- 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
- }
37
+ await updateInstallRecord(targetWorkspace, slug, targetVersion, {
38
+ rolledBackAt: new Date().toISOString(),
39
+ rolledBackFrom: currentVersion,
40
+ });
56
41
 
57
42
  return {
58
43
  rolledBack: true,
@@ -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
- const results = await searchRegistry(path.resolve(options.registry), query);
6
- return results;
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
  }
@@ -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
- export async function statsCommand(agentSpec, options) {
10
- const registryDir = path.resolve(options.registry);
11
- const [slug] = agentSpec.split(":");
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) {