mihomo-cli 3.4.0 → 3.6.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 +33 -0
- package/README.md +5 -4
- package/dist/index.js +1058 -1003
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// src/colors.ts
|
|
4
|
+
var NO_COLOR = process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
|
|
5
|
+
function colorize(code, str) {
|
|
6
|
+
if (NO_COLOR) return String(str);
|
|
7
|
+
return `${code + String(str)}\x1B[0m`;
|
|
8
|
+
}
|
|
9
|
+
var colors = {
|
|
10
|
+
bold: (s) => colorize("\x1B[1m", s),
|
|
11
|
+
red: (s) => colorize("\x1B[31m", s),
|
|
12
|
+
green: (s) => colorize("\x1B[32m", s),
|
|
13
|
+
yellow: (s) => colorize("\x1B[33m", s),
|
|
14
|
+
cyan: (s) => colorize("\x1B[36m", s),
|
|
15
|
+
gray: (s) => colorize("\x1B[90m", s)
|
|
16
|
+
};
|
|
17
|
+
|
|
3
18
|
// src/config.ts
|
|
4
|
-
import { spawnSync
|
|
19
|
+
import { spawnSync } from "child_process";
|
|
5
20
|
import fs4 from "fs";
|
|
6
21
|
|
|
7
22
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
@@ -3040,6 +3055,11 @@ var CHOMPING_STRIP = CHOMPING_MODE.STRIP;
|
|
|
3040
3055
|
var CHOMPING_KEEP = CHOMPING_MODE.KEEP;
|
|
3041
3056
|
|
|
3042
3057
|
// src/constants.ts
|
|
3058
|
+
import { createRequire } from "module";
|
|
3059
|
+
var require2 = createRequire(import.meta.url);
|
|
3060
|
+
var pkg = require2("../package.json");
|
|
3061
|
+
var VERSION = pkg.version;
|
|
3062
|
+
var PKG_NAME = pkg.name;
|
|
3043
3063
|
var AVAILABLE_MIRRORS = ["v6.gh-proxy.org", "gh-proxy.org", "hk.gh-proxy.org", "cdn.gh-proxy.org"];
|
|
3044
3064
|
var DEFAULT_MIRROR = "https://v6.gh-proxy.org/";
|
|
3045
3065
|
var UI_URLS = {
|
|
@@ -3171,6 +3191,43 @@ function rmrf(dir) {
|
|
|
3171
3191
|
// src/settings.ts
|
|
3172
3192
|
import fs2 from "fs";
|
|
3173
3193
|
import path2 from "path";
|
|
3194
|
+
|
|
3195
|
+
// src/errors.ts
|
|
3196
|
+
var TimeoutError = class extends Error {
|
|
3197
|
+
constructor() {
|
|
3198
|
+
super("timeout");
|
|
3199
|
+
this.name = "TimeoutError";
|
|
3200
|
+
}
|
|
3201
|
+
};
|
|
3202
|
+
var CliError = class extends Error {
|
|
3203
|
+
hint;
|
|
3204
|
+
label;
|
|
3205
|
+
exitCode;
|
|
3206
|
+
constructor(message, options = {}) {
|
|
3207
|
+
super(message);
|
|
3208
|
+
this.name = "CliError";
|
|
3209
|
+
this.label = options.label ?? "\u9519\u8BEF";
|
|
3210
|
+
this.hint = options.hint === void 0 ? [] : Array.isArray(options.hint) ? options.hint : [options.hint];
|
|
3211
|
+
this.exitCode = options.exitCode ?? 1;
|
|
3212
|
+
}
|
|
3213
|
+
};
|
|
3214
|
+
function withTimeout(promise, ms) {
|
|
3215
|
+
return new Promise((resolve, reject) => {
|
|
3216
|
+
const timer = setTimeout(() => reject(new TimeoutError()), ms);
|
|
3217
|
+
promise.then(
|
|
3218
|
+
(v) => {
|
|
3219
|
+
clearTimeout(timer);
|
|
3220
|
+
resolve(v);
|
|
3221
|
+
},
|
|
3222
|
+
(e) => {
|
|
3223
|
+
clearTimeout(timer);
|
|
3224
|
+
reject(e);
|
|
3225
|
+
}
|
|
3226
|
+
);
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
// src/settings.ts
|
|
3174
3231
|
var settingsCache = null;
|
|
3175
3232
|
function readSettings() {
|
|
3176
3233
|
if (settingsCache !== null) return settingsCache;
|
|
@@ -3274,7 +3331,7 @@ function getSubscriptionsWithCache() {
|
|
|
3274
3331
|
var SAFE_NAME_RE = /^[\w\-\p{Unified_Ideograph}]{1,64}$/u;
|
|
3275
3332
|
function validateSubscriptionName(name) {
|
|
3276
3333
|
if (!name || !SAFE_NAME_RE.test(name)) {
|
|
3277
|
-
throw new
|
|
3334
|
+
throw new CliError(`\u8BA2\u9605\u540D\u79F0\u65E0\u6548: "${name}"\uFF0C\u53EA\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u3001\u77ED\u6A2A\u7EBF\u548C\u4E2D\u6587\uFF08\u6700\u957F 64 \u5B57\u7B26\uFF09`);
|
|
3278
3335
|
}
|
|
3279
3336
|
}
|
|
3280
3337
|
function addSubscription(url, name = "default") {
|
|
@@ -3282,7 +3339,7 @@ function addSubscription(url, name = "default") {
|
|
|
3282
3339
|
const settings = readSettings();
|
|
3283
3340
|
const subs = [...settings.subscriptions || []];
|
|
3284
3341
|
if (subs.some((s) => s.name === name)) {
|
|
3285
|
-
throw new
|
|
3342
|
+
throw new CliError(`\u8BA2\u9605 "${name}" \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E2A\u540D\u79F0\uFF08mihomo sub add <url> <\u540D\u79F0>\uFF09\uFF0C\u6216\u5148\u5220\u9664\uFF08mihomo sub remove ${name}\uFF09`);
|
|
3286
3343
|
}
|
|
3287
3344
|
subs.push({ name, url });
|
|
3288
3345
|
const updates = { subscriptions: subs };
|
|
@@ -3326,7 +3383,7 @@ function setDefaultSubscription(name) {
|
|
|
3326
3383
|
}
|
|
3327
3384
|
function getSubscriptionRawConfigPath(subName) {
|
|
3328
3385
|
if (!SAFE_NAME_RE.test(subName)) {
|
|
3329
|
-
throw new
|
|
3386
|
+
throw new CliError(`\u8BA2\u9605\u540D\u79F0\u65E0\u6548: "${subName}"`);
|
|
3330
3387
|
}
|
|
3331
3388
|
return path2.join(DIRS.subscriptions, `${subName}.yaml`);
|
|
3332
3389
|
}
|
|
@@ -3564,26 +3621,7 @@ function listOverwriteFile() {
|
|
|
3564
3621
|
}
|
|
3565
3622
|
|
|
3566
3623
|
// src/utils.ts
|
|
3567
|
-
import { spawnSync } from "child_process";
|
|
3568
|
-
import { createRequire } from "module";
|
|
3569
|
-
var require2 = createRequire(import.meta.url);
|
|
3570
|
-
var pkg = require2("../package.json");
|
|
3571
|
-
var VERSION = pkg.version;
|
|
3572
|
-
var MAX_RESPONSE_BYTES = 50 * 1024 * 1024;
|
|
3573
3624
|
var sleepBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
3574
|
-
var NO_COLOR = process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
|
|
3575
|
-
function colorize(code, str) {
|
|
3576
|
-
if (NO_COLOR) return String(str);
|
|
3577
|
-
return `${code + String(str)}\x1B[0m`;
|
|
3578
|
-
}
|
|
3579
|
-
var colors = {
|
|
3580
|
-
bold: (s) => colorize("\x1B[1m", s),
|
|
3581
|
-
red: (s) => colorize("\x1B[31m", s),
|
|
3582
|
-
green: (s) => colorize("\x1B[32m", s),
|
|
3583
|
-
yellow: (s) => colorize("\x1B[33m", s),
|
|
3584
|
-
cyan: (s) => colorize("\x1B[36m", s),
|
|
3585
|
-
gray: (s) => colorize("\x1B[90m", s)
|
|
3586
|
-
};
|
|
3587
3625
|
function sleepSync(ms) {
|
|
3588
3626
|
Atomics.wait(sleepBuf, 0, 0, ms);
|
|
3589
3627
|
}
|
|
@@ -3596,27 +3634,6 @@ function escapeRegExp(s) {
|
|
|
3596
3634
|
function shellQuote(s) {
|
|
3597
3635
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
3598
3636
|
}
|
|
3599
|
-
var TimeoutError = class extends Error {
|
|
3600
|
-
constructor() {
|
|
3601
|
-
super("timeout");
|
|
3602
|
-
this.name = "TimeoutError";
|
|
3603
|
-
}
|
|
3604
|
-
};
|
|
3605
|
-
function withTimeout(promise, ms) {
|
|
3606
|
-
return new Promise((resolve, reject) => {
|
|
3607
|
-
const timer = setTimeout(() => reject(new TimeoutError()), ms);
|
|
3608
|
-
promise.then(
|
|
3609
|
-
(v) => {
|
|
3610
|
-
clearTimeout(timer);
|
|
3611
|
-
resolve(v);
|
|
3612
|
-
},
|
|
3613
|
-
(e) => {
|
|
3614
|
-
clearTimeout(timer);
|
|
3615
|
-
reject(e);
|
|
3616
|
-
}
|
|
3617
|
-
);
|
|
3618
|
-
});
|
|
3619
|
-
}
|
|
3620
3637
|
function formatBytes(bytes) {
|
|
3621
3638
|
if (bytes === void 0 || bytes === null) return "\u672A\u77E5";
|
|
3622
3639
|
const num = Number(bytes);
|
|
@@ -3696,90 +3713,35 @@ function getNonFlagArg(args, startIdx, valueFlags = VALUE_FLAGS) {
|
|
|
3696
3713
|
}
|
|
3697
3714
|
return null;
|
|
3698
3715
|
}
|
|
3699
|
-
function
|
|
3700
|
-
if (
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
function isProcessCommandMatching(pid, needle) {
|
|
3709
|
-
if (!pid) return false;
|
|
3710
|
-
try {
|
|
3711
|
-
const result = spawnSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8", timeout: 5e3 });
|
|
3712
|
-
return (result.stdout || "").includes(needle);
|
|
3713
|
-
} catch {
|
|
3714
|
-
return false;
|
|
3715
|
-
}
|
|
3716
|
-
}
|
|
3717
|
-
function isProcessRoot(pid) {
|
|
3718
|
-
if (!pid) return false;
|
|
3719
|
-
try {
|
|
3720
|
-
const result = spawnSync("ps", ["-p", String(pid), "-o", "uid="], { encoding: "utf8", timeout: 5e3 });
|
|
3721
|
-
return (result.stdout || "").trim() === "0";
|
|
3722
|
-
} catch {
|
|
3723
|
-
return false;
|
|
3724
|
-
}
|
|
3725
|
-
}
|
|
3726
|
-
function createHttpClient(options = {}) {
|
|
3727
|
-
const { timeout = 6e4, secret } = options;
|
|
3728
|
-
const authHeaders = secret ? { Authorization: `Bearer ${secret}` } : {};
|
|
3729
|
-
return {
|
|
3730
|
-
async get(url, config) {
|
|
3731
|
-
const controller = new AbortController();
|
|
3732
|
-
const timer = setTimeout(() => controller.abort(), timeout);
|
|
3733
|
-
const signal = config?.signal ? AbortSignal.any([controller.signal, config.signal]) : controller.signal;
|
|
3734
|
-
try {
|
|
3735
|
-
const response = await fetch(url, {
|
|
3736
|
-
signal,
|
|
3737
|
-
headers: { "User-Agent": `mihomo-cli/${VERSION}`, ...authHeaders }
|
|
3738
|
-
});
|
|
3739
|
-
if (!response.ok) {
|
|
3740
|
-
const error = new Error(`HTTP ${response.status}`);
|
|
3741
|
-
error.response = { status: response.status };
|
|
3742
|
-
try {
|
|
3743
|
-
error.response.data = await response.json();
|
|
3744
|
-
} catch {
|
|
3745
|
-
}
|
|
3746
|
-
throw error;
|
|
3747
|
-
}
|
|
3748
|
-
const declaredLen = Number(response.headers.get("content-length"));
|
|
3749
|
-
if (Number.isFinite(declaredLen) && declaredLen > MAX_RESPONSE_BYTES) {
|
|
3750
|
-
throw new Error(`\u54CD\u5E94\u4F53\u8FC7\u5927\uFF08${formatBytes(declaredLen)}\uFF0C\u4E0A\u9650 ${formatBytes(MAX_RESPONSE_BYTES)}\uFF09`);
|
|
3751
|
-
}
|
|
3752
|
-
const text = await readBodyWithLimit(response, controller);
|
|
3753
|
-
const data = config?.responseType === "json" ? JSON.parse(text) : text;
|
|
3754
|
-
return { data, headers: response.headers, status: response.status };
|
|
3755
|
-
} finally {
|
|
3756
|
-
clearTimeout(timer);
|
|
3757
|
-
}
|
|
3716
|
+
function levenshtein(a, b) {
|
|
3717
|
+
if (a === b) return 0;
|
|
3718
|
+
if (a.length === 0) return b.length;
|
|
3719
|
+
if (b.length === 0) return a.length;
|
|
3720
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
3721
|
+
for (let i = 1; i <= a.length; i++) {
|
|
3722
|
+
const curr = [i];
|
|
3723
|
+
for (let j = 1; j <= b.length; j++) {
|
|
3724
|
+
curr[j] = Math.min(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
3758
3725
|
}
|
|
3759
|
-
|
|
3726
|
+
prev = curr;
|
|
3727
|
+
}
|
|
3728
|
+
return prev[b.length];
|
|
3760
3729
|
}
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
const
|
|
3764
|
-
const
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
if (total > MAX_RESPONSE_BYTES) {
|
|
3773
|
-
controller.abort();
|
|
3774
|
-
throw new Error(`\u54CD\u5E94\u4F53\u8D85\u8FC7\u5927\u5C0F\u4E0A\u9650\uFF08${formatBytes(MAX_RESPONSE_BYTES)}\uFF09`);
|
|
3775
|
-
}
|
|
3776
|
-
chunks.push(value);
|
|
3777
|
-
}
|
|
3730
|
+
function suggestSimilar(input, candidates) {
|
|
3731
|
+
const lower = input.toLowerCase();
|
|
3732
|
+
const scored = [];
|
|
3733
|
+
for (const cand of candidates) {
|
|
3734
|
+
const c = cand.toLowerCase();
|
|
3735
|
+
if (cand === input) continue;
|
|
3736
|
+
if (c.startsWith(lower)) {
|
|
3737
|
+
scored.push({ name: cand, score: 0, lenDiff: Math.abs(cand.length - input.length) });
|
|
3738
|
+
} else if (lower.length >= 3) {
|
|
3739
|
+
const d = levenshtein(lower, c);
|
|
3740
|
+
if (d <= 2) scored.push({ name: cand, score: d, lenDiff: Math.abs(cand.length - input.length) });
|
|
3778
3741
|
}
|
|
3779
|
-
} finally {
|
|
3780
|
-
reader.releaseLock();
|
|
3781
3742
|
}
|
|
3782
|
-
|
|
3743
|
+
scored.sort((a, b) => a.score - b.score || a.lenDiff - b.lenDiff);
|
|
3744
|
+
return scored.slice(0, 3).map((s) => s.name);
|
|
3783
3745
|
}
|
|
3784
3746
|
function normalizeMirrorUrl(val) {
|
|
3785
3747
|
if (!val) return null;
|
|
@@ -3822,15 +3784,6 @@ function parseMirrorArg(args) {
|
|
|
3822
3784
|
}
|
|
3823
3785
|
return { mirror: null, isOverride: false, type: "download" };
|
|
3824
3786
|
}
|
|
3825
|
-
function isProxyValid(proxy) {
|
|
3826
|
-
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
3827
|
-
if (!proxy.type) return false;
|
|
3828
|
-
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
3829
|
-
const pw = String(proxy.password || "");
|
|
3830
|
-
if (!/^[A-Za-z0-9+/\-_]+=*$/.test(pw) || pw.length < 20) return false;
|
|
3831
|
-
}
|
|
3832
|
-
return true;
|
|
3833
|
-
}
|
|
3834
3787
|
|
|
3835
3788
|
// src/config.ts
|
|
3836
3789
|
var SAFE_YAML_LOAD_OPTIONS = { maxAliases: 200 };
|
|
@@ -4085,7 +4038,7 @@ function getKernelVersion() {
|
|
|
4085
4038
|
}
|
|
4086
4039
|
if (kernelVersionCached) return kernelVersionCache;
|
|
4087
4040
|
try {
|
|
4088
|
-
const result =
|
|
4041
|
+
const result = spawnSync(PATHS.mihomoBinary, ["-v"], { encoding: "utf8", timeout: 5e3 });
|
|
4089
4042
|
const output = `${result.stdout || ""}${result.stderr || ""}`.trim();
|
|
4090
4043
|
if (output) {
|
|
4091
4044
|
const match = output.match(/v?[\d]+\.[\d]+\.[\d]+/);
|
|
@@ -4179,12 +4132,12 @@ function printVersion() {
|
|
|
4179
4132
|
}
|
|
4180
4133
|
|
|
4181
4134
|
// src/daemon.ts
|
|
4182
|
-
import { spawnSync as
|
|
4135
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
4183
4136
|
import fs6 from "fs";
|
|
4184
4137
|
import path5 from "path";
|
|
4185
4138
|
|
|
4186
4139
|
// src/process.ts
|
|
4187
|
-
import { spawn, spawnSync as
|
|
4140
|
+
import { spawn, spawnSync as spawnSync2 } from "child_process";
|
|
4188
4141
|
import fs5 from "fs";
|
|
4189
4142
|
import path4 from "path";
|
|
4190
4143
|
|
|
@@ -4221,6 +4174,34 @@ var SUDO_TIMEOUT_MS = 6e4;
|
|
|
4221
4174
|
var TUN_MODE_POST_WAIT_MS = 500;
|
|
4222
4175
|
var BATCH_KILL_THRESHOLD = 3;
|
|
4223
4176
|
var DEFAULT_LOG_RETENTION_DAYS = 7;
|
|
4177
|
+
var PS_TIMEOUT_MS = 5e3;
|
|
4178
|
+
function isProcessRunning(pid) {
|
|
4179
|
+
if (!pid) return false;
|
|
4180
|
+
try {
|
|
4181
|
+
const result = spawnSync2("ps", ["-p", String(pid), "-o", "pid="], { encoding: "utf8", timeout: PS_TIMEOUT_MS });
|
|
4182
|
+
return (result.stdout || "").trim().length > 0;
|
|
4183
|
+
} catch {
|
|
4184
|
+
return false;
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
4187
|
+
function isProcessCommandMatching(pid, needle) {
|
|
4188
|
+
if (!pid) return false;
|
|
4189
|
+
try {
|
|
4190
|
+
const result = spawnSync2("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8", timeout: PS_TIMEOUT_MS });
|
|
4191
|
+
return (result.stdout || "").includes(needle);
|
|
4192
|
+
} catch {
|
|
4193
|
+
return false;
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
function isProcessRoot(pid) {
|
|
4197
|
+
if (!pid) return false;
|
|
4198
|
+
try {
|
|
4199
|
+
const result = spawnSync2("ps", ["-p", String(pid), "-o", "uid="], { encoding: "utf8", timeout: PS_TIMEOUT_MS });
|
|
4200
|
+
return (result.stdout || "").trim() === "0";
|
|
4201
|
+
} catch {
|
|
4202
|
+
return false;
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4224
4205
|
var MAIN_INSTANCE_PATTERN = `${escapeRegExp(PATHS.mihomoBinary)}.*${escapeRegExp(PATHS.configFile)}`;
|
|
4225
4206
|
function clearRuntime() {
|
|
4226
4207
|
if (fs5.existsSync(DIRS.runtime)) {
|
|
@@ -4239,11 +4220,12 @@ function getPid() {
|
|
|
4239
4220
|
}
|
|
4240
4221
|
function isRunning() {
|
|
4241
4222
|
const pid = getPid();
|
|
4242
|
-
|
|
4223
|
+
if (!pid) return false;
|
|
4224
|
+
return isProcessRunning(pid) && isProcessCommandMatching(pid, PATHS.mihomoBinary);
|
|
4243
4225
|
}
|
|
4244
4226
|
function getMihomoPids() {
|
|
4245
4227
|
try {
|
|
4246
|
-
const result =
|
|
4228
|
+
const result = spawnSync2("pgrep", ["-f", MAIN_INSTANCE_PATTERN], { encoding: "utf8", timeout: 1e4 });
|
|
4247
4229
|
const output = (result.stdout || "").trim();
|
|
4248
4230
|
if (!output) return [];
|
|
4249
4231
|
return output.split("\n").filter(Boolean).map((p) => parseInt(p, 10)).filter((p) => Number.isInteger(p) && p > 0);
|
|
@@ -4283,7 +4265,7 @@ function clearPid() {
|
|
|
4283
4265
|
if (!fs5.existsSync(PATHS.pidFile)) return;
|
|
4284
4266
|
if (isPidFileOwnedByRoot()) {
|
|
4285
4267
|
try {
|
|
4286
|
-
|
|
4268
|
+
spawnSync2("sudo", ["rm", "-f", PATHS.pidFile], { stdio: "inherit", timeout: 1e4 });
|
|
4287
4269
|
} catch {
|
|
4288
4270
|
}
|
|
4289
4271
|
} else {
|
|
@@ -4305,14 +4287,14 @@ function killAllMihomo(forceSudo = false) {
|
|
|
4305
4287
|
const pattern = MAIN_INSTANCE_PATTERN;
|
|
4306
4288
|
if (forceSudo) {
|
|
4307
4289
|
try {
|
|
4308
|
-
|
|
4290
|
+
spawnSync2("sudo", ["pkill", "-9", "-f", pattern], { stdio: "inherit", timeout: 15e3 });
|
|
4309
4291
|
return true;
|
|
4310
4292
|
} catch {
|
|
4311
4293
|
return false;
|
|
4312
4294
|
}
|
|
4313
4295
|
} else {
|
|
4314
4296
|
try {
|
|
4315
|
-
|
|
4297
|
+
spawnSync2("pkill", ["-9", "-f", pattern], { timeout: 1e4 });
|
|
4316
4298
|
return true;
|
|
4317
4299
|
} catch {
|
|
4318
4300
|
return false;
|
|
@@ -4408,7 +4390,7 @@ exit 2
|
|
|
4408
4390
|
}
|
|
4409
4391
|
function getProcessInfo(pid) {
|
|
4410
4392
|
try {
|
|
4411
|
-
const result =
|
|
4393
|
+
const result = spawnSync2("ps", ["-p", String(pid), "-o", "rss="], { encoding: "utf8", timeout: 5e3 });
|
|
4412
4394
|
const psOutput = (result.stdout || "").trim();
|
|
4413
4395
|
if (!psOutput) return null;
|
|
4414
4396
|
const rss = parseInt(psOutput, 10);
|
|
@@ -4442,11 +4424,11 @@ async function start(mode = "mixed") {
|
|
|
4442
4424
|
rotateAndCleanupLogs();
|
|
4443
4425
|
const binary = PATHS.mihomoBinary;
|
|
4444
4426
|
if (!fs5.existsSync(binary)) {
|
|
4445
|
-
throw new
|
|
4427
|
+
throw new CliError("\u672A\u627E\u5230 mihomo \u5185\u6838\uFF0C\u8BF7\u5148\u4E0B\u8F7D\u5185\u6838");
|
|
4446
4428
|
}
|
|
4447
4429
|
const configFile = PATHS.configFile;
|
|
4448
4430
|
if (!fs5.existsSync(configFile)) {
|
|
4449
|
-
throw new
|
|
4431
|
+
throw new CliError("\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605\u5E76\u542F\u52A8");
|
|
4450
4432
|
}
|
|
4451
4433
|
const staleState = checkStaleState();
|
|
4452
4434
|
if (isTunMode) {
|
|
@@ -4523,7 +4505,7 @@ async function startTunMode(staleState) {
|
|
|
4523
4505
|
}
|
|
4524
4506
|
console.log("TUN \u6A21\u5F0F\u9700\u8981 sudo \u6743\u9650...");
|
|
4525
4507
|
try {
|
|
4526
|
-
const result =
|
|
4508
|
+
const result = spawnSync2("sudo", [launchScript], { stdio: "inherit", timeout: SUDO_TIMEOUT_MS });
|
|
4527
4509
|
if (result.error) throw result.error;
|
|
4528
4510
|
if (result.status !== 0) {
|
|
4529
4511
|
const err = new Error("TUN \u542F\u52A8\u811A\u672C\u6267\u884C\u5931\u8D25");
|
|
@@ -4763,7 +4745,7 @@ function runSudoScript(scriptBody, opts) {
|
|
|
4763
4745
|
const scriptPath = path5.join(DIRS.runtime, opts.file);
|
|
4764
4746
|
fs6.writeFileSync(scriptPath, scriptBody, { mode: 448 });
|
|
4765
4747
|
try {
|
|
4766
|
-
const result =
|
|
4748
|
+
const result = spawnSync3("sudo", [scriptPath], { stdio: "inherit", timeout: SUDO_TIMEOUT_MS });
|
|
4767
4749
|
if (result.error) throw result.error;
|
|
4768
4750
|
if (result.status !== 0) {
|
|
4769
4751
|
if (result.status === 1) {
|
|
@@ -4797,10 +4779,10 @@ function isDaemonRunning(status) {
|
|
|
4797
4779
|
}
|
|
4798
4780
|
function enableDaemon() {
|
|
4799
4781
|
if (!fs6.existsSync(PATHS.mihomoBinary)) {
|
|
4800
|
-
throw new
|
|
4782
|
+
throw new CliError("\u672A\u627E\u5230 mihomo \u5185\u6838\uFF0C\u8BF7\u5148\u4E0B\u8F7D\u5185\u6838");
|
|
4801
4783
|
}
|
|
4802
4784
|
if (!fs6.existsSync(PATHS.configFile)) {
|
|
4803
|
-
throw new
|
|
4785
|
+
throw new CliError("\u672A\u627E\u5230\u8FD0\u884C\u65F6\u914D\u7F6E\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
4804
4786
|
}
|
|
4805
4787
|
ensureDirs();
|
|
4806
4788
|
const stagePath = path5.join(DIRS.runtime, "daemon.plist.stage");
|
|
@@ -4905,25 +4887,86 @@ async function restartDaemon() {
|
|
|
4905
4887
|
cleanupOldLogs();
|
|
4906
4888
|
}
|
|
4907
4889
|
|
|
4908
|
-
// src/
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
}
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4890
|
+
// src/http.ts
|
|
4891
|
+
var MAX_RESPONSE_BYTES = 50 * 1024 * 1024;
|
|
4892
|
+
function createHttpClient(options = {}) {
|
|
4893
|
+
const { timeout = 6e4, secret } = options;
|
|
4894
|
+
const authHeaders = secret ? { Authorization: `Bearer ${secret}` } : {};
|
|
4895
|
+
return {
|
|
4896
|
+
async get(url, config) {
|
|
4897
|
+
const controller = new AbortController();
|
|
4898
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
4899
|
+
const signal = config?.signal ? AbortSignal.any([controller.signal, config.signal]) : controller.signal;
|
|
4900
|
+
try {
|
|
4901
|
+
const response = await fetch(url, {
|
|
4902
|
+
signal,
|
|
4903
|
+
headers: { "User-Agent": `mihomo-cli/${VERSION}`, ...authHeaders }
|
|
4904
|
+
});
|
|
4905
|
+
if (!response.ok) {
|
|
4906
|
+
const error = new Error(`HTTP ${response.status}`);
|
|
4907
|
+
error.response = { status: response.status };
|
|
4908
|
+
try {
|
|
4909
|
+
error.response.data = await response.json();
|
|
4910
|
+
} catch {
|
|
4911
|
+
}
|
|
4912
|
+
throw error;
|
|
4913
|
+
}
|
|
4914
|
+
const declaredLen = Number(response.headers.get("content-length"));
|
|
4915
|
+
if (Number.isFinite(declaredLen) && declaredLen > MAX_RESPONSE_BYTES) {
|
|
4916
|
+
throw new Error(`\u54CD\u5E94\u4F53\u8FC7\u5927\uFF08${formatBytes(declaredLen)}\uFF0C\u4E0A\u9650 ${formatBytes(MAX_RESPONSE_BYTES)}\uFF09`);
|
|
4917
|
+
}
|
|
4918
|
+
const text = await readBodyWithLimit(response, controller);
|
|
4919
|
+
const data = config?.responseType === "json" ? JSON.parse(text) : text;
|
|
4920
|
+
return { data, headers: response.headers, status: response.status };
|
|
4921
|
+
} finally {
|
|
4922
|
+
clearTimeout(timer);
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4925
|
+
};
|
|
4925
4926
|
}
|
|
4926
|
-
function
|
|
4927
|
+
async function readBodyWithLimit(response, controller) {
|
|
4928
|
+
if (!response.body) return response.text();
|
|
4929
|
+
const reader = response.body.getReader();
|
|
4930
|
+
const chunks = [];
|
|
4931
|
+
let total = 0;
|
|
4932
|
+
try {
|
|
4933
|
+
while (true) {
|
|
4934
|
+
const { done, value } = await reader.read();
|
|
4935
|
+
if (done) break;
|
|
4936
|
+
if (value) {
|
|
4937
|
+
total += value.byteLength;
|
|
4938
|
+
if (total > MAX_RESPONSE_BYTES) {
|
|
4939
|
+
controller.abort();
|
|
4940
|
+
throw new Error(`\u54CD\u5E94\u4F53\u8D85\u8FC7\u5927\u5C0F\u4E0A\u9650\uFF08${formatBytes(MAX_RESPONSE_BYTES)}\uFF09`);
|
|
4941
|
+
}
|
|
4942
|
+
chunks.push(value);
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4945
|
+
} finally {
|
|
4946
|
+
reader.releaseLock();
|
|
4947
|
+
}
|
|
4948
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
4949
|
+
}
|
|
4950
|
+
|
|
4951
|
+
// src/subscription.ts
|
|
4952
|
+
function isGithubUrl(url) {
|
|
4953
|
+
const githubRe = /github\.com|raw\.githubusercontent\.com/i;
|
|
4954
|
+
if (isMultiUrl(url)) {
|
|
4955
|
+
return splitUrls(url).every((u) => githubRe.test(u));
|
|
4956
|
+
}
|
|
4957
|
+
return githubRe.test(url);
|
|
4958
|
+
}
|
|
4959
|
+
function getDefaultUpdateInterval(url) {
|
|
4960
|
+
return isGithubUrl(url) ? DEFAULT_UPDATE_INTERVAL_HOURS_GITHUB : DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4961
|
+
}
|
|
4962
|
+
function resolveUpdateInterval(url, cachedInterval) {
|
|
4963
|
+
return cachedInterval && cachedInterval > 0 ? cachedInterval : getDefaultUpdateInterval(url);
|
|
4964
|
+
}
|
|
4965
|
+
var HTTP_CLIENT = createHttpClient({ timeout: 6e4 });
|
|
4966
|
+
function isMultiUrl(url) {
|
|
4967
|
+
return url.includes(",");
|
|
4968
|
+
}
|
|
4969
|
+
function isValidHttpUrl(url) {
|
|
4927
4970
|
try {
|
|
4928
4971
|
const u = new URL(url.trim());
|
|
4929
4972
|
return u.protocol === "http:" || u.protocol === "https:";
|
|
@@ -4937,7 +4980,7 @@ function splitUrls(url) {
|
|
|
4937
4980
|
function loadSubscriptionConfig(subName) {
|
|
4938
4981
|
const rawContent = readSubscriptionRawConfig(subName);
|
|
4939
4982
|
if (!rawContent) {
|
|
4940
|
-
throw new
|
|
4983
|
+
throw new CliError(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"\uFF0C\u8BF7\u5148\u66F4\u65B0\u8BA2\u9605\uFF08mihomo sub update ${subName}\uFF09`);
|
|
4941
4984
|
}
|
|
4942
4985
|
const raw = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
4943
4986
|
return {
|
|
@@ -5016,6 +5059,13 @@ function getActiveSubscription() {
|
|
|
5016
5059
|
}
|
|
5017
5060
|
return subs[0];
|
|
5018
5061
|
}
|
|
5062
|
+
function requireActiveSubscription(emptyMsg = "\u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605") {
|
|
5063
|
+
const sub = getActiveSubscription();
|
|
5064
|
+
if (!sub) {
|
|
5065
|
+
throw new CliError(emptyMsg);
|
|
5066
|
+
}
|
|
5067
|
+
return sub;
|
|
5068
|
+
}
|
|
5019
5069
|
function findSubscriptionFuzzy(subs, pattern) {
|
|
5020
5070
|
const lowerPattern = pattern.toLowerCase();
|
|
5021
5071
|
const exact = [];
|
|
@@ -5037,14 +5087,15 @@ function findSubscriptionFuzzy(subs, pattern) {
|
|
|
5037
5087
|
}
|
|
5038
5088
|
function pickSingleSubscription(subs, pattern) {
|
|
5039
5089
|
if (subs.length === 0) {
|
|
5040
|
-
|
|
5041
|
-
process.exit(1);
|
|
5090
|
+
throw new CliError(`\u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u8BA2\u9605`);
|
|
5042
5091
|
}
|
|
5043
5092
|
if (subs.length === 1) return subs[0];
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5093
|
+
throw new CliError("\u5339\u914D\u5230\u591A\u4E2A\u8BA2\u9605\uFF0C\u8BF7\u66F4\u7CBE\u786E\u6307\u5B9A", {
|
|
5094
|
+
hint: ["", "\u5339\u914D\u7684\u8BA2\u9605:", ...subs.map((s) => ` ${s.name}`)]
|
|
5095
|
+
});
|
|
5096
|
+
}
|
|
5097
|
+
function resolveSubscription(subs, pattern) {
|
|
5098
|
+
return pickSingleSubscription(findSubscriptionFuzzy(subs, pattern), pattern);
|
|
5048
5099
|
}
|
|
5049
5100
|
async function downloadSubscription(url, subName = "default", signal, persist = true) {
|
|
5050
5101
|
let response;
|
|
@@ -5145,7 +5196,7 @@ async function downloadMergedSubscription(urls, subName, signal, persist = true)
|
|
|
5145
5196
|
function prepareConfigForStart(mode, subName = "default") {
|
|
5146
5197
|
const rawContent = readSubscriptionRawConfig(subName);
|
|
5147
5198
|
if (!rawContent) {
|
|
5148
|
-
throw new
|
|
5199
|
+
throw new CliError(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605`);
|
|
5149
5200
|
}
|
|
5150
5201
|
const subUrl = getSubscriptions().find((s) => s.name === subName)?.url;
|
|
5151
5202
|
const buildResult = buildConfig(rawContent, mode, { subName, subUrl });
|
|
@@ -5414,144 +5465,461 @@ async function autoCleanSubscription(subName, options = {}) {
|
|
|
5414
5465
|
return { summary, removedProxies, updatedGroups, removedGroups, skipped };
|
|
5415
5466
|
}
|
|
5416
5467
|
|
|
5417
|
-
// src/
|
|
5418
|
-
function
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
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"));
|
|
5468
|
+
// src/runtime.ts
|
|
5469
|
+
function getRuntimeMode() {
|
|
5470
|
+
if (isDaemonEnabled()) return "mixed";
|
|
5471
|
+
return getConfigInfo()?.tun ? "tun" : "mixed";
|
|
5472
|
+
}
|
|
5473
|
+
function getRunningState() {
|
|
5474
|
+
if (isDaemonEnabled()) {
|
|
5475
|
+
const daemon = getDaemonStatus();
|
|
5476
|
+
return { running: isDaemonRunning(daemon), pid: daemon.pid, daemon: true };
|
|
5432
5477
|
}
|
|
5433
|
-
|
|
5478
|
+
const status = getStatus();
|
|
5479
|
+
return { running: status.running, pid: status.pid, daemon: false };
|
|
5434
5480
|
}
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
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("");
|
|
5481
|
+
function isRestartNeededOnChange() {
|
|
5482
|
+
return isDaemonEnabled() || getStatus().running;
|
|
5483
|
+
}
|
|
5484
|
+
async function launchOrRestart(mode) {
|
|
5485
|
+
if (isDaemonEnabled()) {
|
|
5486
|
+
await restartDaemon();
|
|
5465
5487
|
await sleep(DAEMON_BOOT_WAIT_MS);
|
|
5466
|
-
|
|
5467
|
-
|
|
5488
|
+
return getDaemonStatus().pid;
|
|
5489
|
+
}
|
|
5490
|
+
const result = await start(mode);
|
|
5491
|
+
return result.pid;
|
|
5492
|
+
}
|
|
5493
|
+
|
|
5494
|
+
// src/progress.ts
|
|
5495
|
+
var IS_TTY = process.stdout.isTTY === true;
|
|
5496
|
+
var BAR_WIDTH = 20;
|
|
5497
|
+
function createProgressPrinter(totalRounds = 1) {
|
|
5498
|
+
let alive = 0;
|
|
5499
|
+
let dead = 0;
|
|
5500
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
5501
|
+
function render(done, total) {
|
|
5502
|
+
if (!IS_TTY) return;
|
|
5503
|
+
const pct = Math.round(done / total * 100);
|
|
5504
|
+
const filled = Math.round(done / total * BAR_WIDTH);
|
|
5505
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
|
|
5506
|
+
process.stdout.write(`\r${bar} ${done}/${total} (${pct}%) | ${colors.green(`\u2713${alive}`)} ${colors.red(`\u2717${dead}`)}`);
|
|
5468
5507
|
}
|
|
5469
|
-
|
|
5470
|
-
|
|
5471
|
-
|
|
5508
|
+
return {
|
|
5509
|
+
onResult(result, index, total, round = 1) {
|
|
5510
|
+
if (resultMap.size === 0 && totalRounds > 1) {
|
|
5511
|
+
console.log(`--- \u7B2C 1 \u8F6E\u6D4B\u8BD5 (${total} \u4E2A\u8282\u70B9) ---`);
|
|
5512
|
+
}
|
|
5513
|
+
const prev = resultMap.get(result.name);
|
|
5514
|
+
if (prev) {
|
|
5515
|
+
if (prev.result.delay !== null) alive--;
|
|
5516
|
+
else dead--;
|
|
5517
|
+
}
|
|
5518
|
+
if (result.delay !== null) alive++;
|
|
5519
|
+
else dead++;
|
|
5520
|
+
resultMap.set(result.name, { result, round });
|
|
5521
|
+
render(index + 1, total);
|
|
5522
|
+
},
|
|
5523
|
+
onRetryRound(round, count) {
|
|
5524
|
+
if (IS_TTY) {
|
|
5525
|
+
process.stdout.write("\n");
|
|
5526
|
+
}
|
|
5527
|
+
console.log(`--- \u7B2C ${round} \u8F6E\u91CD\u8BD5 (${count} \u4E2A\u8282\u70B9) ---`);
|
|
5528
|
+
alive = 0;
|
|
5529
|
+
dead = 0;
|
|
5530
|
+
},
|
|
5531
|
+
finish() {
|
|
5532
|
+
if (IS_TTY) {
|
|
5533
|
+
process.stdout.write("\n");
|
|
5534
|
+
}
|
|
5535
|
+
console.log("");
|
|
5536
|
+
if (!IS_TTY) return;
|
|
5537
|
+
const entries = [...resultMap.values()];
|
|
5538
|
+
entries.sort((a, b) => a.result.name.localeCompare(b.result.name));
|
|
5539
|
+
const total = entries.length;
|
|
5540
|
+
console.log("\u8282\u70B9\u6700\u7EC8\u72B6\u6001:");
|
|
5541
|
+
for (let i = 0; i < entries.length; i++) {
|
|
5542
|
+
const { result, round } = entries[i];
|
|
5543
|
+
const prefix = `[${i + 1}/${total}]`;
|
|
5544
|
+
if (result.delay !== null) {
|
|
5545
|
+
const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
|
|
5546
|
+
const retryNote = round > 1 ? colors.gray(` (\u7B2C${round}\u8F6E\u901A\u8FC7)`) : "";
|
|
5547
|
+
console.log(`${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}${retryNote}`);
|
|
5548
|
+
} else {
|
|
5549
|
+
console.log(`${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5472
5552
|
console.log("");
|
|
5473
|
-
printDaemonStatus();
|
|
5474
|
-
return;
|
|
5475
5553
|
}
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5554
|
+
};
|
|
5555
|
+
}
|
|
5556
|
+
function formatCleanSummary(result) {
|
|
5557
|
+
const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
|
|
5558
|
+
if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
|
|
5559
|
+
if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
|
|
5560
|
+
return parts.join(", ");
|
|
5561
|
+
}
|
|
5562
|
+
function formatTestSummary(summary) {
|
|
5563
|
+
return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
|
|
5564
|
+
}
|
|
5565
|
+
|
|
5566
|
+
// src/commands/status.ts
|
|
5567
|
+
function printStatus() {
|
|
5568
|
+
const status = getStatus();
|
|
5569
|
+
const state = getRunningState();
|
|
5570
|
+
const info = getConfigInfo();
|
|
5571
|
+
const overwriteEnabled = isOverwriteEnabled();
|
|
5572
|
+
const overwriteFiles = listOverwriteFile().files;
|
|
5573
|
+
const activeSub = getActiveSubscription();
|
|
5574
|
+
const { running, pid, daemon: daemonManaged } = state;
|
|
5575
|
+
console.log("");
|
|
5576
|
+
let modeLabel = "";
|
|
5577
|
+
if (info) {
|
|
5578
|
+
modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
|
|
5579
|
+
}
|
|
5580
|
+
const statusText = running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
|
|
5581
|
+
console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}${modeLabel}`);
|
|
5582
|
+
console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
|
|
5583
|
+
if (pid) {
|
|
5584
|
+
console.log(`${colors.gray("PID: ")}${pid}`);
|
|
5585
|
+
if (!daemonManaged && status.processInfo) {
|
|
5586
|
+
console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
|
|
5482
5587
|
}
|
|
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
5588
|
}
|
|
5488
|
-
if (
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
|
|
5492
|
-
|
|
5589
|
+
if (info) {
|
|
5590
|
+
if (info.tun) {
|
|
5591
|
+
const extra = info.mixedPort ? `\uFF0C\u53E6\u76D1\u542C ${info.mixedPort}` : "";
|
|
5592
|
+
console.log(`${colors.gray("\u7AEF\u53E3: ")}TUN \u63A5\u7BA1${extra}`);
|
|
5593
|
+
} else if (info.mixedPort) {
|
|
5594
|
+
console.log(`${colors.gray("\u7AEF\u53E3: ")}${info.mixedPort}`);
|
|
5595
|
+
} else {
|
|
5596
|
+
const ports = [];
|
|
5597
|
+
if (info.httpPort) ports.push(`HTTP:${info.httpPort}`);
|
|
5598
|
+
if (info.socksPort) ports.push(`SOCKS:${info.socksPort}`);
|
|
5599
|
+
console.log(`${colors.gray("\u7AEF\u53E3: ")}${ports.length > 0 ? ports.join(", ") : "\u672A\u77E5"}`);
|
|
5600
|
+
}
|
|
5493
5601
|
}
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
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;
|
|
5602
|
+
if (activeSub) {
|
|
5603
|
+
let subLine = `${colors.gray("\u8BA2\u9605: ")}${activeSub.name}`;
|
|
5604
|
+
if (info) {
|
|
5605
|
+
subLine += ` (${formatProxySummary(info)})`;
|
|
5510
5606
|
}
|
|
5511
|
-
|
|
5512
|
-
const
|
|
5513
|
-
if (
|
|
5514
|
-
const
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${targetPath}`);
|
|
5607
|
+
console.log(subLine);
|
|
5608
|
+
const cached = getSubscriptionsWithCache().find((s) => s.name === activeSub.name);
|
|
5609
|
+
if (cached && (cached.download !== void 0 || cached.total !== void 0)) {
|
|
5610
|
+
const used = (cached.upload || 0) + (cached.download || 0);
|
|
5611
|
+
let trafficLine = `${colors.gray("\u6D41\u91CF: ")}${formatBytes(used)} / ${formatBytes(cached.total)}`;
|
|
5612
|
+
if (cached.total && cached.total > 0) {
|
|
5613
|
+
trafficLine += ` (${Math.min(used / cached.total * 100, 100).toFixed(1)}%)`;
|
|
5519
5614
|
}
|
|
5520
|
-
|
|
5615
|
+
console.log(trafficLine);
|
|
5521
5616
|
}
|
|
5522
|
-
|
|
5523
|
-
|
|
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
|
-
}
|
|
5617
|
+
if (cached?.expire !== void 0) {
|
|
5618
|
+
console.log(`${colors.gray("\u5230\u671F: ")}${formatTimestamp(cached.expire)}`);
|
|
5530
5619
|
}
|
|
5531
|
-
|
|
5532
|
-
|
|
5620
|
+
} else {
|
|
5621
|
+
console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
|
|
5622
|
+
}
|
|
5623
|
+
if (overwriteEnabled && overwriteFiles.length > 0) {
|
|
5624
|
+
const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "").replace(/\.ya?ml$/, "") || "\u4E3B\u6587\u4EF6").join(", ");
|
|
5625
|
+
console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (${names})`);
|
|
5626
|
+
} else if (overwriteEnabled) {
|
|
5627
|
+
console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (\u65E0\u6587\u4EF6)`);
|
|
5628
|
+
} else {
|
|
5629
|
+
console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
|
|
5630
|
+
}
|
|
5631
|
+
if (isDaemonEnabled()) {
|
|
5632
|
+
console.log(`${colors.gray("\u4FDD\u6D3B: ")}${colors.green("\u5DF2\u542F\u7528")} ${colors.gray("(\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F)")}`);
|
|
5533
5633
|
}
|
|
5534
5634
|
console.log("");
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
console.log(
|
|
5635
|
+
}
|
|
5636
|
+
|
|
5637
|
+
// src/commands/stop.ts
|
|
5638
|
+
function handleStopResult(result) {
|
|
5639
|
+
if (result.remaining && result.remaining.length > 0) {
|
|
5640
|
+
throw new CliError(result.remaining.join(", "), { label: "\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62", hint: "\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo" });
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
async function cmdStop() {
|
|
5644
|
+
if (isDaemonEnabled()) {
|
|
5645
|
+
console.log(colors.yellow("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF0C\u4EE3\u7406\u7531 launchd \u6258\u7BA1"));
|
|
5646
|
+
console.log("\u76F4\u63A5\u505C\u6B62\u4F1A\u88AB\u81EA\u52A8\u91CD\u65B0\u62C9\u8D77\uFF0C\u8BF7\u7528: mihomo daemon off");
|
|
5647
|
+
return;
|
|
5648
|
+
}
|
|
5649
|
+
const pids = getMihomoPids();
|
|
5650
|
+
if (pids.length === 0) {
|
|
5651
|
+
console.log(colors.yellow("\u4E0D\u5728\u8FD0\u884C"));
|
|
5652
|
+
return;
|
|
5653
|
+
}
|
|
5654
|
+
console.log(`\u505C\u6B62 ${pids.length} \u4E2A\u8FDB\u7A0B...`);
|
|
5655
|
+
handleStopResult(stop());
|
|
5656
|
+
console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
|
|
5657
|
+
}
|
|
5658
|
+
|
|
5659
|
+
// src/commands/start.ts
|
|
5660
|
+
async function cmdStart(args) {
|
|
5661
|
+
const modeToken = args[1] && !args[1].startsWith("-") ? args[1].toLowerCase() : void 0;
|
|
5662
|
+
if (modeToken !== void 0 && modeToken !== "tun" && modeToken !== "mixed") {
|
|
5663
|
+
throw new CliError(`\u672A\u77E5\u7684\u542F\u52A8\u6A21\u5F0F: ${args[1]}`, { hint: "\u7528\u6CD5: mihomo start [tun|mixed]\uFF08\u9ED8\u8BA4 mixed\uFF09" });
|
|
5664
|
+
}
|
|
5665
|
+
const targetMode = modeToken === "tun" ? "tun" : "mixed";
|
|
5666
|
+
if (!hasKernel()) {
|
|
5667
|
+
throw new CliError('\u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
|
|
5668
|
+
}
|
|
5669
|
+
const daemonEnabled = isDaemonEnabled();
|
|
5670
|
+
if (targetMode === "tun" && daemonEnabled) {
|
|
5671
|
+
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" });
|
|
5672
|
+
}
|
|
5673
|
+
const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
|
|
5674
|
+
const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
|
|
5675
|
+
const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
|
|
5676
|
+
const skipUpdate = hasFlag(args, "-s", "--no-update");
|
|
5677
|
+
const skipClean = hasFlag(args, "--no-clean");
|
|
5678
|
+
const updateTimeout = parseIntArg(args, "-u", "--update-timeout", DEFAULT_AUTO_UPDATE_TIMEOUT);
|
|
5679
|
+
const sub = requireActiveSubscription("\u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
5680
|
+
if (!skipUpdate) {
|
|
5681
|
+
await autoUpdateStaleSubscription({ timeout: updateTimeout });
|
|
5682
|
+
}
|
|
5683
|
+
if (!daemonEnabled) {
|
|
5684
|
+
if (hasRootResidue()) {
|
|
5685
|
+
throw new CliError("\u5B58\u5728\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559\u8FDB\u7A0B/\u6587\u4EF6", {
|
|
5686
|
+
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"]
|
|
5687
|
+
});
|
|
5688
|
+
}
|
|
5689
|
+
const status = getStatus();
|
|
5690
|
+
const hasProcess = status.running || status.allProcesses.length > 0;
|
|
5691
|
+
if (hasProcess) {
|
|
5692
|
+
const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
|
|
5693
|
+
console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
|
|
5694
|
+
}
|
|
5695
|
+
handleStopResult(stop());
|
|
5696
|
+
if (hasProcess) {
|
|
5697
|
+
console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
|
|
5698
|
+
`);
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
let configInfo;
|
|
5702
|
+
try {
|
|
5703
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
5704
|
+
} catch (e) {
|
|
5705
|
+
if (e instanceof CliError) throw e;
|
|
5706
|
+
throw new CliError(e.message, { label: "\u914D\u7F6E\u9519\u8BEF" });
|
|
5707
|
+
}
|
|
5708
|
+
const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
|
|
5709
|
+
console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
|
|
5710
|
+
try {
|
|
5711
|
+
const pid = await launchOrRestart(targetMode);
|
|
5712
|
+
const label = daemonEnabled ? "\u5DF2\u542F\u52A8 (\u4FDD\u6D3B)" : "\u5DF2\u542F\u52A8";
|
|
5713
|
+
console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""}`);
|
|
5714
|
+
} catch (e) {
|
|
5715
|
+
if (e instanceof CliError) throw e;
|
|
5716
|
+
const lines = e.message.split("\n");
|
|
5717
|
+
throw new CliError(lines[0], { label: "\u542F\u52A8\u5931\u8D25", hint: lines.slice(1) });
|
|
5718
|
+
}
|
|
5719
|
+
const cleanThreshold = isGithubUrl(sub.url) ? AUTO_CLEAN_THRESHOLD_GITHUB : AUTO_CLEAN_THRESHOLD;
|
|
5720
|
+
if (!skipClean && configInfo.proxies > cleanThreshold) {
|
|
5721
|
+
const cache = readSubscriptionCache();
|
|
5722
|
+
const lastCleanAt = cache[sub.name]?.last_auto_clean_at;
|
|
5723
|
+
const withinCooldown = !!lastCleanAt && Date.now() - new Date(lastCleanAt).getTime() < AUTO_CLEAN_COOLDOWN_HOURS * 60 * 60 * 1e3;
|
|
5724
|
+
if (!withinCooldown) {
|
|
5725
|
+
console.log("");
|
|
5726
|
+
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...`);
|
|
5727
|
+
console.log("");
|
|
5728
|
+
await sleep(1e3);
|
|
5729
|
+
const progress = createProgressPrinter(rounds);
|
|
5730
|
+
const cleanResult = await autoCleanSubscription(sub.name, {
|
|
5731
|
+
timeout,
|
|
5732
|
+
concurrency,
|
|
5733
|
+
rounds,
|
|
5734
|
+
onResult: progress.onResult,
|
|
5735
|
+
onRetryRound: progress.onRetryRound
|
|
5736
|
+
});
|
|
5737
|
+
progress.finish();
|
|
5738
|
+
console.log(formatTestSummary(cleanResult.summary));
|
|
5739
|
+
if (cleanResult.skipped) {
|
|
5740
|
+
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"));
|
|
5741
|
+
} else if (cleanResult.removedProxies > 0) {
|
|
5742
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
|
|
5743
|
+
console.log("");
|
|
5744
|
+
console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
|
|
5745
|
+
if (!daemonEnabled) handleStopResult(stop());
|
|
5746
|
+
try {
|
|
5747
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
5748
|
+
const pid = await launchOrRestart(targetMode);
|
|
5749
|
+
console.log(`${colors.green("\u5DF2\u91CD\u542F")}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
|
|
5750
|
+
} catch (e) {
|
|
5751
|
+
if (e instanceof CliError) throw e;
|
|
5752
|
+
throw new CliError(e.message.split("\n")[0], { label: "\u91CD\u542F\u5931\u8D25" });
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
saveSubscriptionCache(sub.name, { last_auto_clean_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5758
|
+
printStatus();
|
|
5759
|
+
}
|
|
5760
|
+
|
|
5761
|
+
// src/commands/shared.ts
|
|
5762
|
+
async function dispatchSubcommand(args, table, options) {
|
|
5763
|
+
const action = args[1];
|
|
5764
|
+
if (action) {
|
|
5765
|
+
const cmd = table.find((c) => c.name === action || c.aliases?.includes(action));
|
|
5766
|
+
if (cmd) return cmd.handler(args);
|
|
5767
|
+
if (options.onUnknown) return options.onUnknown(action);
|
|
5768
|
+
}
|
|
5769
|
+
return options.fallback(args);
|
|
5770
|
+
}
|
|
5771
|
+
function requireRunning() {
|
|
5772
|
+
const state = getRunningState();
|
|
5773
|
+
if (!state.running) {
|
|
5774
|
+
const hint = state.daemon ? "mihomo daemon on" : "mihomo start";
|
|
5775
|
+
throw new CliError(`mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (${hint})`);
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
async function restartToApply(args) {
|
|
5779
|
+
if (!isRestartNeededOnChange()) return false;
|
|
5780
|
+
const currentMode = getRuntimeMode();
|
|
5781
|
+
console.log("");
|
|
5782
|
+
await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
|
|
5783
|
+
return true;
|
|
5784
|
+
}
|
|
5785
|
+
|
|
5786
|
+
// src/commands/daemon.ts
|
|
5787
|
+
function printDaemonStatus() {
|
|
5788
|
+
const status = getDaemonStatus();
|
|
5789
|
+
const stateText = status.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
|
|
5790
|
+
console.log(`${colors.gray("\u4FDD\u6D3B: ")}${stateText}`);
|
|
5791
|
+
if (status.enabled) {
|
|
5792
|
+
const runText = isDaemonRunning(status) ? colors.green(`\u8FD0\u884C\u4E2D (PID ${status.pid})`) : colors.yellow("\u672A\u8FD0\u884C");
|
|
5793
|
+
console.log(`${colors.gray("\u5185\u6838: ")}${runText}`);
|
|
5794
|
+
}
|
|
5795
|
+
console.log("");
|
|
5796
|
+
if (status.enabled) {
|
|
5797
|
+
console.log("\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off");
|
|
5798
|
+
} else {
|
|
5799
|
+
console.log("\u5F00\u542F\u4FDD\u6D3B: mihomo daemon on");
|
|
5800
|
+
console.log(colors.gray(" \u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF08\u4EC5 Mixed \u6A21\u5F0F\uFF09"));
|
|
5801
|
+
}
|
|
5802
|
+
console.log("");
|
|
5803
|
+
}
|
|
5804
|
+
async function daemonOn() {
|
|
5805
|
+
if (!hasKernel()) {
|
|
5806
|
+
throw new CliError('\u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
|
|
5807
|
+
}
|
|
5808
|
+
const sub = requireActiveSubscription("\u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
5809
|
+
let configInfo;
|
|
5810
|
+
try {
|
|
5811
|
+
configInfo = prepareConfigForStart("mixed", sub.name);
|
|
5812
|
+
} catch (e) {
|
|
5813
|
+
if (e instanceof CliError) throw e;
|
|
5814
|
+
throw new CliError(e.message, { label: "\u914D\u7F6E\u9519\u8BEF" });
|
|
5815
|
+
}
|
|
5816
|
+
console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u5B89\u88C5\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1\uFF08LaunchDaemon\uFF09"));
|
|
5817
|
+
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"));
|
|
5818
|
+
try {
|
|
5819
|
+
enableDaemon();
|
|
5820
|
+
} catch (e) {
|
|
5821
|
+
if (e instanceof CliError) throw e;
|
|
5822
|
+
throw new CliError(e.message, { label: "\u542F\u7528\u4FDD\u6D3B\u5931\u8D25" });
|
|
5823
|
+
}
|
|
5824
|
+
console.log(`${colors.green("\u5DF2\u542F\u7528\u4FDD\u6D3B")} \xB7 ${sub.name} \xB7 ${formatProxySummary(configInfo)}`);
|
|
5825
|
+
console.log(colors.gray("\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF0C\u4EE3\u7406\u5C06\u5728\u540E\u53F0\u5E38\u9A7B"));
|
|
5826
|
+
console.log("");
|
|
5827
|
+
await sleep(DAEMON_BOOT_WAIT_MS);
|
|
5828
|
+
printDaemonStatus();
|
|
5829
|
+
}
|
|
5830
|
+
function daemonOff() {
|
|
5831
|
+
if (!isDaemonEnabled()) {
|
|
5832
|
+
console.log("\u4FDD\u6D3B\u5DF2\u662F\u5173\u95ED\u72B6\u6001");
|
|
5833
|
+
console.log("");
|
|
5834
|
+
printDaemonStatus();
|
|
5835
|
+
return;
|
|
5836
|
+
}
|
|
5837
|
+
console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u79FB\u9664\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1"));
|
|
5838
|
+
try {
|
|
5839
|
+
disableDaemon();
|
|
5840
|
+
} catch (e) {
|
|
5841
|
+
if (e instanceof CliError) throw e;
|
|
5842
|
+
throw new CliError(e.message, { label: "\u5173\u95ED\u4FDD\u6D3B\u5931\u8D25" });
|
|
5843
|
+
}
|
|
5844
|
+
console.log(`${colors.green("\u5DF2\u5173\u95ED\u4FDD\u6D3B")}\uFF0C\u4EE3\u7406\u5DF2\u505C\u6B62`);
|
|
5845
|
+
console.log(colors.gray("\u91CD\u65B0\u542F\u7528: mihomo daemon on"));
|
|
5846
|
+
console.log("");
|
|
5847
|
+
}
|
|
5848
|
+
function printStatusView() {
|
|
5849
|
+
console.log("");
|
|
5850
|
+
printDaemonStatus();
|
|
5851
|
+
}
|
|
5852
|
+
var SUBCOMMANDS = [
|
|
5853
|
+
{ name: "on", aliases: ["enable"], handler: daemonOn },
|
|
5854
|
+
{ name: "off", aliases: ["disable"], handler: daemonOff },
|
|
5855
|
+
{ name: "status", handler: printStatusView }
|
|
5856
|
+
];
|
|
5857
|
+
async function cmdDaemon(args) {
|
|
5858
|
+
await dispatchSubcommand(args, SUBCOMMANDS, {
|
|
5859
|
+
// 无 action → 显示状态;未知 action → 报错
|
|
5860
|
+
fallback: printStatusView,
|
|
5861
|
+
onUnknown: (action) => {
|
|
5862
|
+
const names = SUBCOMMANDS.flatMap((c) => [c.name, ...c.aliases ?? []]);
|
|
5863
|
+
const suggestion = suggestSimilar(action, names);
|
|
5864
|
+
throw new CliError(`\u672A\u77E5\u7684 daemon \u5B50\u547D\u4EE4: ${action}`, {
|
|
5865
|
+
hint: [...suggestion.length > 0 ? [`\u662F\u5426\u60F3\u8F93\u5165: ${suggestion.join(" / ")}?`] : [], "", "\u53EF\u7528\u5B50\u547D\u4EE4: on, off, status"]
|
|
5866
|
+
});
|
|
5867
|
+
}
|
|
5868
|
+
});
|
|
5869
|
+
}
|
|
5870
|
+
|
|
5871
|
+
// src/commands/directory.ts
|
|
5872
|
+
function openDirectory(args) {
|
|
5873
|
+
const target = args[2];
|
|
5874
|
+
if (!target || target === "root") {
|
|
5875
|
+
console.log("\u6B63\u5728\u6253\u5F00: \u6839\u76EE\u5F55");
|
|
5876
|
+
const success = openUrl(USER_DATA_DIR);
|
|
5877
|
+
if (!success) {
|
|
5878
|
+
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${USER_DATA_DIR}`);
|
|
5879
|
+
}
|
|
5880
|
+
return;
|
|
5881
|
+
}
|
|
5882
|
+
const key = target.toLowerCase();
|
|
5883
|
+
const targetInfo = Object.hasOwn(DIRECTORY_TARGETS, key) ? DIRECTORY_TARGETS[key] : void 0;
|
|
5884
|
+
if (targetInfo) {
|
|
5885
|
+
const targetPath = targetInfo.path || USER_DATA_DIR;
|
|
5886
|
+
console.log(`\u6B63\u5728\u6253\u5F00: ${targetInfo.label}`);
|
|
5887
|
+
const success = openUrl(targetPath);
|
|
5888
|
+
if (!success) {
|
|
5889
|
+
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${targetPath}`);
|
|
5890
|
+
}
|
|
5891
|
+
return;
|
|
5892
|
+
}
|
|
5893
|
+
const hint = ["", "\u53EF\u7528\u76EE\u6807:", " root (\u9ED8\u8BA4) \u6839\u76EE\u5F55"];
|
|
5894
|
+
for (const [k, val] of Object.entries(DIRECTORY_TARGETS)) {
|
|
5895
|
+
if (k !== "root") {
|
|
5896
|
+
hint.push(` ${k.padEnd(14)}${val.label}`);
|
|
5897
|
+
}
|
|
5898
|
+
}
|
|
5899
|
+
throw new CliError(`\u672A\u77E5\u7684\u76EE\u5F55\u76EE\u6807 "${target}"`, { hint });
|
|
5900
|
+
}
|
|
5901
|
+
function printDirectoryInfo() {
|
|
5902
|
+
console.log("");
|
|
5903
|
+
console.log("\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E:");
|
|
5904
|
+
console.log(` \u6839\u76EE\u5F55: ${USER_DATA_DIR}`);
|
|
5905
|
+
console.log(` \u5168\u5C40\u8BBE\u7F6E: ${PATHS.settingsFile}`);
|
|
5906
|
+
console.log(` \u5185\u6838\u76EE\u5F55: ${DIRS.kernel}`);
|
|
5907
|
+
console.log(` \u5185\u6838\u6587\u4EF6: ${PATHS.mihomoBinary}`);
|
|
5908
|
+
console.log(` \u8BA2\u9605\u76EE\u5F55: ${DIRS.subscriptions}`);
|
|
5909
|
+
console.log(" - cache.json (\u8BA2\u9605\u7F13\u5B58\uFF1A\u66F4\u65B0\u65F6\u95F4\u3001\u6D41\u91CF\u7B49)");
|
|
5910
|
+
console.log(" - xxx.yaml (\u8BA2\u9605\u539F\u59CB\u914D\u7F6E)");
|
|
5911
|
+
console.log(` \u8FD0\u884C\u65F6\u76EE\u5F55: ${DIRS.runtime}`);
|
|
5912
|
+
console.log(" - config.yaml (\u542F\u52A8\u65F6\u751F\u6210\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
5913
|
+
console.log(" - pid (PID \u6587\u4EF6\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
5914
|
+
console.log(` \u65E5\u5FD7\u6587\u4EF6: ${PATHS.logFile}`);
|
|
5915
|
+
console.log(` mihomo \u6570\u636E: ${DIRS.data}`);
|
|
5916
|
+
console.log(" - cache.db, Geo*.dat \u7B49 (mihomo \u81EA\u884C\u7BA1\u7406)");
|
|
5917
|
+
console.log("");
|
|
5918
|
+
console.log("\u6253\u5F00\u76EE\u5F55:");
|
|
5919
|
+
console.log(" mihomo dir open \u6253\u5F00\u6839\u76EE\u5F55");
|
|
5920
|
+
console.log(" mihomo dir open subs \u6253\u5F00\u8BA2\u9605\u76EE\u5F55");
|
|
5921
|
+
console.log(" mihomo dir open logs \u6253\u5F00\u65E5\u5FD7\u76EE\u5F55");
|
|
5922
|
+
console.log(" mihomo dir open data \u6253\u5F00 mihomo \u6570\u636E\u76EE\u5F55");
|
|
5555
5923
|
console.log(" mihomo dir open runtime \u6253\u5F00\u8FD0\u884C\u65F6\u76EE\u5F55");
|
|
5556
5924
|
console.log(" mihomo dir open kernel \u6253\u5F00\u5185\u6838\u76EE\u5F55");
|
|
5557
5925
|
console.log("");
|
|
@@ -5559,9 +5927,13 @@ function cmdDirectory(args) {
|
|
|
5559
5927
|
console.log(" MIHOMO_CLI_DIR: \u81EA\u5B9A\u4E49\u6839\u76EE\u5F55\u4F4D\u7F6E");
|
|
5560
5928
|
console.log("");
|
|
5561
5929
|
}
|
|
5930
|
+
var SUBCOMMANDS2 = [{ name: "open", handler: openDirectory }];
|
|
5931
|
+
function cmdDirectory(args) {
|
|
5932
|
+
void dispatchSubcommand(args, SUBCOMMANDS2, { fallback: printDirectoryInfo });
|
|
5933
|
+
}
|
|
5562
5934
|
|
|
5563
5935
|
// src/kernel.ts
|
|
5564
|
-
import { spawnSync as
|
|
5936
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
5565
5937
|
import fs7 from "fs";
|
|
5566
5938
|
import path6 from "path";
|
|
5567
5939
|
|
|
@@ -5726,7 +6098,7 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5726
6098
|
if (progressCallback) {
|
|
5727
6099
|
progressCallback(`\u4E0B\u8F7D\u5185\u6838: ${asset.name} (${sizeMB} MB)`);
|
|
5728
6100
|
}
|
|
5729
|
-
const curlResult =
|
|
6101
|
+
const curlResult = spawnSync4(
|
|
5730
6102
|
"curl",
|
|
5731
6103
|
["-L", "--progress-bar", "--connect-timeout", "30", "--max-time", String(Math.floor(KERNEL_DOWNLOAD_TIMEOUT / 1e3)), "-o", tempPath, downloadUrl],
|
|
5732
6104
|
{ stdio: "inherit" }
|
|
@@ -5754,7 +6126,7 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5754
6126
|
let extractedBinary = null;
|
|
5755
6127
|
try {
|
|
5756
6128
|
if (tempPath.endsWith(".tar.gz") || tempPath.endsWith(".tgz")) {
|
|
5757
|
-
const listResult =
|
|
6129
|
+
const listResult = spawnSync4("tar", ["-tzf", tempPath], { encoding: "utf8", timeout: 6e4 });
|
|
5758
6130
|
if (listResult.error) throw listResult.error;
|
|
5759
6131
|
if (listResult.status !== 0) throw new Error(`tar \u5217\u8868\u9000\u51FA\u7801 ${listResult.status}`);
|
|
5760
6132
|
const entries = (listResult.stdout || "").split("\n").filter(Boolean);
|
|
@@ -5763,13 +6135,13 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5763
6135
|
throw new Error(`\u5F52\u6863\u542B\u975E\u6CD5\u8DEF\u5F84\u6761\u76EE: ${entry}`);
|
|
5764
6136
|
}
|
|
5765
6137
|
}
|
|
5766
|
-
const tarResult =
|
|
6138
|
+
const tarResult = spawnSync4("tar", ["-xzf", tempPath, "-C", extractPath], { stdio: ["ignore", "ignore", "inherit"], timeout: 6e4 });
|
|
5767
6139
|
if (tarResult.error) throw tarResult.error;
|
|
5768
6140
|
if (tarResult.status !== 0) throw new Error(`tar \u9000\u51FA\u7801 ${tarResult.status}`);
|
|
5769
6141
|
} else if (tempPath.endsWith(".gz")) {
|
|
5770
6142
|
const baseName = path6.basename(tempPath, ".gz");
|
|
5771
6143
|
const outputPath = path6.join(extractPath, baseName);
|
|
5772
|
-
const gzipResult =
|
|
6144
|
+
const gzipResult = spawnSync4("gzip", ["-dc", tempPath], { maxBuffer: 256 * 1024 * 1024, timeout: 6e4 });
|
|
5773
6145
|
if (gzipResult.error) throw gzipResult.error;
|
|
5774
6146
|
if (gzipResult.status !== 0) throw new Error(`gzip \u9000\u51FA\u7801 ${gzipResult.status}`);
|
|
5775
6147
|
fs7.writeFileSync(outputPath, gzipResult.stdout, { mode: 493 });
|
|
@@ -5805,7 +6177,7 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5805
6177
|
if (progressCallback) {
|
|
5806
6178
|
progressCallback("\u6821\u9A8C\u5185\u6838...");
|
|
5807
6179
|
}
|
|
5808
|
-
const check =
|
|
6180
|
+
const check = spawnSync4(targetPath, ["-v"], { encoding: "utf8", timeout: 5e3 });
|
|
5809
6181
|
const checkOutput = `${check.stdout || ""}${check.stderr || ""}`.trim();
|
|
5810
6182
|
if (check.error || check.status !== 0 || !/v?\d+\.\d+\.\d+/.test(checkOutput)) {
|
|
5811
6183
|
try {
|
|
@@ -5850,27 +6222,30 @@ async function cmdKernel(args) {
|
|
|
5850
6222
|
const result = await downloadKernel((msg) => console.log(msg), mirrorInfo.mirror, info.release);
|
|
5851
6223
|
console.log(`
|
|
5852
6224
|
\u5DF2\u66F4\u65B0\u5230 ${result.version}`);
|
|
6225
|
+
if (getRunningState().running) {
|
|
6226
|
+
console.log(colors.yellow("\u63D0\u793A: \u8FD0\u884C\u4E2D\u7684\u5185\u6838\u4ECD\u662F\u65E7\u7248\u672C\uFF0C\u6267\u884C mihomo start \u91CD\u542F\u540E\u751F\u6548"));
|
|
6227
|
+
}
|
|
5853
6228
|
}
|
|
5854
6229
|
} catch (e) {
|
|
5855
|
-
|
|
5856
|
-
\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
6230
|
+
if (e instanceof CliError) throw e;
|
|
5857
6231
|
const err = e;
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
}
|
|
6232
|
+
const hint = [];
|
|
6233
|
+
if (err.response?.data?.message) {
|
|
6234
|
+
hint.push(`\u539F\u56E0: ${err.response.data.message}`);
|
|
6235
|
+
}
|
|
6236
|
+
if (err.response?.data?.documentation_url) {
|
|
6237
|
+
hint.push(`\u6587\u6863: ${err.response.data.documentation_url}`);
|
|
5865
6238
|
}
|
|
5866
6239
|
if (!effectiveMirror) {
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
6240
|
+
hint.push(
|
|
6241
|
+
"",
|
|
6242
|
+
"\u63D0\u793A: \u76F4\u8FDE\u5931\u8D25\u6216\u4E0B\u8F7D\u8FC7\u6162\u65F6\u53EF\u4F7F\u7528\u955C\u50CF:",
|
|
6243
|
+
" mihomo kernel --mirror [\u955C\u50CF] # \u4E0B\u8F7D\u8D70\u955C\u50CF\uFF08\u9ED8\u8BA4 v6.gh-proxy.org\uFF09",
|
|
6244
|
+
" mihomo kernel --mirror-all [\u955C\u50CF] # API \u548C\u4E0B\u8F7D\u90FD\u8D70\u955C\u50CF",
|
|
6245
|
+
` \u53EF\u7528\u955C\u50CF: ${AVAILABLE_MIRRORS.join(", ")}`
|
|
6246
|
+
);
|
|
5872
6247
|
}
|
|
5873
|
-
|
|
6248
|
+
throw new CliError(err.message, { label: "\u66F4\u65B0\u5931\u8D25", hint });
|
|
5874
6249
|
}
|
|
5875
6250
|
}
|
|
5876
6251
|
|
|
@@ -5887,370 +6262,73 @@ function cmdLogs(args) {
|
|
|
5887
6262
|
const targetName = getNonFlagArg(args, 1);
|
|
5888
6263
|
const lines = parseIntArg(args, "-n", "--lines", 100);
|
|
5889
6264
|
const openInViewer = hasFlag(args, "-o", "--open");
|
|
5890
|
-
if (targetName) {
|
|
5891
|
-
let logPath;
|
|
5892
|
-
if (targetName === "current" || targetName === "0") {
|
|
5893
|
-
logPath = getLogPath();
|
|
5894
|
-
} else {
|
|
5895
|
-
const parsedIdx = parseInt(targetName, 10);
|
|
5896
|
-
if (!Number.isNaN(parsedIdx) && parsedIdx > 0 && String(parsedIdx) === targetName) {
|
|
5897
|
-
const archiveLogs = listLogs();
|
|
5898
|
-
const archive = archiveLogs.archives[parsedIdx - 1];
|
|
5899
|
-
if (!archive) {
|
|
5900
|
-
|
|
5901
|
-
console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
|
|
5902
|
-
process.exit(1);
|
|
5903
|
-
}
|
|
5904
|
-
logPath = archive.path;
|
|
5905
|
-
} else {
|
|
5906
|
-
logPath = getLogPathByName(targetName);
|
|
5907
|
-
}
|
|
5908
|
-
}
|
|
5909
|
-
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);
|
|
5913
|
-
}
|
|
5914
|
-
if (openInViewer) {
|
|
5915
|
-
openLogFile(logPath);
|
|
5916
|
-
return;
|
|
5917
|
-
}
|
|
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
|
-
}
|
|
6188
|
-
}
|
|
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);
|
|
6195
|
-
}
|
|
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);
|
|
6208
|
-
}
|
|
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);
|
|
6265
|
+
if (targetName) {
|
|
6266
|
+
let logPath;
|
|
6267
|
+
if (targetName === "current" || targetName === "0") {
|
|
6268
|
+
logPath = getLogPath();
|
|
6269
|
+
} else {
|
|
6270
|
+
const parsedIdx = parseInt(targetName, 10);
|
|
6271
|
+
if (!Number.isNaN(parsedIdx) && parsedIdx > 0 && String(parsedIdx) === targetName) {
|
|
6272
|
+
const archiveLogs = listLogs();
|
|
6273
|
+
const archive = archiveLogs.archives[parsedIdx - 1];
|
|
6274
|
+
if (!archive) {
|
|
6275
|
+
throw new CliError(`\u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`, { hint: '\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868' });
|
|
6245
6276
|
}
|
|
6277
|
+
logPath = archive.path;
|
|
6278
|
+
} else {
|
|
6279
|
+
logPath = getLogPathByName(targetName);
|
|
6246
6280
|
}
|
|
6247
|
-
saveSubscriptionCache(sub.name, { last_auto_clean_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
6248
6281
|
}
|
|
6282
|
+
if (!logPath) {
|
|
6283
|
+
throw new CliError(`\u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`, { hint: '\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868' });
|
|
6284
|
+
}
|
|
6285
|
+
if (openInViewer) {
|
|
6286
|
+
openLogFile(logPath);
|
|
6287
|
+
return;
|
|
6288
|
+
}
|
|
6289
|
+
viewLogWithTail(logPath, { follow: false, lines });
|
|
6290
|
+
return;
|
|
6249
6291
|
}
|
|
6250
|
-
|
|
6292
|
+
const logs = listLogs();
|
|
6293
|
+
const all = [];
|
|
6294
|
+
if (logs.current) all.push(logs.current);
|
|
6295
|
+
all.push(...logs.archives);
|
|
6296
|
+
if (all.length === 0) {
|
|
6297
|
+
console.log("\u6682\u65E0\u65E5\u5FD7");
|
|
6298
|
+
return;
|
|
6299
|
+
}
|
|
6300
|
+
console.log("");
|
|
6301
|
+
console.log("\u65E5\u5FD7\u5217\u8868:");
|
|
6302
|
+
console.log("");
|
|
6303
|
+
let archiveCounter = 0;
|
|
6304
|
+
for (const log of all) {
|
|
6305
|
+
let num;
|
|
6306
|
+
if (log.isCurrent) {
|
|
6307
|
+
num = " 0";
|
|
6308
|
+
} else {
|
|
6309
|
+
archiveCounter++;
|
|
6310
|
+
num = archiveCounter < 10 ? ` ${archiveCounter}` : `${archiveCounter}`;
|
|
6311
|
+
}
|
|
6312
|
+
const time = formatDate(log.mtime);
|
|
6313
|
+
const size = formatBytes(log.size);
|
|
6314
|
+
const name = log.isCurrent ? "mihomo.log (\u5F53\u524D\u8FD0\u884C\u4E2D)" : log.name;
|
|
6315
|
+
console.log(` ${num}. ${name}`);
|
|
6316
|
+
console.log(` \u65F6\u95F4: ${time} \u5927\u5C0F: ${size}`);
|
|
6317
|
+
if (!log.isCurrent) {
|
|
6318
|
+
console.log(` \u67E5\u770B: mihomo logs ${archiveCounter} \u6216 mihomo logs ${archiveCounter} -o`);
|
|
6319
|
+
}
|
|
6320
|
+
console.log("");
|
|
6321
|
+
}
|
|
6322
|
+
console.log("\u7528\u6CD5:");
|
|
6323
|
+
console.log(" mihomo logs 0 # \u67E5\u770B\u5F53\u524D\u65E5\u5FD7 (\u6700\u540E 100 \u884C)");
|
|
6324
|
+
console.log(" mihomo logs 1 # \u67E5\u770B\u7B2C 1 \u4E2A\u5F52\u6863\u65E5\u5FD7\uFF08\u6700\u65B0\uFF09");
|
|
6325
|
+
console.log(" mihomo logs 1 -n 200 # \u67E5\u770B 200 \u884C");
|
|
6326
|
+
console.log(" mihomo logs 1 -o # \u7528\u7CFB\u7EDF\u9ED8\u8BA4\u7A0B\u5E8F\u6253\u5F00");
|
|
6327
|
+
console.log("");
|
|
6251
6328
|
}
|
|
6252
6329
|
|
|
6253
6330
|
// src/commands/overwrite.ts
|
|
6331
|
+
import path7 from "path";
|
|
6254
6332
|
function printOverwriteList() {
|
|
6255
6333
|
const info = listOverwriteFile();
|
|
6256
6334
|
const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
|
|
@@ -6282,48 +6360,30 @@ function printOverwriteList() {
|
|
|
6282
6360
|
console.log("\u7981\u7528\u8986\u5199: mihomo ow off");
|
|
6283
6361
|
console.log("");
|
|
6284
6362
|
}
|
|
6285
|
-
async function
|
|
6286
|
-
|
|
6287
|
-
|
|
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
|
-
}
|
|
6363
|
+
async function setOverwrite(enabled, args) {
|
|
6364
|
+
if (isOverwriteEnabled() === enabled) {
|
|
6365
|
+
console.log(`\u8986\u5199\u914D\u7F6E\u5DF2\u662F${enabled ? "\u542F\u7528" : "\u7981\u7528"}\u72B6\u6001`);
|
|
6303
6366
|
console.log("");
|
|
6304
6367
|
printOverwriteList();
|
|
6305
6368
|
return;
|
|
6306
6369
|
}
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6370
|
+
setOverwriteEnabled(enabled);
|
|
6371
|
+
console.log(`\u5DF2${enabled ? "\u542F\u7528" : "\u7981\u7528"}\u8986\u5199\u914D\u7F6E`);
|
|
6372
|
+
if (await restartToApply(args)) return;
|
|
6373
|
+
console.log("");
|
|
6374
|
+
printOverwriteList();
|
|
6375
|
+
}
|
|
6376
|
+
var SUBCOMMANDS3 = [
|
|
6377
|
+
{ name: "on", aliases: ["enable"], handler: (args) => setOverwrite(true, args) },
|
|
6378
|
+
{ name: "off", aliases: ["disable"], handler: (args) => setOverwrite(false, args) }
|
|
6379
|
+
];
|
|
6380
|
+
async function cmdOverwrite(args) {
|
|
6381
|
+
await dispatchSubcommand(args, SUBCOMMANDS3, {
|
|
6382
|
+
fallback: () => {
|
|
6310
6383
|
console.log("");
|
|
6311
6384
|
printOverwriteList();
|
|
6312
|
-
return;
|
|
6313
|
-
}
|
|
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
6385
|
}
|
|
6321
|
-
|
|
6322
|
-
printOverwriteList();
|
|
6323
|
-
return;
|
|
6324
|
-
}
|
|
6325
|
-
console.log("");
|
|
6326
|
-
printOverwriteList();
|
|
6386
|
+
});
|
|
6327
6387
|
}
|
|
6328
6388
|
|
|
6329
6389
|
// src/commands/reset.ts
|
|
@@ -6433,10 +6493,7 @@ async function cmdReset(args) {
|
|
|
6433
6493
|
const KNOWN_FLAGS = /* @__PURE__ */ new Set(["--full", "--yes", "-y"]);
|
|
6434
6494
|
const unknownFlags = flags.filter((f) => !KNOWN_FLAGS.has(f));
|
|
6435
6495
|
if (unknownFlags.length > 0) {
|
|
6436
|
-
|
|
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);
|
|
6496
|
+
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
6497
|
}
|
|
6441
6498
|
const fullReset = flags.includes("--full");
|
|
6442
6499
|
const skipConfirm = flags.includes("--yes") || flags.includes("-y");
|
|
@@ -6446,16 +6503,18 @@ async function cmdReset(args) {
|
|
|
6446
6503
|
} else if (names.length > 0) {
|
|
6447
6504
|
const { matched, unmatched } = resolveResetTargets(names);
|
|
6448
6505
|
if (unmatched.length > 0) {
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6506
|
+
throw new CliError(`\u672A\u77E5\u7684\u91CD\u7F6E\u76EE\u6807: ${unmatched.join(", ")}`, {
|
|
6507
|
+
hint: [
|
|
6508
|
+
"",
|
|
6509
|
+
`\u53EF\u7528\u76EE\u6807: ${RESET_TARGETS.map((t) => t.aliases[0]).join(", ")}`,
|
|
6510
|
+
"",
|
|
6511
|
+
"\u793A\u4F8B:",
|
|
6512
|
+
" mihomo reset sub log # \u5220\u9664\u8BA2\u9605\u548C\u65E5\u5FD7",
|
|
6513
|
+
" mihomo reset kernel # \u53EA\u5220\u5185\u6838",
|
|
6514
|
+
" mihomo reset --full # \u5220\u9664\u5168\u90E8",
|
|
6515
|
+
" mihomo reset # \u5220\u9664\u5168\u90E8\uFF08\u4FDD\u7559\u8BBE\u7F6E\u3001\u5185\u6838\u3001\u8986\u5199\uFF09"
|
|
6516
|
+
]
|
|
6517
|
+
});
|
|
6459
6518
|
}
|
|
6460
6519
|
targets = matched;
|
|
6461
6520
|
} else {
|
|
@@ -6525,6 +6584,15 @@ async function cmdReset(args) {
|
|
|
6525
6584
|
import { spawn as spawn2 } from "child_process";
|
|
6526
6585
|
import fs9 from "fs";
|
|
6527
6586
|
import path8 from "path";
|
|
6587
|
+
function isProxyValid(proxy) {
|
|
6588
|
+
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
6589
|
+
if (!proxy.type) return false;
|
|
6590
|
+
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
6591
|
+
const pw = String(proxy.password || "");
|
|
6592
|
+
if (!/^[A-Za-z0-9+/\-_]+=*$/.test(pw) || pw.length < 20) return false;
|
|
6593
|
+
}
|
|
6594
|
+
return true;
|
|
6595
|
+
}
|
|
6528
6596
|
var TEST_DIR = path8.join(USER_DATA_DIR, "test");
|
|
6529
6597
|
var TEST_DIRS = {
|
|
6530
6598
|
data: path8.join(TEST_DIR, "data"),
|
|
@@ -6548,12 +6616,12 @@ function buildTestConfig(subName) {
|
|
|
6548
6616
|
ensureTestDirs();
|
|
6549
6617
|
const rawContent = readSubscriptionRawConfig(subName);
|
|
6550
6618
|
if (!rawContent) {
|
|
6551
|
-
throw new
|
|
6619
|
+
throw new CliError(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"\uFF0C\u8BF7\u5148\u66F4\u65B0\u8BA2\u9605\uFF08mihomo sub update ${subName}\uFF09`);
|
|
6552
6620
|
}
|
|
6553
6621
|
const parsed = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
6554
6622
|
const proxies = (parsed.proxies || []).filter(isProxyValid);
|
|
6555
6623
|
if (proxies.length === 0) {
|
|
6556
|
-
throw new
|
|
6624
|
+
throw new CliError(`\u8BA2\u9605 "${subName}" \u6CA1\u6709\u6709\u6548\u8282\u70B9`);
|
|
6557
6625
|
}
|
|
6558
6626
|
const nameCount = /* @__PURE__ */ new Map();
|
|
6559
6627
|
for (const proxy of proxies) {
|
|
@@ -6580,7 +6648,7 @@ function buildTestConfig(subName) {
|
|
|
6580
6648
|
}
|
|
6581
6649
|
async function startTestInstance() {
|
|
6582
6650
|
const binary = PATHS.mihomoBinary;
|
|
6583
|
-
if (!fs9.existsSync(binary)) throw new
|
|
6651
|
+
if (!fs9.existsSync(binary)) throw new CliError('\u672A\u627E\u5230 mihomo \u5185\u6838\uFF0C\u8BF7\u5148\u8FD0\u884C "mihomo kernel" \u4E0B\u8F7D');
|
|
6584
6652
|
stopTestInstance();
|
|
6585
6653
|
const logFd = fs9.openSync(TEST_PATHS.logFile, "a");
|
|
6586
6654
|
const child = spawn2(binary, ["-d", TEST_DIRS.data, "-f", TEST_PATHS.configFile], {
|
|
@@ -6592,7 +6660,7 @@ async function startTestInstance() {
|
|
|
6592
6660
|
fs9.closeSync(logFd);
|
|
6593
6661
|
child.unref();
|
|
6594
6662
|
const pid = child.pid;
|
|
6595
|
-
if (!pid) throw new
|
|
6663
|
+
if (!pid) throw new CliError("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25\uFF1A\u65E0\u6CD5\u521B\u5EFA\u8FDB\u7A0B\uFF08\u5185\u6838\u4E8C\u8FDB\u5236\u53EF\u80FD\u4E0D\u53EF\u6267\u884C\uFF09");
|
|
6596
6664
|
fs9.writeFileSync(TEST_PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
6597
6665
|
const client = createHttpClient({ timeout: 2e3 });
|
|
6598
6666
|
let ready = false;
|
|
@@ -6612,11 +6680,11 @@ async function startTestInstance() {
|
|
|
6612
6680
|
errorDetail = fs9.readFileSync(TEST_PATHS.logFile, "utf8").slice(-1e3);
|
|
6613
6681
|
} catch {
|
|
6614
6682
|
}
|
|
6615
|
-
throw new
|
|
6683
|
+
throw new CliError(`\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
|
|
6616
6684
|
${errorDetail}` : ""}`);
|
|
6617
6685
|
}
|
|
6618
6686
|
if (!ready) {
|
|
6619
|
-
throw new
|
|
6687
|
+
throw new CliError("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
6620
6688
|
}
|
|
6621
6689
|
}
|
|
6622
6690
|
function stopTestInstance() {
|
|
@@ -6664,21 +6732,18 @@ function githubRepoUrl(rawUrl) {
|
|
|
6664
6732
|
function resolveTestTarget(args) {
|
|
6665
6733
|
const subs = getSubscriptions();
|
|
6666
6734
|
if (subs.length === 0) {
|
|
6667
|
-
|
|
6668
|
-
process.exit(1);
|
|
6735
|
+
throw new CliError("\u6CA1\u6709\u8BA2\u9605");
|
|
6669
6736
|
}
|
|
6670
6737
|
const nameArg = getNonFlagArg(args, 2);
|
|
6671
6738
|
const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
|
|
6672
6739
|
const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
|
|
6673
6740
|
let target;
|
|
6674
6741
|
if (nameArg) {
|
|
6675
|
-
|
|
6676
|
-
target = pickSingleSubscription(matches, nameArg);
|
|
6742
|
+
target = resolveSubscription(subs, nameArg);
|
|
6677
6743
|
} else {
|
|
6678
6744
|
const activeSub = getActiveSubscription();
|
|
6679
6745
|
if (!activeSub) {
|
|
6680
|
-
|
|
6681
|
-
process.exit(1);
|
|
6746
|
+
throw new CliError("\u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
6682
6747
|
}
|
|
6683
6748
|
target = activeSub;
|
|
6684
6749
|
}
|
|
@@ -6739,274 +6804,235 @@ function printSubscriptionList() {
|
|
|
6739
6804
|
console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
|
|
6740
6805
|
console.log("");
|
|
6741
6806
|
}
|
|
6742
|
-
async function
|
|
6743
|
-
const
|
|
6744
|
-
|
|
6745
|
-
|
|
6746
|
-
|
|
6807
|
+
async function subAdd(args) {
|
|
6808
|
+
const url = args[2]?.trim();
|
|
6809
|
+
const name = args[3] || "default";
|
|
6810
|
+
if (!url) {
|
|
6811
|
+
throw new CliError("\u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
6747
6812
|
}
|
|
6748
|
-
if (
|
|
6749
|
-
const
|
|
6750
|
-
|
|
6751
|
-
|
|
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
|
-
}
|
|
6813
|
+
if (isMultiUrl(url)) {
|
|
6814
|
+
const urls = splitUrls(url);
|
|
6815
|
+
if (urls.length === 0) {
|
|
6816
|
+
throw new CliError("\u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
6797
6817
|
}
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
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);
|
|
6818
|
+
for (const u of urls) {
|
|
6819
|
+
if (!isValidHttpUrl(u)) {
|
|
6820
|
+
throw new CliError(`\u65E0\u6548\u7684 URL: ${u}`);
|
|
6816
6821
|
}
|
|
6817
|
-
if (ok === 0) process.exit(1);
|
|
6818
|
-
console.log("");
|
|
6819
|
-
printRestartHintIfRunning();
|
|
6820
|
-
printSubscriptionList();
|
|
6821
|
-
return;
|
|
6822
6822
|
}
|
|
6823
|
-
const
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6823
|
+
const normalizedUrl = urls.join(",");
|
|
6824
|
+
console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
|
|
6825
|
+
addSubscription(normalizedUrl, name);
|
|
6826
|
+
try {
|
|
6827
|
+
setDefaultSubscription(name);
|
|
6828
|
+
const info = await downloadMergedSubscription(urls, name);
|
|
6829
|
+
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
|
|
6830
|
+
} catch (e) {
|
|
6831
|
+
removeSubscription(name);
|
|
6832
|
+
throw new CliError(e.message, { label: "\u6DFB\u52A0\u5931\u8D25" });
|
|
6830
6833
|
}
|
|
6831
|
-
|
|
6832
|
-
|
|
6833
|
-
|
|
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;
|
|
6834
|
+
} else {
|
|
6835
|
+
if (!isValidHttpUrl(url)) {
|
|
6836
|
+
throw new CliError("\u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL\uFF08\u9700\u4EE5 http:// \u6216 https:// \u5F00\u5934\uFF09");
|
|
6857
6837
|
}
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6838
|
+
console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
|
|
6839
|
+
addSubscription(url, name);
|
|
6840
|
+
try {
|
|
6841
|
+
setDefaultSubscription(name);
|
|
6842
|
+
const info = await downloadSubscription(url, name);
|
|
6843
|
+
const repoUrl = githubRepoUrl(url);
|
|
6844
|
+
if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
|
|
6845
|
+
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
6846
|
+
} catch (e) {
|
|
6847
|
+
removeSubscription(name);
|
|
6848
|
+
throw new CliError(e.message, { label: "\u6DFB\u52A0\u5931\u8D25" });
|
|
6866
6849
|
}
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6850
|
+
}
|
|
6851
|
+
console.log("");
|
|
6852
|
+
printSubscriptionList();
|
|
6853
|
+
}
|
|
6854
|
+
async function subUpdate(args) {
|
|
6855
|
+
const name = args[2];
|
|
6856
|
+
const subs = getSubscriptions();
|
|
6857
|
+
if (subs.length === 0) {
|
|
6858
|
+
throw new CliError("\u6CA1\u6709\u8BA2\u9605");
|
|
6859
|
+
}
|
|
6860
|
+
if (!name) {
|
|
6861
|
+
console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
|
|
6862
|
+
const results = await Promise.all(subs.map((sub) => tryUpdateOne(sub)));
|
|
6863
|
+
let ok = 0;
|
|
6864
|
+
for (const r of results) {
|
|
6865
|
+
if (r.success) ok++;
|
|
6866
|
+
printUpdateResult(r);
|
|
6871
6867
|
}
|
|
6868
|
+
if (ok === 0) throw new CliError("\u5168\u90E8\u8BA2\u9605\u66F4\u65B0\u5931\u8D25");
|
|
6872
6869
|
console.log("");
|
|
6870
|
+
printRestartHintIfRunning();
|
|
6873
6871
|
printSubscriptionList();
|
|
6874
6872
|
return;
|
|
6875
6873
|
}
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
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;
|
|
6874
|
+
const target = resolveSubscription(subs, name);
|
|
6875
|
+
console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
|
|
6876
|
+
const result = await tryUpdateOne(target);
|
|
6877
|
+
if (!result.success) {
|
|
6878
|
+
throw new CliError((result.error || "").split("\n")[0], { label: "\u66F4\u65B0\u5931\u8D25" });
|
|
6913
6879
|
}
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
}
|
|
6880
|
+
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
|
|
6881
|
+
console.log("");
|
|
6882
|
+
printRestartHintIfRunning();
|
|
6883
|
+
printSubscriptionList();
|
|
6884
|
+
}
|
|
6885
|
+
async function subUse(args) {
|
|
6886
|
+
const name = args[2];
|
|
6887
|
+
const subs = getSubscriptions();
|
|
6888
|
+
if (!name) {
|
|
6889
|
+
throw new CliError("\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0", {
|
|
6890
|
+
hint: subs.length > 0 ? ["", "\u53EF\u7528\u8BA2\u9605:", ...subs.map((s) => ` ${s.name}`)] : void 0
|
|
6891
|
+
});
|
|
6892
|
+
}
|
|
6893
|
+
const target = resolveSubscription(subs, name);
|
|
6894
|
+
const currentDefault = getActiveSubscription();
|
|
6895
|
+
const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
|
|
6896
|
+
if (isAlreadyDefault) {
|
|
6897
|
+
console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
|
|
6932
6898
|
console.log("");
|
|
6933
6899
|
printSubscriptionList();
|
|
6934
6900
|
return;
|
|
6935
6901
|
}
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
|
|
6955
|
-
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6902
|
+
const success = setDefaultSubscription(target.name);
|
|
6903
|
+
if (!success) {
|
|
6904
|
+
throw new CliError(`\u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
6905
|
+
}
|
|
6906
|
+
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
6907
|
+
if (await restartToApply(args)) return;
|
|
6908
|
+
console.log("");
|
|
6909
|
+
printSubscriptionList();
|
|
6910
|
+
}
|
|
6911
|
+
async function subWeb(args) {
|
|
6912
|
+
const name = args[2];
|
|
6913
|
+
const subs = getSubscriptionsWithCache();
|
|
6914
|
+
if (subs.length === 0) {
|
|
6915
|
+
throw new CliError("\u6CA1\u6709\u8BA2\u9605");
|
|
6916
|
+
}
|
|
6917
|
+
let target;
|
|
6918
|
+
if (name) {
|
|
6919
|
+
target = resolveSubscription(subs, name);
|
|
6920
|
+
} else {
|
|
6921
|
+
target = getActiveSubscription() || subs[0];
|
|
6922
|
+
}
|
|
6923
|
+
const cached = subs.find((s) => s.name === target.name);
|
|
6924
|
+
let webPageUrl = cached?.web_page_url;
|
|
6925
|
+
if (!webPageUrl) {
|
|
6926
|
+
console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u67E5\u8BE2\u8BA2\u9605...");
|
|
6927
|
+
try {
|
|
6928
|
+
const info = isMultiUrl(target.url) ? await downloadMergedSubscription(splitUrls(target.url), target.name, void 0, false) : await downloadSubscription(target.url, target.name, void 0, false);
|
|
6929
|
+
if (!info.webPageUrl) {
|
|
6930
|
+
throw new CliError("\u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
|
|
6963
6931
|
}
|
|
6932
|
+
webPageUrl = info.webPageUrl;
|
|
6933
|
+
} catch (e) {
|
|
6934
|
+
if (e instanceof CliError) throw e;
|
|
6935
|
+
throw new CliError(e.message, { label: "\u67E5\u8BE2\u5931\u8D25" });
|
|
6964
6936
|
}
|
|
6965
|
-
return;
|
|
6966
6937
|
}
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
console.log(
|
|
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;
|
|
6938
|
+
console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
|
|
6939
|
+
const opened = openUrl(webPageUrl);
|
|
6940
|
+
if (!opened) {
|
|
6941
|
+
console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
|
|
6984
6942
|
}
|
|
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
6943
|
}
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6944
|
+
function subRemove(args) {
|
|
6945
|
+
const name = args[2];
|
|
6946
|
+
const subs = getSubscriptions();
|
|
6947
|
+
if (!name) {
|
|
6948
|
+
throw new CliError("\u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0", {
|
|
6949
|
+
hint: subs.length > 0 ? ["", "\u53EF\u7528\u8BA2\u9605:", ...subs.map((s) => ` ${s.name}`)] : void 0
|
|
6950
|
+
});
|
|
6951
|
+
}
|
|
6952
|
+
const target = resolveSubscription(subs, name);
|
|
6953
|
+
const switchedTo = removeSubscription(target.name);
|
|
6954
|
+
console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
|
|
6955
|
+
if (switchedTo) {
|
|
6956
|
+
console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
|
|
6997
6957
|
}
|
|
6958
|
+
console.log("");
|
|
6959
|
+
printSubscriptionList();
|
|
6998
6960
|
}
|
|
6999
|
-
function
|
|
7000
|
-
const
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
6961
|
+
async function subClean(args) {
|
|
6962
|
+
const { target, timeout, concurrency } = resolveTestTarget(args);
|
|
6963
|
+
const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
|
|
6964
|
+
console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
|
|
6965
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6966
|
+
console.log("");
|
|
6967
|
+
const progress = createProgressPrinter(rounds);
|
|
6968
|
+
const result = await withTestInstance(target.name, async (apiBase) => {
|
|
6969
|
+
return autoCleanSubscription(target.name, {
|
|
6970
|
+
timeout,
|
|
6971
|
+
concurrency,
|
|
6972
|
+
rounds,
|
|
6973
|
+
apiBase,
|
|
6974
|
+
onResult: progress.onResult,
|
|
6975
|
+
onRetryRound: progress.onRetryRound
|
|
6976
|
+
});
|
|
6977
|
+
});
|
|
6978
|
+
progress.finish();
|
|
6979
|
+
console.log(formatTestSummary(result.summary));
|
|
6980
|
+
if (result.skipped) {
|
|
6981
|
+
console.log("");
|
|
6982
|
+
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"));
|
|
6983
|
+
} else if (result.removedProxies > 0) {
|
|
6984
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
6985
|
+
if (getRunningState().running) {
|
|
6986
|
+
console.log("");
|
|
6987
|
+
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
6988
|
+
}
|
|
7004
6989
|
}
|
|
7005
|
-
return activeSub;
|
|
7006
6990
|
}
|
|
6991
|
+
async function subTest(args) {
|
|
6992
|
+
const { target, timeout, concurrency } = resolveTestTarget(args);
|
|
6993
|
+
console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
6994
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6995
|
+
console.log("");
|
|
6996
|
+
const progress = createProgressPrinter();
|
|
6997
|
+
const summary = await withTestInstance(target.name, async (apiBase) => {
|
|
6998
|
+
return testSubscriptionProxies(target.name, {
|
|
6999
|
+
timeout,
|
|
7000
|
+
concurrency,
|
|
7001
|
+
apiBase,
|
|
7002
|
+
onResult: progress.onResult
|
|
7003
|
+
});
|
|
7004
|
+
});
|
|
7005
|
+
progress.finish();
|
|
7006
|
+
console.log(formatTestSummary(summary));
|
|
7007
|
+
}
|
|
7008
|
+
var SUBCOMMANDS4 = [
|
|
7009
|
+
{ name: "list", handler: printSubscriptionList },
|
|
7010
|
+
{ name: "add", handler: subAdd },
|
|
7011
|
+
{ name: "update", handler: subUpdate },
|
|
7012
|
+
{ name: "use", handler: subUse },
|
|
7013
|
+
{ name: "web", aliases: ["open"], handler: subWeb },
|
|
7014
|
+
{ name: "remove", aliases: ["rm", "delete"], handler: subRemove },
|
|
7015
|
+
{ name: "clean", handler: subClean },
|
|
7016
|
+
{ name: "test", handler: subTest }
|
|
7017
|
+
];
|
|
7018
|
+
async function cmdSubscription(args) {
|
|
7019
|
+
await dispatchSubcommand(args, SUBCOMMANDS4, {
|
|
7020
|
+
// 无子命令 → 列表;未知子命令 → 报错
|
|
7021
|
+
fallback: printSubscriptionList,
|
|
7022
|
+
onUnknown: (action) => {
|
|
7023
|
+
const names = SUBCOMMANDS4.flatMap((c) => [c.name, ...c.aliases ?? []]);
|
|
7024
|
+
const suggestion = suggestSimilar(action, names);
|
|
7025
|
+
throw new CliError(`\u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4: ${action}`, {
|
|
7026
|
+
hint: [...suggestion.length > 0 ? [`\u662F\u5426\u60F3\u8F93\u5165: ${suggestion.join(" / ")}?`] : [], "\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]"]
|
|
7027
|
+
});
|
|
7028
|
+
}
|
|
7029
|
+
});
|
|
7030
|
+
}
|
|
7031
|
+
|
|
7032
|
+
// src/commands/test.ts
|
|
7007
7033
|
async function cmdTest(args) {
|
|
7008
7034
|
requireRunning();
|
|
7009
|
-
const activeSub =
|
|
7035
|
+
const activeSub = requireActiveSubscription("\u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
|
|
7010
7036
|
const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
|
|
7011
7037
|
const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
|
|
7012
7038
|
console.log(`\u6D4B\u8BD5 "${activeSub.name}" \u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
@@ -7023,7 +7049,7 @@ async function cmdTest(args) {
|
|
|
7023
7049
|
}
|
|
7024
7050
|
async function cmdClean(args) {
|
|
7025
7051
|
requireRunning();
|
|
7026
|
-
const activeSub =
|
|
7052
|
+
const activeSub = requireActiveSubscription("\u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
|
|
7027
7053
|
const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
|
|
7028
7054
|
const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
|
|
7029
7055
|
const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
|
|
@@ -7054,9 +7080,7 @@ async function cmdClean(args) {
|
|
|
7054
7080
|
try {
|
|
7055
7081
|
if (!daemonManaged) {
|
|
7056
7082
|
if (hasRootResidue()) {
|
|
7057
|
-
|
|
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);
|
|
7083
|
+
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
7084
|
}
|
|
7061
7085
|
handleStopResult(stop());
|
|
7062
7086
|
}
|
|
@@ -7065,8 +7089,8 @@ async function cmdClean(args) {
|
|
|
7065
7089
|
const label = daemonManaged ? "\u5DF2\u91CD\u542F (\u4FDD\u6D3B)" : "\u5DF2\u91CD\u542F";
|
|
7066
7090
|
console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
|
|
7067
7091
|
} catch (e) {
|
|
7068
|
-
|
|
7069
|
-
|
|
7092
|
+
if (e instanceof CliError) throw e;
|
|
7093
|
+
throw new CliError(e.message.split("\n")[0], { label: "\u91CD\u542F\u5931\u8D25" });
|
|
7070
7094
|
}
|
|
7071
7095
|
}
|
|
7072
7096
|
}
|
|
@@ -7075,9 +7099,11 @@ async function cmdClean(args) {
|
|
|
7075
7099
|
function cmdUI(args) {
|
|
7076
7100
|
const uiName = args[1] || "zash";
|
|
7077
7101
|
if (!Object.hasOwn(UI_URLS, uiName)) {
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7102
|
+
throw new CliError(`\u672A\u77E5\u7684 UI "${uiName}"`, { hint: "\u53EF\u7528 UI: zash (\u9ED8\u8BA4), dash, yacd" });
|
|
7103
|
+
}
|
|
7104
|
+
if (!getRunningState().running) {
|
|
7105
|
+
console.log(colors.yellow("\u63D0\u793A: mihomo \u672A\u8FD0\u884C\uFF0CUI \u6682\u65F6\u65E0\u6CD5\u8FDE\u63A5\uFF08\u5148\u6267\u884C mihomo start \u542F\u52A8\uFF09"));
|
|
7106
|
+
console.log("");
|
|
7081
7107
|
}
|
|
7082
7108
|
const url = UI_URLS[uiName];
|
|
7083
7109
|
console.log(`\u6253\u5F00 Web UI: ${uiName}`);
|
|
@@ -7093,37 +7119,54 @@ function cmdUI(args) {
|
|
|
7093
7119
|
}
|
|
7094
7120
|
|
|
7095
7121
|
// src/commands/update.ts
|
|
7096
|
-
import {
|
|
7122
|
+
import { execFile, spawn as spawn3 } from "child_process";
|
|
7097
7123
|
import { promisify } from "util";
|
|
7098
|
-
var
|
|
7124
|
+
var execFileAsync = promisify(execFile);
|
|
7125
|
+
var NPM_VIEW_TIMEOUT_MS = 15e3;
|
|
7126
|
+
async function getLatestNpmVersion() {
|
|
7127
|
+
try {
|
|
7128
|
+
const { stdout } = await execFileAsync("npm", ["view", PKG_NAME, "version"], { timeout: NPM_VIEW_TIMEOUT_MS });
|
|
7129
|
+
const version = stdout.trim().split("\n").pop()?.trim();
|
|
7130
|
+
return version || null;
|
|
7131
|
+
} catch {
|
|
7132
|
+
return null;
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
7099
7135
|
async function cmdUpdate() {
|
|
7100
7136
|
console.log(`\u5F53\u524D\u7248\u672C: ${colors.cyan(VERSION)}`);
|
|
7101
7137
|
console.log("");
|
|
7138
|
+
console.log("\u6B63\u5728\u68C0\u67E5\u6700\u65B0\u7248\u672C...");
|
|
7139
|
+
const latest = await getLatestNpmVersion();
|
|
7140
|
+
if (latest && latest === VERSION) {
|
|
7141
|
+
console.log(`\u5DF2\u662F\u6700\u65B0\u7248\u672C (${colors.green(VERSION)})\uFF0C\u65E0\u9700\u66F4\u65B0`);
|
|
7142
|
+
return;
|
|
7143
|
+
}
|
|
7144
|
+
if (latest) {
|
|
7145
|
+
console.log(`\u6700\u65B0\u7248\u672C: ${colors.cyan(latest)}`);
|
|
7146
|
+
} else {
|
|
7147
|
+
console.log(colors.yellow("\u65E0\u6CD5\u67E5\u8BE2\u6700\u65B0\u7248\u672C\uFF08\u7F51\u7EDC\u95EE\u9898\uFF1F\uFF09\uFF0C\u5C06\u76F4\u63A5\u5C1D\u8BD5\u91CD\u65B0\u5B89\u88C5"));
|
|
7148
|
+
}
|
|
7149
|
+
console.log("");
|
|
7102
7150
|
console.log("\u6B63\u5728\u66F4\u65B0 mihomo-cli...");
|
|
7103
7151
|
console.log("");
|
|
7104
|
-
await new Promise((resolve) => {
|
|
7105
|
-
const npm = spawn3("npm", ["install", "-g",
|
|
7152
|
+
await new Promise((resolve, reject) => {
|
|
7153
|
+
const npm = spawn3("npm", ["install", "-g", PKG_NAME], { stdio: "inherit" });
|
|
7106
7154
|
npm.on("close", (code) => {
|
|
7107
7155
|
if (code === 0) {
|
|
7108
7156
|
resolve();
|
|
7109
7157
|
} else {
|
|
7110
|
-
|
|
7111
|
-
process.exit(code || 1);
|
|
7158
|
+
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
7159
|
}
|
|
7113
7160
|
});
|
|
7114
7161
|
npm.on("error", (e) => {
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
} else {
|
|
7118
|
-
console.error(`\u6267\u884C\u5931\u8D25: ${e.message}`);
|
|
7119
|
-
}
|
|
7120
|
-
process.exit(1);
|
|
7162
|
+
const perm = e.message.includes("EACCES") || e.message.includes("permission");
|
|
7163
|
+
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
7164
|
});
|
|
7122
7165
|
});
|
|
7123
7166
|
try {
|
|
7124
|
-
const { stdout } = await
|
|
7167
|
+
const { stdout } = await execFileAsync("npm", ["list", "-g", PKG_NAME, "--json", "--depth=0"]);
|
|
7125
7168
|
const result = JSON.parse(stdout);
|
|
7126
|
-
const newVersion = result.dependencies?.[
|
|
7169
|
+
const newVersion = result.dependencies?.[PKG_NAME]?.version;
|
|
7127
7170
|
console.log("");
|
|
7128
7171
|
if (newVersion) {
|
|
7129
7172
|
console.log(`\u66F4\u65B0\u5B8C\u6210\uFF0C\u6700\u65B0\u7248\u672C: ${colors.green(newVersion)}`);
|
|
@@ -7328,6 +7371,9 @@ var COMMAND_INDEX = (() => {
|
|
|
7328
7371
|
function findCommand(token) {
|
|
7329
7372
|
return COMMAND_INDEX.get(token);
|
|
7330
7373
|
}
|
|
7374
|
+
function allCommandTokens() {
|
|
7375
|
+
return [...COMMAND_INDEX.keys()];
|
|
7376
|
+
}
|
|
7331
7377
|
|
|
7332
7378
|
// src/index.ts
|
|
7333
7379
|
process.on("SIGINT", () => {
|
|
@@ -7377,14 +7423,23 @@ async function main() {
|
|
|
7377
7423
|
const token = args[0].toLowerCase();
|
|
7378
7424
|
const command = findCommand(token);
|
|
7379
7425
|
if (!command) {
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7426
|
+
const suggestion = suggestSimilar(token, allCommandTokens());
|
|
7427
|
+
throw new CliError(`\u672A\u77E5\u547D\u4EE4: ${token}`, {
|
|
7428
|
+
hint: [suggestion.length > 0 ? `\u662F\u5426\u60F3\u8F93\u5165: ${suggestion.join(" / ")}?` : '\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9']
|
|
7429
|
+
});
|
|
7383
7430
|
}
|
|
7384
7431
|
await command.handler(command.rewrite ? command.rewrite(args) : args);
|
|
7385
7432
|
}
|
|
7386
7433
|
main().catch((e) => {
|
|
7387
|
-
|
|
7434
|
+
if (e instanceof CliError) {
|
|
7435
|
+
console.error(`${colors.red(`${e.label}:`)} ${e.message}`);
|
|
7436
|
+
for (const line of e.hint) console.error(line);
|
|
7437
|
+
runCleanup();
|
|
7438
|
+
process.exit(e.exitCode);
|
|
7439
|
+
}
|
|
7440
|
+
const err = e;
|
|
7441
|
+
console.error(`${colors.red("\u9519\u8BEF:")} ${err.message}`);
|
|
7442
|
+
if (err.stack) console.error(err.stack.split("\n").slice(1).join("\n"));
|
|
7388
7443
|
runCleanup();
|
|
7389
7444
|
process.exit(1);
|
|
7390
7445
|
});
|