mihomo-cli 2.0.0 → 2.1.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,43 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.1.0] - 2026-05-01
4
+
5
+ ### 新增
6
+
7
+ - **删除订阅**:`sub remove <name>` 删除订阅(别名 `rm`/`delete`),同时清理缓存和配置文件
8
+ - 删除当前使用中的订阅时自动切换到第一个剩余订阅
9
+ - **添加即切换**:`sub add` 添加订阅后自动切换为当前使用的订阅
10
+
11
+ ### 安全
12
+
13
+ - **强制 `allow-lan: false`**:无论订阅配置如何,始终禁止局域网访问
14
+ - **强制 `external-controller: 127.0.0.1:9090`**:控制面板仅监听本地,防止不可信订阅暴露控制接口
15
+ - **剥离 `external-ui` 相关字段**:构建配置时强制删除 `external-ui`/`external-ui-name`/`external-ui-url`,防止订阅触发额外下载
16
+
17
+ ### 优化
18
+
19
+ - **TUN DNS 劫持**:`dns-hijack` 从 `['0.0.0.0:53']` 改为 `['any:53', 'tcp://any:53']`,同时劫持 UDP 和 TCP DNS,覆盖 IPv4/IPv6
20
+ - **帮助顺序统一**:订阅子命令统一为 use → add → update → remove → web 顺序
21
+ - **`removeSubscription` 返回切换信息**:返回自动切换到的订阅名,避免调用方重复读取状态
22
+ - **`setDefaultSubscription` 跳过冗余写入**:已是同值时直接返回
23
+ - **删除后跳过自动更新**:`sub remove` 后列出订阅时不触发网络更新
24
+
25
+ ---
26
+
27
+ ## [2.0.1] - 2026-04-22
28
+
29
+ ### 修复
30
+
31
+ - **TUN DNS 默认值**:使用属性存在性检查替代 falsy 检查,避免订阅中 `dns.enable: false` 等值被覆盖
32
+ - **覆写文件名显示**:`overwrite.yaml` 不再显示为 "yaml",改为 "主文件"
33
+
34
+ ### 优化
35
+
36
+ - **消除重复文件扫描**:覆写文件加载从每次构建 2 次减少为 1 次
37
+ - **清理死代码**:移除 `resetUserData`、`getGitHubMirror`、`setGitHubMirror`、未使用的类型字段
38
+
39
+ ---
40
+
3
41
  ## [2.0.0] - 2026-04-11
4
42
 
5
43
  ### 架构重写
package/README.md CHANGED
@@ -94,10 +94,11 @@ mihomo ui yacd # YACD
94
94
  | 命令 | 说明 |
95
95
  | ----------------------------- | -------------------------------------- |
96
96
  | `mihomo sub` | 列出所有订阅(含流量、到期时间) |
97
- | `mihomo sub add <url> [name]` | 添加订阅 |
97
+ | `mihomo sub use <name>` | 切换当前订阅(支持模糊匹配,自动重启) |
98
+ | `mihomo sub add <url> [name]` | 添加订阅并自动切换 |
98
99
  | `mihomo sub update` | 更新所有订阅 |
99
100
  | `mihomo sub update <name>` | 更新指定订阅(支持模糊匹配) |
100
- | `mihomo sub use <name>` | 切换当前订阅(支持模糊匹配,自动重启) |
101
+ | `mihomo sub remove <name>` | 删除订阅(支持模糊匹配) |
101
102
  | `mihomo sub web [name]` | 打开订阅页面(无参打开默认) |
102
103
 
103
104
  ### 覆写配置
package/dist/index.js CHANGED
@@ -2695,13 +2695,15 @@ var TUN_CONFIG = {
2695
2695
  tun: {
2696
2696
  enable: true,
2697
2697
  stack: "mixed",
2698
- "dns-hijack": ["0.0.0.0:53"],
2698
+ "dns-hijack": ["any:53", "tcp://any:53"],
2699
2699
  "auto-route": true,
2700
2700
  "auto-detect-interface": true,
2701
2701
  "strict-route": true
2702
2702
  }
2703
2703
  };
2704
2704
  var BASE_CONFIG = {
2705
+ "allow-lan": false,
2706
+ "external-controller": "127.0.0.1:9090",
2705
2707
  "log-level": "warning",
2706
2708
  "geodata-mode": true,
2707
2709
  "geo-update-interval": 24,
@@ -2819,11 +2821,33 @@ function addSubscription(url, name = "default") {
2819
2821
  }
2820
2822
  writeSettings(updates);
2821
2823
  }
