mihomo-cli 2.3.1 → 2.4.1
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 +24 -0
- package/README.md +2 -0
- package/dist/index.js +95 -41
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.1] - 2026-05-02
|
|
4
|
+
|
|
5
|
+
### 修复
|
|
6
|
+
|
|
7
|
+
- 启动时清除代理环境变量(`http_proxy` / `https_proxy` / `all_proxy`),避免系统已有代理导致请求异常
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## [2.4.0] - 2026-05-02
|
|
12
|
+
|
|
13
|
+
### 新功能
|
|
14
|
+
|
|
15
|
+
- **sub best** - `sub best <id>` 一键添加聚合订阅(每小时自动更新、去重、测活)
|
|
16
|
+
- `best 1` 精选 29 组(仅测速源:FreeSubsCheck, shaoyouvip, dalazhi, getnode)
|
|
17
|
+
- `best 2` ACL4SSR 29 组(全部 7 个源)
|
|
18
|
+
- `best 3` freeSub 24 组
|
|
19
|
+
- **新增免费源** - yahr601, Auto-Sync, ssrsub, dalazhi, getnode
|
|
20
|
+
|
|
21
|
+
### 修复
|
|
22
|
+
|
|
23
|
+
- `setDefaultSubscription` 移到下载成功后再设置,避免下载失败留下无效默认订阅
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
3
27
|
## [2.3.1] - 2026-05-02
|
|
4
28
|
|
|
5
29
|
### 新功能
|
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
- 🧹 **节点测速清理** - `sub test` 测试连通性,`sub clean` 自动清理失败节点,启动时自动清理
|
|
11
11
|
- 📊 **免费订阅基准测试** - `bench` 命令一键测试 20 个内置免费订阅源质量排名
|
|
12
12
|
- 🆓 **快速添加免费订阅** - `sub free <id>` 一键添加内置免费订阅源
|
|
13
|
+
- 🏆 **聚合订阅** - `sub best <id>` 一键添加自动更新的聚合订阅(每小时更新、去重、测活)
|
|
13
14
|
- 📝 **覆写配置** - 在订阅基础上进行自定义覆写,支持强制覆盖、数组合并
|
|
14
15
|
- 🔄 **智能重启** - `sub use` 切换订阅、`ow on/off` 切换覆写后自动重启
|
|
15
16
|
- 🚀 **进程管理** - 启动/停止/切换模式,自动清理残留进程
|
|
@@ -100,6 +101,7 @@ mihomo ui yacd # YACD
|
|
|
100
101
|
| `mihomo sub use <name>` | 切换当前订阅(支持模糊匹配,自动重启) |
|
|
101
102
|
| `mihomo sub add <url> [name]` | 添加订阅并自动切换(支持逗号分隔多 URL 合并) |
|
|
102
103
|
| `mihomo sub free <id>` | 添加内置免费订阅(`0`=合并 #1+#2,`sub free` 列出可用源)|
|
|
104
|
+
| `mihomo sub best <id>` | 添加聚合订阅(`sub best` 列出可用源)|
|
|
103
105
|
| `mihomo sub update` | 更新所有订阅 |
|
|
104
106
|
| `mihomo sub update <name>` | 更新指定订阅(支持模糊匹配) |
|
|
105
107
|
| `mihomo sub remove <name>` | 删除订阅(支持模糊匹配) |
|
package/dist/index.js
CHANGED
|
@@ -2652,9 +2652,16 @@ var TUN_CONFIG = {
|
|
|
2652
2652
|
};
|
|
2653
2653
|
function getFreeSubscriptionSources() {
|
|
2654
2654
|
return [
|
|
2655
|
-
// 完整配置(
|
|
2655
|
+
// 完整配置(ACL4SSR 29 组)
|
|
2656
2656
|
{ name: "FreeSubsCheck", url: "https://gh-proxy.org/raw.githubusercontent.com/kooker/FreeSubsCheck/main/mihomo.yaml" },
|
|
2657
|
+
{ name: "yahr601", url: "https://gh-proxy.org/raw.githubusercontent.com/yahr601-prog/1/main/clash.yaml" },
|
|
2658
|
+
{ name: "Auto-Sync", url: "https://gh-proxy.org/raw.githubusercontent.com/walke2019/Auto-Sync/main/clash/GG/clash.yaml" },
|
|
2659
|
+
{ name: "ssrsub", url: "https://gh-proxy.org/raw.githubusercontent.com/ssrsub/ssr/master/clash.yaml" },
|
|
2660
|
+
// OpenAi → Ai平台
|
|
2657
2661
|
{ name: "shaoyouvip", url: "https://gh-proxy.org/raw.githubusercontent.com/shaoyouvip/free/main/mihomo.yaml" },
|
|
2662
|
+
{ name: "dalazhi", url: "https://gh-proxy.org/raw.githubusercontent.com/dalazhi/v2ray/main/data/mihomo.yaml" },
|
|
2663
|
+
{ name: "getnode", url: "https://gh-proxy.org/raw.githubusercontent.com/limitless-d/getnode/main/clash.yaml" },
|
|
2664
|
+
// 完整配置(24 组)
|
|
2658
2665
|
{ name: "freeSub", url: "https://gh-proxy.org/raw.githubusercontent.com/Ruk1ng001/freeSub/main/clash.yaml" },
|
|
2659
2666
|
// 完整配置(13 组)
|
|
2660
2667
|
{ name: "PuddinCat", url: "https://gh-proxy.org/raw.githubusercontent.com/PuddinCat/BestClash/refs/heads/main/proxies.yaml" },
|
|
@@ -2680,6 +2687,14 @@ function getFreeSubscriptionSources() {
|
|
|
2680
2687
|
{ name: "NoMoreWalls", url: "https://gh-proxy.org/raw.githubusercontent.com/peasoft/NoMoreWalls/master/list.meta.yml" }
|
|
2681
2688
|
];
|
|
2682
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
|
+
}
|
|
2683
2698
|
var BENCH_CONFIG = {
|
|
2684
2699
|
"allow-lan": false,
|
|
2685
2700
|
"external-controller": "127.0.0.1:19090",
|
|
@@ -3494,48 +3509,37 @@ function parseProxyUris(content) {
|
|
|
3494
3509
|
return proxies;
|
|
3495
3510
|
}
|
|
3496
3511
|
async function downloadAllSources(sources, onProgress) {
|
|
3497
|
-
const
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
const entry = { name: source.name, url: source.url, proxies: [], proxyGroups: 0 };
|
|
3512
|
+
const client = createHttpClient({ timeout: 3e4 });
|
|
3513
|
+
const tasks = sources.map(async (source) => {
|
|
3514
|
+
const entry = { name: source.name, url: source.url, proxies: [], proxyGroups: 0 };
|
|
3515
|
+
try {
|
|
3516
|
+
const response = await client.get(source.url, { responseType: "text" });
|
|
3517
|
+
const content = response.data;
|
|
3518
|
+
if (!content?.trim()) throw new Error("\u5185\u5BB9\u4E3A\u7A7A");
|
|
3519
|
+
let proxies;
|
|
3506
3520
|
try {
|
|
3507
|
-
const
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
const decoded = tryDecodeBase64Content(content);
|
|
3518
|
-
if (decoded) {
|
|
3519
|
-
proxies = parseProxyUris(decoded);
|
|
3520
|
-
} else {
|
|
3521
|
-
proxies = parseProxyUris(content);
|
|
3522
|
-
}
|
|
3523
|
-
if (proxies.length === 0) throw new Error("\u65E0\u6CD5\u89E3\u6790\u8BA2\u9605\u5185\u5BB9\uFF08\u975E YAML/JSON/Base64\uFF09");
|
|
3521
|
+
const parsed = parseYamlOrJson(content, "\u8BA2\u9605\u5185\u5BB9");
|
|
3522
|
+
proxies = parsed.proxies || [];
|
|
3523
|
+
const groups = parsed["proxy-groups"];
|
|
3524
|
+
if (groups) entry.proxyGroups = groups.length;
|
|
3525
|
+
} catch {
|
|
3526
|
+
const decoded = tryDecodeBase64Content(content);
|
|
3527
|
+
if (decoded) {
|
|
3528
|
+
proxies = parseProxyUris(decoded);
|
|
3529
|
+
} else {
|
|
3530
|
+
proxies = parseProxyUris(content);
|
|
3524
3531
|
}
|
|
3525
|
-
|
|
3526
|
-
onProgress?.(source.name, true, proxies.length, entry.proxyGroups);
|
|
3527
|
-
} catch (e) {
|
|
3528
|
-
entry.error = e.message;
|
|
3529
|
-
onProgress?.(source.name, false, 0, 0, entry.error);
|
|
3532
|
+
if (proxies.length === 0) throw new Error("\u65E0\u6CD5\u89E3\u6790\u8BA2\u9605\u5185\u5BB9\uFF08\u975E YAML/JSON/Base64\uFF09");
|
|
3530
3533
|
}
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
if (val !== void 0) process.env[key] = val;
|
|
3534
|
+
entry.proxies = proxies.map((p) => ({ ...p, name: `[${source.name}] ${p.name}` }));
|
|
3535
|
+
onProgress?.(source.name, true, proxies.length, entry.proxyGroups);
|
|
3536
|
+
} catch (e) {
|
|
3537
|
+
entry.error = e.message;
|
|
3538
|
+
onProgress?.(source.name, false, 0, 0, entry.error);
|
|
3537
3539
|
}
|
|
3538
|
-
|
|
3540
|
+
return entry;
|
|
3541
|
+
});
|
|
3542
|
+
return await Promise.all(tasks);
|
|
3539
3543
|
}
|
|
3540
3544
|
function isProxyValid(proxy) {
|
|
3541
3545
|
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
@@ -4861,8 +4865,8 @@ async function addFreeSubscription(freeId) {
|
|
|
4861
4865
|
console.log(`\u6DFB\u52A0\u5408\u5E76\u514D\u8D39\u8BA2\u9605: ${name2} (${sources.map((s) => s.name).join(" + ")})`);
|
|
4862
4866
|
try {
|
|
4863
4867
|
addSubscription(mergedUrl, name2);
|
|
4864
|
-
setDefaultSubscription(name2);
|
|
4865
4868
|
const info = await downloadMergedSubscription(urls, name2);
|
|
4869
|
+
setDefaultSubscription(name2);
|
|
4866
4870
|
const repoUrls = sources.map((s) => githubRepoUrl(s.url)).filter(Boolean);
|
|
4867
4871
|
if (repoUrls.length > 0) saveSubscriptionCache(name2, { web_page_url: repoUrls.join(", ") });
|
|
4868
4872
|
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name2}" (${formatProxySummary(info)}, \u5408\u5E76 ${sources.length} \u6E90)`);
|
|
@@ -4885,8 +4889,8 @@ async function addFreeSubscription(freeId) {
|
|
|
4885
4889
|
console.log(`\u6DFB\u52A0\u514D\u8D39\u8BA2\u9605: ${name}`);
|
|
4886
4890
|
try {
|
|
4887
4891
|
addSubscription(source.url, name);
|
|
4888
|
-
setDefaultSubscription(name);
|
|
4889
4892
|
const info = await downloadSubscription(source.url, name);
|
|
4893
|
+
setDefaultSubscription(name);
|
|
4890
4894
|
const repoUrl = githubRepoUrl(source.url);
|
|
4891
4895
|
if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
|
|
4892
4896
|
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
@@ -4897,6 +4901,36 @@ async function addFreeSubscription(freeId) {
|
|
|
4897
4901
|
console.log("");
|
|
4898
4902
|
await printSubscriptionList();
|
|
4899
4903
|
}
|
|
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
|
+
}
|
|
4900
4934
|
async function cmdSubscription(args) {
|
|
4901
4935
|
const action = args[1];
|
|
4902
4936
|
if (!action || action === "list") {
|
|
@@ -4914,6 +4948,17 @@ async function cmdSubscription(args) {
|
|
|
4914
4948
|
await addFreeSubscription(id);
|
|
4915
4949
|
return;
|
|
4916
4950
|
}
|
|
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
|
+
}
|
|
4917
4962
|
if (action === "add") {
|
|
4918
4963
|
const freeId = parseIntArg(args, "--free", "--free", -1);
|
|
4919
4964
|
if (freeId > 0) {
|
|
@@ -6118,7 +6163,16 @@ process.on("unhandledRejection", (reason) => {
|
|
|
6118
6163
|
\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD: ${msg}`);
|
|
6119
6164
|
process.exit(1);
|
|
6120
6165
|
});
|
|
6166
|
+
function clearProxyEnv() {
|
|
6167
|
+
delete process.env.http_proxy;
|
|
6168
|
+
delete process.env.https_proxy;
|
|
6169
|
+
delete process.env.HTTP_PROXY;
|
|
6170
|
+
delete process.env.HTTPS_PROXY;
|
|
6171
|
+
delete process.env.all_proxy;
|
|
6172
|
+
delete process.env.ALL_PROXY;
|
|
6173
|
+
}
|
|
6121
6174
|
async function main() {
|
|
6175
|
+
clearProxyEnv();
|
|
6122
6176
|
ensureDirs();
|
|
6123
6177
|
const args = process.argv.slice(2);
|
|
6124
6178
|
if (args.length === 0) {
|