@zshuangmu/agenthub 0.3.2 → 0.3.4
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/cli.js +26 -0
- package/src/commands/doctor.js +335 -0
- package/src/index.js +1 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
formatStatsOutput,
|
|
27
27
|
versionsCommand,
|
|
28
28
|
formatVersionsOutput,
|
|
29
|
+
doctorCommand,
|
|
29
30
|
} from "./index.js";
|
|
30
31
|
|
|
31
32
|
import { success, error, warning, info as infoColor, highlight, muted, symbols } from "./lib/colors.js";
|
|
@@ -69,6 +70,7 @@ AgentHub v${VERSION} - AI Agent 打包与分发平台
|
|
|
69
70
|
update 更新已安装 Agent 到最新版
|
|
70
71
|
rollback 回滚已安装 Agent 到指定版本
|
|
71
72
|
stats 查看 Agent 统计信息
|
|
73
|
+
doctor 诊断 AgentHub 安装和环境问题
|
|
72
74
|
serve 启动 Web + API 服务
|
|
73
75
|
|
|
74
76
|
选项:
|
|
@@ -241,6 +243,22 @@ agenthub stats - 查看 Agent 统计信息
|
|
|
241
243
|
|
|
242
244
|
示例:
|
|
243
245
|
agenthub stats workspace --registry ./.registry
|
|
246
|
+
`,
|
|
247
|
+
doctor: `
|
|
248
|
+
agenthub doctor - 诊断 AgentHub 安装和环境问题
|
|
249
|
+
|
|
250
|
+
用法:
|
|
251
|
+
agenthub doctor [options]
|
|
252
|
+
|
|
253
|
+
选项:
|
|
254
|
+
--full 运行完整诊断(包括测试套件)
|
|
255
|
+
--no-network 跳过网络连接检查
|
|
256
|
+
--server <url> 指定服务器地址检查(默认: https://agenthub.cyou)
|
|
257
|
+
|
|
258
|
+
示例:
|
|
259
|
+
agenthub doctor
|
|
260
|
+
agenthub doctor --full
|
|
261
|
+
agenthub doctor --no-network
|
|
244
262
|
`,
|
|
245
263
|
serve: `
|
|
246
264
|
agenthub serve - 启动完整服务(前端+后端)
|
|
@@ -418,6 +436,14 @@ async function main() {
|
|
|
418
436
|
return;
|
|
419
437
|
}
|
|
420
438
|
|
|
439
|
+
case "doctor": {
|
|
440
|
+
const result = await doctorCommand(options);
|
|
441
|
+
if (!result.healthy) {
|
|
442
|
+
process.exitCode = 1;
|
|
443
|
+
}
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
|
|
421
447
|
case "serve": {
|
|
422
448
|
const result = await serveCommand(options);
|
|
423
449
|
console.log(success(`Server listening at ${result.baseUrl}`));
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor Command
|
|
3
|
+
* 诊断 AgentHub 安装和环境问题
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execSync } from "node:child_process";
|
|
7
|
+
import { access, constants, readFile } from "node:fs/promises";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { success, error, warning, info as infoColor, highlight, muted } from "../lib/colors.js";
|
|
10
|
+
|
|
11
|
+
const PROJECT_ROOT = path.resolve(import.meta.dirname, "../..");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 检查 Node.js 版本
|
|
15
|
+
*/
|
|
16
|
+
function checkNodeVersion() {
|
|
17
|
+
const version = process.version;
|
|
18
|
+
const major = parseInt(version.slice(1).split(".")[0], 10);
|
|
19
|
+
const required = 18;
|
|
20
|
+
const passed = major >= required;
|
|
21
|
+
return {
|
|
22
|
+
name: "Node.js 版本",
|
|
23
|
+
passed,
|
|
24
|
+
message: passed
|
|
25
|
+
? `${version} (需要 >= ${required})`
|
|
26
|
+
: `${version} (需要 >= ${required})`,
|
|
27
|
+
severity: passed ? "ok" : "error",
|
|
28
|
+
fix: passed ? null : `升级 Node.js 到 v${required} 或更高版本`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 检查 npm 包安装
|
|
34
|
+
*/
|
|
35
|
+
async function checkNpmPackage() {
|
|
36
|
+
try {
|
|
37
|
+
const packageJson = await readFile(
|
|
38
|
+
path.join(PROJECT_ROOT, "package.json"),
|
|
39
|
+
"utf8"
|
|
40
|
+
);
|
|
41
|
+
const pkg = JSON.parse(packageJson);
|
|
42
|
+
return {
|
|
43
|
+
name: "AgentHub 包",
|
|
44
|
+
passed: true,
|
|
45
|
+
message: `v${pkg.version} 已安装`,
|
|
46
|
+
severity: "ok",
|
|
47
|
+
fix: null,
|
|
48
|
+
};
|
|
49
|
+
} catch {
|
|
50
|
+
return {
|
|
51
|
+
name: "AgentHub 包",
|
|
52
|
+
passed: false,
|
|
53
|
+
message: "无法读取 package.json",
|
|
54
|
+
severity: "error",
|
|
55
|
+
fix: "重新安装 AgentHub: npm install -g @zshuangmu/agenthub",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 检查 CLI 可执行文件
|
|
62
|
+
*/
|
|
63
|
+
async function checkCliExecutable() {
|
|
64
|
+
try {
|
|
65
|
+
const result = execSync("node src/cli.js --version", {
|
|
66
|
+
cwd: PROJECT_ROOT,
|
|
67
|
+
encoding: "utf8",
|
|
68
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
69
|
+
});
|
|
70
|
+
const version = result.trim();
|
|
71
|
+
return {
|
|
72
|
+
name: "CLI 可执行文件",
|
|
73
|
+
passed: true,
|
|
74
|
+
message: version,
|
|
75
|
+
severity: "ok",
|
|
76
|
+
fix: null,
|
|
77
|
+
};
|
|
78
|
+
} catch (err) {
|
|
79
|
+
return {
|
|
80
|
+
name: "CLI 可执行文件",
|
|
81
|
+
passed: false,
|
|
82
|
+
message: "CLI 无法执行",
|
|
83
|
+
severity: "error",
|
|
84
|
+
fix: "检查 Node.js 安装和文件权限",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 检查测试套件
|
|
91
|
+
*/
|
|
92
|
+
async function checkTests() {
|
|
93
|
+
try {
|
|
94
|
+
const result = execSync("npm test", {
|
|
95
|
+
cwd: PROJECT_ROOT,
|
|
96
|
+
encoding: "utf8",
|
|
97
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
98
|
+
timeout: 30000,
|
|
99
|
+
});
|
|
100
|
+
const match = result.match(/ℹ tests (\d+).*ℹ pass (\d+).*ℹ fail (\d+)/s);
|
|
101
|
+
if (match) {
|
|
102
|
+
const [, total, passed, failed] = match;
|
|
103
|
+
const passRate = (parseInt(passed, 10) / parseInt(total, 10)) * 100;
|
|
104
|
+
return {
|
|
105
|
+
name: "测试套件",
|
|
106
|
+
passed: parseInt(failed, 10) === 0,
|
|
107
|
+
message: `${passed}/${total} 通过 (${passRate.toFixed(0)}%)`,
|
|
108
|
+
severity: parseInt(failed, 10) > 0 ? "warning" : "ok",
|
|
109
|
+
fix: parseInt(failed, 10) > 0 ? "运行 npm test 查看失败详情" : null,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
name: "测试套件",
|
|
114
|
+
passed: true,
|
|
115
|
+
message: "测试通过",
|
|
116
|
+
severity: "ok",
|
|
117
|
+
fix: null,
|
|
118
|
+
};
|
|
119
|
+
} catch (err) {
|
|
120
|
+
return {
|
|
121
|
+
name: "测试套件",
|
|
122
|
+
passed: false,
|
|
123
|
+
message: "测试执行失败",
|
|
124
|
+
severity: "warning",
|
|
125
|
+
fix: "运行 npm test 查看失败详情",
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 检查样本 Agent
|
|
132
|
+
*/
|
|
133
|
+
async function checkSampleAgents() {
|
|
134
|
+
const samplesDir = path.join(PROJECT_ROOT, "samples");
|
|
135
|
+
const expectedSamples = [
|
|
136
|
+
"code-review-assistant",
|
|
137
|
+
"team-knowledge-assistant",
|
|
138
|
+
"product-doc-writer",
|
|
139
|
+
];
|
|
140
|
+
const results = [];
|
|
141
|
+
|
|
142
|
+
for (const sample of expectedSamples) {
|
|
143
|
+
const samplePath = path.join(samplesDir, sample);
|
|
144
|
+
try {
|
|
145
|
+
await access(samplePath, constants.R_OK);
|
|
146
|
+
results.push({ name: sample, exists: true });
|
|
147
|
+
} catch {
|
|
148
|
+
results.push({ name: sample, exists: false });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const existing = results.filter((r) => r.exists).length;
|
|
153
|
+
const passed = existing === expectedSamples.length;
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
name: "样本 Agent",
|
|
157
|
+
passed,
|
|
158
|
+
message: `${existing}/${expectedSamples.length} 可用`,
|
|
159
|
+
severity: passed ? "ok" : "warning",
|
|
160
|
+
fix: passed ? null : `缺失样本: ${results
|
|
161
|
+
.filter((r) => !r.exists)
|
|
162
|
+
.map((r) => r.name)
|
|
163
|
+
.join(", ")}`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 检查文档
|
|
169
|
+
*/
|
|
170
|
+
async function checkDocumentation() {
|
|
171
|
+
const docsDir = path.join(PROJECT_ROOT, "docs");
|
|
172
|
+
const expectedDocs = [
|
|
173
|
+
"faq.md",
|
|
174
|
+
"growth-plan.md",
|
|
175
|
+
"team-distribution-guide.md",
|
|
176
|
+
"quick-start-3-steps.md",
|
|
177
|
+
];
|
|
178
|
+
const results = [];
|
|
179
|
+
|
|
180
|
+
for (const doc of expectedDocs) {
|
|
181
|
+
const docPath = path.join(docsDir, doc);
|
|
182
|
+
try {
|
|
183
|
+
await access(docPath, constants.R_OK);
|
|
184
|
+
results.push({ name: doc, exists: true });
|
|
185
|
+
} catch {
|
|
186
|
+
results.push({ name: doc, exists: false });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const existing = results.filter((r) => r.exists).length;
|
|
191
|
+
const passed = existing >= expectedDocs.length - 1; // 允许缺少1个
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
name: "文档完整性",
|
|
195
|
+
passed,
|
|
196
|
+
message: `${existing}/${expectedDocs.length} 文档可用`,
|
|
197
|
+
severity: passed ? "ok" : "warning",
|
|
198
|
+
fix: passed ? null : `检查 docs/ 目录`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* 检查网络连接 (可选)
|
|
204
|
+
*/
|
|
205
|
+
async function checkNetworkConnection(serverUrl) {
|
|
206
|
+
const url = serverUrl || "https://agenthub.cyou";
|
|
207
|
+
try {
|
|
208
|
+
const controller = new AbortController();
|
|
209
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
210
|
+
|
|
211
|
+
const response = await fetch(url, {
|
|
212
|
+
method: "HEAD",
|
|
213
|
+
signal: controller.signal,
|
|
214
|
+
});
|
|
215
|
+
clearTimeout(timeout);
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
name: "网络连接",
|
|
219
|
+
passed: response.ok,
|
|
220
|
+
message: `${url} - ${response.status}`,
|
|
221
|
+
severity: response.ok ? "ok" : "warning",
|
|
222
|
+
fix: response.ok ? null : "检查网络连接或服务器状态",
|
|
223
|
+
};
|
|
224
|
+
} catch (err) {
|
|
225
|
+
return {
|
|
226
|
+
name: "网络连接",
|
|
227
|
+
passed: false,
|
|
228
|
+
message: `${url} - 连接失败`,
|
|
229
|
+
severity: "warning",
|
|
230
|
+
fix: "检查网络连接或稍后重试",
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 运行所有诊断检查
|
|
237
|
+
*/
|
|
238
|
+
export async function doctorCommand(options) {
|
|
239
|
+
const checks = [];
|
|
240
|
+
|
|
241
|
+
console.log(`\n${highlight("🔍 AgentHub 诊断检查")}\n`);
|
|
242
|
+
console.log(`${muted("检查 AgentHub 安装和环境配置...")}\n`);
|
|
243
|
+
|
|
244
|
+
// 基础检查
|
|
245
|
+
checks.push(checkNodeVersion());
|
|
246
|
+
checks.push(await checkNpmPackage());
|
|
247
|
+
checks.push(await checkCliExecutable());
|
|
248
|
+
|
|
249
|
+
// 测试检查
|
|
250
|
+
if (options.full) {
|
|
251
|
+
checks.push(await checkTests());
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 内容检查
|
|
255
|
+
checks.push(await checkSampleAgents());
|
|
256
|
+
checks.push(await checkDocumentation());
|
|
257
|
+
|
|
258
|
+
// 网络检查
|
|
259
|
+
if (options.network !== false) {
|
|
260
|
+
checks.push(await checkNetworkConnection(options.server));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 输出结果
|
|
264
|
+
let passed = 0;
|
|
265
|
+
let warnings = 0;
|
|
266
|
+
let errors = 0;
|
|
267
|
+
|
|
268
|
+
for (const check of checks) {
|
|
269
|
+
let symbol;
|
|
270
|
+
let colorFn;
|
|
271
|
+
switch (check.severity) {
|
|
272
|
+
case "ok":
|
|
273
|
+
symbol = "✅";
|
|
274
|
+
colorFn = success;
|
|
275
|
+
passed++;
|
|
276
|
+
break;
|
|
277
|
+
case "warning":
|
|
278
|
+
symbol = "⚠️";
|
|
279
|
+
colorFn = warning;
|
|
280
|
+
warnings++;
|
|
281
|
+
break;
|
|
282
|
+
case "error":
|
|
283
|
+
symbol = "❌";
|
|
284
|
+
colorFn = error;
|
|
285
|
+
errors++;
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
console.log(` ${symbol} ${check.name}: ${colorFn(check.message)}`);
|
|
290
|
+
if (check.fix) {
|
|
291
|
+
console.log(` ${muted("建议:")} ${check.fix}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 总结
|
|
296
|
+
console.log(`\n${muted("─".repeat(40))}\n`);
|
|
297
|
+
|
|
298
|
+
const total = checks.length;
|
|
299
|
+
if (errors === 0 && warnings === 0) {
|
|
300
|
+
console.log(success(`🎉 所有检查通过!(${passed}/${total})`));
|
|
301
|
+
console.log(`${infoColor("AgentHub 已就绪,可以正常使用。")}\n`);
|
|
302
|
+
} else if (errors === 0) {
|
|
303
|
+
console.log(warning(`⚠️ 检查完成,有 ${warnings} 个警告 (${passed}/${total} 通过)`));
|
|
304
|
+
console.log(`${infoColor("AgentHub 可以使用,但建议解决上述警告。")}\n`);
|
|
305
|
+
} else {
|
|
306
|
+
console.log(error(`❌ 检查失败,有 ${errors} 个错误 (${passed}/${total} 通过)`));
|
|
307
|
+
console.log(`${infoColor("请先解决上述错误再使用 AgentHub。")}\n`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
passed,
|
|
312
|
+
warnings,
|
|
313
|
+
errors,
|
|
314
|
+
total,
|
|
315
|
+
healthy: errors === 0,
|
|
316
|
+
checks,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 格式化输出
|
|
322
|
+
*/
|
|
323
|
+
export function formatDoctorOutput(result) {
|
|
324
|
+
const lines = [];
|
|
325
|
+
lines.push(`\n${highlight("🔍 AgentHub 诊断检查")}\n`);
|
|
326
|
+
lines.push(`${muted("检查结果:")} ${result.passed}/${result.total} 通过\n`);
|
|
327
|
+
|
|
328
|
+
if (result.healthy) {
|
|
329
|
+
lines.push(success("✅ AgentHub 健康状态良好"));
|
|
330
|
+
} else {
|
|
331
|
+
lines.push(error("❌ AgentHub 需要修复"));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return lines.join("\n");
|
|
335
|
+
}
|
package/src/index.js
CHANGED
|
@@ -13,4 +13,5 @@ export { statsCommand, formatStatsOutput } from "./commands/stats.js";
|
|
|
13
13
|
export { listCommand, formatListOutput } from "./commands/list.js";
|
|
14
14
|
export { apiCommand } from "./commands/api.js";
|
|
15
15
|
export { webCommand } from "./commands/web.js";
|
|
16
|
+
export { doctorCommand } from "./commands/doctor.js";
|
|
16
17
|
export { parseSpec, compareVersionsDesc, getLatestVersion, sortByDownloadsAndTime } from "./lib/registry.js";
|