mihomo-cli 2.3.0 → 2.4.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 CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.4.0] - 2026-05-02
4
+
5
+ ### 新功能
6
+
7
+ - **sub best** - `sub best <id>` 一键添加聚合订阅(每小时自动更新、去重、测活)
8
+ - `best 1` 精选 29 组(仅测速源:FreeSubsCheck, shaoyouvip, dalazhi, getnode)
9
+ - `best 2` ACL4SSR 29 组(全部 7 个源)
10
+ - `best 3` freeSub 24 组
11
+ - **新增免费源** - yahr601, Auto-Sync, ssrsub, dalazhi, getnode
12
+
13
+ ### 修复
14
+
15
+ - `setDefaultSubscription` 移到下载成功后再设置,避免下载失败留下无效默认订阅
16
+
17
+ ---
18
+
19
+ ## [2.3.1] - 2026-05-02
20
+
21
+ ### 新功能
22
+
23
+ - **合并订阅** - `sub add url1,url2 name` 支持逗号分隔多 URL,合并节点(按名去重),分组/规则取第一个源
24
+ - **sub free 0** - 特殊 ID `0` 自动合并免费源 #1 + #2(节点更多,配置相同)
25
+
26
+ ### 改进
27
+
28
+ - 合并订阅在列表中显示 `[合并 N 源]` 标记
29
+ - `sub update` 自动识别合并订阅并重新下载合并
30
+ - URL 脱敏支持逗号分隔多 URL
31
+
32
+ ---
33
+
3
34
  ## [2.3.0] - 2026-05-02
4
35
 
5
36
  ### 新功能
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
  - 🚀 **进程管理** - 启动/停止/切换模式,自动清理残留进程
@@ -98,8 +99,9 @@ mihomo ui yacd # YACD
98
99
  | ----------------------------- | -------------------------------------- |
99
100
  | `mihomo sub` | 列出所有订阅(含流量、到期时间) |
100
101
  | `mihomo sub use <name>` | 切换当前订阅(支持模糊匹配,自动重启) |
101
- | `mihomo sub add <url> [name]` | 添加订阅并自动切换 |
102
- | `mihomo sub free <id>` | 添加内置免费订阅(`sub free` 列出可用源)|
102
+ | `mihomo sub add <url> [name]` | 添加订阅并自动切换(支持逗号分隔多 URL 合并) |
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",
@@ -2795,6 +2810,9 @@ function invalidateSettingsCache() {
2795
2810
  }
