| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687 |
- // lib/doctor.mjs — 环境自检
- // 检查 node / ffmpeg / ffprobe / 凭据 / 各通道探活 / 可选依赖,给出可操作结论。
- import { VERSION, checkVersionConsistency, log } from './index.mjs';
- import { describeConfig, loadConfig } from './config.mjs';
- import { checkFfmpeg, checkDemucs } from './ffmpeg.mjs';
- import { probeAll } from './providers.mjs';
- export async function runDoctor(argv = []) {
- if (argv.includes('--help') || argv.includes('-h')) {
- console.log(`
- fmode-mv doctor — 环境自检
- 检查项:Node 版本 / 三处版本一致性 / ffmpeg & ffprobe / 凭据 / 可选依赖(demucs,edge-tts) / 各通道探活
- 用法:
- fmode-mv doctor
- 无需参数。全部通过返回 0,有阻塞项返回 1。
- `);
- return 0;
- }
- let fail = 0;
- let warn = 0;
- console.log(`\n${'fmode-mv doctor'.padEnd(30, ' ')} v${VERSION}\n`);
- // 1. Node
- const major = Number(process.versions.node.split('.')[0]);
- (major >= 18 ? log.ok : log.err)(`Node.js ${process.versions.node}${major >= 18 ? '' : ' —— 需要 >= 18(内置 fetch)'}`);
- if (major < 18) fail++;
- // 2. 版本一致性
- const v = checkVersionConsistency();
- (v.ok ? log.ok : log.err)(
- `版本一致性:index=${v.version} package=${v.pkgVersion} SKILL.md=${v.skillVersion}` + (v.ok ? '' : ' ← 不一致')
- );
- if (!v.ok) fail++;
- // 3. ffmpeg / ffprobe
- const ff = checkFfmpeg();
- for (const k of ['ffmpeg', 'ffprobe']) {
- (ff[k].available ? log.ok : log.err)(`${k}: ${ff[k].version || '未找到'}`);
- if (!ff[k].available) fail++;
- }
- // 4. 凭据
- const cfg = loadConfig();
- if (cfg.token) {
- log.ok(`凭据已加载(${cfg.tokenSource}),令牌已脱敏:${'*'.repeat(6)}`);
- } else {
- log.err('未找到凭据:设置 FEME_API_TOKEN,或在 /opt/data/fmode-identity.json 写入 newapi_token');
- fail++;
- }
- log.dim(`网关: ${cfg.baseUrl}`);
- log.dim(`身份文件: ${cfg.identityPath || '未找到'}`);
- // 5. 可选依赖
- const dm = checkDemucs();
- (dm.available ? log.ok : log.warn)(`demucs(高质量音轨分离,可选): ${dm.available ? dm.version : '未安装 —— 将用 ffmpeg 相位相减兜底'}`);
- if (!dm.available) warn++;
- // 6. 通道探活
- console.log('');
- log.step('通道探活(轻量,不做真实提交)');
- if (cfg.token) {
- const rows = await probeAll({ deep: false });
- for (const r of rows) {
- const mark = r.available === true ? '✓' : r.available === null ? '·' : '!';
- const paint = r.available === true ? log.ok : r.available === null ? log.dim : log.warn;
- paint(`${r.kind.padEnd(8)} ${r.detail}${r.model ? ` [${r.model}]` : ''}`);
- if (r.available === false) warn++;
- }
- } else {
- log.warn('跳过(无凭据)');
- }
- console.log('');
- console.log('─'.repeat(56));
- if (fail === 0) {
- console.log(`✅ 环境就绪${warn ? `(${warn} 项非阻塞提示)` : ''}`);
- console.log(' 下一步:fmode-mv providers 查看能力现状;fmode-mv lyrics "主题" 试跑');
- return 0;
- }
- console.log(`❌ ${fail} 项阻塞问题需要处理(见上)`);
- return 1;
- }
|