mihomo-cli 1.0.0-alpha.1 → 1.0.2

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
@@ -7,7 +7,7 @@ const kernel = require('./src/kernel');
7
7
  const subscription = require('./src/subscription');
8
8
  const processMgr = require('./src/process');
9
9
 
10
- const VERSION = '1.0.0-alpha.1';
10
+ const VERSION = require('./package.json').version;
11
11
 
12
12
  const UI_URLS = {
13
13
  zash: 'https://board.zash.run.place',
@@ -45,23 +45,47 @@ process.on('unhandledRejection', (reason) => {
45
45
  process.exit(1);
46
46
  });
47
47
 
48
+ function printShortHelp() {
49
+ console.log('\nmihomo-cli v' + VERSION);
50
+ console.log('别名: mihomo, mmc, mh\n');
51
+ console.log('命令:\n' +
52
+ ' start [tun|mixed] 启动/切换代理(重复执行可切换模式)\n' +
53
+ ' stop 停止代理\n' +
54
+ ' status 查看状态\n' +
55
+ ' log 实时日志\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');
67
+ }
68
+
48
69
  function printHelp() {
49
70
  console.log('\nmihomo-cli v' + VERSION + '\n' +
71
+ '\n' +
72
+ '命令别名: mihomo, mmc, mh\n' +
50
73
  '\n' +
51
74
  '用法:\n' +
52
75
  ' mihomo-cli <命令> [选项]\n' +
53
76
  '\n' +
54
77
  '命令:\n' +
55
- ' start [tun|mixed] 启动代理 (默认 mixed)\n' +
78
+ ' start [tun|mixed] 启动/切换代理 (默认 mixed, 重复执行可重启/切换模式)\n' +
56
79
  ' stop 停止代理\n' +
57
- ' restart [tun|mixed] 重启代理\n' +
58
80
  ' status 查看状态\n' +
59
81
  ' log 实时日志\n' +
82
+ ' logs [name] [-n N] 列出/查看历史日志 (默认 100 行)\n' +
60
83
  ' ui [zash|dash|yacd] 打开 Web UI (默认 zash)\n' +
61
- ' clean 清理残留进程\n' +
62
- ' kernel 更新内核\n' +
84
+ ' kernel [镜像] 更新内核 (可指定镜像: hk.gh-proxy.org 或 --no-mirror)\n' +
63
85
  ' sub add <url> [name] 添加订阅\n' +
64
- ' sub update [name] 更新订阅\n' +
86
+ ' sub update [name] 更新订阅 (无参更新所有)\n' +
87
+ ' sub use <name> 设置默认订阅 (支持模糊匹配)\n' +
88
+ ' sub web [name] 打开订阅页面\n' +
65
89
  ' sub list 列出订阅\n' +
66
90
  ' reset [--full] 重置用户数据 (--full 同时删除内核)\n' +
67
91
  ' dirs 显示数据目录位置\n' +
@@ -69,8 +93,8 @@ function printHelp() {
69
93
  ' version, -v 显示版本\n' +
70
94
  '\n' +
71
95
  '示例:\n' +
72
- ' mihomo-cli start # 启动 Mixed 模式\n' +
73
- ' mihomo-cli start tun # 启动 TUN 模式 (透明代理)\n' +
96
+ ' mihomo-cli start # 启动/重启 Mixed 模式\n' +
97
+ ' mihomo-cli start tun # 启动/切换到 TUN 模式 (透明代理)\n' +
74
98
  ' mihomo-cli ui # 打开默认 UI (zash)\n' +
75
99
  ' mihomo-cli ui dash # 打开 metacubexd\n' +
76
100
  ' mihomo-cli ui yacd # 打开 YACD\n' +
@@ -123,6 +147,103 @@ function getActiveSubscription() {
123
147
  return subs[0];
124
148
  }
125
149
 
150
+ function findSubsFuzzy(subs, pattern) {
151
+ const lowerPattern = pattern.toLowerCase();
152
+ let exact = [];
153
+ let prefix = [];
154
+ let includes = [];
155
+
156
+ for (const s of subs) {
157
+ const name = s.name.toLowerCase();
158
+ if (name === lowerPattern) {
159
+ exact.push(s);
160
+ } else if (name.startsWith(lowerPattern)) {
161
+ prefix.push(s);
162
+ } else if (name.includes(lowerPattern)) {
163
+ includes.push(s);
164
+ }
165
+ }
166
+
167
+ if (exact.length > 0) return exact;
168
+ if (prefix.length > 0) return prefix;
169
+ return includes;
170
+ }
171
+
172
+ function pickSingleSub(subs, pattern, actionName) {
173
+ if (subs.length === 0) {
174
+ console.error(' 错误: 未找到匹配 "' + pattern + '" 的订阅');
175
+ process.exit(1);
176
+ }
177
+ if (subs.length === 1) {
178
+ return subs[0];
179
+ }
180
+ console.error(' 错误: 匹配到多个订阅,请更精确指定');
181
+ console.log('\n 匹配的订阅:');
182
+ subs.forEach(s => console.log(' ' + s.name));
183
+ process.exit(1);
184
+ }
185
+
186
+ function hasFlag(args, short, long) {
187
+ return args && (args.includes(short) || args.includes(long));
188
+ }
189
+
190
+ function parseIntArg(args, short, long, defaultValue) {
191
+ if (!args) return defaultValue;
192
+ for (let i = 0; i < args.length; i++) {
193
+ if (args[i] === short || args[i] === long) {
194
+ if (i + 1 < args.length) {
195
+ const val = parseInt(args[i + 1]);
196
+ return isNaN(val) ? defaultValue : val;
197
+ }
198
+ }
199
+ }
200
+ return defaultValue;
201
+ }
202
+
203
+ function getNonFlagArg(args, startIdx) {
204
+ if (!args) return null;
205
+ for (let i = startIdx; i < args.length; i++) {
206
+ if (!args[i].startsWith('-')) {
207
+ return args[i];
208
+ }
209
+ }
210
+ return null;
211
+ }
212
+
213
+ function openLogFile(logPath, label) {
214
+ const displayLabel = label || logPath;
215
+ console.log(' 用系统默认程序打开: ' + displayLabel);
216
+ const success = processMgr.openUrl(logPath);
217
+ if (!success) {
218
+ console.log(' 请手动打开: ' + logPath);
219
+ }
220
+ }
221
+
222
+ function viewLogWithTail(logPath, options) {
223
+ const follow = options && options.follow;
224
+ const lines = (options && options.lines) || 100;
225
+
226
+ console.log(' 日志: ' + logPath);
227
+ if (follow) {
228
+ console.log(' 按 Ctrl+C 退出\n');
229
+ } else {
230
+ console.log(' 显示最后 ' + lines + ' 行\n');
231
+ }
232
+
233
+ const tailArgs = [];
234
+ if (follow) tailArgs.push('-f');
235
+ tailArgs.push('-n', lines.toString());
236
+ tailArgs.push(logPath);
237
+
238
+ const tail = spawn('tail', tailArgs, { stdio: 'inherit' });
239
+
240
+ tail.on('close', () => process.exit(0));
241
+ tail.on('error', (e) => {
242
+ console.error(' 无法读取日志: ' + e.message);
243
+ process.exit(1);
244
+ });
245
+ }
246
+
126
247
  async function cmdStart(args) {
127
248
  if (!config.hasKernel()) {
128
249
  console.error(' 错误: 未找到内核,请运行 \'mihomo-cli kernel\'');
@@ -137,14 +258,28 @@ async function cmdStart(args) {
137
258
  process.exit(1);
138
259
  }
139
260
 
140
- const pids = processMgr.getAllMihomoPids();
141
- if (pids.length > 0) {
142
- console.log(' 检测到 ' + pids.length + ' 个运行中的进程,正在停止...');
143
- processMgr.stop(targetMode === 'tun');
144
- for (let i = 0; i < 50; i++) {
145
- if (processMgr.getAllMihomoPids().length === 0) break;
146
- await new Promise(r => setTimeout(r, 100));
147
- }
261
+ await subscription.autoUpdateStaleSubscriptions();
262
+
263
+ // 每次 start 都先确保完全干净的状态(停止进程 + 清理运行时文件)
264
+ const status = processMgr.getStatus();
265
+ const hasProcess = status.running || status.allProcesses.length > 0;
266
+
267
+ if (hasProcess) {
268
+ const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
269
+ console.log(' 停止 ' + count + ' 个进程...');
270
+ }
271
+
272
+ // 总是调用 stop(即使没进程也会清理 PID 文件和运行时目录)
273
+ const stopResult = processMgr.stop(true);
274
+
275
+ if (stopResult.remaining && stopResult.remaining.length > 0) {
276
+ console.error(' 部分进程无法终止: ' + stopResult.remaining.join(', '));
277
+ console.error(' 请手动运行: sudo pkill -9 mihomo');
278
+ process.exit(1);
279
+ }
280
+
281
+ if (hasProcess) {
282
+ console.log(' 已停止\n');
148
283
  }
149
284
 
150
285
  let cfgInfo;
@@ -169,36 +304,22 @@ async function cmdStart(args) {
169
304
  }
170
305
  }
171
306
 
172
- function doStop() {
307
+ async function cmdStop() {
173
308
  const pids = processMgr.getAllMihomoPids();
174
309
  if (pids.length === 0) {
175
310
  console.log(' 未在运行');
176
- return { hasRunning: false, success: true };
311
+ return;
177
312
  }
178
313
 
179
314
  console.log(' 停止 ' + pids.length + ' 个进程...');
180
315
  const result = processMgr.stop(true);
181
316
 
182
317
  if (result.remaining && result.remaining.length > 0) {
183
- console.warn(' 部分进程未终止: ' + result.remaining.join(', '));
184
- console.warn(' 手动清理: sudo pkill -9 mihomo');
185
- return { hasRunning: true, success: false, remaining: result.remaining };
186
- }
187
- console.log(' 已停止');
188
- return { hasRunning: true, success: true };
189
- }
190
-
191
- async function cmdStop() {
192
- const result = doStop();
193
- if (!result.success) {
318
+ console.error(' 部分进程未终止: ' + result.remaining.join(', '));
319
+ console.error(' 请手动运行: sudo pkill -9 mihomo');
194
320
  process.exit(1);
195
321
  }
196
- }
197
-
198
- async function cmdRestart(args) {
199
- const stopResult = doStop();
200
- console.log('');
201
- await cmdStart(args);
322
+ console.log(' 已停止');
202
323
  }
203
324
 
204
325
  function cmdUi(args) {
@@ -220,42 +341,167 @@ function cmdUi(args) {
220
341
  }
221
342
  }
222
343
 
223
- function cmdClean() {
224
- console.log(' 清理残留进程...');
225
- const result = processMgr.cleanupAll();
226
344
 
227
- if (result.killed > 0) {
228
- console.log(' 已清理 ' + result.killed + ' 个进程');
345
+ function cmdLog(args) {
346
+ const logPath = processMgr.getLogPath();
347
+
348
+ if (hasFlag(args, '-o', '--open')) {
349
+ openLogFile(logPath);
350
+ return;
229
351
  }
230
- if (result.remaining && result.remaining.length > 0) {
231
- console.warn(' 仍有 ' + result.remaining.length + ' 个进程需要手动清理');
232
- console.warn(' 手动命令: sudo pkill -9 mihomo');
233
- process.exit(1);
352
+
353
+ viewLogWithTail(logPath, { follow: true, lines: 50 });
354
+ }
355
+
356
+ function cmdLogs(args) {
357
+ const targetName = getNonFlagArg(args, 1);
358
+ const lines = parseIntArg(args, '-n', '--lines', 100);
359
+ const openInViewer = hasFlag(args, '-o', '--open');
360
+
361
+ if (targetName) {
362
+ let logPath;
363
+
364
+ if (targetName === 'current' || targetName === '0') {
365
+ logPath = processMgr.getLogPath();
366
+ } else {
367
+ logPath = processMgr.getLogPathByName(targetName);
368
+ }
369
+
370
+ if (!logPath) {
371
+ console.error(' 错误: 未找到日志 "' + targetName + '"');
372
+ console.log(' 使用 "mihomo-cli logs" 查看可用日志列表');
373
+ process.exit(1);
374
+ }
375
+
376
+ if (openInViewer) {
377
+ openLogFile(logPath);
378
+ return;
379
+ }
380
+
381
+ viewLogWithTail(logPath, { follow: false, lines });
382
+ return;
234
383
  }
235
- if (result.killed === 0 && (!result.remaining || result.remaining.length === 0)) {
236
- console.log(' 没有发现残留进程');
384
+
385
+ const logs = processMgr.listLogs();
386
+ const all = [];
387
+
388
+ if (logs.current) {
389
+ all.push(logs.current);
237
390
  }
238
- }
391
+ all.push(...logs.archives);
239
392
 
240
- function cmdLog() {
241
- const logPath = processMgr.getLogPath();
242
- console.log(' 日志: ' + logPath);
243
- console.log(' 按 Ctrl+C 退出\n');
393
+ if (all.length === 0) {
394
+ console.log(' 暂无日志');
395
+ return;
396
+ }
244
397
 
245
- const tail = spawn('tail', ['-f', '-n', '50', logPath], {
246
- stdio: 'inherit',
247
- });
398
+ console.log('');
399
+ console.log(' 日志列表:');
400
+ console.log('');
248
401
 
249
- tail.on('close', () => process.exit(0));
250
- tail.on('error', (e) => {
251
- console.error(' 无法启动日志查看: ' + e.message);
252
- process.exit(1);
402
+ all.forEach((log, idx) => {
403
+ const num = log.isCurrent ? ' 0' : (idx < 10 ? ' ' + idx : '' + idx);
404
+ const time = subscription.formatDate(log.mtime);
405
+ const size = subscription.formatBytes(log.size);
406
+ const name = log.isCurrent ? 'mihomo.log (当前运行中)' : log.name;
407
+
408
+ console.log(' ' + num + '. ' + name);
409
+ console.log(' 时间: ' + time + ' 大小: ' + size);
410
+ if (!log.isCurrent) {
411
+ console.log(' 查看: mihomo-cli logs ' + idx + ' 或 mihomo-cli logs -o ' + idx);
412
+ }
413
+ console.log('');
253
414
  });
415
+
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 # 用系统默认程序打开');
421
+ console.log('');
422
+ }
423
+
424
+ // 解析镜像参数
425
+ function parseMirrorArg(args) {
426
+ // 返回: { mirror: 镜像URL|null, isOverride: boolean }
427
+ // mirror = null 表示禁用镜像
428
+ // mirror = undefined 表示使用默认/配置
429
+
430
+ if (!args || args.length < 2) {
431
+ return { mirror: undefined, isOverride: false };
432
+ }
433
+
434
+ // 检查 --no-mirror
435
+ if (args.includes('--no-mirror') || args.includes('--direct')) {
436
+ return { mirror: null, isOverride: true };
437
+ }
438
+
439
+ // 检查 --mirror <值>
440
+ const mirrorIdx = args.indexOf('--mirror');
441
+ if (mirrorIdx >= 0 && mirrorIdx + 1 < args.length) {
442
+ let mirrorVal = args[mirrorIdx + 1];
443
+ return { mirror: normalizeMirrorUrl(mirrorVal), isOverride: true };
444
+ }
445
+
446
+ // 第一个非 flag 参数作为镜像
447
+ for (let i = 1; i < args.length; i++) {
448
+ const arg = args[i];
449
+ if (!arg.startsWith('-')) {
450
+ return { mirror: normalizeMirrorUrl(arg), isOverride: true };
451
+ }
452
+ }
453
+
454
+ return { mirror: undefined, isOverride: false };
254
455
  }
255
456
 
256
- async function cmdKernel() {
457
+ function normalizeMirrorUrl(val) {
458
+ if (!val) return null;
459
+ if (val === 'direct' || val === 'no' || val === 'none') return null;
460
+
461
+ let url = val;
462
+ if (!url.startsWith('http')) {
463
+ url = 'https://' + url;
464
+ }
465
+ if (!url.endsWith('/')) {
466
+ url += '/';
467
+ }
468
+ return url;
469
+ }
470
+
471
+ async function cmdKernel(args) {
472
+ const mirrorInfo = parseMirrorArg(args);
473
+ const effectiveMirror = mirrorInfo.isOverride ? mirrorInfo.mirror : config.getGitHubMirror();
474
+ const isDefault = !mirrorInfo.isOverride && effectiveMirror === config.DEFAULT_GITHUB_MIRROR;
475
+
257
476
  console.log(' 检查内核更新...');
258
477
 
478
+ if (mirrorInfo.isOverride) {
479
+ if (effectiveMirror === null) {
480
+ console.log(' 镜像: 直连(命令行指定 --no-mirror)');
481
+ } else {
482
+ console.log(' 镜像: ' + effectiveMirror + ' (命令行指定)');
483
+ }
484
+ } else {
485
+ console.log(' 镜像: ' + (effectiveMirror || '直连(无镜像)') + (isDefault && effectiveMirror ? ' (默认)' : ''));
486
+ }
487
+
488
+ console.log('\n 可用镜像:');
489
+ config.AVAILABLE_MIRRORS.forEach(m => {
490
+ const isCurrent = effectiveMirror && (
491
+ effectiveMirror.includes('//' + m + '/') ||
492
+ effectiveMirror.includes('//' + m + ':') ||
493
+ effectiveMirror.endsWith('//' + m)
494
+ );
495
+ console.log(' ' + m + (isCurrent ? ' (当前)' : ''));
496
+ });
497
+
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 # 直连,不使用镜像');
503
+ console.log('');
504
+
259
505
  try {
260
506
  const info = await kernel.checkUpdate();
261
507
  console.log(' 当前: ' + info.current);
@@ -269,7 +515,7 @@ async function cmdKernel() {
269
515
  console.log('\n 正在下载...');
270
516
  const result = await kernel.downloadKernel((msg) => {
271
517
  console.log(' ' + msg);
272
- });
518
+ }, mirrorInfo.mirror); // 传递镜像参数(undefined = 用配置,null = 禁用)
273
519
  console.log(' 已更新到 ' + result.version);
274
520
  } catch (e) {
275
521
  console.error(' 更新失败: ' + e.message);
@@ -281,7 +527,12 @@ async function cmdSub(args) {
281
527
  const action = args[1];
282
528
 
283
529
  if (!action || action === 'list') {
284
- const subs = config.getSubscriptions();
530
+ const updateResult = await subscription.autoUpdateStaleSubscriptions();
531
+ if (updateResult.total > 0) {
532
+ console.log('');
533
+ }
534
+
535
+ const subs = config.getSubscriptionsWithCache();
285
536
  if (subs.length === 0) {
286
537
  console.log(' 没有订阅');
287
538
  console.log('\n 添加订阅:');
@@ -290,11 +541,39 @@ async function cmdSub(args) {
290
541
  }
291
542
  console.log(' 订阅列表:');
292
543
  subs.forEach((s, i) => {
293
- const time = s.updatedAt ? new Date(s.updatedAt).toLocaleString('zh-CN') : '未更新';
294
- console.log(' ' + (i + 1) + '. ' + s.name + ' (' + time + ')');
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)');
549
+
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
+ }
295
570
  });
296
- console.log('\n 更新订阅:');
571
+ console.log('\n 切换默认订阅:');
572
+ console.log(' mihomo-cli sub use <name>');
573
+ console.log(' 更新订阅:');
297
574
  console.log(' mihomo-cli sub update [name]');
575
+ console.log(' 打开订阅页面:');
576
+ console.log(' mihomo-cli sub web [name]');
298
577
  return;
299
578
  }
300
579
 
@@ -328,12 +607,25 @@ async function cmdSub(args) {
328
607
  process.exit(1);
329
608
  }
330
609
 
331
- let target = name ? subs.find(s => s.name === name) : subs[0];
332
- if (!target) {
333
- console.error(' 错误: 未找到订阅 "' + name + '"');
334
- process.exit(1);
610
+ if (!name) {
611
+ console.log(' 更新所有 ' + subs.length + ' 个订阅...');
612
+ const results = await Promise.all(subs.map(subscription.tryUpdateOne));
613
+ let ok = 0;
614
+ results.forEach(r => {
615
+ if (r.success) {
616
+ ok++;
617
+ console.log(' ✓ ' + r.name + ': ' + r.proxies + ' 节点');
618
+ } else {
619
+ console.log(' ✗ ' + r.name + ': 失败 (' + r.error.split('\n')[0] + ')');
620
+ }
621
+ });
622
+ if (ok === 0) process.exit(1);
623
+ return;
335
624
  }
336
625
 
626
+ const matches = findSubsFuzzy(subs, name);
627
+ const target = pickSingleSub(matches, name, '更新');
628
+
337
629
  console.log(' 更新订阅: ' + target.name);
338
630
  try {
339
631
  const info = await subscription.downloadSubscription(target.url, target.name);
@@ -345,8 +637,76 @@ async function cmdSub(args) {
345
637
  return;
346
638
  }
347
639
 
640
+ if (action === 'use') {
641
+ const name = args[2];
642
+ const subs = config.getSubscriptions();
643
+
644
+ if (!name) {
645
+ console.error(' 错误: 请指定订阅名称');
646
+ if (subs.length > 0) {
647
+ console.log('\n 可用订阅:');
648
+ subs.forEach(s => console.log(' ' + s.name));
649
+ }
650
+ process.exit(1);
651
+ }
652
+
653
+ const matches = findSubsFuzzy(subs, name);
654
+ const target = pickSingleSub(matches, name, '切换');
655
+
656
+ const success = config.setDefaultSubscription(target.name);
657
+ if (success) {
658
+ console.log(' 已设置 "' + target.name + '" 为默认订阅');
659
+ } else {
660
+ console.error(' 错误: 未找到订阅 "' + name + '"');
661
+ process.exit(1);
662
+ }
663
+ return;
664
+ }
665
+
666
+ if (action === 'web' || action === 'open') {
667
+ const name = args[2];
668
+ const subs = config.getSubscriptionsWithCache();
669
+
670
+ if (subs.length === 0) {
671
+ console.error(' 错误: 没有订阅');
672
+ process.exit(1);
673
+ }
674
+
675
+ let target;
676
+ if (name) {
677
+ const matches = findSubsFuzzy(subs, name);
678
+ target = pickSingleSub(matches, name, '打开');
679
+ } else {
680
+ target = subs[0];
681
+ }
682
+
683
+ let webPageUrl = target.webPageUrl;
684
+ if (!webPageUrl) {
685
+ console.log(' 订阅信息中缺少页面地址,正在更新订阅...');
686
+ try {
687
+ const info = await subscription.downloadSubscription(target.url, target.name);
688
+ if (info.webPageUrl) {
689
+ webPageUrl = info.webPageUrl;
690
+ } else {
691
+ console.error(' 错误: 该订阅没有提供页面地址');
692
+ process.exit(1);
693
+ }
694
+ } catch (e) {
695
+ console.error(' 更新失败: ' + e.message);
696
+ process.exit(1);
697
+ }
698
+ }
699
+
700
+ console.log(' 打开订阅页面: ' + webPageUrl);
701
+ const opened = processMgr.openUrl(webPageUrl);
702
+ if (!opened) {
703
+ console.log(' 请手动访问上面的地址');
704
+ }
705
+ return;
706
+ }
707
+
348
708
  console.error(' 错误: 未知的订阅命令');
349
- console.log(' 用法: mihomo-cli sub [list|add|update]');
709
+ console.log(' 用法: mihomo-cli sub [list|add|update|use|web]');
350
710
  process.exit(1);
351
711
  }
352
712
 
@@ -417,7 +777,8 @@ async function main() {
417
777
  const args = process.argv.slice(2);
418
778
 
419
779
  if (args.length === 0) {
420
- printHelp();
780
+ printStatus();
781
+ printShortHelp();
421
782
  return;
422
783
  }
423
784
 
@@ -439,23 +800,20 @@ async function main() {
439
800
  case 'stop':
440
801
  await cmdStop();
441
802
  break;
442
- case 'restart':
443
- await cmdRestart(args);
444
- break;
445
803
  case 'status':
446
804
  printStatus();
447
805
  break;
448
806
  case 'log':
449
- cmdLog();
807
+ cmdLog(args);
808
+ break;
809
+ case 'logs':
810
+ cmdLogs(args);
450
811
  break;
451
812
  case 'ui':
452
813
  cmdUi(args);
453
814
  break;
454
- case 'clean':
455
- cmdClean();
456
- break;
457
815
  case 'kernel':
458
- await cmdKernel();
816
+ await cmdKernel(args);
459
817
  break;
460
818
  case 'sub':
461
819
  case 'subscription':
package/package.json CHANGED
@@ -1,14 +1,18 @@
1
1
  {
2
2
  "name": "mihomo-cli",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.2",
4
4
  "description": "A terminal-based mihomo (Clash.Meta) client for macOS",
5
5
  "main": "index.js",
6
6
  "bin": {
7
- "mihomo-cli": "./index.js"
7
+ "mihomo-cli": "index.js",
8
+ "mihomo": "index.js",
9
+ "mmc": "index.js",
10
+ "mh": "index.js"
8
11
  },
9
12
  "files": [
10
13
  "index.js",
11
- "src/**/*.js"
14
+ "src/**/*.js",
15
+ "CHANGELOG.md"
12
16
  ],
13
17
  "scripts": {
14
18
  "start": "node index.js"
@@ -27,8 +31,13 @@
27
31
  "engines": {
28
32
  "node": ">=18.0.0"
29
33
  },
30
- "os": ["darwin"],
31
- "cpu": ["x64", "arm64"],
34
+ "os": [
35
+ "darwin"
36
+ ],
37
+ "cpu": [
38
+ "x64",
39
+ "arm64"
40
+ ],
32
41
  "dependencies": {
33
42
  "axios": "^1.6.0",
34
43
  "compare-versions": "^6.1.0",