2824
+ function removeSubscription(name) {
2825
+ const settings = readSettings();
2826
+ const subs = settings.subscriptions || [];
2827
+ const idx = subs.findIndex((s) => s.name === name);
2828
+ if (idx < 0) return null;
2829
+ subs.splice(idx, 1);
2830
+ const updates = { subscriptions: subs };
2831
+ let switchedTo = null;
2832
+ if (settings.active_subscription === name) {
2833
+ switchedTo = subs.length > 0 ? subs[0].name : null;
2834
+ updates.active_subscription = switchedTo ?? void 0;
2835
+ }
2836
+ writeSettings(updates);
2837
+ const cache = readSubscriptionCache();
2838
+ if (cache[name]) {
2839
+ delete cache[name];
2840
+ writeSubscriptionCache(cache);
2841
+ }
2842
+ fs2.rmSync(getSubscriptionRawConfigPath(name), { force: true });
2843
+ return switchedTo;
2844
+ }
2822
2845
  function setDefaultSubscription(name) {
2823
2846
  const settings = readSettings();
2824
2847
  const subs = settings.subscriptions || [];
2825
2848
  const idx = subs.findIndex((s) => s.name === name);
2826
2849
  if (idx < 0) return false;
2850
+ if (settings.active_subscription === name) return true;
2827
2851
  writeSettings({ active_subscription: name });
2828
2852
  return true;
2829
2853
  }
@@ -2949,9 +2973,9 @@ function loadOverwriteFile() {
2949
2973
  }
2950
2974
  return results;
2951
2975
  }
