mihomo-cli 2.5.0 → 2.6.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,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.6.1] - 2026-05-03
4
+
5
+ ### 修复
6
+
7
+ - **启动前配置校验** - 自动检测并修复 proxy-group 中引用不存在的节点/分组,避免内核启动失败
8
+ - 写入配置后使用 `mihomo -t` 做内核级校验,提前发现配置错误
9
+
10
+ ### 改进
11
+
12
+ - 移除已废弃的 `global-client-fingerprint` 配置项,消除内核启动时的 warning
13
+
14
+ ---
15
+
16
+ ## [2.6.0] - 2026-05-03
17
+
18
+ ### 改进
19
+
20
+ - **统一使用 mixed-port**:用 `mixed-port: 7890` 替代原来的 `port: 7890` + `socks-port: 7891`,单端口同时支持 HTTP 和 SOCKS5
21
+ - **BASE_CONFIG 优化**:新增 `unified-delay`、`tcp-concurrent`、`geo-auto-update`、`profile.store-selected`,不再依赖订阅自带这些配置
22
+ - **自动启用 sniffer**:检测到 `fake-ip` 模式时自动注入 sniffer 配置(嗅探 HTTP/TLS/QUIC),确保域名规则正常工作;订阅自带 sniffer 时不覆盖
23
+
24
+ ---
25
+
3
26
  ## [2.5.0] - 2026-05-03
4
27
 
5
28
  ### 新功能
package/README.md CHANGED
@@ -301,8 +301,7 @@ sudo pkill -9 mihomo
301
301
 
302
302
  默认端口(系统强制,不受订阅配置影响):
303
303
 
304
- - HTTP 端口: `7890`
305
- - SOCKS5 端口: `7891`
304
+ - 混合端口 (HTTP + SOCKS5): `7890`
306
305
  - 外部控制器: `127.0.0.1:9090`
307
306
 
308
307
  ## 安全特性
package/dist/index.js CHANGED
@@ -2688,29 +2688,32 @@ function getFreeSubscriptionSources() {
2688
2688
  ];
2689
2689
  }
2690
2690
  var BENCH_CONFIG = {
2691
+ "mixed-port": 17890,
2691
2692
  "allow-lan": false,
2692
2693
  "external-controller": "127.0.0.1:19090",
2693
- port: 17890,
2694
- "socks-port": 17891,
2695
2694
  "log-level": "error",
2696
2695
  "geodata-mode": true
2697
2696
  };
2698
2697
  var TEST_CONFIG = {
2698
+ "mixed-port": 27890,
2699
2699
  "allow-lan": false,
2700
2700
  "external-controller": "127.0.0.1:29090",
2701
- port: 27890,
2702
- "socks-port": 27891,
2703
2701
  "log-level": "error",
2704
2702
  "geodata-mode": true
2705
2703
  };
2706
2704
  var BASE_CONFIG = {
2705
+ "mixed-port": 7890,
2707
2706
  "allow-lan": false,
2708
2707
  "external-controller": "127.0.0.1:9090",
2709
- port: 7890,
2710
- "socks-port": 7891,
2711
- "log-level": "warning",
2712
- "geodata-mode": true,
2708
+ "unified-delay": true,
2709
+ "tcp-concurrent": true,
2710
+ "geo-auto-update": true,
2713
2711
  "geo-update-interval": 24,
2712
+ "geodata-mode": true,
2713
+ "log-level": "warning",
2714
+ profile: {
2715
+ "store-selected": true
2716
+ },
2714
2717
  "geox-url": {
2715
2718
  geoip: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geoip-lite.dat",
2716
2719
  geosite: "https://testingcf.jsdelivr.net/gh/MetaCubeX/meta-rules-dat@release/geosite-lite.dat",
@@ -3109,6 +3112,40 @@ function excludeOverwriteProxiesFromIncludeAll(config, overwriteFiles) {
3109
3112
  }
3110
3113
  }
3111
3114
  }
