@zshuangmu/agenthub 0.4.14 → 0.4.16

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.
Files changed (43) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +268 -268
  3. package/package.json +41 -41
  4. package/src/api-server.js +518 -244
  5. package/src/cli.js +714 -671
  6. package/src/commands/api.js +9 -9
  7. package/src/commands/doctor.js +335 -335
  8. package/src/commands/info.js +15 -15
  9. package/src/commands/install.js +56 -56
  10. package/src/commands/list.js +78 -78
  11. package/src/commands/pack.js +249 -156
  12. package/src/commands/publish-remote.js +9 -9
  13. package/src/commands/publish.js +7 -7
  14. package/src/commands/rollback.js +59 -59
  15. package/src/commands/search.js +14 -14
  16. package/src/commands/serve.js +9 -9
  17. package/src/commands/stats.js +105 -105
  18. package/src/commands/uninstall.js +76 -76
  19. package/src/commands/update.js +54 -54
  20. package/src/commands/verify.js +133 -133
  21. package/src/commands/versions.js +75 -75
  22. package/src/commands/web.js +9 -9
  23. package/src/index.js +18 -18
  24. package/src/lib/auth.js +301 -0
  25. package/src/lib/bundle-transfer.js +58 -58
  26. package/src/lib/colors.js +60 -60
  27. package/src/lib/database.js +450 -244
  28. package/src/lib/debug.js +135 -135
  29. package/src/lib/fs-utils.js +107 -50
  30. package/src/lib/html.js +2163 -1824
  31. package/src/lib/http.js +168 -168
  32. package/src/lib/install.js +60 -60
  33. package/src/lib/manifest.js +124 -124
  34. package/src/lib/openclaw-config.js +40 -40
  35. package/src/lib/permissions.js +105 -0
  36. package/src/lib/privacy-engine.js +220 -0
  37. package/src/lib/registry.js +130 -130
  38. package/src/lib/remote.js +11 -11
  39. package/src/lib/security-scanner.js +233 -233
  40. package/src/lib/signing.js +158 -0
  41. package/src/lib/version-manager.js +77 -77
  42. package/src/server.js +176 -176
  43. package/src/web-server.js +135 -135
