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/CHANGELOG.md +64 -0
- package/README.md +11 -11
- package/index.js +474 -222
- package/package.json +1 -1
- package/src/config.js +36 -26
- package/src/overwrite.js +254 -0
- package/src/process.js +10 -10
- package/src/subscription.js +19 -29
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
|
|
37
|
+
console.error('\n未捕获的异常: ' + e.message);
|
|
36
38
|
if (e.stack) {
|
|
37
|
-
console.error(
|
|
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
|
|
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]
|
|
53
|
-
' stop
|
|
54
|
-
' status
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
'
|
|
59
|
-
'
|
|
60
|
-
'
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
'
|
|
64
|
-
' reset
|
|
65
|
-
'
|
|
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
|
|
76
|
+
' mihomo <命令> [选项]\n' +
|
|
76
77
|
'\n' +
|
|
77
|
-
'
|
|
78
|
-
' start [tun|mixed]
|
|
79
|
-
' stop
|
|
80
|
-
' status
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
' ui [zash|dash|yacd]
|
|
84
|
-
'
|
|
85
|
-
'
|
|
86
|
-
'
|
|
87
|
-
'
|
|
88
|
-
'
|
|
89
|
-
'
|
|
90
|
-
'
|
|
91
|
-
'
|
|
92
|
-
'
|
|
93
|
-
'
|
|
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
|
|
97
|
-
' mihomo
|
|
98
|
-
' mihomo
|
|
99
|
-
' mihomo
|
|
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
|
-
|
|
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('
|
|
142
|
+
console.log('PID: ' + status.pid);
|
|
126
143
|
if (status.processInfo) {
|
|
127
|
-
console.log('
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
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
|
|
215
|
+
function pickSingleSubscription(subs, pattern, actionName) {
|
|
173
216
|
if (subs.length === 0) {
|
|
174
|
-
console.error('
|
|
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('
|
|
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('
|
|
258
|
+
console.log('用系统默认程序打开: ' + displayLabel);
|
|
216
259
|
const success = processMgr.openUrl(logPath);
|
|
217
260
|
if (!success) {
|
|
218
|
-
console.log('
|
|
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('
|
|
278
|
+
console.log('日志: ' + logPath);
|
|
227
279
|
if (follow) {
|
|
228
|
-
console.log('
|
|
280
|
+
console.log('按 Ctrl+C 退出\n');
|
|
229
281
|
} else {
|
|
230
|
-
console.log('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
277
|
-
console.error('
|
|
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('
|
|
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('
|
|
341
|
+
console.error('配置错误: ' + e.message);
|
|
290
342
|
process.exit(1);
|
|
291
343
|
}
|
|
292
344
|
|
|
293
345
|
const modeLabel = targetMode === 'tun' ? 'TUN' : 'Mixed';
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
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('
|
|
353
|
+
console.log('已启动 (PID ' + result.pid + ')');
|
|
354
|
+
printStatus();
|
|
301
355
|
} catch (e) {
|
|
302
|
-
console.error('
|
|
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('
|
|
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('
|
|
319
|
-
console.error('
|
|
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('
|
|
331
|
-
console.error('
|
|
384
|
+
console.error('错误: 未知的 UI "' + uiName + '"');
|
|
385
|
+
console.error('可用 UI: zash (默认), dash, yacd');
|
|
332
386
|
process.exit(1);
|
|
333
387
|
}
|
|
334
388
|
|
|
335
|
-
console.log('
|
|
336
|
-
console.log('
|
|
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('
|
|
372
|
-
console.log('
|
|
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('
|
|
409
|
-
console.log('
|
|
462
|
+
console.log(' ' + num + '. ' + name);
|
|
463
|
+
console.log(' 时间: ' + time + ' 大小: ' + size);
|
|
410
464
|
if (!log.isCurrent) {
|
|
411
|
-
console.log('
|
|
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('
|
|
418
|
-
console.log('
|
|
419
|
-
console.log('
|
|
420
|
-
console.log('
|
|
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('
|
|
534
|
+
console.log('镜像: 直连(命令行指定 --no-mirror)');
|
|
481
535
|
} else {
|
|
482
|
-
console.log('
|
|
536
|
+
console.log('镜像: ' + effectiveMirror + ' (命令行指定)');
|
|
483
537
|
}
|
|
484
538
|
} else {
|
|
485
|
-
console.log('
|
|
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('
|
|
549
|
+
console.log(' ' + m + (isCurrent ? ' (当前)' : ''));
|
|
496
550
|
});
|
|
497
551
|
|
|
498
|
-
console.log('\n
|
|
499
|
-
console.log('
|
|
500
|
-
console.log('
|
|
501
|
-
console.log('
|
|
502
|
-
console.log('
|
|
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('
|
|
508
|
-
console.log('
|
|
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(
|
|
571
|
+
console.log(msg);
|
|
518
572
|
}, mirrorInfo.mirror); // 传递镜像参数(undefined = 用配置,null = 禁用)
|
|
519
|
-
console.log('
|
|
573
|
+
console.log('已更新到 ' + result.version);
|
|
520
574
|
} catch (e) {
|
|
521
|
-
console.error('
|
|
575
|
+
console.error('更新失败: ' + e.message);
|
|
522
576
|
process.exit(1);
|
|
523
577
|
}
|
|
524
578
|
}
|
|
525
579
|
|
|
526
|
-
async function
|
|
527
|
-
const
|
|
580
|
+
async function printSubscriptionList() {
|
|
581
|
+
const updateResult = await subscription.autoUpdateStaleSubscriptions();
|
|
582
|
+
if (updateResult.total > 0) {
|
|
583
|
+
console.log('');
|
|
584
|
+
}
|
|
528
585
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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('
|
|
643
|
+
console.error('错误: 请提供有效的订阅 URL');
|
|
586
644
|
process.exit(1);
|
|
587
645
|
}
|
|
588
646
|
|
|
589
|
-
console.log('
|
|
647
|
+
console.log('添加订阅: ' + name);
|
|
590
648
|
try {
|
|
591
649
|
config.addSubscription(url, name);
|
|
592
650
|
const info = await subscription.downloadSubscription(url, name);
|
|
593
|
-
|
|
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('
|
|
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('
|
|
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
|
-
|
|
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('
|
|
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 =
|
|
627
|
-
const target =
|
|
694
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
695
|
+
const target = pickSingleSubscription(matches, name, '更新');
|
|
628
696
|
|
|
629
|
-
console.log('
|
|
697
|
+
console.log('更新订阅: ' + target.name);
|
|
630
698
|
try {
|
|
631
699
|
const info = await subscription.downloadSubscription(target.url, target.name);
|
|
632
|
-
|
|
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('
|
|
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('
|
|
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 =
|
|
654
|
-
const target =
|
|
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('
|
|
747
|
+
console.log('已设置 "' + target.name + '" 为默认订阅');
|
|
659
748
|
} else {
|
|
660
|
-
console.error('
|
|
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 =
|
|
678
|
-
target =
|
|
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.
|
|
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
|
-
|
|
689
|
-
|
|
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('
|
|
796
|
+
console.error('更新失败: ' + e.message);
|
|
696
797
|
process.exit(1);
|
|
697
798
|
}
|
|
698
799
|
}
|
|
699
800
|
|
|
700
|
-
console.log('
|
|
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('
|
|
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('
|
|
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(
|
|
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('
|
|
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('
|
|
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
|
-
|
|
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('
|
|
758
|
-
console.log('
|
|
759
|
-
console.log('
|
|
760
|
-
console.log('
|
|
761
|
-
console.log('
|
|
762
|
-
console.log('
|
|
763
|
-
console.log('
|
|
764
|
-
console.log('
|
|
765
|
-
console.log('
|
|
766
|
-
console.log('
|
|
767
|
-
console.log('
|
|
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('
|
|
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
|
-
|
|
1064
|
+
case 'subscriptions':
|
|
1065
|
+
await cmdSubscription(args);
|
|
821
1066
|
break;
|
|
1067
|
+
case 'dir':
|
|
822
1068
|
case 'dirs':
|
|
823
|
-
|
|
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('
|
|
830
|
-
console.error('
|
|
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('
|
|
1088
|
+
console.error('错误:', e.message);
|
|
837
1089
|
process.exit(1);
|
|
838
1090
|
});
|