mihomo-cli 3.4.0 → 3.5.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/index.js +779 -823
  3. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -3602,6 +3602,18 @@ var TimeoutError = class extends Error {
3602
3602
  this.name = "TimeoutError";
3603
3603
  }
3604
3604
  };
3605
+ var CliError = class extends Error {
3606
+ hint;
3607
+ label;
3608
+ exitCode;
3609
+ constructor(message, options = {}) {
3610
+ super(message);
3611
+ this.name = "CliError";
3612
+ this.label = options.label ?? "\u9519\u8BEF";
3613
+ this.hint = options.hint === void 0 ? [] : Array.isArray(options.hint) ? options.hint : [options.hint];
3614
+ this.exitCode = options.exitCode ?? 1;
3615
+ }
3616
+ };
3605
3617
  function withTimeout(promise, ms) {
3606
3618
  return new Promise((resolve, reject) => {
3607
3619
  const timer = setTimeout(() => reject(new TimeoutError()), ms);
@@ -5016,6 +5028,13 @@ function getActiveSubscription() {
5016
5028
  }
5017
5029
  return subs[0];
5018
5030
  }
5031
+ function requireActiveSubscription(emptyMsg = "\u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605") {
5032
+ const sub = getActiveSubscription();
5033
+ if (!sub) {
5034
+ throw new CliError(emptyMsg);
5035
+ }
5036
+ return sub;
5037
+ }
5019
5038
  function findSubscriptionFuzzy(subs, pattern) {
5020
5039
  const lowerPattern = pattern.toLowerCase();
5021
5040
  const exact = [];
@@ -5037,14 +5056,15 @@ function findSubscriptionFuzzy(subs, pattern) {
5037
5056
  }
5038
5057
  function pickSingleSubscription(subs, pattern) {
5039
5058
  if (subs.length === 0) {
5040
- console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u8BA2\u9605`);
5041
- process.exit(1);
5059
+ throw new CliError(`\u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u8BA2\u9605`);
5042
5060
  }
5043
5061
  if (subs.length === 1) return subs[0];
5044
- console.error("\u9519\u8BEF: \u5339\u914D\u5230\u591A\u4E2A\u8BA2\u9605\uFF0C\u8BF7\u66F4\u7CBE\u786E\u6307\u5B9A");
5045
- console.log("\n\u5339\u914D\u7684\u8BA2\u9605:");
5046
- for (const s of subs) console.log(` ${s.name}`);
5047
- process.exit(1);
5062
+ throw new CliError("\u5339\u914D\u5230\u591A\u4E2A\u8BA2\u9605\uFF0C\u8BF7\u66F4\u7CBE\u786E\u6307\u5B9A", {
5063
+ hint: ["", "\u5339\u914D\u7684\u8BA2\u9605:", ...subs.map((s) => ` ${s.name}`)]
5064
+ });
5065
+ }
5066
+ function resolveSubscription(subs, pattern) {
5067
+ return pickSingleSubscription(findSubscriptionFuzzy(subs, pattern), pattern);
5048
5068
  }
5049
5069
  async function downloadSubscription(url, subName = "default", signal, persist = true) {
5050
5070
  let response;
@@ -5414,174 +5434,475 @@ async function autoCleanSubscription(subName, options = {}) {
5414
5434
  return { summary, removedProxies, updatedGroups, removedGroups, skipped };
5415
5435
  }
5416
5436
 
5417
- // src/commands/daemon.ts
5418
- function printDaemonStatus() {
5419
- const status = getDaemonStatus();
5420
- const stateText = status.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
5421
- console.log(`${colors.gray("\u4FDD\u6D3B: ")}${stateText}`);
5422
- if (status.enabled) {
5423
- const runText = isDaemonRunning(status) ? colors.green(`\u8FD0\u884C\u4E2D (PID ${status.pid})`) : colors.yellow("\u672A\u8FD0\u884C");
5424
- console.log(`${colors.gray("\u5185\u6838: ")}${runText}`);
5425
- }
5426
- console.log("");
5427
- if (status.enabled) {
5428
- console.log("\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off");
5429
- } else {
5430
- console.log("\u5F00\u542F\u4FDD\u6D3B: mihomo daemon on");
5431
- console.log(colors.gray(" \u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF08\u4EC5 Mixed \u6A21\u5F0F\uFF09"));
5437
+ // src/runtime.ts
5438
+ function getRuntimeMode() {
5439
+ if (isDaemonEnabled()) return "mixed";
5440
+ return getConfigInfo()?.tun ? "tun" : "mixed";
5441
+ }
5442
+ function getRunningState() {
5443
+ if (isDaemonEnabled()) {
5444
+ const daemon = getDaemonStatus();
5445
+ return { running: isDaemonRunning(daemon), pid: daemon.pid, daemon: true };
5432
5446
  }
5433
- console.log("");
5447
+ const status = getStatus();
5448
+ return { running: status.running, pid: status.pid, daemon: false };
5434
5449
  }
5435
- async function cmdDaemon(args) {
5436
- const action = args?.[1];
5437
- if (action === "on" || action === "enable") {
5438
- if (!hasKernel()) {
5439
- console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
5440
- process.exit(1);
5441
- }
5442
- const sub = getActiveSubscription();
5443
- if (!sub) {
5444
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
5445
- process.exit(1);
5446
- }
5447
- let configInfo;
5448
- try {
5449
- configInfo = prepareConfigForStart("mixed", sub.name);
5450
- } catch (e) {
5451
- console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
5452
- process.exit(1);
5453
- }
5454
- console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u5B89\u88C5\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1\uFF08LaunchDaemon\uFF09"));
5455
- console.log(colors.gray("\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u9700\u8981 root\uFF0C\u4EE5\u89E3\u51B3\u5C40\u57DF\u7F51\u8BBF\u95EE\u53D7\u9650\u95EE\u9898"));
5456
- try {
5457
- enableDaemon();
5458
- } catch (e) {
5459
- console.error(`${colors.red("\u542F\u7528\u4FDD\u6D3B\u5931\u8D25:")} ${e.message}`);
5460
- process.exit(1);
5461
- }
5462
- console.log(`${colors.green("\u5DF2\u542F\u7528\u4FDD\u6D3B")} \xB7 ${sub.name} \xB7 ${formatProxySummary(configInfo)}`);
5463
- console.log(colors.gray("\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF0C\u4EE3\u7406\u5C06\u5728\u540E\u53F0\u5E38\u9A7B"));
5464
- console.log("");
5450
+ function isRestartNeededOnChange() {
5451
+ return isDaemonEnabled() || getStatus().running;
5452
+ }
5453
+ async function launchOrRestart(mode) {
5454
+ if (isDaemonEnabled()) {
5455
+ await restartDaemon();
5465
5456
  await sleep(DAEMON_BOOT_WAIT_MS);
5466
- printDaemonStatus();
5467
- return;
5457
+ return getDaemonStatus().pid;
5458
+ }
5459
+ const result = await start(mode);
5460
+ return result.pid;
5461
+ }
5462
+
5463
+ // src/progress.ts
5464
+ var IS_TTY = process.stdout.isTTY === true;
5465
+ var BAR_WIDTH = 20;
5466
+ function createProgressPrinter(totalRounds = 1) {
5467
+ let alive = 0;
5468
+ let dead = 0;
5469
+ const resultMap = /* @__PURE__ */ new Map();
5470
+ function render(done, total) {
5471
+ if (!IS_TTY) return;
5472
+ const pct = Math.round(done / total * 100);
5473
+ const filled = Math.round(done / total * BAR_WIDTH);
5474
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
5475
+ process.stdout.write(`\r${bar} ${done}/${total} (${pct}%) | ${colors.green(`\u2713${alive}`)} ${colors.red(`\u2717${dead}`)}`);
5468
5476
  }
5469
- if (action === "off" || action === "disable") {
5470
- if (!isDaemonEnabled()) {
5471
- console.log("\u4FDD\u6D3B\u5DF2\u662F\u5173\u95ED\u72B6\u6001");
5477
+ return {
5478
+ onResult(result, index, total, round = 1) {
5479
+ if (resultMap.size === 0 && totalRounds > 1) {
5480
+ console.log(`--- \u7B2C 1 \u8F6E\u6D4B\u8BD5 (${total} \u4E2A\u8282\u70B9) ---`);
5481
+ }
5482
+ const prev = resultMap.get(result.name);
5483
+ if (prev) {
5484
+ if (prev.result.delay !== null) alive--;
5485
+ else dead--;
5486
+ }
5487
+ if (result.delay !== null) alive++;
5488
+ else dead++;
5489
+ resultMap.set(result.name, { result, round });
5490
+ render(index + 1, total);
5491
+ },
5492
+ onRetryRound(round, count) {
5493
+ if (IS_TTY) {
5494
+ process.stdout.write("\n");
5495
+ }
5496
+ console.log(`--- \u7B2C ${round} \u8F6E\u91CD\u8BD5 (${count} \u4E2A\u8282\u70B9) ---`);
5497
+ alive = 0;
5498
+ dead = 0;
5499
+ },
5500
+ finish() {
5501
+ if (IS_TTY) {
5502
+ process.stdout.write("\n");
5503
+ }
5504
+ console.log("");
5505
+ if (!IS_TTY) return;
5506
+ const entries = [...resultMap.values()];
5507
+ entries.sort((a, b) => a.result.name.localeCompare(b.result.name));
5508
+ const total = entries.length;
5509
+ console.log("\u8282\u70B9\u6700\u7EC8\u72B6\u6001:");
5510
+ for (let i = 0; i < entries.length; i++) {
5511
+ const { result, round } = entries[i];
5512
+ const prefix = `[${i + 1}/${total}]`;
5513
+ if (result.delay !== null) {
5514
+ const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
5515
+ const retryNote = round > 1 ? colors.gray(` (\u7B2C${round}\u8F6E\u901A\u8FC7)`) : "";
5516
+ console.log(`${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}${retryNote}`);
5517
+ } else {
5518
+ console.log(`${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
5519
+ }
5520
+ }
5472
5521
  console.log("");
5473
- printDaemonStatus();
5474
- return;
5475
- }
5476
- console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u79FB\u9664\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1"));
5477
- try {
5478
- disableDaemon();
5479
- } catch (e) {
5480
- console.error(`${colors.red("\u5173\u95ED\u4FDD\u6D3B\u5931\u8D25:")} ${e.message}`);
5481
- process.exit(1);
5482
5522
  }
5483
- console.log(`${colors.green("\u5DF2\u5173\u95ED\u4FDD\u6D3B")}\uFF0C\u4EE3\u7406\u5DF2\u505C\u6B62`);
5484
- console.log(colors.gray("\u91CD\u65B0\u542F\u7528: mihomo daemon on"));
5485
- console.log("");
5486
- return;
5487
- }
5488
- if (action !== void 0 && action !== "status") {
5489
- console.error(`\u9519\u8BEF: \u672A\u77E5\u7684 daemon \u5B50\u547D\u4EE4: ${action}`);
5490
- console.log("");
5491
- console.log("\u53EF\u7528\u5B50\u547D\u4EE4: on, off, status");
5492
- process.exit(1);
5493
- }
5494
- console.log("");
5495
- printDaemonStatus();
5523
+ };
5524
+ }
5525
+ function formatCleanSummary(result) {
5526
+ const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
5527
+ if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
5528
+ if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
5529
+ return parts.join(", ");
5530
+ }
5531
+ function formatTestSummary(summary) {
5532
+ return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
5496
5533
  }
