mihomo-cli 2.1.0 → 2.2.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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.2.0] - 2026-05-01
4
+
5
+ ### 新增
6
+
7
+ - **节点测速**:`sub test [name]` 测试订阅节点连通性,支持 `-t` 超时和 `-j` 并发参数
8
+ - **节点清理**:`sub clean [name]` 测速后自动清理不可用节点,移除空分组
9
+ - **启动自动清理**:`start` / `start tun` 启动时,节点数超过 100 自动执行清理
10
+
11
+ ### 安全
12
+
13
+ - **强制端口配置**:HTTP 端口固定 7890,SOCKS5 端口固定 7891,忽略订阅中的 `mixed-port` 配置
14
+
15
+ ---
16
+
3
17
  ## [2.1.0] - 2026-05-01
4
18
 
5
19
  ### 新增
package/README.md CHANGED
@@ -7,6 +7,7 @@
7
7
  - 🌐 **订阅管理** - 添加/更新订阅,支持流量统计和到期时间显示
8
8
  - 🔄 **自动更新** - 启动时自动检查并更新过期订阅
9
9
  - 🔍 **模糊匹配** - `sub use` / `sub web` 支持订阅名称模糊匹配
10
+ - 🧹 **节点测速清理** - `sub test` 测试连通性,`sub clean` 自动清理失败节点,启动时超过 100 节点自动清理
10
11
  - 📝 **覆写配置** - 在订阅基础上进行自定义覆写,支持强制覆盖、数组合并
11
12
  - 🔄 **智能重启** - `sub use` 切换订阅、`ow on/off` 切换覆写后自动重启
12
13
  - 🚀 **进程管理** - 启动/停止/切换模式,自动清理残留进程
@@ -100,6 +101,8 @@ mihomo ui yacd # YACD
100
101
  | `mihomo sub update <name>` | 更新指定订阅(支持模糊匹配) |
101
102
  | `mihomo sub remove <name>` | 删除订阅(支持模糊匹配) |
102
103
  | `mihomo sub web [name]` | 打开订阅页面(无参打开默认) |
104
+ | `mihomo sub test [name]` | 测试节点连通性(`-t` 超时,`-j` 并发) |
105
+ | `mihomo sub clean [name]` | 测速并清理失败节点 |
103
106
 
104
107
  ### 覆写配置
105
108
 
@@ -290,9 +293,10 @@ sudo pkill -9 mihomo
290
293
 
291
294
  ### 端口被占用
292
295
 
293
- 默认端口(取决于订阅配置):
296
+ 默认端口(系统强制,不受订阅配置影响):
294
297
 
295
- - Mixed 端口: `7890`
298
+ - HTTP 端口: `7890`
299
+ - SOCKS5 端口: `7891`
296
300
  - 外部控制器: `127.0.0.1:9090`
297
301
 
298
302
  ## 安全特性