@@ -1,156 +1,249 @@
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
- const version = options.version || "1.0.0";
71
- const customName = options.name || null;
72
-
73
- // 从工作区名称或自定义名称生成 slug
74
- const baseName = customName || path.basename(workspace);
75
- const slug = baseName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
76
- const bundleDir = path.join(output, `${slug}-${version}.agent`);
77
-
78
- // 1. 扫描工作区
79
- const workspaceFiles = await scanWorkspace(workspace);
80
-
81
- // 2. 统计 Memory 分层
82
- const memoryCounts = {
83
- public: await countFiles(path.join(workspace, "memory", "public")),
84
- portable: await countFiles(path.join(workspace, "memory", "portable")),
85
- private: await countFiles(path.join(workspace, "memory", "private")),
86
- };
87
-
88
- // 3. 读取并处理 OpenClaw 配置
89
- let template = {};
90
- if (configPath && (await pathExists(configPath))) {
91
- const config = await readJson(configPath);
92
- template = extractOpenClawTemplate(config);
93
- }
94
-
95
- // 4. 生成 MANIFEST
96
- const manifest = createManifest({
97
- slug,
98
- name: customName || path.basename(workspace),
99
- memoryCounts,
100
- openclawTemplate: template,
101
- skills: workspaceFiles.skills,
102
- prompts: workspaceFiles.prompts,
103
- tags: options.tags && typeof options.tags === "string" && options.tags.trim() ? options.tags.split(",").map((t) => t.trim()) : [],
104
- category: options.category,
105
- version,
106
- featured: options.featured === true,
107
- });
108
-
109
- // 5. 验证 MANIFEST
110
- const validation = validateManifest(manifest);
111
- if (!validation.valid) {
112
- throw new Error(`Invalid manifest: ${validation.errors.join(", ")}`);
113
- }
114
-
115
- // 6. 创建 Bundle 目录结构
116
- await ensureDir(bundleDir);
117
-
118
- // 6.1 复制 WORKSPACE
119
- await copyDir(workspace, path.join(bundleDir, "WORKSPACE"));
120
-
121
- // 6.2 写入 OPENCLAW.template.json
122
- await writeJson(path.join(bundleDir, "OPENCLAW.template.json"), template);
123
-
124
- // 6.3 写入 MANIFEST.json
125
- await writeJson(path.join(bundleDir, "MANIFEST.json"), manifest);
126
-
127
- // 6.4 生成 README.md
128
- const readme = `# ${manifest.name}
129
-
130
- ${manifest.description}
131
-
132
- ## 安装
133
-
134
- \`\`\`bash
135
- agenthub install ${manifest.slug}
136
- \`\`\`
137
-
138
- ## 包含内容
139
-
140
- - **记忆**: ${manifest.includes.memory.count}
141
- - **技能**: ${manifest.includes.skills.join(", ") || "无"}
142
- - **提示词**: ${manifest.includes.prompts}
143
-
144
- ## 要求
145
-
146
- - 运行时: OpenClaw ${manifest.runtime.version}
147
- `;
148
- await writeFile(path.join(bundleDir, "README.md"), readme, "utf8");
149
-
150
- return {
151
- bundleDir,
152
- manifest,
153
- validation,
154
- bundleId: generateBundleId(manifest.slug, manifest.version),
155
- };
156
- }
1
+ /**
2
+ * Pack Command
3
+ * 打包 OpenClaw 工作区为 Agent Bundle
4
+ *
5
+ * 根据 PRD v1.1 Bundle 结构规范实现
6
+ * v0.5: 集成隐私防护引擎 + 安全扫描
7
+ */
8
+
9
+ import path from "node:path";
10
+ import { readdir, readFile, writeFile } from "node:fs/promises";
11
+ import { countFiles, copyDirFiltered, ensureDir, readJson, writeJson, pathExists } from "../lib/fs-utils.js";
12
+ import { createManifest, validateManifest, generateBundleId } from "../lib/manifest.js";
13
+ import { extractOpenClawTemplate } from "../lib/openclaw-config.js";
14
+ import { scanPrivacyRisks, getPackFilterOptions, generatePrivacyReport } from "../lib/privacy-engine.js";
15
+ import { scanWorkspace as securityScanWorkspace } from "../lib/security-scanner.js";
16
+
17
+ const WORKSPACE_CORE_FILES = ["AGENTS.md", "SOUL.md", "USER.md", "IDENTITY.md", "TOOLS.md", "HEARTBEAT.md", "BOOTSTRAP.md"];
18
+
19
+ /**
20
+ * 扫描工作区文件
21
+ */
22
+ async function scanWorkspace(workspacePath) {
23
+ const files = {
24
+ core: [],
25
+ skills: [],
26
+ prompts: [],
27
+ canvas: false,
28
+ };
29
+
30
+ for (const filename of WORKSPACE_CORE_FILES) {
31
+ const filePath = path.join(workspacePath, filename);
32
+ if (await pathExists(filePath)) {
33
+ files.core.push(filename);
34
+ }
35
+ }
36
+
37
+ // 检查 skills 目录
38
+ const skillsPath = path.join(workspacePath, "skills");
39
+ if (await pathExists(skillsPath)) {
40
+ const entries = await readdir(skillsPath, { withFileTypes: true });
41
+ for (const entry of entries) {
42
+ if (entry.isDirectory()) {
43
+ files.skills.push(entry.name);
44
+ }
45
+ }
46
+ }
47
+
48
+ // 检查 PROMPTS 目录
49
+ const promptsPath = path.join(workspacePath, "PROMPTS");
50
+ if (await pathExists(promptsPath)) {
51
+ const entries = await readdir(promptsPath, { withFileTypes: true });
52
+ for (const entry of entries) {
53
+ if (entry.isFile() && entry.name.endsWith(".txt")) {
54
+ files.prompts.push(entry.name);
55
+ }
56
+ }
57
+ }
58
+
59
+ // 检查 canvas 目录
60
+ const canvasPath = path.join(workspacePath, "canvas");
61
+ files.canvas = await pathExists(canvasPath);
62
+
63
+ return files;
64
+ }
65
+
66
+ /**
67
+ * Pack 命令主函数
68
+ *
69
+ * @param {object} options
70
+ * @param {string} [options.workspace] - 工作区路径
71
+ * @param {string} [options.output] - 输出目录
72
+ * @param {string} [options.config] - OpenClaw 配置文件路径
73
+ * @param {string} [options.version] - 版本号
74
+ * @param {string} [options.name] - 自定义名称
75
+ * @param {boolean} [options.strict] - 严格模式:发现高危敏感信息时中止打包
76
+ * @param {boolean} [options.stripPrivate] - 自动剥离私有数据(默认 true)
77
+ * @param {boolean} [options.skipScan] - 跳过安全扫描
78
+ */
79
+ export async function packCommand(options) {
80
+ const workspace = path.resolve(options.workspace || process.cwd());
81
+ const output = path.resolve(options.output ?? "./bundles");
82
+ const configPath = options.config ? path.resolve(options.config) : null;
83
+ const version = options.version || "1.0.0";
84
+ const customName = options.name || null;
85
+ const strict = options.strict === true;
86
+ const stripPrivate = options.stripPrivate !== false; // 默认开启
87
+ const skipScan = options.skipScan === true;
88
+
89
+ // 从工作区名称或自定义名称生成 slug
90
+ const baseName = customName || path.basename(workspace);
91
+ const slug = baseName.toLowerCase().replace(/[^a-z0-9-]/g, "-");
92
+ const bundleDir = path.join(output, `${slug}-${version}.agent`);
93
+
94
+ // ===== 隐私防护阶段 =====
95
+
96
+ // 1. 安全扫描(除非显式跳过)
97
+ let securityResult = null;
98
+ let privacyResult = null;
99
+
100
+ if (!skipScan) {
101
+ // 1.1 安全扫描:检测 API Key、密码、Token 等
102
+ securityResult = await securityScanWorkspace(workspace);
103
+
104
+ // 1.2 隐私风险扫描:检测私有记忆、敏感信息
105
+ privacyResult = await scanPrivacyRisks(workspace);
106
+
107
+ // 1.3 严格模式:发现高危问题时中止
108
+ if (strict) {
109
+ const criticalFindings = securityResult.findings.filter(
110
+ (f) => f.severity === "critical" || f.severity === "high"
111
+ );
112
+ if (criticalFindings.length > 0) {
113
+ const details = criticalFindings
114
+ .map((f) => ` ${f.file}: ${f.type} (${f.count} 处)`)
115
+ .join("\n");
116
+ throw new Error(
117
+ `[严格模式] 安全扫描发现 ${criticalFindings.length} 个高危问题,打包已中止:\n${details}\n\n提示: 移除敏感信息后重试,或使用 --no-strict 跳过严格检查`
118
+ );
119
+ }
120
+
121
+ if (privacyResult.hasPrivateMemory) {
122
+ throw new Error(
123
+ `[严格模式] 检测到 memory/private/ 中有 ${privacyResult.privateMemoryCount} 个文件,打包已中止。\n\n提示: 私有记忆会被自动排除,使用 --no-strict 允许打包`
124
+ );
125
+ }
126
+ }
127
+ }
128
+
129
+ // 2. 扫描工作区文件结构
130
+ const workspaceFiles = await scanWorkspace(workspace);
131
+
132
+ // 3. 统计 Memory 分层(用于 Manifest 记录,不影响过滤)
133
+ const memoryCounts = {
134
+ public: await countFiles(path.join(workspace, "memory", "public")),
135
+ portable: await countFiles(path.join(workspace, "memory", "portable")),
136
+ private: await countFiles(path.join(workspace, "memory", "private")),
137
+ };
138
+
139
+ // 4. 读取并处理 OpenClaw 配置
140
+ let template = {};
141
+ if (configPath && (await pathExists(configPath))) {
142
+ const config = await readJson(configPath);
143
+ template = extractOpenClawTemplate(config);
144
+ }
145
+
146
+ // 5. 生成 MANIFEST
147
+ const manifest = createManifest({
148
+ slug,
149
+ name: customName || path.basename(workspace),
150
+ memoryCounts,
151
+ openclawTemplate: template,
152
+ skills: workspaceFiles.skills,
153
+ prompts: workspaceFiles.prompts,
154
+ tags: options.tags && typeof options.tags === "string" && options.tags.trim() ? options.tags.split(",").map((t) => t.trim()) : [],
155
+ category: options.category,
156
+ version,
157
+ featured: options.featured === true,
158
+ });
159
+
160
+ // 6. 验证 MANIFEST
161
+ const validation = validateManifest(manifest);
162
+ if (!validation.valid) {
163
+ throw new Error(`Invalid manifest: ${validation.errors.join(", ")}`);
164
+ }
165
+
166
+ // ===== 构建阶段 =====
167
+
168
+ // 7. 创建 Bundle 目录结构
169
+ await ensureDir(bundleDir);
170
+
171
+ // 7.1 使用带过滤的复制替代全量复制
172
+ const filterOptions = stripPrivate
173
+ ? getPackFilterOptions(manifest)
174
+ : { excludeDirs: [".git", "node_modules"], excludeFiles: [], maxFileSize: 50 * 1024 * 1024 };
175
+
176
+ const copyReport = await copyDirFiltered(
177
+ workspace,
178
+ path.join(bundleDir, "WORKSPACE"),
179
+ filterOptions,
180
+ );
181
+
182
+ // 7.2 写入 OPENCLAW.template.json
183
+ await writeJson(path.join(bundleDir, "OPENCLAW.template.json"), template);
184
+
185
+ // 7.3 写入 MANIFEST.json(更新排除信息)
186
+ if (stripPrivate) {
187
+ // 更新 memory.private 为 0 因为已被剥离
188
+ manifest.includes.memory.private = 0;
189
+ manifest.includes.memory.count = manifest.includes.memory.public + manifest.includes.memory.portable;
190
+ }
191
+ await writeJson(path.join(bundleDir, "MANIFEST.json"), manifest);
192
+
193
+ // 7.4 生成隐私合规报告
194
+ if (!skipScan && privacyResult) {
195
+ const privacyReport = generatePrivacyReport(privacyResult, copyReport);
196
+ await writeJson(path.join(bundleDir, "PRIVACY_REPORT.json"), privacyReport);
197
+ }
198
+
199
+ // 7.5 生成 README.md
200
+ const privacyBadge = stripPrivate ? "🛡️ 已通过隐私审查" : "⚠️ 未启用隐私防护";
201
+ const readme = `# ${manifest.name}
202
+
203
+ ${manifest.description}
204
+
205
+ ${privacyBadge}
206
+
207
+ ## 安装
208
+
209
+ \`\`\`bash
210
+ agenthub install ${manifest.slug}
211
+ \`\`\`
212
+
213
+ ## 包含内容
214
+
215
+ - **记忆**: ${manifest.includes.memory.count} 条 (public: ${manifest.includes.memory.public}, portable: ${manifest.includes.memory.portable})
216
+ - **技能**: ${manifest.includes.skills.join(", ") || "无"}
217
+ - **提示词**: ${manifest.includes.prompts} 个
218
+
219
+ ## 隐私说明
220
+
221
+ - 私有记忆 (memory/private): ${stripPrivate ? "已自动剥离" : "未处理"}
222
+ - 敏感信息扫描: ${skipScan ? "已跳过" : "已完成"}
223
+ - 文件过滤: 排除了 ${copyReport.skipped.length} 个文件/目录
224
+
225
+ ## 要求
226
+
227
+ - 运行时: OpenClaw ${manifest.runtime.version}
228
+ `;
229
+ await writeFile(path.join(bundleDir, "README.md"), readme, "utf8");
230
+
231
+ return {
232
+ bundleDir,
233
+ manifest,
234
+ validation,
235
+ bundleId: generateBundleId(manifest.slug, manifest.version),
236
+ // 新增:安全与隐私报告
237
+ security: securityResult ? {
238
+ warnings: securityResult.warnings,
239
+ findingsCount: securityResult.findings.length,
240
+ canPublish: securityResult.canPublish,
241
+ } : null,
242
+ privacy: {
243
+ stripped: stripPrivate,
244
+ skippedFiles: copyReport.skipped.length,
245
+ copiedFiles: copyReport.copied.length,
246
+ skippedList: copyReport.skipped,
247
+ },
248
+ };
249
+ }
@@ -1,9 +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
- }
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
+ }
@@ -1,7 +1,7 @@
1
- import path from "node:path";
2
- import { publishBundle } from "../lib/registry.js";
3
-
4
- export async function publishCommand(bundleDir, options) {
5
- const registry = path.resolve(options.registry);
6
- return publishBundle(path.resolve(bundleDir), registry);
7
- }
1
+ import path from "node:path";
2
+ import { publishBundle } from "../lib/registry.js";
3
+
4
+ export async function publishCommand(bundleDir, options) {
5
+ const registry = path.resolve(options.registry);
6
+ return publishBundle(path.resolve(bundleDir), registry);
7
+ }
@@ -1,59 +1,59 @@
1
- /**
2
- * Rollback Command
3
- * 回滚 Agent 到指定版本
4
- */
5
-
6
- import path from "node:path";
7
- import { getCurrentVersion, updateInstallRecord, buildInstallOptions } from "../lib/version-manager.js";
8
- import { installBundle } from "../lib/install.js";
9
- import { parseSpec } from "../lib/registry.js";
10
- import { versionsCommand } from "./versions.js";
11
- import { success, warning, highlight, muted, symbols } from "../lib/colors.js";
12
-
13
- export async function rollbackCommand(agentSpec, options = {}) {
14
- const targetWorkspace = options.targetWorkspace ? path.resolve(options.targetWorkspace) : null;
15
- const targetVersion = options.to;
16
-
17
- if (!targetVersion) {
18
- throw new Error("请指定回滚版本: --to <version>");
19
- }
20
-
21
- const { slug } = parseSpec(agentSpec);
22
-
23
- // 验证目标版本存在
24
- const versions = await versionsCommand(slug, options);
25
- const versionExists = versions.some((v) => v.version === targetVersion);
26
- if (!versionExists) {
27
- throw new Error(`版本 ${targetVersion} 不存在`);
28
- }
29
-
30
- // 获取当前版本
31
- const currentVersion = await getCurrentVersion(targetWorkspace);
32
-
33
- // 执行回滚
34
- const installOptions = buildInstallOptions(slug, targetVersion, targetWorkspace, options);
35
- const result = await installBundle(installOptions);
36
-
37
- // 记录回滚
38
- await updateInstallRecord(targetWorkspace, slug, targetVersion, {
39
- rolledBackAt: new Date().toISOString(),
40
- rolledBackFrom: currentVersion,
41
- });
42
-
43
- return {
44
- rolledBack: true,
45
- message: [
46
- success(`${symbols.success} 回滚成功`),
47
- "",
48
- ` ${highlight("Agent:")} ${slug}`,
49
- ` ${muted("回滚前:")} ${currentVersion || "未知"}`,
50
- ` ${highlight("当前版本:")} ${targetVersion}`,
51
- "",
52
- ` ${muted("运行")} ${highlight("agenthub verify " + slug)} ${muted("校验安装状态")}`,
53
- ` ${muted("运行")} ${highlight("agenthub update " + slug)} ${muted("恢复到最新版本")}`,
54
- ].join("\n"),
55
- currentVersion: targetVersion,
56
- previousVersion: currentVersion,
57
- manifest: result.manifest,
58
- };
59
- }
1
+ /**
2
+ * Rollback Command
3
+ * 回滚 Agent 到指定版本
4
+ */
5
+
6
+ import path from "node:path";
7
+ import { getCurrentVersion, updateInstallRecord, buildInstallOptions } from "../lib/version-manager.js";
8
+ import { installBundle } from "../lib/install.js";
9
+ import { parseSpec } from "../lib/registry.js";
10
+ import { versionsCommand } from "./versions.js";
11
+ import { success, warning, highlight, muted, symbols } from "../lib/colors.js";
12
+
13
+ export async function rollbackCommand(agentSpec, options = {}) {
14
+ const targetWorkspace = options.targetWorkspace ? path.resolve(options.targetWorkspace) : null;
15
+ const targetVersion = options.to;
16
+
17
+ if (!targetVersion) {
18
+ throw new Error("请指定回滚版本: --to <version>");
19
+ }
20
+
21
+ const { slug } = parseSpec(agentSpec);
22
+
23
+ // 验证目标版本存在
24
+ const versions = await versionsCommand(slug, options);
25
+ const versionExists = versions.some((v) => v.version === targetVersion);
26
+ if (!versionExists) {
27
+ throw new Error(`版本 ${targetVersion} 不存在`);
28
+ }
29
+
30
+ // 获取当前版本
31
+ const currentVersion = await getCurrentVersion(targetWorkspace);
32
+
33
+ // 执行回滚
34
+ const installOptions = buildInstallOptions(slug, targetVersion, targetWorkspace, options);
35
+ const result = await installBundle(installOptions);
36
+
37
+ // 记录回滚
38
+ await updateInstallRecord(targetWorkspace, slug, targetVersion, {
39
+ rolledBackAt: new Date().toISOString(),
40
+ rolledBackFrom: currentVersion,
41
+ });
42
+
43
+ return {
44
+ rolledBack: true,
45
+ message: [
46
+ success(`${symbols.success} 回滚成功`),
47
+ "",
48
+ ` ${highlight("Agent:")} ${slug}`,
49
+ ` ${muted("回滚前:")} ${currentVersion || "未知"}`,
50
+ ` ${highlight("当前版本:")} ${targetVersion}`,
51
+ "",
52
+ ` ${muted("运行")} ${highlight("agenthub verify " + slug)} ${muted("校验安装状态")}`,
53
+ ` ${muted("运行")} ${highlight("agenthub update " + slug)} ${muted("恢复到最新版本")}`,
54
+ ].join("\n"),
55
+ currentVersion: targetVersion,
56
+ previousVersion: currentVersion,
57
+ manifest: result.manifest,
58
+ };
59
+ }
@@ -1,14 +1,14 @@
1
- import path from "node:path";
2
- import { searchRegistry } from "../lib/registry.js";
3
- import { fetchRemoteJson } from "../lib/remote.js";
4
-
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 || [];
14
- }
1
+ import path from "node:path";
2
+ import { searchRegistry } from "../lib/registry.js";
3
+ import { fetchRemoteJson } from "../lib/remote.js";
4
+
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 || [];
14
+ }
@@ -1,9 +1,9 @@
1
- import path from "node:path";
2
- import { createServer } from "../server.js";
3
-
4
- export async function serveCommand(options) {
5
- const registryDir = path.resolve(options.registry);
6
- const port = options.port ? Number(options.port) : 3000;
7
- const host = options.host || "0.0.0.0";
8
- return createServer({ registryDir, port, host });
9
- }
1
+ import path from "node:path";
2
+ import { createServer } from "../server.js";
3
+
4
+ export async function serveCommand(options) {
5
+ const registryDir = path.resolve(options.registry);
6
+ const port = options.port ? Number(options.port) : 3000;
7
+ const host = options.host || "0.0.0.0";
8
+ return createServer({ registryDir, port, host });
9
+ }