mihomo-cli 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/index.js +1351 -1253
  3. package/package.json +6 -2
package/dist/index.js CHANGED
@@ -6,7 +6,6 @@ import fs4 from "fs";
6
6
 
7
7
  // node_modules/js-yaml/dist/js-yaml.mjs
8
8
  var NOT_RESOLVED = /* @__PURE__ */ Symbol("NOT_RESOLVED");
9
- var MERGE_KEY = /* @__PURE__ */ Symbol("MERGE_KEY");
10
9
  function defineScalarTag(tagName, options) {
11
10
  return {
12
11
  tagName,
@@ -15,9 +14,9 @@ function defineScalarTag(tagName, options) {
15
14
  matchByTagPrefix: options.matchByTagPrefix ?? false,
16
15
  implicitFirstChars: options.implicitFirstChars ?? null,
17
16
  resolve: options.resolve,
18
- identify: options.identify ?? null,
17
+ identify: options.identify,
19
18
  represent: options.represent ?? ((data) => String(data)),
20
- representTagName: options.representTagName ?? null
19
+ representTagName: options.representTagName ?? (() => tagName)
21
20
  };
22
21
  }
23
22
  function defineSequenceTag(tagName, options) {
@@ -31,9 +30,9 @@ function defineSequenceTag(tagName, options) {
31
30
  addItem: options.addItem,
32
31
  finalize: options.finalize ?? ((carrier) => carrier),
33
32
  carrierIsResult,
34
- identify: options.identify ?? null,
33
+ identify: options.identify,
35
34
  represent: options.represent ?? ((data) => data),
36
- representTagName: options.representTagName ?? null
35
+ representTagName: options.representTagName ?? (() => tagName)
37
36
  };
38
37
  }
39
38
  function defineMappingTag(tagName, options) {
@@ -50,9 +49,9 @@ function defineMappingTag(tagName, options) {
50
49
  get: options.get,
51
50
  finalize: options.finalize ?? ((carrier) => carrier),
52
51
  carrierIsResult,
53
- identify: options.identify ?? null,
52
+ identify: options.identify,
54
53
  represent: options.represent ?? ((data) => data),
55
- representTagName: options.representTagName ?? null
54
+ representTagName: options.representTagName ?? (() => tagName)
56
55
  };
57
56
  }
58
57
  var strTag = defineScalarTag("tag:yaml.org,2002:str", {
@@ -401,9 +400,10 @@ var mergeTag = defineScalarTag("tag:yaml.org,2002:merge", {
401
400
  implicit: true,
402
401
  implicitFirstChars: ["<"],
403
402
  resolve: (source, isExplicit) => {
404
- if (source === "<<" || isExplicit && source === "") return MERGE_KEY;
403
+ if (source === "<<" || isExplicit && source === "") return "<<";
405
404
  return NOT_RESOLVED;
406
- }
405
+ },
406
+ identify: () => false
407
407
  });
408
408
  var BASE64_PATTERN = /^[A-Za-z0-9+/]*={0,2}$/;
409
409
  function resolveYamlBinary(source) {
@@ -426,6 +426,11 @@ var binaryTag = defineScalarTag("tag:yaml.org,2002:binary", {
426
426
  });
427
427
  var YAML_DATE_REGEXP = /* @__PURE__ */ new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$");
428
428
  var YAML_TIMESTAMP_REGEXP = /* @__PURE__ */ new RegExp("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$");
429
+ function makeUtcDate(year, month, day, hour = 0, minute = 0, second = 0, fraction = 0) {
430
+ const date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction));
431
+ date.setUTCFullYear(year, month, day);
432
+ return date;
433
+ }
429
434
  function resolveYamlTimestamp(source) {
430
435
  let match = YAML_DATE_REGEXP.exec(source);
431
436
  if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(source);
@@ -434,7 +439,7 @@ function resolveYamlTimestamp(source) {
434
439
  const month = +match[2] - 1;
435
440
  const day = +match[3];
436
441
  if (!match[4]) {
437
- const date2 = new Date(Date.UTC(year, month, day));
442
+ const date2 = makeUtcDate(year, month, day);
438
443
  if (date2.getUTCFullYear() !== year || date2.getUTCMonth() !== month || date2.getUTCDate() !== day) return NOT_RESOLVED;
439
444
  return date2;
440
445
  }
@@ -448,7 +453,7 @@ function resolveYamlTimestamp(source) {
448
453
  while (value.length < 3) value += "0";
449
454
  fraction = +value;
450
455
  }
451
- const date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction));
456
+ const date = makeUtcDate(year, month, day, hour, minute, second, fraction);
452
457
  if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month || date.getUTCDate() !== day) return NOT_RESOLVED;
453
458
  if (match[9]) {
454
459
  const offsetHour = +match[10];
@@ -503,7 +508,8 @@ var omapTag = defineSequenceTag("tag:yaml.org,2002:omap", {
503
508
  carrier.list.push(item);
504
509
  return "";
505
510
  },
506
- finalize: (carrier) => carrier.list
511
+ finalize: (carrier) => carrier.list,
512
+ identify: () => false
507
513
  });
508
514
  var pairsTag = defineSequenceTag("tag:yaml.org,2002:pairs", {
509
515
  create: () => [],
@@ -519,7 +525,8 @@ var pairsTag = defineSequenceTag("tag:yaml.org,2002:pairs", {
519
525
  if (keys.length !== 1) return "cannot resolve a pairs item";
520
526
  container.push([keys[0], object[keys[0]]]);
521
527
  return "";
522
- }
528
+ },
529
+ identify: () => false
523
530
  });
