mihomo-cli 2.4.1 → 2.5.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 +27 -0
- package/README.md +4 -4
- package/dist/index.js +275 -122
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.5.0] - 2026-05-03
|
|
4
|
+
|
|
5
|
+
### 新功能
|
|
6
|
+
|
|
7
|
+
- **test 命令** - `mihomo test` 快速测试当前运行实例的节点连通性
|
|
8
|
+
- **clean 命令** - `mihomo clean` 清理失败节点并自动重启
|
|
9
|
+
|
|
10
|
+
### 改进
|
|
11
|
+
|
|
12
|
+
- `sub test` / `sub clean` 改用独立临时进程测试,不影响当前代理,支持测试任意订阅(不限于活跃订阅)
|
|
13
|
+
- 启动时 auto-clean 使用当前运行实例直接测速,提升启动速度
|
|
14
|
+
|
|
15
|
+
### 移除
|
|
16
|
+
|
|
17
|
+
- 移除 `sub best` 命令
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## [2.4.2] - 2026-05-02
|
|
22
|
+
|
|
23
|
+
### 改进
|
|
24
|
+
|
|
25
|
+
- 自动清理阈值统一为 50 个节点(不再区分订阅类型)
|
|
26
|
+
- 订阅默认更新间隔从 12 小时缩短为 4 小时
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
3
30
|
## [2.4.1] - 2026-05-02
|
|
4
31
|
|
|
5
32
|
### 修复
|
package/README.md
CHANGED
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
- 🌐 **订阅管理** - 添加/更新订阅,支持流量统计和到期时间显示
|
|
8
8
|
- 🔄 **自动更新** - 启动时自动检查并更新过期订阅
|
|
9
9
|
- 🔍 **模糊匹配** - `sub use` / `sub web` 支持订阅名称模糊匹配
|
|
10
|
-
- 🧹 **节点测速清理** - `
|
|
10
|
+
- 🧹 **节点测速清理** - `test` 快速测试、`clean` 清理并重启;`sub test/clean` 独立进程测试任意订阅
|
|
11
11
|
- 📊 **免费订阅基准测试** - `bench` 命令一键测试 20 个内置免费订阅源质量排名
|
|
12
12
|
- 🆓 **快速添加免费订阅** - `sub free <id>` 一键添加内置免费订阅源
|
|
13
|
-
- 🏆 **聚合订阅** - `sub best <id>` 一键添加自动更新的聚合订阅(每小时更新、去重、测活)
|
|
14
13
|
- 📝 **覆写配置** - 在订阅基础上进行自定义覆写,支持强制覆盖、数组合并
|
|
15
14
|
- 🔄 **智能重启** - `sub use` 切换订阅、`ow on/off` 切换覆写后自动重启
|
|
16
15
|
- 🚀 **进程管理** - 启动/停止/切换模式,自动清理残留进程
|
|
@@ -101,13 +100,14 @@ mihomo ui yacd # YACD
|
|
|
101
100
|
| `mihomo sub use <name>` | 切换当前订阅(支持模糊匹配,自动重启) |
|
|
102
101
|
| `mihomo sub add <url> [name]` | 添加订阅并自动切换(支持逗号分隔多 URL 合并) |
|
|
103
102
|
| `mihomo sub free <id>` | 添加内置免费订阅(`0`=合并 #1+#2,`sub free` 列出可用源)|
|
|
104
|
-
| `mihomo sub best <id>` | 添加聚合订阅(`sub best` 列出可用源)|
|
|
105
103
|
| `mihomo sub update` | 更新所有订阅 |
|
|
106
104
|
| `mihomo sub update <name>` | 更新指定订阅(支持模糊匹配) |
|
|
107
105
|
| `mihomo sub remove <name>` | 删除订阅(支持模糊匹配) |
|
|
108
106
|
| `mihomo sub web [name]` | 打开订阅页面(无参打开默认) |
|
|
109
107
|
| `mihomo sub test [name]` | 测试节点连通性(`-t` 超时,`-j` 并发) |
|
|
110
108
|
| `mihomo sub clean [name]` | 测速并清理失败节点 |
|
|
109
|
+
| `mihomo test` | 快速测试当前节点连通性(`-t` 超时,`-j` 并发) |
|
|
110
|
+
| `mihomo clean` | 清理失败节点并自动重启(`-t` 超时,`-j` 并发) |
|
|
111
111
|
|
|
112
112
|
### 覆写配置
|
|
113
113
|
|
|
@@ -198,7 +198,7 @@ mihomo kernel --mirror-all hk.gh-proxy.org
|
|
|
198
198
|
|
|
199
199
|
## 订阅自动更新
|
|
200
200
|
|
|
201
|
-
- 默认更新间隔:
|
|
201
|
+
- 默认更新间隔:4 小时(或订阅服务端指定的 `profile-update-interval`)
|
|
202
202
|
- 触发时机:`start` 命令、`sub list` 命令
|
|
203
203
|
- 更新失败时继续使用本地缓存,不影响使用
|
|
204
204
|
|
package/dist/index.js
CHANGED
|
@@ -2687,14 +2687,6 @@ function getFreeSubscriptionSources() {
|
|
|
2687
2687
|
{ name: "NoMoreWalls", url: "https://gh-proxy.org/raw.githubusercontent.com/peasoft/NoMoreWalls/master/list.meta.yml" }
|
|
2688
2688
|
];
|
|
2689
2689
|
}
|
|
2690
|
-
var GH_SUB = "https://gh-proxy.org/raw.githubusercontent.com/imaex/mihomo-free-sub/sub";
|
|
2691
|
-
function getBestSubscriptionSources() {
|
|
2692
|
-
return [
|
|
2693
|
-
{ name: "curated", url: `${GH_SUB}/curated.yaml`, description: "\u7CBE\u9009 29 \u7EC4\uFF08\u4EC5\u6D4B\u901F\u6E90\uFF09" },
|
|
2694
|
-
{ name: "acl4ssr", url: `${GH_SUB}/acl4ssr.yaml`, description: "ACL4SSR 29 \u7EC4\uFF08\u5168\u90E8\u6E90\uFF09" },
|
|
2695
|
-
{ name: "freesub", url: `${GH_SUB}/freesub.yaml`, description: "freeSub 24 \u7EC4" }
|
|
2696
|
-
];
|
|
2697
|
-
}
|
|
2698
2690
|
var BENCH_CONFIG = {
|
|
2699
2691
|
"allow-lan": false,
|
|
2700
2692
|
"external-controller": "127.0.0.1:19090",
|
|
@@ -2703,6 +2695,14 @@ var BENCH_CONFIG = {
|
|
|
2703
2695
|
"log-level": "error",
|
|
2704
2696
|
"geodata-mode": true
|
|
2705
2697
|
};
|
|
2698
|
+
var TEST_CONFIG = {
|
|
2699
|
+
"allow-lan": false,
|
|
2700
|
+
"external-controller": "127.0.0.1:29090",
|
|
2701
|
+
port: 27890,
|
|
2702
|
+
"socks-port": 27891,
|
|
2703
|
+
"log-level": "error",
|
|
2704
|
+
"geodata-mode": true
|
|
2705
|
+
};
|
|
2706
2706
|
var BASE_CONFIG = {
|
|
2707
2707
|
"allow-lan": false,
|
|
2708
2708
|
"external-controller": "127.0.0.1:9090",
|
|
@@ -3397,6 +3397,15 @@ function parseMirrorArg(args) {
|
|
|
3397
3397
|
}
|
|
3398
3398
|
return { mirror: null, isOverride: false, type: "download" };
|
|
3399
3399
|
}
|
|
3400
|
+
function isProxyValid(proxy) {
|
|
3401
|
+
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
3402
|
+
if (!proxy.type) return false;
|
|
3403
|
+
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
3404
|
+
const pw = String(proxy.password || "");
|
|
3405
|
+
if (!/^[A-Za-z0-9+/]+=*$/.test(pw) || pw.length < 20) return false;
|
|
3406
|
+
}
|
|
3407
|
+
return true;
|
|
3408
|
+
}
|
|
3400
3409
|
|
|
3401
3410
|
// src/bench.ts
|
|
3402
3411
|
var BENCH_DIR = path3.join(USER_DATA_DIR, "bench");
|
|
@@ -3541,15 +3550,6 @@ async function downloadAllSources(sources, onProgress) {
|
|
|
3541
3550
|
});
|
|
3542
3551
|
return await Promise.all(tasks);
|
|
3543
3552
|
}
|
|
3544
|
-
function isProxyValid(proxy) {
|
|
3545
|
-
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
3546
|
-
if (!proxy.type) return false;
|
|
3547
|
-
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
3548
|
-
const pw = String(proxy.password || "");
|
|
3549
|
-
if (!/^[A-Za-z0-9+/]+=*$/.test(pw) || pw.length < 20) return false;
|
|
3550
|
-
}
|
|
3551
|
-
return true;
|
|
3552
|
-
}
|
|
3553
3553
|
function buildMergedBenchConfig(allProxies) {
|
|
3554
3554
|
ensureBenchDirs();
|
|
3555
3555
|
const validProxies = allProxies.filter(isProxyValid);
|
|
@@ -4211,7 +4211,7 @@ function viewLogWithTail(logPath, options) {
|
|
|
4211
4211
|
}
|
|
4212
4212
|
|
|
4213
4213
|
// src/subscription.ts
|
|
4214
|
-
var DEFAULT_UPDATE_INTERVAL_HOURS =
|
|
4214
|
+
var DEFAULT_UPDATE_INTERVAL_HOURS = 4;
|
|
4215
4215
|
var YAML_DUMP_OPTS = { indent: 2, lineWidth: -1, noCompatMode: true };
|
|
4216
4216
|
var HTTP_CLIENT = createHttpClient({ timeout: 6e4 });
|
|
4217
4217
|
function isMultiUrl(url) {
|
|
@@ -4484,9 +4484,9 @@ async function autoUpdateStaleSubscription() {
|
|
|
4484
4484
|
}
|
|
4485
4485
|
var API_BASE = `http://${BASE_CONFIG["external-controller"]}`;
|
|
4486
4486
|
var DEFAULT_TEST_URL = "http://www.gstatic.com/generate_204";
|
|
4487
|
-
async function testProxyDelay(proxyName, timeout, testUrl, client) {
|
|
4487
|
+
async function testProxyDelay(proxyName, timeout, testUrl, client, apiBase = API_BASE) {
|
|
4488
4488
|
const encodedName = encodeURIComponent(proxyName);
|
|
4489
|
-
const url = `${
|
|
4489
|
+
const url = `${apiBase}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(testUrl)}`;
|
|
4490
4490
|
try {
|
|
4491
4491
|
const response = await client.get(url);
|
|
4492
4492
|
const data = JSON.parse(response.data);
|
|
@@ -4506,7 +4506,7 @@ async function testProxyDelay(proxyName, timeout, testUrl, client) {
|
|
|
4506
4506
|
}
|
|
4507
4507
|
}
|
|
4508
4508
|
async function testSubscriptionProxies(subName, options = {}) {
|
|
4509
|
-
const { timeout = 2e3, concurrency = 100, testUrl = DEFAULT_TEST_URL, onResult } = options;
|
|
4509
|
+
const { timeout = 2e3, concurrency = 100, testUrl = DEFAULT_TEST_URL, apiBase = API_BASE, onResult } = options;
|
|
4510
4510
|
const { proxies } = options.parsed || loadSubscriptionConfig(subName);
|
|
4511
4511
|
if (proxies.length === 0) {
|
|
4512
4512
|
return { total: 0, alive: 0, dead: 0, results: [] };
|
|
@@ -4516,7 +4516,7 @@ async function testSubscriptionProxies(subName, options = {}) {
|
|
|
4516
4516
|
let completedCount = 0;
|
|
4517
4517
|
for (let i = 0; i < proxies.length; i += concurrency) {
|
|
4518
4518
|
const batch = proxies.slice(i, i + concurrency);
|
|
4519
|
-
const batchResults = await Promise.all(batch.map((proxy) => testProxyDelay(proxy.name, timeout, testUrl, client)));
|
|
4519
|
+
const batchResults = await Promise.all(batch.map((proxy) => testProxyDelay(proxy.name, timeout, testUrl, client, apiBase)));
|
|
4520
4520
|
for (const result of batchResults) {
|
|
4521
4521
|
results.push(result);
|
|
4522
4522
|
onResult?.(result, completedCount, proxies.length);
|
|
@@ -4598,12 +4598,138 @@ async function autoCleanSubscription(subName, options = {}) {
|
|
|
4598
4598
|
removedGroups = cleanResult.removedGroups;
|
|
4599
4599
|
}
|
|
4600
4600
|
}
|
|
4601
|
-
if (!skipped) {
|
|
4601
|
+
if (!skipped && removedProxies > 0) {
|
|
4602
4602
|
saveSubscriptionConfig(subName, parsed);
|
|
4603
4603
|
}
|
|
4604
4604
|
return { summary, removedProxies, updatedGroups, removedGroups, skipped };
|
|
4605
4605
|
}
|
|
4606
4606
|
|
|
4607
|
+
// src/test-instance.ts
|
|
4608
|
+
import { spawn as spawn3 } from "child_process";
|
|
4609
|
+
import fs7 from "fs";
|
|
4610
|
+
import path5 from "path";
|
|
4611
|
+
var TEST_DIR = path5.join(USER_DATA_DIR, "test");
|
|
4612
|
+
var TEST_DIRS = {
|
|
4613
|
+
data: path5.join(TEST_DIR, "data"),
|
|
4614
|
+
runtime: path5.join(TEST_DIR, "runtime")
|
|
4615
|
+
};
|
|
4616
|
+
var TEST_PATHS = {
|
|
4617
|
+
configFile: path5.join(TEST_DIRS.runtime, "config.yaml"),
|
|
4618
|
+
pidFile: path5.join(TEST_DIRS.runtime, "pid"),
|
|
4619
|
+
logFile: path5.join(TEST_DIR, "test.log")
|
|
4620
|
+
};
|
|
4621
|
+
var TEST_API = `http://${TEST_CONFIG["external-controller"]}`;
|
|
4622
|
+
function ensureTestDirs() {
|
|
4623
|
+
for (const dir of Object.values(TEST_DIRS)) {
|
|
4624
|
+
fs7.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
4625
|
+
}
|
|
4626
|
+
}
|
|
4627
|
+
function cleanupTestDir() {
|
|
4628
|
+
rmrf(TEST_DIR);
|
|
4629
|
+
}
|
|
4630
|
+
function buildTestConfig(subName) {
|
|
4631
|
+
ensureTestDirs();
|
|
4632
|
+
const rawContent = readSubscriptionRawConfig(subName);
|
|
4633
|
+
if (!rawContent) {
|
|
4634
|
+
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
|
|
4635
|
+
}
|
|
4636
|
+
const parsed = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
4637
|
+
const proxies = (parsed.proxies || []).filter(isProxyValid);
|
|
4638
|
+
if (proxies.length === 0) {
|
|
4639
|
+
throw new Error(`\u8BA2\u9605 "${subName}" \u6CA1\u6709\u6709\u6548\u8282\u70B9`);
|
|
4640
|
+
}
|
|
4641
|
+
const nameCount = /* @__PURE__ */ new Map();
|
|
4642
|
+
for (const proxy of proxies) {
|
|
4643
|
+
const count = (nameCount.get(proxy.name) || 0) + 1;
|
|
4644
|
+
nameCount.set(proxy.name, count);
|
|
4645
|
+
if (count > 1) {
|
|
4646
|
+
proxy.name = `${proxy.name} #${count}`;
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
const config = {
|
|
4650
|
+
...TEST_CONFIG,
|
|
4651
|
+
proxies,
|
|
4652
|
+
"proxy-groups": [
|
|
4653
|
+
{
|
|
4654
|
+
name: "PROXY",
|
|
4655
|
+
type: "select",
|
|
4656
|
+
proxies: proxies.map((p) => p.name)
|
|
4657
|
+
}
|
|
4658
|
+
],
|
|
4659
|
+
rules: ["MATCH,PROXY"]
|
|
4660
|
+
};
|
|
4661
|
+
const content = jsYaml.dump(config, { indent: 2, lineWidth: -1, noCompatMode: true });
|
|
4662
|
+
fs7.writeFileSync(TEST_PATHS.configFile, content, { mode: 384 });
|
|
4663
|
+
}
|
|
4664
|
+
async function startTestInstance() {
|
|
4665
|
+
const binary2 = PATHS.mihomoBinary;
|
|
4666
|
+
if (!fs7.existsSync(binary2)) throw new Error("\u672A\u627E\u5230 mihomo \u5185\u6838");
|
|
4667
|
+
stopTestInstance();
|
|
4668
|
+
const logFd = fs7.openSync(TEST_PATHS.logFile, "a");
|
|
4669
|
+
const child = spawn3(binary2, ["-d", TEST_DIRS.data, "-f", TEST_PATHS.configFile], {
|
|
4670
|
+
detached: true,
|
|
4671
|
+
stdio: ["ignore", logFd, logFd]
|
|
4672
|
+
});
|
|
4673
|
+
fs7.closeSync(logFd);
|
|
4674
|
+
child.unref();
|
|
4675
|
+
const pid = child.pid;
|
|
4676
|
+
fs7.writeFileSync(TEST_PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
4677
|
+
const client = createHttpClient({ timeout: 2e3 });
|
|
4678
|
+
let ready = false;
|
|
4679
|
+
for (let i = 0; i < 60; i++) {
|
|
4680
|
+
if (!isProcessRunning(pid)) break;
|
|
4681
|
+
try {
|
|
4682
|
+
await client.get(`${TEST_API}/version`);
|
|
4683
|
+
ready = true;
|
|
4684
|
+
break;
|
|
4685
|
+
} catch {
|
|
4686
|
+
await sleep(500);
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
if (!isProcessRunning(pid)) {
|
|
4690
|
+
let errorDetail = "";
|
|
4691
|
+
try {
|
|
4692
|
+
errorDetail = fs7.readFileSync(TEST_PATHS.logFile, "utf8").slice(-1e3);
|
|
4693
|
+
} catch {
|
|
4694
|
+
}
|
|
4695
|
+
throw new Error(`\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
|
|
4696
|
+
${errorDetail}` : ""}`);
|
|
4697
|
+
}
|
|
4698
|
+
if (!ready) {
|
|
4699
|
+
throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
function stopTestInstance() {
|
|
4703
|
+
let pid;
|
|
4704
|
+
try {
|
|
4705
|
+
pid = parseInt(fs7.readFileSync(TEST_PATHS.pidFile, "utf8").trim(), 10);
|
|
4706
|
+
} catch {
|
|
4707
|
+
return;
|
|
4708
|
+
}
|
|
4709
|
+
if (pid > 0 && isProcessRunning(pid)) {
|
|
4710
|
+
process.kill(pid, "SIGKILL");
|
|
4711
|
+
for (let i = 0; i < 20; i++) {
|
|
4712
|
+
if (!isProcessRunning(pid)) break;
|
|
4713
|
+
sleepSync(100);
|
|
4714
|
+
}
|
|
4715
|
+
}
|
|
4716
|
+
try {
|
|
4717
|
+
fs7.unlinkSync(TEST_PATHS.pidFile);
|
|
4718
|
+
} catch {
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
async function withTestInstance(subName, fn) {
|
|
4722
|
+
cleanupTestDir();
|
|
4723
|
+
buildTestConfig(subName);
|
|
4724
|
+
try {
|
|
4725
|
+
await startTestInstance();
|
|
4726
|
+
return await fn(TEST_API);
|
|
4727
|
+
} finally {
|
|
4728
|
+
stopTestInstance();
|
|
4729
|
+
cleanupTestDir();
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4732
|
+
|
|
4607
4733
|
// src/commands/status.ts
|
|
4608
4734
|
function printStatus() {
|
|
4609
4735
|
const status = getStatus();
|
|
@@ -4656,8 +4782,7 @@ function printStatus() {
|
|
|
4656
4782
|
}
|
|
4657
4783
|
|
|
4658
4784
|
// src/commands/start.ts
|
|
4659
|
-
var AUTO_CLEAN_THRESHOLD =
|
|
4660
|
-
var AUTO_CLEAN_THRESHOLD_FREE = 50;
|
|
4785
|
+
var AUTO_CLEAN_THRESHOLD = 50;
|
|
4661
4786
|
function handleStopResult(result) {
|
|
4662
4787
|
if (result.remaining && result.remaining.length > 0) {
|
|
4663
4788
|
console.error(`${colors.red("\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62:")} ${result.remaining.join(", ")}`);
|
|
@@ -4709,10 +4834,9 @@ async function cmdStart(args) {
|
|
|
4709
4834
|
}
|
|
4710
4835
|
process.exit(1);
|
|
4711
4836
|
}
|
|
4712
|
-
|
|
4713
|
-
if (configInfo.proxies > cleanThreshold) {
|
|
4837
|
+
if (configInfo.proxies > AUTO_CLEAN_THRESHOLD) {
|
|
4714
4838
|
console.log("");
|
|
4715
|
-
console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${
|
|
4839
|
+
console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${AUTO_CLEAN_THRESHOLD}\uFF0C\u81EA\u52A8\u6E05\u7406...`);
|
|
4716
4840
|
console.log("");
|
|
4717
4841
|
await sleep(1e3);
|
|
4718
4842
|
const cleanResult = await autoCleanSubscription(sub.name, { onResult: printTestResult });
|
|
@@ -4743,9 +4867,9 @@ function printTestResult(result, index, total) {
|
|
|
4743
4867
|
const prefix = `[${index + 1}/${total}]`;
|
|
4744
4868
|
if (result.delay !== null) {
|
|
4745
4869
|
const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
|
|
4746
|
-
console.log(
|
|
4870
|
+
console.log(`${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}`);
|
|
4747
4871
|
} else {
|
|
4748
|
-
console.log(
|
|
4872
|
+
console.log(`${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
|
|
4749
4873
|
}
|
|
4750
4874
|
}
|
|
4751
4875
|
function formatCleanSummary(result) {
|
|
@@ -4762,7 +4886,7 @@ function githubRepoUrl(rawUrl) {
|
|
|
4762
4886
|
if (match) return `https://github.com/${match[1]}`;
|
|
4763
4887
|
return null;
|
|
4764
4888
|
}
|
|
4765
|
-
function
|
|
4889
|
+
function resolveTestTarget(args) {
|
|
4766
4890
|
const subs = getSubscriptions();
|
|
4767
4891
|
if (subs.length === 0) {
|
|
4768
4892
|
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
@@ -4771,28 +4895,18 @@ function resolveActiveTestTarget(args) {
|
|
|
4771
4895
|
const nameArg = getNonFlagArg(args, 2);
|
|
4772
4896
|
const timeout = parseIntArg(args, "-t", "--timeout", 2e3);
|
|
4773
4897
|
const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
|
|
4774
|
-
const activeSub = getActiveSubscription();
|
|
4775
4898
|
let target;
|
|
4776
4899
|
if (nameArg) {
|
|
4777
4900
|
const matches = findSubscriptionFuzzy(subs, nameArg);
|
|
4778
4901
|
target = pickSingleSubscription(matches, nameArg);
|
|
4779
4902
|
} else {
|
|
4903
|
+
const activeSub = getActiveSubscription();
|
|
4780
4904
|
if (!activeSub) {
|
|
4781
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
|
|
4905
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
4782
4906
|
process.exit(1);
|
|
4783
4907
|
}
|
|
4784
4908
|
target = activeSub;
|
|
4785
4909
|
}
|
|
4786
|
-
const status = getStatus();
|
|
4787
|
-
if (!status.running) {
|
|
4788
|
-
console.error("\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (mihomo start)");
|
|
4789
|
-
process.exit(1);
|
|
4790
|
-
}
|
|
4791
|
-
if (!activeSub || activeSub.name !== target.name) {
|
|
4792
|
-
console.error(`\u9519\u8BEF: \u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605\u662F "${activeSub?.name}"\uFF0C\u4E0D\u662F "${target.name}"`);
|
|
4793
|
-
console.log(`\u8BF7\u5148\u5207\u6362: mihomo sub use ${target.name}`);
|
|
4794
|
-
process.exit(1);
|
|
4795
|
-
}
|
|
4796
4910
|
return { target, timeout, concurrency };
|
|
4797
4911
|
}
|
|
4798
4912
|
async function printSubscriptionList(options) {
|
|
@@ -4901,36 +5015,6 @@ async function addFreeSubscription(freeId) {
|
|
|
4901
5015
|
console.log("");
|
|
4902
5016
|
await printSubscriptionList();
|
|
4903
5017
|
}
|
|
4904
|
-
function printBestSourceList() {
|
|
4905
|
-
const bestSources = getBestSubscriptionSources();
|
|
4906
|
-
for (let i = 0; i < bestSources.length; i++) {
|
|
4907
|
-
console.log(` ${i + 1} ${bestSources[i].name} \u2014 ${bestSources[i].description}`);
|
|
4908
|
-
}
|
|
4909
|
-
}
|
|
4910
|
-
async function addBestSubscription(bestId) {
|
|
4911
|
-
const bestSources = getBestSubscriptionSources();
|
|
4912
|
-
if (bestId < 1 || bestId > bestSources.length) {
|
|
4913
|
-
console.error(`\u9519\u8BEF: best \u8BA2\u9605 ID \u8303\u56F4 1-${bestSources.length}`);
|
|
4914
|
-
console.log("\n\u53EF\u7528\u6E90:");
|
|
4915
|
-
printBestSourceList();
|
|
4916
|
-
process.exit(1);
|
|
4917
|
-
}
|
|
4918
|
-
const source = bestSources[bestId - 1];
|
|
4919
|
-
const name = `best${bestId}`;
|
|
4920
|
-
console.log(`\u6DFB\u52A0 best \u8BA2\u9605: ${name} (${source.description})`);
|
|
4921
|
-
try {
|
|
4922
|
-
addSubscription(source.url, name);
|
|
4923
|
-
const info = await downloadSubscription(source.url, name);
|
|
4924
|
-
setDefaultSubscription(name);
|
|
4925
|
-
saveSubscriptionCache(name, { web_page_url: "https://github.com/imaex/mihomo-free-sub" });
|
|
4926
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
4927
|
-
} catch (e) {
|
|
4928
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
4929
|
-
process.exit(1);
|
|
4930
|
-
}
|
|
4931
|
-
console.log("");
|
|
4932
|
-
await printSubscriptionList();
|
|
4933
|
-
}
|
|
4934
5018
|
async function cmdSubscription(args) {
|
|
4935
5019
|
const action = args[1];
|
|
4936
5020
|
if (!action || action === "list") {
|
|
@@ -4948,17 +5032,6 @@ async function cmdSubscription(args) {
|
|
|
4948
5032
|
await addFreeSubscription(id);
|
|
4949
5033
|
return;
|
|
4950
5034
|
}
|
|
4951
|
-
if (action === "best") {
|
|
4952
|
-
const id = parseInt(args[2], 10);
|
|
4953
|
-
if (Number.isNaN(id)) {
|
|
4954
|
-
console.log("\u7528\u6CD5: mihomo sub best <id>\n");
|
|
4955
|
-
console.log("\u53EF\u7528\u6E90:");
|
|
4956
|
-
printBestSourceList();
|
|
4957
|
-
process.exit(1);
|
|
4958
|
-
}
|
|
4959
|
-
await addBestSubscription(id);
|
|
4960
|
-
return;
|
|
4961
|
-
}
|
|
4962
5035
|
if (action === "add") {
|
|
4963
5036
|
const freeId = parseIntArg(args, "--free", "--free", -1);
|
|
4964
5037
|
if (freeId > 0) {
|
|
@@ -5157,14 +5230,17 @@ async function cmdSubscription(args) {
|
|
|
5157
5230
|
return;
|
|
5158
5231
|
}
|
|
5159
5232
|
if (action === "clean") {
|
|
5160
|
-
const { target, timeout, concurrency } =
|
|
5233
|
+
const { target, timeout, concurrency } = resolveTestTarget(args);
|
|
5161
5234
|
console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
|
|
5162
5235
|
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
5163
5236
|
console.log("");
|
|
5164
|
-
const result = await
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5237
|
+
const result = await withTestInstance(target.name, async (apiBase) => {
|
|
5238
|
+
return autoCleanSubscription(target.name, {
|
|
5239
|
+
timeout,
|
|
5240
|
+
concurrency,
|
|
5241
|
+
apiBase,
|
|
5242
|
+
onResult: printTestResult
|
|
5243
|
+
});
|
|
5168
5244
|
});
|
|
5169
5245
|
console.log("");
|
|
5170
5246
|
console.log(formatTestSummary(result.summary));
|
|
@@ -5173,20 +5249,26 @@ async function cmdSubscription(args) {
|
|
|
5173
5249
|
console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
|
|
5174
5250
|
} else if (result.removedProxies > 0) {
|
|
5175
5251
|
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
5176
|
-
|
|
5177
|
-
|
|
5252
|
+
const status = getStatus();
|
|
5253
|
+
if (status.running) {
|
|
5254
|
+
console.log("");
|
|
5255
|
+
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
5256
|
+
}
|
|
5178
5257
|
}
|
|
5179
5258
|
return;
|
|
5180
5259
|
}
|
|
5181
5260
|
if (action === "test") {
|
|
5182
|
-
const { target, timeout, concurrency } =
|
|
5261
|
+
const { target, timeout, concurrency } = resolveTestTarget(args);
|
|
5183
5262
|
console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
5184
5263
|
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
5185
5264
|
console.log("");
|
|
5186
|
-
const summary = await
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5265
|
+
const summary = await withTestInstance(target.name, async (apiBase) => {
|
|
5266
|
+
return testSubscriptionProxies(target.name, {
|
|
5267
|
+
timeout,
|
|
5268
|
+
concurrency,
|
|
5269
|
+
apiBase,
|
|
5270
|
+
onResult: printTestResult
|
|
5271
|
+
});
|
|
5190
5272
|
});
|
|
5191
5273
|
console.log("");
|
|
5192
5274
|
console.log(formatTestSummary(summary));
|
|
@@ -5433,6 +5515,8 @@ ${colors.cyan("\u8BA2\u9605:")}
|
|
|
5433
5515
|
${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
|
|
5434
5516
|
${colors.bold("subscription")} test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027
|
|
5435
5517
|
${colors.bold("subscription")} clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9
|
|
5518
|
+
${colors.bold("test")} [-t ms] [-j N] \u5FEB\u901F\u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\u8FDE\u901A\u6027
|
|
5519
|
+
${colors.bold("clean")} [-t ms] [-j N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u81EA\u52A8\u91CD\u542F
|
|
5436
5520
|
${colors.bold("bench")} [name] [-t ms] [-j N] \u6D4B\u8BD5\u514D\u8D39\u8BA2\u9605\u6E90\u8D28\u91CF\u6392\u540D
|
|
5437
5521
|
|
|
5438
5522
|
${colors.cyan("\u914D\u7F6E:")}
|
|
@@ -5474,8 +5558,8 @@ function printVersion() {
|
|
|
5474
5558
|
|
|
5475
5559
|
// src/kernel.ts
|
|
5476
5560
|
import { execSync as execSync4, spawnSync } from "child_process";
|
|
5477
|
-
import
|
|
5478
|
-
import
|
|
5561
|
+
import fs8 from "fs";
|
|
5562
|
+
import path6 from "path";
|
|
5479
5563
|
|
|
5480
5564
|
// node_modules/compare-versions/lib/esm/utils.js
|
|
5481
5565
|
var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
|
@@ -5600,10 +5684,10 @@ async function checkUpdate(mirror) {
|
|
|
5600
5684
|
};
|
|
5601
5685
|
}
|
|
5602
5686
|
function findBinaryInDir(dir) {
|
|
5603
|
-
const files =
|
|
5687
|
+
const files = fs8.readdirSync(dir);
|
|
5604
5688
|
for (const f of files) {
|
|
5605
|
-
const fullPath =
|
|
5606
|
-
const stat =
|
|
5689
|
+
const fullPath = path6.join(dir, f);
|
|
5690
|
+
const stat = fs8.statSync(fullPath);
|
|
5607
5691
|
if (stat.isDirectory()) {
|
|
5608
5692
|
const found = findBinaryInDir(fullPath);
|
|
5609
5693
|
if (found) return found;
|
|
@@ -5629,7 +5713,7 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5629
5713
|
\u5E73\u53F0: ${platform}, \u67B6\u6784: ${arch}${hint}`);
|
|
5630
5714
|
}
|
|
5631
5715
|
const downloadUrl = withMirror(asset.browser_download_url, mirror);
|
|
5632
|
-
const tempPath =
|
|
5716
|
+
const tempPath = path6.join(DIRS.kernel, asset.name);
|
|
5633
5717
|
const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
|
|
5634
5718
|
if (progressCallback) {
|
|
5635
5719
|
progressCallback(`\u4E0B\u8F7D\u5185\u6838: ${asset.name} (${sizeMB} MB)`);
|
|
@@ -5641,12 +5725,12 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5641
5725
|
);
|
|
5642
5726
|
if (curlResult.status !== 0) {
|
|
5643
5727
|
try {
|
|
5644
|
-
|
|
5728
|
+
fs8.unlinkSync(tempPath);
|
|
5645
5729
|
} catch {
|
|
5646
5730
|
}
|
|
5647
5731
|
throw new Error(`\u4E0B\u8F7D\u5931\u8D25 (curl \u9000\u51FA\u7801 ${curlResult.status})`);
|
|
5648
5732
|
}
|
|
5649
|
-
if (!
|
|
5733
|
+
if (!fs8.existsSync(tempPath)) {
|
|
5650
5734
|
throw new Error("\u4E0B\u8F7D\u5931\u8D25: \u6587\u4EF6\u672A\u751F\u6210");
|
|
5651
5735
|
}
|
|
5652
5736
|
if (progressCallback) {
|
|
@@ -5658,14 +5742,14 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5658
5742
|
if (tempPath.endsWith(".tar.gz") || tempPath.endsWith(".tgz")) {
|
|
5659
5743
|
execSync4(`tar -xzf "${tempPath}" -C "${extractPath}"`, { stdio: ["pipe", "pipe", "inherit"] });
|
|
5660
5744
|
} else if (tempPath.endsWith(".gz")) {
|
|
5661
|
-
const baseName =
|
|
5662
|
-
const outputPath =
|
|
5745
|
+
const baseName = path6.basename(tempPath, ".gz");
|
|
5746
|
+
const outputPath = path6.join(extractPath, baseName);
|
|
5663
5747
|
execSync4(`gzip -dc "${tempPath}" > "${outputPath}"`, { stdio: ["pipe", "pipe", "inherit"] });
|
|
5664
5748
|
extractedBinary = outputPath;
|
|
5665
5749
|
}
|
|
5666
5750
|
} catch (e) {
|
|
5667
5751
|
try {
|
|
5668
|
-
|
|
5752
|
+
fs8.unlinkSync(tempPath);
|
|
5669
5753
|
} catch {
|
|
5670
5754
|
}
|
|
5671
5755
|
throw new Error(`\u89E3\u538B\u5931\u8D25: ${e.message}`);
|
|
@@ -5673,25 +5757,25 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5673
5757
|
const foundBinary = extractedBinary || findBinaryInDir(extractPath);
|
|
5674
5758
|
if (!foundBinary) {
|
|
5675
5759
|
try {
|
|
5676
|
-
|
|
5760
|
+
fs8.unlinkSync(tempPath);
|
|
5677
5761
|
} catch {
|
|
5678
5762
|
}
|
|
5679
5763
|
throw new Error("\u89E3\u538B\u540E\u672A\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6");
|
|
5680
5764
|
}
|
|
5681
5765
|
const targetPath = PATHS.mihomoBinary;
|
|
5682
5766
|
if (foundBinary !== targetPath) {
|
|
5683
|
-
if (
|
|
5684
|
-
|
|
5767
|
+
if (fs8.existsSync(targetPath)) {
|
|
5768
|
+
fs8.chmodSync(targetPath, 493);
|
|
5685
5769
|
try {
|
|
5686
|
-
|
|
5770
|
+
fs8.unlinkSync(targetPath);
|
|
5687
5771
|
} catch {
|
|
5688
5772
|
}
|
|
5689
5773
|
}
|
|
5690
|
-
|
|
5774
|
+
fs8.renameSync(foundBinary, targetPath);
|
|
5691
5775
|
}
|
|
5692
|
-
|
|
5776
|
+
fs8.chmodSync(targetPath, 493);
|
|
5693
5777
|
try {
|
|
5694
|
-
|
|
5778
|
+
fs8.unlinkSync(tempPath);
|
|
5695
5779
|
} catch {
|
|
5696
5780
|
}
|
|
5697
5781
|
clearKernelVersionCache();
|
|
@@ -5832,7 +5916,7 @@ function cmdLogs(args) {
|
|
|
5832
5916
|
}
|
|
5833
5917
|
|
|
5834
5918
|
// src/commands/overwrite.ts
|
|
5835
|
-
import
|
|
5919
|
+
import path7 from "path";
|
|
5836
5920
|
function printOverwriteList() {
|
|
5837
5921
|
const info = listOverwriteFile();
|
|
5838
5922
|
const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
|
|
@@ -5842,8 +5926,8 @@ function printOverwriteList() {
|
|
|
5842
5926
|
if (info.files.length === 0) {
|
|
5843
5927
|
console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
|
|
5844
5928
|
console.log("");
|
|
5845
|
-
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${
|
|
5846
|
-
console.log(` \u6216 ${
|
|
5929
|
+
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path7.join(info.dir, "overwrite.yaml")}`);
|
|
5930
|
+
console.log(` \u6216 ${path7.join(info.dir, "overwrite.dns.yaml")}`);
|
|
5847
5931
|
console.log("");
|
|
5848
5932
|
} else {
|
|
5849
5933
|
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
@@ -5907,7 +5991,7 @@ async function cmdOverwrite(args) {
|
|
|
5907
5991
|
}
|
|
5908
5992
|
|
|
5909
5993
|
// src/commands/reset.ts
|
|
5910
|
-
import
|
|
5994
|
+
import fs9 from "fs";
|
|
5911
5995
|
import readline from "readline";
|
|
5912
5996
|
var RESET_TARGETS = [
|
|
5913
5997
|
{
|
|
@@ -5962,8 +6046,8 @@ var RESET_TARGETS = [
|
|
|
5962
6046
|
label: "\u8986\u5199",
|
|
5963
6047
|
paths: () => {
|
|
5964
6048
|
const dir = USER_DATA_DIR;
|
|
5965
|
-
if (!
|
|
5966
|
-
return
|
|
6049
|
+
if (!fs9.existsSync(dir)) return [];
|
|
6050
|
+
return fs9.readdirSync(dir).filter((f) => f === "overwrite.yaml" || /^overwrite\..+\.ya?ml$/.test(f)).map((f) => `${dir}/${f}`);
|
|
5967
6051
|
},
|
|
5968
6052
|
needsStop: false
|
|
5969
6053
|
}
|
|
@@ -6079,6 +6163,69 @@ async function cmdStop() {
|
|
|
6079
6163
|
console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
|
|
6080
6164
|
}
|
|
6081
6165
|
|
|
6166
|
+
// src/commands/test.ts
|
|
6167
|
+
function requireRunning() {
|
|
6168
|
+
const status = getStatus();
|
|
6169
|
+
if (!status.running) {
|
|
6170
|
+
console.error("\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (mihomo start)");
|
|
6171
|
+
process.exit(1);
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
function requireActiveSub() {
|
|
6175
|
+
const activeSub = getActiveSubscription();
|
|
6176
|
+
if (!activeSub) {
|
|
6177
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
|
|
6178
|
+
process.exit(1);
|
|
6179
|
+
}
|
|
6180
|
+
return activeSub;
|
|
6181
|
+
}
|
|
6182
|
+
async function cmdTest(args) {
|
|
6183
|
+
requireRunning();
|
|
6184
|
+
const activeSub = requireActiveSub();
|
|
6185
|
+
const timeout = parseIntArg(args, "-t", "--timeout", 1500);
|
|
6186
|
+
const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
|
|
6187
|
+
console.log(`\u6D4B\u8BD5 "${activeSub.name}" \u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
6188
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6189
|
+
console.log("");
|
|
6190
|
+
const summary = await testSubscriptionProxies(activeSub.name, {
|
|
6191
|
+
timeout,
|
|
6192
|
+
concurrency,
|
|
6193
|
+
onResult: printTestResult
|
|
6194
|
+
});
|
|
6195
|
+
console.log("");
|
|
6196
|
+
console.log(formatTestSummary(summary));
|
|
6197
|
+
}
|
|
6198
|
+
async function cmdClean(args) {
|
|
6199
|
+
requireRunning();
|
|
6200
|
+
const activeSub = requireActiveSub();
|
|
6201
|
+
const timeout = parseIntArg(args, "-t", "--timeout", 1500);
|
|
6202
|
+
const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
|
|
6203
|
+
console.log(`\u6E05\u7406 "${activeSub.name}" \u5931\u8D25\u8282\u70B9...`);
|
|
6204
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6205
|
+
console.log("");
|
|
6206
|
+
const result = await autoCleanSubscription(activeSub.name, {
|
|
6207
|
+
timeout,
|
|
6208
|
+
concurrency,
|
|
6209
|
+
onResult: printTestResult
|
|
6210
|
+
});
|
|
6211
|
+
console.log("");
|
|
6212
|
+
console.log(formatTestSummary(result.summary));
|
|
6213
|
+
if (result.skipped) {
|
|
6214
|
+
console.log("");
|
|
6215
|
+
console.log("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548");
|
|
6216
|
+
} else if (result.removedProxies === 0) {
|
|
6217
|
+
console.log("\u6240\u6709\u8282\u70B9\u6B63\u5E38\uFF0C\u65E0\u9700\u6E05\u7406");
|
|
6218
|
+
} else {
|
|
6219
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
6220
|
+
console.log("");
|
|
6221
|
+
console.log("\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548...");
|
|
6222
|
+
stop();
|
|
6223
|
+
const configInfo = prepareConfigForStart("mixed", activeSub.name);
|
|
6224
|
+
const startResult = await start("mixed");
|
|
6225
|
+
console.log(`${colors.green("\u5DF2\u91CD\u542F")} (PID ${startResult.pid}) \xB7 ${formatProxySummary(configInfo)}`);
|
|
6226
|
+
}
|
|
6227
|
+
}
|
|
6228
|
+
|
|
6082
6229
|
// src/commands/ui.ts
|
|
6083
6230
|
function cmdUI(args) {
|
|
6084
6231
|
const uiName = args[1] || "zash";
|
|
@@ -6097,7 +6244,7 @@ function cmdUI(args) {
|
|
|
6097
6244
|
}
|
|
6098
6245
|
|
|
6099
6246
|
// src/commands/update.ts
|
|
6100
|
-
import { exec, spawn as
|
|
6247
|
+
import { exec, spawn as spawn4 } from "child_process";
|
|
6101
6248
|
import { promisify } from "util";
|
|
6102
6249
|
var execAsync = promisify(exec);
|
|
6103
6250
|
async function cmdUpdate() {
|
|
@@ -6106,7 +6253,7 @@ async function cmdUpdate() {
|
|
|
6106
6253
|
console.log("\u6B63\u5728\u66F4\u65B0 mihomo-cli...");
|
|
6107
6254
|
console.log("");
|
|
6108
6255
|
await new Promise((resolve) => {
|
|
6109
|
-
const npm =
|
|
6256
|
+
const npm = spawn4("npm", ["install", "-g", "mihomo-cli"], { stdio: "inherit" });
|
|
6110
6257
|
npm.on("close", (code) => {
|
|
6111
6258
|
if (code === 0) {
|
|
6112
6259
|
resolve();
|
|
@@ -6254,6 +6401,12 @@ async function main() {
|
|
|
6254
6401
|
case "bench":
|
|
6255
6402
|
await cmdBench(args);
|
|
6256
6403
|
break;
|
|
6404
|
+
case "test":
|
|
6405
|
+
await cmdTest(args);
|
|
6406
|
+
break;
|
|
6407
|
+
case "clean":
|
|
6408
|
+
await cmdClean(args);
|
|
6409
|
+
break;
|
|
6257
6410
|
default:
|
|
6258
6411
|
console.error(`\u672A\u77E5\u547D\u4EE4: ${cmd}`);
|
|
6259
6412
|
console.error('\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9');
|