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 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
- // 完整配置(DNS + 分组 + rule-provider, 29 组)
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 savedProxy = { http: process.env.http_proxy, https: process.env.https_proxy, HTTP: process.env.HTTP_PROXY, HTTPS: process.env.HTTPS_PROXY };
3498
- delete process.env.http_proxy;
3499
- delete process.env.https_proxy;
3500
- delete process.env.HTTP_PROXY;
3501
- delete process.env.HTTPS_PROXY;
3502
- try {
3503
- const client = createHttpClient({ timeout: 3e4 });
3504
- const tasks = sources.map(async (source) => {
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 response = await client.get(source.url, { responseType: "text" });
3508
- const content = response.data;
3509
- if (!content?.trim()) throw new Error("\u5185\u5BB9\u4E3A\u7A7A");
3510
- let proxies;
3511
- try {
3512
- const parsed = parseYamlOrJson(content, "\u8BA2\u9605\u5185\u5BB9");
3513
- proxies = parsed.proxies || [];
3514
- const groups = parsed["proxy-groups"];
3515
- if (groups) entry.proxyGroups = groups.length;
3516
- } catch {
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
- entry.proxies = proxies.map((p) => ({ ...p, name: `[${source.name}] ${p.name}` }));
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
- return entry;
3532
- });
3533
- return await Promise.all(tasks);
3534
- } finally {
3535
- for (const [key, val] of Object.entries(savedProxy)) {
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mihomo-cli",
3
- "version": "2.3.1",
3
+ "version": "2.4.1",
4
4
  "type": "module",
5
5
  "description": "A terminal-based mihomo (Clash.Meta) client for macOS",
6
6
  "bin": {