5497
5534
 
5498
- // src/commands/directory.ts
5499
- function cmdDirectory(args) {
5500
- const action = args?.[1];
5501
- if (action === "open") {
5502
- const target = args[2];
5503
- if (!target || target === "root") {
5504
- console.log("\u6B63\u5728\u6253\u5F00: \u6839\u76EE\u5F55");
5505
- const success = openUrl(USER_DATA_DIR);
5506
- if (!success) {
5507
- console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${USER_DATA_DIR}`);
5508
- }
5509
- return;
5535
+ // src/commands/status.ts
5536
+ function printStatus() {
5537
+ const status = getStatus();
5538
+ const state = getRunningState();
5539
+ const info = getConfigInfo();
5540
+ const overwriteEnabled = isOverwriteEnabled();
5541
+ const overwriteFiles = listOverwriteFile().files;
5542
+ const activeSub = getActiveSubscription();
5543
+ const { running, pid, daemon: daemonManaged } = state;
5544
+ console.log("");
5545
+ let modeLabel = "";
5546
+ if (info) {
5547
+ modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
5548
+ }
5549
+ const statusText = running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
5550
+ console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}${modeLabel}`);
5551
+ console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
5552
+ if (pid) {
5553
+ console.log(`${colors.gray("PID: ")}${pid}`);
5554
+ if (!daemonManaged && status.processInfo) {
5555
+ console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
5510
5556
  }
5511
- const key = target.toLowerCase();
5512
- const targetInfo = Object.hasOwn(DIRECTORY_TARGETS, key) ? DIRECTORY_TARGETS[key] : void 0;
5513
- if (targetInfo) {
5514
- const targetPath = targetInfo.path || USER_DATA_DIR;
5515
- console.log(`\u6B63\u5728\u6253\u5F00: ${targetInfo.label}`);
5516
- const success = openUrl(targetPath);
5517
- if (!success) {
5518
- console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${targetPath}`);
5519
- }
5520
- return;
5557
+ }
5558
+ if (info) {
5559
+ if (info.tun) {
5560
+ const extra = info.mixedPort ? `\uFF0C\u53E6\u76D1\u542C ${info.mixedPort}` : "";
5561
+ console.log(`${colors.gray("\u7AEF\u53E3: ")}TUN \u63A5\u7BA1${extra}`);
5562
+ } else if (info.mixedPort) {
5563
+ console.log(`${colors.gray("\u7AEF\u53E3: ")}${info.mixedPort}`);
5564
+ } else {
5565
+ const ports = [];
5566
+ if (info.httpPort) ports.push(`HTTP:${info.httpPort}`);
5567
+ if (info.socksPort) ports.push(`SOCKS:${info.socksPort}`);
5568
+ console.log(`${colors.gray("\u7AEF\u53E3: ")}${ports.length > 0 ? ports.join(", ") : "\u672A\u77E5"}`);
5521
5569
  }
5522
- console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u76EE\u5F55\u76EE\u6807 "${target}"`);
5523
- console.log("");
5524
- console.log("\u53EF\u7528\u76EE\u6807:");
5525
- console.log(" root (\u9ED8\u8BA4) \u6839\u76EE\u5F55");
5526
- for (const [key2, val] of Object.entries(DIRECTORY_TARGETS)) {
5527
- if (key2 !== "root") {
5528
- console.log(` ${key2.padEnd(14)}${val.label}`);
5529
- }
5570
+ }
5571
+ if (activeSub) {
5572
+ let subLine = `${colors.gray("\u8BA2\u9605: ")}${activeSub.name}`;
5573
+ if (info) {
5574
+ subLine += ` (${formatProxySummary(info)})`;
5530
5575
  }
5531
- console.log("");
5532
- process.exit(1);
5576
+ console.log(subLine);
5577
+ } else {
5578
+ console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
5579
+ }
5580
+ if (overwriteEnabled && overwriteFiles.length > 0) {
5581
+ const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "").replace(/\.ya?ml$/, "") || "\u4E3B\u6587\u4EF6").join(", ");
5582
+ console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (${names})`);
5583
+ } else if (overwriteEnabled) {
5584
+ console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (\u65E0\u6587\u4EF6)`);
5585
+ } else {
5586
+ console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
5587
+ }
5588
+ if (isDaemonEnabled()) {
5589
+ console.log(`${colors.gray("\u4FDD\u6D3B: ")}${colors.green("\u5DF2\u542F\u7528")} ${colors.gray("(\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F)")}`);
5533
5590
  }
5534
- console.log("");
5535
- console.log("\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E:");
5536
- console.log(` \u6839\u76EE\u5F55: ${USER_DATA_DIR}`);
5537
- console.log(` \u5168\u5C40\u8BBE\u7F6E: ${PATHS.settingsFile}`);
5538
- console.log(` \u5185\u6838\u76EE\u5F55: ${DIRS.kernel}`);
5539
- console.log(` \u5185\u6838\u6587\u4EF6: ${PATHS.mihomoBinary}`);
5540
- console.log(` \u8BA2\u9605\u76EE\u5F55: ${DIRS.subscriptions}`);
5541
- console.log(" - cache.json (\u8BA2\u9605\u7F13\u5B58\uFF1A\u66F4\u65B0\u65F6\u95F4\u3001\u6D41\u91CF\u7B49)");
5542
- console.log(" - xxx.yaml (\u8BA2\u9605\u539F\u59CB\u914D\u7F6E)");
5543
- console.log(` \u8FD0\u884C\u65F6\u76EE\u5F55: ${DIRS.runtime}`);
5544
- console.log(" - config.yaml (\u542F\u52A8\u65F6\u751F\u6210\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
5545
- console.log(" - pid (PID \u6587\u4EF6\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
5546
- console.log(` \u65E5\u5FD7\u6587\u4EF6: ${PATHS.logFile}`);
5547
- console.log(` mihomo \u6570\u636E: ${DIRS.data}`);
5548
- console.log(" - cache.db, Geo*.dat \u7B49 (mihomo \u81EA\u884C\u7BA1\u7406)");
5549
- console.log("");
5550
- console.log("\u6253\u5F00\u76EE\u5F55:");
5551
- console.log(" mihomo dir open \u6253\u5F00\u6839\u76EE\u5F55");
5552
- console.log(" mihomo dir open subs \u6253\u5F00\u8BA2\u9605\u76EE\u5F55");
5553
- console.log(" mihomo dir open logs \u6253\u5F00\u65E5\u5FD7\u76EE\u5F55");
5554
- console.log(" mihomo dir open data \u6253\u5F00 mihomo \u6570\u636E\u76EE\u5F55");
5555
- console.log(" mihomo dir open runtime \u6253\u5F00\u8FD0\u884C\u65F6\u76EE\u5F55");
5556
- console.log(" mihomo dir open kernel \u6253\u5F00\u5185\u6838\u76EE\u5F55");
5557
- console.log("");
5558
- console.log("\u73AF\u5883\u53D8\u91CF:");
5559
- console.log(" MIHOMO_CLI_DIR: \u81EA\u5B9A\u4E49\u6839\u76EE\u5F55\u4F4D\u7F6E");
5560
5591
  console.log("");
5561
5592
  }
5562
5593
 
5563
- // src/kernel.ts
5564
- import { spawnSync as spawnSync5 } from "child_process";
5565
- import fs7 from "fs";
5566
- import path6 from "path";
5567
-
5568
- // node_modules/compare-versions/lib/esm/utils.js
5569
- var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
5570
- var validateAndParse = (version) => {
5571
- if (typeof version !== "string") {
5572
- throw new TypeError("Invalid argument expected string");
5594
+ // src/commands/stop.ts
5595
+ function handleStopResult(result) {
5596
+ if (result.remaining && result.remaining.length > 0) {
5597
+ throw new CliError(result.remaining.join(", "), { label: "\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62", hint: "\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo" });
5573
5598
  }
5574
- const match = version.match(semver);
5575
- if (!match) {
5576
- throw new Error(`Invalid argument not valid semver ('${version}' received)`);
5599
+ }
5600
+ async function cmdStop() {
5601
+ if (isDaemonEnabled()) {
5602
+ console.log(colors.yellow("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF0C\u4EE3\u7406\u7531 launchd \u6258\u7BA1"));
5603
+ console.log("\u76F4\u63A5\u505C\u6B62\u4F1A\u88AB\u81EA\u52A8\u91CD\u65B0\u62C9\u8D77\uFF0C\u8BF7\u7528: mihomo daemon off");
5604
+ return;
5577
5605
  }
5578
- match.shift();
5579
- return match;
5580
- };
5581
- var isWildcard = (s) => s === "*" || s === "x" || s === "X";
5582
- var tryParse = (v) => {
5583
- const n = parseInt(v, 10);
5584
- return isNaN(n) ? v : n;
5606
+ const pids = getMihomoPids();
5607
+ if (pids.length === 0) {
5608
+ console.log(colors.yellow("\u4E0D\u5728\u8FD0\u884C"));
5609
+ return;
5610
+ }
5611
+ console.log(`\u505C\u6B62 ${pids.length} \u4E2A\u8FDB\u7A0B...`);
5612
+ handleStopResult(stop());
5613
+ console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
5614
+ }
5615
+
5616
+ // src/commands/start.ts
5617
+ async function cmdStart(args) {
5618
+ if (!hasKernel()) {
5619
+ throw new CliError('\u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
5620
+ }
5621
+ const targetMode = args[1] === "tun" ? "tun" : "mixed";
5622
+ const daemonEnabled = isDaemonEnabled();
5623
+ if (targetMode === "tun" && daemonEnabled) {
5624
+ throw new CliError("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF08\u4EC5\u652F\u6301 Mixed \u6A21\u5F0F\uFF09\uFF0C\u65E0\u6CD5\u542F\u52A8 TUN", { hint: "\u8BF7\u5148\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off" });
5625
+ }
5626
+ const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
5627
+ const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
5628
+ const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
5629
+ const skipUpdate = hasFlag(args, "-s", "--no-update");
5630
+ const skipClean = hasFlag(args, "--no-clean");
5631
+ const updateTimeout = parseIntArg(args, "-u", "--update-timeout", DEFAULT_AUTO_UPDATE_TIMEOUT);
5632
+ const sub = requireActiveSubscription("\u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
5633
+ if (!skipUpdate) {
5634
+ await autoUpdateStaleSubscription({ timeout: updateTimeout });
5635
+ }
5636
+ if (!daemonEnabled) {
5637
+ if (hasRootResidue()) {
5638
+ throw new CliError("\u5B58\u5728\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559\u8FDB\u7A0B/\u6587\u4EF6", {
5639
+ hint: [`\u8BF7\u5148\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo && sudo rm -f ${PATHS.pidFile}`, "\u6216\u5207\u6362\u5230 TUN \u6A21\u5F0F\u542F\u52A8\uFF08\u81EA\u52A8\u6E05\u7406\uFF09: mihomo start tun"]
5640
+ });
5641
+ }
5642
+ const status = getStatus();
5643
+ const hasProcess = status.running || status.allProcesses.length > 0;
5644
+ if (hasProcess) {
5645
+ const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
5646
+ console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
5647
+ }
5648
+ handleStopResult(stop());
5649
+ if (hasProcess) {
5650
+ console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
5651
+ `);
5652
+ }
5653
+ }
5654
+ let configInfo;
5655
+ try {
5656
+ configInfo = prepareConfigForStart(targetMode, sub.name);
5657
+ } catch (e) {
5658
+ if (e instanceof CliError) throw e;
5659
+ throw new CliError(e.message, { label: "\u914D\u7F6E\u9519\u8BEF" });
5660
+ }
5661
+ const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
5662
+ console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
5663
+ try {
5664
+ const pid = await launchOrRestart(targetMode);
5665
+ const label = daemonEnabled ? "\u5DF2\u542F\u52A8 (\u4FDD\u6D3B)" : "\u5DF2\u542F\u52A8";
5666
+ console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""}`);
5667
+ } catch (e) {
5668
+ if (e instanceof CliError) throw e;
5669
+ const lines = e.message.split("\n");
5670
+ throw new CliError(lines[0], { label: "\u542F\u52A8\u5931\u8D25", hint: lines.slice(1) });
5671
+ }
5672
+ const cleanThreshold = isGithubUrl(sub.url) ? AUTO_CLEAN_THRESHOLD_GITHUB : AUTO_CLEAN_THRESHOLD;
5673
+ if (!skipClean && configInfo.proxies > cleanThreshold) {
5674
+ const cache = readSubscriptionCache();
5675
+ const lastCleanAt = cache[sub.name]?.last_auto_clean_at;
5676
+ const withinCooldown = !!lastCleanAt && Date.now() - new Date(lastCleanAt).getTime() < AUTO_CLEAN_COOLDOWN_HOURS * 60 * 60 * 1e3;
5677
+ if (!withinCooldown) {
5678
+ console.log("");
5679
+ console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${cleanThreshold}\uFF0C\u81EA\u52A8\u6E05\u7406\uFF08${AUTO_CLEAN_COOLDOWN_HOURS}h \u5185\u4EC5\u4E00\u6B21\uFF0C--no-clean \u8DF3\u8FC7\uFF09...`);
5680
+ console.log("");
5681
+ await sleep(1e3);
5682
+ const progress = createProgressPrinter(rounds);
5683
+ const cleanResult = await autoCleanSubscription(sub.name, {
5684
+ timeout,
5685
+ concurrency,
5686
+ rounds,
5687
+ onResult: progress.onResult,
5688
+ onRetryRound: progress.onRetryRound
5689
+ });
5690
+ progress.finish();
5691
+ console.log(formatTestSummary(cleanResult.summary));
5692
+ if (cleanResult.skipped) {
5693
+ console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
5694
+ } else if (cleanResult.removedProxies > 0) {
5695
+ console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
5696
+ console.log("");
5697
+ console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
5698
+ if (!daemonEnabled) handleStopResult(stop());
5699
+ try {
5700
+ configInfo = prepareConfigForStart(targetMode, sub.name);
5701
+ const pid = await launchOrRestart(targetMode);
5702
+ console.log(`${colors.green("\u5DF2\u91CD\u542F")}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
5703
+ } catch (e) {
5704
+ if (e instanceof CliError) throw e;
5705
+ throw new CliError(e.message.split("\n")[0], { label: "\u91CD\u542F\u5931\u8D25" });
5706
+ }
5707
+ }
5708
+ saveSubscriptionCache(sub.name, { last_auto_clean_at: (/* @__PURE__ */ new Date()).toISOString() });
5709
+ }
5710
+ }
5711
+ printStatus();
5712
+ }
5713
+
5714
+ // src/commands/shared.ts
5715
+ async function dispatchSubcommand(args, table, options) {
5716
+ const action = args[1];
5717
+ if (action) {
5718
+ const cmd = table.find((c) => c.name === action || c.aliases?.includes(action));
5719
+ if (cmd) return cmd.handler(args);
5720
+ if (options.onUnknown) return options.onUnknown(action);
5721
+ }
5722
+ return options.fallback(args);
5723
+ }
5724
+ function requireRunning() {
5725
+ const state = getRunningState();
5726
+ if (!state.running) {
5727
+ const hint = state.daemon ? "mihomo daemon on" : "mihomo start";
5728
+ throw new CliError(`mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (${hint})`);
5729
+ }
5730
+ }
5731
+ async function restartToApply(args) {
5732
+ if (!isRestartNeededOnChange()) return false;
5733
+ const currentMode = getRuntimeMode();
5734
+ console.log("");
5735
+ await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
5736
+ return true;
5737
+ }
5738
+
5739
+ // src/commands/daemon.ts
5740
+ function printDaemonStatus() {
5741
+ const status = getDaemonStatus();
5742
+ const stateText = status.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
5743
+ console.log(`${colors.gray("\u4FDD\u6D3B: ")}${stateText}`);
5744
+ if (status.enabled) {
5745
+ const runText = isDaemonRunning(status) ? colors.green(`\u8FD0\u884C\u4E2D (PID ${status.pid})`) : colors.yellow("\u672A\u8FD0\u884C");
5746
+ console.log(`${colors.gray("\u5185\u6838: ")}${runText}`);
5747
+ }
5748
+ console.log("");
5749
+ if (status.enabled) {
5750
+ console.log("\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off");
5751
+ } else {
5752
+ console.log("\u5F00\u542F\u4FDD\u6D3B: mihomo daemon on");
5753
+ console.log(colors.gray(" \u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF08\u4EC5 Mixed \u6A21\u5F0F\uFF09"));
5754
+ }
5755
+ console.log("");
5756
+ }
5757
+ async function daemonOn() {
5758
+ if (!hasKernel()) {
5759
+ throw new CliError('\u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
5760
+ }
5761
+ const sub = requireActiveSubscription("\u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
5762
+ let configInfo;
5763
+ try {
5764
+ configInfo = prepareConfigForStart("mixed", sub.name);
5765
+ } catch (e) {
5766
+ if (e instanceof CliError) throw e;
5767
+ throw new CliError(e.message, { label: "\u914D\u7F6E\u9519\u8BEF" });
5768
+ }
5769
+ console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u5B89\u88C5\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1\uFF08LaunchDaemon\uFF09"));
5770
+ console.log(colors.gray("\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u9700\u8981 root\uFF0C\u4EE5\u89E3\u51B3\u5C40\u57DF\u7F51\u8BBF\u95EE\u53D7\u9650\u95EE\u9898"));
5771
+ try {
5772
+ enableDaemon();
5773
+ } catch (e) {
5774
+ if (e instanceof CliError) throw e;
5775
+ throw new CliError(e.message, { label: "\u542F\u7528\u4FDD\u6D3B\u5931\u8D25" });
5776
+ }
5777
+ console.log(`${colors.green("\u5DF2\u542F\u7528\u4FDD\u6D3B")} \xB7 ${sub.name} \xB7 ${formatProxySummary(configInfo)}`);
5778
+ console.log(colors.gray("\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF0C\u4EE3\u7406\u5C06\u5728\u540E\u53F0\u5E38\u9A7B"));
5779
+ console.log("");
5780
+ await sleep(DAEMON_BOOT_WAIT_MS);
5781
+ printDaemonStatus();
5782
+ }
5783
+ function daemonOff() {
5784
+ if (!isDaemonEnabled()) {
5785
+ console.log("\u4FDD\u6D3B\u5DF2\u662F\u5173\u95ED\u72B6\u6001");
5786
+ console.log("");
5787
+ printDaemonStatus();
5788
+ return;
5789
+ }
5790
+ console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u79FB\u9664\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1"));
5791
+ try {
5792
+ disableDaemon();
5793
+ } catch (e) {
5794
+ if (e instanceof CliError) throw e;
5795
+ throw new CliError(e.message, { label: "\u5173\u95ED\u4FDD\u6D3B\u5931\u8D25" });
5796
+ }
5797
+ console.log(`${colors.green("\u5DF2\u5173\u95ED\u4FDD\u6D3B")}\uFF0C\u4EE3\u7406\u5DF2\u505C\u6B62`);
5798
+ console.log(colors.gray("\u91CD\u65B0\u542F\u7528: mihomo daemon on"));
5799
+ console.log("");
5800
+ }
5801
+ function printStatusView() {
5802
+ console.log("");
5803
+ printDaemonStatus();
5804
+ }
5805
+ var SUBCOMMANDS = [
5806
+ { name: "on", aliases: ["enable"], handler: daemonOn },
5807
+ { name: "off", aliases: ["disable"], handler: daemonOff },
5808
+ { name: "status", handler: printStatusView }
5809
+ ];
5810
+ async function cmdDaemon(args) {
5811
+ await dispatchSubcommand(args, SUBCOMMANDS, {
5812
+ // 无 action → 显示状态;未知 action → 报错
5813
+ fallback: printStatusView,
5814
+ onUnknown: (action) => {
5815
+ throw new CliError(`\u672A\u77E5\u7684 daemon \u5B50\u547D\u4EE4: ${action}`, { hint: ["", "\u53EF\u7528\u5B50\u547D\u4EE4: on, off, status"] });
5816
+ }
5817
+ });
5818
+ }
5819
+
5820
+ // src/commands/directory.ts
5821
+ function openDirectory(args) {
5822
+ const target = args[2];
5823
+ if (!target || target === "root") {
5824
+ console.log("\u6B63\u5728\u6253\u5F00: \u6839\u76EE\u5F55");
5825
+ const success = openUrl(USER_DATA_DIR);
5826
+ if (!success) {
5827
+ console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${USER_DATA_DIR}`);
5828
+ }
5829
+ return;
5830
+ }
5831
+ const key = target.toLowerCase();
5832
+ const targetInfo = Object.hasOwn(DIRECTORY_TARGETS, key) ? DIRECTORY_TARGETS[key] : void 0;
5833
+ if (targetInfo) {
5834
+ const targetPath = targetInfo.path || USER_DATA_DIR;
5835
+ console.log(`\u6B63\u5728\u6253\u5F00: ${targetInfo.label}`);
5836
+ const success = openUrl(targetPath);
5837
+ if (!success) {
5838
+ console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${targetPath}`);
5839
+ }
5840
+ return;
5841
+ }
5842
+ const hint = ["", "\u53EF\u7528\u76EE\u6807:", " root (\u9ED8\u8BA4) \u6839\u76EE\u5F55"];
5843
+ for (const [k, val] of Object.entries(DIRECTORY_TARGETS)) {
5844
+ if (k !== "root") {
5845
+ hint.push(` ${k.padEnd(14)}${val.label}`);
5846
+ }
5847
+ }
5848
+ throw new CliError(`\u672A\u77E5\u7684\u76EE\u5F55\u76EE\u6807 "${target}"`, { hint });
5849
+ }
5850
+ function printDirectoryInfo() {
5851
+ console.log("");
5852
+ console.log("\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E:");
5853
+ console.log(` \u6839\u76EE\u5F55: ${USER_DATA_DIR}`);
5854
+ console.log(` \u5168\u5C40\u8BBE\u7F6E: ${PATHS.settingsFile}`);
5855
+ console.log(` \u5185\u6838\u76EE\u5F55: ${DIRS.kernel}`);
5856
+ console.log(` \u5185\u6838\u6587\u4EF6: ${PATHS.mihomoBinary}`);
5857
+ console.log(` \u8BA2\u9605\u76EE\u5F55: ${DIRS.subscriptions}`);
5858
+ console.log(" - cache.json (\u8BA2\u9605\u7F13\u5B58\uFF1A\u66F4\u65B0\u65F6\u95F4\u3001\u6D41\u91CF\u7B49)");
5859
+ console.log(" - xxx.yaml (\u8BA2\u9605\u539F\u59CB\u914D\u7F6E)");
5860
+ console.log(` \u8FD0\u884C\u65F6\u76EE\u5F55: ${DIRS.runtime}`);
5861
+ console.log(" - config.yaml (\u542F\u52A8\u65F6\u751F\u6210\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
5862
+ console.log(" - pid (PID \u6587\u4EF6\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
5863
+ console.log(` \u65E5\u5FD7\u6587\u4EF6: ${PATHS.logFile}`);
5864
+ console.log(` mihomo \u6570\u636E: ${DIRS.data}`);
5865
+ console.log(" - cache.db, Geo*.dat \u7B49 (mihomo \u81EA\u884C\u7BA1\u7406)");
5866
+ console.log("");
5867
+ console.log("\u6253\u5F00\u76EE\u5F55:");
5868
+ console.log(" mihomo dir open \u6253\u5F00\u6839\u76EE\u5F55");
5869
+ console.log(" mihomo dir open subs \u6253\u5F00\u8BA2\u9605\u76EE\u5F55");
5870
+ console.log(" mihomo dir open logs \u6253\u5F00\u65E5\u5FD7\u76EE\u5F55");
5871
+ console.log(" mihomo dir open data \u6253\u5F00 mihomo \u6570\u636E\u76EE\u5F55");
5872
+ console.log(" mihomo dir open runtime \u6253\u5F00\u8FD0\u884C\u65F6\u76EE\u5F55");
5873
+ console.log(" mihomo dir open kernel \u6253\u5F00\u5185\u6838\u76EE\u5F55");
5874
+ console.log("");
5875
+ console.log("\u73AF\u5883\u53D8\u91CF:");
5876
+ console.log(" MIHOMO_CLI_DIR: \u81EA\u5B9A\u4E49\u6839\u76EE\u5F55\u4F4D\u7F6E");
5877
+ console.log("");
5878
+ }
5879
+ var SUBCOMMANDS2 = [{ name: "open", handler: openDirectory }];
5880
+ function cmdDirectory(args) {
5881
+ void dispatchSubcommand(args, SUBCOMMANDS2, { fallback: printDirectoryInfo });
5882
+ }
5883
+
5884
+ // src/kernel.ts
5885
+ import { spawnSync as spawnSync5 } from "child_process";
5886
+ import fs7 from "fs";
5887
+ import path6 from "path";
5888
+
5889
+ // node_modules/compare-versions/lib/esm/utils.js
5890
+ var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
5891
+ var validateAndParse = (version) => {
5892
+ if (typeof version !== "string") {
5893
+ throw new TypeError("Invalid argument expected string");
5894
+ }
5895
+ const match = version.match(semver);
5896
+ if (!match) {
5897
+ throw new Error(`Invalid argument not valid semver ('${version}' received)`);
5898
+ }
5899
+ match.shift();
5900
+ return match;
5901
+ };
5902
+ var isWildcard = (s) => s === "*" || s === "x" || s === "X";
5903
+ var tryParse = (v) => {
5904
+ const n = parseInt(v, 10);
5905
+ return isNaN(n) ? v : n;
5585
5906
  };
5586
5907
  var forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b];
5587
5908
  var compareStrings = (a, b) => {
@@ -5852,25 +6173,25 @@ async function cmdKernel(args) {
5852
6173
  \u5DF2\u66F4\u65B0\u5230 ${result.version}`);
5853
6174
  }
5854
6175
  } catch (e) {
5855
- console.error(`
5856
- \u66F4\u65B0\u5931\u8D25: ${e.message}`);
6176
+ if (e instanceof CliError) throw e;
5857
6177
  const err = e;
5858
- if (err.response?.data) {
5859
- if (err.response.data.message) {
5860
- console.error(`\u539F\u56E0: ${err.response.data.message}`);
5861
- }
5862
- if (err.response.data.documentation_url) {
5863
- console.error(`\u6587\u6863: ${err.response.data.documentation_url}`);
5864
- }
6178
+ const hint = [];
6179
+ if (err.response?.data?.message) {
6180
+ hint.push(`\u539F\u56E0: ${err.response.data.message}`);
6181
+ }
6182
+ if (err.response?.data?.documentation_url) {
6183
+ hint.push(`\u6587\u6863: ${err.response.data.documentation_url}`);
5865
6184
  }
5866
6185
  if (!effectiveMirror) {
5867
- console.error("");
5868
- console.error("\u63D0\u793A: \u76F4\u8FDE\u5931\u8D25\u6216\u4E0B\u8F7D\u8FC7\u6162\u65F6\u53EF\u4F7F\u7528\u955C\u50CF:");
5869
- console.error(" mihomo kernel --mirror [\u955C\u50CF] # \u4E0B\u8F7D\u8D70\u955C\u50CF\uFF08\u9ED8\u8BA4 v6.gh-proxy.org\uFF09");
5870
- console.error(" mihomo kernel --mirror-all [\u955C\u50CF] # API \u548C\u4E0B\u8F7D\u90FD\u8D70\u955C\u50CF");
5871
- console.error(` \u53EF\u7528\u955C\u50CF: ${AVAILABLE_MIRRORS.join(", ")}`);
6186
+ hint.push(
6187
+ "",
6188
+ "\u63D0\u793A: \u76F4\u8FDE\u5931\u8D25\u6216\u4E0B\u8F7D\u8FC7\u6162\u65F6\u53EF\u4F7F\u7528\u955C\u50CF:",
6189
+ " mihomo kernel --mirror [\u955C\u50CF] # \u4E0B\u8F7D\u8D70\u955C\u50CF\uFF08\u9ED8\u8BA4 v6.gh-proxy.org\uFF09",
6190
+ " mihomo kernel --mirror-all [\u955C\u50CF] # API \u548C\u4E0B\u8F7D\u90FD\u8D70\u955C\u50CF",
6191
+ ` \u53EF\u7528\u955C\u50CF: ${AVAILABLE_MIRRORS.join(", ")}`
6192
+ );
5872
6193
  }
5873
- process.exit(1);
6194
+ throw new CliError(err.message, { label: "\u66F4\u65B0\u5931\u8D25", hint });
5874
6195
  }
5875
6196
  }
5876
6197
 
@@ -5897,9 +6218,7 @@ function cmdLogs(args) {
5897
6218
  const archiveLogs = listLogs();
5898
6219
  const archive = archiveLogs.archives[parsedIdx - 1];
5899
6220
  if (!archive) {
5900
- console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
5901
- console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
5902
- process.exit(1);
6221
+ throw new CliError(`\u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`, { hint: '\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868' });
5903
6222
  }
5904
6223
  logPath = archive.path;
5905
6224
  } else {
@@ -5907,350 +6226,55 @@ function cmdLogs(args) {
5907
6226
  }
5908
6227
  }
5909
6228
  if (!logPath) {
5910
- console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
5911
- console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
5912
- process.exit(1);
6229
+ throw new CliError(`\u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`, { hint: '\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868' });
5913
6230
  }
5914
6231
  if (openInViewer) {
5915
6232
  openLogFile(logPath);
5916
6233
  return;
5917
6234
  }
5918
- viewLogWithTail(logPath, { follow: false, lines });
5919
- return;
5920
- }
5921
- const logs = listLogs();
5922
- const all = [];
5923
- if (logs.current) all.push(logs.current);
5924
- all.push(...logs.archives);
5925
- if (all.length === 0) {
5926
- console.log("\u6682\u65E0\u65E5\u5FD7");
5927
- return;
5928
- }
5929
- console.log("");
5930
- console.log("\u65E5\u5FD7\u5217\u8868:");
5931
- console.log("");
5932
- let archiveCounter = 0;
5933
- for (const log of all) {
5934
- let num;
5935
- if (log.isCurrent) {
5936
- num = " 0";
5937
- } else {
5938
- archiveCounter++;
5939
- num = archiveCounter < 10 ? ` ${archiveCounter}` : `${archiveCounter}`;
5940
- }
5941
- const time = formatDate(log.mtime);
5942
- const size = formatBytes(log.size);
5943
- const name = log.isCurrent ? "mihomo.log (\u5F53\u524D\u8FD0\u884C\u4E2D)" : log.name;
5944
- console.log(` ${num}. ${name}`);
5945
- console.log(` \u65F6\u95F4: ${time} \u5927\u5C0F: ${size}`);
5946
- if (!log.isCurrent) {
5947
- console.log(` \u67E5\u770B: mihomo logs ${archiveCounter} \u6216 mihomo logs ${archiveCounter} -o`);
5948
- }
5949
- console.log("");
5950
- }
5951
- console.log("\u7528\u6CD5:");
5952
- console.log(" mihomo logs 0 # \u67E5\u770B\u5F53\u524D\u65E5\u5FD7 (\u6700\u540E 100 \u884C)");
5953
- console.log(" mihomo logs 1 # \u67E5\u770B\u7B2C 1 \u4E2A\u5F52\u6863\u65E5\u5FD7\uFF08\u6700\u65B0\uFF09");
5954
- console.log(" mihomo logs 1 -n 200 # \u67E5\u770B 200 \u884C");
5955
- console.log(" mihomo logs 1 -o # \u7528\u7CFB\u7EDF\u9ED8\u8BA4\u7A0B\u5E8F\u6253\u5F00");
5956
- console.log("");
5957
- }
5958
-
5959
- // src/commands/overwrite.ts
5960
- import path7 from "path";
5961
-
5962
- // src/runtime.ts
5963
- function getRuntimeMode() {
5964
- if (isDaemonEnabled()) return "mixed";
5965
- return getConfigInfo()?.tun ? "tun" : "mixed";
5966
- }
5967
- function getRunningState() {
5968
- if (isDaemonEnabled()) {
5969
- const daemon = getDaemonStatus();
5970
- return { running: isDaemonRunning(daemon), pid: daemon.pid, daemon: true };
5971
- }
5972
- const status = getStatus();
5973
- return { running: status.running, pid: status.pid, daemon: false };
5974
- }
5975
- function isRestartNeededOnChange() {
5976
- return isDaemonEnabled() || getStatus().running;
5977
- }
5978
- async function launchOrRestart(mode) {
5979
- if (isDaemonEnabled()) {
5980
- await restartDaemon();
5981
- await sleep(DAEMON_BOOT_WAIT_MS);
5982
- return getDaemonStatus().pid;
5983
- }
5984
- const result = await start(mode);
5985
- return result.pid;
5986
- }
5987
-
5988
- // src/progress.ts
5989
- var IS_TTY = process.stdout.isTTY === true;
5990
- var BAR_WIDTH = 20;
5991
- function createProgressPrinter(totalRounds = 1) {
5992
- let alive = 0;
5993
- let dead = 0;
5994
- const resultMap = /* @__PURE__ */ new Map();
5995
- function render(done, total) {
5996
- if (!IS_TTY) return;
5997
- const pct = Math.round(done / total * 100);
5998
- const filled = Math.round(done / total * BAR_WIDTH);
5999
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
6000
- process.stdout.write(`\r${bar} ${done}/${total} (${pct}%) | ${colors.green(`\u2713${alive}`)} ${colors.red(`\u2717${dead}`)}`);
6001
- }
6002
- return {
6003
- onResult(result, index, total, round = 1) {
6004
- if (resultMap.size === 0 && totalRounds > 1) {
6005
- console.log(`--- \u7B2C 1 \u8F6E\u6D4B\u8BD5 (${total} \u4E2A\u8282\u70B9) ---`);
6006
- }
6007
- const prev = resultMap.get(result.name);
6008
- if (prev) {
6009
- if (prev.result.delay !== null) alive--;
6010
- else dead--;
6011
- }
6012
- if (result.delay !== null) alive++;
6013
- else dead++;
6014
- resultMap.set(result.name, { result, round });
6015
- render(index + 1, total);
6016
- },
6017
- onRetryRound(round, count) {
6018
- if (IS_TTY) {
6019
- process.stdout.write("\n");
6020
- }
6021
- console.log(`--- \u7B2C ${round} \u8F6E\u91CD\u8BD5 (${count} \u4E2A\u8282\u70B9) ---`);
6022
- alive = 0;
6023
- dead = 0;
6024
- },
6025
- finish() {
6026
- if (IS_TTY) {
6027
- process.stdout.write("\n");
6028
- }
6029
- console.log("");
6030
- if (!IS_TTY) return;
6031
- const entries = [...resultMap.values()];
6032
- entries.sort((a, b) => a.result.name.localeCompare(b.result.name));
6033
- const total = entries.length;
6034
- console.log("\u8282\u70B9\u6700\u7EC8\u72B6\u6001:");
6035
- for (let i = 0; i < entries.length; i++) {
6036
- const { result, round } = entries[i];
6037
- const prefix = `[${i + 1}/${total}]`;
6038
- if (result.delay !== null) {
6039
- const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
6040
- const retryNote = round > 1 ? colors.gray(` (\u7B2C${round}\u8F6E\u901A\u8FC7)`) : "";
6041
- console.log(`${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}${retryNote}`);
6042
- } else {
6043
- console.log(`${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
6044
- }
6045
- }
6046
- console.log("");
6047
- }
6048
- };
6049
- }
6050
- function formatCleanSummary(result) {
6051
- const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
6052
- if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
6053
- if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
6054
- return parts.join(", ");
6055
- }
6056
- function formatTestSummary(summary) {
6057
- return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
6058
- }
6059
-
6060
- // src/commands/status.ts
6061
- function printStatus() {
6062
- const status = getStatus();
6063
- const state = getRunningState();
6064
- const info = getConfigInfo();
6065
- const overwriteEnabled = isOverwriteEnabled();
6066
- const overwriteFiles = listOverwriteFile().files;
6067
- const activeSub = getActiveSubscription();
6068
- const { running, pid, daemon: daemonManaged } = state;
6069
- console.log("");
6070
- let modeLabel = "";
6071
- if (info) {
6072
- modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
6073
- }
6074
- const statusText = running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
6075
- console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}${modeLabel}`);
6076
- console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
6077
- if (pid) {
6078
- console.log(`${colors.gray("PID: ")}${pid}`);
6079
- if (!daemonManaged && status.processInfo) {
6080
- console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
6081
- }
6082
- }
6083
- if (info) {
6084
- if (info.tun) {
6085
- const extra = info.mixedPort ? `\uFF0C\u53E6\u76D1\u542C ${info.mixedPort}` : "";
6086
- console.log(`${colors.gray("\u7AEF\u53E3: ")}TUN \u63A5\u7BA1${extra}`);
6087
- } else if (info.mixedPort) {
6088
- console.log(`${colors.gray("\u7AEF\u53E3: ")}${info.mixedPort}`);
6089
- } else {
6090
- const ports = [];
6091
- if (info.httpPort) ports.push(`HTTP:${info.httpPort}`);
6092
- if (info.socksPort) ports.push(`SOCKS:${info.socksPort}`);
6093
- console.log(`${colors.gray("\u7AEF\u53E3: ")}${ports.length > 0 ? ports.join(", ") : "\u672A\u77E5"}`);
6094
- }
6095
- }
6096
- if (activeSub) {
6097
- let subLine = `${colors.gray("\u8BA2\u9605: ")}${activeSub.name}`;
6098
- if (info) {
6099
- subLine += ` (${formatProxySummary(info)})`;
6100
- }
6101
- console.log(subLine);
6102
- } else {
6103
- console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
6104
- }
6105
- if (overwriteEnabled && overwriteFiles.length > 0) {
6106
- const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "").replace(/\.ya?ml$/, "") || "\u4E3B\u6587\u4EF6").join(", ");
6107
- console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (${names})`);
6108
- } else if (overwriteEnabled) {
6109
- console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (\u65E0\u6587\u4EF6)`);
6110
- } else {
6111
- console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
6112
- }
6113
- if (isDaemonEnabled()) {
6114
- console.log(`${colors.gray("\u4FDD\u6D3B: ")}${colors.green("\u5DF2\u542F\u7528")} ${colors.gray("(\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F)")}`);
6115
- }
6116
- console.log("");
6117
- }
6118
-
6119
- // src/commands/stop.ts
6120
- function handleStopResult(result) {
6121
- if (result.remaining && result.remaining.length > 0) {
6122
- console.error(`${colors.red("\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62:")} ${result.remaining.join(", ")}`);
6123
- console.error("\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo");
6124
- process.exit(1);
6125
- }
6126
- }
6127
- async function cmdStop() {
6128
- if (isDaemonEnabled()) {
6129
- console.log(colors.yellow("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF0C\u4EE3\u7406\u7531 launchd \u6258\u7BA1"));
6130
- console.log("\u76F4\u63A5\u505C\u6B62\u4F1A\u88AB\u81EA\u52A8\u91CD\u65B0\u62C9\u8D77\uFF0C\u8BF7\u7528: mihomo daemon off");
6131
- return;
6132
- }
6133
- const pids = getMihomoPids();
6134
- if (pids.length === 0) {
6135
- console.log(colors.yellow("\u4E0D\u5728\u8FD0\u884C"));
6136
- return;
6137
- }
6138
- console.log(`\u505C\u6B62 ${pids.length} \u4E2A\u8FDB\u7A0B...`);
6139
- handleStopResult(stop());
6140
- console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
6141
- }
6142
-
6143
- // src/commands/start.ts
6144
- async function cmdStart(args) {
6145
- if (!hasKernel()) {
6146
- console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
6147
- process.exit(1);
6148
- }
6149
- const targetMode = args[1] === "tun" ? "tun" : "mixed";
6150
- const daemonEnabled = isDaemonEnabled();
6151
- if (targetMode === "tun" && daemonEnabled) {
6152
- console.error(`${colors.red("\u9519\u8BEF:")} \u4FDD\u6D3B\u5DF2\u542F\u7528\uFF08\u4EC5\u652F\u6301 Mixed \u6A21\u5F0F\uFF09\uFF0C\u65E0\u6CD5\u542F\u52A8 TUN`);
6153
- console.error("\u8BF7\u5148\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off");
6154
- process.exit(1);
6155
- }
6156
- const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
6157
- const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
6158
- const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
6159
- const skipUpdate = hasFlag(args, "-s", "--no-update");
6160
- const skipClean = hasFlag(args, "--no-clean");
6161
- const updateTimeout = parseIntArg(args, "-u", "--update-timeout", DEFAULT_AUTO_UPDATE_TIMEOUT);
6162
- const sub = getActiveSubscription();
6163
- if (!sub) {
6164
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
6165
- process.exit(1);
6166
- }
6167
- if (!skipUpdate) {
6168
- await autoUpdateStaleSubscription({ timeout: updateTimeout });
6169
- }
6170
- if (!daemonEnabled) {
6171
- if (hasRootResidue()) {
6172
- console.error(`${colors.red("\u9519\u8BEF:")} \u5B58\u5728\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559\u8FDB\u7A0B/\u6587\u4EF6`);
6173
- console.error(`\u8BF7\u5148\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo && sudo rm -f ${PATHS.pidFile}`);
6174
- console.error("\u6216\u5207\u6362\u5230 TUN \u6A21\u5F0F\u542F\u52A8\uFF08\u81EA\u52A8\u6E05\u7406\uFF09: mihomo start tun");
6175
- process.exit(1);
6176
- }
6177
- const status = getStatus();
6178
- const hasProcess = status.running || status.allProcesses.length > 0;
6179
- if (hasProcess) {
6180
- const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
6181
- console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
6182
- }
6183
- handleStopResult(stop());
6184
- if (hasProcess) {
6185
- console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
6186
- `);
6187
- }
6235
+ viewLogWithTail(logPath, { follow: false, lines });
6236
+ return;
6188
6237
  }
6189
- let configInfo;
6190
- try {
6191
- configInfo = prepareConfigForStart(targetMode, sub.name);
6192
- } catch (e) {
6193
- console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
6194
- process.exit(1);
6238
+ const logs = listLogs();
6239
+ const all = [];
6240
+ if (logs.current) all.push(logs.current);
6241
+ all.push(...logs.archives);
6242
+ if (all.length === 0) {
6243
+ console.log("\u6682\u65E0\u65E5\u5FD7");
6244
+ return;
6195
6245
  }
6196
- const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
6197
- console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
6198
- try {
6199
- const pid = await launchOrRestart(targetMode);
6200
- const label = daemonEnabled ? "\u5DF2\u542F\u52A8 (\u4FDD\u6D3B)" : "\u5DF2\u542F\u52A8";
6201
- console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""}`);
6202
- } catch (e) {
6203
- const msg = e.message;
6204
- const lines = msg.split("\n");
6205
- console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${lines[0]}`);
6206
- if (lines.length > 1) {
6207
- for (const line of lines.slice(1)) console.error(line);
6246
+ console.log("");
6247
+ console.log("\u65E5\u5FD7\u5217\u8868:");
6248
+ console.log("");
6249
+ let archiveCounter = 0;
6250
+ for (const log of all) {
6251
+ let num;
6252
+ if (log.isCurrent) {
6253
+ num = " 0";
6254
+ } else {
6255
+ archiveCounter++;
6256
+ num = archiveCounter < 10 ? ` ${archiveCounter}` : `${archiveCounter}`;
6208
6257
  }
6209
- process.exit(1);
6210
- }
6211
- const cleanThreshold = isGithubUrl(sub.url) ? AUTO_CLEAN_THRESHOLD_GITHUB : AUTO_CLEAN_THRESHOLD;
6212
- if (!skipClean && configInfo.proxies > cleanThreshold) {
6213
- const cache = readSubscriptionCache();
6214
- const lastCleanAt = cache[sub.name]?.last_auto_clean_at;
6215
- const withinCooldown = !!lastCleanAt && Date.now() - new Date(lastCleanAt).getTime() < AUTO_CLEAN_COOLDOWN_HOURS * 60 * 60 * 1e3;
6216
- if (!withinCooldown) {
6217
- console.log("");
6218
- console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${cleanThreshold}\uFF0C\u81EA\u52A8\u6E05\u7406\uFF08${AUTO_CLEAN_COOLDOWN_HOURS}h \u5185\u4EC5\u4E00\u6B21\uFF0C--no-clean \u8DF3\u8FC7\uFF09...`);
6219
- console.log("");
6220
- await sleep(1e3);
6221
- const progress = createProgressPrinter(rounds);
6222
- const cleanResult = await autoCleanSubscription(sub.name, {
6223
- timeout,
6224
- concurrency,
6225
- rounds,
6226
- onResult: progress.onResult,
6227
- onRetryRound: progress.onRetryRound
6228
- });
6229
- progress.finish();
6230
- console.log(formatTestSummary(cleanResult.summary));
6231
- if (cleanResult.skipped) {
6232
- console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
6233
- } else if (cleanResult.removedProxies > 0) {
6234
- console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
6235
- console.log("");
6236
- console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
6237
- if (!daemonEnabled) handleStopResult(stop());
6238
- try {
6239
- configInfo = prepareConfigForStart(targetMode, sub.name);
6240
- const pid = await launchOrRestart(targetMode);
6241
- console.log(`${colors.green("\u5DF2\u91CD\u542F")}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
6242
- } catch (e) {
6243
- console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
6244
- process.exit(1);
6245
- }
6246
- }
6247
- saveSubscriptionCache(sub.name, { last_auto_clean_at: (/* @__PURE__ */ new Date()).toISOString() });
6258
+ const time = formatDate(log.mtime);
6259
+ const size = formatBytes(log.size);
6260
+ const name = log.isCurrent ? "mihomo.log (\u5F53\u524D\u8FD0\u884C\u4E2D)" : log.name;
6261
+ console.log(` ${num}. ${name}`);
6262
+ console.log(` \u65F6\u95F4: ${time} \u5927\u5C0F: ${size}`);
6263
+ if (!log.isCurrent) {
6264
+ console.log(` \u67E5\u770B: mihomo logs ${archiveCounter} \u6216 mihomo logs ${archiveCounter} -o`);
6248
6265
  }
6266
+ console.log("");
6249
6267
  }
6250
- printStatus();
6268
+ console.log("\u7528\u6CD5:");
6269
+ console.log(" mihomo logs 0 # \u67E5\u770B\u5F53\u524D\u65E5\u5FD7 (\u6700\u540E 100 \u884C)");
6270
+ console.log(" mihomo logs 1 # \u67E5\u770B\u7B2C 1 \u4E2A\u5F52\u6863\u65E5\u5FD7\uFF08\u6700\u65B0\uFF09");
6271
+ console.log(" mihomo logs 1 -n 200 # \u67E5\u770B 200 \u884C");
6272
+ console.log(" mihomo logs 1 -o # \u7528\u7CFB\u7EDF\u9ED8\u8BA4\u7A0B\u5E8F\u6253\u5F00");
6273
+ console.log("");
6251
6274
  }
6252
6275
 
6253
6276
  // src/commands/overwrite.ts
6277
+ import path7 from "path";
6254
6278
  function printOverwriteList() {
6255
6279
  const info = listOverwriteFile();
6256
6280
  const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
@@ -6282,48 +6306,30 @@ function printOverwriteList() {
6282
6306
  console.log("\u7981\u7528\u8986\u5199: mihomo ow off");
6283
6307
  console.log("");
6284
6308
  }
6285
- async function cmdOverwrite(args) {
6286
- const action = args?.[1];
6287
- const currentMode = getRuntimeMode();
6288
- const restartNeeded = isRestartNeededOnChange();
6289
- if (action === "on" || action === "enable") {
6290
- if (isOverwriteEnabled()) {
6291
- console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
6292
- console.log("");
6293
- printOverwriteList();
6294
- return;
6295
- }
6296
- setOverwriteEnabled(true);
6297
- console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
6298
- if (restartNeeded) {
6299
- console.log("");
6300
- await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
6301
- return;
6302
- }
6309
+ async function setOverwrite(enabled, args) {
6310
+ if (isOverwriteEnabled() === enabled) {
6311
+ console.log(`\u8986\u5199\u914D\u7F6E\u5DF2\u662F${enabled ? "\u542F\u7528" : "\u7981\u7528"}\u72B6\u6001`);
6303
6312
  console.log("");
6304
6313
  printOverwriteList();
6305
6314
  return;
6306
6315
  }
6307
- if (action === "off" || action === "disable") {
6308
- if (!isOverwriteEnabled()) {
6309
- console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u7981\u7528\u72B6\u6001");
6316
+ setOverwriteEnabled(enabled);
6317
+ console.log(`\u5DF2${enabled ? "\u542F\u7528" : "\u7981\u7528"}\u8986\u5199\u914D\u7F6E`);
6318
+ if (await restartToApply(args)) return;
6319
+ console.log("");
6320
+ printOverwriteList();
6321
+ }
6322
+ var SUBCOMMANDS3 = [
6323
+ { name: "on", aliases: ["enable"], handler: (args) => setOverwrite(true, args) },
6324
+ { name: "off", aliases: ["disable"], handler: (args) => setOverwrite(false, args) }
6325
+ ];
6326
+ async function cmdOverwrite(args) {
6327
+ await dispatchSubcommand(args, SUBCOMMANDS3, {
6328
+ fallback: () => {
6310
6329
  console.log("");
6311
6330
  printOverwriteList();
6312
- return;
6313
6331
  }
6314
- setOverwriteEnabled(false);
6315
- console.log("\u5DF2\u7981\u7528\u8986\u5199\u914D\u7F6E");
6316
- if (restartNeeded) {
6317
- console.log("");
6318
- await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
6319
- return;
6320
- }
6321
- console.log("");
6322
- printOverwriteList();
6323
- return;
6324
- }
6325
- console.log("");
6326
- printOverwriteList();
6332
+ });
6327
6333
  }
6328
6334
 
6329
6335
  // src/commands/reset.ts
@@ -6433,10 +6439,7 @@ async function cmdReset(args) {
6433
6439
  const KNOWN_FLAGS = /* @__PURE__ */ new Set(["--full", "--yes", "-y"]);
6434
6440
  const unknownFlags = flags.filter((f) => !KNOWN_FLAGS.has(f));
6435
6441
  if (unknownFlags.length > 0) {
6436
- console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u9009\u9879: ${unknownFlags.join(", ")}`);
6437
- console.log("");
6438
- console.log("\u53EF\u7528\u9009\u9879: --full\uFF08\u5220\u5168\u90E8\uFF09, -y/--yes\uFF08\u8DF3\u8FC7\u786E\u8BA4\uFF09");
6439
- process.exit(1);
6442
+ throw new CliError(`\u672A\u77E5\u7684\u9009\u9879: ${unknownFlags.join(", ")}`, { hint: ["", "\u53EF\u7528\u9009\u9879: --full\uFF08\u5220\u5168\u90E8\uFF09, -y/--yes\uFF08\u8DF3\u8FC7\u786E\u8BA4\uFF09"] });
6440
6443
  }
6441
6444
  const fullReset = flags.includes("--full");
6442
6445
  const skipConfirm = flags.includes("--yes") || flags.includes("-y");
@@ -6446,16 +6449,18 @@ async function cmdReset(args) {
6446
6449
  } else if (names.length > 0) {
6447
6450
  const { matched, unmatched } = resolveResetTargets(names);
6448
6451
  if (unmatched.length > 0) {
6449
- console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u91CD\u7F6E\u76EE\u6807: ${unmatched.join(", ")}`);
6450
- console.log("");
6451
- console.log(`\u53EF\u7528\u76EE\u6807: ${RESET_TARGETS.map((t) => t.aliases[0]).join(", ")}`);
6452
- console.log("");
6453
- console.log("\u793A\u4F8B:");
6454
- console.log(" mihomo reset sub log # \u5220\u9664\u8BA2\u9605\u548C\u65E5\u5FD7");
6455
- console.log(" mihomo reset kernel # \u53EA\u5220\u5185\u6838");
6456
- console.log(" mihomo reset --full # \u5220\u9664\u5168\u90E8");
6457
- console.log(" mihomo reset # \u5220\u9664\u5168\u90E8\uFF08\u4FDD\u7559\u8BBE\u7F6E\u3001\u5185\u6838\u3001\u8986\u5199\uFF09");
6458
- process.exit(1);
6452
+ throw new CliError(`\u672A\u77E5\u7684\u91CD\u7F6E\u76EE\u6807: ${unmatched.join(", ")}`, {
6453
+ hint: [
6454
+ "",
6455
+ `\u53EF\u7528\u76EE\u6807: ${RESET_TARGETS.map((t) => t.aliases[0]).join(", ")}`,
6456
+ "",
6457
+ "\u793A\u4F8B:",
6458
+ " mihomo reset sub log # \u5220\u9664\u8BA2\u9605\u548C\u65E5\u5FD7",
6459
+ " mihomo reset kernel # \u53EA\u5220\u5185\u6838",
6460
+ " mihomo reset --full # \u5220\u9664\u5168\u90E8",
6461
+ " mihomo reset # \u5220\u9664\u5168\u90E8\uFF08\u4FDD\u7559\u8BBE\u7F6E\u3001\u5185\u6838\u3001\u8986\u5199\uFF09"
6462
+ ]
6463
+ });
6459
6464
  }
6460
6465
  targets = matched;
6461
6466
  } else {
@@ -6664,21 +6669,18 @@ function githubRepoUrl(rawUrl) {
6664
6669
  function resolveTestTarget(args) {
6665
6670
  const subs = getSubscriptions();
6666
6671
  if (subs.length === 0) {
6667
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
6668
- process.exit(1);
6672
+ throw new CliError("\u6CA1\u6709\u8BA2\u9605");
6669
6673
  }
6670
6674
  const nameArg = getNonFlagArg(args, 2);
6671
6675
  const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
6672
6676
  const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
6673
6677
  let target;
6674
6678
  if (nameArg) {
6675
- const matches = findSubscriptionFuzzy(subs, nameArg);
6676
- target = pickSingleSubscription(matches, nameArg);
6679
+ target = resolveSubscription(subs, nameArg);
6677
6680
  } else {
6678
6681
  const activeSub = getActiveSubscription();
6679
6682
  if (!activeSub) {
6680
- console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
6681
- process.exit(1);
6683
+ throw new CliError("\u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
6682
6684
  }
6683
6685
  target = activeSub;
6684
6686
  }
@@ -6739,274 +6741,231 @@ function printSubscriptionList() {
6739
6741
  console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
6740
6742
  console.log("");
6741
6743
  }
6742
- async function cmdSubscription(args) {
6743
- const action = args[1];
6744
- if (!action || action === "list") {
6745
- printSubscriptionList();
6746
- return;
6744
+ async function subAdd(args) {
6745
+ const url = args[2]?.trim();
6746
+ const name = args[3] || "default";
6747
+ if (!url) {
6748
+ throw new CliError("\u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
6747
6749
  }
6748
- if (action === "add") {
6749
- const url = args[2]?.trim();
6750
- const name = args[3] || "default";
6751
- if (!url) {
6752
- console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
6753
- process.exit(1);
6754
- }
6755
- if (isMultiUrl(url)) {
6756
- const urls = splitUrls(url);
6757
- if (urls.length === 0) {
6758
- console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
6759
- process.exit(1);
6760
- }
6761
- for (const u of urls) {
6762
- if (!isValidHttpUrl(u)) {
6763
- console.error(`\u9519\u8BEF: \u65E0\u6548\u7684 URL: ${u}`);
6764
- process.exit(1);
6765
- }
6766
- }
6767
- const normalizedUrl = urls.join(",");
6768
- console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
6769
- try {
6770
- addSubscription(normalizedUrl, name);
6771
- setDefaultSubscription(name);
6772
- const info = await downloadMergedSubscription(urls, name);
6773
- console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
6774
- } catch (e) {
6775
- removeSubscription(name);
6776
- console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
6777
- process.exit(1);
6778
- }
6779
- } else {
6780
- if (!isValidHttpUrl(url)) {
6781
- console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL\uFF08\u9700\u4EE5 http:// \u6216 https:// \u5F00\u5934\uFF09");
6782
- process.exit(1);
6783
- }
6784
- console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
6785
- try {
6786
- addSubscription(url, name);
6787
- setDefaultSubscription(name);
6788
- const info = await downloadSubscription(url, name);
6789
- const repoUrl = githubRepoUrl(url);
6790
- if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
6791
- console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
6792
- } catch (e) {
6793
- removeSubscription(name);
6794
- console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
6795
- process.exit(1);
6796
- }
6750
+ if (isMultiUrl(url)) {
6751
+ const urls = splitUrls(url);
6752
+ if (urls.length === 0) {
6753
+ throw new CliError("\u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
6797
6754
  }
6798
- console.log("");
6799
- printSubscriptionList();
6800
- return;
6801
- }
6802
- if (action === "update") {
6803
- const name = args[2];
6804
- const subs = getSubscriptions();
6805
- if (subs.length === 0) {
6806
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
6807
- process.exit(1);
6808
- }
6809
- if (!name) {
6810
- console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
6811
- const results = await Promise.all(subs.map((sub) => tryUpdateOne(sub)));
6812
- let ok = 0;
6813
- for (const r of results) {
6814
- if (r.success) ok++;
6815
- printUpdateResult(r);
6755
+ for (const u of urls) {
6756
+ if (!isValidHttpUrl(u)) {
6757
+ throw new CliError(`\u65E0\u6548\u7684 URL: ${u}`);
6816
6758
  }
6817
- if (ok === 0) process.exit(1);
6818
- console.log("");
6819
- printRestartHintIfRunning();
6820
- printSubscriptionList();
6821
- return;
6822
6759
  }
6823
- const matches = findSubscriptionFuzzy(subs, name);
6824
- const target = pickSingleSubscription(matches, name);
6825
- console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
6826
- const result = await tryUpdateOne(target);
6827
- if (!result.success) {
6828
- console.error(`\u66F4\u65B0\u5931\u8D25: ${(result.error || "").split("\n")[0]}`);
6829
- process.exit(1);
6760
+ const normalizedUrl = urls.join(",");
6761
+ console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
6762
+ try {
6763
+ addSubscription(normalizedUrl, name);
6764
+ setDefaultSubscription(name);
6765
+ const info = await downloadMergedSubscription(urls, name);
6766
+ console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
6767
+ } catch (e) {
6768
+ removeSubscription(name);
6769
+ throw new CliError(e.message, { label: "\u6DFB\u52A0\u5931\u8D25" });
6830
6770
  }
6831
- console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
6832
- console.log("");
6833
- printRestartHintIfRunning();
6834
- printSubscriptionList();
6835
- return;
6836
- }
6837
- if (action === "use") {
6838
- const name = args[2];
6839
- const subs = getSubscriptions();
6840
- if (!name) {
6841
- console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
6842
- if (subs.length > 0) {
6843
- console.log("\n\u53EF\u7528\u8BA2\u9605:");
6844
- for (const s of subs) console.log(` ${s.name}`);
6845
- }
6846
- process.exit(1);
6847
- }
6848
- const matches = findSubscriptionFuzzy(subs, name);
6849
- const target = pickSingleSubscription(matches, name);
6850
- const currentDefault = getActiveSubscription();
6851
- const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
6852
- if (isAlreadyDefault) {
6853
- console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
6854
- console.log("");
6855
- printSubscriptionList();
6856
- return;
6771
+ } else {
6772
+ if (!isValidHttpUrl(url)) {
6773
+ throw new CliError("\u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL\uFF08\u9700\u4EE5 http:// \u6216 https:// \u5F00\u5934\uFF09");
6857
6774
  }
6858
- const currentMode = getRuntimeMode();
6859
- const restartNeeded = isRestartNeededOnChange();
6860
- const success = setDefaultSubscription(target.name);
6861
- if (success) {
6862
- console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
6863
- } else {
6864
- console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
6865
- process.exit(1);
6775
+ console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
6776
+ try {
6777
+ addSubscription(url, name);
6778
+ setDefaultSubscription(name);
6779
+ const info = await downloadSubscription(url, name);
6780
+ const repoUrl = githubRepoUrl(url);
6781
+ if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
6782
+ console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
6783
+ } catch (e) {
6784
+ removeSubscription(name);
6785
+ throw new CliError(e.message, { label: "\u6DFB\u52A0\u5931\u8D25" });
6866
6786
  }
6867
- if (restartNeeded) {
6868
- console.log("");
6869
- await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
6870
- return;
6787
+ }
6788
+ console.log("");
6789
+ printSubscriptionList();
6790
+ }
6791
+ async function subUpdate(args) {
6792
+ const name = args[2];
6793
+ const subs = getSubscriptions();
6794
+ if (subs.length === 0) {
6795
+ throw new CliError("\u6CA1\u6709\u8BA2\u9605");
6796
+ }
6797
+ if (!name) {
6798
+ console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
6799
+ const results = await Promise.all(subs.map((sub) => tryUpdateOne(sub)));
6800
+ let ok = 0;
6801
+ for (const r of results) {
6802
+ if (r.success) ok++;
6803
+ printUpdateResult(r);
6871
6804
  }
6805
+ if (ok === 0) throw new CliError("\u5168\u90E8\u8BA2\u9605\u66F4\u65B0\u5931\u8D25");
6872
6806
  console.log("");
6807
+ printRestartHintIfRunning();
6873
6808
  printSubscriptionList();
6874
6809
  return;
6875
6810
  }
6876
- if (action === "web" || action === "open") {
6877
- const name = args[2];
6878
- const subs = getSubscriptionsWithCache();
6879
- if (subs.length === 0) {
6880
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
6881
- process.exit(1);
6882
- }
6883
- let target;
6884
- if (name) {
6885
- const matches = findSubscriptionFuzzy(subs, name);
6886
- target = pickSingleSubscription(matches, name);
6887
- } else {
6888
- target = getActiveSubscription() || subs[0];
6889
- }
6890
- const cached = subs.find((s) => s.name === target.name);
6891
- let webPageUrl = cached?.web_page_url;
6892
- if (!webPageUrl) {
6893
- console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u67E5\u8BE2\u8BA2\u9605...");
6894
- try {
6895
- const info = isMultiUrl(target.url) ? await downloadMergedSubscription(splitUrls(target.url), target.name, void 0, false) : await downloadSubscription(target.url, target.name, void 0, false);
6896
- if (info.webPageUrl) {
6897
- webPageUrl = info.webPageUrl;
6898
- } else {
6899
- console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
6900
- process.exit(1);
6901
- }
6902
- } catch (e) {
6903
- console.error(`\u67E5\u8BE2\u5931\u8D25: ${e.message}`);
6904
- process.exit(1);
6905
- }
6906
- }
6907
- console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
6908
- const opened = openUrl(webPageUrl);
6909
- if (!opened) {
6910
- console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
6911
- }
6912
- return;
6811
+ const target = resolveSubscription(subs, name);
6812
+ console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
6813
+ const result = await tryUpdateOne(target);
6814
+ if (!result.success) {
6815
+ throw new CliError((result.error || "").split("\n")[0], { label: "\u66F4\u65B0\u5931\u8D25" });
6913
6816
  }
6914
- if (action === "remove" || action === "rm" || action === "delete") {
6915
- const name = args[2];
6916
- const subs = getSubscriptions();
6917
- if (!name) {
6918
- console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0");
6919
- if (subs.length > 0) {
6920
- console.log("\n\u53EF\u7528\u8BA2\u9605:");
6921
- for (const s of subs) console.log(` ${s.name}`);
6922
- }
6923
- process.exit(1);
6924
- }
6925
- const matches = findSubscriptionFuzzy(subs, name);
6926
- const target = pickSingleSubscription(matches, name);
6927
- const switchedTo = removeSubscription(target.name);
6928
- console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
6929
- if (switchedTo) {
6930
- console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
6931
- }
6817
+ console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
6818
+ console.log("");
6819
+ printRestartHintIfRunning();
6820
+ printSubscriptionList();
6821
+ }
6822
+ async function subUse(args) {
6823
+ const name = args[2];
6824
+ const subs = getSubscriptions();
6825
+ if (!name) {
6826
+ throw new CliError("\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0", {
6827
+ hint: subs.length > 0 ? ["", "\u53EF\u7528\u8BA2\u9605:", ...subs.map((s) => ` ${s.name}`)] : void 0
6828
+ });
6829
+ }
6830
+ const target = resolveSubscription(subs, name);
6831
+ const currentDefault = getActiveSubscription();
6832
+ const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
6833
+ if (isAlreadyDefault) {
6834
+ console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
6932
6835
  console.log("");
6933
6836
  printSubscriptionList();
6934
6837
  return;
6935
6838
  }
6936
- if (action === "clean") {
6937
- const { target, timeout, concurrency } = resolveTestTarget(args);
6938
- const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
6939
- console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
6940
- console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
6941
- console.log("");
6942
- const progress = createProgressPrinter(rounds);
6943
- const result = await withTestInstance(target.name, async (apiBase) => {
6944
- return autoCleanSubscription(target.name, {
6945
- timeout,
6946
- concurrency,
6947
- rounds,
6948
- apiBase,
6949
- onResult: progress.onResult,
6950
- onRetryRound: progress.onRetryRound
6951
- });
6952
- });
6953
- progress.finish();
6954
- console.log(formatTestSummary(result.summary));
6955
- if (result.skipped) {
6956
- console.log("");
6957
- console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
6958
- } else if (result.removedProxies > 0) {
6959
- console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
6960
- if (getRunningState().running) {
6961
- console.log("");
6962
- console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
6839
+ const success = setDefaultSubscription(target.name);
6840
+ if (!success) {
6841
+ throw new CliError(`\u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
6842
+ }
6843
+ console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
6844
+ if (await restartToApply(args)) return;
6845
+ console.log("");
6846
+ printSubscriptionList();
6847
+ }
6848
+ async function subWeb(args) {
6849
+ const name = args[2];
6850
+ const subs = getSubscriptionsWithCache();
6851
+ if (subs.length === 0) {
6852
+ throw new CliError("\u6CA1\u6709\u8BA2\u9605");
6853
+ }
6854
+ let target;
6855
+ if (name) {
6856
+ target = resolveSubscription(subs, name);
6857
+ } else {
6858
+ target = getActiveSubscription() || subs[0];
6859
+ }
6860
+ const cached = subs.find((s) => s.name === target.name);
6861
+ let webPageUrl = cached?.web_page_url;
6862
+ if (!webPageUrl) {
6863
+ console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u67E5\u8BE2\u8BA2\u9605...");
6864
+ try {
6865
+ const info = isMultiUrl(target.url) ? await downloadMergedSubscription(splitUrls(target.url), target.name, void 0, false) : await downloadSubscription(target.url, target.name, void 0, false);
6866
+ if (!info.webPageUrl) {
6867
+ throw new CliError("\u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
6963
6868
  }
6869
+ webPageUrl = info.webPageUrl;
6870
+ } catch (e) {
6871
+ if (e instanceof CliError) throw e;
6872
+ throw new CliError(e.message, { label: "\u67E5\u8BE2\u5931\u8D25" });
6964
6873
  }
6965
- return;
6966
6874
  }
6967
- if (action === "test") {
6968
- const { target, timeout, concurrency } = resolveTestTarget(args);
6969
- console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
6970
- console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
6971
- console.log("");
6972
- const progress = createProgressPrinter();
6973
- const summary = await withTestInstance(target.name, async (apiBase) => {
6974
- return testSubscriptionProxies(target.name, {
6975
- timeout,
6976
- concurrency,
6977
- apiBase,
6978
- onResult: progress.onResult
6979
- });
6980
- });
6981
- progress.finish();
6982
- console.log(formatTestSummary(summary));
6983
- return;
6875
+ console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
6876
+ const opened = openUrl(webPageUrl);
6877
+ if (!opened) {
6878
+ console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
6984
6879
  }
6985
- console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
6986
- console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
6987
- process.exit(1);
6988
6880
  }
6989
-
6990
- // src/commands/test.ts
6991
- function requireRunning() {
6992
- const state = getRunningState();
6993
- if (!state.running) {
6994
- const hint = state.daemon ? "mihomo daemon on" : "mihomo start";
6995
- console.error(`\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (${hint})`);
6996
- process.exit(1);
6881
+ function subRemove(args) {
6882
+ const name = args[2];
6883
+ const subs = getSubscriptions();
6884
+ if (!name) {
6885
+ throw new CliError("\u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0", {
6886
+ hint: subs.length > 0 ? ["", "\u53EF\u7528\u8BA2\u9605:", ...subs.map((s) => ` ${s.name}`)] : void 0
6887
+ });
6997
6888
  }
6889
+ const target = resolveSubscription(subs, name);
6890
+ const switchedTo = removeSubscription(target.name);
6891
+ console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
6892
+ if (switchedTo) {
6893
+ console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
6894
+ }
6895
+ console.log("");
6896
+ printSubscriptionList();
6998
6897
  }
6999
- function requireActiveSub() {
7000
- const activeSub = getActiveSubscription();
7001
- if (!activeSub) {
7002
- console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
7003
- process.exit(1);
6898
+ async function subClean(args) {
6899
+ const { target, timeout, concurrency } = resolveTestTarget(args);
6900
+ const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
6901
+ console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
6902
+ console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
6903
+ console.log("");
6904
+ const progress = createProgressPrinter(rounds);
6905
+ const result = await withTestInstance(target.name, async (apiBase) => {
6906
+ return autoCleanSubscription(target.name, {
6907
+ timeout,
6908
+ concurrency,
6909
+ rounds,
6910
+ apiBase,
6911
+ onResult: progress.onResult,
6912
+ onRetryRound: progress.onRetryRound
6913
+ });
6914
+ });
6915
+ progress.finish();
6916
+ console.log(formatTestSummary(result.summary));
6917
+ if (result.skipped) {
6918
+ console.log("");
6919
+ console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
6920
+ } else if (result.removedProxies > 0) {
6921
+ console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
6922
+ if (getRunningState().running) {
6923
+ console.log("");
6924
+ console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
6925
+ }
7004
6926
  }
7005
- return activeSub;
7006
6927
  }
6928
+ async function subTest(args) {
6929
+ const { target, timeout, concurrency } = resolveTestTarget(args);
6930
+ console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
6931
+ console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
6932
+ console.log("");
6933
+ const progress = createProgressPrinter();
6934
+ const summary = await withTestInstance(target.name, async (apiBase) => {
6935
+ return testSubscriptionProxies(target.name, {
6936
+ timeout,
6937
+ concurrency,
6938
+ apiBase,
6939
+ onResult: progress.onResult
6940
+ });
6941
+ });
6942
+ progress.finish();
6943
+ console.log(formatTestSummary(summary));
6944
+ }
6945
+ var SUBCOMMANDS4 = [
6946
+ { name: "list", handler: printSubscriptionList },
6947
+ { name: "add", handler: subAdd },
6948
+ { name: "update", handler: subUpdate },
6949
+ { name: "use", handler: subUse },
6950
+ { name: "web", aliases: ["open"], handler: subWeb },
6951
+ { name: "remove", aliases: ["rm", "delete"], handler: subRemove },
6952
+ { name: "clean", handler: subClean },
6953
+ { name: "test", handler: subTest }
6954
+ ];
6955
+ async function cmdSubscription(args) {
6956
+ await dispatchSubcommand(args, SUBCOMMANDS4, {
6957
+ // 无子命令 → 列表;未知子命令 → 报错
6958
+ fallback: printSubscriptionList,
6959
+ onUnknown: () => {
6960
+ throw new CliError("\u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4", { hint: "\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]" });
6961
+ }
6962
+ });
6963
+ }
6964
+
6965
+ // src/commands/test.ts
7007
6966
  async function cmdTest(args) {
7008
6967
  requireRunning();
7009
- const activeSub = requireActiveSub();
6968
+ const activeSub = requireActiveSubscription("\u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
7010
6969
  const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
7011
6970
  const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
7012
6971
  console.log(`\u6D4B\u8BD5 "${activeSub.name}" \u8282\u70B9\u8FDE\u901A\u6027...`);
@@ -7023,7 +6982,7 @@ async function cmdTest(args) {
7023
6982
  }
7024
6983
  async function cmdClean(args) {
7025
6984
  requireRunning();
7026
- const activeSub = requireActiveSub();
6985
+ const activeSub = requireActiveSubscription("\u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
7027
6986
  const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
7028
6987
  const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
7029
6988
  const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
@@ -7054,9 +7013,7 @@ async function cmdClean(args) {
7054
7013
  try {
7055
7014
  if (!daemonManaged) {
7056
7015
  if (hasRootResidue()) {
7057
- console.error(`${colors.red("\u9519\u8BEF:")} \u4E3B\u5B9E\u4F8B\u4EE5 root \u8FD0\u884C\uFF08TUN\uFF09\uFF0C\u505C\u6B62\u5B83\u9700\u8981 sudo`);
7058
- console.error("\u8BF7\u6539\u7528 mihomo sub clean\uFF08\u9694\u79BB\u5B9E\u4F8B\u6D4B\u901F\uFF0C\u65E0\u9700\u505C\u6B62\u4E3B\u5B9E\u4F8B\uFF09");
7059
- process.exit(1);
7016
+ throw new CliError("\u4E3B\u5B9E\u4F8B\u4EE5 root \u8FD0\u884C\uFF08TUN\uFF09\uFF0C\u505C\u6B62\u5B83\u9700\u8981 sudo", { hint: "\u8BF7\u6539\u7528 mihomo sub clean\uFF08\u9694\u79BB\u5B9E\u4F8B\u6D4B\u901F\uFF0C\u65E0\u9700\u505C\u6B62\u4E3B\u5B9E\u4F8B\uFF09" });
7060
7017
  }
7061
7018
  handleStopResult(stop());
7062
7019
  }
@@ -7065,8 +7022,8 @@ async function cmdClean(args) {
7065
7022
  const label = daemonManaged ? "\u5DF2\u91CD\u542F (\u4FDD\u6D3B)" : "\u5DF2\u91CD\u542F";
7066
7023
  console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
7067
7024
  } catch (e) {
7068
- console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
7069
- process.exit(1);
7025
+ if (e instanceof CliError) throw e;
7026
+ throw new CliError(e.message.split("\n")[0], { label: "\u91CD\u542F\u5931\u8D25" });
7070
7027
  }
7071
7028
  }
7072
7029
  }
@@ -7075,9 +7032,7 @@ async function cmdClean(args) {
7075
7032
  function cmdUI(args) {
7076
7033
  const uiName = args[1] || "zash";
7077
7034
  if (!Object.hasOwn(UI_URLS, uiName)) {
7078
- console.error(`\u9519\u8BEF: \u672A\u77E5\u7684 UI "${uiName}"`);
7079
- console.error("\u53EF\u7528 UI: zash (\u9ED8\u8BA4), dash, yacd");
7080
- process.exit(1);
7035
+ throw new CliError(`\u672A\u77E5\u7684 UI "${uiName}"`, { hint: "\u53EF\u7528 UI: zash (\u9ED8\u8BA4), dash, yacd" });
7081
7036
  }
7082
7037
  const url = UI_URLS[uiName];
7083
7038
  console.log(`\u6253\u5F00 Web UI: ${uiName}`);
@@ -7101,23 +7056,18 @@ async function cmdUpdate() {
7101
7056
  console.log("");
7102
7057
  console.log("\u6B63\u5728\u66F4\u65B0 mihomo-cli...");
7103
7058
  console.log("");
7104
- await new Promise((resolve) => {
7059
+ await new Promise((resolve, reject) => {
7105
7060
  const npm = spawn3("npm", ["install", "-g", "mihomo-cli"], { stdio: "inherit" });
7106
7061
  npm.on("close", (code) => {
7107
7062
  if (code === 0) {
7108
7063
  resolve();
7109
7064
  } else {
7110
- console.error("\u66F4\u65B0\u5931\u8D25\u3002\u82E5\u4E3A\u6743\u9650\u95EE\u9898\uFF08EACCES\uFF09\uFF0C\u53EF\u5C1D\u8BD5: sudo npm install -g mihomo-cli");
7111
- process.exit(code || 1);
7065
+ reject(new CliError("\u66F4\u65B0\u5931\u8D25\u3002\u82E5\u4E3A\u6743\u9650\u95EE\u9898\uFF08EACCES\uFF09\uFF0C\u53EF\u5C1D\u8BD5: sudo npm install -g mihomo-cli", { exitCode: code || 1 }));
7112
7066
  }
7113
7067
  });
7114
7068
  npm.on("error", (e) => {
7115
- if (e.message.includes("EACCES") || e.message.includes("permission")) {
7116
- console.error("\u6743\u9650\u4E0D\u8DB3\uFF0C\u53EF\u5C1D\u8BD5: sudo npm install -g mihomo-cli");
7117
- } else {
7118
- console.error(`\u6267\u884C\u5931\u8D25: ${e.message}`);
7119
- }
7120
- process.exit(1);
7069
+ const perm = e.message.includes("EACCES") || e.message.includes("permission");
7070
+ reject(perm ? new CliError("\u6743\u9650\u4E0D\u8DB3\uFF0C\u53EF\u5C1D\u8BD5: sudo npm install -g mihomo-cli") : new CliError(`\u6267\u884C\u5931\u8D25: ${e.message}`));
7121
7071
  });
7122
7072
  });
7123
7073
  try {
@@ -7377,14 +7327,20 @@ async function main() {
7377
7327
  const token = args[0].toLowerCase();
7378
7328
  const command = findCommand(token);
7379
7329
  if (!command) {
7380
- console.error(`\u672A\u77E5\u547D\u4EE4: ${token}`);
7381
- console.error('\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9');
7382
- process.exit(1);
7330
+ throw new CliError(`\u672A\u77E5\u547D\u4EE4: ${token}`, { hint: '\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9' });
7383
7331
  }
7384
7332
  await command.handler(command.rewrite ? command.rewrite(args) : args);
7385
7333
  }
7386
7334
  main().catch((e) => {
7387
- console.error(`\u9519\u8BEF: ${e.message}`);
7335
+ if (e instanceof CliError) {
7336
+ console.error(`${colors.red(`${e.label}:`)} ${e.message}`);
7337
+ for (const line of e.hint) console.error(line);
7338
+ runCleanup();
7339
+ process.exit(e.exitCode);
7340
+ }
7341
+ const err = e;
7342
+ console.error(`${colors.red("\u9519\u8BEF:")} ${err.message}`);
7343
+ if (err.stack) console.error(err.stack.split("\n").slice(1).join("\n"));
7388
7344
  runCleanup();
7389
7345
  process.exit(1);
7390
7346
  });