2952
- function applyOverwrite(baseConfig) {
2976
+ function applyOverwrite(baseConfig, preloadedFiles) {
2953
2977
  if (!isOverwriteEnabled()) return baseConfig;
2954
- const overwriteFiles = loadOverwriteFile();
2978
+ const overwriteFiles = preloadedFiles || loadOverwriteFile();
2955
2979
  if (overwriteFiles.length === 0) return baseConfig;
2956
2980
  let result = { ...baseConfig };
2957
2981
  for (const file of overwriteFiles) {
@@ -2995,21 +3019,26 @@ function buildConfig(subRawContent, mode) {
2995
3019
  throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
2996
3020
  }
2997
3021
  const overwriteEnabled = isOverwriteEnabled();
2998
- const withOverwrites = applyOverwrite(subscriptionConfig);
2999
3022
  const overwriteFiles = overwriteEnabled ? loadOverwriteFile() : [];
3023
+ const withOverwrites = applyOverwrite(subscriptionConfig, overwriteFiles);
3000
3024
  const systemConfig = {};
3001
3025
  for (const [key, value] of Object.entries(BASE_CONFIG)) {
3002
3026
  if (!(key in withOverwrites)) {
3003
3027
  systemConfig[key] = value;
3004
3028
  }
3005
3029
  }
3030
+ systemConfig["allow-lan"] = false;
3031
+ systemConfig["external-controller"] = BASE_CONFIG["external-controller"];
3032
+ delete withOverwrites["external-ui"];
3033
+ delete withOverwrites["external-ui-name"];
3034
+ delete withOverwrites["external-ui-url"];
3006
3035
  if (mode === "tun") {
3007
3036
  systemConfig.tun = TUN_CONFIG.tun;
3008
3037
  const subDns = withOverwrites.dns || {};
3009
3038
  const dns = {};
3010
- if (!subDns.enable) dns.enable = true;
3011
- if (!subDns["enhanced-mode"]) dns["enhanced-mode"] = "fake-ip";
3012
- if (!subDns["fake-ip-range"]) dns["fake-ip-range"] = "198.18.0.1/16";
3039
+ if (!("enable" in subDns)) dns.enable = true;
3040
+ if (!("enhanced-mode" in subDns)) dns["enhanced-mode"] = "fake-ip";
3041
+ if (!("fake-ip-range" in subDns)) dns["fake-ip-range"] = "198.18.0.1/16";
3013
3042
  if (Object.keys(dns).length > 0) {
3014
3043
  systemConfig.dns = dns;
3015
3044
  }
@@ -3662,7 +3691,6 @@ function listLogs() {
3662
3691
  const stat = fs5.statSync(filePath);
3663
3692
  result.archives.push({
3664
3693
  name: file,
3665
- timestamp: match[1],
3666
3694
  path: filePath,
3667
3695
  size: stat.size,
3668
3696
  mtime: stat.mtime,
@@ -3834,9 +3862,10 @@ ${colors.cyan("\u754C\u9762:")}
3834
3862
 
3835
3863
  ${colors.cyan("\u8BA2\u9605:")}
3836
3864
  ${colors.bold("subscription")} \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09
3865
+ ${colors.bold("subscription")} use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605
3837
3866
  ${colors.bold("subscription")} add <url> [name] \u6DFB\u52A0\u8BA2\u9605
3838
3867
  ${colors.bold("subscription")} update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09
3839
- ${colors.bold("subscription")} use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605
3868
+ ${colors.bold("subscription")} remove <name> \u5220\u9664\u8BA2\u9605
3840
3869
  ${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
3841
3870
 
3842
3871
  ${colors.cyan("\u914D\u7F6E:")}
@@ -3938,9 +3967,8 @@ var compareVersions = (v1, v2) => {
3938
3967
  // src/kernel.ts
3939
3968
  var GITHUB_REPO = "MetaCubeX/mihomo";
3940
3969
  var KERNEL_HTTP_TIMEOUT = 12e4;
3941
- var KERNEL_MAX_CONTENT_LENGTH = 200 * 1024 * 1024;
3942
3970
  var KERNEL_DOWNLOAD_TIMEOUT = 18e4;
3943
- var HTTP_CLIENT = createHttpClient({ timeout: KERNEL_HTTP_TIMEOUT, maxContentLength: KERNEL_MAX_CONTENT_LENGTH });
3971
+ var HTTP_CLIENT = createHttpClient({ timeout: KERNEL_HTTP_TIMEOUT });
3944
3972
  function withMirror(url, mirror) {
3945
3973
  if (mirror && (url.startsWith("https://github.com/") || url.startsWith("https://api.github.com/"))) {
3946
3974
  return mirror + url;
@@ -4000,7 +4028,6 @@ async function checkUpdate(mirror) {
4000
4028
  latest: latestVersion,
4001
4029
  needsUpdate,
4002
4030
  assets: latest.assets,
4003
- htmlUrl: latest.html_url,
4004
4031
  release: latest
4005
4032
  };
4006
4033
  }
@@ -4241,7 +4268,7 @@ import path5 from "path";
4241
4268
 
4242
4269
  // src/subscription.ts
4243
4270
  var DEFAULT_UPDATE_INTERVAL_HOURS = 12;
4244
- var HTTP_CLIENT2 = createHttpClient({ timeout: 6e4, maxContentLength: 50 * 1024 * 1024 });
4271
+ var HTTP_CLIENT2 = createHttpClient({ timeout: 6e4 });
4245
4272
  function parseUserInfo(header) {
4246
4273
  if (!header) return null;
4247
4274
  const info = {};
@@ -4458,7 +4485,7 @@ function printStatus() {
4458
4485
  console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
4459
4486
  }
4460
4487
  if (overwriteEnabled && overwriteFiles.length > 0) {
4461
- const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "")).join(", ");
4488
+ const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "").replace(/\.ya?ml$/, "") || "\u4E3B\u6587\u4EF6").join(", ");
4462
4489
  console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (${names})`);
4463
4490
  } else if (overwriteEnabled) {
4464
4491
  console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (\u65E0\u6587\u4EF6)`);
@@ -4766,9 +4793,11 @@ async function cmdStop() {
4766
4793
  }
4767
4794
 
4768
4795
  // src/commands/subscription.ts
4769
- async function printSubscriptionList() {
4770
- const updateResult = await autoUpdateStaleSubscription();
4771
- if (updateResult.total > 0) console.log("");
4796
+ async function printSubscriptionList(options) {
4797
+ if (options?.autoUpdate !== false) {
4798
+ const updateResult = await autoUpdateStaleSubscription();
4799
+ if (updateResult.total > 0) console.log("");
4800
+ }
4772
4801
  const subs = getSubscriptionsWithCache();
4773
4802
  if (subs.length === 0) {
4774
4803
  console.log("\u6CA1\u6709\u8BA2\u9605");
@@ -4808,9 +4837,10 @@ async function printSubscriptionList() {
4808
4837
  });
4809
4838
  console.log("");
4810
4839
  console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
4840
+ console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
4811
4841
  console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
4842
+ console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
4812
4843
  console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
4813
- console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
4814
4844
  console.log("");
4815
4845
  }
4816
4846
  async function cmdSubscription(args) {
@@ -4829,8 +4859,9 @@ async function cmdSubscription(args) {
4829
4859
  console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
4830
4860
  try {
4831
4861
  addSubscription(url, name);
4862
+ setDefaultSubscription(name);
4832
4863
  const info = await downloadSubscription(url, name);
4833
- console.log(`\u5DF2\u6DFB\u52A0 (${formatProxySummary(info)})`);
4864
+ console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
4834
4865
  } catch (e) {
4835
4866
  console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
4836
4867
  process.exit(1);
@@ -4956,8 +4987,30 @@ async function cmdSubscription(args) {
4956
4987
  }
4957
4988
  return;
4958
4989
  }
4990
+ if (action === "remove" || action === "rm" || action === "delete") {
4991
+ const name = args[2];
4992
+ const subs = getSubscriptions();
4993
+ if (!name) {
4994
+ console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0");
4995
+ if (subs.length > 0) {
4996
+ console.log("\n\u53EF\u7528\u8BA2\u9605:");
4997
+ for (const s of subs) console.log(` ${s.name}`);
4998
+ }
4999
+ process.exit(1);
5000
+ }
5001
+ const matches = findSubscriptionFuzzy(subs, name);
5002
+ const target = pickSingleSubscription(matches, name);
5003
+ const switchedTo = removeSubscription(target.name);
5004
+ console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
5005
+ if (switchedTo) {
5006
+ console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
5007
+ }
5008
+ console.log("");
5009
+ await printSubscriptionList({ autoUpdate: false });
5010
+ return;
5011
+ }
4959
5012
  console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
4960
- console.log("\u7528\u6CD5: mihomo sub [list|add|update|use|web]");
5013
+ console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web]");
4961
5014
  process.exit(1);
4962
5015
  }
4963
5016
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mihomo-cli",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "A terminal-based mihomo (Clash.Meta) client for macOS",
6
6
  "bin": {