@zshuangmu/agenthub 0.1.6 → 0.1.7

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.7",
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,24 @@ import {
26
28
  formatVersionsOutput,
27
29
  } from "./index.js";
28
30
 
29
- const VERSION = "0.1.3";
31
+ import { createRequire } from "node:module";
32
+ const require = createRequire(import.meta.url);
33
+ const VERSION = require("../package.json").version;
34
+
35
+ /**
36
+ * 验证必需参数,如果缺失则打印错误并设置退出码
37
+ * @param {string} arg - 要验证的参数
38
+ * @param {string} message - 错误消息
39
+ * @returns {boolean} 参数是否存在
40
+ */
41
+ function requireArg(arg, message) {
42
+ if (!arg) {
43
+ console.error(message);
44
+ process.exitCode = 1;
45
+ return false;
46
+ }
47
+ return true;
48
+ }
30
49
 
31
50
  function printHelp() {
32
51
  console.log(`
@@ -43,6 +62,7 @@ AgentHub v${VERSION} - AI Agent 打包与分发平台
43
62
  search 搜索 Registry 中的 Agent
44
63
  info 查看 Agent 详情
45
64
  list 列出当前目录/指定目录的已安装 Agent
65
+ verify 校验已安装 Agent 是否完整可用
46
66
  versions 查看 Agent 版本历史
47
67
  update 更新已安装 Agent 到最新版
48
68
  rollback 回滚已安装 Agent 到指定版本
@@ -147,45 +167,66 @@ agenthub list - 列出已安装 Agent
147
167
  示例:
148
168
  agenthub list
149
169
  agenthub list --target-workspace ./my-workspace
170
+ `,
171
+ verify: `
172
+ agenthub verify - 校验已安装 Agent
173
+
174
+ 用法:
175
+ agenthub verify [agent-slug] [--registry <dir> | --server <url>] [--target-workspace <dir>]
176
+
177
+ 选项:
178
+ --registry <dir> 本地 Registry 目录(与 --server 二选一)
179
+ --server <url> 远程服务器地址(默认: https://agenthub.cyou)
180
+ --target-workspace <dir> 目标工作区目录(默认当前目录)
181
+
182
+ 示例:
183
+ agenthub verify workspace --registry ./.registry --target-workspace ./my-workspace
184
+ agenthub verify workspace --server https://agenthub.cyou --target-workspace ./my-workspace
150
185
  `,
151
186
  versions: `
152
187
  agenthub versions - 查看版本历史
153
188
 
154
189
  用法:
155
- agenthub versions <agent-slug> --registry <dir>
190
+ agenthub versions <agent-slug> [--registry <dir> | --server <url>]
156
191
 
157
192
  选项:
158
- --registry <dir> Registry 目录 (必需)
193
+ --registry <dir> 本地 Registry 目录(与 --server 二选一)
194
+ --server <url> 远程服务器地址(默认: https://agenthub.cyou)
159
195
 
160
196
  示例:
161
197
  agenthub versions workspace --registry ./.registry
198
+ agenthub versions workspace --server https://agenthub.cyou
162
199
  `,
163
200
  update: `
164
201
  agenthub update - 更新 Agent 到最新版
165
202
 
166
203
  用法:
167
- agenthub update <agent-slug> --registry <dir> --target-workspace <dir>
204
+ agenthub update <agent-slug> [--registry <dir> | --server <url>] --target-workspace <dir>
168
205
 
169
206
  选项:
170
- --registry <dir> Registry 目录 (必需)
207
+ --registry <dir> 本地 Registry 目录(与 --server 二选一)
208
+ --server <url> 远程服务器地址(默认: https://agenthub.cyou)
171
209
  --target-workspace <dir> 目标工作区目录 (必需)
172
210
 
173
211
  示例:
174
212
  agenthub update workspace --registry ./.registry --target-workspace ./my-workspace
213
+ agenthub update workspace --server https://agenthub.cyou --target-workspace ./my-workspace
175
214
  `,
176
215
  rollback: `
177
216
  agenthub rollback - 回滚 Agent 到指定版本
178
217
 
179
218
  用法:
180
- agenthub rollback <agent-slug> --to <version> --registry <dir> --target-workspace <dir>
219
+ agenthub rollback <agent-slug> --to <version> [--registry <dir> | --server <url>] --target-workspace <dir>
181
220
 
182
221
  选项:
183
222
  --to <version> 目标版本 (必需)
184
- --registry <dir> Registry 目录 (必需)
223
+ --registry <dir> 本地 Registry 目录(与 --server 二选一)
224
+ --server <url> 远程服务器地址(默认: https://agenthub.cyou)
185
225
  --target-workspace <dir> 目标工作区目录 (必需)
186
226
 
187
227
  示例:
188
228
  agenthub rollback workspace --to 1.0.0 --registry ./.registry --target-workspace ./my-workspace
229
+ agenthub rollback workspace --to 1.0.0 --server https://agenthub.cyou --target-workspace ./my-workspace
189
230
  `,
190
231
  stats: `
191
232
  agenthub stats - 查看 Agent 统计信息
@@ -283,22 +324,14 @@ async function main() {
283
324
  }
284
325
 
285
326
  case "publish": {
286
- if (!rest[0]) {
287
- console.error("错误: 需要指定 bundle 目录");
288
- process.exitCode = 1;
289
- return;
290
- }
327
+ if (!requireArg(rest[0], "错误: 需要指定 bundle 目录")) return;
291
328
  const result = await publishCommand(rest[0], options);
292
329
  console.log(`✓ 已发布 ${result.slug}@${result.version}`);
293
330
  return;
294
331
  }
295
332
 
296
333
  case "publish-remote": {
297
- if (!rest[0]) {
298
- console.error("错误: 需要指定 bundle 目录");
299
- process.exitCode = 1;
300
- return;
301
- }
334
+ if (!requireArg(rest[0], "错误: 需要指定 bundle 目录")) return;
302
335
  const result = await publishRemoteCommand(rest[0], options);
303
336
  console.log(`✓ 已发布到远程 ${result.slug}@${result.version}`);
304
337
  return;
@@ -318,11 +351,7 @@ async function main() {
318
351
  }
319
352
 
320
353
  case "info": {
321
- if (!rest[0]) {
322
- console.error("错误: 需要指定 agent slug");
323
- process.exitCode = 1;
324
- return;
325
- }
354
+ if (!requireArg(rest[0], "错误: 需要指定 agent slug")) return;
326
355
  const result = await infoCommand(rest[0], options);
327
356
  console.log(`\n📦 ${result.name} (${result.slug}@${result.version})`);
328
357
  console.log(` ${result.description}`);
@@ -336,11 +365,7 @@ async function main() {
336
365
  }
337
366
 
338
367
  case "install": {
339
- if (!rest[0]) {
340
- console.error("错误: 需要指定 agent slug");
341
- process.exitCode = 1;
342
- return;
343
- }
368
+ if (!requireArg(rest[0], "错误: 需要指定 agent slug")) return;
344
369
  console.log(`\n📥 正在安装 ${rest[0]}...\n`);
345
370
  const installResult = await installCommand(rest[0], options);
346
371
  console.log(`✓ 已安装 ${installResult.manifest.slug}@${installResult.manifest.version}`);
@@ -354,45 +379,38 @@ async function main() {
354
379
  return;
355
380
  }
356
381
 
357
- case "versions": {
358
- if (!rest[0]) {
359
- console.error("错误: 需要指定 agent slug");
382
+ case "verify": {
383
+ const result = await verifyCommand(rest[0], options);
384
+ console.log(formatVerifyOutput(result));
385
+ if (!result.verified) {
360
386
  process.exitCode = 1;
361
- return;
362
387
  }
388
+ return;
389
+ }
390
+
391
+ case "versions": {
392
+ if (!requireArg(rest[0], "错误: 需要指定 agent slug")) return;
363
393
  const versions = await versionsCommand(rest[0], options);
364
394
  console.log(formatVersionsOutput(rest[0], versions));
365
395
  return;
366
396
  }
367
397
 
368
398
  case "update": {
369
- if (!rest[0]) {
370
- console.error("错误: 需要指定 agent slug");
371
- process.exitCode = 1;
372
- return;
373
- }
399
+ if (!requireArg(rest[0], "错误: 需要指定 agent slug")) return;
374
400
  const updateResult = await updateCommand(rest[0], options);
375
401
  console.log(updateResult.message);
376
402
  return;
377
403
  }
378
404
 
379
405
  case "rollback": {
380
- if (!rest[0]) {
381
- console.error("错误: 需要指定 agent slug");
382
- process.exitCode = 1;
383
- return;
384
- }
406
+ if (!requireArg(rest[0], "错误: 需要指定 agent slug")) return;
385
407
  const rollbackResult = await rollbackCommand(rest[0], options);
386
408
  console.log(rollbackResult.message);
387
409
  return;
388
410
  }
389
411
 
390
412
  case "stats": {
391
- if (!rest[0]) {
392
- console.error("错误: 需要指定 agent slug");
393
- process.exitCode = 1;
394
- return;
395
- }
413
+ if (!requireArg(rest[0], "错误: 需要指定 agent slug")) return;
396
414
  const stats = await statsCommand(rest[0], options);
397
415
  console.log(formatStatsOutput(stats));
398
416
  return;
@@ -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) {
@@ -4,17 +4,16 @@
4
4
  */
5
5
 
6
6
  import path from "node:path";
7
- import { pathExists, readJson, writeJson } from "../lib/fs-utils.js";
8
- import { installBundle } from "../lib/install.js";
7
+ import { getCurrentVersion, performVersionChange } from "../lib/version-manager.js";
8
+ import { parseSpec } from "../lib/registry.js";
9
9
  import { versionsCommand } from "./versions.js";
10
10
 
11
- export async function updateCommand(agentSpec, options) {
12
- const registryDir = path.resolve(options.registry);
11
+ export async function updateCommand(agentSpec, options = {}) {
13
12
  const targetWorkspace = options.targetWorkspace ? path.resolve(options.targetWorkspace) : null;
14
- const [slug] = agentSpec.split(":");
13
+ const { slug } = parseSpec(agentSpec);
15
14
 
16
15
  // 获取可用版本
17
- const versions = await versionsCommand(slug, { registry: registryDir });
16
+ const versions = await versionsCommand(slug, options);
18
17
  if (versions.length === 0) {
19
18
  throw new Error(`Agent not found: ${slug}`);
20
19
  }
@@ -22,14 +21,7 @@ export async function updateCommand(agentSpec, options) {
22
21
  const latestVersion = versions[0].version;
23
22
 
24
23
  // 获取当前安装版本
25
- let currentVersion = null;
26
- if (targetWorkspace) {
27
- const installRecordPath = path.join(targetWorkspace, ".agenthub", "install.json");
28
- if (await pathExists(installRecordPath)) {
29
- const record = await readJson(installRecordPath);
30
- currentVersion = record.version;
31
- }
32
- }
24
+ const currentVersion = await getCurrentVersion(targetWorkspace);
33
25
 
34
26
  if (currentVersion === latestVersion) {
35
27
  return {
@@ -41,22 +33,7 @@ export async function updateCommand(agentSpec, options) {
41
33
  }
42
34
 
43
35
  // 执行更新
44
- const result = await installBundle({
45
- registryDir,
46
- agentSpec: `${slug}:${latestVersion}`,
47
- targetWorkspace,
48
- });
49
-
50
- // 记录更新
51
- if (targetWorkspace) {
52
- const installRecordPath = path.join(targetWorkspace, ".agenthub", "install.json");
53
- await writeJson(installRecordPath, {
54
- slug,
55
- version: latestVersion,
56
- updatedAt: new Date().toISOString(),
57
- previousVersion: currentVersion,
58
- });
59
- }
36
+ const result = await performVersionChange(slug, latestVersion, currentVersion, targetWorkspace, options);
60
37
 
61
38
  return {
62
39
  updated: true,
@@ -0,0 +1,94 @@
1
+ import path from "node:path";
2
+ import { pathExists, readJson } from "../lib/fs-utils.js";
3
+ import { parseSpec } from "../lib/registry.js";
4
+ import { infoCommand } from "./info.js";
5
+
6
+ export async function verifyCommand(agentSpec, options = {}) {
7
+ const targetWorkspace = path.resolve(options.targetWorkspace || process.cwd());
8
+ const installRecordPath = path.join(targetWorkspace, ".agenthub", "install.json");
9
+ const appliedConfigPath = path.join(targetWorkspace, ".agenthub", "OPENCLAW.applied.json");
10
+
11
+ const checks = [];
12
+
13
+ if (!(await pathExists(installRecordPath))) {
14
+ return {
15
+ ok: false,
16
+ verified: false,
17
+ targetWorkspace,
18
+ reason: "install record missing",
19
+ checks: [{ name: "install_record", ok: false, detail: installRecordPath }],
20
+ };
21
+ }
22
+
23
+ const installRecord = await readJson(installRecordPath);
24
+ const expectedSlug = agentSpec ? parseSpec(agentSpec).slug : installRecord.slug;
25
+ if (expectedSlug && installRecord.slug !== expectedSlug) {
26
+ checks.push({
27
+ name: "slug_match",
28
+ ok: false,
29
+ detail: `installed=${installRecord.slug}, expected=${expectedSlug}`,
30
+ });
31
+ return {
32
+ ok: false,
33
+ verified: false,
34
+ targetWorkspace,
35
+ installRecord,
36
+ checks,
37
+ reason: "installed slug mismatch",
38
+ };
39
+ }
40
+
41
+ checks.push({ name: "install_record", ok: true, detail: installRecordPath });
42
+
43
+ const configExists = await pathExists(appliedConfigPath);
44
+ checks.push({ name: "applied_config", ok: configExists, detail: appliedConfigPath });
45
+
46
+ let manifest = null;
47
+ try {
48
+ manifest = await infoCommand(`${installRecord.slug}:${installRecord.version}`, options);
49
+ checks.push({ name: "manifest_lookup", ok: true, detail: `${installRecord.slug}@${installRecord.version}` });
50
+ } catch (error) {
51
+ checks.push({
52
+ name: "manifest_lookup",
53
+ ok: false,
54
+ detail: error.message,
55
+ });
56
+ }
57
+
58
+ const requiredFiles = ["AGENTS.md", "SOUL.md", "USER.md"];
59
+ for (const fileName of requiredFiles) {
60
+ const filePath = path.join(targetWorkspace, fileName);
61
+ checks.push({
62
+ name: `workspace_file:${fileName}`,
63
+ ok: await pathExists(filePath),
64
+ detail: filePath,
65
+ });
66
+ }
67
+
68
+ const verified = checks.every((check) => check.ok);
69
+ return {
70
+ ok: verified,
71
+ verified,
72
+ targetWorkspace,
73
+ installRecord,
74
+ manifest,
75
+ checks,
76
+ };
77
+ }
78
+
79
+ export function formatVerifyOutput(result) {
80
+ const lines = [];
81
+ lines.push(`\n${result.verified ? "✅" : "❌"} Verify ${result.verified ? "passed" : "failed"}\n`);
82
+ if (result.installRecord) {
83
+ lines.push(`Agent: ${result.installRecord.slug}@${result.installRecord.version}`);
84
+ }
85
+ lines.push(`Workspace: ${result.targetWorkspace}`);
86
+ lines.push("");
87
+ for (const check of result.checks || []) {
88
+ lines.push(`- ${check.ok ? "PASS" : "FAIL"} ${check.name}: ${check.detail}`);
89
+ }
90
+ if (result.reason) {
91
+ lines.push(`\nReason: ${result.reason}`);
92
+ }
93
+ return lines.join("\n");
94
+ }