524
531
  var mapTag = defineMappingTag("tag:yaml.org,2002:map", {
525
532
  create: () => ({}),
@@ -546,7 +553,11 @@ var mapTag = defineMappingTag("tag:yaml.org,2002:map", {
546
553
  return Object.prototype.hasOwnProperty.call(container, String(key));
547
554
  },
548
555
  keys: (container) => Object.keys(container),
549
- get: (container, key) => container[String(key)]
556
+ get: (container, key) => {
557
+ const normalizedKey = String(key);
558
+ if (!Object.prototype.hasOwnProperty.call(container, normalizedKey)) return null;
559
+ return container[normalizedKey];
560
+ }
550
561
  });
551
562
  var setTag = defineMappingTag("tag:yaml.org,2002:set", {
552
563
  create: () => /* @__PURE__ */ new Set(),
@@ -567,9 +578,9 @@ var setTag = defineMappingTag("tag:yaml.org,2002:set", {
567
578
  });
568
579
  function createTagDefinitionMap() {
569
580
  return {
570
- scalar: {},
571
- sequence: {},
572
- mapping: {}
581
+ scalar: /* @__PURE__ */ Object.create(null),
582
+ sequence: /* @__PURE__ */ Object.create(null),
583
+ mapping: /* @__PURE__ */ Object.create(null)
573
584
  };
574
585
  }
575
586
  function createTagDefinitionListMap() {
@@ -596,11 +607,35 @@ function compileTags(tags) {
596
607
  }
597
608
  var Schema = class Schema2 {
598
609
  tags;
610
+ /** @internal */
599
611
  implicitScalarTags;
612
+ /**
613
+ * Dispatch implicit scalar resolvers by `source.charAt(0)`. Each bucket holds
614
+ * the resolvers that may match that key, in schema order; a key absent from
615
+ * the map uses
616
+ * {@link Schema.implicitScalarAnyFirstChar}
617
+ * (resolvers that declared no first-char constraint, so they apply to any
618
+ * first character).
619
+ */
600
620
  implicitScalarByFirstChar;
601
621
  implicitScalarAnyFirstChar;
622
+ /**
623
+ * The default scalar tag (`!!str`), resolved once so the composer's fallback
624
+ * for unresolved plain scalars avoids a keyed lookup per scalar.
625
+ *
626
+ * @internal
627
+ */
602
628
  defaultScalarTag;
629
+ /**
630
+ * The default container tags (`!!seq` / `!!map`), used by the dumper: when a
631
+ * value is identified by its default tag, the tag is implicit and not
632
+ * printed. Undefined if the schema does not define them (then such values
633
+ * can't be dumped).
634
+ *
635
+ * @internal
636
+ */
603
637
  defaultSequenceTag;
638
+ /** @internal */
604
639
  defaultMappingTag;
605
640
  exact;
606
641
  prefix;
@@ -646,6 +681,52 @@ var Schema = class Schema2 {
646
681
  this.exact = exact;
647
682
  this.prefix = prefix;
648
683
  }
684
+ /** @internal */
685
+ lookupScalarTag(tagName) {
686
+ const exactTag = this.exact.scalar[tagName];
687
+ if (exactTag) return exactTag;
688
+ for (const tag of this.prefix.scalar) if (tagName.startsWith(tag.tagName)) return tag;
689
+ }
690
+ /** @internal */
691
+ lookupSequenceTag(tagName) {
692
+ const exactTag = this.exact.sequence[tagName];
693
+ if (exactTag) return exactTag;
694
+ for (const tag of this.prefix.sequence) if (tagName.startsWith(tag.tagName)) return tag;
695
+ }
696
+ /** @internal */
697
+ lookupMappingTag(tagName) {
698
+ const exactTag = this.exact.mapping[tagName];
699
+ if (exactTag) return exactTag;
700
+ for (const tag of this.prefix.mapping) if (tagName.startsWith(tag.tagName)) return tag;
701
+ }
702
+ /** @internal */
703
+ resolveImplicitScalarTag(source) {
704
+ const candidates = this.implicitScalarByFirstChar.get(source.charAt(0)) ?? this.implicitScalarAnyFirstChar;
705
+ for (const tag2 of candidates) {
706
+ const value = tag2.resolve(source, false, tag2.tagName);
707
+ if (value !== NOT_RESOLVED) return {
708
+ value,
709
+ tag: tag2
710
+ };
711
+ }
712
+ const tag = this.defaultScalarTag;
713
+ return {
714
+ value: tag.resolve(source, false, tag.tagName),
715
+ tag
716
+ };
717
+ }
718
+ /**
719
+ * Creates a new schema with the specified tags added. If a tag already
720
+ * exists, it is replaced by the specified tag.
721
+ *
722
+ * @example
723
+ *
724
+ * ```javascript
725
+ * import { CORE_SCHEMA, mergeTag, realMapTag } from 'js-yaml'
726
+ *
727
+ * const schema = CORE_SCHEMA.withTags(mergeTag, realMapTag)
728
+ * ```
729
+ */
649
730
  withTags(...tags) {
650
731
  let flatTags = [];
651
732
  for (const tag of tags) flatTags = flatTags.concat(tag);
@@ -684,6 +765,19 @@ var YAML11_SCHEMA = new Schema([
684
765
  pairsTag,
685
766
  setTag
686
767
  ]);
768
+ var DUMP_SCHEMA = YAML11_SCHEMA.withTags({
769
+ ...intYaml11Tag,
770
+ resolve: (source, isExplicit, tagName) => {
771
+ const result = intYaml11Tag.resolve(source, isExplicit, tagName);
772
+ return result === NOT_RESOLVED ? intCoreTag.resolve(source, isExplicit, tagName) : result;
773
+ }
774
+ }, {
775
+ ...floatYaml11Tag,
776
+ resolve: (source, isExplicit, tagName) => {
777
+ const result = floatYaml11Tag.resolve(source, isExplicit, tagName);
778
+ return result === NOT_RESOLVED ? floatCoreTag.resolve(source, isExplicit, tagName) : result;
779
+ }
780
+ });
687
781
  var realMapTag = defineMappingTag("tag:yaml.org,2002:map", {
688
782
  create: () => /* @__PURE__ */ new Map(),
689
783
  addPair: (container, key, value) => {
@@ -739,7 +833,11 @@ var legacyMapTag = defineMappingTag("tag:yaml.org,2002:map", {
739
833
  return normalizedKey !== null && Object.prototype.hasOwnProperty.call(container, normalizedKey);
740
834
  },
741
835
  keys: (container) => Object.keys(container),
742
- get: (container, key) => container[String(key)]
836
+ get: (container, key) => {
837
+ const normalizedKey = String(key);
838
+ if (!Object.prototype.hasOwnProperty.call(container, normalizedKey)) return null;
839
+ return container[normalizedKey];
840
+ }
743
841
  });
744
842
  var DEFAULT_SNIPPET_OPTIONS = {
745
843
  maxLength: 79,
@@ -816,9 +914,13 @@ function formatError(exception, compact) {
816
914
  ${exception.mark.snippet}`;
817
915
  return `${exception.reason} ${where}`;
818
916
  }
819
- var YAMLException = class extends Error {
917
+ var YAMLException = class YAMLException2 extends Error {
820
918
  reason;
821
919
  mark;
920
+ /**
921
+ * Optional `mark` contains source snippet data. Usually, use
922
+ * {@link YAMLException.throwAt} instead of passing it directly.
923
+ */
822
924
  constructor(reason, mark) {
823
925
  super();
824
926
  this.name = "YAMLException";
@@ -827,34 +929,65 @@ var YAMLException = class extends Error {
827
929
  this.message = formatError(this, false);
828
930
  if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
829
931
  }
932
+ /**
933
+ * Returns the formatted error, omitting the source snippet in compact mode.
934
+ */
830
935
  toString(compact) {
831
936
  return `${this.name}: ${formatError(this, compact)}`;
832
937
  }
833
- };
834
- function throwErrorAt(source, position, message, filename = "") {
835
- let line = 0;
836
- let lineStart = 0;
837
- for (let index = 0; index < position; index++) {
838
- const ch = source.charCodeAt(index);
839
- if (ch === 10) {
840
- line++;
841
- lineStart = index + 1;
842
- } else if (ch === 13) {
843
- line++;
844
- if (source.charCodeAt(index + 1) === 10) index++;
845
- lineStart = index + 1;
938
+ /**
939
+ * Builds a YAMLException with a source snippet and throws it. `source` is
940
+ * the raw input text; `position` is an offset into it.
941
+ */
942
+ static throwAt(source, position, message, filename = "") {
943
+ let line = 0;
944
+ let lineStart = 0;
945
+ for (let index = 0; index < position; index++) {
946
+ const ch = source.charCodeAt(index);
947
+ if (ch === 10) {
948
+ line++;
949
+ lineStart = index + 1;
950
+ } else if (ch === 13) {
951
+ line++;
952
+ if (source.charCodeAt(index + 1) === 10) index++;
953
+ lineStart = index + 1;
954
+ }
846
955
  }
956
+ const mark = {
957
+ name: filename,
958
+ buffer: source,
959
+ position,
960
+ line,
961
+ column: position - lineStart
962
+ };
963
+ mark.snippet = makeSnippet(mark);
964
+ throw new YAMLException2(message, mark);
847
965
  }
848
- const mark = {
849
- name: filename,
850
- buffer: source,
851
- position,
852
- line,
853
- column: position - lineStart
854
- };
855
- mark.snippet = makeSnippet(mark);
856
- throw new YAMLException(message, mark);
857
- }
966
+ };
967
+ var EVENT_ID = {
968
+ DOCUMENT: 1,
969
+ SEQUENCE: 2,
970
+ MAPPING: 3,
971
+ SCALAR: 4,
972
+ ALIAS: 5,
973
+ POP: 6
974
+ };
975
+ var SCALAR_STYLE = {
976
+ PLAIN: 1,
977
+ SINGLE_QUOTED: 2,
978
+ DOUBLE_QUOTED: 3,
979
+ LITERAL_BLOCK: 4,
980
+ FOLDED_BLOCK: 5
981
+ };
982
+ var COLLECTION_STYLE = {
983
+ BLOCK: 1,
984
+ FLOW: 2
985
+ };
986
+ var CHOMPING_MODE = {
987
+ CLIP: 1,
988
+ STRIP: 2,
989
+ KEEP: 3
990
+ };
858
991
  var NO_RANGE$3 = -1;
859
992
  function simpleEscapeSequence(c) {
860
993
  switch (c) {
@@ -1052,8 +1185,8 @@ function getBlockValue(input, start2, end, indent, chomping, folded) {
1052
1185
  didReadContent = true;
1053
1186
  emptyLines = 0;
1054
1187
  }
1055
- if (chomping === 3) result += "\n".repeat(didReadContent ? 1 + emptyLines : emptyLines);
1056
- else if (chomping !== 2) {
1188
+ if (chomping === CHOMPING_MODE.KEEP) result += "\n".repeat(didReadContent ? 1 + emptyLines : emptyLines);
1189
+ else if (chomping !== CHOMPING_MODE.STRIP) {
1057
1190
  if (didReadContent) result += "\n";
1058
1191
  }
1059
1192
  return result;
@@ -1063,22 +1196,22 @@ function getScalarValue(input, scalar) {
1063
1196
  const { valueStart, valueEnd } = scalar;
1064
1197
  if (scalar.fast) return input.slice(valueStart, valueEnd);
1065
1198
  switch (scalar.style) {
1066
- case 2:
1199
+ case SCALAR_STYLE.SINGLE_QUOTED:
1067
1200
  return getSingleQuotedValue(input, valueStart, valueEnd);
1068
- case 3:
1201
+ case SCALAR_STYLE.DOUBLE_QUOTED:
1069
1202
  return getDoubleQuotedValue(input, valueStart, valueEnd);
1070
- case 4:
1203
+ case SCALAR_STYLE.LITERAL_BLOCK:
1071
1204
  return getBlockValue(input, valueStart, valueEnd, scalar.indent, scalar.chomping, false);
1072
- case 5:
1205
+ case SCALAR_STYLE.FOLDED_BLOCK:
1073
1206
  return getBlockValue(input, valueStart, valueEnd, scalar.indent, scalar.chomping, true);
1074
1207
  default:
1075
1208
  return getPlainValue(input, valueStart, valueEnd);
1076
1209
  }
1077
1210
  }
1078
- var DEFAULT_TAG_HANDLERS = {
1211
+ var DEFAULT_TAG_HANDLERS = Object.assign(/* @__PURE__ */ Object.create(null), {
1079
1212
  "!": "!",
1080
1213
  "!!": "tag:yaml.org,2002:"
1081
- };
1214
+ });
1082
1215
  function tagPercentEncode(source) {
1083
1216
  return encodeURI(source).replace(/!/g, "%21");
1084
1217
  }
@@ -1099,6 +1232,7 @@ function tagNameShort(fullTag) {
1099
1232
  return `!<${tagPercentEncode(tag)}>`;
1100
1233
  }
1101
1234
  var NO_RANGE$2 = -1;
1235
+ var MERGE_TAG_NAME = "tag:yaml.org,2002:merge";
1102
1236
  var DEFAULT_CONSTRUCTOR_OPTIONS = {
1103
1237
  filename: "",
1104
1238
  schema: CORE_SCHEMA,
@@ -1114,26 +1248,16 @@ function eventPosition$1(event) {
1114
1248
  return 0;
1115
1249
  }
1116
1250
  function throwError$1(state, message) {
1117
- throwErrorAt(state.source, state.position, message, state.filename);
1251
+ YAMLException.throwAt(state.source, state.position, message, state.filename);
1118
1252
  }
1119
1253
  function finalizeCollection(state, position, tag, carrier) {
1120
1254
  try {
1121
1255
  return tag.finalize(carrier);
1122
1256
  } catch (error) {
1123
1257
  if (error instanceof YAMLException) throw error;
1124
- throwErrorAt(state.source, position, error instanceof Error ? error.message : String(error), state.filename);
1258
+ YAMLException.throwAt(state.source, position, error instanceof Error ? error.message : String(error), state.filename);
1125
1259
  }
1126
1260
  }
1127
- function lookupTag(exact, prefix, tagName) {
1128
- const exactTag = exact[tagName];
1129
- if (exactTag) return exactTag;
1130
- for (const tag of prefix) if (tagName.startsWith(tag.tagName)) return tag;
1131
- }
1132
- function findExplicitTag(state, exact, prefix, tagName, nodeKind) {
1133
- const tag = lookupTag(exact, prefix, tagName);
1134
- if (tag) return tag;
1135
- throwError$1(state, `unknown ${nodeKind} tag !<${tagName}>`);
1136
- }
1137
1261
  function constructScalar(state, event) {
1138
1262
  const source = getScalarValue(state.source, event);
1139
1263
  const rawTag = event.tagStart === NO_RANGE$2 ? "" : state.source.slice(event.tagStart, event.tagEnd);
@@ -1144,7 +1268,7 @@ function constructScalar(state, event) {
1144
1268
  tag: strTag2
1145
1269
  };
1146
1270
  const tagName = tagNameFull(rawTag, state.tagHandlers);
1147
- const scalarTag = lookupTag(state.schema.exact.scalar, state.schema.prefix.scalar, tagName);
1271
+ const scalarTag = state.schema.lookupScalarTag(tagName);
1148
1272
  if (scalarTag) {
1149
1273
  const result = scalarTag.resolve(source, true, tagName);
1150
1274
  if (result === NOT_RESOLVED) throwError$1(state, `cannot resolve a node with !<${tagName}> explicit tag`);
@@ -1153,7 +1277,7 @@ function constructScalar(state, event) {
1153
1277
  tag: scalarTag
1154
1278
  };
1155
1279
  }
1156
- const collectionTagDef = lookupTag(state.schema.exact.mapping, state.schema.prefix.mapping, tagName) ?? lookupTag(state.schema.exact.sequence, state.schema.prefix.sequence, tagName);
1280
+ const collectionTagDef = state.schema.lookupMappingTag(tagName) ?? state.schema.lookupSequenceTag(tagName);
1157
1281
  if (collectionTagDef) {
1158
1282
  if (source !== "") throwError$1(state, `cannot resolve a node with !<${tagName}> explicit tag`);
1159
1283
  const carrier = collectionTagDef.create(tagName);
@@ -1164,28 +1288,15 @@ function constructScalar(state, event) {
1164
1288
  }
1165
1289
  throwError$1(state, `unknown scalar tag !<${tagName}>`);
1166
1290
  }
1167
- if (event.style === 1) {
1168
- const candidates = state.schema.implicitScalarByFirstChar.get(source.charAt(0)) ?? state.schema.implicitScalarAnyFirstChar;
1169
- for (const tag of candidates) {
1170
- const result = tag.resolve(source, false, tag.tagName);
1171
- if (result !== NOT_RESOLVED) return {
1172
- value: result,
1173
- tag
1174
- };
1175
- }
1176
- }
1291
+ if (event.style === SCALAR_STYLE.PLAIN) return state.schema.resolveImplicitScalarTag(source);
1177
1292
  return {
1178
1293
  value: strTag2.resolve(source, false, strTag2.tagName),
1179
1294
  tag: strTag2
1180
1295
  };
1181
1296
  }
1182
- function collectionTag(state, event, exact, prefix, defaultTagName, nodeKind) {
1297
+ function collectionTagName(state, event, defaultTagName) {
1183
1298
  const rawTag = event.tagStart === NO_RANGE$2 ? "" : state.source.slice(event.tagStart, event.tagEnd);
1184
- const tagName = rawTag === "" || rawTag === "!" ? defaultTagName : tagNameFull(rawTag, state.tagHandlers);
1185
- return {
1186
- tagName,
1187
- tag: findExplicitTag(state, exact, prefix, tagName, nodeKind)
1188
- };
1299
+ return rawTag === "" || rawTag === "!" ? defaultTagName : tagNameFull(rawTag, state.tagHandlers);
1189
1300
  }
1190
1301
  function isMappingTag(tag) {
1191
1302
  return tag.nodeKind === "mapping";
@@ -1202,12 +1313,16 @@ function mergeKeys(state, frame, source, sourceTag) {
1202
1313
  function mergeSource(state, frame, source, sourceTag) {
1203
1314
  state.position = frame.keyPosition;
1204
1315
  if (isMappingTag(sourceTag)) mergeKeys(state, frame, source, sourceTag);
1205
- else if (sourceTag.nodeKind === "sequence" && Array.isArray(source)) for (const element of source) mergeKeys(state, frame, element, frame.tag);
1316
+ else if (sourceTag.nodeKind === "sequence" && Array.isArray(source)) for (const element of source) {
1317
+ const elementTag = state.nodeTags.get(element);
1318
+ if (!elementTag) throwError$1(state, "cannot merge mappings; the provided source object is unacceptable");
1319
+ mergeKeys(state, frame, element, elementTag);
1320
+ }
1206
1321
  else throwError$1(state, "cannot merge mappings; the provided source object is unacceptable");
1207
1322
  }
1208
1323
  function addMappingValue(state, frame, key, value, tag) {
1209
1324
  state.position = frame.keyPosition;
1210
- if (key === MERGE_KEY) {
1325
+ if (frame.keyIsMerge) {
1211
1326
  mergeSource(state, frame, value, tag);
1212
1327
  return;
1213
1328
  }
@@ -1222,9 +1337,7 @@ function addValue(state, value, tag) {
1222
1337
  frame.value = value;
1223
1338
  frame.hasValue = true;
1224
1339
  } else if (frame.kind === "sequence") {
1225
- if (frame.merge) {
1226
- if (!isMappingTag(tag)) throwError$1(state, "cannot merge mappings; the provided source object is unacceptable");
1227
- }
1340
+ if (isMappingTag(tag)) state.nodeTags.set(value, tag);
1228
1341
  const err = frame.tag.addItem(frame.value, value, frame.index++);
1229
1342
  if (err) throwError$1(state, err);
1230
1343
  } else if (frame.hasKey) {
@@ -1236,6 +1349,7 @@ function addValue(state, value, tag) {
1236
1349
  frame.key = value;
1237
1350
  frame.keyPosition = state.position;
1238
1351
  frame.hasKey = true;
1352
+ frame.keyIsMerge = tag.tagName === MERGE_TAG_NAME;
1239
1353
  }
1240
1354
  }
1241
1355
  function storeAnchor(state, event, value, tag, isValueFinal) {
@@ -1260,6 +1374,7 @@ function constructFromEvents(events, options) {
1260
1374
  position: 0,
1261
1375
  frames: [],
1262
1376
  anchors: /* @__PURE__ */ new Map(),
1377
+ nodeTags: /* @__PURE__ */ new Map(),
1263
1378
  tagHandlers: /* @__PURE__ */ Object.create(null),
1264
1379
  totalMergeKeys: 0,
1265
1380
  aliasCount: 0
@@ -1268,8 +1383,9 @@ function constructFromEvents(events, options) {
1268
1383
  const event = state.events[state.eventIndex++];
1269
1384
  state.position = eventPosition$1(event);
1270
1385
  switch (event.type) {
1271
- case 1:
1386
+ case EVENT_ID.DOCUMENT:
1272
1387
  state.anchors = /* @__PURE__ */ new Map();
1388
+ state.nodeTags = /* @__PURE__ */ new Map();
1273
1389
  state.aliasCount = 0;
1274
1390
  state.tagHandlers = /* @__PURE__ */ Object.create(null);
1275
1391
  for (const directive of event.directives) if (directive.kind === "tag") state.tagHandlers[directive.handle] = directive.prefix;
@@ -1280,47 +1396,49 @@ function constructFromEvents(events, options) {
1280
1396
  hasValue: false
1281
1397
  });
1282
1398
  break;
1283
- case 4: {
1399
+ case EVENT_ID.SCALAR: {
1284
1400
  const { value, tag } = constructScalar(state, event);
1285
1401
  storeAnchor(state, event, value, tag, true);
1286
1402
  addValue(state, value, tag);
1287
1403
  break;
1288
1404
  }
1289
- case 2: {
1290
- const definition = collectionTag(state, event, state.schema.exact.sequence, state.schema.prefix.sequence, "tag:yaml.org,2002:seq", "sequence");
1291
- const value = definition.tag.create(definition.tagName);
1292
- const anchor = storeAnchor(state, event, value, definition.tag, definition.tag.carrierIsResult);
1293
- const parent = state.frames[state.frames.length - 1];
1294
- const merge = parent !== void 0 && parent.kind === "mapping" && parent.hasKey && parent.key === MERGE_KEY;
1405
+ case EVENT_ID.SEQUENCE: {
1406
+ const tagName = collectionTagName(state, event, "tag:yaml.org,2002:seq");
1407
+ const tag = state.schema.lookupSequenceTag(tagName);
1408
+ if (!tag) throwError$1(state, `unknown sequence tag !<${tagName}>`);
1409
+ const value = tag.create(tagName);
1410
+ const anchor = storeAnchor(state, event, value, tag, tag.carrierIsResult);
1295
1411
  state.frames.push({
1296
1412
  kind: "sequence",
1297
1413
  position: state.position,
1298
1414
  value,
1299
- tag: definition.tag,
1415
+ tag,
1300
1416
  anchor,
1301
- index: 0,
1302
- merge
1417
+ index: 0
1303
1418
  });
1304
1419
  break;
1305
1420
  }
1306
- case 3: {
1307
- const definition = collectionTag(state, event, state.schema.exact.mapping, state.schema.prefix.mapping, "tag:yaml.org,2002:map", "mapping");
1308
- const value = definition.tag.create(definition.tagName);
1309
- const anchor = storeAnchor(state, event, value, definition.tag, definition.tag.carrierIsResult);
1421
+ case EVENT_ID.MAPPING: {
1422
+ const tagName = collectionTagName(state, event, "tag:yaml.org,2002:map");
1423
+ const tag = state.schema.lookupMappingTag(tagName);
1424
+ if (!tag) throwError$1(state, `unknown mapping tag !<${tagName}>`);
1425
+ const value = tag.create(tagName);
1426
+ const anchor = storeAnchor(state, event, value, tag, tag.carrierIsResult);
1310
1427
  state.frames.push({
1311
1428
  kind: "mapping",
1312
1429
  position: state.position,
1313
1430
  value,
1314
- tag: definition.tag,
1431
+ tag,
1315
1432
  anchor,
1316
1433
  key: void 0,
1317
1434
  keyPosition: state.position,
1318
1435
  hasKey: false,
1436
+ keyIsMerge: false,
1319
1437
  overridable: null
1320
1438
  });
1321
1439
  break;
1322
1440
  }
1323
- case 5: {
1441
+ case EVENT_ID.ALIAS: {
1324
1442
  if (state.maxAliases !== -1 && ++state.aliasCount > state.maxAliases) throwError$1(state, `aliases exceeded maxAliases (${state.maxAliases})`);
1325
1443
  const name = state.source.slice(event.anchorStart, event.anchorEnd);
1326
1444
  const anchor = state.anchors.get(name);
@@ -1329,8 +1447,12 @@ function constructFromEvents(events, options) {
1329
1447
  addValue(state, anchor.value, anchor.tag);
1330
1448
  break;
1331
1449
  }
1332
- case 6: {
1450
+ case EVENT_ID.POP: {
1333
1451
  const frame = state.frames.pop();
1452
+ if (frame.kind === "mapping" && frame.hasKey) {
1453
+ state.position = frame.keyPosition;
1454
+ throwError$1(state, "incomplete mapping pair in event stream");
1455
+ }
1334
1456
  if (frame.kind === "document") state.documents.push(frame.value);
1335
1457
  else {
1336
1458
  const value = frame.tag.carrierIsResult ? frame.value : finalizeCollection(state, frame.position, frame.tag, frame.value);
@@ -1366,7 +1488,7 @@ var DEFAULT_PARSER_OPTIONS = {
1366
1488
  };
1367
1489
  function addDocumentEvent(state, explicitStart, explicitEnd) {
1368
1490
  state.events.push({
1369
- type: 1,
1491
+ type: EVENT_ID.DOCUMENT,
1370
1492
  explicitStart,
1371
1493
  explicitEnd,
1372
1494
  directives: state.directives
@@ -1374,7 +1496,7 @@ function addDocumentEvent(state, explicitStart, explicitEnd) {
1374
1496
  }
1375
1497
  function addSequenceEvent(state, start2, anchorStart, anchorEnd, tagStart, tagEnd, style) {
1376
1498
  state.events.push({
1377
- type: 2,
1499
+ type: EVENT_ID.SEQUENCE,
1378
1500
  start: start2,
1379
1501
  anchorStart,
1380
1502
  anchorEnd,
@@ -1385,7 +1507,7 @@ function addSequenceEvent(state, start2, anchorStart, anchorEnd, tagStart, tagEn
1385
1507
  }
1386
1508
  function addMappingEvent(state, start2, anchorStart, anchorEnd, tagStart, tagEnd, style) {
1387
1509
  state.events.push({
1388
- type: 3,
1510
+ type: EVENT_ID.MAPPING,
1389
1511
  start: start2,
1390
1512
  anchorStart,
1391
1513
  anchorEnd,
@@ -1394,9 +1516,20 @@ function addMappingEvent(state, start2, anchorStart, anchorEnd, tagStart, tagEnd
1394
1516
  style
1395
1517
  });
1396
1518
  }
1397
- function addScalarEvent(state, valueStart, valueEnd, anchorStart, anchorEnd, tagStart, tagEnd, style, chomping = 1, indent = -1, fast = false) {
1519
+ function insertFlowPairMappingEvent(state, snapshot) {
1520
+ state.events.splice(snapshot.eventsLength, 0, {
1521
+ type: EVENT_ID.MAPPING,
1522
+ start: snapshot.position,
1523
+ anchorStart: NO_RANGE$1,
1524
+ anchorEnd: NO_RANGE$1,
1525
+ tagStart: NO_RANGE$1,
1526
+ tagEnd: NO_RANGE$1,
1527
+ style: COLLECTION_STYLE.FLOW
1528
+ });
1529
+ }
1530
+ function addScalarEvent(state, valueStart, valueEnd, anchorStart, anchorEnd, tagStart, tagEnd, style, chomping = CHOMPING_MODE.CLIP, indent = -1, fast = false) {
1398
1531
  state.events.push({
1399
- type: 4,
1532
+ type: EVENT_ID.SCALAR,
1400
1533
  valueStart,
1401
1534
  valueEnd,
1402
1535
  anchorStart,
@@ -1411,16 +1544,16 @@ function addScalarEvent(state, valueStart, valueEnd, anchorStart, anchorEnd, tag
1411
1544
  }
1412
1545
  function addAliasEvent(state, anchorStart, anchorEnd) {
1413
1546
  state.events.push({
1414
- type: 5,
1547
+ type: EVENT_ID.ALIAS,
1415
1548
  anchorStart,
1416
1549
  anchorEnd
1417
1550
  });
1418
1551
  }
1419
1552
  function addPopEvent(state) {
1420
- state.events.push({ type: 6 });
1553
+ state.events.push({ type: EVENT_ID.POP });
1421
1554
  }
1422
1555
  function addEmptyScalarEvent(state) {
1423
- addScalarEvent(state, NO_RANGE$1, NO_RANGE$1, NO_RANGE$1, NO_RANGE$1, NO_RANGE$1, NO_RANGE$1, 1);
1556
+ addScalarEvent(state, NO_RANGE$1, NO_RANGE$1, NO_RANGE$1, NO_RANGE$1, NO_RANGE$1, NO_RANGE$1, SCALAR_STYLE.PLAIN);
1424
1557
  }
1425
1558
  function emptyProperties() {
1426
1559
  return {
@@ -1449,7 +1582,7 @@ function restoreState(state, snapshot) {
1449
1582
  state.events.length = snapshot.eventsLength;
1450
1583
  }
1451
1584
  function throwError(state, message) {
1452
- throwErrorAt(state.input.slice(0, state.length), state.position, message, state.filename);
1585
+ YAMLException.throwAt(state.input.slice(0, state.length), state.position, message, state.filename);
1453
1586
  }
1454
1587
  function isEol(c) {
1455
1588
  return c === 10 || c === 13;
@@ -1617,7 +1750,7 @@ function readSingleQuotedScalar(state, nodeIndent, props) {
1617
1750
  }
1618
1751
  const end = state.position;
1619
1752
  state.position++;
1620
- addScalarEvent(state, start2, end, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, 2, 1, -1, simple);
1753
+ addScalarEvent(state, start2, end, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, SCALAR_STYLE.SINGLE_QUOTED, CHOMPING_MODE.CLIP, -1, simple);
1621
1754
  return true;
1622
1755
  }
1623
1756
  if (isEol(ch)) {
@@ -1639,7 +1772,7 @@ function readDoubleQuotedScalar(state, nodeIndent, props) {
1639
1772
  if (ch === 34) {
1640
1773
  const end = state.position;
1641
1774
  state.position++;
1642
- addScalarEvent(state, start2, end, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, 3, 1, -1, simple);
1775
+ addScalarEvent(state, start2, end, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, SCALAR_STYLE.DOUBLE_QUOTED, CHOMPING_MODE.CLIP, -1, simple);
1643
1776
  return true;
1644
1777
  }
1645
1778
  if (ch === 92) {
@@ -1667,18 +1800,18 @@ function readDoubleQuotedScalar(state, nodeIndent, props) {
1667
1800
  }
1668
1801
  function readBlockScalar(state, parentIndent, props) {
1669
1802
  const ch = state.input.charCodeAt(state.position);
1670
- let chomping = 1;
1803
+ let chomping = CHOMPING_MODE.CLIP;
1671
1804
  let indent = -1;
1672
1805
  let detectedIndent = false;
1673
1806
  if (ch !== 124 && ch !== 62) return false;
1674
- const style = ch === 124 ? 4 : 5;
1807
+ const style = ch === 124 ? SCALAR_STYLE.LITERAL_BLOCK : SCALAR_STYLE.FOLDED_BLOCK;
1675
1808
  state.position++;
1676
1809
  while (state.input.charCodeAt(state.position) !== 0) {
1677
1810
  const current = state.input.charCodeAt(state.position);
1678
1811
  const digit = fromDecimalCode(current);
1679
1812
  if (current === 43 || current === 45) {
1680
- if (chomping !== 1) throwError(state, "repeat of a chomping mode identifier");
1681
- chomping = current === 43 ? 3 : 2;
1813
+ if (chomping !== CHOMPING_MODE.CLIP) throwError(state, "repeat of a chomping mode identifier");
1814
+ chomping = current === 43 ? CHOMPING_MODE.KEEP : CHOMPING_MODE.STRIP;
1682
1815
  state.position++;
1683
1816
  } else if (digit >= 0) {
1684
1817
  if (digit === 0) throwError(state, "bad explicit indentation width of a block scalar; it cannot be less than one");
@@ -1793,7 +1926,7 @@ function readPlainScalar(state, nodeIndent, nodeContext, props) {
1793
1926
  }
1794
1927
  if (end === start2) return false;
1795
1928
  checkPrintable(state, start2, end);
1796
- addScalarEvent(state, start2, end, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, 1, 1, -1, !multiline);
1929
+ addScalarEvent(state, start2, end, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, SCALAR_STYLE.PLAIN, CHOMPING_MODE.CLIP, -1, !multiline);
1797
1930
  return true;
1798
1931
  }
1799
1932
  function skipFlowSeparationSpace(state, nodeIndent) {
@@ -1808,8 +1941,8 @@ function readFlowCollection(state, nodeIndent, props) {
1808
1941
  let readNext = true;
1809
1942
  if (ch !== 91 && ch !== 123) return false;
1810
1943
  const terminator = isMapping ? 125 : 93;
1811
- if (isMapping) addMappingEvent(state, start2, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, 2);
1812
- else addSequenceEvent(state, start2, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, 2);
1944
+ if (isMapping) addMappingEvent(state, start2, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, COLLECTION_STYLE.FLOW);
1945
+ else addSequenceEvent(state, start2, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, COLLECTION_STYLE.FLOW);
1813
1946
  state.position++;
1814
1947
  while (state.input.charCodeAt(state.position) !== 0) {
1815
1948
  skipFlowSeparationSpace(state, nodeIndent);
@@ -1837,12 +1970,8 @@ function readFlowCollection(state, nodeIndent, props) {
1837
1970
  state.position++;
1838
1971
  skipFlowSeparationSpace(state, nodeIndent);
1839
1972
  if (!isMapping) {
1840
- restoreState(state, entryStart);
1841
- addMappingEvent(state, entryStart.position, NO_RANGE$1, NO_RANGE$1, NO_RANGE$1, NO_RANGE$1, 2);
1842
- if (!parseNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true)) addEmptyScalarEvent(state);
1843
- skipFlowSeparationSpace(state, nodeIndent);
1844
- state.position++;
1845
- skipFlowSeparationSpace(state, nodeIndent);
1973
+ insertFlowPairMappingEvent(state, entryStart);
1974
+ if (!keyWasRead) addEmptyScalarEvent(state);
1846
1975
  } else if (!keyWasRead) addEmptyScalarEvent(state);
1847
1976
  if (!parseNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true)) addEmptyScalarEvent(state);
1848
1977
  skipFlowSeparationSpace(state, nodeIndent);
@@ -1852,9 +1981,8 @@ function readFlowCollection(state, nodeIndent, props) {
1852
1981
  addEmptyScalarEvent(state);
1853
1982
  } else if (isMapping) addEmptyScalarEvent(state);
1854
1983
  else if (isPair) {
1855
- restoreState(state, entryStart);
1856
- addMappingEvent(state, entryStart.position, NO_RANGE$1, NO_RANGE$1, NO_RANGE$1, NO_RANGE$1, 2);
1857
- parseNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);
1984
+ insertFlowPairMappingEvent(state, entryStart);
1985
+ if (!keyWasRead) addEmptyScalarEvent(state);
1858
1986
  addEmptyScalarEvent(state);
1859
1987
  addPopEvent(state);
1860
1988
  }
@@ -1868,7 +1996,7 @@ function readFlowCollection(state, nodeIndent, props) {
1868
1996
  }
1869
1997
  function readBlockSequence(state, nodeIndent, props) {
1870
1998
  if (state.firstTabInLine !== -1 || state.input.charCodeAt(state.position) !== 45 || !isWsOrEolOrEnd(state.input.charCodeAt(state.position + 1))) return false;
1871
- addSequenceEvent(state, state.position, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, 1);
1999
+ addSequenceEvent(state, state.position, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, COLLECTION_STYLE.BLOCK);
1872
2000
  while (state.input.charCodeAt(state.position) === 45 && isWsOrEolOrEnd(state.input.charCodeAt(state.position + 1))) {
1873
2001
  if (state.firstTabInLine !== -1) {
1874
2002
  state.position = state.firstTabInLine;
@@ -1904,7 +2032,7 @@ function readBlockMapping(state, nodeIndent, flowIndent, props) {
1904
2032
  const entryLine = state.line;
1905
2033
  if ((ch === 63 || ch === 58) && isWsOrEolOrEnd(following)) {
1906
2034
  if (!mappingOpened) {
1907
- addMappingEvent(state, state.position, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, 1);
2035
+ addMappingEvent(state, state.position, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, COLLECTION_STYLE.BLOCK);
1908
2036
  mappingOpened = true;
1909
2037
  }
1910
2038
  if (ch === 63) {
@@ -1934,7 +2062,7 @@ function readBlockMapping(state, nodeIndent, flowIndent, props) {
1934
2062
  if (!isWsOrEolOrEnd(ch)) throwError(state, "a whitespace character is expected after the key-value separator within a block mapping");
1935
2063
  if (!mappingOpened) {
1936
2064
  restoreState(state, beforeKey);
1937
- addMappingEvent(state, beforeKey.position, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, 1);
2065
+ addMappingEvent(state, beforeKey.position, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, COLLECTION_STYLE.BLOCK);
1938
2066
  mappingOpened = true;
1939
2067
  parseNode(state, flowIndent, CONTEXT_FLOW_OUT, false, true);
1940
2068
  ch = state.input.charCodeAt(state.position);
@@ -1995,10 +2123,6 @@ function parseNode(state, parentIndent, nodeContext, allowToSeek, allowCompact,
1995
2123
  else if (state.lineIndent === parentIndent) indentStatus = 0;
1996
2124
  else indentStatus = -1;
1997
2125
  }
1998
- if (state.position === state.lineStart && testDocumentSeparator(state)) {
1999
- state.depth--;
2000
- return false;
2001
- }
2002
2126
  if (indentStatus === 1) while (true) {
2003
2127
  const ch = state.input.charCodeAt(state.position);
2004
2128
  const propertyState = snapshotState(state);
@@ -2006,7 +2130,7 @@ function parseNode(state, parentIndent, nodeContext, allowToSeek, allowCompact,
2006
2130
  if (atNewLine && allowBlockStyles && (props.tagStart !== NO_RANGE$1 || props.anchorStart !== NO_RANGE$1) && (ch === 33 || ch === 38)) {
2007
2131
  const fallbackState = snapshotState(state);
2008
2132
  const flowIndent = parentIndent + 1;
2009
- if (readBlockMapping(state, state.position - state.lineStart, flowIndent, props) && state.events[fallbackState.eventsLength]?.type === 3) {
2133
+ if (readBlockMapping(state, state.position - state.lineStart, flowIndent, props) && state.events[fallbackState.eventsLength]?.type === EVENT_ID.MAPPING) {
2010
2134
  state.depth--;
2011
2135
  return true;
2012
2136
  }
@@ -2034,7 +2158,7 @@ function parseNode(state, parentIndent, nodeContext, allowToSeek, allowCompact,
2034
2158
  const fallbackState = snapshotState(state);
2035
2159
  const propertyIndent = propertyStart.position - propertyStart.lineStart;
2036
2160
  restoreState(state, propertyStart);
2037
- if (readBlockMapping(state, propertyIndent, flowIndent, emptyProperties()) && state.events[fallbackState.eventsLength]?.type === 3) hasContent = true;
2161
+ if (readBlockMapping(state, propertyIndent, flowIndent, emptyProperties()) && state.events[fallbackState.eventsLength]?.type === EVENT_ID.MAPPING) hasContent = true;
2038
2162
  else restoreState(state, fallbackState);
2039
2163
  }
2040
2164
  if (!hasContent && (allowBlockScalars && readBlockScalar(state, flowIndent, props) || readSingleQuotedScalar(state, flowIndent, props) || readDoubleQuotedScalar(state, flowIndent, props) || readAlias(state, props) || readPlainScalar(state, flowIndent, nodeContext, props))) hasContent = true;
@@ -2043,7 +2167,7 @@ function parseNode(state, parentIndent, nodeContext, allowToSeek, allowCompact,
2043
2167
  }
2044
2168
  allowBlockScalars = allowBlockScalars && !hasContent;
2045
2169
  if (!hasContent && (props.anchorStart !== NO_RANGE$1 || props.tagStart !== NO_RANGE$1 || allowBlockScalars)) {
2046
- addScalarEvent(state, NO_RANGE$1, NO_RANGE$1, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, 1);
2170
+ addScalarEvent(state, NO_RANGE$1, NO_RANGE$1, props.anchorStart, props.anchorEnd, props.tagStart, props.tagEnd, SCALAR_STYLE.PLAIN);
2047
2171
  hasContent = true;
2048
2172
  }
2049
2173
  state.depth--;
@@ -2128,7 +2252,7 @@ function readDocument(state) {
2128
2252
  }
2129
2253
  }
2130
2254
  const documentEvent = state.events[documentEventIndex];
2131
- if (documentEvent?.type === 1) documentEvent.explicitEnd = explicitEnd;
2255
+ if (documentEvent?.type === EVENT_ID.DOCUMENT) documentEvent.explicitEnd = explicitEnd;
2132
2256
  addPopEvent(state);
2133
2257
  if (!explicitEnd && state.position < state.length && !(state.position === state.lineStart && testDocumentSeparator(state))) throwError(state, "end of the stream or a document separator is expected");
2134
2258
  }
@@ -2150,7 +2274,7 @@ function parseEvents(input, options) {
2150
2274
  events: []
2151
2275
  };
2152
2276
  const nullpos = input.indexOf("\0");
2153
- if (nullpos !== -1) throwErrorAt(input, nullpos, "null byte is not allowed in input", state.filename);
2277
+ if (nullpos !== -1) YAMLException.throwAt(input, nullpos, "null byte is not allowed in input", state.filename);
2154
2278
  if (state.input.charCodeAt(state.position) === 65279) state.position++;
2155
2279
  while (state.position < state.length) {
2156
2280
  skipSeparationSpace(state, true);
@@ -2186,6 +2310,7 @@ function load(input, options) {
2186
2310
  throw new YAMLException("expected a single document in the stream, but found more");
2187
2311
  }
2188
2312
  var Style = class {
2313
+ /** Whether to print the node's tag explicitly. */
2189
2314
  tagged = false;
2190
2315
  flow = false;
2191
2316
  singleQuoted = false;
@@ -2221,9 +2346,9 @@ function buildRepresentTypes(schema) {
2221
2346
  function matchTag(state, object) {
2222
2347
  for (let index = 0, length = state.representTypes.length; index < length; index += 1) {
2223
2348
  const { tag, implicitTag } = state.representTypes[index];
2224
- if (tag.identify && tag.identify(object)) {
2349
+ if (tag.identify(object)) {
2225
2350
  let tagName;
2226
- if (tag.matchByTagPrefix && tag.representTagName) tagName = tag.representTagName(object);
2351
+ if (tag.matchByTagPrefix) tagName = tag.representTagName(object);
2227
2352
  else tagName = tag.tagName;
2228
2353
  return {
2229
2354
  tag,
@@ -2423,8 +2548,7 @@ function createPresenterState(options) {
2423
2548
  };
2424
2549
  return {
2425
2550
  ...opts,
2426
- defaultScalarTagName: opts.schema.defaultScalarTag.tagName,
2427
- implicitResolvers: opts.schema.implicitScalarTags
2551
+ defaultScalarTagName: opts.schema.defaultScalarTag.tagName
2428
2552
  };
2429
2553
  }
2430
2554
  function encodeNonPrintable(character) {
@@ -2465,13 +2589,6 @@ function scalarLayout(state, level) {
2465
2589
  lineWidth: state.lineWidth === -1 ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent)
2466
2590
  };
2467
2591
  }
2468
- function resolveImplicitTag(state, str) {
2469
- for (let index = 0, length = state.implicitResolvers.length; index < length; index += 1) {
2470
- const tagDefinition = state.implicitResolvers[index];
2471
- if (tagDefinition.resolve(str, false, tagDefinition.tagName) !== NOT_RESOLVED) return tagDefinition.tagName;
2472
- }
2473
- return state.defaultScalarTagName;
2474
- }
2475
2592
  function isWhitespace(c) {
2476
2593
  return c === CHAR_SPACE || c === CHAR_TAB;
2477
2594
  }
@@ -2491,7 +2608,7 @@ function isNsCharOrWhitespace(c) {
2491
2608
  function isPlainSafe(c, prev, inblock) {
2492
2609
  const cIsNsCharOrWhitespace = isNsCharOrWhitespace(c);
2493
2610
  const cIsNsChar = cIsNsCharOrWhitespace && !isWhitespace(c);
2494
- return (inblock ? cIsNsCharOrWhitespace : cIsNsCharOrWhitespace && c !== CHAR_COMMA && c !== CHAR_LEFT_SQUARE_BRACKET && c !== CHAR_RIGHT_SQUARE_BRACKET && c !== CHAR_LEFT_CURLY_BRACKET && c !== CHAR_RIGHT_CURLY_BRACKET) && c !== CHAR_SHARP && !(prev === CHAR_COLON && !cIsNsChar) || isNsCharOrWhitespace(prev) && !isWhitespace(prev) && c === CHAR_SHARP || prev === CHAR_COLON && cIsNsChar;
2611
+ return (inblock ? cIsNsCharOrWhitespace : cIsNsCharOrWhitespace && c !== CHAR_COMMA && c !== CHAR_LEFT_SQUARE_BRACKET && c !== CHAR_RIGHT_SQUARE_BRACKET && c !== CHAR_LEFT_CURLY_BRACKET && c !== CHAR_RIGHT_CURLY_BRACKET) && c !== CHAR_SHARP && !(prev === CHAR_COLON && !cIsNsChar) || isNsCharOrWhitespace(prev) && !isWhitespace(prev) && c === CHAR_SHARP || prev === CHAR_COLON && cIsNsChar && (inblock || c !== CHAR_COMMA && c !== CHAR_LEFT_SQUARE_BRACKET && c !== CHAR_RIGHT_SQUARE_BRACKET && c !== CHAR_LEFT_CURLY_BRACKET && c !== CHAR_RIGHT_CURLY_BRACKET);
2495
2612
  }
2496
2613
  function isPlainSafeFirst(c) {
2497
2614
  return isPrintable(c) && c !== CHAR_BOM && !isWhitespace(c) && c !== CHAR_MINUS && c !== CHAR_QUESTION && c !== CHAR_COLON && c !== CHAR_COMMA && c !== CHAR_LEFT_SQUARE_BRACKET && c !== CHAR_RIGHT_SQUARE_BRACKET && c !== CHAR_LEFT_CURLY_BRACKET && c !== CHAR_RIGHT_CURLY_BRACKET && c !== CHAR_SHARP && c !== CHAR_AMPERSAND && c !== CHAR_ASTERISK && c !== CHAR_EXCLAMATION && c !== CHAR_VERTICAL_LINE && c !== CHAR_EQUALS && c !== CHAR_GREATER_THAN && c !== CHAR_SINGLE_QUOTE && c !== CHAR_DOUBLE_QUOTE && c !== CHAR_PERCENT && c !== CHAR_COMMERCIAL_AT && c !== CHAR_GRAVE_ACCENT;
@@ -2547,14 +2664,14 @@ function chooseScalarStyle(state, string, layout, singleLineOnly, forceQuote, in
2547
2664
  if (char === CHAR_LINE_FEED) {
2548
2665
  hasLineBreak = true;
2549
2666
  if (shouldTrackWidth) {
2550
- hasFoldableLine = hasFoldableLine || i - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " ";
2667
+ hasFoldableLine = hasFoldableLine || i - previousLineBreak - 1 > lineWidth && !isMoreIndented(string[previousLineBreak + 1]);
2551
2668
  previousLineBreak = i;
2552
2669
  }
2553
2670
  } else if (!isPrintable(char)) return STYLE_DOUBLE;
2554
2671
  plain = plain && isPlainSafe(char, prevChar, inblock);
2555
2672
  prevChar = char;
2556
2673
  }
2557
- hasFoldableLine = hasFoldableLine || shouldTrackWidth && i - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " ";
2674
+ hasFoldableLine = hasFoldableLine || shouldTrackWidth && i - previousLineBreak - 1 > lineWidth && !isMoreIndented(string[previousLineBreak + 1]);
2558
2675
  }
2559
2676
  if (!hasLineBreak && !hasFoldableLine) {
2560
2677
  if (plain && !forceQuote) return STYLE_PLAIN;
@@ -2588,11 +2705,11 @@ function resolveScalarStyle(state, node, layout, iskey, inblock) {
2588
2705
  }
2589
2706
  const string = node.value;
2590
2707
  if (string.length === 0) {
2591
- if (node.style.tagged || resolveImplicitTag(state, string) === node.tag) return STYLE_PLAIN;
2708
+ if (node.style.tagged || state.schema.resolveImplicitScalarTag(string).tag.tagName === node.tag) return STYLE_PLAIN;
2592
2709
  return state.quoteStyle === "double" ? STYLE_DOUBLE : STYLE_SINGLE;
2593
2710
  }
2594
2711
  const style = chooseScalarStyle(state, string, layout, singleLineOnly, state.forceQuotes && !iskey, inblock);
2595
- if (style === STYLE_PLAIN && !node.style.tagged && resolveImplicitTag(state, string) !== node.tag) return state.quoteStyle === "double" ? STYLE_DOUBLE : STYLE_SINGLE;
2712
+ if (style === STYLE_PLAIN && !node.style.tagged && state.schema.resolveImplicitScalarTag(string).tag.tagName !== node.tag) return state.quoteStyle === "double" ? STYLE_DOUBLE : STYLE_SINGLE;
2596
2713
  return style;
2597
2714
  }
2598
2715
  function blockHeader(string, indentPerLevel) {
@@ -2619,27 +2736,30 @@ function encodeFlowBreaks(string, indent) {
2619
2736
  function dropEndingNewline(string) {
2620
2737
  return string[string.length - 1] === "\n" ? string.slice(0, -1) : string;
2621
2738
  }
2739
+ function isMoreIndented(char) {
2740
+ return char === " " || char === " ";
2741
+ }
2622
2742
  function foldBlockScalar(string, width) {
2623
2743
  const lineRe = /(\n+)([^\n]*)/g;
2624
2744
  let nextLF = string.indexOf("\n");
2625
2745
  if (nextLF === -1) nextLF = string.length;
2626
2746
  lineRe.lastIndex = nextLF;
2627
2747
  let result = foldLine(string.slice(0, nextLF), width);
2628
- let prevMoreIndented = string[0] === "\n" || string[0] === " ";
2748
+ let prevMoreIndented = string[0] === "\n" || isMoreIndented(string[0]);
2629
2749
  let moreIndented;
2630
2750
  let match;
2631
2751
  while (match = lineRe.exec(string)) {
2632
2752
  const prefix = match[1];
2633
2753
  const line = match[2];
2634
- moreIndented = line[0] === " ";
2754
+ moreIndented = line !== "" && isMoreIndented(line[0]);
2635
2755
  result += prefix + (!prevMoreIndented && !moreIndented && line !== "" ? "\n" : "") + foldLine(line, width);
2636
2756
  prevMoreIndented = moreIndented;
2637
2757
  }
2638
2758
  return result;
2639
2759
  }
2640
2760
  function foldLine(line, width) {
2641
- if (line === "" || line[0] === " ") return line;
2642
- const breakRe = / [^ ]/g;
2761
+ if (line === "" || isMoreIndented(line[0])) return line;
2762
+ const breakRe = / [^ \t]/g;
2643
2763
  let match;
2644
2764
  let start2 = 0;
2645
2765
  let end;
@@ -2873,22 +2993,9 @@ function present(documents, options) {
2873
2993
  }
2874
2994
  return result;
2875
2995
  }
2876
- var DEFAULT_DUMP_SCHEMA = YAML11_SCHEMA.withTags({
2877
- ...intYaml11Tag,
2878
- resolve: (source, isExplicit, tagName) => {
2879
- const result = intYaml11Tag.resolve(source, isExplicit, tagName);
2880
- return result === NOT_RESOLVED ? intCoreTag.resolve(source, isExplicit, tagName) : result;
2881
- }
2882
- }, {
2883
- ...floatYaml11Tag,
2884
- resolve: (source, isExplicit, tagName) => {
2885
- const result = floatYaml11Tag.resolve(source, isExplicit, tagName);
2886
- return result === NOT_RESOLVED ? floatCoreTag.resolve(source, isExplicit, tagName) : result;
2887
- }
2888
- });
2889
2996
  var DEFAULT_DUMP_OPTIONS = {
2890
2997
  ...DEFAULT_PRESENTER_OPTIONS,
2891
- schema: DEFAULT_DUMP_SCHEMA,
2998
+ schema: DUMP_SCHEMA,
2892
2999
  skipInvalid: false,
2893
3000
  noRefs: false,
2894
3001
  flowLevel: -1,
@@ -2915,6 +3022,22 @@ function dump(input, options = {}) {
2915
3022
  schema: opts.schema
2916
3023
  });
2917
3024
  }
3025
+ var EVENT_DOCUMENT = EVENT_ID.DOCUMENT;
3026
+ var EVENT_SEQUENCE = EVENT_ID.SEQUENCE;
3027
+ var EVENT_MAPPING = EVENT_ID.MAPPING;
3028
+ var EVENT_SCALAR = EVENT_ID.SCALAR;
3029
+ var EVENT_ALIAS = EVENT_ID.ALIAS;
3030
+ var EVENT_POP = EVENT_ID.POP;
3031
+ var SCALAR_STYLE_PLAIN = SCALAR_STYLE.PLAIN;
3032
+ var SCALAR_STYLE_SINGLE_QUOTED = SCALAR_STYLE.SINGLE_QUOTED;
3033
+ var SCALAR_STYLE_DOUBLE_QUOTED = SCALAR_STYLE.DOUBLE_QUOTED;
3034
+ var SCALAR_STYLE_LITERAL_BLOCK = SCALAR_STYLE.LITERAL_BLOCK;
3035
+ var SCALAR_STYLE_FOLDED_BLOCK = SCALAR_STYLE.FOLDED_BLOCK;
3036
+ var COLLECTION_STYLE_BLOCK = COLLECTION_STYLE.BLOCK;
3037
+ var COLLECTION_STYLE_FLOW = COLLECTION_STYLE.FLOW;
3038
+ var CHOMPING_CLIP = CHOMPING_MODE.CLIP;
3039
+ var CHOMPING_STRIP = CHOMPING_MODE.STRIP;
3040
+ var CHOMPING_KEEP = CHOMPING_MODE.KEEP;
2918
3041
 
2919
3042
  // src/constants.ts
2920
3043
  var AVAILABLE_MIRRORS = ["v6.gh-proxy.org", "gh-proxy.org", "hk.gh-proxy.org", "cdn.gh-proxy.org"];
@@ -3027,8 +3150,9 @@ function ensureDirs() {
3027
3150
  }
3028
3151
  }
3029
3152
  }
3153
+ var atomicWriteSeq = 0;
3030
3154
  function atomicWriteFileSync(filePath, content, options) {
3031
- const tmp = `${filePath}.${process.pid}.tmp`;
3155
+ const tmp = `${filePath}.${process.pid}.${atomicWriteSeq++}.tmp`;
3032
3156
  try {
3033
3157
  fs.writeFileSync(tmp, content, options);
3034
3158
  fs.renameSync(tmp, filePath);
@@ -3401,7 +3525,7 @@ function loadOverwriteFile() {
3401
3525
  const filePath = path3.join(dir, file);
3402
3526
  try {
3403
3527
  const content = fs3.readFileSync(filePath, "utf8");
3404
- const parsed = load(content);
3528
+ const parsed = load(content, { maxAliases: 200 });
3405
3529
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3406
3530
  const { match, ...config } = parsed;
3407
3531
  results.push({ name: file, path: filePath, config, match: normalizeMatch(match, file) });
@@ -3478,6 +3602,18 @@ var TimeoutError = class extends Error {
3478
3602
  this.name = "TimeoutError";
3479
3603
  }
3480
3604
  };
3605
+ var CliError = class extends Error {
3606
+ hint;
3607
+ label;
3608
+ exitCode;
3609
+ constructor(message, options = {}) {
3610
+ super(message);
3611
+ this.name = "CliError";
3612
+ this.label = options.label ?? "\u9519\u8BEF";
3613
+ this.hint = options.hint === void 0 ? [] : Array.isArray(options.hint) ? options.hint : [options.hint];
3614
+ this.exitCode = options.exitCode ?? 1;
3615
+ }
3616
+ };
3481
3617
  function withTimeout(promise, ms) {
3482
3618
  return new Promise((resolve, reject) => {
3483
3619
  const timer = setTimeout(() => reject(new TimeoutError()), ms);
@@ -3581,6 +3717,15 @@ function isProcessRunning(pid) {
3581
3717
  return false;
3582
3718
  }
3583
3719
  }
3720
+ function isProcessCommandMatching(pid, needle) {
3721
+ if (!pid) return false;
3722
+ try {
3723
+ const result = spawnSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8", timeout: 5e3 });
3724
+ return (result.stdout || "").includes(needle);
3725
+ } catch {
3726
+ return false;
3727
+ }
3728
+ }
3584
3729
  function isProcessRoot(pid) {
3585
3730
  if (!pid) return false;
3586
3731
  try {
@@ -3591,7 +3736,8 @@ function isProcessRoot(pid) {
3591
3736
  }
3592
3737
  }
3593
3738
  function createHttpClient(options = {}) {
3594
- const { timeout = 6e4 } = options;
3739
+ const { timeout = 6e4, secret } = options;
3740
+ const authHeaders = secret ? { Authorization: `Bearer ${secret}` } : {};
3595
3741
  return {
3596
3742
  async get(url, config) {
3597
3743
  const controller = new AbortController();
@@ -3600,7 +3746,7 @@ function createHttpClient(options = {}) {
3600
3746
  try {
3601
3747
  const response = await fetch(url, {
3602
3748
  signal,
3603
- headers: { "User-Agent": `mihomo-cli/${VERSION}` }
3749
+ headers: { "User-Agent": `mihomo-cli/${VERSION}`, ...authHeaders }
3604
3750
  });
3605
3751
  if (!response.ok) {
3606
3752
  const error = new Error(`HTTP ${response.status}`);
@@ -3699,12 +3845,16 @@ function isProxyValid(proxy) {
3699
3845
  }
3700
3846
 
3701
3847
  // src/config.ts
3848
+ var SAFE_YAML_LOAD_OPTIONS = { maxAliases: 200 };
3849
+ function loadYamlSafe(content) {
3850
+ return load(content, SAFE_YAML_LOAD_OPTIONS);
3851
+ }
3702
3852
  function parseYamlOrJson(content, errorMsg) {
3703
3853
  if (!content?.trim()) {
3704
3854
  throw new Error(`${errorMsg || "\u5185\u5BB9"}\u4E3A\u7A7A`);
3705
3855
  }
3706
3856
  try {
3707
- const result = load(content);
3857
+ const result = loadYamlSafe(content);
3708
3858
  if (result != null && typeof result === "object" && !Array.isArray(result)) return result;
3709
3859
  } catch {
3710
3860
  }
@@ -3715,13 +3865,13 @@ function parseYamlOrJson(content, errorMsg) {
3715
3865
  }
3716
3866
  }
3717
3867
  function dumpYaml(obj) {
3718
- return dump(obj, { indent: 2, lineWidth: -1, schema: CORE_SCHEMA });
3868
+ return dump(obj, { indent: 2, lineWidth: -1 });
3719
3869
  }
3720
3870
  function collectOverwriteProxyNames(overwriteFiles) {
3721
3871
  const names = [];
3722
3872
  for (const file of overwriteFiles) {
3723
3873
  for (const [key, value] of Object.entries(file.config)) {
3724
- if ((key === "+proxies" || key === "proxies+") && Array.isArray(value)) {
3874
+ if ((key === "+proxies" || key === "proxies+" || key === "~proxies") && Array.isArray(value)) {
3725
3875
  for (const proxy of value) {
3726
3876
  if (proxy && typeof proxy === "object" && "name" in proxy) {
3727
3877
  const name = proxy.name;
@@ -3916,7 +4066,7 @@ function getConfigInfo() {
3916
4066
  if (!hasConfig()) return null;
3917
4067
  try {
3918
4068
  const content = fs4.readFileSync(PATHS.configFile, "utf8");
3919
- const cfg = load(content);
4069
+ const cfg = loadYamlSafe(content);
3920
4070
  if (!cfg) return null;
3921
4071
  const proxies = cfg.proxies;
3922
4072
  const proxyGroups = cfg["proxy-groups"];
@@ -4535,7 +4685,7 @@ function getLogPathByName(name) {
4535
4685
  }
4536
4686
  function openUrl(url) {
4537
4687
  try {
4538
- const child = spawn("open", [url], { stdio: "ignore", detached: true });
4688
+ const child = spawn("open", ["--", url], { stdio: "ignore", detached: true });
4539
4689
  child.unref();
4540
4690
  child.on("error", () => {
4541
4691
  });
@@ -4723,10 +4873,13 @@ function disableDaemon() {
4723
4873
  async function tryHotReload() {
4724
4874
  const controller = new AbortController();
4725
4875
  const timer = setTimeout(() => controller.abort(), HOT_RELOAD_TIMEOUT_MS);
4876
+ const secret = readSettings().controller_secret;
4877
+ const headers = { "Content-Type": "application/json" };
4878
+ if (secret) headers.Authorization = `Bearer ${secret}`;
4726
4879
  try {
4727
4880
  const res = await fetch(`${CONTROLLER_BASE_URL}/configs?force=true`, {
4728
4881
  method: "PUT",
4729
- headers: { "Content-Type": "application/json" },
4882
+ headers,
4730
4883
  body: "{}",
4731
4884
  signal: controller.signal
4732
4885
  });
@@ -4875,6 +5028,13 @@ function getActiveSubscription() {
4875
5028
  }
4876
5029
  return subs[0];
4877
5030
  }
5031
+ function requireActiveSubscription(emptyMsg = "\u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605") {
5032
+ const sub = getActiveSubscription();
5033
+ if (!sub) {
5034
+ throw new CliError(emptyMsg);
5035
+ }
5036
+ return sub;
5037
+ }
4878
5038
  function findSubscriptionFuzzy(subs, pattern) {
4879
5039
  const lowerPattern = pattern.toLowerCase();
4880
5040
  const exact = [];
@@ -4896,14 +5056,15 @@ function findSubscriptionFuzzy(subs, pattern) {
4896
5056
  }
4897
5057
  function pickSingleSubscription(subs, pattern) {
4898
5058
  if (subs.length === 0) {
4899
- console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u8BA2\u9605`);
4900
- process.exit(1);
5059
+ throw new CliError(`\u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u8BA2\u9605`);
4901
5060
  }
4902
5061
  if (subs.length === 1) return subs[0];
4903
- console.error("\u9519\u8BEF: \u5339\u914D\u5230\u591A\u4E2A\u8BA2\u9605\uFF0C\u8BF7\u66F4\u7CBE\u786E\u6307\u5B9A");
4904
- console.log("\n\u5339\u914D\u7684\u8BA2\u9605:");
4905
- for (const s of subs) console.log(` ${s.name}`);
4906
- process.exit(1);
5062
+ throw new CliError("\u5339\u914D\u5230\u591A\u4E2A\u8BA2\u9605\uFF0C\u8BF7\u66F4\u7CBE\u786E\u6307\u5B9A", {
5063
+ hint: ["", "\u5339\u914D\u7684\u8BA2\u9605:", ...subs.map((s) => ` ${s.name}`)]
5064
+ });
5065
+ }
5066
+ function resolveSubscription(subs, pattern) {
5067
+ return pickSingleSubscription(findSubscriptionFuzzy(subs, pattern), pattern);
4907
5068
  }
4908
5069
  async function downloadSubscription(url, subName = "default", signal, persist = true) {
4909
5070
  let response;
@@ -5124,7 +5285,8 @@ async function testSubscriptionProxies(subName, options = {}) {
5124
5285
  if (proxies.length === 0) {
5125
5286
  return { total: 0, alive: 0, dead: 0, results: [] };
5126
5287
  }
5127
- const client = createHttpClient({ timeout: timeout + 3e3 });
5288
+ const secret = apiBase === CONTROLLER_BASE_URL ? readSettings().controller_secret : void 0;
5289
+ const client = createHttpClient({ timeout: timeout + 3e3, secret });
5128
5290
  const results = new Array(proxies.length);
5129
5291
  let completedCount = 0;
5130
5292
  let nextIndex = 0;
@@ -5272,843 +5434,847 @@ async function autoCleanSubscription(subName, options = {}) {
5272
5434
  return { summary, removedProxies, updatedGroups, removedGroups, skipped };
5273
5435
  }
5274
5436
 
5275
- // src/commands/daemon.ts
5276
- function printDaemonStatus() {
5277
- const status = getDaemonStatus();
5278
- const stateText = status.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
5279
- console.log(`${colors.gray("\u4FDD\u6D3B: ")}${stateText}`);
5280
- if (status.enabled) {
5281
- const runText = isDaemonRunning(status) ? colors.green(`\u8FD0\u884C\u4E2D (PID ${status.pid})`) : colors.yellow("\u672A\u8FD0\u884C");
5282
- console.log(`${colors.gray("\u5185\u6838: ")}${runText}`);
5283
- }
5284
- console.log("");
5285
- if (status.enabled) {
5286
- console.log("\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off");
5287
- } else {
5288
- console.log("\u5F00\u542F\u4FDD\u6D3B: mihomo daemon on");
5289
- console.log(colors.gray(" \u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF08\u4EC5 Mixed \u6A21\u5F0F\uFF09"));
5437
+ // src/runtime.ts
5438
+ function getRuntimeMode() {
5439
+ if (isDaemonEnabled()) return "mixed";
5440
+ return getConfigInfo()?.tun ? "tun" : "mixed";
5441
+ }
5442
+ function getRunningState() {
5443
+ if (isDaemonEnabled()) {
5444
+ const daemon = getDaemonStatus();
5445
+ return { running: isDaemonRunning(daemon), pid: daemon.pid, daemon: true };
5290
5446
  }
5291
- console.log("");
5447
+ const status = getStatus();
5448
+ return { running: status.running, pid: status.pid, daemon: false };
5292
5449
  }
5293
- async function cmdDaemon(args) {
5294
- const action = args?.[1];
5295
- if (action === "on" || action === "enable") {
5296
- if (!hasKernel()) {
5297
- console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
5298
- process.exit(1);
5299
- }
5300
- const sub = getActiveSubscription();
5301
- if (!sub) {
5302
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
5303
- process.exit(1);
5304
- }
5305
- let configInfo;
5306
- try {
5307
- configInfo = prepareConfigForStart("mixed", sub.name);
5308
- } catch (e) {
5309
- console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
5310
- process.exit(1);
5311
- }
5312
- console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u5B89\u88C5\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1\uFF08LaunchDaemon\uFF09"));
5313
- 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"));
5314
- try {
5315
- enableDaemon();
5316
- } catch (e) {
5317
- console.error(`${colors.red("\u542F\u7528\u4FDD\u6D3B\u5931\u8D25:")} ${e.message}`);
5318
- process.exit(1);
5319
- }
5320
- console.log(`${colors.green("\u5DF2\u542F\u7528\u4FDD\u6D3B")} \xB7 ${sub.name} \xB7 ${formatProxySummary(configInfo)}`);
5321
- console.log(colors.gray("\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF0C\u4EE3\u7406\u5C06\u5728\u540E\u53F0\u5E38\u9A7B"));
5322
- console.log("");
5450
+ function isRestartNeededOnChange() {
5451
+ return isDaemonEnabled() || getStatus().running;
5452
+ }
5453
+ async function launchOrRestart(mode) {
5454
+ if (isDaemonEnabled()) {
5455
+ await restartDaemon();
5323
5456
  await sleep(DAEMON_BOOT_WAIT_MS);
5324
- printDaemonStatus();
5325
- return;
5326
- }
5327
- if (action === "off" || action === "disable") {
5328
- if (!isDaemonEnabled()) {
5329
- console.log("\u4FDD\u6D3B\u5DF2\u662F\u5173\u95ED\u72B6\u6001");
5330
- console.log("");
5331
- printDaemonStatus();
5332
- return;
5333
- }
5334
- console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u79FB\u9664\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1"));
5335
- try {
5336
- disableDaemon();
5337
- } catch (e) {
5338
- console.error(`${colors.red("\u5173\u95ED\u4FDD\u6D3B\u5931\u8D25:")} ${e.message}`);
5339
- process.exit(1);
5340
- }
5341
- console.log(`${colors.green("\u5DF2\u5173\u95ED\u4FDD\u6D3B")}\uFF0C\u4EE3\u7406\u5DF2\u505C\u6B62`);
5342
- console.log(colors.gray("\u91CD\u65B0\u542F\u7528: mihomo daemon on"));
5343
- console.log("");
5344
- return;
5345
- }
5346
- if (action !== void 0 && action !== "status") {
5347
- console.error(`\u9519\u8BEF: \u672A\u77E5\u7684 daemon \u5B50\u547D\u4EE4: ${action}`);
5348
- console.log("");
5349
- console.log("\u53EF\u7528\u5B50\u547D\u4EE4: on, off, status");
5350
- process.exit(1);
5457
+ return getDaemonStatus().pid;
5351
5458
  }
5352
- console.log("");
5353
- printDaemonStatus();
5459
+ const result = await start(mode);
5460
+ return result.pid;
5354
5461
  }
5355
5462
 
5356
- // src/commands/directory.ts
5357
- function cmdDirectory(args) {
5358
- const action = args?.[1];
5359
- if (action === "open") {
5360
- const target = args[2];
5361
- if (!target || target === "root") {
5362
- console.log("\u6B63\u5728\u6253\u5F00: \u6839\u76EE\u5F55");
5363
- const success = openUrl(USER_DATA_DIR);
5364
- if (!success) {
5365
- console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${USER_DATA_DIR}`);
5463
+ // src/progress.ts
5464
+ var IS_TTY = process.stdout.isTTY === true;
5465
+ var BAR_WIDTH = 20;
5466
+ function createProgressPrinter(totalRounds = 1) {
5467
+ let alive = 0;
5468
+ let dead = 0;
5469
+ const resultMap = /* @__PURE__ */ new Map();
5470
+ function render(done, total) {
5471
+ if (!IS_TTY) return;
5472
+ const pct = Math.round(done / total * 100);
5473
+ const filled = Math.round(done / total * BAR_WIDTH);
5474
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
5475
+ process.stdout.write(`\r${bar} ${done}/${total} (${pct}%) | ${colors.green(`\u2713${alive}`)} ${colors.red(`\u2717${dead}`)}`);
5476
+ }
5477
+ return {
5478
+ onResult(result, index, total, round = 1) {
5479
+ if (resultMap.size === 0 && totalRounds > 1) {
5480
+ console.log(`--- \u7B2C 1 \u8F6E\u6D4B\u8BD5 (${total} \u4E2A\u8282\u70B9) ---`);
5366
5481
  }
5367
- return;
5368
- }
5369
- const key = target.toLowerCase();
5370
- const targetInfo = Object.hasOwn(DIRECTORY_TARGETS, key) ? DIRECTORY_TARGETS[key] : void 0;
5371
- if (targetInfo) {
5372
- const targetPath = targetInfo.path || USER_DATA_DIR;
5373
- console.log(`\u6B63\u5728\u6253\u5F00: ${targetInfo.label}`);
5374
- const success = openUrl(targetPath);
5375
- if (!success) {
5376
- console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${targetPath}`);
5482
+ const prev = resultMap.get(result.name);
5483
+ if (prev) {
5484
+ if (prev.result.delay !== null) alive--;
5485
+ else dead--;
5377
5486
  }
5378
- return;
5379
- }
5380
- console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u76EE\u5F55\u76EE\u6807 "${target}"`);
5381
- console.log("");
5382
- console.log("\u53EF\u7528\u76EE\u6807:");
5383
- console.log(" root (\u9ED8\u8BA4) \u6839\u76EE\u5F55");
5384
- for (const [key2, val] of Object.entries(DIRECTORY_TARGETS)) {
5385
- if (key2 !== "root") {
5386
- console.log(` ${key2.padEnd(14)}${val.label}`);
5487
+ if (result.delay !== null) alive++;
5488
+ else dead++;
5489
+ resultMap.set(result.name, { result, round });
5490
+ render(index + 1, total);
5491
+ },
5492
+ onRetryRound(round, count) {
5493
+ if (IS_TTY) {
5494
+ process.stdout.write("\n");
5495
+ }
5496
+ console.log(`--- \u7B2C ${round} \u8F6E\u91CD\u8BD5 (${count} \u4E2A\u8282\u70B9) ---`);
5497
+ alive = 0;
5498
+ dead = 0;
5499
+ },
5500
+ finish() {
5501
+ if (IS_TTY) {
5502
+ process.stdout.write("\n");
5503
+ }
5504
+ console.log("");
5505
+ if (!IS_TTY) return;
5506
+ const entries = [...resultMap.values()];
5507
+ entries.sort((a, b) => a.result.name.localeCompare(b.result.name));
5508
+ const total = entries.length;
5509
+ console.log("\u8282\u70B9\u6700\u7EC8\u72B6\u6001:");
5510
+ for (let i = 0; i < entries.length; i++) {
5511
+ const { result, round } = entries[i];
5512
+ const prefix = `[${i + 1}/${total}]`;
5513
+ if (result.delay !== null) {
5514
+ const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
5515
+ const retryNote = round > 1 ? colors.gray(` (\u7B2C${round}\u8F6E\u901A\u8FC7)`) : "";
5516
+ console.log(`${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}${retryNote}`);
5517
+ } else {
5518
+ console.log(`${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
5519
+ }
5387
5520
  }
5521
+ console.log("");
5388
5522
  }
5389
- console.log("");
5390
- process.exit(1);
5391
- }
5392
- console.log("");
5393
- console.log("\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E:");
5394
- console.log(` \u6839\u76EE\u5F55: ${USER_DATA_DIR}`);
5395
- console.log(` \u5168\u5C40\u8BBE\u7F6E: ${PATHS.settingsFile}`);
5396
- console.log(` \u5185\u6838\u76EE\u5F55: ${DIRS.kernel}`);
5397
- console.log(` \u5185\u6838\u6587\u4EF6: ${PATHS.mihomoBinary}`);
5398
- console.log(` \u8BA2\u9605\u76EE\u5F55: ${DIRS.subscriptions}`);
5399
- console.log(" - cache.json (\u8BA2\u9605\u7F13\u5B58\uFF1A\u66F4\u65B0\u65F6\u95F4\u3001\u6D41\u91CF\u7B49)");
5400
- console.log(" - xxx.yaml (\u8BA2\u9605\u539F\u59CB\u914D\u7F6E)");
5401
- console.log(` \u8FD0\u884C\u65F6\u76EE\u5F55: ${DIRS.runtime}`);
5402
- console.log(" - config.yaml (\u542F\u52A8\u65F6\u751F\u6210\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
5403
- console.log(" - pid (PID \u6587\u4EF6\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
5404
- console.log(` \u65E5\u5FD7\u6587\u4EF6: ${PATHS.logFile}`);
5405
- console.log(` mihomo \u6570\u636E: ${DIRS.data}`);
5406
- console.log(" - cache.db, Geo*.dat \u7B49 (mihomo \u81EA\u884C\u7BA1\u7406)");
5407
- console.log("");
5408
- console.log("\u6253\u5F00\u76EE\u5F55:");
5409
- console.log(" mihomo dir open \u6253\u5F00\u6839\u76EE\u5F55");
5410
- console.log(" mihomo dir open subs \u6253\u5F00\u8BA2\u9605\u76EE\u5F55");
5411
- console.log(" mihomo dir open logs \u6253\u5F00\u65E5\u5FD7\u76EE\u5F55");
5412
- console.log(" mihomo dir open data \u6253\u5F00 mihomo \u6570\u636E\u76EE\u5F55");
5413
- console.log(" mihomo dir open runtime \u6253\u5F00\u8FD0\u884C\u65F6\u76EE\u5F55");
5414
- console.log(" mihomo dir open kernel \u6253\u5F00\u5185\u6838\u76EE\u5F55");
5415
- console.log("");
5416
- console.log("\u73AF\u5883\u53D8\u91CF:");
5417
- console.log(" MIHOMO_CLI_DIR: \u81EA\u5B9A\u4E49\u6839\u76EE\u5F55\u4F4D\u7F6E");
5418
- console.log("");
5523
+ };
5524
+ }
5525
+ function formatCleanSummary(result) {
5526
+ const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
5527
+ if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
5528
+ if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
5529
+ return parts.join(", ");
5530
+ }
5531
+ function formatTestSummary(summary) {
5532
+ return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
5419
5533
  }
5420
5534
 
5421
- // src/kernel.ts
5422
- import { spawnSync as spawnSync5 } from "child_process";
5423
- import fs7 from "fs";
5424
- import path6 from "path";
5425
-
5426
- // node_modules/compare-versions/lib/esm/utils.js
5427
- var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
5428
- var validateAndParse = (version) => {
5429
- if (typeof version !== "string") {
5430
- throw new TypeError("Invalid argument expected string");
5535
+ // src/commands/status.ts
5536
+ function printStatus() {
5537
+ const status = getStatus();
5538
+ const state = getRunningState();
5539
+ const info = getConfigInfo();
5540
+ const overwriteEnabled = isOverwriteEnabled();
5541
+ const overwriteFiles = listOverwriteFile().files;
5542
+ const activeSub = getActiveSubscription();
5543
+ const { running, pid, daemon: daemonManaged } = state;
5544
+ console.log("");
5545
+ let modeLabel = "";
5546
+ if (info) {
5547
+ modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
5431
5548
  }
5432
- const match = version.match(semver);
5433
- if (!match) {
5434
- throw new Error(`Invalid argument not valid semver ('${version}' received)`);
5549
+ const statusText = running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
5550
+ console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}${modeLabel}`);
5551
+ console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
5552
+ if (pid) {
5553
+ console.log(`${colors.gray("PID: ")}${pid}`);
5554
+ if (!daemonManaged && status.processInfo) {
5555
+ console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
5556
+ }
5435
5557
  }
5436
- match.shift();
5437
- return match;
5438
- };
5439
- var isWildcard = (s) => s === "*" || s === "x" || s === "X";
5440
- var tryParse = (v) => {
5441
- const n = parseInt(v, 10);
5442
- return isNaN(n) ? v : n;
5443
- };
5444
- var forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b];
5445
- var compareStrings = (a, b) => {
5446
- if (isWildcard(a) || isWildcard(b))
5447
- return 0;
5448
- const [ap, bp] = forceType(tryParse(a), tryParse(b));
5449
- if (ap > bp)
5450
- return 1;
5451
- if (ap < bp)
5452
- return -1;
5453
- return 0;
5454
- };
5455
- var compareSegments = (a, b) => {
5456
- for (let i = 0; i < Math.max(a.length, b.length); i++) {
5457
- const r = compareStrings(a[i] || "0", b[i] || "0");
5458
- if (r !== 0)
5459
- return r;
5558
+ if (info) {
5559
+ if (info.tun) {
5560
+ const extra = info.mixedPort ? `\uFF0C\u53E6\u76D1\u542C ${info.mixedPort}` : "";
5561
+ console.log(`${colors.gray("\u7AEF\u53E3: ")}TUN \u63A5\u7BA1${extra}`);
5562
+ } else if (info.mixedPort) {
5563
+ console.log(`${colors.gray("\u7AEF\u53E3: ")}${info.mixedPort}`);
5564
+ } else {
5565
+ const ports = [];
5566
+ if (info.httpPort) ports.push(`HTTP:${info.httpPort}`);
5567
+ if (info.socksPort) ports.push(`SOCKS:${info.socksPort}`);
5568
+ console.log(`${colors.gray("\u7AEF\u53E3: ")}${ports.length > 0 ? ports.join(", ") : "\u672A\u77E5"}`);
5569
+ }
5460
5570
  }
5461
- return 0;
5462
- };
5463
-
5464
- // node_modules/compare-versions/lib/esm/compareVersions.js
5465
- var compareVersions = (v1, v2) => {
5466
- const n1 = validateAndParse(v1);
5467
- const n2 = validateAndParse(v2);
5468
- const p1 = n1.pop();
5469
- const p2 = n2.pop();
5470
- const r = compareSegments(n1, n2);
5471
- if (r !== 0)
5472
- return r;
5473
- if (p1 && p2) {
5474
- return compareSegments(p1.split("."), p2.split("."));
5475
- } else if (p1 || p2) {
5476
- return p1 ? -1 : 1;
5571
+ if (activeSub) {
5572
+ let subLine = `${colors.gray("\u8BA2\u9605: ")}${activeSub.name}`;
5573
+ if (info) {
5574
+ subLine += ` (${formatProxySummary(info)})`;
5575
+ }
5576
+ console.log(subLine);
5577
+ } else {
5578
+ console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
5477
5579
  }
5478
- return 0;
5479
- };
5480
-
5481
- // src/kernel.ts
5482
- var GITHUB_REPO = "MetaCubeX/mihomo";
5483
- var KERNEL_HTTP_TIMEOUT = 12e4;
5484
- var KERNEL_DOWNLOAD_TIMEOUT = 18e4;
5485
- var HTTP_CLIENT2 = createHttpClient({ timeout: KERNEL_HTTP_TIMEOUT });
5486
- function withMirror(url, mirror) {
5487
- if (mirror && (url.startsWith("https://github.com/") || url.startsWith("https://api.github.com/"))) {
5488
- return mirror + url;
5580
+ if (overwriteEnabled && overwriteFiles.length > 0) {
5581
+ const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "").replace(/\.ya?ml$/, "") || "\u4E3B\u6587\u4EF6").join(", ");
5582
+ console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (${names})`);
5583
+ } else if (overwriteEnabled) {
5584
+ console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (\u65E0\u6587\u4EF6)`);
5585
+ } else {
5586
+ console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
5489
5587
  }
5490
- return url;
5491
- }
5492
- function getArch() {
5493
- const arch = process.arch;
5494
- if (arch === "arm64") return "arm64";
5495
- if (arch === "x64") return "amd64";
5496
- return arch;
5497
- }
5498
- function findMatchingAsset(assets, platform, arch) {
5499
- const prefix = `mihomo-${platform}-${arch}`;
5500
- const matchingAssets = assets.filter(
5501
- (a) => a.name.startsWith(prefix) && a.name.endsWith(".gz") || a.name.startsWith(`${prefix}-`) && a.name.endsWith(".gz")
5502
- );
5503
- if (matchingAssets.length === 0) return null;
5504
- if (matchingAssets.length === 1) return matchingAssets[0];
5505
- const standardAsset = matchingAssets.find((a) => {
5506
- const nameWithoutGz = a.name.slice(0, -3);
5507
- const parts = nameWithoutGz.split("-");
5508
- const lastPart = parts[parts.length - 1];
5509
- return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes("-go") && !nameWithoutGz.includes("-compatible");
5510
- });
5511
- return standardAsset || matchingAssets[0];
5512
- }
5513
- async function getLatestRelease(repo, mirror) {
5514
- const url = withMirror(`https://api.github.com/repos/${repo}/releases`, mirror);
5515
- const response = await HTTP_CLIENT2.get(url, { responseType: "json" });
5516
- const releases = response.data;
5517
- if (!Array.isArray(releases) || releases.length === 0) {
5518
- throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7248\u672C\u4FE1\u606F");
5588
+ if (isDaemonEnabled()) {
5589
+ console.log(`${colors.gray("\u4FDD\u6D3B: ")}${colors.green("\u5DF2\u542F\u7528")} ${colors.gray("(\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F)")}`);
5519
5590
  }
5520
- const stableReleases = releases.filter(
5521
- (r) => !r.prerelease && !r.tag_name.toLowerCase().includes("alpha") && !r.tag_name.toLowerCase().includes("beta") && !r.tag_name.toLowerCase().includes("prerelease")
5522
- );
5523
- return stableReleases.length > 0 ? stableReleases[0] : releases[0];
5591
+ console.log("");
5524
5592
  }
5525
- async function checkUpdate(mirror) {
5526
- const currentVersion = getKernelVersion();
5527
- const latest = await getLatestRelease(GITHUB_REPO, mirror);
5528
- const latestVersion = latest.tag_name;
5529
- let needsUpdate = false;
5530
- const currentDisplay = currentVersion || "\u672A\u5B89\u88C5";
5531
- if (!currentVersion) {
5532
- needsUpdate = true;
5533
- } else {
5534
- try {
5535
- needsUpdate = compareVersions(latestVersion.replace(/^v/, ""), currentVersion.replace(/^v/, "")) > 0;
5536
- } catch {
5537
- needsUpdate = latestVersion !== currentVersion;
5538
- }
5593
+
5594
+ // src/commands/stop.ts
5595
+ function handleStopResult(result) {
5596
+ if (result.remaining && result.remaining.length > 0) {
5597
+ throw new CliError(result.remaining.join(", "), { label: "\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62", hint: "\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo" });
5539
5598
  }
5540
- return {
5541
- current: currentDisplay,
5542
- latest: latestVersion,
5543
- needsUpdate,
5544
- assets: latest.assets,
5545
- release: latest
5546
- };
5547
5599
  }
5548
- function findBinaryInDir(dir, maxDepth = 4) {
5549
- if (maxDepth <= 0) return null;
5550
- const files = fs7.readdirSync(dir);
5551
- for (const f of files) {
5552
- const fullPath = path6.join(dir, f);
5553
- const stat = fs7.statSync(fullPath);
5554
- if (stat.isDirectory()) {
5555
- const found = findBinaryInDir(fullPath, maxDepth - 1);
5556
- if (found) return found;
5557
- continue;
5558
- }
5559
- if (f === "mihomo") return fullPath;
5560
- if (f.includes("mihomo") && !f.endsWith(".gz")) return fullPath;
5600
+ async function cmdStop() {
5601
+ if (isDaemonEnabled()) {
5602
+ console.log(colors.yellow("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF0C\u4EE3\u7406\u7531 launchd \u6258\u7BA1"));
5603
+ console.log("\u76F4\u63A5\u505C\u6B62\u4F1A\u88AB\u81EA\u52A8\u91CD\u65B0\u62C9\u8D77\uFF0C\u8BF7\u7528: mihomo daemon off");
5604
+ return;
5561
5605
  }
5562
- return null;
5606
+ const pids = getMihomoPids();
5607
+ if (pids.length === 0) {
5608
+ console.log(colors.yellow("\u4E0D\u5728\u8FD0\u884C"));
5609
+ return;
5610
+ }
5611
+ console.log(`\u505C\u6B62 ${pids.length} \u4E2A\u8FDB\u7A0B...`);
5612
+ handleStopResult(stop());
5613
+ console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
5563
5614
  }
5564
- async function downloadKernel(progressCallback, mirror, releaseInfo) {
5565
- ensureDirs();
5566
- const latest = releaseInfo || await getLatestRelease(GITHUB_REPO, mirror);
5567
- const arch = getArch();
5568
- const platform = process.platform;
5569
- const asset = findMatchingAsset(latest.assets, platform, arch);
5570
- if (!asset) {
5571
- const available = latest.assets.map((a) => a.name).join(", ");
5572
- let hint = "";
5573
- if (available) hint = `
5574
- \u53EF\u7528\u7248\u672C: ${available}`;
5575
- throw new Error(`\u672A\u627E\u5230\u5339\u914D\u7684\u5185\u6838\u6587\u4EF6
5576
- \u5E73\u53F0: ${platform}, \u67B6\u6784: ${arch}${hint}`);
5615
+
5616
+ // src/commands/start.ts
5617
+ async function cmdStart(args) {
5618
+ if (!hasKernel()) {
5619
+ throw new CliError('\u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
5577
5620
  }
5578
- const downloadUrl = withMirror(asset.browser_download_url, mirror);
5579
- const tempPath = path6.join(DIRS.kernel, asset.name);
5580
- const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
5581
- if (mirror && progressCallback) {
5582
- progressCallback("\u63D0\u793A: \u7ECF\u7B2C\u4E09\u65B9\u955C\u50CF\u4E2D\u8F6C\u4E0B\u8F7D\uFF0C\u65E0\u6CD5\u9A8C\u8BC1\u6765\u6E90\u5B8C\u6574\u6027\uFF0C\u5EFA\u8BAE\u76F4\u8FDE\u6216\u81EA\u884C\u6821\u9A8C\u4EA7\u7269");
5621
+ const targetMode = args[1] === "tun" ? "tun" : "mixed";
5622
+ const daemonEnabled = isDaemonEnabled();
5623
+ if (targetMode === "tun" && daemonEnabled) {
5624
+ throw new CliError("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF08\u4EC5\u652F\u6301 Mixed \u6A21\u5F0F\uFF09\uFF0C\u65E0\u6CD5\u542F\u52A8 TUN", { hint: "\u8BF7\u5148\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off" });
5583
5625
  }
5584
- if (progressCallback) {
5585
- progressCallback(`\u4E0B\u8F7D\u5185\u6838: ${asset.name} (${sizeMB} MB)`);
5626
+ const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
5627
+ const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
5628
+ const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
5629
+ const skipUpdate = hasFlag(args, "-s", "--no-update");
5630
+ const skipClean = hasFlag(args, "--no-clean");
5631
+ const updateTimeout = parseIntArg(args, "-u", "--update-timeout", DEFAULT_AUTO_UPDATE_TIMEOUT);
5632
+ const sub = requireActiveSubscription("\u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
5633
+ if (!skipUpdate) {
5634
+ await autoUpdateStaleSubscription({ timeout: updateTimeout });
5586
5635
  }
5587
- const curlResult = spawnSync5(
5588
- "curl",
5589
- ["-L", "--progress-bar", "--connect-timeout", "30", "--max-time", String(Math.floor(KERNEL_DOWNLOAD_TIMEOUT / 1e3)), "-o", tempPath, downloadUrl],
5590
- { stdio: "inherit" }
5591
- );
5592
- if (curlResult.error) {
5593
- if (curlResult.error.code === "ENOENT") {
5594
- throw new Error("\u672A\u627E\u5230 curl \u547D\u4EE4\uFF0C\u8BF7\u5148\u5B89\u88C5 curl \u540E\u91CD\u8BD5");
5636
+ if (!daemonEnabled) {
5637
+ if (hasRootResidue()) {
5638
+ throw new CliError("\u5B58\u5728\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559\u8FDB\u7A0B/\u6587\u4EF6", {
5639
+ hint: [`\u8BF7\u5148\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo && sudo rm -f ${PATHS.pidFile}`, "\u6216\u5207\u6362\u5230 TUN \u6A21\u5F0F\u542F\u52A8\uFF08\u81EA\u52A8\u6E05\u7406\uFF09: mihomo start tun"]
5640
+ });
5595
5641
  }
5596
- throw new Error(`\u4E0B\u8F7D\u5931\u8D25: ${curlResult.error.message}`);
5597
- }
5598
- if (curlResult.status !== 0) {
5599
- try {
5600
- fs7.unlinkSync(tempPath);
5601
- } catch {
5642
+ const status = getStatus();
5643
+ const hasProcess = status.running || status.allProcesses.length > 0;
5644
+ if (hasProcess) {
5645
+ const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
5646
+ console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
5647
+ }
5648
+ handleStopResult(stop());
5649
+ if (hasProcess) {
5650
+ console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
5651
+ `);
5602
5652
  }
5603
- throw new Error(`\u4E0B\u8F7D\u5931\u8D25 (curl \u9000\u51FA\u7801 ${curlResult.status})`);
5604
- }
5605
- if (!fs7.existsSync(tempPath)) {
5606
- throw new Error("\u4E0B\u8F7D\u5931\u8D25: \u6587\u4EF6\u672A\u751F\u6210");
5607
5653
  }
5608
- if (progressCallback) {
5609
- progressCallback("\u89E3\u538B\u5185\u6838...");
5654
+ let configInfo;
5655
+ try {
5656
+ configInfo = prepareConfigForStart(targetMode, sub.name);
5657
+ } catch (e) {
5658
+ if (e instanceof CliError) throw e;
5659
+ throw new CliError(e.message, { label: "\u914D\u7F6E\u9519\u8BEF" });
5610
5660
  }
5611
- const extractPath = DIRS.kernel;
5612
- let extractedBinary = null;
5661
+ const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
5662
+ console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
5613
5663
  try {
5614
- if (tempPath.endsWith(".tar.gz") || tempPath.endsWith(".tgz")) {
5615
- const listResult = spawnSync5("tar", ["-tzf", tempPath], { encoding: "utf8", timeout: 6e4 });
5616
- if (listResult.error) throw listResult.error;
5617
- if (listResult.status !== 0) throw new Error(`tar \u5217\u8868\u9000\u51FA\u7801 ${listResult.status}`);
5618
- const entries = (listResult.stdout || "").split("\n").filter(Boolean);
5619
- for (const entry of entries) {
5620
- if (entry.startsWith("/") || entry.split("/").includes("..")) {
5621
- throw new Error(`\u5F52\u6863\u542B\u975E\u6CD5\u8DEF\u5F84\u6761\u76EE: ${entry}`);
5664
+ const pid = await launchOrRestart(targetMode);
5665
+ const label = daemonEnabled ? "\u5DF2\u542F\u52A8 (\u4FDD\u6D3B)" : "\u5DF2\u542F\u52A8";
5666
+ console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""}`);
5667
+ } catch (e) {
5668
+ if (e instanceof CliError) throw e;
5669
+ const lines = e.message.split("\n");
5670
+ throw new CliError(lines[0], { label: "\u542F\u52A8\u5931\u8D25", hint: lines.slice(1) });
5671
+ }
5672
+ const cleanThreshold = isGithubUrl(sub.url) ? AUTO_CLEAN_THRESHOLD_GITHUB : AUTO_CLEAN_THRESHOLD;
5673
+ if (!skipClean && configInfo.proxies > cleanThreshold) {
5674
+ const cache = readSubscriptionCache();
5675
+ const lastCleanAt = cache[sub.name]?.last_auto_clean_at;
5676
+ const withinCooldown = !!lastCleanAt && Date.now() - new Date(lastCleanAt).getTime() < AUTO_CLEAN_COOLDOWN_HOURS * 60 * 60 * 1e3;
5677
+ if (!withinCooldown) {
5678
+ console.log("");
5679
+ console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${cleanThreshold}\uFF0C\u81EA\u52A8\u6E05\u7406\uFF08${AUTO_CLEAN_COOLDOWN_HOURS}h \u5185\u4EC5\u4E00\u6B21\uFF0C--no-clean \u8DF3\u8FC7\uFF09...`);
5680
+ console.log("");
5681
+ await sleep(1e3);
5682
+ const progress = createProgressPrinter(rounds);
5683
+ const cleanResult = await autoCleanSubscription(sub.name, {
5684
+ timeout,
5685
+ concurrency,
5686
+ rounds,
5687
+ onResult: progress.onResult,
5688
+ onRetryRound: progress.onRetryRound
5689
+ });
5690
+ progress.finish();
5691
+ console.log(formatTestSummary(cleanResult.summary));
5692
+ if (cleanResult.skipped) {
5693
+ console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
5694
+ } else if (cleanResult.removedProxies > 0) {
5695
+ console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
5696
+ console.log("");
5697
+ console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
5698
+ if (!daemonEnabled) handleStopResult(stop());
5699
+ try {
5700
+ configInfo = prepareConfigForStart(targetMode, sub.name);
5701
+ const pid = await launchOrRestart(targetMode);
5702
+ console.log(`${colors.green("\u5DF2\u91CD\u542F")}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
5703
+ } catch (e) {
5704
+ if (e instanceof CliError) throw e;
5705
+ throw new CliError(e.message.split("\n")[0], { label: "\u91CD\u542F\u5931\u8D25" });
5622
5706
  }
5623
5707
  }
5624
- const tarResult = spawnSync5("tar", ["-xzf", tempPath, "-C", extractPath], { stdio: ["ignore", "ignore", "inherit"], timeout: 6e4 });
5625
- if (tarResult.error) throw tarResult.error;
5626
- if (tarResult.status !== 0) throw new Error(`tar \u9000\u51FA\u7801 ${tarResult.status}`);
5627
- } else if (tempPath.endsWith(".gz")) {
5628
- const baseName = path6.basename(tempPath, ".gz");
5629
- const outputPath = path6.join(extractPath, baseName);
5630
- const gzipResult = spawnSync5("gzip", ["-dc", tempPath], { maxBuffer: 256 * 1024 * 1024, timeout: 6e4 });
5631
- if (gzipResult.error) throw gzipResult.error;
5632
- if (gzipResult.status !== 0) throw new Error(`gzip \u9000\u51FA\u7801 ${gzipResult.status}`);
5633
- fs7.writeFileSync(outputPath, gzipResult.stdout, { mode: 493 });
5634
- extractedBinary = outputPath;
5635
- }
5636
- } catch (e) {
5637
- try {
5638
- fs7.unlinkSync(tempPath);
5639
- } catch {
5708
+ saveSubscriptionCache(sub.name, { last_auto_clean_at: (/* @__PURE__ */ new Date()).toISOString() });
5640
5709
  }
5641
- throw new Error(`\u89E3\u538B\u5931\u8D25: ${e.message}`);
5642
5710
  }
5643
- const foundBinary = extractedBinary || findBinaryInDir(extractPath);
5644
- if (!foundBinary) {
5645
- try {
5646
- fs7.unlinkSync(tempPath);
5647
- } catch {
5648
- }
5649
- throw new Error("\u89E3\u538B\u540E\u672A\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6");
5711
+ printStatus();
5712
+ }
5713
+
5714
+ // src/commands/shared.ts
5715
+ async function dispatchSubcommand(args, table, options) {
5716
+ const action = args[1];
5717
+ if (action) {
5718
+ const cmd = table.find((c) => c.name === action || c.aliases?.includes(action));
5719
+ if (cmd) return cmd.handler(args);
5720
+ if (options.onUnknown) return options.onUnknown(action);
5650
5721
  }
5651
- const targetPath = PATHS.mihomoBinary;
5652
- if (foundBinary !== targetPath) {
5653
- if (fs7.existsSync(targetPath)) {
5654
- fs7.chmodSync(targetPath, 493);
5655
- try {
5656
- fs7.unlinkSync(targetPath);
5657
- } catch {
5658
- }
5659
- }
5660
- fs7.renameSync(foundBinary, targetPath);
5722
+ return options.fallback(args);
5723
+ }
5724
+ function requireRunning() {
5725
+ const state = getRunningState();
5726
+ if (!state.running) {
5727
+ const hint = state.daemon ? "mihomo daemon on" : "mihomo start";
5728
+ throw new CliError(`mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (${hint})`);
5661
5729
  }
5662
- fs7.chmodSync(targetPath, 493);
5663
- if (progressCallback) {
5664
- progressCallback("\u6821\u9A8C\u5185\u6838...");
5730
+ }
5731
+ async function restartToApply(args) {
5732
+ if (!isRestartNeededOnChange()) return false;
5733
+ const currentMode = getRuntimeMode();
5734
+ console.log("");
5735
+ await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
5736
+ return true;
5737
+ }
5738
+
5739
+ // src/commands/daemon.ts
5740
+ function printDaemonStatus() {
5741
+ const status = getDaemonStatus();
5742
+ const stateText = status.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
5743
+ console.log(`${colors.gray("\u4FDD\u6D3B: ")}${stateText}`);
5744
+ if (status.enabled) {
5745
+ const runText = isDaemonRunning(status) ? colors.green(`\u8FD0\u884C\u4E2D (PID ${status.pid})`) : colors.yellow("\u672A\u8FD0\u884C");
5746
+ console.log(`${colors.gray("\u5185\u6838: ")}${runText}`);
5665
5747
  }
5666
- const check = spawnSync5(targetPath, ["-v"], { encoding: "utf8", timeout: 5e3 });
5667
- const checkOutput = `${check.stdout || ""}${check.stderr || ""}`.trim();
5668
- if (check.error || check.status !== 0 || !/v?\d+\.\d+\.\d+/.test(checkOutput)) {
5669
- try {
5670
- fs7.unlinkSync(targetPath);
5671
- } catch {
5672
- }
5673
- try {
5674
- fs7.unlinkSync(tempPath);
5675
- } catch {
5676
- }
5677
- throw new Error(`\u5185\u6838\u81EA\u68C0\u5931\u8D25\uFF08\u53EF\u80FD\u4E0B\u8F7D\u635F\u574F\u6216\u67B6\u6784\u4E0D\u5339\u914D\uFF09\uFF0C\u5DF2\u5220\u9664
5678
- \u9000\u51FA\u7801: ${check.status}
5679
- \u8F93\u51FA: ${checkOutput || "(\u7A7A)"}`);
5748
+ console.log("");
5749
+ if (status.enabled) {
5750
+ console.log("\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off");
5751
+ } else {
5752
+ console.log("\u5F00\u542F\u4FDD\u6D3B: mihomo daemon on");
5753
+ console.log(colors.gray(" \u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF08\u4EC5 Mixed \u6A21\u5F0F\uFF09"));
5680
5754
  }
5755
+ console.log("");
5756
+ }
5757
+ async function daemonOn() {
5758
+ if (!hasKernel()) {
5759
+ throw new CliError('\u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
5760
+ }
5761
+ const sub = requireActiveSubscription("\u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
5762
+ let configInfo;
5681
5763
  try {
5682
- fs7.unlinkSync(tempPath);
5683
- } catch {
5764
+ configInfo = prepareConfigForStart("mixed", sub.name);
5765
+ } catch (e) {
5766
+ if (e instanceof CliError) throw e;
5767
+ throw new CliError(e.message, { label: "\u914D\u7F6E\u9519\u8BEF" });
5684
5768
  }
5685
- clearKernelVersionCache();
5686
- return { version: latest.tag_name, path: targetPath };
5769
+ console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u5B89\u88C5\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1\uFF08LaunchDaemon\uFF09"));
5770
+ console.log(colors.gray("\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u9700\u8981 root\uFF0C\u4EE5\u89E3\u51B3\u5C40\u57DF\u7F51\u8BBF\u95EE\u53D7\u9650\u95EE\u9898"));
5771
+ try {
5772
+ enableDaemon();
5773
+ } catch (e) {
5774
+ if (e instanceof CliError) throw e;
5775
+ throw new CliError(e.message, { label: "\u542F\u7528\u4FDD\u6D3B\u5931\u8D25" });
5776
+ }
5777
+ console.log(`${colors.green("\u5DF2\u542F\u7528\u4FDD\u6D3B")} \xB7 ${sub.name} \xB7 ${formatProxySummary(configInfo)}`);
5778
+ console.log(colors.gray("\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF0C\u4EE3\u7406\u5C06\u5728\u540E\u53F0\u5E38\u9A7B"));
5779
+ console.log("");
5780
+ await sleep(DAEMON_BOOT_WAIT_MS);
5781
+ printDaemonStatus();
5687
5782
  }
5688
-
5689
- // src/commands/kernel.ts
5690
- async function cmdKernel(args) {
5691
- const mirrorInfo = parseMirrorArg(args);
5692
- const effectiveMirror = mirrorInfo.mirror;
5693
- if (effectiveMirror) {
5694
- const mirrorDesc = mirrorInfo.type === "all" ? " (API\u548C\u4E0B\u8F7D\u5747\u4F7F\u7528\u955C\u50CF)" : " (\u4E0B\u8F7D\u65F6\u4F7F\u7528\u955C\u50CF)";
5695
- console.log(`\u955C\u50CF: ${effectiveMirror}${mirrorDesc}`);
5783
+ function daemonOff() {
5784
+ if (!isDaemonEnabled()) {
5785
+ console.log("\u4FDD\u6D3B\u5DF2\u662F\u5173\u95ED\u72B6\u6001");
5696
5786
  console.log("");
5787
+ printDaemonStatus();
5788
+ return;
5697
5789
  }
5698
- console.log("\u68C0\u67E5\u5185\u6838\u66F4\u65B0...");
5790
+ console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u79FB\u9664\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1"));
5699
5791
  try {
5700
- const apiMirror = mirrorInfo.type === "all" ? effectiveMirror : null;
5701
- const info = await checkUpdate(apiMirror);
5702
- console.log(`\u5F53\u524D: ${info.current}`);
5703
- console.log(`\u6700\u65B0: ${info.latest}`);
5704
- if (!info.needsUpdate) {
5705
- console.log("\u5DF2\u662F\u6700\u65B0\u7248\u672C");
5706
- } else {
5707
- console.log("\n\u6B63\u5728\u4E0B\u8F7D...");
5708
- const result = await downloadKernel((msg) => console.log(msg), mirrorInfo.mirror, info.release);
5709
- console.log(`
5710
- \u5DF2\u66F4\u65B0\u5230 ${result.version}`);
5711
- }
5792
+ disableDaemon();
5712
5793
  } catch (e) {
5713
- console.error(`
5714
- \u66F4\u65B0\u5931\u8D25: ${e.message}`);
5715
- const err = e;
5716
- if (err.response?.data) {
5717
- if (err.response.data.message) {
5718
- console.error(`\u539F\u56E0: ${err.response.data.message}`);
5719
- }
5720
- if (err.response.data.documentation_url) {
5721
- console.error(`\u6587\u6863: ${err.response.data.documentation_url}`);
5722
- }
5723
- }
5724
- if (!effectiveMirror) {
5725
- console.error("");
5726
- console.error("\u63D0\u793A: \u76F4\u8FDE\u5931\u8D25\u6216\u4E0B\u8F7D\u8FC7\u6162\u65F6\u53EF\u4F7F\u7528\u955C\u50CF:");
5727
- console.error(" mihomo kernel --mirror [\u955C\u50CF] # \u4E0B\u8F7D\u8D70\u955C\u50CF\uFF08\u9ED8\u8BA4 v6.gh-proxy.org\uFF09");
5728
- console.error(" mihomo kernel --mirror-all [\u955C\u50CF] # API \u548C\u4E0B\u8F7D\u90FD\u8D70\u955C\u50CF");
5729
- console.error(` \u53EF\u7528\u955C\u50CF: ${AVAILABLE_MIRRORS.join(", ")}`);
5730
- }
5731
- process.exit(1);
5794
+ if (e instanceof CliError) throw e;
5795
+ throw new CliError(e.message, { label: "\u5173\u95ED\u4FDD\u6D3B\u5931\u8D25" });
5732
5796
  }
5797
+ console.log(`${colors.green("\u5DF2\u5173\u95ED\u4FDD\u6D3B")}\uFF0C\u4EE3\u7406\u5DF2\u505C\u6B62`);
5798
+ console.log(colors.gray("\u91CD\u65B0\u542F\u7528: mihomo daemon on"));
5799
+ console.log("");
5733
5800
  }
5734
-
5735
- // src/commands/log.ts
5736
- function cmdLog(args) {
5737
- const logPath = getLogPath();
5738
- if (hasFlag(args, "-o", "--open")) {
5739
- openLogFile(logPath);
5740
- return;
5741
- }
5742
- viewLogWithTail(logPath, { follow: true, lines: 50 });
5801
+ function printStatusView() {
5802
+ console.log("");
5803
+ printDaemonStatus();
5743
5804
  }
5744
- function cmdLogs(args) {
5745
- const targetName = getNonFlagArg(args, 1);
5746
- const lines = parseIntArg(args, "-n", "--lines", 100);
5747
- const openInViewer = hasFlag(args, "-o", "--open");
5748
- if (targetName) {
5749
- let logPath;
5750
- if (targetName === "current" || targetName === "0") {
5751
- logPath = getLogPath();
5752
- } else {
5753
- const parsedIdx = parseInt(targetName, 10);
5754
- if (!Number.isNaN(parsedIdx) && parsedIdx > 0 && String(parsedIdx) === targetName) {
5755
- const archiveLogs = listLogs();
5756
- const archive = archiveLogs.archives[parsedIdx - 1];
5757
- if (!archive) {
5758
- console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
5759
- console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
5760
- process.exit(1);
5761
- }
5762
- logPath = archive.path;
5763
- } else {
5764
- logPath = getLogPathByName(targetName);
5765
- }
5766
- }
5767
- if (!logPath) {
5768
- console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
5769
- console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
5770
- process.exit(1);
5805
+ var SUBCOMMANDS = [
5806
+ { name: "on", aliases: ["enable"], handler: daemonOn },
5807
+ { name: "off", aliases: ["disable"], handler: daemonOff },
5808
+ { name: "status", handler: printStatusView }
5809
+ ];
5810
+ async function cmdDaemon(args) {
5811
+ await dispatchSubcommand(args, SUBCOMMANDS, {
5812
+ // action → 显示状态;未知 action → 报错
5813
+ fallback: printStatusView,
5814
+ onUnknown: (action) => {
5815
+ throw new CliError(`\u672A\u77E5\u7684 daemon \u5B50\u547D\u4EE4: ${action}`, { hint: ["", "\u53EF\u7528\u5B50\u547D\u4EE4: on, off, status"] });
5771
5816
  }
5772
- if (openInViewer) {
5773
- openLogFile(logPath);
5774
- return;
5817
+ });
5818
+ }
5819
+
5820
+ // src/commands/directory.ts
5821
+ function openDirectory(args) {
5822
+ const target = args[2];
5823
+ if (!target || target === "root") {
5824
+ console.log("\u6B63\u5728\u6253\u5F00: \u6839\u76EE\u5F55");
5825
+ const success = openUrl(USER_DATA_DIR);
5826
+ if (!success) {
5827
+ console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${USER_DATA_DIR}`);
5775
5828
  }
5776
- viewLogWithTail(logPath, { follow: false, lines });
5777
5829
  return;
5778
5830
  }
5779
- const logs = listLogs();
5780
- const all = [];
5781
- if (logs.current) all.push(logs.current);
5782
- all.push(...logs.archives);
5783
- if (all.length === 0) {
5784
- console.log("\u6682\u65E0\u65E5\u5FD7");
5831
+ const key = target.toLowerCase();
5832
+ const targetInfo = Object.hasOwn(DIRECTORY_TARGETS, key) ? DIRECTORY_TARGETS[key] : void 0;
5833
+ if (targetInfo) {
5834
+ const targetPath = targetInfo.path || USER_DATA_DIR;
5835
+ console.log(`\u6B63\u5728\u6253\u5F00: ${targetInfo.label}`);
5836
+ const success = openUrl(targetPath);
5837
+ if (!success) {
5838
+ console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${targetPath}`);
5839
+ }
5785
5840
  return;
5786
5841
  }
5842
+ const hint = ["", "\u53EF\u7528\u76EE\u6807:", " root (\u9ED8\u8BA4) \u6839\u76EE\u5F55"];
5843
+ for (const [k, val] of Object.entries(DIRECTORY_TARGETS)) {
5844
+ if (k !== "root") {
5845
+ hint.push(` ${k.padEnd(14)}${val.label}`);
5846
+ }
5847
+ }
5848
+ throw new CliError(`\u672A\u77E5\u7684\u76EE\u5F55\u76EE\u6807 "${target}"`, { hint });
5849
+ }
5850
+ function printDirectoryInfo() {
5787
5851
  console.log("");
5788
- console.log("\u65E5\u5FD7\u5217\u8868:");
5789
- console.log("");
5790
- let archiveCounter = 0;
5791
- for (const log of all) {
5792
- let num;
5793
- if (log.isCurrent) {
5794
- num = " 0";
5795
- } else {
5796
- archiveCounter++;
5797
- num = archiveCounter < 10 ? ` ${archiveCounter}` : `${archiveCounter}`;
5798
- }
5799
- const time = formatDate(log.mtime);
5800
- const size = formatBytes(log.size);
5801
- const name = log.isCurrent ? "mihomo.log (\u5F53\u524D\u8FD0\u884C\u4E2D)" : log.name;
5802
- console.log(` ${num}. ${name}`);
5803
- console.log(` \u65F6\u95F4: ${time} \u5927\u5C0F: ${size}`);
5804
- if (!log.isCurrent) {
5805
- console.log(` \u67E5\u770B: mihomo logs ${archiveCounter} \u6216 mihomo logs ${archiveCounter} -o`);
5806
- }
5807
- console.log("");
5808
- }
5809
- console.log("\u7528\u6CD5:");
5810
- console.log(" mihomo logs 0 # \u67E5\u770B\u5F53\u524D\u65E5\u5FD7 (\u6700\u540E 100 \u884C)");
5811
- console.log(" mihomo logs 1 # \u67E5\u770B\u7B2C 1 \u4E2A\u5F52\u6863\u65E5\u5FD7\uFF08\u6700\u65B0\uFF09");
5812
- console.log(" mihomo logs 1 -n 200 # \u67E5\u770B 200 \u884C");
5813
- console.log(" mihomo logs 1 -o # \u7528\u7CFB\u7EDF\u9ED8\u8BA4\u7A0B\u5E8F\u6253\u5F00");
5852
+ console.log("\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E:");
5853
+ console.log(` \u6839\u76EE\u5F55: ${USER_DATA_DIR}`);
5854
+ console.log(` \u5168\u5C40\u8BBE\u7F6E: ${PATHS.settingsFile}`);
5855
+ console.log(` \u5185\u6838\u76EE\u5F55: ${DIRS.kernel}`);
5856
+ console.log(` \u5185\u6838\u6587\u4EF6: ${PATHS.mihomoBinary}`);
5857
+ console.log(` \u8BA2\u9605\u76EE\u5F55: ${DIRS.subscriptions}`);
5858
+ console.log(" - cache.json (\u8BA2\u9605\u7F13\u5B58\uFF1A\u66F4\u65B0\u65F6\u95F4\u3001\u6D41\u91CF\u7B49)");
5859
+ console.log(" - xxx.yaml (\u8BA2\u9605\u539F\u59CB\u914D\u7F6E)");
5860
+ console.log(` \u8FD0\u884C\u65F6\u76EE\u5F55: ${DIRS.runtime}`);
5861
+ console.log(" - config.yaml (\u542F\u52A8\u65F6\u751F\u6210\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
5862
+ console.log(" - pid (PID \u6587\u4EF6\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
5863
+ console.log(` \u65E5\u5FD7\u6587\u4EF6: ${PATHS.logFile}`);
5864
+ console.log(` mihomo \u6570\u636E: ${DIRS.data}`);
5865
+ console.log(" - cache.db, Geo*.dat \u7B49 (mihomo \u81EA\u884C\u7BA1\u7406)");
5866
+ console.log("");
5867
+ console.log("\u6253\u5F00\u76EE\u5F55:");
5868
+ console.log(" mihomo dir open \u6253\u5F00\u6839\u76EE\u5F55");
5869
+ console.log(" mihomo dir open subs \u6253\u5F00\u8BA2\u9605\u76EE\u5F55");
5870
+ console.log(" mihomo dir open logs \u6253\u5F00\u65E5\u5FD7\u76EE\u5F55");
5871
+ console.log(" mihomo dir open data \u6253\u5F00 mihomo \u6570\u636E\u76EE\u5F55");
5872
+ console.log(" mihomo dir open runtime \u6253\u5F00\u8FD0\u884C\u65F6\u76EE\u5F55");
5873
+ console.log(" mihomo dir open kernel \u6253\u5F00\u5185\u6838\u76EE\u5F55");
5874
+ console.log("");
5875
+ console.log("\u73AF\u5883\u53D8\u91CF:");
5876
+ console.log(" MIHOMO_CLI_DIR: \u81EA\u5B9A\u4E49\u6839\u76EE\u5F55\u4F4D\u7F6E");
5814
5877
  console.log("");
5815
5878
  }
5879
+ var SUBCOMMANDS2 = [{ name: "open", handler: openDirectory }];
5880
+ function cmdDirectory(args) {
5881
+ void dispatchSubcommand(args, SUBCOMMANDS2, { fallback: printDirectoryInfo });
5882
+ }
5816
5883
 
5817
- // src/commands/overwrite.ts
5818
- import path7 from "path";
5884
+ // src/kernel.ts
5885
+ import { spawnSync as spawnSync5 } from "child_process";
5886
+ import fs7 from "fs";
5887
+ import path6 from "path";
5819
5888
 
5820
- // src/runtime.ts
5821
- function getRuntimeMode() {
5822
- if (isDaemonEnabled()) return "mixed";
5823
- return getConfigInfo()?.tun ? "tun" : "mixed";
5824
- }
5825
- function getRunningState() {
5826
- if (isDaemonEnabled()) {
5827
- const daemon = getDaemonStatus();
5828
- return { running: isDaemonRunning(daemon), pid: daemon.pid, daemon: true };
5889
+ // node_modules/compare-versions/lib/esm/utils.js
5890
+ var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
5891
+ var validateAndParse = (version) => {
5892
+ if (typeof version !== "string") {
5893
+ throw new TypeError("Invalid argument expected string");
5829
5894
  }
5830
- const status = getStatus();
5831
- return { running: status.running, pid: status.pid, daemon: false };
5895
+ const match = version.match(semver);
5896
+ if (!match) {
5897
+ throw new Error(`Invalid argument not valid semver ('${version}' received)`);
5898
+ }
5899
+ match.shift();
5900
+ return match;
5901
+ };
5902
+ var isWildcard = (s) => s === "*" || s === "x" || s === "X";
5903
+ var tryParse = (v) => {
5904
+ const n = parseInt(v, 10);
5905
+ return isNaN(n) ? v : n;
5906
+ };
5907
+ var forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b];
5908
+ var compareStrings = (a, b) => {
5909
+ if (isWildcard(a) || isWildcard(b))
5910
+ return 0;
5911
+ const [ap, bp] = forceType(tryParse(a), tryParse(b));
5912
+ if (ap > bp)
5913
+ return 1;
5914
+ if (ap < bp)
5915
+ return -1;
5916
+ return 0;
5917
+ };
5918
+ var compareSegments = (a, b) => {
5919
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
5920
+ const r = compareStrings(a[i] || "0", b[i] || "0");
5921
+ if (r !== 0)
5922
+ return r;
5923
+ }
5924
+ return 0;
5925
+ };
5926
+
5927
+ // node_modules/compare-versions/lib/esm/compareVersions.js
5928
+ var compareVersions = (v1, v2) => {
5929
+ const n1 = validateAndParse(v1);
5930
+ const n2 = validateAndParse(v2);
5931
+ const p1 = n1.pop();
5932
+ const p2 = n2.pop();
5933
+ const r = compareSegments(n1, n2);
5934
+ if (r !== 0)
5935
+ return r;
5936
+ if (p1 && p2) {
5937
+ return compareSegments(p1.split("."), p2.split("."));
5938
+ } else if (p1 || p2) {
5939
+ return p1 ? -1 : 1;
5940
+ }
5941
+ return 0;
5942
+ };
5943
+
5944
+ // src/kernel.ts
5945
+ var GITHUB_REPO = "MetaCubeX/mihomo";
5946
+ var KERNEL_HTTP_TIMEOUT = 12e4;
5947
+ var KERNEL_DOWNLOAD_TIMEOUT = 18e4;
5948
+ var HTTP_CLIENT2 = createHttpClient({ timeout: KERNEL_HTTP_TIMEOUT });
5949
+ function withMirror(url, mirror) {
5950
+ if (mirror && (url.startsWith("https://github.com/") || url.startsWith("https://api.github.com/"))) {
5951
+ return mirror + url;
5952
+ }
5953
+ return url;
5832
5954
  }
5833
- function isRestartNeededOnChange() {
5834
- return isDaemonEnabled() || getStatus().running;
5955
+ function getArch() {
5956
+ const arch = process.arch;
5957
+ if (arch === "arm64") return "arm64";
5958
+ if (arch === "x64") return "amd64";
5959
+ return arch;
5835
5960
  }
5836
- async function launchOrRestart(mode) {
5837
- if (isDaemonEnabled()) {
5838
- await restartDaemon();
5839
- await sleep(DAEMON_BOOT_WAIT_MS);
5840
- return getDaemonStatus().pid;
5961
+ function findMatchingAsset(assets, platform, arch) {
5962
+ const prefix = `mihomo-${platform}-${arch}`;
5963
+ const matchingAssets = assets.filter(
5964
+ (a) => a.name.startsWith(prefix) && a.name.endsWith(".gz") || a.name.startsWith(`${prefix}-`) && a.name.endsWith(".gz")
5965
+ );
5966
+ if (matchingAssets.length === 0) return null;
5967
+ if (matchingAssets.length === 1) return matchingAssets[0];
5968
+ const standardAsset = matchingAssets.find((a) => {
5969
+ const nameWithoutGz = a.name.slice(0, -3);
5970
+ const parts = nameWithoutGz.split("-");
5971
+ const lastPart = parts[parts.length - 1];
5972
+ return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes("-go") && !nameWithoutGz.includes("-compatible");
5973
+ });
5974
+ return standardAsset || matchingAssets[0];
5975
+ }
5976
+ async function getLatestRelease(repo, mirror) {
5977
+ const url = withMirror(`https://api.github.com/repos/${repo}/releases`, mirror);
5978
+ const response = await HTTP_CLIENT2.get(url, { responseType: "json" });
5979
+ const releases = response.data;
5980
+ if (!Array.isArray(releases) || releases.length === 0) {
5981
+ throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7248\u672C\u4FE1\u606F");
5841
5982
  }
5842
- const result = await start(mode);
5843
- return result.pid;
5983
+ const stableReleases = releases.filter(
5984
+ (r) => !r.prerelease && !r.tag_name.toLowerCase().includes("alpha") && !r.tag_name.toLowerCase().includes("beta") && !r.tag_name.toLowerCase().includes("prerelease")
5985
+ );
5986
+ return stableReleases.length > 0 ? stableReleases[0] : releases[0];
5844
5987
  }
5845
-
5846
- // src/progress.ts
5847
- var IS_TTY = process.stdout.isTTY === true;
5848
- var BAR_WIDTH = 20;
5849
- function createProgressPrinter(totalRounds = 1) {
5850
- let alive = 0;
5851
- let dead = 0;
5852
- const resultMap = /* @__PURE__ */ new Map();
5853
- function render(done, total) {
5854
- if (!IS_TTY) return;
5855
- const pct = Math.round(done / total * 100);
5856
- const filled = Math.round(done / total * BAR_WIDTH);
5857
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
5858
- process.stdout.write(`\r${bar} ${done}/${total} (${pct}%) | ${colors.green(`\u2713${alive}`)} ${colors.red(`\u2717${dead}`)}`);
5988
+ async function checkUpdate(mirror) {
5989
+ const currentVersion = getKernelVersion();
5990
+ const latest = await getLatestRelease(GITHUB_REPO, mirror);
5991
+ const latestVersion = latest.tag_name;
5992
+ let needsUpdate = false;
5993
+ const currentDisplay = currentVersion || "\u672A\u5B89\u88C5";
5994
+ if (!currentVersion) {
5995
+ needsUpdate = true;
5996
+ } else {
5997
+ try {
5998
+ needsUpdate = compareVersions(latestVersion.replace(/^v/, ""), currentVersion.replace(/^v/, "")) > 0;
5999
+ } catch {
6000
+ needsUpdate = latestVersion !== currentVersion;
6001
+ }
5859
6002
  }
5860
6003
  return {
5861
- onResult(result, index, total, round = 1) {
5862
- if (resultMap.size === 0 && totalRounds > 1) {
5863
- console.log(`--- \u7B2C 1 \u8F6E\u6D4B\u8BD5 (${total} \u4E2A\u8282\u70B9) ---`);
5864
- }
5865
- const prev = resultMap.get(result.name);
5866
- if (prev) {
5867
- if (prev.result.delay !== null) alive--;
5868
- else dead--;
5869
- }
5870
- if (result.delay !== null) alive++;
5871
- else dead++;
5872
- resultMap.set(result.name, { result, round });
5873
- render(index + 1, total);
5874
- },
5875
- onRetryRound(round, count) {
5876
- if (IS_TTY) {
5877
- process.stdout.write("\n");
5878
- }
5879
- console.log(`--- \u7B2C ${round} \u8F6E\u91CD\u8BD5 (${count} \u4E2A\u8282\u70B9) ---`);
5880
- alive = 0;
5881
- dead = 0;
5882
- },
5883
- finish() {
5884
- if (IS_TTY) {
5885
- process.stdout.write("\n");
5886
- }
5887
- console.log("");
5888
- if (!IS_TTY) return;
5889
- const entries = [...resultMap.values()];
5890
- entries.sort((a, b) => a.result.name.localeCompare(b.result.name));
5891
- const total = entries.length;
5892
- console.log("\u8282\u70B9\u6700\u7EC8\u72B6\u6001:");
5893
- for (let i = 0; i < entries.length; i++) {
5894
- const { result, round } = entries[i];
5895
- const prefix = `[${i + 1}/${total}]`;
5896
- if (result.delay !== null) {
5897
- const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
5898
- const retryNote = round > 1 ? colors.gray(` (\u7B2C${round}\u8F6E\u901A\u8FC7)`) : "";
5899
- console.log(`${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}${retryNote}`);
5900
- } else {
5901
- console.log(`${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
6004
+ current: currentDisplay,
6005
+ latest: latestVersion,
6006
+ needsUpdate,
6007
+ assets: latest.assets,
6008
+ release: latest
6009
+ };
6010
+ }
6011
+ function findBinaryInDir(dir, maxDepth = 4) {
6012
+ if (maxDepth <= 0) return null;
6013
+ const files = fs7.readdirSync(dir);
6014
+ for (const f of files) {
6015
+ const fullPath = path6.join(dir, f);
6016
+ const stat = fs7.statSync(fullPath);
6017
+ if (stat.isDirectory()) {
6018
+ const found = findBinaryInDir(fullPath, maxDepth - 1);
6019
+ if (found) return found;
6020
+ continue;
6021
+ }
6022
+ if (f === "mihomo") return fullPath;
6023
+ if (f.includes("mihomo") && !f.endsWith(".gz")) return fullPath;
6024
+ }
6025
+ return null;
6026
+ }
6027
+ async function downloadKernel(progressCallback, mirror, releaseInfo) {
6028
+ ensureDirs();
6029
+ const latest = releaseInfo || await getLatestRelease(GITHUB_REPO, mirror);
6030
+ const arch = getArch();
6031
+ const platform = process.platform;
6032
+ const asset = findMatchingAsset(latest.assets, platform, arch);
6033
+ if (!asset) {
6034
+ const available = latest.assets.map((a) => a.name).join(", ");
6035
+ let hint = "";
6036
+ if (available) hint = `
6037
+ \u53EF\u7528\u7248\u672C: ${available}`;
6038
+ throw new Error(`\u672A\u627E\u5230\u5339\u914D\u7684\u5185\u6838\u6587\u4EF6
6039
+ \u5E73\u53F0: ${platform}, \u67B6\u6784: ${arch}${hint}`);
6040
+ }
6041
+ const downloadUrl = withMirror(asset.browser_download_url, mirror);
6042
+ const tempPath = path6.join(DIRS.kernel, path6.basename(asset.name));
6043
+ const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
6044
+ if (mirror && progressCallback) {
6045
+ progressCallback("\u63D0\u793A: \u7ECF\u7B2C\u4E09\u65B9\u955C\u50CF\u4E2D\u8F6C\u4E0B\u8F7D\uFF0C\u65E0\u6CD5\u9A8C\u8BC1\u6765\u6E90\u5B8C\u6574\u6027\uFF0C\u5EFA\u8BAE\u76F4\u8FDE\u6216\u81EA\u884C\u6821\u9A8C\u4EA7\u7269");
6046
+ }
6047
+ if (progressCallback) {
6048
+ progressCallback(`\u4E0B\u8F7D\u5185\u6838: ${asset.name} (${sizeMB} MB)`);
6049
+ }
6050
+ const curlResult = spawnSync5(
6051
+ "curl",
6052
+ ["-L", "--progress-bar", "--connect-timeout", "30", "--max-time", String(Math.floor(KERNEL_DOWNLOAD_TIMEOUT / 1e3)), "-o", tempPath, downloadUrl],
6053
+ { stdio: "inherit" }
6054
+ );
6055
+ if (curlResult.error) {
6056
+ if (curlResult.error.code === "ENOENT") {
6057
+ throw new Error("\u672A\u627E\u5230 curl \u547D\u4EE4\uFF0C\u8BF7\u5148\u5B89\u88C5 curl \u540E\u91CD\u8BD5");
6058
+ }
6059
+ throw new Error(`\u4E0B\u8F7D\u5931\u8D25: ${curlResult.error.message}`);
6060
+ }
6061
+ if (curlResult.status !== 0) {
6062
+ try {
6063
+ fs7.unlinkSync(tempPath);
6064
+ } catch {
6065
+ }
6066
+ throw new Error(`\u4E0B\u8F7D\u5931\u8D25 (curl \u9000\u51FA\u7801 ${curlResult.status})`);
6067
+ }
6068
+ if (!fs7.existsSync(tempPath)) {
6069
+ throw new Error("\u4E0B\u8F7D\u5931\u8D25: \u6587\u4EF6\u672A\u751F\u6210");
6070
+ }
6071
+ if (progressCallback) {
6072
+ progressCallback("\u89E3\u538B\u5185\u6838...");
6073
+ }
6074
+ const extractPath = DIRS.kernel;
6075
+ let extractedBinary = null;
6076
+ try {
6077
+ if (tempPath.endsWith(".tar.gz") || tempPath.endsWith(".tgz")) {
6078
+ const listResult = spawnSync5("tar", ["-tzf", tempPath], { encoding: "utf8", timeout: 6e4 });
6079
+ if (listResult.error) throw listResult.error;
6080
+ if (listResult.status !== 0) throw new Error(`tar \u5217\u8868\u9000\u51FA\u7801 ${listResult.status}`);
6081
+ const entries = (listResult.stdout || "").split("\n").filter(Boolean);
6082
+ for (const entry of entries) {
6083
+ if (entry.startsWith("/") || entry.split("/").includes("..")) {
6084
+ throw new Error(`\u5F52\u6863\u542B\u975E\u6CD5\u8DEF\u5F84\u6761\u76EE: ${entry}`);
5902
6085
  }
5903
6086
  }
5904
- console.log("");
6087
+ const tarResult = spawnSync5("tar", ["-xzf", tempPath, "-C", extractPath], { stdio: ["ignore", "ignore", "inherit"], timeout: 6e4 });
6088
+ if (tarResult.error) throw tarResult.error;
6089
+ if (tarResult.status !== 0) throw new Error(`tar \u9000\u51FA\u7801 ${tarResult.status}`);
6090
+ } else if (tempPath.endsWith(".gz")) {
6091
+ const baseName = path6.basename(tempPath, ".gz");
6092
+ const outputPath = path6.join(extractPath, baseName);
6093
+ const gzipResult = spawnSync5("gzip", ["-dc", tempPath], { maxBuffer: 256 * 1024 * 1024, timeout: 6e4 });
6094
+ if (gzipResult.error) throw gzipResult.error;
6095
+ if (gzipResult.status !== 0) throw new Error(`gzip \u9000\u51FA\u7801 ${gzipResult.status}`);
6096
+ fs7.writeFileSync(outputPath, gzipResult.stdout, { mode: 493 });
6097
+ extractedBinary = outputPath;
5905
6098
  }
5906
- };
5907
- }
5908
- function formatCleanSummary(result) {
5909
- const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
5910
- if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
5911
- if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
5912
- return parts.join(", ");
5913
- }
5914
- function formatTestSummary(summary) {
5915
- return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
5916
- }
5917
-
5918
- // src/commands/status.ts
5919
- function printStatus() {
5920
- const status = getStatus();
5921
- const state = getRunningState();
5922
- const info = getConfigInfo();
5923
- const overwriteEnabled = isOverwriteEnabled();
5924
- const overwriteFiles = listOverwriteFile().files;
5925
- const activeSub = getActiveSubscription();
5926
- const { running, pid, daemon: daemonManaged } = state;
5927
- console.log("");
5928
- let modeLabel = "";
5929
- if (info) {
5930
- modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
5931
- }
5932
- const statusText = running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
5933
- console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}${modeLabel}`);
5934
- console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
5935
- if (pid) {
5936
- console.log(`${colors.gray("PID: ")}${pid}`);
5937
- if (!daemonManaged && status.processInfo) {
5938
- console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
6099
+ } catch (e) {
6100
+ try {
6101
+ fs7.unlinkSync(tempPath);
6102
+ } catch {
5939
6103
  }
6104
+ throw new Error(`\u89E3\u538B\u5931\u8D25: ${e.message}`);
5940
6105
  }
5941
- if (info) {
5942
- if (info.tun) {
5943
- const extra = info.mixedPort ? `\uFF0C\u53E6\u76D1\u542C ${info.mixedPort}` : "";
5944
- console.log(`${colors.gray("\u7AEF\u53E3: ")}TUN \u63A5\u7BA1${extra}`);
5945
- } else if (info.mixedPort) {
5946
- console.log(`${colors.gray("\u7AEF\u53E3: ")}${info.mixedPort}`);
5947
- } else {
5948
- const ports = [];
5949
- if (info.httpPort) ports.push(`HTTP:${info.httpPort}`);
5950
- if (info.socksPort) ports.push(`SOCKS:${info.socksPort}`);
5951
- console.log(`${colors.gray("\u7AEF\u53E3: ")}${ports.length > 0 ? ports.join(", ") : "\u672A\u77E5"}`);
6106
+ const foundBinary = extractedBinary || findBinaryInDir(extractPath);
6107
+ if (!foundBinary) {
6108
+ try {
6109
+ fs7.unlinkSync(tempPath);
6110
+ } catch {
5952
6111
  }
6112
+ throw new Error("\u89E3\u538B\u540E\u672A\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6");
5953
6113
  }
5954
- if (activeSub) {
5955
- let subLine = `${colors.gray("\u8BA2\u9605: ")}${activeSub.name}`;
5956
- if (info) {
5957
- subLine += ` (${formatProxySummary(info)})`;
6114
+ const targetPath = PATHS.mihomoBinary;
6115
+ if (foundBinary !== targetPath) {
6116
+ if (fs7.existsSync(targetPath)) {
6117
+ fs7.chmodSync(targetPath, 493);
6118
+ try {
6119
+ fs7.unlinkSync(targetPath);
6120
+ } catch {
6121
+ }
5958
6122
  }
5959
- console.log(subLine);
5960
- } else {
5961
- console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
6123
+ fs7.renameSync(foundBinary, targetPath);
5962
6124
  }
5963
- if (overwriteEnabled && overwriteFiles.length > 0) {
5964
- const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "").replace(/\.ya?ml$/, "") || "\u4E3B\u6587\u4EF6").join(", ");
5965
- console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (${names})`);
5966
- } else if (overwriteEnabled) {
5967
- console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (\u65E0\u6587\u4EF6)`);
5968
- } else {
5969
- console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
6125
+ fs7.chmodSync(targetPath, 493);
6126
+ if (progressCallback) {
6127
+ progressCallback("\u6821\u9A8C\u5185\u6838...");
5970
6128
  }
5971
- if (isDaemonEnabled()) {
5972
- console.log(`${colors.gray("\u4FDD\u6D3B: ")}${colors.green("\u5DF2\u542F\u7528")} ${colors.gray("(\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F)")}`);
6129
+ const check = spawnSync5(targetPath, ["-v"], { encoding: "utf8", timeout: 5e3 });
6130
+ const checkOutput = `${check.stdout || ""}${check.stderr || ""}`.trim();
6131
+ if (check.error || check.status !== 0 || !/v?\d+\.\d+\.\d+/.test(checkOutput)) {
6132
+ try {
6133
+ fs7.unlinkSync(targetPath);
6134
+ } catch {
6135
+ }
6136
+ try {
6137
+ fs7.unlinkSync(tempPath);
6138
+ } catch {
6139
+ }
6140
+ throw new Error(`\u5185\u6838\u81EA\u68C0\u5931\u8D25\uFF08\u53EF\u80FD\u4E0B\u8F7D\u635F\u574F\u6216\u67B6\u6784\u4E0D\u5339\u914D\uFF09\uFF0C\u5DF2\u5220\u9664
6141
+ \u9000\u51FA\u7801: ${check.status}
6142
+ \u8F93\u51FA: ${checkOutput || "(\u7A7A)"}`);
5973
6143
  }
5974
- console.log("");
5975
- }
5976
-
5977
- // src/commands/stop.ts
5978
- function handleStopResult(result) {
5979
- if (result.remaining && result.remaining.length > 0) {
5980
- console.error(`${colors.red("\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62:")} ${result.remaining.join(", ")}`);
5981
- console.error("\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo");
5982
- process.exit(1);
6144
+ try {
6145
+ fs7.unlinkSync(tempPath);
6146
+ } catch {
5983
6147
  }
6148
+ clearKernelVersionCache();
6149
+ return { version: latest.tag_name, path: targetPath };
5984
6150
  }
5985
- async function cmdStop() {
5986
- if (isDaemonEnabled()) {
5987
- console.log(colors.yellow("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF0C\u4EE3\u7406\u7531 launchd \u6258\u7BA1"));
5988
- console.log("\u76F4\u63A5\u505C\u6B62\u4F1A\u88AB\u81EA\u52A8\u91CD\u65B0\u62C9\u8D77\uFF0C\u8BF7\u7528: mihomo daemon off");
5989
- return;
6151
+
6152
+ // src/commands/kernel.ts
6153
+ async function cmdKernel(args) {
6154
+ const mirrorInfo = parseMirrorArg(args);
6155
+ const effectiveMirror = mirrorInfo.mirror;
6156
+ if (effectiveMirror) {
6157
+ const mirrorDesc = mirrorInfo.type === "all" ? " (API\u548C\u4E0B\u8F7D\u5747\u4F7F\u7528\u955C\u50CF)" : " (\u4E0B\u8F7D\u65F6\u4F7F\u7528\u955C\u50CF)";
6158
+ console.log(`\u955C\u50CF: ${effectiveMirror}${mirrorDesc}`);
6159
+ console.log("");
5990
6160
  }
5991
- const pids = getMihomoPids();
5992
- if (pids.length === 0) {
5993
- console.log(colors.yellow("\u4E0D\u5728\u8FD0\u884C"));
5994
- return;
6161
+ console.log("\u68C0\u67E5\u5185\u6838\u66F4\u65B0...");
6162
+ try {
6163
+ const apiMirror = mirrorInfo.type === "all" ? effectiveMirror : null;
6164
+ const info = await checkUpdate(apiMirror);
6165
+ console.log(`\u5F53\u524D: ${info.current}`);
6166
+ console.log(`\u6700\u65B0: ${info.latest}`);
6167
+ if (!info.needsUpdate) {
6168
+ console.log("\u5DF2\u662F\u6700\u65B0\u7248\u672C");
6169
+ } else {
6170
+ console.log("\n\u6B63\u5728\u4E0B\u8F7D...");
6171
+ const result = await downloadKernel((msg) => console.log(msg), mirrorInfo.mirror, info.release);
6172
+ console.log(`
6173
+ \u5DF2\u66F4\u65B0\u5230 ${result.version}`);
6174
+ }
6175
+ } catch (e) {
6176
+ if (e instanceof CliError) throw e;
6177
+ const err = e;
6178
+ const hint = [];
6179
+ if (err.response?.data?.message) {
6180
+ hint.push(`\u539F\u56E0: ${err.response.data.message}`);
6181
+ }
6182
+ if (err.response?.data?.documentation_url) {
6183
+ hint.push(`\u6587\u6863: ${err.response.data.documentation_url}`);
6184
+ }
6185
+ if (!effectiveMirror) {
6186
+ hint.push(
6187
+ "",
6188
+ "\u63D0\u793A: \u76F4\u8FDE\u5931\u8D25\u6216\u4E0B\u8F7D\u8FC7\u6162\u65F6\u53EF\u4F7F\u7528\u955C\u50CF:",
6189
+ " mihomo kernel --mirror [\u955C\u50CF] # \u4E0B\u8F7D\u8D70\u955C\u50CF\uFF08\u9ED8\u8BA4 v6.gh-proxy.org\uFF09",
6190
+ " mihomo kernel --mirror-all [\u955C\u50CF] # API \u548C\u4E0B\u8F7D\u90FD\u8D70\u955C\u50CF",
6191
+ ` \u53EF\u7528\u955C\u50CF: ${AVAILABLE_MIRRORS.join(", ")}`
6192
+ );
6193
+ }
6194
+ throw new CliError(err.message, { label: "\u66F4\u65B0\u5931\u8D25", hint });
5995
6195
  }
5996
- console.log(`\u505C\u6B62 ${pids.length} \u4E2A\u8FDB\u7A0B...`);
5997
- handleStopResult(stop());
5998
- console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
5999
6196
  }
6000
6197
 
6001
- // src/commands/start.ts
6002
- async function cmdStart(args) {
6003
- if (!hasKernel()) {
6004
- console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
6005
- process.exit(1);
6006
- }
6007
- const targetMode = args[1] === "tun" ? "tun" : "mixed";
6008
- const daemonEnabled = isDaemonEnabled();
6009
- if (targetMode === "tun" && daemonEnabled) {
6010
- 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`);
6011
- console.error("\u8BF7\u5148\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off");
6012
- process.exit(1);
6013
- }
6014
- const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
6015
- const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
6016
- const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
6017
- const skipUpdate = hasFlag(args, "-s", "--no-update");
6018
- const skipClean = hasFlag(args, "--no-clean");
6019
- const updateTimeout = parseIntArg(args, "-u", "--update-timeout", DEFAULT_AUTO_UPDATE_TIMEOUT);
6020
- const sub = getActiveSubscription();
6021
- if (!sub) {
6022
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
6023
- process.exit(1);
6024
- }
6025
- if (!skipUpdate) {
6026
- await autoUpdateStaleSubscription({ timeout: updateTimeout });
6198
+ // src/commands/log.ts
6199
+ function cmdLog(args) {
6200
+ const logPath = getLogPath();
6201
+ if (hasFlag(args, "-o", "--open")) {
6202
+ openLogFile(logPath);
6203
+ return;
6027
6204
  }
6028
- if (!daemonEnabled) {
6029
- if (hasRootResidue()) {
6030
- console.error(`${colors.red("\u9519\u8BEF:")} \u5B58\u5728\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559\u8FDB\u7A0B/\u6587\u4EF6`);
6031
- console.error(`\u8BF7\u5148\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo && sudo rm -f ${PATHS.pidFile}`);
6032
- console.error("\u6216\u5207\u6362\u5230 TUN \u6A21\u5F0F\u542F\u52A8\uFF08\u81EA\u52A8\u6E05\u7406\uFF09: mihomo start tun");
6033
- process.exit(1);
6205
+ viewLogWithTail(logPath, { follow: true, lines: 50 });
6206
+ }
6207
+ function cmdLogs(args) {
6208
+ const targetName = getNonFlagArg(args, 1);
6209
+ const lines = parseIntArg(args, "-n", "--lines", 100);
6210
+ const openInViewer = hasFlag(args, "-o", "--open");
6211
+ if (targetName) {
6212
+ let logPath;
6213
+ if (targetName === "current" || targetName === "0") {
6214
+ logPath = getLogPath();
6215
+ } else {
6216
+ const parsedIdx = parseInt(targetName, 10);
6217
+ if (!Number.isNaN(parsedIdx) && parsedIdx > 0 && String(parsedIdx) === targetName) {
6218
+ const archiveLogs = listLogs();
6219
+ const archive = archiveLogs.archives[parsedIdx - 1];
6220
+ if (!archive) {
6221
+ throw new CliError(`\u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`, { hint: '\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868' });
6222
+ }
6223
+ logPath = archive.path;
6224
+ } else {
6225
+ logPath = getLogPathByName(targetName);
6226
+ }
6034
6227
  }
6035
- const status = getStatus();
6036
- const hasProcess = status.running || status.allProcesses.length > 0;
6037
- if (hasProcess) {
6038
- const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
6039
- console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
6228
+ if (!logPath) {
6229
+ throw new CliError(`\u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`, { hint: '\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868' });
6040
6230
  }
6041
- handleStopResult(stop());
6042
- if (hasProcess) {
6043
- console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
6044
- `);
6231
+ if (openInViewer) {
6232
+ openLogFile(logPath);
6233
+ return;
6045
6234
  }
6235
+ viewLogWithTail(logPath, { follow: false, lines });
6236
+ return;
6046
6237
  }
6047
- let configInfo;
6048
- try {
6049
- configInfo = prepareConfigForStart(targetMode, sub.name);
6050
- } catch (e) {
6051
- console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
6052
- process.exit(1);
6238
+ const logs = listLogs();
6239
+ const all = [];
6240
+ if (logs.current) all.push(logs.current);
6241
+ all.push(...logs.archives);
6242
+ if (all.length === 0) {
6243
+ console.log("\u6682\u65E0\u65E5\u5FD7");
6244
+ return;
6053
6245
  }
6054
- const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
6055
- console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
6056
- try {
6057
- const pid = await launchOrRestart(targetMode);
6058
- const label = daemonEnabled ? "\u5DF2\u542F\u52A8 (\u4FDD\u6D3B)" : "\u5DF2\u542F\u52A8";
6059
- console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""}`);
6060
- } catch (e) {
6061
- const msg = e.message;
6062
- const lines = msg.split("\n");
6063
- console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${lines[0]}`);
6064
- if (lines.length > 1) {
6065
- for (const line of lines.slice(1)) console.error(line);
6246
+ console.log("");
6247
+ console.log("\u65E5\u5FD7\u5217\u8868:");
6248
+ console.log("");
6249
+ let archiveCounter = 0;
6250
+ for (const log of all) {
6251
+ let num;
6252
+ if (log.isCurrent) {
6253
+ num = " 0";
6254
+ } else {
6255
+ archiveCounter++;
6256
+ num = archiveCounter < 10 ? ` ${archiveCounter}` : `${archiveCounter}`;
6066
6257
  }
6067
- process.exit(1);
6068
- }
6069
- const cleanThreshold = isGithubUrl(sub.url) ? AUTO_CLEAN_THRESHOLD_GITHUB : AUTO_CLEAN_THRESHOLD;
6070
- if (!skipClean && configInfo.proxies > cleanThreshold) {
6071
- const cache = readSubscriptionCache();
6072
- const lastCleanAt = cache[sub.name]?.last_auto_clean_at;
6073
- const withinCooldown = !!lastCleanAt && Date.now() - new Date(lastCleanAt).getTime() < AUTO_CLEAN_COOLDOWN_HOURS * 60 * 60 * 1e3;
6074
- if (!withinCooldown) {
6075
- console.log("");
6076
- 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...`);
6077
- console.log("");
6078
- await sleep(1e3);
6079
- const progress = createProgressPrinter(rounds);
6080
- const cleanResult = await autoCleanSubscription(sub.name, {
6081
- timeout,
6082
- concurrency,
6083
- rounds,
6084
- onResult: progress.onResult,
6085
- onRetryRound: progress.onRetryRound
6086
- });
6087
- progress.finish();
6088
- console.log(formatTestSummary(cleanResult.summary));
6089
- if (cleanResult.skipped) {
6090
- 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"));
6091
- } else if (cleanResult.removedProxies > 0) {
6092
- console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
6093
- console.log("");
6094
- console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
6095
- if (!daemonEnabled) handleStopResult(stop());
6096
- try {
6097
- configInfo = prepareConfigForStart(targetMode, sub.name);
6098
- const pid = await launchOrRestart(targetMode);
6099
- console.log(`${colors.green("\u5DF2\u91CD\u542F")}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
6100
- } catch (e) {
6101
- console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
6102
- process.exit(1);
6103
- }
6104
- }
6105
- saveSubscriptionCache(sub.name, { last_auto_clean_at: (/* @__PURE__ */ new Date()).toISOString() });
6258
+ const time = formatDate(log.mtime);
6259
+ const size = formatBytes(log.size);
6260
+ const name = log.isCurrent ? "mihomo.log (\u5F53\u524D\u8FD0\u884C\u4E2D)" : log.name;
6261
+ console.log(` ${num}. ${name}`);
6262
+ console.log(` \u65F6\u95F4: ${time} \u5927\u5C0F: ${size}`);
6263
+ if (!log.isCurrent) {
6264
+ console.log(` \u67E5\u770B: mihomo logs ${archiveCounter} \u6216 mihomo logs ${archiveCounter} -o`);
6106
6265
  }
6266
+ console.log("");
6107
6267
  }
6108
- printStatus();
6268
+ console.log("\u7528\u6CD5:");
6269
+ console.log(" mihomo logs 0 # \u67E5\u770B\u5F53\u524D\u65E5\u5FD7 (\u6700\u540E 100 \u884C)");
6270
+ console.log(" mihomo logs 1 # \u67E5\u770B\u7B2C 1 \u4E2A\u5F52\u6863\u65E5\u5FD7\uFF08\u6700\u65B0\uFF09");
6271
+ console.log(" mihomo logs 1 -n 200 # \u67E5\u770B 200 \u884C");
6272
+ console.log(" mihomo logs 1 -o # \u7528\u7CFB\u7EDF\u9ED8\u8BA4\u7A0B\u5E8F\u6253\u5F00");
6273
+ console.log("");
6109
6274
  }
6110
6275
 
6111
6276
  // src/commands/overwrite.ts
6277
+ import path7 from "path";
6112
6278
  function printOverwriteList() {
6113
6279
  const info = listOverwriteFile();
6114
6280
  const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
@@ -6140,48 +6306,30 @@ function printOverwriteList() {
6140
6306
  console.log("\u7981\u7528\u8986\u5199: mihomo ow off");
6141
6307
  console.log("");
6142
6308
  }
6143
- async function cmdOverwrite(args) {
6144
- const action = args?.[1];
6145
- const currentMode = getRuntimeMode();
6146
- const restartNeeded = isRestartNeededOnChange();
6147
- if (action === "on" || action === "enable") {
6148
- if (isOverwriteEnabled()) {
6149
- console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
6150
- console.log("");
6151
- printOverwriteList();
6152
- return;
6153
- }
6154
- setOverwriteEnabled(true);
6155
- console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
6156
- if (restartNeeded) {
6157
- console.log("");
6158
- await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
6159
- return;
6160
- }
6309
+ async function setOverwrite(enabled, args) {
6310
+ if (isOverwriteEnabled() === enabled) {
6311
+ console.log(`\u8986\u5199\u914D\u7F6E\u5DF2\u662F${enabled ? "\u542F\u7528" : "\u7981\u7528"}\u72B6\u6001`);
6161
6312
  console.log("");
6162
6313
  printOverwriteList();
6163
6314
  return;
6164
6315
  }
6165
- if (action === "off" || action === "disable") {
6166
- if (!isOverwriteEnabled()) {
6167
- console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u7981\u7528\u72B6\u6001");
6316
+ setOverwriteEnabled(enabled);
6317
+ console.log(`\u5DF2${enabled ? "\u542F\u7528" : "\u7981\u7528"}\u8986\u5199\u914D\u7F6E`);
6318
+ if (await restartToApply(args)) return;
6319
+ console.log("");
6320
+ printOverwriteList();
6321
+ }
6322
+ var SUBCOMMANDS3 = [
6323
+ { name: "on", aliases: ["enable"], handler: (args) => setOverwrite(true, args) },
6324
+ { name: "off", aliases: ["disable"], handler: (args) => setOverwrite(false, args) }
6325
+ ];
6326
+ async function cmdOverwrite(args) {
6327
+ await dispatchSubcommand(args, SUBCOMMANDS3, {
6328
+ fallback: () => {
6168
6329
  console.log("");
6169
6330
  printOverwriteList();
6170
- return;
6171
6331
  }
6172
- setOverwriteEnabled(false);
6173
- console.log("\u5DF2\u7981\u7528\u8986\u5199\u914D\u7F6E");
6174
- if (restartNeeded) {
6175
- console.log("");
6176
- await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
6177
- return;
6178
- }
6179
- console.log("");
6180
- printOverwriteList();
6181
- return;
6182
- }
6183
- console.log("");
6184
- printOverwriteList();
6332
+ });
6185
6333
  }
6186
6334
 
6187
6335
  // src/commands/reset.ts
@@ -6291,10 +6439,7 @@ async function cmdReset(args) {
6291
6439
  const KNOWN_FLAGS = /* @__PURE__ */ new Set(["--full", "--yes", "-y"]);
6292
6440
  const unknownFlags = flags.filter((f) => !KNOWN_FLAGS.has(f));
6293
6441
  if (unknownFlags.length > 0) {
6294
- console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u9009\u9879: ${unknownFlags.join(", ")}`);
6295
- console.log("");
6296
- console.log("\u53EF\u7528\u9009\u9879: --full\uFF08\u5220\u5168\u90E8\uFF09, -y/--yes\uFF08\u8DF3\u8FC7\u786E\u8BA4\uFF09");
6297
- process.exit(1);
6442
+ throw new CliError(`\u672A\u77E5\u7684\u9009\u9879: ${unknownFlags.join(", ")}`, { hint: ["", "\u53EF\u7528\u9009\u9879: --full\uFF08\u5220\u5168\u90E8\uFF09, -y/--yes\uFF08\u8DF3\u8FC7\u786E\u8BA4\uFF09"] });
6298
6443
  }
6299
6444
  const fullReset = flags.includes("--full");
6300
6445
  const skipConfirm = flags.includes("--yes") || flags.includes("-y");
@@ -6304,16 +6449,18 @@ async function cmdReset(args) {
6304
6449
  } else if (names.length > 0) {
6305
6450
  const { matched, unmatched } = resolveResetTargets(names);
6306
6451
  if (unmatched.length > 0) {
6307
- console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u91CD\u7F6E\u76EE\u6807: ${unmatched.join(", ")}`);
6308
- console.log("");
6309
- console.log(`\u53EF\u7528\u76EE\u6807: ${RESET_TARGETS.map((t) => t.aliases[0]).join(", ")}`);
6310
- console.log("");
6311
- console.log("\u793A\u4F8B:");
6312
- console.log(" mihomo reset sub log # \u5220\u9664\u8BA2\u9605\u548C\u65E5\u5FD7");
6313
- console.log(" mihomo reset kernel # \u53EA\u5220\u5185\u6838");
6314
- console.log(" mihomo reset --full # \u5220\u9664\u5168\u90E8");
6315
- console.log(" mihomo reset # \u5220\u9664\u5168\u90E8\uFF08\u4FDD\u7559\u8BBE\u7F6E\u3001\u5185\u6838\u3001\u8986\u5199\uFF09");
6316
- process.exit(1);
6452
+ throw new CliError(`\u672A\u77E5\u7684\u91CD\u7F6E\u76EE\u6807: ${unmatched.join(", ")}`, {
6453
+ hint: [
6454
+ "",
6455
+ `\u53EF\u7528\u76EE\u6807: ${RESET_TARGETS.map((t) => t.aliases[0]).join(", ")}`,
6456
+ "",
6457
+ "\u793A\u4F8B:",
6458
+ " mihomo reset sub log # \u5220\u9664\u8BA2\u9605\u548C\u65E5\u5FD7",
6459
+ " mihomo reset kernel # \u53EA\u5220\u5185\u6838",
6460
+ " mihomo reset --full # \u5220\u9664\u5168\u90E8",
6461
+ " mihomo reset # \u5220\u9664\u5168\u90E8\uFF08\u4FDD\u7559\u8BBE\u7F6E\u3001\u5185\u6838\u3001\u8986\u5199\uFF09"
6462
+ ]
6463
+ });
6317
6464
  }
6318
6465
  targets = matched;
6319
6466
  } else {
@@ -6484,7 +6631,7 @@ function stopTestInstance() {
6484
6631
  } catch {
6485
6632
  return;
6486
6633
  }
6487
- if (pid > 0 && isProcessRunning(pid)) {
6634
+ if (pid > 0 && isProcessRunning(pid) && isProcessCommandMatching(pid, TEST_PATHS.configFile)) {
6488
6635
  process.kill(pid, "SIGKILL");
6489
6636
  for (let i = 0; i < 20; i++) {
6490
6637
  if (!isProcessRunning(pid)) break;
@@ -6522,21 +6669,18 @@ function githubRepoUrl(rawUrl) {
6522
6669
  function resolveTestTarget(args) {
6523
6670
  const subs = getSubscriptions();
6524
6671
  if (subs.length === 0) {
6525
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
6526
- process.exit(1);
6672
+ throw new CliError("\u6CA1\u6709\u8BA2\u9605");
6527
6673
  }
6528
6674
  const nameArg = getNonFlagArg(args, 2);
6529
6675
  const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
6530
6676
  const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
6531
6677
  let target;
6532
6678
  if (nameArg) {
6533
- const matches = findSubscriptionFuzzy(subs, nameArg);
6534
- target = pickSingleSubscription(matches, nameArg);
6679
+ target = resolveSubscription(subs, nameArg);
6535
6680
  } else {
6536
6681
  const activeSub = getActiveSubscription();
6537
6682
  if (!activeSub) {
6538
- console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
6539
- process.exit(1);
6683
+ throw new CliError("\u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
6540
6684
  }
6541
6685
  target = activeSub;
6542
6686
  }
@@ -6597,274 +6741,231 @@ function printSubscriptionList() {
6597
6741
  console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
6598
6742
  console.log("");
6599
6743
  }
6600
- async function cmdSubscription(args) {
6601
- const action = args[1];
6602
- if (!action || action === "list") {
6603
- printSubscriptionList();
6604
- return;
6744
+ async function subAdd(args) {
6745
+ const url = args[2]?.trim();
6746
+ const name = args[3] || "default";
6747
+ if (!url) {
6748
+ throw new CliError("\u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
6605
6749
  }
6606
- if (action === "add") {
6607
- const url = args[2]?.trim();
6608
- const name = args[3] || "default";
6609
- if (!url) {
6610
- console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
6611
- process.exit(1);
6612
- }
6613
- if (isMultiUrl(url)) {
6614
- const urls = splitUrls(url);
6615
- if (urls.length === 0) {
6616
- console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
6617
- process.exit(1);
6618
- }
6619
- for (const u of urls) {
6620
- if (!isValidHttpUrl(u)) {
6621
- console.error(`\u9519\u8BEF: \u65E0\u6548\u7684 URL: ${u}`);
6622
- process.exit(1);
6623
- }
6624
- }
6625
- const normalizedUrl = urls.join(",");
6626
- console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
6627
- try {
6628
- addSubscription(normalizedUrl, name);
6629
- setDefaultSubscription(name);
6630
- const info = await downloadMergedSubscription(urls, name);
6631
- console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
6632
- } catch (e) {
6633
- removeSubscription(name);
6634
- console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
6635
- process.exit(1);
6636
- }
6637
- } else {
6638
- if (!isValidHttpUrl(url)) {
6639
- console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL\uFF08\u9700\u4EE5 http:// \u6216 https:// \u5F00\u5934\uFF09");
6640
- process.exit(1);
6641
- }
6642
- console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
6643
- try {
6644
- addSubscription(url, name);
6645
- setDefaultSubscription(name);
6646
- const info = await downloadSubscription(url, name);
6647
- const repoUrl = githubRepoUrl(url);
6648
- if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
6649
- console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
6650
- } catch (e) {
6651
- removeSubscription(name);
6652
- console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
6653
- process.exit(1);
6654
- }
6750
+ if (isMultiUrl(url)) {
6751
+ const urls = splitUrls(url);
6752
+ if (urls.length === 0) {
6753
+ throw new CliError("\u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
6655
6754
  }
6656
- console.log("");
6657
- printSubscriptionList();
6658
- return;
6659
- }
6660
- if (action === "update") {
6661
- const name = args[2];
6662
- const subs = getSubscriptions();
6663
- if (subs.length === 0) {
6664
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
6665
- process.exit(1);
6666
- }
6667
- if (!name) {
6668
- console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
6669
- const results = await Promise.all(subs.map((sub) => tryUpdateOne(sub)));
6670
- let ok = 0;
6671
- for (const r of results) {
6672
- if (r.success) ok++;
6673
- printUpdateResult(r);
6755
+ for (const u of urls) {
6756
+ if (!isValidHttpUrl(u)) {
6757
+ throw new CliError(`\u65E0\u6548\u7684 URL: ${u}`);
6674
6758
  }
6675
- if (ok === 0) process.exit(1);
6676
- console.log("");
6677
- printRestartHintIfRunning();
6678
- printSubscriptionList();
6679
- return;
6680
6759
  }
6681
- const matches = findSubscriptionFuzzy(subs, name);
6682
- const target = pickSingleSubscription(matches, name);
6683
- console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
6684
- const result = await tryUpdateOne(target);
6685
- if (!result.success) {
6686
- console.error(`\u66F4\u65B0\u5931\u8D25: ${(result.error || "").split("\n")[0]}`);
6687
- process.exit(1);
6760
+ const normalizedUrl = urls.join(",");
6761
+ console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
6762
+ try {
6763
+ addSubscription(normalizedUrl, name);
6764
+ setDefaultSubscription(name);
6765
+ const info = await downloadMergedSubscription(urls, name);
6766
+ console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
6767
+ } catch (e) {
6768
+ removeSubscription(name);
6769
+ throw new CliError(e.message, { label: "\u6DFB\u52A0\u5931\u8D25" });
6688
6770
  }
6689
- console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
6690
- console.log("");
6691
- printRestartHintIfRunning();
6692
- printSubscriptionList();
6693
- return;
6694
- }
6695
- if (action === "use") {
6696
- const name = args[2];
6697
- const subs = getSubscriptions();
6698
- if (!name) {
6699
- console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
6700
- if (subs.length > 0) {
6701
- console.log("\n\u53EF\u7528\u8BA2\u9605:");
6702
- for (const s of subs) console.log(` ${s.name}`);
6703
- }
6704
- process.exit(1);
6705
- }
6706
- const matches = findSubscriptionFuzzy(subs, name);
6707
- const target = pickSingleSubscription(matches, name);
6708
- const currentDefault = getActiveSubscription();
6709
- const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
6710
- if (isAlreadyDefault) {
6711
- console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
6712
- console.log("");
6713
- printSubscriptionList();
6714
- return;
6771
+ } else {
6772
+ if (!isValidHttpUrl(url)) {
6773
+ throw new CliError("\u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL\uFF08\u9700\u4EE5 http:// \u6216 https:// \u5F00\u5934\uFF09");
6715
6774
  }
6716
- const currentMode = getRuntimeMode();
6717
- const restartNeeded = isRestartNeededOnChange();
6718
- const success = setDefaultSubscription(target.name);
6719
- if (success) {
6720
- console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
6721
- } else {
6722
- console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
6723
- process.exit(1);
6775
+ console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
6776
+ try {
6777
+ addSubscription(url, name);
6778
+ setDefaultSubscription(name);
6779
+ const info = await downloadSubscription(url, name);
6780
+ const repoUrl = githubRepoUrl(url);
6781
+ if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
6782
+ console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
6783
+ } catch (e) {
6784
+ removeSubscription(name);
6785
+ throw new CliError(e.message, { label: "\u6DFB\u52A0\u5931\u8D25" });
6724
6786
  }
6725
- if (restartNeeded) {
6726
- console.log("");
6727
- await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
6728
- return;
6787
+ }
6788
+ console.log("");
6789
+ printSubscriptionList();
6790
+ }
6791
+ async function subUpdate(args) {
6792
+ const name = args[2];
6793
+ const subs = getSubscriptions();
6794
+ if (subs.length === 0) {
6795
+ throw new CliError("\u6CA1\u6709\u8BA2\u9605");
6796
+ }
6797
+ if (!name) {
6798
+ console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
6799
+ const results = await Promise.all(subs.map((sub) => tryUpdateOne(sub)));
6800
+ let ok = 0;
6801
+ for (const r of results) {
6802
+ if (r.success) ok++;
6803
+ printUpdateResult(r);
6729
6804
  }
6805
+ if (ok === 0) throw new CliError("\u5168\u90E8\u8BA2\u9605\u66F4\u65B0\u5931\u8D25");
6730
6806
  console.log("");
6807
+ printRestartHintIfRunning();
6731
6808
  printSubscriptionList();
6732
6809
  return;
6733
6810
  }
6734
- if (action === "web" || action === "open") {
6735
- const name = args[2];
6736
- const subs = getSubscriptionsWithCache();
6737
- if (subs.length === 0) {
6738
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
6739
- process.exit(1);
6740
- }
6741
- let target;
6742
- if (name) {
6743
- const matches = findSubscriptionFuzzy(subs, name);
6744
- target = pickSingleSubscription(matches, name);
6745
- } else {
6746
- target = getActiveSubscription() || subs[0];
6747
- }
6748
- const cached = subs.find((s) => s.name === target.name);
6749
- let webPageUrl = cached?.web_page_url;
6750
- if (!webPageUrl) {
6751
- console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u67E5\u8BE2\u8BA2\u9605...");
6752
- try {
6753
- const info = isMultiUrl(target.url) ? await downloadMergedSubscription(splitUrls(target.url), target.name, void 0, false) : await downloadSubscription(target.url, target.name, void 0, false);
6754
- if (info.webPageUrl) {
6755
- webPageUrl = info.webPageUrl;
6756
- } else {
6757
- console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
6758
- process.exit(1);
6759
- }
6760
- } catch (e) {
6761
- console.error(`\u67E5\u8BE2\u5931\u8D25: ${e.message}`);
6762
- process.exit(1);
6763
- }
6764
- }
6765
- console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
6766
- const opened = openUrl(webPageUrl);
6767
- if (!opened) {
6768
- console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
6769
- }
6770
- return;
6811
+ const target = resolveSubscription(subs, name);
6812
+ console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
6813
+ const result = await tryUpdateOne(target);
6814
+ if (!result.success) {
6815
+ throw new CliError((result.error || "").split("\n")[0], { label: "\u66F4\u65B0\u5931\u8D25" });
6771
6816
  }
6772
- if (action === "remove" || action === "rm" || action === "delete") {
6773
- const name = args[2];
6774
- const subs = getSubscriptions();
6775
- if (!name) {
6776
- console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0");
6777
- if (subs.length > 0) {
6778
- console.log("\n\u53EF\u7528\u8BA2\u9605:");
6779
- for (const s of subs) console.log(` ${s.name}`);
6780
- }
6781
- process.exit(1);
6782
- }
6783
- const matches = findSubscriptionFuzzy(subs, name);
6784
- const target = pickSingleSubscription(matches, name);
6785
- const switchedTo = removeSubscription(target.name);
6786
- console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
6787
- if (switchedTo) {
6788
- console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
6789
- }
6817
+ console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
6818
+ console.log("");
6819
+ printRestartHintIfRunning();
6820
+ printSubscriptionList();
6821
+ }
6822
+ async function subUse(args) {
6823
+ const name = args[2];
6824
+ const subs = getSubscriptions();
6825
+ if (!name) {
6826
+ throw new CliError("\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0", {
6827
+ hint: subs.length > 0 ? ["", "\u53EF\u7528\u8BA2\u9605:", ...subs.map((s) => ` ${s.name}`)] : void 0
6828
+ });
6829
+ }
6830
+ const target = resolveSubscription(subs, name);
6831
+ const currentDefault = getActiveSubscription();
6832
+ const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
6833
+ if (isAlreadyDefault) {
6834
+ console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
6790
6835
  console.log("");
6791
6836
  printSubscriptionList();
6792
6837
  return;
6793
6838
  }
6794
- if (action === "clean") {
6795
- const { target, timeout, concurrency } = resolveTestTarget(args);
6796
- const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
6797
- console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
6798
- console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
6799
- console.log("");
6800
- const progress = createProgressPrinter(rounds);
6801
- const result = await withTestInstance(target.name, async (apiBase) => {
6802
- return autoCleanSubscription(target.name, {
6803
- timeout,
6804
- concurrency,
6805
- rounds,
6806
- apiBase,
6807
- onResult: progress.onResult,
6808
- onRetryRound: progress.onRetryRound
6809
- });
6810
- });
6811
- progress.finish();
6812
- console.log(formatTestSummary(result.summary));
6813
- if (result.skipped) {
6814
- console.log("");
6815
- 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"));
6816
- } else if (result.removedProxies > 0) {
6817
- console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
6818
- if (getRunningState().running) {
6819
- console.log("");
6820
- console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
6839
+ const success = setDefaultSubscription(target.name);
6840
+ if (!success) {
6841
+ throw new CliError(`\u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
6842
+ }
6843
+ console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
6844
+ if (await restartToApply(args)) return;
6845
+ console.log("");
6846
+ printSubscriptionList();
6847
+ }
6848
+ async function subWeb(args) {
6849
+ const name = args[2];
6850
+ const subs = getSubscriptionsWithCache();
6851
+ if (subs.length === 0) {
6852
+ throw new CliError("\u6CA1\u6709\u8BA2\u9605");
6853
+ }
6854
+ let target;
6855
+ if (name) {
6856
+ target = resolveSubscription(subs, name);
6857
+ } else {
6858
+ target = getActiveSubscription() || subs[0];
6859
+ }
6860
+ const cached = subs.find((s) => s.name === target.name);
6861
+ let webPageUrl = cached?.web_page_url;
6862
+ if (!webPageUrl) {
6863
+ console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u67E5\u8BE2\u8BA2\u9605...");
6864
+ try {
6865
+ const info = isMultiUrl(target.url) ? await downloadMergedSubscription(splitUrls(target.url), target.name, void 0, false) : await downloadSubscription(target.url, target.name, void 0, false);
6866
+ if (!info.webPageUrl) {
6867
+ throw new CliError("\u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
6821
6868
  }
6869
+ webPageUrl = info.webPageUrl;
6870
+ } catch (e) {
6871
+ if (e instanceof CliError) throw e;
6872
+ throw new CliError(e.message, { label: "\u67E5\u8BE2\u5931\u8D25" });
6822
6873
  }
6823
- return;
6824
6874
  }
6825
- if (action === "test") {
6826
- const { target, timeout, concurrency } = resolveTestTarget(args);
6827
- console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
6828
- console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
6829
- console.log("");
6830
- const progress = createProgressPrinter();
6831
- const summary = await withTestInstance(target.name, async (apiBase) => {
6832
- return testSubscriptionProxies(target.name, {
6833
- timeout,
6834
- concurrency,
6835
- apiBase,
6836
- onResult: progress.onResult
6837
- });
6838
- });
6839
- progress.finish();
6840
- console.log(formatTestSummary(summary));
6841
- return;
6875
+ console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
6876
+ const opened = openUrl(webPageUrl);
6877
+ if (!opened) {
6878
+ console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
6842
6879
  }
6843
- console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
6844
- console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
6845
- process.exit(1);
6846
6880
  }
6847
-
6848
- // src/commands/test.ts
6849
- function requireRunning() {
6850
- const state = getRunningState();
6851
- if (!state.running) {
6852
- const hint = state.daemon ? "mihomo daemon on" : "mihomo start";
6853
- console.error(`\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (${hint})`);
6854
- process.exit(1);
6881
+ function subRemove(args) {
6882
+ const name = args[2];
6883
+ const subs = getSubscriptions();
6884
+ if (!name) {
6885
+ throw new CliError("\u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0", {
6886
+ hint: subs.length > 0 ? ["", "\u53EF\u7528\u8BA2\u9605:", ...subs.map((s) => ` ${s.name}`)] : void 0
6887
+ });
6855
6888
  }
6889
+ const target = resolveSubscription(subs, name);
6890
+ const switchedTo = removeSubscription(target.name);
6891
+ console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
6892
+ if (switchedTo) {
6893
+ console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
6894
+ }
6895
+ console.log("");
6896
+ printSubscriptionList();
6856
6897
  }
6857
- function requireActiveSub() {
6858
- const activeSub = getActiveSubscription();
6859
- if (!activeSub) {
6860
- console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
6861
- process.exit(1);
6898
+ async function subClean(args) {
6899
+ const { target, timeout, concurrency } = resolveTestTarget(args);
6900
+ const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
6901
+ console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
6902
+ console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
6903
+ console.log("");
6904
+ const progress = createProgressPrinter(rounds);
6905
+ const result = await withTestInstance(target.name, async (apiBase) => {
6906
+ return autoCleanSubscription(target.name, {
6907
+ timeout,
6908
+ concurrency,
6909
+ rounds,
6910
+ apiBase,
6911
+ onResult: progress.onResult,
6912
+ onRetryRound: progress.onRetryRound
6913
+ });
6914
+ });
6915
+ progress.finish();
6916
+ console.log(formatTestSummary(result.summary));
6917
+ if (result.skipped) {
6918
+ console.log("");
6919
+ console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
6920
+ } else if (result.removedProxies > 0) {
6921
+ console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
6922
+ if (getRunningState().running) {
6923
+ console.log("");
6924
+ console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
6925
+ }
6862
6926
  }
6863
- return activeSub;
6864
6927
  }
6928
+ async function subTest(args) {
6929
+ const { target, timeout, concurrency } = resolveTestTarget(args);
6930
+ console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
6931
+ console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
6932
+ console.log("");
6933
+ const progress = createProgressPrinter();
6934
+ const summary = await withTestInstance(target.name, async (apiBase) => {
6935
+ return testSubscriptionProxies(target.name, {
6936
+ timeout,
6937
+ concurrency,
6938
+ apiBase,
6939
+ onResult: progress.onResult
6940
+ });
6941
+ });
6942
+ progress.finish();
6943
+ console.log(formatTestSummary(summary));
6944
+ }
6945
+ var SUBCOMMANDS4 = [
6946
+ { name: "list", handler: printSubscriptionList },
6947
+ { name: "add", handler: subAdd },
6948
+ { name: "update", handler: subUpdate },
6949
+ { name: "use", handler: subUse },
6950
+ { name: "web", aliases: ["open"], handler: subWeb },
6951
+ { name: "remove", aliases: ["rm", "delete"], handler: subRemove },
6952
+ { name: "clean", handler: subClean },
6953
+ { name: "test", handler: subTest }
6954
+ ];
6955
+ async function cmdSubscription(args) {
6956
+ await dispatchSubcommand(args, SUBCOMMANDS4, {
6957
+ // 无子命令 → 列表;未知子命令 → 报错
6958
+ fallback: printSubscriptionList,
6959
+ onUnknown: () => {
6960
+ throw new CliError("\u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4", { hint: "\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]" });
6961
+ }
6962
+ });
6963
+ }
6964
+
6965
+ // src/commands/test.ts
6865
6966
  async function cmdTest(args) {
6866
6967
  requireRunning();
6867
- const activeSub = requireActiveSub();
6968
+ const activeSub = requireActiveSubscription("\u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
6868
6969
  const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
6869
6970
  const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
6870
6971
  console.log(`\u6D4B\u8BD5 "${activeSub.name}" \u8282\u70B9\u8FDE\u901A\u6027...`);
@@ -6881,7 +6982,7 @@ async function cmdTest(args) {
6881
6982
  }
6882
6983
  async function cmdClean(args) {
6883
6984
  requireRunning();
6884
- const activeSub = requireActiveSub();
6985
+ const activeSub = requireActiveSubscription("\u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605");
6885
6986
  const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
6886
6987
  const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
6887
6988
  const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
@@ -6912,9 +7013,7 @@ async function cmdClean(args) {
6912
7013
  try {
6913
7014
  if (!daemonManaged) {
6914
7015
  if (hasRootResidue()) {
6915
- console.error(`${colors.red("\u9519\u8BEF:")} \u4E3B\u5B9E\u4F8B\u4EE5 root \u8FD0\u884C\uFF08TUN\uFF09\uFF0C\u505C\u6B62\u5B83\u9700\u8981 sudo`);
6916
- console.error("\u8BF7\u6539\u7528 mihomo sub clean\uFF08\u9694\u79BB\u5B9E\u4F8B\u6D4B\u901F\uFF0C\u65E0\u9700\u505C\u6B62\u4E3B\u5B9E\u4F8B\uFF09");
6917
- process.exit(1);
7016
+ throw new CliError("\u4E3B\u5B9E\u4F8B\u4EE5 root \u8FD0\u884C\uFF08TUN\uFF09\uFF0C\u505C\u6B62\u5B83\u9700\u8981 sudo", { hint: "\u8BF7\u6539\u7528 mihomo sub clean\uFF08\u9694\u79BB\u5B9E\u4F8B\u6D4B\u901F\uFF0C\u65E0\u9700\u505C\u6B62\u4E3B\u5B9E\u4F8B\uFF09" });
6918
7017
  }
6919
7018
  handleStopResult(stop());
6920
7019
  }
@@ -6923,8 +7022,8 @@ async function cmdClean(args) {
6923
7022
  const label = daemonManaged ? "\u5DF2\u91CD\u542F (\u4FDD\u6D3B)" : "\u5DF2\u91CD\u542F";
6924
7023
  console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
6925
7024
  } catch (e) {
6926
- console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
6927
- process.exit(1);
7025
+ if (e instanceof CliError) throw e;
7026
+ throw new CliError(e.message.split("\n")[0], { label: "\u91CD\u542F\u5931\u8D25" });
6928
7027
  }
6929
7028
  }
6930
7029
  }
@@ -6933,9 +7032,7 @@ async function cmdClean(args) {
6933
7032
  function cmdUI(args) {
6934
7033
  const uiName = args[1] || "zash";
6935
7034
  if (!Object.hasOwn(UI_URLS, uiName)) {
6936
- console.error(`\u9519\u8BEF: \u672A\u77E5\u7684 UI "${uiName}"`);
6937
- console.error("\u53EF\u7528 UI: zash (\u9ED8\u8BA4), dash, yacd");
6938
- process.exit(1);
7035
+ throw new CliError(`\u672A\u77E5\u7684 UI "${uiName}"`, { hint: "\u53EF\u7528 UI: zash (\u9ED8\u8BA4), dash, yacd" });
6939
7036
  }
6940
7037
  const url = UI_URLS[uiName];
6941
7038
  console.log(`\u6253\u5F00 Web UI: ${uiName}`);
@@ -6959,23 +7056,18 @@ async function cmdUpdate() {
6959
7056
  console.log("");
6960
7057
  console.log("\u6B63\u5728\u66F4\u65B0 mihomo-cli...");
6961
7058
  console.log("");
6962
- await new Promise((resolve) => {
7059
+ await new Promise((resolve, reject) => {
6963
7060
  const npm = spawn3("npm", ["install", "-g", "mihomo-cli"], { stdio: "inherit" });
6964
7061
  npm.on("close", (code) => {
6965
7062
  if (code === 0) {
6966
7063
  resolve();
6967
7064
  } else {
6968
- console.error("\u66F4\u65B0\u5931\u8D25\u3002\u82E5\u4E3A\u6743\u9650\u95EE\u9898\uFF08EACCES\uFF09\uFF0C\u53EF\u5C1D\u8BD5: sudo npm install -g mihomo-cli");
6969
- process.exit(code || 1);
7065
+ reject(new CliError("\u66F4\u65B0\u5931\u8D25\u3002\u82E5\u4E3A\u6743\u9650\u95EE\u9898\uFF08EACCES\uFF09\uFF0C\u53EF\u5C1D\u8BD5: sudo npm install -g mihomo-cli", { exitCode: code || 1 }));
6970
7066
  }
6971
7067
  });
6972
7068
  npm.on("error", (e) => {
6973
- if (e.message.includes("EACCES") || e.message.includes("permission")) {
6974
- console.error("\u6743\u9650\u4E0D\u8DB3\uFF0C\u53EF\u5C1D\u8BD5: sudo npm install -g mihomo-cli");
6975
- } else {
6976
- console.error(`\u6267\u884C\u5931\u8D25: ${e.message}`);
6977
- }
6978
- process.exit(1);
7069
+ const perm = e.message.includes("EACCES") || e.message.includes("permission");
7070
+ reject(perm ? new CliError("\u6743\u9650\u4E0D\u8DB3\uFF0C\u53EF\u5C1D\u8BD5: sudo npm install -g mihomo-cli") : new CliError(`\u6267\u884C\u5931\u8D25: ${e.message}`));
6979
7071
  });
6980
7072
  });
6981
7073
  try {
@@ -7235,19 +7327,25 @@ async function main() {
7235
7327
  const token = args[0].toLowerCase();
7236
7328
  const command = findCommand(token);
7237
7329
  if (!command) {
7238
- console.error(`\u672A\u77E5\u547D\u4EE4: ${token}`);
7239
- console.error('\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9');
7240
- process.exit(1);
7330
+ throw new CliError(`\u672A\u77E5\u547D\u4EE4: ${token}`, { hint: '\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9' });
7241
7331
  }
7242
7332
  await command.handler(command.rewrite ? command.rewrite(args) : args);
7243
7333
  }
7244
7334
  main().catch((e) => {
7245
- console.error(`\u9519\u8BEF: ${e.message}`);
7335
+ if (e instanceof CliError) {
7336
+ console.error(`${colors.red(`${e.label}:`)} ${e.message}`);
7337
+ for (const line of e.hint) console.error(line);
7338
+ runCleanup();
7339
+ process.exit(e.exitCode);
7340
+ }
7341
+ const err = e;
7342
+ console.error(`${colors.red("\u9519\u8BEF:")} ${err.message}`);
7343
+ if (err.stack) console.error(err.stack.split("\n").slice(1).join("\n"));
7246
7344
  runCleanup();
7247
7345
  process.exit(1);
7248
7346
  });
7249
7347
  /*! Bundled license information:
7250
7348
 
7251
7349
  js-yaml/dist/js-yaml.mjs:
7252
- (*! js-yaml 5.2.1 https://github.com/nodeca/js-yaml @license MIT *)
7350
+ (*! js-yaml 5.3.0 https://github.com/nodeca/js-yaml @license MIT *)
7253
7351
  */