package/dist/index.js CHANGED
@@ -2704,6 +2704,8 @@ var TUN_CONFIG = {
2704
2704
  var BASE_CONFIG = {
2705
2705
  "allow-lan": false,
2706
2706
  "external-controller": "127.0.0.1:9090",
2707
+ port: 7890,
2708
+ "socks-port": 7891,
2707
2709
  "log-level": "warning",
2708
2710
  "geodata-mode": true,
2709
2711
  "geo-update-interval": 24,
@@ -3029,6 +3031,9 @@ function buildConfig(subRawContent, mode) {
3029
3031
  }
3030
3032
  systemConfig["allow-lan"] = false;
3031
3033
  systemConfig["external-controller"] = BASE_CONFIG["external-controller"];
3034
+ systemConfig.port = BASE_CONFIG.port;
3035
+ systemConfig["socks-port"] = BASE_CONFIG["socks-port"];
3036
+ delete withOverwrites["mixed-port"];
3032
3037
  delete withOverwrites["external-ui"];
3033
3038
  delete withOverwrites["external-ui-name"];
3034
3039
  delete withOverwrites["external-ui-url"];
@@ -3145,6 +3150,9 @@ var colors = {
3145
3150
  function sleepSync(ms) {
3146
3151
  Atomics.wait(sleepBuf, 0, 0, ms);
3147
3152
  }
3153
+ function sleep(ms) {
3154
+ return new Promise((resolve) => setTimeout(resolve, ms));
3155
+ }
3148
3156
  function formatBytes(bytes) {
3149
3157
  if (bytes === void 0 || bytes === null) return "\u672A\u77E5";
3150
3158
  const num = Number(bytes);
@@ -3867,6 +3875,8 @@ ${colors.cyan("\u8BA2\u9605:")}
3867
3875
  ${colors.bold("subscription")} update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09
3868
3876
  ${colors.bold("subscription")} remove <name> \u5220\u9664\u8BA2\u9605
3869
3877
  ${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
3878
+ ${colors.bold("subscription")} test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027
3879
+ ${colors.bold("subscription")} clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9
3870
3880
 
3871
3881
  ${colors.cyan("\u914D\u7F6E:")}
3872
3882
  ${colors.bold("overwrite")} \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09
@@ -4268,7 +4278,25 @@ import path5 from "path";
4268
4278
 
4269
4279
  // src/subscription.ts
4270
4280
  var DEFAULT_UPDATE_INTERVAL_HOURS = 12;
4281
+ var YAML_DUMP_OPTS = { indent: 2, lineWidth: -1, noCompatMode: true };
4271
4282
  var HTTP_CLIENT2 = createHttpClient({ timeout: 6e4 });
4283
+ function loadSubscriptionConfig(subName) {
4284
+ const rawContent = readSubscriptionRawConfig(subName);
4285
+ if (!rawContent) {
4286
+ throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
4287
+ }
4288
+ const raw = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
4289
+ return {
4290
+ raw,
4291
+ proxies: raw.proxies || [],
4292
+ proxyGroups: raw["proxy-groups"] || []
4293
+ };
4294
+ }
4295
+ function saveSubscriptionConfig(subName, parsed) {
4296
+ parsed.raw.proxies = parsed.proxies;
4297
+ parsed.raw["proxy-groups"] = parsed.proxyGroups;
4298
+ saveSubscriptionRawConfig(subName, jsYaml.dump(parsed.raw, YAML_DUMP_OPTS));
4299
+ }
4272
4300
  function parseUserInfo(header) {
4273
4301
  if (!header) return null;
4274
4302
  const info = {};
@@ -4443,6 +4471,122 @@ async function autoUpdateStaleSubscription() {
4443
4471
  }
4444
4472
  return { total: staleSubs.length, updated: updatedCount, failed: staleSubs.length - updatedCount };
4445
4473
  }
4474
+ var API_BASE = `http://${BASE_CONFIG["external-controller"]}`;
4475
+ var DEFAULT_TEST_URL = "http://www.gstatic.com/generate_204";
4476
+ async function testProxyDelay(proxyName, timeout, testUrl, client) {
4477
+ const encodedName = encodeURIComponent(proxyName);
4478
+ const url = `${API_BASE}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(testUrl)}`;
4479
+ try {
4480
+ const response = await client.get(url);
4481
+ const data = JSON.parse(response.data);
4482
+ if (data.delay && data.delay > 0) {
4483
+ return { name: proxyName, delay: data.delay };
4484
+ }
4485
+ return { name: proxyName, delay: null, error: data.message || "no delay" };
4486
+ } catch (e) {
4487
+ const err = e;
4488
+ let errorMsg = "timeout";
4489
+ if (err.response?.data?.message) {
4490
+ errorMsg = String(err.response.data.message);
4491
+ } else if (err.message) {
4492
+ errorMsg = err.message;
4493
+ }
4494
+ return { name: proxyName, delay: null, error: errorMsg };
4495
+ }
4496
+ }
4497
+ async function testSubscriptionProxies(subName, options = {}) {
4498
+ const { timeout = 2e3, concurrency = 100, testUrl = DEFAULT_TEST_URL, onResult } = options;
4499
+ const { proxies } = options.parsed || loadSubscriptionConfig(subName);
4500
+ if (proxies.length === 0) {
4501
+ return { total: 0, alive: 0, dead: 0, results: [] };
4502
+ }
4503
+ const client = createHttpClient({ timeout: timeout + 3e3 });
4504
+ const results = [];
4505
+ let completedCount = 0;
4506
+ for (let i = 0; i < proxies.length; i += concurrency) {
4507
+ const batch = proxies.slice(i, i + concurrency);
4508
+ const batchResults = await Promise.all(batch.map((proxy) => testProxyDelay(proxy.name, timeout, testUrl, client)));
4509
+ for (const result of batchResults) {
4510
+ results.push(result);
4511
+ onResult?.(result, completedCount, proxies.length);
4512
+ completedCount++;
4513
+ }
4514
+ }
4515
+ const alive = results.filter((r) => r.delay !== null).length;
4516
+ return { total: results.length, alive, dead: results.length - alive, results };
4517
+ }
4518
+ function shortenProxyNames(parsed) {
4519
+ const { proxies, proxyGroups } = parsed;
4520
+ const renameMap = /* @__PURE__ */ new Map();
4521
+ const usedNames = /* @__PURE__ */ new Set();
4522
+ for (const proxy of proxies) {
4523
+ const shortened = proxy.name.replace(/_github\.com\/[^_]+/, "");
4524
+ if (shortened !== proxy.name && !usedNames.has(shortened)) {
4525
+ renameMap.set(proxy.name, shortened);
4526
+ usedNames.add(shortened);
4527
+ } else {
4528
+ usedNames.add(proxy.name);
4529
+ }
4530
+ }
4531
+ if (renameMap.size === 0) return 0;
4532
+ for (const proxy of proxies) {
4533
+ const newName = renameMap.get(proxy.name);
4534
+ if (newName) proxy.name = newName;
4535
+ }
4536
+ for (const group of proxyGroups) {
4537
+ if (Array.isArray(group.proxies)) {
4538
+ group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
4539
+ }
4540
+ }
4541
+ return renameMap.size;
4542
+ }
4543
+ function cleanDeadProxies(parsed, deadNames) {
4544
+ const { proxies, proxyGroups } = parsed;
4545
+ const originalCount = proxies.length;
4546
+ parsed.proxies = proxies.filter((p) => !deadNames.has(p.name));
4547
+ const removedProxies = originalCount - parsed.proxies.length;
4548
+ let updatedGroups = 0;
4549
+ const removedGroupNames = /* @__PURE__ */ new Set();
4550
+ for (const group of proxyGroups) {
4551
+ if (Array.isArray(group.proxies)) {
4552
+ const before = group.proxies.length;
4553
+ group.proxies = group.proxies.filter((name) => !deadNames.has(name));
4554
+ if (group.proxies.length < before) {
4555
+ updatedGroups++;
4556
+ }
4557
+ if (group.proxies.length === 0) {
4558
+ removedGroupNames.add(group.name);
4559
+ }
4560
+ }
4561
+ }
4562
+ if (removedGroupNames.size > 0) {
4563
+ parsed.proxyGroups = proxyGroups.filter((g) => !removedGroupNames.has(g.name));
4564
+ for (const group of parsed.proxyGroups) {
4565
+ if (Array.isArray(group.proxies)) {
4566
+ group.proxies = group.proxies.filter((name) => !removedGroupNames.has(name));
4567
+ }
4568
+ }
4569
+ }
4570
+ return { removedProxies, updatedGroups, removedGroups: removedGroupNames.size };
4571
+ }
4572
+ async function autoCleanSubscription(subName, options = {}) {
4573
+ const parsed = loadSubscriptionConfig(subName);
4574
+ shortenProxyNames(parsed);
4575
+ saveSubscriptionConfig(subName, parsed);
4576
+ const summary = await testSubscriptionProxies(subName, { ...options, parsed });
4577
+ let removedProxies = 0;
4578
+ let updatedGroups = 0;
4579
+ let removedGroups = 0;
4580
+ if (summary.dead > 0) {
4581
+ const deadNames = new Set(summary.results.filter((r) => r.delay === null).map((r) => r.name));
4582
+ const cleanResult = cleanDeadProxies(parsed, deadNames);
4583
+ removedProxies = cleanResult.removedProxies;
4584
+ updatedGroups = cleanResult.updatedGroups;
4585
+ removedGroups = cleanResult.removedGroups;
4586
+ saveSubscriptionConfig(subName, parsed);
4587
+ }
4588
+ return { summary, removedProxies, updatedGroups, removedGroups };
4589
+ }
4446
4590
 
4447
4591
  // src/commands/status.ts
4448
4592
  function printStatus() {
@@ -4495,124 +4639,456 @@ function printStatus() {
4495
4639
  console.log("");
4496
4640
  }
4497
4641
 
4498
- // src/commands/start.ts
4499
- function handleStopResult(result) {
4500
- if (result.remaining && result.remaining.length > 0) {
4501
- console.error(`${colors.red("\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62:")} ${result.remaining.join(", ")}`);
4502
- console.error("\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo");
4503
- process.exit(1);
4642
+ // src/commands/subscription.ts
4643
+ function printTestResult(result, index, total) {
4644
+ const prefix = `[${index + 1}/${total}]`;
4645
+ if (result.delay !== null) {
4646
+ const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
4647
+ console.log(` ${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}`);
4648
+ } else {
4649
+ console.log(` ${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
4504
4650
  }
4505
4651
  }
4506
- async function cmdStart(args) {
4507
- if (!hasKernel()) {
4508
- console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
4652
+ function formatCleanSummary(result) {
4653
+ const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
4654
+ if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
4655
+ if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
4656
+ return parts.join(", ");
4657
+ }
4658
+ function formatTestSummary(summary) {
4659
+ return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
4660
+ }
4661
+ function resolveActiveTestTarget(args) {
4662
+ const subs = getSubscriptions();
4663
+ if (subs.length === 0) {
4664
+ console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
4509
4665
  process.exit(1);
4510
4666
  }
4511
- const targetMode = args[1] === "tun" ? "tun" : "mixed";
4512
- const sub = getActiveSubscription();
4513
- if (!sub) {
4514
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
4515
- process.exit(1);
4667
+ const nameArg = getNonFlagArg(args, 2);
4668
+ const timeout = parseIntArg(args, "-t", "--timeout", 2e3);
4669
+ const concurrency = parseIntArg(args, "-j", "--concurrency", 100);
4670
+ const activeSub = getActiveSubscription();
4671
+ let target;
4672
+ if (nameArg) {
4673
+ const matches = findSubscriptionFuzzy(subs, nameArg);
4674
+ target = pickSingleSubscription(matches, nameArg);
4675
+ } else {
4676
+ if (!activeSub) {
4677
+ console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
4678
+ process.exit(1);
4679
+ }
4680
+ target = activeSub;
4516
4681
  }
4517
- await autoUpdateStaleSubscription();
4518
4682
  const status = getStatus();
4519
- const hasProcess = status.running || status.allProcesses.length > 0;
4520
- if (hasProcess) {
4521
- const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
4522
- console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
4523
- }
4524
- handleStopResult(stop(true));
4525
- if (hasProcess) {
4526
- console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
4527
- `);
4528
- }
4529
- let configInfo;
4530
- try {
4531
- configInfo = prepareConfigForStart(targetMode, sub.name);
4532
- } catch (e) {
4533
- console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
4683
+ if (!status.running) {
4684
+ console.error("\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (mihomo start)");
4534
4685
  process.exit(1);
4535
4686
  }
4536
- const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
4537
- console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
4538
- try {
4539
- const result = await start(targetMode);
4540
- console.log(`${colors.green("\u5DF2\u542F\u52A8")} (PID ${result.pid})`);
4541
- printStatus();
4542
- } catch (e) {
4543
- console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
4687
+ if (!activeSub || activeSub.name !== target.name) {
4688
+ console.error(`\u9519\u8BEF: \u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605\u662F "${activeSub?.name}"\uFF0C\u4E0D\u662F "${target.name}"`);
4689
+ console.log(`\u8BF7\u5148\u5207\u6362: mihomo sub use ${target.name}`);
4544
4690
  process.exit(1);
4545
4691
  }
4692
+ return { target, timeout, concurrency };
4546
4693
  }
4547
-
4548
- // src/commands/overwrite.ts
4549
- function printOverwriteList() {
4550
- const info = listOverwriteFile();
4551
- const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
4552
- console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}`);
4553
- console.log(`${colors.gray("\u4F4D\u7F6E: ")}${info.dir}`);
4554
- console.log("");
4555
- if (info.files.length === 0) {
4556
- console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
4557
- console.log("");
4558
- console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path5.join(info.dir, "overwrite.yaml")}`);
4559
- console.log(` \u6216 ${path5.join(info.dir, "overwrite.dns.yaml")}`);
4560
- console.log("");
4561
- } else {
4562
- console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
4694
+ async function printSubscriptionList(options) {
4695
+ if (options?.autoUpdate !== false) {
4696
+ const updateResult = await autoUpdateStaleSubscription();
4697
+ if (updateResult.total > 0) console.log("");
4698
+ }
4699
+ const subs = getSubscriptionsWithCache();
4700
+ if (subs.length === 0) {
4701
+ console.log("\u6CA1\u6709\u8BA2\u9605");
4563
4702
  console.log("");
4564
- info.files.forEach((f, i) => {
4565
- const num = i < 10 ? ` ${i}` : `${i}`;
4566
- console.log(` ${num}. ${f.name}`);
4567
- if (f.keys.length > 0) {
4568
- console.log(` ${colors.gray("\u5B57\u6BB5: ")}${f.keys.join(", ")}`);
4569
- }
4570
- });
4703
+ console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
4571
4704
  console.log("");
4705
+ return;
4572
4706
  }
4573
- console.log("\u542F\u7528\u8986\u5199: mihomo ow on");
4574
- console.log("\u7981\u7528\u8986\u5199: mihomo ow off");
4707
+ const activeSub = getActiveSubscription();
4708
+ console.log(colors.cyan("\u8BA2\u9605\u5217\u8868:"));
4709
+ subs.forEach((s, i) => {
4710
+ const time = formatDate(s.updated_at);
4711
+ const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
4712
+ const interval = s.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
4713
+ console.log(` ${i + 1}. ${s.name}${defaultMark}`);
4714
+ console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
4715
+ if (s.username) {
4716
+ console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
4717
+ }
4718
+ if (s.download !== void 0 || s.total !== void 0) {
4719
+ const used = (s.upload || 0) + (s.download || 0);
4720
+ const usedStr = formatBytes(used);
4721
+ const totalStr = formatBytes(s.total);
4722
+ let percentStr = "";
4723
+ if (s.total && s.total > 0) {
4724
+ const percent = Math.min(used / s.total * 100, 100);
4725
+ percentStr = ` (${percent.toFixed(1)}%)`;
4726
+ }
4727
+ console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
4728
+ }
4729
+ if (s.expire !== void 0) {
4730
+ console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
4731
+ }
4732
+ if (s.web_page_url) {
4733
+ console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
4734
+ }
4735
+ });
4736
+ console.log("");
4737
+ console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
4738
+ console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
4739
+ console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
4740
+ console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
4741
+ console.log("\u6D4B\u8BD5\u8282\u70B9: mihomo sub test [name]");
4742
+ console.log("\u6E05\u7406\u8282\u70B9: mihomo sub clean [name]");
4743
+ console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
4575
4744
  console.log("");
4576
4745
  }
4577
- async function cmdOverwrite(args) {
4578
- const action = args?.[1];
4579
- const status = getStatus();
4580
- const configInfo = getConfigInfo();
4581
- const currentMode = configInfo?.tun ? "tun" : "mixed";
4582
- if (action === "on" || action === "enable") {
4583
- if (isOverwriteEnabled()) {
4584
- console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
4585
- console.log("");
4586
- printOverwriteList();
4587
- return;
4746
+ async function cmdSubscription(args) {
4747
+ const action = args[1];
4748
+ if (!action || action === "list") {
4749
+ await printSubscriptionList();
4750
+ return;
4751
+ }
4752
+ if (action === "add") {
4753
+ const url = args[2];
4754
+ const name = args[3] || "default";
4755
+ if (!url?.startsWith("http")) {
4756
+ console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
4757
+ process.exit(1);
4588
4758
  }
4589
- setOverwriteEnabled(true);
4590
- console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
4591
- if (status.running) {
4759
+ console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
4760
+ try {
4761
+ addSubscription(url, name);
4762
+ setDefaultSubscription(name);
4763
+ const info = await downloadSubscription(url, name);
4764
+ console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
4765
+ } catch (e) {
4766
+ console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
4767
+ process.exit(1);
4768
+ }
4769
+ console.log("");
4770
+ await printSubscriptionList();
4771
+ return;
4772
+ }
4773
+ if (action === "update") {
4774
+ const name = args[2];
4775
+ const subs = getSubscriptions();
4776
+ if (subs.length === 0) {
4777
+ console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
4778
+ process.exit(1);
4779
+ }
4780
+ if (!name) {
4781
+ console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
4782
+ const results = await Promise.all(subs.map(tryUpdateOne));
4783
+ let ok = 0;
4784
+ for (const r of results) {
4785
+ if (r.success) {
4786
+ ok++;
4787
+ console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
4788
+ } else {
4789
+ console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
4790
+ }
4791
+ }
4792
+ if (ok === 0) process.exit(1);
4592
4793
  console.log("");
4593
- await cmdStart(["start", currentMode]);
4794
+ await printSubscriptionList();
4594
4795
  return;
4595
4796
  }
4797
+ const matches = findSubscriptionFuzzy(subs, name);
4798
+ const target = pickSingleSubscription(matches, name);
4799
+ console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
4800
+ try {
4801
+ const info = await downloadSubscription(target.url, target.name);
4802
+ console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(info)})`);
4803
+ } catch (e) {
4804
+ console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
4805
+ process.exit(1);
4806
+ }
4596
4807
  console.log("");
4597
- printOverwriteList();
4808
+ await printSubscriptionList();
4598
4809
  return;
4599
4810
  }
4600
- if (action === "off" || action === "disable") {
4601
- if (!isOverwriteEnabled()) {
4602
- console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u7981\u7528\u72B6\u6001");
4811
+ if (action === "use") {
4812
+ const name = args[2];
4813
+ const subs = getSubscriptions();
4814
+ if (!name) {
4815
+ console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
4816
+ if (subs.length > 0) {
4817
+ console.log("\n\u53EF\u7528\u8BA2\u9605:");
4818
+ for (const s of subs) console.log(` ${s.name}`);
4819
+ }
4820
+ process.exit(1);
4821
+ }
4822
+ const matches = findSubscriptionFuzzy(subs, name);
4823
+ const target = pickSingleSubscription(matches, name);
4824
+ const currentDefault = getActiveSubscription();
4825
+ const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
4826
+ if (isAlreadyDefault) {
4827
+ console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
4603
4828
  console.log("");
4604
- printOverwriteList();
4829
+ await printSubscriptionList();
4605
4830
  return;
4606
4831
  }
4607
- setOverwriteEnabled(false);
4608
- console.log("\u5DF2\u7981\u7528\u8986\u5199\u914D\u7F6E");
4832
+ const status = getStatus();
4833
+ const configInfo = getConfigInfo();
4834
+ const currentMode = configInfo?.tun ? "tun" : "mixed";
4835
+ const success = setDefaultSubscription(target.name);
4836
+ if (success) {
4837
+ console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
4838
+ } else {
4839
+ console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
4840
+ process.exit(1);
4841
+ }
4609
4842
  if (status.running) {
4610
4843
  console.log("");
4611
4844
  await cmdStart(["start", currentMode]);
4612
4845
  return;
4613
4846
  }
4614
4847
  console.log("");
4615
- printOverwriteList();
4848
+ await printSubscriptionList();
4849
+ return;
4850
+ }
4851
+ if (action === "web" || action === "open") {
4852
+ const name = args[2];
4853
+ const subs = getSubscriptionsWithCache();
4854
+ if (subs.length === 0) {
4855
+ console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
4856
+ process.exit(1);
4857
+ }
4858
+ let target;
4859
+ if (name) {
4860
+ const matches = findSubscriptionFuzzy(subs, name);
4861
+ target = pickSingleSubscription(matches, name);
4862
+ } else {
4863
+ target = subs[0];
4864
+ }
4865
+ const cached = getSubscriptionsWithCache().find((s) => s.name === target.name);
4866
+ let webPageUrl = cached?.web_page_url;
4867
+ if (!webPageUrl) {
4868
+ console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
4869
+ try {
4870
+ await downloadSubscription(target.url, target.name);
4871
+ const cache = readSubscriptionCache();
4872
+ if (cache[target.name]?.web_page_url) {
4873
+ webPageUrl = cache[target.name].web_page_url;
4874
+ } else {
4875
+ console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
4876
+ process.exit(1);
4877
+ }
4878
+ } catch (e) {
4879
+ console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
4880
+ process.exit(1);
4881
+ }
4882
+ }
4883
+ console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
4884
+ const opened = openUrl(webPageUrl);
4885
+ if (!opened) {
4886
+ console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
4887
+ }
4888
+ return;
4889
+ }
4890
+ if (action === "remove" || action === "rm" || action === "delete") {
4891
+ const name = args[2];
4892
+ const subs = getSubscriptions();
4893
+ if (!name) {
4894
+ console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0");
4895
+ if (subs.length > 0) {
4896
+ console.log("\n\u53EF\u7528\u8BA2\u9605:");
4897
+ for (const s of subs) console.log(` ${s.name}`);
4898
+ }
4899
+ process.exit(1);
4900
+ }
4901
+ const matches = findSubscriptionFuzzy(subs, name);
4902
+ const target = pickSingleSubscription(matches, name);
4903
+ const switchedTo = removeSubscription(target.name);
4904
+ console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
4905
+ if (switchedTo) {
4906
+ console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
4907
+ }
4908
+ console.log("");
4909
+ await printSubscriptionList({ autoUpdate: false });
4910
+ return;
4911
+ }
4912
+ if (action === "clean") {
4913
+ const { target, timeout, concurrency } = resolveActiveTestTarget(args);
4914
+ console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
4915
+ console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
4916
+ console.log("");
4917
+ const result = await autoCleanSubscription(target.name, {
4918
+ timeout,
4919
+ concurrency,
4920
+ onResult: printTestResult
4921
+ });
4922
+ console.log("");
4923
+ console.log(formatTestSummary(result.summary));
4924
+ if (result.removedProxies > 0) {
4925
+ console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
4926
+ }
4927
+ console.log("");
4928
+ console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
4929
+ return;
4930
+ }
4931
+ if (action === "test") {
4932
+ const { target, timeout, concurrency } = resolveActiveTestTarget(args);
4933
+ console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
4934
+ console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
4935
+ console.log("");
4936
+ const summary = await testSubscriptionProxies(target.name, {
4937
+ timeout,
4938
+ concurrency,
4939
+ onResult: printTestResult
4940
+ });
4941
+ console.log("");
4942
+ console.log(formatTestSummary(summary));
4943
+ return;
4944
+ }
4945
+ console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
4946
+ console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
4947
+ process.exit(1);
4948
+ }
4949
+
4950
+ // src/commands/start.ts
4951
+ var AUTO_CLEAN_THRESHOLD = 100;
4952
+ function handleStopResult(result) {
4953
+ if (result.remaining && result.remaining.length > 0) {
4954
+ console.error(`${colors.red("\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62:")} ${result.remaining.join(", ")}`);
4955
+ console.error("\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo");
4956
+ process.exit(1);
4957
+ }
4958
+ }
4959
+ async function cmdStart(args) {
4960
+ if (!hasKernel()) {
4961
+ console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
4962
+ process.exit(1);
4963
+ }
4964
+ const targetMode = args[1] === "tun" ? "tun" : "mixed";
4965
+ const sub = getActiveSubscription();
4966
+ if (!sub) {
4967
+ console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
4968
+ process.exit(1);
4969
+ }
4970
+ await autoUpdateStaleSubscription();
4971
+ const status = getStatus();
4972
+ const hasProcess = status.running || status.allProcesses.length > 0;
4973
+ if (hasProcess) {
4974
+ const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
4975
+ console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
4976
+ }
4977
+ handleStopResult(stop(true));
4978
+ if (hasProcess) {
4979
+ console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
4980
+ `);
4981
+ }
4982
+ let configInfo;
4983
+ try {
4984
+ configInfo = prepareConfigForStart(targetMode, sub.name);
4985
+ } catch (e) {
4986
+ console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
4987
+ process.exit(1);
4988
+ }
4989
+ const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
4990
+ console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
4991
+ try {
4992
+ const result = await start(targetMode);
4993
+ console.log(`${colors.green("\u5DF2\u542F\u52A8")} (PID ${result.pid})`);
4994
+ } catch (e) {
4995
+ console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
4996
+ process.exit(1);
4997
+ }
4998
+ if (configInfo.proxies > AUTO_CLEAN_THRESHOLD) {
4999
+ console.log("");
5000
+ console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${AUTO_CLEAN_THRESHOLD}\uFF0C\u81EA\u52A8\u6E05\u7406...`);
5001
+ console.log("");
5002
+ await sleep(1e3);
5003
+ const cleanResult = await autoCleanSubscription(sub.name, { onResult: printTestResult });
5004
+ console.log("");
5005
+ console.log(formatTestSummary(cleanResult.summary));
5006
+ if (cleanResult.removedProxies > 0) {
5007
+ console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
5008
+ console.log("");
5009
+ console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
5010
+ handleStopResult(stop(true));
5011
+ try {
5012
+ configInfo = prepareConfigForStart(targetMode, sub.name);
5013
+ const result = await start(targetMode);
5014
+ console.log(`${colors.green("\u5DF2\u91CD\u542F")} (PID ${result.pid}) \xB7 ${formatProxySummary(configInfo)}`);
5015
+ } catch (e) {
5016
+ console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
5017
+ process.exit(1);
5018
+ }
5019
+ }
5020
+ }
5021
+ printStatus();
5022
+ }
5023
+
5024
+ // src/commands/overwrite.ts
5025
+ function printOverwriteList() {
5026
+ const info = listOverwriteFile();
5027
+ const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
5028
+ console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}`);
5029
+ console.log(`${colors.gray("\u4F4D\u7F6E: ")}${info.dir}`);
5030
+ console.log("");
5031
+ if (info.files.length === 0) {
5032
+ console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
5033
+ console.log("");
5034
+ console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path5.join(info.dir, "overwrite.yaml")}`);
5035
+ console.log(` \u6216 ${path5.join(info.dir, "overwrite.dns.yaml")}`);
5036
+ console.log("");
5037
+ } else {
5038
+ console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
5039
+ console.log("");
5040
+ info.files.forEach((f, i) => {
5041
+ const num = i < 10 ? ` ${i}` : `${i}`;
5042
+ console.log(` ${num}. ${f.name}`);
5043
+ if (f.keys.length > 0) {
5044
+ console.log(` ${colors.gray("\u5B57\u6BB5: ")}${f.keys.join(", ")}`);
5045
+ }
5046
+ });
5047
+ console.log("");
5048
+ }
5049
+ console.log("\u542F\u7528\u8986\u5199: mihomo ow on");
5050
+ console.log("\u7981\u7528\u8986\u5199: mihomo ow off");
5051
+ console.log("");
5052
+ }
5053
+ async function cmdOverwrite(args) {
5054
+ const action = args?.[1];
5055
+ const status = getStatus();
5056
+ const configInfo = getConfigInfo();
5057
+ const currentMode = configInfo?.tun ? "tun" : "mixed";
5058
+ if (action === "on" || action === "enable") {
5059
+ if (isOverwriteEnabled()) {
5060
+ console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
5061
+ console.log("");
5062
+ printOverwriteList();
5063
+ return;
5064
+ }
5065
+ setOverwriteEnabled(true);
5066
+ console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
5067
+ if (status.running) {
5068
+ console.log("");
5069
+ await cmdStart(["start", currentMode]);
5070
+ return;
5071
+ }
5072
+ console.log("");
5073
+ printOverwriteList();
5074
+ return;
5075
+ }
5076
+ if (action === "off" || action === "disable") {
5077
+ if (!isOverwriteEnabled()) {
5078
+ console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u7981\u7528\u72B6\u6001");
5079
+ console.log("");
5080
+ printOverwriteList();
5081
+ return;
5082
+ }
5083
+ setOverwriteEnabled(false);
5084
+ console.log("\u5DF2\u7981\u7528\u8986\u5199\u914D\u7F6E");
5085
+ if (status.running) {
5086
+ console.log("");
5087
+ await cmdStart(["start", currentMode]);
5088
+ return;
5089
+ }
5090
+ console.log("");
5091
+ printOverwriteList();
4616
5092
  return;
4617
5093
  }
4618
5094
  console.log("");
@@ -4792,228 +5268,6 @@ async function cmdStop() {
4792
5268
  console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
4793
5269
  }
4794
5270
 
4795
- // src/commands/subscription.ts
4796
- async function printSubscriptionList(options) {
4797
- if (options?.autoUpdate !== false) {
4798
- const updateResult = await autoUpdateStaleSubscription();
4799
- if (updateResult.total > 0) console.log("");
4800
- }
4801
- const subs = getSubscriptionsWithCache();
4802
- if (subs.length === 0) {
4803
- console.log("\u6CA1\u6709\u8BA2\u9605");
4804
- console.log("");
4805
- console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
4806
- console.log("");
4807
- return;
4808
- }
4809
- const activeSub = getActiveSubscription();
4810
- console.log(colors.cyan("\u8BA2\u9605\u5217\u8868:"));
4811
- subs.forEach((s, i) => {
4812
- const time = formatDate(s.updated_at);
4813
- const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
4814
- const interval = s.update_interval || DEFAULT_UPDATE_INTERVAL_HOURS;
4815
- console.log(` ${i + 1}. ${s.name}${defaultMark}`);
4816
- console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
4817
- if (s.username) {
4818
- console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
4819
- }
4820
- if (s.download !== void 0 || s.total !== void 0) {
4821
- const used = (s.upload || 0) + (s.download || 0);
4822
- const usedStr = formatBytes(used);
4823
- const totalStr = formatBytes(s.total);
4824
- let percentStr = "";
4825
- if (s.total && s.total > 0) {
4826
- const percent = Math.min(used / s.total * 100, 100);
4827
- percentStr = ` (${percent.toFixed(1)}%)`;
4828
- }
4829
- console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
4830
- }
4831
- if (s.expire !== void 0) {
4832
- console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
4833
- }
4834
- if (s.web_page_url) {
4835
- console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
4836
- }
4837
- });
4838
- console.log("");
4839
- console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
4840
- console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
4841
- console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
4842
- console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
4843
- console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
4844
- console.log("");
4845
- }
4846
- async function cmdSubscription(args) {
4847
- const action = args[1];
4848
- if (!action || action === "list") {
4849
- await printSubscriptionList();
4850
- return;
4851
- }
4852
- if (action === "add") {
4853
- const url = args[2];
4854
- const name = args[3] || "default";
4855
- if (!url?.startsWith("http")) {
4856
- console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
4857
- process.exit(1);
4858
- }
4859
- console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
4860
- try {
4861
- addSubscription(url, name);
4862
- setDefaultSubscription(name);
4863
- const info = await downloadSubscription(url, name);
4864
- console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
4865
- } catch (e) {
4866
- console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
4867
- process.exit(1);
4868
- }
4869
- console.log("");
4870
- await printSubscriptionList();
4871
- return;
4872
- }
4873
- if (action === "update") {
4874
- const name = args[2];
4875
- const subs = getSubscriptions();
4876
- if (subs.length === 0) {
4877
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
4878
- process.exit(1);
4879
- }
4880
- if (!name) {
4881
- console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
4882
- const results = await Promise.all(subs.map(tryUpdateOne));
4883
- let ok = 0;
4884
- for (const r of results) {
4885
- if (r.success) {
4886
- ok++;
4887
- console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
4888
- } else {
4889
- console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
4890
- }
4891
- }
4892
- if (ok === 0) process.exit(1);
4893
- console.log("");
4894
- await printSubscriptionList();
4895
- return;
4896
- }
4897
- const matches = findSubscriptionFuzzy(subs, name);
4898
- const target = pickSingleSubscription(matches, name);
4899
- console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
4900
- try {
4901
- const info = await downloadSubscription(target.url, target.name);
4902
- console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(info)})`);
4903
- } catch (e) {
4904
- console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
4905
- process.exit(1);
4906
- }
4907
- console.log("");
4908
- await printSubscriptionList();
4909
- return;
4910
- }
4911
- if (action === "use") {
4912
- const name = args[2];
4913
- const subs = getSubscriptions();
4914
- if (!name) {
4915
- console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
4916
- if (subs.length > 0) {
4917
- console.log("\n\u53EF\u7528\u8BA2\u9605:");
4918
- for (const s of subs) console.log(` ${s.name}`);
4919
- }
4920
- process.exit(1);
4921
- }
4922
- const matches = findSubscriptionFuzzy(subs, name);
4923
- const target = pickSingleSubscription(matches, name);
4924
- const currentDefault = getActiveSubscription();
4925
- const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
4926
- if (isAlreadyDefault) {
4927
- console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
4928
- console.log("");
4929
- await printSubscriptionList();
4930
- return;
4931
- }
4932
- const status = getStatus();
4933
- const configInfo = getConfigInfo();
4934
- const currentMode = configInfo?.tun ? "tun" : "mixed";
4935
- const success = setDefaultSubscription(target.name);
4936
- if (success) {
4937
- console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
4938
- } else {
4939
- console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
4940
- process.exit(1);
4941
- }
4942
- if (status.running) {
4943
- console.log("");
4944
- await cmdStart(["start", currentMode]);
4945
- return;
4946
- }
4947
- console.log("");
4948
- await printSubscriptionList();
4949
- return;
4950
- }
4951
- if (action === "web" || action === "open") {
4952
- const name = args[2];
4953
- const subs = getSubscriptionsWithCache();
4954
- if (subs.length === 0) {
4955
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
4956
- process.exit(1);
4957
- }
4958
- let target;
4959
- if (name) {
4960
- const matches = findSubscriptionFuzzy(subs, name);
4961
- target = pickSingleSubscription(matches, name);
4962
- } else {
4963
- target = subs[0];
4964
- }
4965
- const cached = getSubscriptionsWithCache().find((s) => s.name === target.name);
4966
- let webPageUrl = cached?.web_page_url;
4967
- if (!webPageUrl) {
4968
- console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
4969
- try {
4970
- await downloadSubscription(target.url, target.name);
4971
- const cache = readSubscriptionCache();
4972
- if (cache[target.name]?.web_page_url) {
4973
- webPageUrl = cache[target.name].web_page_url;
4974
- } else {
4975
- console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
4976
- process.exit(1);
4977
- }
4978
- } catch (e) {
4979
- console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
4980
- process.exit(1);
4981
- }
4982
- }
4983
- console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
4984
- const opened = openUrl(webPageUrl);
4985
- if (!opened) {
4986
- console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
4987
- }
4988
- return;
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
- }
5012
- console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
5013
- console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web]");
5014
- process.exit(1);
5015
- }
5016
-
5017
5271
  // src/commands/ui.ts
5018
5272
  function cmdUI(args) {
5019
5273
  const uiName = args[1] || "zash";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mihomo-cli",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "type": "module",
5
5
  "description": "A terminal-based mihomo (Clash.Meta) client for macOS",
6
6
  "bin": {