mihomo-cli 1.0.3 → 1.2.0

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/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ const path = require('path');
3
4
  const { spawn } = require('child_process');
4
5
 
5
6
  const config = require('./src/config');
6
7
  const kernel = require('./src/kernel');
7
8
  const subscription = require('./src/subscription');
8
9
  const processMgr = require('./src/process');
10
+ const overwrite = require('./src/overwrite');
9
11
 
10
12
  const VERSION = require('./package.json').version;
11
13
 
@@ -19,11 +21,11 @@ let exiting = false;
19
21
 
20
22
  process.on('SIGINT', () => {
21
23
  if (exiting) {
22
- console.log('\n 强制退出');
24
+ console.log('\n强制退出');
23
25
  process.exit(1);
24
26
  }
25
27
  exiting = true;
26
- console.log('\n 正在退出...');
28
+ console.log('\n正在退出...');
27
29
  process.exit(0);
28
30
  });
29
31
 
@@ -32,16 +34,16 @@ process.on('SIGTERM', () => {
32
34
  });
33
35
 
34
36
  process.on('uncaughtException', (e) => {
35
- console.error('\n 未捕获的异常: ' + e.message);
37
+ console.error('\n未捕获的异常: ' + e.message);
36
38
  if (e.stack) {
37
- console.error(' ' + e.stack.split('\n').slice(1).join('\n '));
39
+ console.error(e.stack.split('\n').slice(1).join('\n'));
38
40
  }
39
41
  process.exit(1);
40
42
  });
41
43
 
42
44
  process.on('unhandledRejection', (reason) => {
43
45
  const msg = reason instanceof Error ? reason.message : String(reason);
44
- console.error('\n 未处理的 Promise 拒绝: ' + msg);
46
+ console.error('\n未处理的 Promise 拒绝: ' + msg);
45
47
  process.exit(1);
46
48
  });
47
49
 
@@ -49,21 +51,20 @@ function printShortHelp() {
49
51
  console.log('\nmihomo-cli v' + VERSION);
50
52
  console.log('别名: mihomo, mmc, mh\n');
51
53
  console.log('命令:\n' +
52
- ' start [tun|mixed] 启动/切换代理(重复执行可切换模式)\n' +
53
- ' stop 停止代理\n' +
54
- ' status 查看状态\n' +
55
- ' log 实时日志(-o 打开文件)\n' +
56
- ' logs 日志列表(当前 + 历史归档)\n' +
57
- ' ui [zash|dash|yacd] Web 界面\n' +
58
- ' kernel 更新内核\n' +
59
- ' sub add <url> [name] 添加订阅\n' +
60
- ' sub update [name] 更新订阅(无参更新所有)\n' +
61
- ' sub use <name> 切换默认订阅\n' +
62
- ' sub web [name] 打开订阅页面\n' +
63
- ' sub list 列出订阅\n' +
64
- ' reset 重置配置\n' +
65
- ' dirs 数据目录\n' +
66
- ' version 版本信息\n');
54
+ ' start [tun|mixed] 启动/切换代理\n' +
55
+ ' stop 停止代理\n' +
56
+ ' status 查看状态\n' +
57
+ ' ui [zash|dash|yacd] Web 界面\n' +
58
+ ' log 实时日志\n' +
59
+ ' logs 日志列表\n' +
60
+ ' subscription add <url> 添加订阅(别名 sub)\n' +
61
+ ' subscription update 更新订阅\n' +
62
+ ' subscription use <name> 切换默认订阅\n' +
63
+ ' overwrite [on|off] 覆写配置(别名 ow)\n' +
64
+ ' directory 数据目录(别名 dir)\n' +
65
+ ' kernel 更新内核\n' +
66
+ ' reset 重置配置\n' +
67
+ ' version 版本信息\n');
67
68
  }
68
69
 