2796
2811
  function maskUrl(url) {
2797
2812
  if (!url) return url;
2813
+ if (url.includes(",")) {
2814
+ return url.split(",").map((u) => maskUrl(u.trim())).join(", ");
2815
+ }
2798
2816
  try {
2799
2817
  const parsed = new URL(url);
2800
2818
  const tokenKeys = ["token", "key", "secret", "pass", "password", "auth", "access_token", "api_key"];
@@ -4207,6 +4225,12 @@ function viewLogWithTail(logPath, options) {
4207
4225
  var DEFAULT_UPDATE_INTERVAL_HOURS = 12;
4208
4226
  var YAML_DUMP_OPTS = { indent: 2, lineWidth: -1, noCompatMode: true };
4209
4227
  var HTTP_CLIENT = createHttpClient({ timeout: 6e4 });
4228
+ function isMultiUrl(url) {
4229
+ return url.includes(",");
4230
+ }
4231
+ function splitUrls(url) {
4232
+ return url.split(",").map((u) => u.trim()).filter(Boolean);
4233
+ }
4210
4234
  function loadSubscriptionConfig(subName) {
4211
4235
  const rawContent = readSubscriptionRawConfig(subName);
4212
4236
  if (!rawContent) {
@@ -4343,6 +4367,71 @@ async function downloadSubscription(url, subName = "default") {
4343
4367
  username
4344
4368
  };
4345
4369
  }
4370
+ async function downloadMergedSubscription(urls, subName) {
4371
+ const responses = await Promise.all(
4372
+ urls.map(async (url, index) => {
4373
+ try {
4374
+ const response = await HTTP_CLIENT.get(url, { responseType: "text" });
4375
+ return { url, index, response, error: null };
4376
+ } catch (e) {
4377
+ return { url, index, response: null, error: e };
4378
+ }
4379
+ })
4380
+ );
4381
+ for (const r of responses) {
4382
+ if (r.error) {
4383
+ const maskedUrl = maskUrl(r.url);
4384
+ throw new Error(`\u5408\u5E76\u8BA2\u9605\u7B2C ${r.index + 1} \u4E2A URL \u83B7\u53D6\u5931\u8D25: ${r.error.message}
4385
+ URL: ${maskedUrl}`);
4386
+ }
4387
+ }
4388
+ const parsed = responses.map((r, i) => {
4389
+ const content = r.response?.data;
4390
+ if (!content?.trim()) throw new Error(`\u5408\u5E76\u8BA2\u9605\u7B2C ${i + 1} \u4E2A URL \u5185\u5BB9\u4E3A\u7A7A`);
4391
+ return parseYamlOrJson(content, `\u5408\u5E76\u8BA2\u9605\u7B2C ${i + 1} \u4E2A`);
4392
+ });
4393
+ const base = parsed[0];
4394
+ const baseProxies = base.proxies || [];
4395
+ const seenNames = new Set(baseProxies.map((p) => p.name));
4396
+ for (let i = 1; i < parsed.length; i++) {
4397
+ const extraProxies = parsed[i].proxies || [];
4398
+ for (const proxy of extraProxies) {
4399
+ if (!seenNames.has(proxy.name)) {
4400
+ baseProxies.push(proxy);
4401
+ seenNames.add(proxy.name);
4402
+ }
4403
+ }
4404
+ }
4405
+ base.proxies = baseProxies;
4406
+ const mergedContent = jsYaml.dump(base, YAML_DUMP_OPTS);
4407
+ saveSubscriptionRawConfig(subName, mergedContent);
4408
+ const firstHeaders = responses[0].response?.headers;
4409
+ const userInfo = parseUserInfo(firstHeaders?.get("subscription-userinfo") ?? null);
4410
+ const updateIntervalHeader = firstHeaders?.get("profile-update-interval");
4411
+ const updateInterval = updateIntervalHeader ? parseInt(updateIntervalHeader, 10) : null;
4412
+ const webPageUrl = firstHeaders?.get("profile-web-page-url") || null;
4413
+ const username = parseUsernameFromContentDisposition(firstHeaders?.get("content-disposition") ?? null);
4414
+ const cacheData = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
4415
+ if (userInfo) {
4416
+ cacheData.upload = userInfo.upload;
4417
+ cacheData.download = userInfo.download;
4418
+ cacheData.total = userInfo.total;
4419
+ cacheData.expire = userInfo.expire;
4420
+ }
4421
+ if (updateInterval) cacheData.update_interval = updateInterval;
4422
+ if (webPageUrl) cacheData.web_page_url = webPageUrl;
4423
+ if (username) cacheData.username = username;
4424
+ saveSubscriptionCache(subName, cacheData);
4425
+ const proxyGroups = base["proxy-groups"];
4426
+ return {
4427
+ proxies: baseProxies.length,
4428
+ proxyGroups: proxyGroups ? proxyGroups.length : 0,
4429
+ userInfo,
4430
+ updateInterval,
4431
+ webPageUrl,
4432
+ username
4433
+ };
4434
+ }
4346
4435
  function prepareConfigForStart(mode, subName = "default") {
4347
4436
  const rawContent = readSubscriptionRawConfig(subName);
4348
4437
  if (!rawContent) {
@@ -4368,7 +4457,12 @@ function needsAutoUpdate(sub) {
4368
4457
  }
4369
4458
  async function tryUpdateOne(sub) {
4370
4459
  try {
4371
- const info = await downloadSubscription(sub.url, sub.name);
4460
+ let info;
4461
+ if (isMultiUrl(sub.url)) {
4462
+ info = await downloadMergedSubscription(splitUrls(sub.url), sub.name);
4463
+ } else {
4464
+ info = await downloadSubscription(sub.url, sub.name);
4465
+ }
4372
4466
  return { name: sub.name, success: true, proxies: info.proxies, proxyGroups: info.proxyGroups };
4373
4467
  } catch (e) {
4374
4468
  return { name: sub.name, success: false, error: e.message };
@@ -4730,8 +4824,9 @@ async function printSubscriptionList(options) {
4730
4824
  subs.forEach((s, i) => {
4731
4825
  const time = formatDate(s.updated_at);
4732
4826
  const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
4827
+ const mergeBadge = isMultiUrl(s.url) ? colors.cyan(` [\u5408\u5E76 ${splitUrls(s.url).length} \u6E90]`) : "";
4733
4828
  const interval = s.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
4734
- console.log(` ${i + 1}. ${s.name}${defaultMark}`);
4829
+ console.log(` ${i + 1}. ${s.name}${defaultMark}${mergeBadge}`);
4735
4830
  console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
4736
4831
  if (s.username) {
4737
4832
  console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
@@ -4764,14 +4859,40 @@ async function printSubscriptionList(options) {
4764
4859
  console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
4765
4860
  console.log("");
4766
4861
  }
4862
+ function printFreeSourceList() {
4863
+ const freeSources = getFreeSubscriptionSources();
4864
+ console.log(` 00 \u5408\u5E76 #1 + #2 (\u8282\u70B9\u66F4\u591A)`);
4865
+ for (let i = 0; i < freeSources.length; i++) {
4866
+ console.log(` ${String(i + 1).padStart(2, "0")} ${freeSources[i].name}`);
4867
+ }
4868
+ }
4767
4869
  async function addFreeSubscription(freeId) {
4768
4870
  const freeSources = getFreeSubscriptionSources();
4871
+ if (freeId === 0) {
4872
+ const sources = [freeSources[0], freeSources[1]];
4873
+ const urls = sources.map((s) => s.url);
4874
+ const mergedUrl = urls.join(",");
4875
+ const name2 = "free0";
4876
+ console.log(`\u6DFB\u52A0\u5408\u5E76\u514D\u8D39\u8BA2\u9605: ${name2} (${sources.map((s) => s.name).join(" + ")})`);
4877
+ try {
4878
+ addSubscription(mergedUrl, name2);
4879
+ const info = await downloadMergedSubscription(urls, name2);
4880
+ setDefaultSubscription(name2);
4881
+ const repoUrls = sources.map((s) => githubRepoUrl(s.url)).filter(Boolean);
4882
+ if (repoUrls.length > 0) saveSubscriptionCache(name2, { web_page_url: repoUrls.join(", ") });
4883
+ console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name2}" (${formatProxySummary(info)}, \u5408\u5E76 ${sources.length} \u6E90)`);
4884
+ } catch (e) {
4885
+ console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
4886
+ process.exit(1);
4887
+ }
4888
+ console.log("");
4889
+ await printSubscriptionList();
4890
+ return;
4891
+ }
4769
4892
  if (freeId < 1 || freeId > freeSources.length) {
4770
- console.error(`\u9519\u8BEF: \u514D\u8D39\u8BA2\u9605 ID \u8303\u56F4 1-${freeSources.length}`);
4893
+ console.error(`\u9519\u8BEF: \u514D\u8D39\u8BA2\u9605 ID \u8303\u56F4 0-${freeSources.length}`);
4771
4894
  console.log("\n\u53EF\u7528\u6E90:");
4772
- for (let i = 0; i < freeSources.length; i++) {
4773
- console.log(` ${String(i + 1).padStart(2, "0")} ${freeSources[i].name}`);
4774
- }
4895
+ printFreeSourceList();
4775
4896
  process.exit(1);
4776
4897
  }
4777
4898
  const source = freeSources[freeId - 1];
@@ -4779,8 +4900,8 @@ async function addFreeSubscription(freeId) {
4779
4900
  console.log(`\u6DFB\u52A0\u514D\u8D39\u8BA2\u9605: ${name}`);
4780
4901
  try {
4781
4902
  addSubscription(source.url, name);
4782
- setDefaultSubscription(name);
4783
4903
  const info = await downloadSubscription(source.url, name);
4904
+ setDefaultSubscription(name);
4784
4905
  const repoUrl = githubRepoUrl(source.url);
4785
4906
  if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
4786
4907
  console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
@@ -4791,6 +4912,36 @@ async function addFreeSubscription(freeId) {
4791
4912
  console.log("");
4792
4913
  await printSubscriptionList();
4793
4914
  }
4915
+ function printBestSourceList() {
4916
+ const bestSources = getBestSubscriptionSources();
4917
+ for (let i = 0; i < bestSources.length; i++) {
4918
+ console.log(` ${i + 1} ${bestSources[i].name} \u2014 ${bestSources[i].description}`);
4919
+ }
4920
+ }
4921
+ async function addBestSubscription(bestId) {
4922
+ const bestSources = getBestSubscriptionSources();
4923
+ if (bestId < 1 || bestId > bestSources.length) {
4924
+ console.error(`\u9519\u8BEF: best \u8BA2\u9605 ID \u8303\u56F4 1-${bestSources.length}`);
4925
+ console.log("\n\u53EF\u7528\u6E90:");
4926
+ printBestSourceList();
4927
+ process.exit(1);
4928
+ }
4929
+ const source = bestSources[bestId - 1];
4930
+ const name = `best${bestId}`;
4931
+ console.log(`\u6DFB\u52A0 best \u8BA2\u9605: ${name} (${source.description})`);
4932
+ try {
4933
+ addSubscription(source.url, name);
4934
+ const info = await downloadSubscription(source.url, name);
4935
+ setDefaultSubscription(name);
4936
+ saveSubscriptionCache(name, { web_page_url: "https://github.com/imaex/mihomo-free-sub" });
4937
+ console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
4938
+ } catch (e) {
4939
+ console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
4940
+ process.exit(1);
4941
+ }
4942
+ console.log("");
4943
+ await printSubscriptionList();
4944
+ }
4794
4945
  async function cmdSubscription(args) {
4795
4946
  const action = args[1];
4796
4947
  if (!action || action === "list") {
@@ -4799,18 +4950,26 @@ async function cmdSubscription(args) {
4799
4950
  }
4800
4951
  if (action === "free") {
4801
4952
  const id = parseInt(args[2], 10);
4802
- if (!id || Number.isNaN(id)) {
4803
- const freeSources = getFreeSubscriptionSources();
4953
+ if (Number.isNaN(id)) {
4804
4954
  console.log("\u7528\u6CD5: mihomo sub free <id>\n");
4805
4955
  console.log("\u53EF\u7528\u6E90:");
4806
- for (let i = 0; i < freeSources.length; i++) {
4807
- console.log(` ${String(i + 1).padStart(2, "0")} ${freeSources[i].name}`);
4808
- }
4956
+ printFreeSourceList();
4809
4957
  process.exit(1);
4810
4958
  }
4811
4959
  await addFreeSubscription(id);
4812
4960
  return;
4813
4961
  }
4962
+ if (action === "best") {
4963
+ const id = parseInt(args[2], 10);
4964
+ if (Number.isNaN(id)) {
4965
+ console.log("\u7528\u6CD5: mihomo sub best <id>\n");
4966
+ console.log("\u53EF\u7528\u6E90:");
4967
+ printBestSourceList();
4968
+ process.exit(1);
4969
+ }
4970
+ await addBestSubscription(id);
4971
+ return;
4972
+ }
4814
4973
  if (action === "add") {
4815
4974
  const freeId = parseIntArg(args, "--free", "--free", -1);
4816
4975
  if (freeId > 0) {
@@ -4819,21 +4978,45 @@ async function cmdSubscription(args) {
4819
4978
  }
4820
4979
  const url = args[2];
4821
4980
  const name = args[3] || "default";
4822
- if (!url?.startsWith("http")) {
4981
+ if (!url) {
4823
4982
  console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
4824
4983
  process.exit(1);
4825
4984
  }
4826
- console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
4827
- try {
4828
- addSubscription(url, name);
4829
- setDefaultSubscription(name);
4830
- const info = await downloadSubscription(url, name);
4831
- const repoUrl = githubRepoUrl(url);
4832
- if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
4833
- console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
4834
- } catch (e) {
4835
- console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
4836
- process.exit(1);
4985
+ if (isMultiUrl(url)) {
4986
+ const urls = splitUrls(url);
4987
+ for (const u of urls) {
4988
+ if (!u.startsWith("http")) {
4989
+ console.error(`\u9519\u8BEF: \u65E0\u6548\u7684 URL: ${u}`);
4990
+ process.exit(1);
4991
+ }
4992
+ }
4993
+ console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
4994
+ try {
4995
+ addSubscription(url, name);
4996
+ setDefaultSubscription(name);
4997
+ const info = await downloadMergedSubscription(urls, name);
4998
+ console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
4999
+ } catch (e) {
5000
+ console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
5001
+ process.exit(1);
5002
+ }
5003
+ } else {
5004
+ if (!url.startsWith("http")) {
5005
+ console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
5006
+ process.exit(1);
5007
+ }
5008
+ console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
5009
+ try {
5010
+ addSubscription(url, name);
5011
+ setDefaultSubscription(name);
5012
+ const info = await downloadSubscription(url, name);
5013
+ const repoUrl = githubRepoUrl(url);
5014
+ if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
5015
+ console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
5016
+ } catch (e) {
5017
+ console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
5018
+ process.exit(1);
5019
+ }
4837
5020
  }
4838
5021
  console.log("");
4839
5022
  await printSubscriptionList();
@@ -4867,7 +5050,13 @@ async function cmdSubscription(args) {
4867
5050
  const target = pickSingleSubscription(matches, name);
4868
5051
  console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
4869
5052
  try {
4870
- const info = await downloadSubscription(target.url, target.name);
5053
+ let info;
5054
+ if (isMultiUrl(target.url)) {
5055
+ const urls = splitUrls(target.url);
5056
+ info = await downloadMergedSubscription(urls, target.name);
5057
+ } else {
5058
+ info = await downloadSubscription(target.url, target.name);
5059
+ }
4871
5060
  console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(info)})`);
4872
5061
  } catch (e) {
4873
5062
  console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mihomo-cli",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "description": "A terminal-based mihomo (Clash.Meta) client for macOS",
6
6
  "bin": {