3115
+ var BUILTIN_PROXY_NAMES = /* @__PURE__ */ new Set(["DIRECT", "REJECT", "REJECT-DROP", "PASS", "COMPATIBLE"]);
3116
+ function validateProxyGroupDependencies(config) {
3117
+ const warnings = [];
3118
+ const proxies = config.proxies || [];
3119
+ const groups = config["proxy-groups"] || [];
3120
+ if (groups.length === 0) return warnings;
3121
+ const validNames = new Set(BUILTIN_PROXY_NAMES);
3122
+ for (const p of proxies) validNames.add(p.name);
3123
+ for (const g of groups) validNames.add(g.name);
3124
+ const removedGroups = /* @__PURE__ */ new Set();
3125
+ let changed = true;
3126
+ while (changed) {
3127
+ changed = false;
3128
+ for (const group of groups) {
3129
+ if (removedGroups.has(group.name)) continue;
3130
+ if (!Array.isArray(group.proxies)) continue;
3131
+ const invalid = group.proxies.filter((name) => !validNames.has(name));
3132
+ if (invalid.length === 0) continue;
3133
+ group.proxies = group.proxies.filter((name) => validNames.has(name));
3134
+ warnings.push(`proxy-group "${group.name}": \u79FB\u9664\u4E86\u4E0D\u5B58\u5728\u7684\u5F15\u7528 ${invalid.map((n) => `"${n}"`).join(", ")}`);
3135
+ const hasOtherSource = group.use || group["include-all"] || group["include-all-proxies"];
3136
+ if (group.proxies.length === 0 && !hasOtherSource) {
3137
+ removedGroups.add(group.name);
3138
+ validNames.delete(group.name);
3139
+ warnings.push(`proxy-group "${group.name}": \u5DF2\u79FB\u9664\uFF08\u65E0\u53EF\u7528\u8282\u70B9\uFF09`);
3140
+ changed = true;
3141
+ }
3142
+ }
3143
+ }
3144
+ if (removedGroups.size > 0) {
3145
+ config["proxy-groups"] = groups.filter((g) => !removedGroups.has(g.name));
3146
+ }
3147
+ return warnings;
3148
+ }
3112
3149
  function buildConfig(subRawContent, mode) {
3113
3150
  const subscriptionConfig = parseYamlOrJson(subRawContent, "\u8BA2\u9605\u5185\u5BB9");
3114
3151
  if (!subscriptionConfig) {
@@ -3128,9 +3165,10 @@ function buildConfig(subRawContent, mode) {
3128
3165
  }
3129
3166
  systemConfig["allow-lan"] = false;
3130
3167
  systemConfig["external-controller"] = BASE_CONFIG["external-controller"];
3131
- systemConfig.port = BASE_CONFIG.port;
3132
- systemConfig["socks-port"] = BASE_CONFIG["socks-port"];
3168
+ systemConfig["mixed-port"] = BASE_CONFIG["mixed-port"];
3133
3169
  delete withOverwrites["mixed-port"];
3170
+ delete withOverwrites.port;
3171
+ delete withOverwrites["socks-port"];
3134
3172
  delete withOverwrites["external-ui"];
3135
3173
  delete withOverwrites["external-ui-name"];
3136
3174
  delete withOverwrites["external-ui-url"];
@@ -3149,7 +3187,34 @@ function buildConfig(subRawContent, mode) {
3149
3187
  if (systemConfig.dns) {
3150
3188
  merged.dns = { ...withOverwrites.dns || {}, ...systemConfig.dns };
3151
3189
  }
3152
- return { config: merged, subscriptionConfig, overwriteFiles, systemConfig };
3190
+ const mergedDns = merged.dns || {};
3191
+ if (mergedDns["enhanced-mode"] === "fake-ip" && !("sniffer" in withOverwrites)) {
3192
+ merged.sniffer = {
3193
+ enable: true,
3194
+ sniff: {
3195
+ HTTP: { ports: [80, "8080-8880"], "override-destination": true },
3196
+ TLS: { ports: [443, 8443] },
3197
+ QUIC: { ports: [443, 8443] }
3198
+ },
3199
+ "skip-domain": ["+.push.apple.com"]
3200
+ };
3201
+ }
3202
+ const warnings = validateProxyGroupDependencies(merged);
3203
+ return { config: merged, subscriptionConfig, overwriteFiles, systemConfig, warnings };
3204
+ }
3205
+ function checkConfig() {
3206
+ if (!hasKernel() || !hasConfig()) return { ok: true, errors: [] };
3207
+ try {
3208
+ execSync(`"${PATHS.mihomoBinary}" -t -f "${PATHS.configFile}" 2>&1`, { encoding: "utf8" });
3209
+ return { ok: true, errors: [] };
3210
+ } catch (e) {
3211
+ const output = e.stdout || e.message || "";
3212
+ const errors = output.split("\n").filter((line) => line.includes("level=error") || line.includes("level=fatal")).map((line) => {
3213
+ const match = line.match(/msg="(.+)"/);
3214
+ return match ? match[1] : line;
3215
+ });
3216
+ return { ok: false, errors };
3217
+ }
3153
3218
  }
3154
3219
  function writeMihomoConfig(configObj) {
3155
3220
  ensureDirs();
@@ -4427,8 +4492,20 @@ function prepareConfigForStart(mode, subName = "default") {
4427
4492
  throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605`);
4428
4493
  }
4429
4494
  const buildResult = buildConfig(rawContent, mode);
4495
+ if (buildResult.warnings.length > 0) {
4496
+ for (const warning of buildResult.warnings) {
4497
+ console.log(`${colors.yellow("\u81EA\u52A8\u4FEE\u590D:")} ${warning}`);
4498
+ }
4499
+ console.log("");
4500
+ }
4430
4501
  writeMihomoConfig(buildResult.config);
4431
4502
  writeDebugConfig(buildResult);
4503
+ const configCheck = checkConfig();
4504
+ if (!configCheck.ok) {
4505
+ const errorDetail = configCheck.errors.length > 0 ? configCheck.errors.join("\n ") : "\u672A\u77E5\u9519\u8BEF";
4506
+ throw new Error(`\u914D\u7F6E\u6821\u9A8C\u5931\u8D25:
4507
+ ${errorDetail}`);
4508
+ }
4432
4509
  const proxies = buildResult.config.proxies;
4433
4510
  const proxyGroups = buildResult.config["proxy-groups"];
4434
4511
  return {
@@ -5379,7 +5456,7 @@ async function cmdBench(args) {
5379
5456
  for (const d of downloaded) {
5380
5457
  d.proxies = d.proxies.filter((p) => survivingSet.has(p));
5381
5458
  }
5382
- const benchPort = BENCH_CONFIG.port;
5459
+ const benchPort = BENCH_CONFIG["mixed-port"];
5383
5460
  const benchApi = BENCH_CONFIG["external-controller"];
5384
5461
  console.log(colors.cyan("\u542F\u52A8\u6D4B\u8BD5\u5B9E\u4F8B..."));
5385
5462
  await startBenchInstance();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mihomo-cli",
3
- "version": "2.5.0",
3
+ "version": "2.6.1",
4
4
  "type": "module",
5
5
  "description": "A terminal-based mihomo (Clash.Meta) client for macOS",
6
6
  "bin": {