69
70
  function printHelp() {
@@ -72,32 +73,39 @@ function printHelp() {
72
73
  '命令别名: mihomo, mmc, mh\n' +
73
74
  '\n' +
74
75
  '用法:\n' +
75
- ' mihomo-cli <命令> [选项]\n' +
76
+ ' mihomo <命令> [选项]\n' +
76
77
  '\n' +
77
- '命令:\n' +
78
- ' start [tun|mixed] 启动/切换代理 (默认 mixed, 重复执行可重启/切换模式)\n' +
79
- ' stop 停止代理\n' +
80
- ' status 查看状态\n' +
81
- ' log [-o] 实时日志(-o 用系统编辑器打开)\n' +
82
- ' logs [编号] [-n N] [-o] 列出/查看日志(0=当前,1+=归档,-n 指定行数,-o 打开)\n' +
83
- ' ui [zash|dash|yacd] 打开 Web UI (默认 zash)\n' +
84
- ' kernel [镜像|--no-mirror] 更新内核(指定镜像或 --no-mirror 直连)\n' +
85
- ' sub add <url> [name] 添加订阅\n' +
86
- ' sub update [name] 更新订阅 (无参更新所有)\n' +
87
- ' sub use <name> 设置默认订阅 (支持模糊匹配)\n' +
88
- ' sub web [name] 打开订阅页面\n' +
89
- ' sub list 列出订阅\n' +
90
- ' reset [--full] 重置用户数据 (--full 同时删除内核)\n' +
91
- ' dirs 显示数据目录位置\n' +
92
- ' help, -h 显示帮助\n' +
93
- ' version, -v 显示版本\n' +
78
+ '控制:\n' +
79
+ ' start [tun|mixed] 启动/切换代理 (默认 mixed)\n' +
80
+ ' stop 停止代理\n' +
81
+ ' status 查看状态\n' +
82
+ '\n' +
83
+ '界面:\n' +
84
+ ' ui [zash|dash|yacd] 打开 Web UI (默认 zash)\n' +
85
+ ' log [-o] 实时日志(-o 打开文件)\n' +
86
+ ' logs [编号] [-n N] [-o] 日志列表(0=当前,1+=归档)\n' +
87
+ '\n' +
88
+ '订阅:\n' +
89
+ ' subscription add <url> [name] 添加订阅(别名 sub)\n' +
90
+ ' subscription update [name] 更新订阅(无参更新所有)\n' +
91
+ ' subscription use <name> 设置默认订阅\n' +
92
+ ' subscription web [name] 打开订阅页面\n' +
93
+ '\n' +
94
+ '配置:\n' +
95
+ ' overwrite [on|off] 覆写配置(别名 ow)\n' +
96
+ ' directory [open] 数据目录(别名 dir)\n' +
97
+ '\n' +
98
+ '系统:\n' +
99
+ ' kernel [镜像|--no-mirror] 更新内核\n' +
100
+ ' reset [--full] 重置用户数据 (--full 同时删除内核)\n' +
101
+ ' help, -h 显示帮助\n' +
102
+ ' version, -v 显示版本\n' +
94
103
  '\n' +
95
104
  '示例:\n' +
96
- ' mihomo-cli start # 启动/重启 Mixed 模式\n' +
97
- ' mihomo-cli start tun # 启动/切换到 TUN 模式 (透明代理)\n' +
98
- ' mihomo-cli ui # 打开默认 UI (zash)\n' +
99
- ' mihomo-cli ui dash # 打开 metacubexd\n' +
100
- ' mihomo-cli ui yacd # 打开 YACD\n' +
105
+ ' mihomo start # 启动/重启 Mixed 模式\n' +
106
+ ' mihomo start tun # 切换到 TUN 透明代理模式\n' +
107
+ ' mihomo sub add <url> # 添加订阅 (sub 是 subscription 别名)\n' +
108
+ ' mihomo ui # 打开 Web UI\n' +
101
109
  '\n' +
102
110
  '模式说明:\n' +
103
111
  ' mixed HTTP + SOCKS5 混合端口 (默认)\n' +
@@ -118,24 +126,59 @@ function printVersion() {
118
126
  function printStatus() {
119
127
  const status = processMgr.getStatus();
120
128
  const info = subscription.getConfigInfo();
129
+ const owEnabled = overwrite.isOverwriteEnabled();
130
+ const owFiles = overwrite.listOverwriteFiles().files;
131
+ const activeSub = getActiveSubscription();
121
132
 
122
133
  console.log('');
123
- console.log(' 状态: ' + (status.running ? '运行中' : '已停止'));
134
+ let modeLabel = '';
135
+ if (info && status.running) {
136
+ modeLabel = info.tun ? ' (TUN)' : ' (Mixed)';
137
+ }
138
+ console.log('状态: ' + (status.running ? '运行中' : '已停止') + modeLabel);
139
+ console.log('内核: ' + (status.kernelVersion || '未安装'));
140
+
124
141
  if (status.pid) {
125
- console.log(' PID: ' + status.pid);
142
+ console.log('PID: ' + status.pid);
126
143
  if (status.processInfo) {
127
- console.log(' 内存: ' + status.processInfo.memory);
128
- if (status.processInfo.cpu) {
129
- console.log(' CPU: ' + status.processInfo.cpu);
130
- }
144
+ console.log('内存: ' + status.processInfo.memory);
131
145
  }
132
146
  }
147
+
133
148
  if (info) {
134
- console.log(' 节点: ' + info.proxies);
135
- console.log(' 端口: ' + info.port);
136
- console.log(' TUN: ' + (info.tun ? '启用' : '未启用'));
149
+ if (info.mixedPort) {
150
+ console.log('端口: ' + info.mixedPort);
151
+ } else {
152
+ let ports = [];
153
+ if (info.httpPort) ports.push('HTTP:' + info.httpPort);
154
+ if (info.socksPort) ports.push('SOCKS:' + info.socksPort);
155
+ console.log('端口: ' + (ports.length > 0 ? ports.join(', ') : '未知'));
156
+ }
157
+ }
158
+
159
+ if (activeSub) {
160
+ let subLine = '订阅: ' + activeSub.name;
161
+ if (info) {
162
+ let parts = [];
163
+ if (info.proxyGroups && info.proxyGroups > 0) {
164
+ parts.push(info.proxyGroups + ' 组');
165
+ }
166
+ parts.push(info.proxies + ' 节点');
167
+ subLine += ' (' + parts.join(', ') + ')';
168
+ }
169
+ console.log(subLine);
170
+ } else {
171
+ console.log('订阅: 未配置');
172
+ }
173
+
174
+ if (owEnabled && owFiles.length > 0) {
175
+ const names = owFiles.map(f => f.name).join(', ');
176
+ console.log('覆写: 已启用 (' + names + ')');
177
+ } else if (owEnabled) {
178
+ console.log('覆写: 已启用 (无文件)');
179
+ } else {
180
+ console.log('覆写: 已禁用');
137
181
  }
138
- console.log(' 内核: ' + (status.kernelVersion || '未安装'));
139
182
  console.log('');
140
183
  }
141
184
 
@@ -147,7 +190,7 @@ function getActiveSubscription() {
147
190
  return subs[0];
148
191
  }
149
192
 
150
- function findSubsFuzzy(subs, pattern) {
193
+ function findSubscriptionFuzzy(subs, pattern) {
151
194
  const lowerPattern = pattern.toLowerCase();
152
195
  let exact = [];
153
196
  let prefix = [];
@@ -169,17 +212,17 @@ function findSubsFuzzy(subs, pattern) {
169
212
  return includes;
170
213
  }
171
214
 
172
- function pickSingleSub(subs, pattern, actionName) {
215
+ function pickSingleSubscription(subs, pattern, actionName) {
173
216
  if (subs.length === 0) {
174
- console.error(' 错误: 未找到匹配 "' + pattern + '" 的订阅');
217
+ console.error('错误: 未找到匹配 "' + pattern + '" 的订阅');
175
218
  process.exit(1);
176
219
  }
177
220
  if (subs.length === 1) {
178
221
  return subs[0];
179
222
  }
180
- console.error(' 错误: 匹配到多个订阅,请更精确指定');
181
- console.log('\n 匹配的订阅:');
182
- subs.forEach(s => console.log(' ' + s.name));
223
+ console.error('错误: 匹配到多个订阅,请更精确指定');
224
+ console.log('\n匹配的订阅:');
225
+ subs.forEach(s => console.log(' ' + s.name));
183
226
  process.exit(1);
184
227
  }
185
228
 
@@ -212,10 +255,19 @@ function getNonFlagArg(args, startIdx) {
212
255
 
213
256
  function openLogFile(logPath, label) {
214
257
  const displayLabel = label || logPath;
215
- console.log(' 用系统默认程序打开: ' + displayLabel);
258
+ console.log('用系统默认程序打开: ' + displayLabel);
216
259
  const success = processMgr.openUrl(logPath);
217
260
  if (!success) {
218
- console.log(' 请手动打开: ' + logPath);
261
+ console.log('请手动打开: ' + logPath);
262
+ }
263
+ }
264
+
265
+ function openDir(dirPath, label) {
266
+ const displayLabel = label || dirPath;
267
+ console.log('正在打开: ' + displayLabel);
268
+ const success = processMgr.openUrl(dirPath);
269
+ if (!success) {
270
+ console.log('请手动打开: ' + dirPath);
219
271
  }
220
272
  }
221
273
 
@@ -223,11 +275,11 @@ function viewLogWithTail(logPath, options) {
223
275
  const follow = options && options.follow;
224
276
  const lines = (options && options.lines) || 100;
225
277
 
226
- console.log(' 日志: ' + logPath);
278
+ console.log('日志: ' + logPath);
227
279
  if (follow) {
228
- console.log(' 按 Ctrl+C 退出\n');
280
+ console.log('按 Ctrl+C 退出\n');
229
281
  } else {
230
- console.log(' 显示最后 ' + lines + ' 行\n');
282
+ console.log('显示最后 ' + lines + ' 行\n');
231
283
  }
232
284
 
233
285
  const tailArgs = [];
@@ -239,14 +291,14 @@ function viewLogWithTail(logPath, options) {
239
291
 
240
292
  tail.on('close', () => process.exit(0));
241
293
  tail.on('error', (e) => {
242
- console.error(' 无法读取日志: ' + e.message);
294
+ console.error('无法读取日志: ' + e.message);
243
295
  process.exit(1);
244
296
  });
245
297
  }
246
298
 
247
299
  async function cmdStart(args) {
248
300
  if (!config.hasKernel()) {
249
- console.error(' 错误: 未找到内核,请运行 \'mihomo-cli kernel\'');
301
+ console.error('错误: 未找到内核,请运行 "mihomo kernel"');
250
302
  process.exit(1);
251
303
  }
252
304
 
@@ -254,7 +306,7 @@ async function cmdStart(args) {
254
306
 
255
307
  const sub = getActiveSubscription();
256
308
  if (!sub) {
257
- console.error(' 错误: 没有订阅,请先添加订阅');
309
+ console.error('错误: 没有订阅,请先添加订阅');
258
310
  process.exit(1);
259
311
  }
260
312
 
@@ -266,40 +318,42 @@ async function cmdStart(args) {
266
318
 
267
319
  if (hasProcess) {
268
320
  const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
269
- console.log(' 停止 ' + count + ' 个进程...');
321
+ console.log('停止 ' + count + ' 个进程...');
270
322
  }
271
323
 
272
324
  // 总是调用 stop(即使没进程也会清理 PID 文件和运行时目录)
273
325
  const stopResult = processMgr.stop(true);
274
326
 
275
327
  if (stopResult.remaining && stopResult.remaining.length > 0) {
276
- console.error(' 部分进程无法终止: ' + stopResult.remaining.join(', '));
277
- console.error(' 请手动运行: sudo pkill -9 mihomo');
328
+ console.error('部分进程未终止: ' + stopResult.remaining.join(', '));
329
+ console.error('请手动运行: sudo pkill -9 mihomo');
278
330
  process.exit(1);
279
331
  }
280
332
 
281
333
  if (hasProcess) {
282
- console.log(' 已停止\n');
334
+ console.log('已停止\n');
283
335
  }
284
336
 
285
337
  let cfgInfo;
286
338
  try {
287
339
  cfgInfo = subscription.prepareConfigForStart(targetMode, sub.name);
288
340
  } catch (e) {
289
- console.error(' 配置错误: ' + e.message);
341
+ console.error('配置错误: ' + e.message);
290
342
  process.exit(1);
291
343
  }
292
344
 
293
345
  const modeLabel = targetMode === 'tun' ? 'TUN' : 'Mixed';
294
- const groupsLabel = cfgInfo.proxyGroups ? cfgInfo.proxyGroups + ' 组' : '';
295
- const countLabel = groupsLabel ? (groupsLabel + ' ' + cfgInfo.proxies + ' 节点') : (cfgInfo.proxies + ' 节点');
296
- console.log(' ' + [modeLabel, sub.name, countLabel].join(' · '));
346
+ const parts = [];
347
+ if (cfgInfo.proxyGroups && cfgInfo.proxyGroups > 0) parts.push(cfgInfo.proxyGroups + ' ');
348
+ parts.push(cfgInfo.proxies + ' 节点');
349
+ console.log([modeLabel, sub.name, parts.join(', ')].join(' · '));
297
350
 
298
351
  try {
299
352
  const result = await processMgr.start(targetMode);
300
- console.log(' 已启动 (PID ' + result.pid + ')');
353
+ console.log('已启动 (PID ' + result.pid + ')');
354
+ printStatus();
301
355
  } catch (e) {
302
- console.error(' 启动失败: ' + e.message.split('\n')[0]);
356
+ console.error('启动失败: ' + e.message.split('\n')[0]);
303
357
  process.exit(1);
304
358
  }
305
359
  }
@@ -307,19 +361,19 @@ async function cmdStart(args) {
307
361
  async function cmdStop() {
308
362
  const pids = processMgr.getAllMihomoPids();
309
363
  if (pids.length === 0) {
310
- console.log(' 未在运行');
364
+ console.log('未在运行');
311
365
  return;
312
366
  }
313
367
 
314
- console.log(' 停止 ' + pids.length + ' 个进程...');
368
+ console.log('停止 ' + pids.length + ' 个进程...');
315
369
  const result = processMgr.stop(true);
316
370
 
317
371
  if (result.remaining && result.remaining.length > 0) {
318
- console.error(' 部分进程未终止: ' + result.remaining.join(', '));
319
- console.error(' 请手动运行: sudo pkill -9 mihomo');
372
+ console.error('部分进程未终止: ' + result.remaining.join(', '));
373
+ console.error('请手动运行: sudo pkill -9 mihomo');
320
374
  process.exit(1);
321
375
  }
322
- console.log(' 已停止');
376
+ console.log('已停止');
323
377
  }
324
378
 
325
379
  function cmdUi(args) {
@@ -327,17 +381,17 @@ function cmdUi(args) {
327
381
  const url = UI_URLS[uiName];
328
382
 
329
383
  if (!url) {
330
- console.error(' 错误: 未知的 UI "' + uiName + '"');
331
- console.error(' 可用 UI: zash (默认), dash, yacd');
384
+ console.error('错误: 未知的 UI "' + uiName + '"');
385
+ console.error('可用 UI: zash (默认), dash, yacd');
332
386
  process.exit(1);
333
387
  }
334
388
 
335
- console.log(' 打开 Web UI: ' + uiName);
336
- console.log(' 地址: ' + url);
389
+ console.log('打开 Web UI: ' + uiName);
390
+ console.log('地址: ' + url);
337
391
 
338
392
  const success = processMgr.openUrl(url);
339
393
  if (!success) {
340
- console.log(' 请手动访问上面的地址');
394
+ console.log('请手动访问上面的地址');
341
395
  }
342
396
  }
343
397
 
@@ -368,8 +422,8 @@ function cmdLogs(args) {
368
422
  }
369
423
 
370
424
  if (!logPath) {
371
- console.error(' 错误: 未找到日志 "' + targetName + '"');
372
- console.log(' 使用 "mihomo-cli logs" 查看可用日志列表');
425
+ console.error('错误: 未找到日志 "' + targetName + '"');
426
+ console.log('使用 "mihomo logs" 查看可用日志列表');
373
427
  process.exit(1);
374
428
  }
375
429
 
@@ -391,12 +445,12 @@ function cmdLogs(args) {
391
445
  all.push(...logs.archives);
392
446
 
393
447
  if (all.length === 0) {
394
- console.log(' 暂无日志');
448
+ console.log('暂无日志');
395
449
  return;
396
450
  }
397
451
 
398
452
  console.log('');
399
- console.log(' 日志列表:');
453
+ console.log('日志列表:');
400
454
  console.log('');
401
455
 
402
456
  all.forEach((log, idx) => {
@@ -405,19 +459,19 @@ function cmdLogs(args) {
405
459
  const size = subscription.formatBytes(log.size);
406
460
  const name = log.isCurrent ? 'mihomo.log (当前运行中)' : log.name;
407
461
 
408
- console.log(' ' + num + '. ' + name);
409
- console.log(' 时间: ' + time + ' 大小: ' + size);
462
+ console.log(' ' + num + '. ' + name);
463
+ console.log(' 时间: ' + time + ' 大小: ' + size);
410
464
  if (!log.isCurrent) {
411
- console.log(' 查看: mihomo-cli logs ' + idx + ' 或 mihomo-cli logs -o ' + idx);
465
+ console.log(' 查看: mihomo logs ' + idx + ' 或 mihomo logs -o ' + idx);
412
466
  }
413
467
  console.log('');
414
468
  });
415
469
 
416
- console.log(' 用法:');
417
- console.log(' mihomo-cli logs 0 # 查看当前日志 (最后 100 行)');
418
- console.log(' mihomo-cli logs 1 # 查看第 1 个归档日志');
419
- console.log(' mihomo-cli logs 1 -n 200 # 查看 200 行');
420
- console.log(' mihomo-cli logs 1 -o # 用系统默认程序打开');
470
+ console.log('用法:');
471
+ console.log(' mihomo logs 0 # 查看当前日志 (最后 100 行)');
472
+ console.log(' mihomo logs 1 # 查看第 1 个归档日志');
473
+ console.log(' mihomo logs 1 -n 200 # 查看 200 行');
474
+ console.log(' mihomo logs 1 -o # 用系统默认程序打开');
421
475
  console.log('');
422
476
  }
423
477
 
@@ -473,107 +527,111 @@ async function cmdKernel(args) {
473
527
  const effectiveMirror = mirrorInfo.isOverride ? mirrorInfo.mirror : config.getGitHubMirror();
474
528
  const isDefault = !mirrorInfo.isOverride && effectiveMirror === config.DEFAULT_GITHUB_MIRROR;
475
529
 
476
- console.log(' 检查内核更新...');
530
+ console.log('检查内核更新...');
477
531
 
478
532
  if (mirrorInfo.isOverride) {
479
533
  if (effectiveMirror === null) {
480
- console.log(' 镜像: 直连(命令行指定 --no-mirror)');
534
+ console.log('镜像: 直连(命令行指定 --no-mirror)');
481
535
  } else {
482
- console.log(' 镜像: ' + effectiveMirror + ' (命令行指定)');
536
+ console.log('镜像: ' + effectiveMirror + ' (命令行指定)');
483
537
  }
484
538
  } else {
485
- console.log(' 镜像: ' + (effectiveMirror || '直连(无镜像)') + (isDefault && effectiveMirror ? ' (默认)' : ''));
539
+ console.log('镜像: ' + (effectiveMirror || '直连(无镜像)') + (isDefault && effectiveMirror ? ' (默认)' : ''));
486
540
  }
487
541
 
488
- console.log('\n 可用镜像:');
542
+ console.log('\n可用镜像:');
489
543
  config.AVAILABLE_MIRRORS.forEach(m => {
490
544
  const isCurrent = effectiveMirror && (
491
545
  effectiveMirror.includes('//' + m + '/') ||
492
546
  effectiveMirror.includes('//' + m + ':') ||
493
547
  effectiveMirror.endsWith('//' + m)
494
548
  );
495
- console.log(' ' + m + (isCurrent ? ' (当前)' : ''));
549
+ console.log(' ' + m + (isCurrent ? ' (当前)' : ''));
496
550
  });
497
551
 
498
- console.log('\n 用法:');
499
- console.log(' mihomo-cli kernel # 使用默认镜像');
500
- console.log(' mihomo-cli kernel hk.gh-proxy.org # 使用指定镜像');
501
- console.log(' mihomo-cli kernel --mirror hk.gh-proxy.org');
502
- console.log(' mihomo-cli kernel --no-mirror # 直连,不使用镜像');
552
+ console.log('\n用法:');
553
+ console.log(' mihomo kernel # 使用默认镜像');
554
+ console.log(' mihomo kernel hk.gh-proxy.org # 使用指定镜像');
555
+ console.log(' mihomo kernel --mirror hk.gh-proxy.org');
556
+ console.log(' mihomo kernel --no-mirror # 直连,不使用镜像');
503
557
  console.log('');
504
558
 
505
559
  try {
506
560
  const info = await kernel.checkUpdate();
507
- console.log(' 当前: ' + info.current);
508
- console.log(' 最新: ' + info.latest);
561
+ console.log('当前: ' + info.current);
562
+ console.log('最新: ' + info.latest);
509
563
 
510
564
  if (!info.needsUpdate) {
511
- console.log(' 已是最新版本');
565
+ console.log('已是最新版本');
512
566
  return;
513
567
  }
514
568
 
515
- console.log('\n 正在下载...');
569
+ console.log('\n正在下载...');
516
570
  const result = await kernel.downloadKernel((msg) => {
517
- console.log(' ' + msg);
571
+ console.log(msg);
518
572
  }, mirrorInfo.mirror); // 传递镜像参数(undefined = 用配置,null = 禁用)
519
- console.log(' 已更新到 ' + result.version);
573
+ console.log('已更新到 ' + result.version);
520
574
  } catch (e) {
521
- console.error(' 更新失败: ' + e.message);
575
+ console.error('更新失败: ' + e.message);
522
576
  process.exit(1);
523
577
  }
524
578
  }
525
579
 
526
- async function cmdSub(args) {
527
- const action = args[1];
580
+ async function printSubscriptionList() {
581
+ const updateResult = await subscription.autoUpdateStaleSubscriptions();
582
+ if (updateResult.total > 0) {
583
+ console.log('');
584
+ }
528
585
 
529
- if (!action || action === 'list') {
530
- const updateResult = await subscription.autoUpdateStaleSubscriptions();
531
- if (updateResult.total > 0) {
532
- console.log('');
586
+ const subs = config.getSubscriptionsWithCache();
587
+ if (subs.length === 0) {
588
+ console.log('没有订阅');
589
+ console.log('');
590
+ console.log('添加订阅: mihomo sub add <url> [name]');
591
+ console.log('');
592
+ return;
593
+ }
594
+ console.log('订阅列表:');
595
+ subs.forEach((s, i) => {
596
+ const time = subscription.formatDate(s.updated_at);
597
+ const defaultMark = i === 0 ? ' [默认]' : '';
598
+ const interval = s.update_interval || subscription.DEFAULT_UPDATE_INTERVAL_HOURS;
599
+ console.log(' ' + (i + 1) + '. ' + s.name + defaultMark);
600
+ console.log(' 更新: ' + time + ' (间隔: ' + interval + 'h)');
601
+
602
+ if (s.username) {
603
+ console.log(' 用户: ' + s.username);
533
604
  }
534
-
535
- const subs = config.getSubscriptionsWithCache();
536
- if (subs.length === 0) {
537
- console.log(' 没有订阅');
538
- console.log('\n 添加订阅:');
539
- console.log(' mihomo-cli sub add <url> [name]');
540
- return;
605
+ if (s.download !== undefined || s.total !== undefined) {
606
+ const used = (s.upload || 0) + (s.download || 0);
607
+ const usedStr = subscription.formatBytes(used);
608
+ const totalStr = subscription.formatBytes(s.total);
609
+ let percentStr = '';
610
+ if (s.total && s.total > 0) {
611
+ const percent = Math.min((used / s.total) * 100, 100);
612
+ percentStr = ' (' + percent.toFixed(1) + '%)';
613
+ }
614
+ console.log(' 流量: ' + usedStr + ' / ' + totalStr + percentStr);
615
+ }
616
+ if (s.expire !== undefined) {
617
+ console.log(' 到期: ' + subscription.formatTimestamp(s.expire));
541
618
  }
542
- console.log(' 订阅列表:');
543
- subs.forEach((s, i) => {
544
- const time = subscription.formatDate(s.updatedAt);
545
- const defaultMark = i === 0 ? ' [默认]' : '';
546
- const interval = s.updateInterval || subscription.DEFAULT_UPDATE_INTERVAL_HOURS;
547
- console.log(' ' + (i + 1) + '. ' + s.name + defaultMark);
548
- console.log(' 更新: ' + time + ' (间隔: ' + interval + 'h)');
619
+ if (s.web_page_url) {
620
+ console.log(' 页面: ' + s.web_page_url);
621
+ }
622
+ });
623
+ console.log('');
624
+ console.log('切换默认: mihomo sub use <name>');
625
+ console.log('更新订阅: mihomo sub update [name]');
626
+ console.log('打开页面: mihomo sub web [name]');
627
+ console.log('');
628
+ }
549
629
 
550
- if (s.username) {
551
- console.log(' 用户: ' + s.username);
552
- }
553
- if (s.download !== undefined || s.total !== undefined) {
554
- const used = (s.upload || 0) + (s.download || 0);
555
- const usedStr = subscription.formatBytes(used);
556
- const totalStr = subscription.formatBytes(s.total);
557
- let percentStr = '';
558
- if (s.total && s.total > 0) {
559
- const percent = Math.min((used / s.total) * 100, 100);
560
- percentStr = ' (' + percent.toFixed(1) + '%)';
561
- }
562
- console.log(' 流量: ' + usedStr + ' / ' + totalStr + percentStr);
563
- }
564
- if (s.expire !== undefined) {
565
- console.log(' 到期: ' + subscription.formatTimestamp(s.expire));
566
- }
567
- if (s.webPageUrl) {
568
- console.log(' 页面: ' + s.webPageUrl);
569
- }
570
- });
571
- console.log('\n 切换默认订阅:');
572
- console.log(' mihomo-cli sub use <name>');
573
- console.log(' 更新订阅:');
574
- console.log(' mihomo-cli sub update [name]');
575
- console.log(' 打开订阅页面:');
576
- console.log(' mihomo-cli sub web [name]');
630
+ async function cmdSubscription(args) {
631
+ const action = args[1];
632
+
633
+ if (!action || action === 'list') {
634
+ await printSubscriptionList();
577
635
  return;
578
636
  }
579
637
 
@@ -582,19 +640,24 @@ async function cmdSub(args) {
582
640
  const name = args[3] || 'default';
583
641
 
584
642
  if (!url || !url.startsWith('http')) {
585
- console.error(' 错误: 请提供有效的订阅 URL');
643
+ console.error('错误: 请提供有效的订阅 URL');
586
644
  process.exit(1);
587
645
  }
588
646
 
589
- console.log(' 添加订阅: ' + name);
647
+ console.log('添加订阅: ' + name);
590
648
  try {
591
649
  config.addSubscription(url, name);
592
650
  const info = await subscription.downloadSubscription(url, name);
593
- console.log(' 已添加 (节点: ' + info.proxies + ')');
651
+ const parts = [];
652
+ if (info.proxyGroups && info.proxyGroups > 0) parts.push(info.proxyGroups + ' 组');
653
+ parts.push(info.proxies + ' 节点');
654
+ console.log('已添加 (' + parts.join(', ') + ')');
594
655
  } catch (e) {
595
- console.error(' 添加失败: ' + e.message);
656
+ console.error('添加失败: ' + e.message);
596
657
  process.exit(1);
597
658
  }
659
+ console.log('');
660
+ await printSubscriptionList();
598
661
  return;
599
662
  }
600
663
 
@@ -603,37 +666,47 @@ async function cmdSub(args) {
603
666
  const subs = config.getSubscriptions();
604
667
 
605
668
  if (subs.length === 0) {
606
- console.error(' 错误: 没有订阅');
669
+ console.error('错误: 没有订阅');
607
670
  process.exit(1);
608
671
  }
609
672
 
610
673
  if (!name) {
611
- console.log(' 更新所有 ' + subs.length + ' 个订阅...');
674
+ console.log('更新所有 ' + subs.length + ' 个订阅...');
612
675
  const results = await Promise.all(subs.map(subscription.tryUpdateOne));
613
676
  let ok = 0;
614
677
  results.forEach(r => {
615
678
  if (r.success) {
616
679
  ok++;
617
- console.log(' ✓ ' + r.name + ': ' + r.proxies + ' 节点');
680
+ const parts = [];
681
+ if (r.proxyGroups && r.proxyGroups > 0) parts.push(r.proxyGroups + ' 组');
682
+ parts.push(r.proxies + ' 节点');
683
+ console.log('✓ ' + r.name + ': 已更新 (' + parts.join(', ') + ')');
618
684
  } else {
619
- console.log(' ✗ ' + r.name + ': 失败 (' + r.error.split('\n')[0] + ')');
685
+ console.log('✗ ' + r.name + ': 失败 (' + r.error.split('\n')[0] + ')');
620
686
  }
621
687
  });
622
688
  if (ok === 0) process.exit(1);
689
+ console.log('');
690
+ await printSubscriptionList();
623
691
  return;
624
692
  }
625
693
 
626
- const matches = findSubsFuzzy(subs, name);
627
- const target = pickSingleSub(matches, name, '更新');
694
+ const matches = findSubscriptionFuzzy(subs, name);
695
+ const target = pickSingleSubscription(matches, name, '更新');
628
696
 
629
- console.log(' 更新订阅: ' + target.name);
697
+ console.log('更新订阅: ' + target.name);
630
698
  try {
631
699
  const info = await subscription.downloadSubscription(target.url, target.name);
632
- console.log(' 已更新 (节点: ' + info.proxies + ')');
700
+ const parts = [];
701
+ if (info.proxyGroups && info.proxyGroups > 0) parts.push(info.proxyGroups + ' 组');
702
+ parts.push(info.proxies + ' 节点');
703
+ console.log('已更新 (' + parts.join(', ') + ')');
633
704
  } catch (e) {
634
- console.error(' 更新失败: ' + e.message);
705
+ console.error('更新失败: ' + e.message);
635
706
  process.exit(1);
636
707
  }
708
+ console.log('');
709
+ await printSubscriptionList();
637
710
  return;
638
711
  }
639
712
 
@@ -642,24 +715,50 @@ async function cmdSub(args) {
642
715
  const subs = config.getSubscriptions();
643
716
 
644
717
  if (!name) {
645
- console.error(' 错误: 请指定订阅名称');
718
+ console.error('错误: 请指定订阅名称');
646
719
  if (subs.length > 0) {
647
- console.log('\n 可用订阅:');
648
- subs.forEach(s => console.log(' ' + s.name));
720
+ console.log('\n可用订阅:');
721
+ subs.forEach(s => console.log(' ' + s.name));
649
722
  }
650
723
  process.exit(1);
651
724
  }
652
725
 
653
- const matches = findSubsFuzzy(subs, name);
654
- const target = pickSingleSub(matches, name, '切换');
726
+ const matches = findSubscriptionFuzzy(subs, name);
727
+ const target = pickSingleSubscription(matches, name, '切换');
728
+
729
+ // 检查是否已是当前默认订阅
730
+ const currentDefault = getActiveSubscription();
731
+ const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
732
+
733
+ if (isAlreadyDefault) {
734
+ console.log('"' + target.name + '" 已是当前默认订阅');
735
+ console.log('');
736
+ await printSubscriptionList();
737
+ return;
738
+ }
739
+
740
+ // 检查当前运行状态和模式
741
+ const status = processMgr.getStatus();
742
+ const cfgInfo = config.getConfigInfo();
743
+ const currentMode = cfgInfo && cfgInfo.tun ? 'tun' : 'mixed';
655
744
 
656
745
  const success = config.setDefaultSubscription(target.name);
657
746
  if (success) {
658
- console.log(' 已设置 "' + target.name + '" 为默认订阅');
747
+ console.log('已设置 "' + target.name + '" 为默认订阅');
659
748
  } else {
660
- console.error(' 错误: 未找到订阅 "' + name + '"');
749
+ console.error('错误: 未找到订阅 "' + name + '"');
661
750
  process.exit(1);
662
751
  }
752
+
753
+ // 如果正在运行,自动重启
754
+ if (status.running) {
755
+ console.log('');
756
+ await cmdStart(['start', currentMode]);
757
+ return;
758
+ }
759
+
760
+ console.log('');
761
+ await printSubscriptionList();
663
762
  return;
664
763
  }
665
764
 
@@ -668,45 +767,47 @@ async function cmdSub(args) {
668
767
  const subs = config.getSubscriptionsWithCache();
669
768
 
670
769
  if (subs.length === 0) {
671
- console.error(' 错误: 没有订阅');
770
+ console.error('错误: 没有订阅');
672
771
  process.exit(1);
673
772
  }
674
773
 
675
774
  let target;
676
775
  if (name) {
677
- const matches = findSubsFuzzy(subs, name);
678
- target = pickSingleSub(matches, name, '打开');
776
+ const matches = findSubscriptionFuzzy(subs, name);
777
+ target = pickSingleSubscription(matches, name, '打开');
679
778
  } else {
680
779
  target = subs[0];
681
780
  }
682
781
 
683
- let webPageUrl = target.webPageUrl;
782
+ let webPageUrl = target.web_page_url;
684
783
  if (!webPageUrl) {
685
- console.log(' 订阅信息中缺少页面地址,正在更新订阅...');
784
+ console.log('订阅信息中缺少页面地址,正在更新订阅...');
686
785
  try {
687
786
  const info = await subscription.downloadSubscription(target.url, target.name);
688
- if (info.webPageUrl) {
689
- webPageUrl = info.webPageUrl;
787
+ // 重新读取缓存获取 web_page_url
788
+ const cache = config.readSubscriptionsCache();
789
+ if (cache[target.name] && cache[target.name].web_page_url) {
790
+ webPageUrl = cache[target.name].web_page_url;
690
791
  } else {
691
- console.error(' 错误: 该订阅没有提供页面地址');
792
+ console.error('错误: 该订阅没有提供页面地址');
692
793
  process.exit(1);
693
794
  }
694
795
  } catch (e) {
695
- console.error(' 更新失败: ' + e.message);
796
+ console.error('更新失败: ' + e.message);
696
797
  process.exit(1);
697
798
  }
698
799
  }
699
800
 
700
- console.log(' 打开订阅页面: ' + webPageUrl);
801
+ console.log('打开订阅页面: ' + webPageUrl);
701
802
  const opened = processMgr.openUrl(webPageUrl);
702
803
  if (!opened) {
703
- console.log(' 请手动访问上面的地址');
804
+ console.log('请手动访问上面的地址');
704
805
  }
705
806
  return;
706
807
  }
707
808
 
708
- console.error(' 错误: 未知的订阅命令');
709
- console.log(' 用法: mihomo-cli sub [list|add|update|use|web]');
809
+ console.error('错误: 未知的订阅命令');
810
+ console.log('用法: mihomo sub [list|add|update|use|web]');
710
811
  process.exit(1);
711
812
  }
712
813
 
@@ -716,7 +817,7 @@ async function cmdReset(args) {
716
817
 
717
818
  const pids = processMgr.getAllMihomoPids();
718
819
  if (pids.length > 0) {
719
- console.log(' 停止 ' + pids.length + ' 个进程...');
820
+ console.log('停止 ' + pids.length + ' 个进程...');
720
821
  processMgr.cleanupAll(true);
721
822
  for (let i = 0; i < 50; i++) {
722
823
  if (processMgr.getAllMihomoPids().length === 0) break;
@@ -725,7 +826,7 @@ async function cmdReset(args) {
725
826
  }
726
827
 
727
828
  const mode = fullReset ? '完整重置 (含内核)' : '重置配置';
728
- console.log(' ' + mode);
829
+ console.log(mode);
729
830
 
730
831
  if (!skipConfirm) {
731
832
  const readline = require('readline');
@@ -735,39 +836,182 @@ async function cmdReset(args) {
735
836
  });
736
837
 
737
838
  const answer = await new Promise(resolve => {
738
- rl.question(' 确认? (y/N) ', (a) => {
839
+ rl.question('确认? (y/N) ', (a) => {
739
840
  rl.close();
740
841
  resolve(a);
741
842
  });
742
843
  });
743
844
 
744
845
  if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
745
- console.log(' 已取消');
846
+ console.log('已取消');
746
847
  return;
747
848
  }
748
849
  }
749
850
 
750
851
  const count = config.resetUserData({ keepKernel: !fullReset });
751
- console.log(' 已重置 ' + count + ' 项');
852
+ console.log('已重置 ' + count + ' 项');
853
+ }
854
+
855
+ function printOverwriteList() {
856
+ const info = overwrite.listOverwriteFiles();
857
+ console.log('状态: ' + (info.enabled ? '已启用' : '已禁用'));
858
+ console.log('目录: ' + info.dir);
859
+ console.log('');
860
+ if (info.files.length === 0) {
861
+ console.log('暂无覆写文件');
862
+ console.log('');
863
+ console.log('用法示例: 创建文件 ' + path.join(info.dir, '01-custom.yaml'));
864
+ console.log('');
865
+ } else {
866
+ console.log('覆写文件 (' + info.files.length + ' 个,按顺序加载):');
867
+ console.log('');
868
+ info.files.forEach((f, i) => {
869
+ const num = i < 10 ? ' ' + i : '' + i;
870
+ console.log(' ' + num + '. ' + f.name);
871
+ if (f.keys.length > 0) {
872
+ console.log(' 字段: ' + f.keys.join(', '));
873
+ }
874
+ });
875
+ console.log('');
876
+ }
877
+ console.log('启用覆写: mihomo ow on');
878
+ console.log('禁用覆写: mihomo ow off');
879
+ console.log('');
880
+ }
881
+
882
+ async function cmdOverwrite(args) {
883
+ const action = args && args[1];
884
+
885
+ // 检查当前运行状态和模式
886
+ const status = processMgr.getStatus();
887
+ const cfgInfo = config.getConfigInfo();
888
+ const currentMode = cfgInfo && cfgInfo.tun ? 'tun' : 'mixed';
889
+
890
+ if (action === 'on' || action === 'enable') {
891
+ // 如果已经启用,提示后直接返回
892
+ if (overwrite.isOverwriteEnabled()) {
893
+ console.log('覆写配置已是启用状态');
894
+ console.log('');
895
+ printOverwriteList();
896
+ return;
897
+ }
898
+
899
+ overwrite.setOverwriteEnabled(true);
900
+ console.log('已启用覆写配置');
901
+
902
+ // 如果正在运行,自动重启
903
+ if (status.running) {
904
+ console.log('');
905
+ await cmdStart(['start', currentMode]);
906
+ return;
907
+ }
908
+
909
+ console.log('');
910
+ printOverwriteList();
911
+ return;
912
+ }
913
+
914
+ if (action === 'off' || action === 'disable') {
915
+ // 如果已经禁用,提示后直接返回
916
+ if (!overwrite.isOverwriteEnabled()) {
917
+ console.log('覆写配置已是禁用状态');
918
+ console.log('');
919
+ printOverwriteList();
920
+ return;
921
+ }
922
+
923
+ overwrite.setOverwriteEnabled(false);
924
+ console.log('已禁用覆写配置');
925
+
926
+ // 如果正在运行,自动重启
927
+ if (status.running) {
928
+ console.log('');
929
+ await cmdStart(['start', currentMode]);
930
+ return;
931
+ }
932
+
933
+ console.log('');
934
+ printOverwriteList();
935
+ return;
936
+ }
937
+
938
+ // 无参数、list、ls 都显示文件列表
939
+ console.log('');
940
+ printOverwriteList();
752
941
  }
753
942
 
754
- function cmdDirs() {
943
+ // 目录目标映射(精确匹配)
944
+ const DIRECTORY_TARGETS = {
945
+ 'root': { path: null, label: '根目录' },
946
+ 'subs': { path: config.DIRS.subscriptions, label: '订阅目录' },
947
+ 'logs': { path: config.DIRS.logs, label: '日志目录' },
948
+ 'data': { path: config.DIRS.data, label: 'mihomo 数据目录' },
949
+ 'runtime': { path: config.DIRS.runtime, label: '运行时目录' },
950
+ 'overwrites': { path: config.DIRS.overwrites, label: '覆写目录' },
951
+ 'settings': { path: config.PATHS.settingsFile, label: '设置文件' },
952
+ 'kernel': { path: config.DIRS.core, label: '内核目录' },
953
+ };
954
+
955
+ function cmdDirectory(args) {
956
+ const action = args && args[1];
957
+
958
+ if (action === 'open') {
959
+ const target = args[2];
960
+
961
+ if (!target || target === 'root') {
962
+ openDir(config.USER_DATA_DIR, '根目录');
963
+ return;
964
+ }
965
+
966
+ const targetInfo = DIRECTORY_TARGETS[target.toLowerCase()];
967
+ if (targetInfo) {
968
+ const path = targetInfo.path || config.USER_DATA_DIR;
969
+ openDir(path, targetInfo.label);
970
+ return;
971
+ }
972
+
973
+ console.error('错误: 未知的目录目标 "' + target + '"');
974
+ console.log('');
975
+ console.log('可用目标:');
976
+ console.log(' root (默认) 根目录');
977
+ console.log(' subs 订阅目录');
978
+ console.log(' logs 日志目录');
979
+ console.log(' data mihomo 数据目录');
980
+ console.log(' runtime 运行时目录');
981
+ console.log(' overwrites 覆写目录');
982
+ console.log(' settings 设置文件 (settings.json)');
983
+ console.log(' kernel 内核目录');
984
+ console.log('');
985
+ process.exit(1);
986
+ }
987
+
988
+ // 无参数或未知参数:显示目录列表
755
989
  console.log('');
756
- console.log(' 数据目录位置:');
757
- console.log(' 根目录: ' + config.USER_DATA_DIR);
758
- console.log(' 全局设置: ' + config.PATHS.settingsFile);
759
- console.log(' 内核文件: ' + config.PATHS.mihomoBinary);
760
- console.log(' 订阅目录: ' + config.DIRS.subs);
761
- console.log(' - xxx.yaml (订阅原始配置,不修改)');
762
- console.log(' 运行时目录: ' + config.DIRS.runtime);
763
- console.log(' - config.yaml (启动时生成,stop 自动清除)');
764
- console.log(' - pid (PID 文件,stop 自动清除)');
765
- console.log(' 日志文件: ' + config.PATHS.logFile);
766
- console.log(' mihomo 数据: ' + config.DIRS.data);
767
- console.log(' - cache.db, Geo*.dat (mihomo 自行管理)');
990
+ console.log('数据目录位置:');
991
+ console.log(' 根目录: ' + config.USER_DATA_DIR);
992
+ console.log(' 全局设置: ' + config.PATHS.settingsFile);
993
+ console.log(' 内核文件: ' + config.PATHS.mihomoBinary);
994
+ console.log(' 订阅目录: ' + config.DIRS.subscriptions);
995
+ console.log(' - cache.json (订阅缓存:更新时间、流量等)');
996
+ console.log(' - xxx.yaml (订阅原始配置)');
997
+ console.log(' 运行时目录: ' + config.DIRS.runtime);
998
+ console.log(' - config.yaml (启动时生成,stop 自动清除)');
999
+ console.log(' - pid (PID 文件,stop 自动清除)');
1000
+ console.log(' 日志文件: ' + config.PATHS.logFile);
1001
+ console.log(' mihomo 数据: ' + config.DIRS.data);
1002
+ console.log(' - cache.db, Geo*.dat 等 (mihomo 自行管理)');
768
1003
  console.log('');
769
- console.log(' 环境变量:');
770
- console.log(' MIHOMO_CLI_DIR: 自定义根目录位置');
1004
+ console.log('打开目录:');
1005
+ console.log(' mihomo dir open 打开根目录');
1006
+ console.log(' mihomo dir open subs 打开订阅目录');
1007
+ console.log(' mihomo dir open logs 打开日志目录');
1008
+ console.log(' mihomo dir open runtime 打开运行时目录');
1009
+ console.log(' mihomo dir open overwrites 打开覆写目录');
1010
+ console.log(' mihomo dir open settings 打开设置文件');
1011
+ console.log(' mihomo dir open kernel 打开内核目录');
1012
+ console.log('');
1013
+ console.log('环境变量:');
1014
+ console.log(' MIHOMO_CLI_DIR: 自定义根目录位置');
771
1015
  console.log('');
772
1016
  }
773
1017
 
@@ -817,22 +1061,30 @@ async function main() {
817
1061
  break;
818
1062
  case 'sub':
819
1063
  case 'subscription':
820
- await cmdSub(args);
1064
+ case 'subscriptions':
1065
+ await cmdSubscription(args);
821
1066
  break;
1067
+ case 'dir':
822
1068
  case 'dirs':
823
- cmdDirs();
1069
+ case 'directory':
1070
+ case 'directories':
1071
+ cmdDirectory(args);
824
1072
  break;
825
1073
  case 'reset':
826
1074
  await cmdReset(args);
827
1075
  break;
1076
+ case 'ow':
1077
+ case 'overwrite':
1078
+ await cmdOverwrite(args);
1079
+ break;
828
1080
  default:
829
- console.error(' 未知命令: ' + cmd);
830
- console.error(' 使用 "mihomo-cli help" 查看帮助');
1081
+ console.error('未知命令: ' + cmd);
1082
+ console.error('使用 "mihomo help" 查看帮助');
831
1083
  process.exit(1);
832
1084
  }
833
1085
  }
834
1086
 
835
1087
  main().catch(e => {
836
- console.error(' 错误:', e.message);
1088
+ console.error('错误:', e.message);
837
1089
  process.exit(1);
838
1090
  });