mihomo-cli 3.2.0 → 3.4.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 (4) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +51 -17
  3. package/dist/index.js +1522 -1092
  4. package/package.json +5 -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,9 +3022,26 @@ 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
- var AVAILABLE_MIRRORS = ["gh-proxy.org", "v6.gh-proxy.org", "hk.gh-proxy.org", "cdn.gh-proxy.org"];
3043
+ var AVAILABLE_MIRRORS = ["v6.gh-proxy.org", "gh-proxy.org", "hk.gh-proxy.org", "cdn.gh-proxy.org"];
3044
+ var DEFAULT_MIRROR = "https://v6.gh-proxy.org/";
2921
3045
  var UI_URLS = {
2922
3046
  zash: "https://board.zash.run.place",
2923
3047
  dash: "https://metacubex.github.io/metacubexd",
@@ -2974,6 +3098,7 @@ var DEFAULT_TEST_URL = "http://www.gstatic.com/generate_204";
2974
3098
  var DEFAULT_CLEAN_ROUNDS = 2;
2975
3099
  var AUTO_CLEAN_THRESHOLD = 100;
2976
3100
  var AUTO_CLEAN_THRESHOLD_GITHUB = 50;
3101
+ var AUTO_CLEAN_COOLDOWN_HOURS = 12;
2977
3102
 
2978
3103
  // src/overwrite.ts
2979
3104
  import fs3 from "fs";
@@ -3025,8 +3150,9 @@ function ensureDirs() {
3025
3150
  }
3026
3151
  }
3027
3152
  }
3153
+ var atomicWriteSeq = 0;
3028
3154
  function atomicWriteFileSync(filePath, content, options) {
3029
- const tmp = `${filePath}.${process.pid}.tmp`;
3155
+ const tmp = `${filePath}.${process.pid}.${atomicWriteSeq++}.tmp`;
3030
3156
  try {
3031
3157
  fs.writeFileSync(tmp, content, options);
3032
3158
  fs.renameSync(tmp, filePath);
@@ -3097,6 +3223,7 @@ function maskUrl(url) {
3097
3223
  }
3098
3224
  if (parsed.username) parsed.username = "***";
3099
3225
  if (parsed.password) parsed.password = "***";
3226
+ parsed.pathname = parsed.pathname.split("/").map((seg) => seg.length >= 16 ? `${seg.slice(0, 4)}***${seg.slice(-4)}` : seg).join("/");
3100
3227
  return parsed.toString();
3101
3228
  } catch {
3102
3229
  if (url.length > 30) {
@@ -3112,6 +3239,12 @@ function readSubscriptionCache() {
3112
3239
  const content = fs2.readFileSync(PATHS.subscriptionsCacheFile, "utf8");
3113
3240
  return JSON.parse(content);
3114
3241
  } catch {
3242
+ try {
3243
+ fs2.copyFileSync(PATHS.subscriptionsCacheFile, `${PATHS.subscriptionsCacheFile}.bak`);
3244
+ console.warn(`\u8B66\u544A: \u8BA2\u9605\u7F13\u5B58\u683C\u5F0F\u635F\u574F\uFF0C\u5DF2\u5907\u4EFD\u5230 ${PATHS.subscriptionsCacheFile}.bak`);
3245
+ } catch {
3246
+ console.warn("\u8B66\u544A: \u8BA2\u9605\u7F13\u5B58\u683C\u5F0F\u635F\u574F\uFF0C\u5DF2\u5FFD\u7565");
3247
+ }
3115
3248
  return {};
3116
3249
  }
3117
3250
  }
@@ -3214,6 +3347,7 @@ function parseOverrideKey(key) {
3214
3347
  let forceOverwrite = false;
3215
3348
  let arrayPrepend = false;
3216
3349
  let arrayAppend = false;
3350
+ let arrayMergeByName = false;
3217
3351
  const lastChar = key[key.length - 1];
3218
3352
  const openAngleCount = (key.match(/</g) || []).length;
3219
3353
  const closeAngleCount = (key.match(/>/g) || []).length;
@@ -3234,6 +3368,9 @@ function parseOverrideKey(key) {
3234
3368
  } else {
3235
3369
  actualKey = unwrapped;
3236
3370
  }
3371
+ } else if (actualKey.startsWith("~")) {
3372
+ arrayMergeByName = true;
3373
+ actualKey = actualKey.slice(1);
3237
3374
  } else {
3238
3375
  if (actualKey.startsWith("+")) {
3239
3376
  arrayPrepend = true;
@@ -3244,7 +3381,7 @@ function parseOverrideKey(key) {
3244
3381
  actualKey = actualKey.slice(0, -1);
3245
3382
  }
3246
3383
  }
3247
- return { key: actualKey, forceOverwrite, arrayPrepend, arrayAppend };
3384
+ return { key: actualKey, forceOverwrite, arrayPrepend, arrayAppend, arrayMergeByName };
3248
3385
  }
3249
3386
  function deepMergeWithOverrides(target, override) {
3250
3387
  let t = target;
@@ -3262,8 +3399,24 @@ function deepMergeWithOverrides(target, override) {
3262
3399
  }
3263
3400
  const result = { ...t };
3264
3401
  for (const [rawKey, value] of Object.entries(override)) {
3265
- const { key, forceOverwrite, arrayPrepend, arrayAppend } = parseOverrideKey(rawKey);
3402
+ const { key, forceOverwrite, arrayPrepend, arrayAppend, arrayMergeByName } = parseOverrideKey(rawKey);
3266
3403
  const existingValue = result[key];
3404
+ if (arrayMergeByName) {
3405
+ const existingArr = Array.isArray(existingValue) ? existingValue : [];
3406
+ const overrideArr = Array.isArray(value) ? value : [value];
3407
+ const merged = [...existingArr];
3408
+ for (const item of overrideArr) {
3409
+ const name = item && typeof item === "object" && !Array.isArray(item) ? item.name : void 0;
3410
+ const idx = name != null ? merged.findIndex((e) => e && typeof e === "object" && e.name === name) : -1;
3411
+ if (idx >= 0) {
3412
+ merged[idx] = deepMergeWithOverrides(merged[idx], item);
3413
+ } else {
3414
+ merged.push(item);
3415
+ }
3416
+ }
3417
+ result[key] = merged;
3418
+ continue;
3419
+ }
3267
3420
  if (arrayPrepend || arrayAppend) {
3268
3421
  const existingArr = Array.isArray(existingValue) ? existingValue : [];
3269
3422
  const overrideArr = Array.isArray(value) ? value : [value];
@@ -3296,6 +3449,69 @@ function setOverwriteEnabled(enabled) {
3296
3449
  function isOverwriteFilename(filename) {
3297
3450
  return filename === "overwrite.yaml" || /^overwrite\..+\.ya?ml$/.test(filename);
3298
3451
  }
3452
+ var MATCH_KEYS = /* @__PURE__ */ new Set(["subscription", "url-domain"]);
3453
+ function normalizeMatch(raw, fileName) {
3454
+ if (raw == null) return void 0;
3455
+ if (typeof raw !== "object" || Array.isArray(raw)) {
3456
+ console.warn(`\u8B66\u544A: \u8986\u5199\u6587\u4EF6 "${fileName}" \u7684 match \u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565\u4F5C\u7528\u57DF\u9650\u5B9A`);
3457
+ return void 0;
3458
+ }
3459
+ const result = {};
3460
+ let hasValid = false;
3461
+ for (const [key, value] of Object.entries(raw)) {
3462
+ if (!MATCH_KEYS.has(key)) {
3463
+ console.warn(`\u8B66\u544A: \u8986\u5199\u6587\u4EF6 "${fileName}" \u7684 match \u542B\u672A\u77E5\u952E "${key}"\uFF0C\u5DF2\u5FFD\u7565`);
3464
+ continue;
3465
+ }
3466
+ const arr = (Array.isArray(value) ? value : [value]).filter((v) => typeof v === "string" && v.length > 0);
3467
+ if (arr.length === 0) continue;
3468
+ result[key] = arr;
3469
+ hasValid = true;
3470
+ }
3471
+ return hasValid ? result : void 0;
3472
+ }
3473
+ function summarizeMatch(match) {
3474
+ if (!match) return void 0;
3475
+ const parts = [];
3476
+ for (const [key, value] of Object.entries(match)) {
3477
+ const vals = Array.isArray(value) ? value : [value];
3478
+ parts.push(`${key}=${vals.join("/")}`);
3479
+ }
3480
+ return parts.length > 0 ? parts.join(", ") : void 0;
3481
+ }
3482
+ function splitUrlsLocal(url) {
3483
+ return url.split(",").map((u) => u.trim()).filter(Boolean);
3484
+ }
3485
+ function hostMatchesDomain(host, domain) {
3486
+ const h = host.toLowerCase();
3487
+ const d = domain.toLowerCase();
3488
+ return h === d || h.endsWith(`.${d}`);
3489
+ }
3490
+ function matchesScope(match, scope) {
3491
+ if (!match) return true;
3492
+ if (match.subscription) {
3493
+ const names = Array.isArray(match.subscription) ? match.subscription : [match.subscription];
3494
+ if (!scope?.subName || !names.includes(scope.subName)) return false;
3495
+ }
3496
+ if (match["url-domain"]) {
3497
+ const domains = Array.isArray(match["url-domain"]) ? match["url-domain"] : [match["url-domain"]];
3498
+ if (!scope?.subUrl) return false;
3499
+ const hosts = [];
3500
+ for (const u of splitUrlsLocal(scope.subUrl)) {
3501
+ try {
3502
+ hosts.push(new URL(u).hostname);
3503
+ } catch {
3504
+ }
3505
+ }
3506
+ if (hosts.length === 0) return false;
3507
+ const ok = domains.some((d) => hosts.some((h) => hostMatchesDomain(h, d)));
3508
+ if (!ok) return false;
3509
+ }
3510
+ return true;
3511
+ }
3512
+ function filterOverwriteFilesByScope(files, scope) {
3513
+ return files.filter((f) => matchesScope(f.match, scope));
3514
+ }
3299
3515
  function loadOverwriteFile() {
3300
3516
  const dir = USER_DATA_DIR;
3301
3517
  if (!fs3.existsSync(dir)) return [];
@@ -3309,9 +3525,12 @@ function loadOverwriteFile() {
3309
3525
  const filePath = path3.join(dir, file);
3310
3526
  try {
3311
3527
  const content = fs3.readFileSync(filePath, "utf8");
3312
- const parsed = load(content);
3313
- if (parsed && typeof parsed === "object") {
3314
- results.push({ name: file, path: filePath, config: parsed });
3528
+ const parsed = load(content, { maxAliases: 200 });
3529
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3530
+ const { match, ...config } = parsed;
3531
+ results.push({ name: file, path: filePath, config, match: normalizeMatch(match, file) });
3532
+ } else if (parsed !== null) {
3533
+ console.warn(`\u8B66\u544A: \u8986\u5199\u6587\u4EF6 "${file}" \u9876\u5C42\u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u5DF2\u8DF3\u8FC7`);
3315
3534
  }
3316
3535
  } catch (e) {
3317
3536
  console.warn(`\u8B66\u544A: \u8986\u5199\u6587\u4EF6 "${file}" \u89E3\u6790\u5931\u8D25: ${e.message}`);
@@ -3320,9 +3539,9 @@ function loadOverwriteFile() {
3320
3539
  return results;
3321
3540
  }
3322
3541
  function applyOverwrite(baseConfig, preloadedFiles) {
3323
- if (!isOverwriteEnabled()) return baseConfig;
3542
+ if (!isOverwriteEnabled()) return { ...baseConfig };
3324
3543
  const overwriteFiles = preloadedFiles || loadOverwriteFile();
3325
- if (overwriteFiles.length === 0) return baseConfig;
3544
+ if (overwriteFiles.length === 0) return { ...baseConfig };
3326
3545
  let result = { ...baseConfig };
3327
3546
  for (const file of overwriteFiles) {
3328
3547
  result = deepMergeWithOverrides(result, file.config);
@@ -3338,7 +3557,8 @@ function listOverwriteFile() {
3338
3557
  files: files.map((f) => ({
3339
3558
  name: f.name,
3340
3559
  path: f.path,
3341
- keys: Object.keys(f.config || {})
3560
+ keys: Object.keys(f.config || {}),
3561
+ scope: summarizeMatch(f.match)
3342
3562
  }))
3343
3563
  };
3344
3564
  }
@@ -3349,6 +3569,7 @@ import { createRequire } from "module";
3349
3569
  var require2 = createRequire(import.meta.url);
3350
3570
  var pkg = require2("../package.json");
3351
3571
  var VERSION = pkg.version;
3572
+ var MAX_RESPONSE_BYTES = 50 * 1024 * 1024;
3352
3573
  var sleepBuf = new Int32Array(new SharedArrayBuffer(4));
3353
3574
  var NO_COLOR = process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
3354
3575
  function colorize(code, str) {
@@ -3408,6 +3629,7 @@ function formatBytes(bytes) {
3408
3629
  }
3409
3630
  function formatTimestamp(ts) {
3410
3631
  if (ts === void 0 || ts === null) return "\u672A\u77E5";
3632
+ if (ts === 0) return "\u6C38\u4E45";
3411
3633
  try {
3412
3634
  return new Date(ts * 1e3).toLocaleString("zh-CN");
3413
3635
  } catch {
@@ -3429,7 +3651,7 @@ function formatDate(dateOrIso) {
3429
3651
  }
3430
3652
  }
3431
3653
  function hasFlag(args, short, long) {
3432
- return !!args && (args.includes(short) || args.includes(long));
3654
+ return !!args && (args.includes(short) || long !== void 0 && args.includes(long));
3433
3655
  }
3434
3656
  function parseIntArg(args, short, long, defaultValue) {
3435
3657
  if (!args) return defaultValue;
@@ -3439,11 +3661,29 @@ function parseIntArg(args, short, long, defaultValue) {
3439
3661
  const val = parseInt(args[i + 1], 10);
3440
3662
  return Number.isNaN(val) ? defaultValue : val;
3441
3663
  }
3664
+ } else if (args[i].startsWith(`${long}=`)) {
3665
+ const val = parseInt(args[i].slice(long.length + 1), 10);
3666
+ if (!Number.isNaN(val)) return val;
3442
3667
  }
3443
3668
  }
3444
3669
  return defaultValue;
3445
3670
  }
3446
3671
  var VALUE_FLAGS = /* @__PURE__ */ new Set(["-t", "--timeout", "-j", "--concurrency", "-r", "--rounds", "-n", "--lines", "-u", "--update-timeout"]);
3672
+ function extractStartOptions(args) {
3673
+ if (!args) return [];
3674
+ const BOOL_FLAGS = /* @__PURE__ */ new Set(["-s", "--no-update", "--no-clean"]);
3675
+ const out = [];
3676
+ for (let i = 0; i < args.length; i++) {
3677
+ const a = args[i];
3678
+ if (VALUE_FLAGS.has(a)) {
3679
+ out.push(a);
3680
+ if (i + 1 < args.length) out.push(args[++i]);
3681
+ } else if (BOOL_FLAGS.has(a) || /^--(timeout|concurrency|rounds|update-timeout)=/.test(a)) {
3682
+ out.push(a);
3683
+ }
3684
+ }
3685
+ return out;
3686
+ }
3447
3687
  function getNonFlagArg(args, startIdx, valueFlags = VALUE_FLAGS) {
3448
3688
  if (!args) return null;
3449
3689
  for (let i = startIdx; i < args.length; i++) {
@@ -3465,6 +3705,15 @@ function isProcessRunning(pid) {
3465
3705
  return false;
3466
3706
  }
3467
3707
  }
3708
+ function isProcessCommandMatching(pid, needle) {
3709
+ if (!pid) return false;
3710
+ try {
3711
+ const result = spawnSync("ps", ["-p", String(pid), "-o", "command="], { encoding: "utf8", timeout: 5e3 });
3712
+ return (result.stdout || "").includes(needle);
3713
+ } catch {
3714
+ return false;
3715
+ }
3716
+ }
3468
3717
  function isProcessRoot(pid) {
3469
3718
  if (!pid) return false;
3470
3719
  try {
@@ -3475,7 +3724,8 @@ function isProcessRoot(pid) {
3475
3724
  }
3476
3725
  }
3477
3726
  function createHttpClient(options = {}) {
3478
- const { timeout = 6e4 } = options;
3727
+ const { timeout = 6e4, secret } = options;
3728
+ const authHeaders = secret ? { Authorization: `Bearer ${secret}` } : {};
3479
3729
  return {
3480
3730
  async get(url, config) {
3481
3731
  const controller = new AbortController();
@@ -3484,7 +3734,7 @@ function createHttpClient(options = {}) {
3484
3734
  try {
3485
3735
  const response = await fetch(url, {
3486
3736
  signal,
3487
- headers: { "User-Agent": `mihomo-cli/${VERSION}` }
3737
+ headers: { "User-Agent": `mihomo-cli/${VERSION}`, ...authHeaders }
3488
3738
  });
3489
3739
  if (!response.ok) {
3490
3740
  const error = new Error(`HTTP ${response.status}`);
@@ -3495,7 +3745,12 @@ function createHttpClient(options = {}) {
3495
3745
  }
3496
3746
  throw error;
3497
3747
  }
3498
- const data = config?.responseType === "json" ? await response.json() : await response.text();
3748
+ const declaredLen = Number(response.headers.get("content-length"));
3749
+ if (Number.isFinite(declaredLen) && declaredLen > MAX_RESPONSE_BYTES) {
3750
+ throw new Error(`\u54CD\u5E94\u4F53\u8FC7\u5927\uFF08${formatBytes(declaredLen)}\uFF0C\u4E0A\u9650 ${formatBytes(MAX_RESPONSE_BYTES)}\uFF09`);
3751
+ }
3752
+ const text = await readBodyWithLimit(response, controller);
3753
+ const data = config?.responseType === "json" ? JSON.parse(text) : text;
3499
3754
  return { data, headers: response.headers, status: response.status };
3500
3755
  } finally {
3501
3756
  clearTimeout(timer);
@@ -3503,6 +3758,29 @@ function createHttpClient(options = {}) {
3503
3758
  }
3504
3759
  };
3505
3760
  }
3761
+ async function readBodyWithLimit(response, controller) {
3762
+ if (!response.body) return response.text();
3763
+ const reader = response.body.getReader();
3764
+ const chunks = [];
3765
+ let total = 0;
3766
+ try {
3767
+ while (true) {
3768
+ const { done, value } = await reader.read();
3769
+ if (done) break;
3770
+ if (value) {
3771
+ total += value.byteLength;
3772
+ if (total > MAX_RESPONSE_BYTES) {
3773
+ controller.abort();
3774
+ throw new Error(`\u54CD\u5E94\u4F53\u8D85\u8FC7\u5927\u5C0F\u4E0A\u9650\uFF08${formatBytes(MAX_RESPONSE_BYTES)}\uFF09`);
3775
+ }
3776
+ chunks.push(value);
3777
+ }
3778
+ }
3779
+ } finally {
3780
+ reader.releaseLock();
3781
+ }
3782
+ return Buffer.concat(chunks).toString("utf8");
3783
+ }
3506
3784
  function normalizeMirrorUrl(val) {
3507
3785
  if (!val) return null;
3508
3786
  if (val === "direct" || val === "no" || val === "none") return null;
@@ -3522,19 +3800,23 @@ function parseMirrorArg(args) {
3522
3800
  if (args.includes("--no-mirror") || args.includes("--direct")) {
3523
3801
  return { mirror: null, isOverride: true, type: "download" };
3524
3802
  }
3803
+ const mirrorAllEq = args.find((a) => a.startsWith("--mirror-all="));
3525
3804
  const mirrorAllIdx = args.indexOf("--mirror-all");
3526
- if (mirrorAllIdx >= 0) {
3527
- const nextArg = args[mirrorAllIdx + 1];
3805
+ if (mirrorAllIdx >= 0 || mirrorAllEq) {
3806
+ const inline = mirrorAllEq?.slice("--mirror-all=".length);
3807
+ const nextArg = inline ?? args[mirrorAllIdx + 1];
3528
3808
  if (!nextArg || nextArg.startsWith("-")) {
3529
- return { mirror: "https://v6.gh-proxy.org/", isOverride: true, type: "all" };
3809
+ return { mirror: DEFAULT_MIRROR, isOverride: true, type: "all" };
3530
3810
  }
3531
3811
  return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "all" };
3532
3812
  }
3813
+ const mirrorEq = args.find((a) => a.startsWith("--mirror="));
3533
3814
  const mirrorIdx = args.indexOf("--mirror");
3534
- if (mirrorIdx >= 0) {
3535
- const nextArg = args[mirrorIdx + 1];
3815
+ if (mirrorIdx >= 0 || mirrorEq) {
3816
+ const inline = mirrorEq?.slice("--mirror=".length);
3817
+ const nextArg = inline ?? args[mirrorIdx + 1];
3536
3818
  if (!nextArg || nextArg.startsWith("-")) {
3537
- return { mirror: "https://v6.gh-proxy.org/", isOverride: true, type: "download" };
3819
+ return { mirror: DEFAULT_MIRROR, isOverride: true, type: "download" };
3538
3820
  }
3539
3821
  return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "download" };
3540
3822
  }
@@ -3551,12 +3833,16 @@ function isProxyValid(proxy) {
3551
3833
  }
3552
3834
 
3553
3835
  // src/config.ts
3836
+ var SAFE_YAML_LOAD_OPTIONS = { maxAliases: 200 };
3837
+ function loadYamlSafe(content) {
3838
+ return load(content, SAFE_YAML_LOAD_OPTIONS);
3839
+ }
3554
3840
  function parseYamlOrJson(content, errorMsg) {
3555
3841
  if (!content?.trim()) {
3556
3842
  throw new Error(`${errorMsg || "\u5185\u5BB9"}\u4E3A\u7A7A`);
3557
3843
  }
3558
3844
  try {
3559
- const result = load(content);
3845
+ const result = loadYamlSafe(content);
3560
3846
  if (result != null && typeof result === "object" && !Array.isArray(result)) return result;
3561
3847
  } catch {
3562
3848
  }
@@ -3567,16 +3853,17 @@ function parseYamlOrJson(content, errorMsg) {
3567
3853
  }
3568
3854
  }
3569
3855
  function dumpYaml(obj) {
3570
- return dump(obj, { indent: 2, lineWidth: -1, schema: CORE_SCHEMA });
3856
+ return dump(obj, { indent: 2, lineWidth: -1 });
3571
3857
  }
3572
3858
  function collectOverwriteProxyNames(overwriteFiles) {
3573
3859
  const names = [];
3574
3860
  for (const file of overwriteFiles) {
3575
3861
  for (const [key, value] of Object.entries(file.config)) {
3576
- if ((key === "+proxies" || key === "proxies+") && Array.isArray(value)) {
3862
+ if ((key === "+proxies" || key === "proxies+" || key === "~proxies") && Array.isArray(value)) {
3577
3863
  for (const proxy of value) {
3578
3864
  if (proxy && typeof proxy === "object" && "name" in proxy) {
3579
- names.push(proxy.name);
3865
+ const name = proxy.name;
3866
+ if (typeof name === "string" && name.length > 0) names.push(name);
3580
3867
  }
3581
3868
  }
3582
3869
  }
@@ -3614,6 +3901,7 @@ function deduplicateByName(items) {
3614
3901
  });
3615
3902
  return { result, names, duplicates };
3616
3903
  }
3904
+ var NON_TARGET_RULE_TYPES = /* @__PURE__ */ new Set(["SUB-RULE"]);
3617
3905
  function getRuleTarget(rule) {
3618
3906
  const parts = rule.split(",");
3619
3907
  if (parts.length < 2) return "";
@@ -3667,6 +3955,8 @@ function validateConfig(config) {
3667
3955
  if (rules.length > 0) {
3668
3956
  const removedRules = [];
3669
3957
  config.rules = rules.filter((rule) => {
3958
+ const ruleType = rule.split(",")[0]?.trim().toUpperCase();
3959
+ if (NON_TARGET_RULE_TYPES.has(ruleType)) return true;
3670
3960
  const target = getRuleTarget(rule);
3671
3961
  if (!target || validNames.has(target)) return true;
3672
3962
  removedRules.push(rule);
@@ -3678,13 +3968,14 @@ function validateConfig(config) {
3678
3968
  }
3679
3969
  return warnings;
3680
3970
  }
3681
- function buildConfig(subRawContent, mode) {
3971
+ function buildConfig(subRawContent, mode, scope) {
3682
3972
  const subscriptionConfig = parseYamlOrJson(subRawContent, "\u8BA2\u9605\u5185\u5BB9");
3683
3973
  if (!subscriptionConfig) {
3684
3974
  throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
3685
3975
  }
3686
3976
  const overwriteEnabled = isOverwriteEnabled();
3687
- const overwriteFiles = overwriteEnabled ? loadOverwriteFile() : [];
3977
+ const allFiles = overwriteEnabled ? loadOverwriteFile() : [];
3978
+ const overwriteFiles = filterOverwriteFilesByScope(allFiles, scope);
3688
3979
  const withOverwrites = applyOverwrite(subscriptionConfig, overwriteFiles);
3689
3980
  if (overwriteFiles.length > 0) {
3690
3981
  excludeOverwriteProxiesFromIncludeAll(withOverwrites, overwriteFiles);
@@ -3695,7 +3986,6 @@ function buildConfig(subRawContent, mode) {
3695
3986
  systemConfig[key] = value;
3696
3987
  }
3697
3988
  }
3698
- systemConfig["allow-lan"] = false;
3699
3989
  systemConfig["external-controller"] = BASE_CONFIG["external-controller"];
3700
3990
  systemConfig["mixed-port"] = BASE_CONFIG["mixed-port"];
3701
3991
  delete withOverwrites["mixed-port"];
@@ -3704,6 +3994,11 @@ function buildConfig(subRawContent, mode) {
3704
3994
  delete withOverwrites["external-ui"];
3705
3995
  delete withOverwrites["external-ui-name"];
3706
3996
  delete withOverwrites["external-ui-url"];
3997
+ delete withOverwrites.secret;
3998
+ const controllerSecret = readSettings().controller_secret;
3999
+ if (controllerSecret) {
4000
+ systemConfig.secret = controllerSecret;
4001
+ }
3707
4002
  if (mode === "tun") {
3708
4003
  systemConfig.tun = TUN_CONFIG.tun;
3709
4004
  const subDns = withOverwrites.dns || {};
@@ -3759,7 +4054,7 @@ function getConfigInfo() {
3759
4054
  if (!hasConfig()) return null;
3760
4055
  try {
3761
4056
  const content = fs4.readFileSync(PATHS.configFile, "utf8");
3762
- const cfg = load(content);
4057
+ const cfg = loadYamlSafe(content);
3763
4058
  if (!cfg) return null;
3764
4059
  const proxies = cfg.proxies;
3765
4060
  const proxyGroups = cfg["proxy-groups"];
@@ -3892,6 +4187,33 @@ import path5 from "path";
3892
4187
  import { spawn, spawnSync as spawnSync3 } from "child_process";
3893
4188
  import fs5 from "fs";
3894
4189
  import path4 from "path";
4190
+
4191
+ // src/lifecycle.ts
4192
+ var cleanupFns = /* @__PURE__ */ new Set();
4193
+ var silentSigint = false;
4194
+ function setSilentSigint(value) {
4195
+ silentSigint = value;
4196
+ }
4197
+ function isSilentSigint() {
4198
+ return silentSigint;
4199
+ }
4200
+ function registerCleanup(fn) {
4201
+ cleanupFns.add(fn);
4202
+ return () => {
4203
+ cleanupFns.delete(fn);
4204
+ };
4205
+ }
4206
+ function runCleanup() {
4207
+ for (const fn of cleanupFns) {
4208
+ try {
4209
+ fn();
4210
+ } catch {
4211
+ }
4212
+ }
4213
+ cleanupFns.clear();
4214
+ }
4215
+
4216
+ // src/process.ts
3895
4217
  var PROCESS_WAIT_ATTEMPTS = 50;
3896
4218
  var PROCESS_WAIT_INTERVAL = 100;
3897
4219
  var STARTUP_WAIT_MS = 800;
@@ -3950,6 +4272,9 @@ function checkStaleState() {
3950
4272
  needsSudo: hasRootProcess || hasRootPidFile
3951
4273
  };
3952
4274
  }
4275
+ function hasRootResidue() {
4276
+ return checkStaleState().needsSudo;
4277
+ }
3953
4278
  function savePid(pid) {
3954
4279
  ensureDirs();
3955
4280
  fs5.writeFileSync(PATHS.pidFile, pid.toString(), { mode: 384 });
@@ -3968,23 +4293,10 @@ function clearPid() {
3968
4293
  }
3969
4294
  }
3970
4295
  }
3971
- function killProcess(pid, needsSudo = false) {
4296
+ function killProcess(pid) {
3972
4297
  try {
3973
- if (needsSudo) {
3974
- const result = spawnSync3("sudo", ["kill", "-9", String(pid)], { stdio: "inherit", timeout: 1e4 });
3975
- if (result.status === 0) {
3976
- return true;
3977
- }
3978
- try {
3979
- process.kill(pid, "SIGKILL");
3980
- return true;
3981
- } catch {
3982
- return false;
3983
- }
3984
- } else {
3985
- process.kill(pid, "SIGKILL");
3986
- return true;
3987
- }
4298
+ process.kill(pid, "SIGKILL");
4299
+ return true;
3988
4300
  } catch {
3989
4301
  return false;
3990
4302
  }
@@ -4030,7 +4342,7 @@ function cleanupAll(forceSudo = false) {
4030
4342
  killedCount = pids.length;
4031
4343
  } else {
4032
4344
  for (const pid of pids) {
4033
- if (killProcess(pid, false)) {
4345
+ if (killProcess(pid)) {
4034
4346
  killedCount++;
4035
4347
  } else {
4036
4348
  failedPids.push(pid);
@@ -4082,12 +4394,13 @@ for i in 1 2 3 4 5; do
4082
4394
  fi
4083
4395
  done
4084
4396
 
4085
- # \u5931\u8D25\uFF0C\u663E\u793A\u65E5\u5FD7
4397
+ # \u5931\u8D25\uFF0C\u663E\u793A\u65E5\u5FD7\uFF08\u9000\u51FA\u7801 2\uFF1A\u907F\u5F00 sudo \u7684 1=\u9274\u6743\u5931\u8D25/\u53D6\u6D88\uFF0C\u4F9B\u8C03\u7528\u65B9\u533A\u5206\uFF09
4398
+ rm -f "\${PID_FILE}" 2>/dev/null || true
4086
4399
  echo "TUN \u542F\u52A8\u5931\u8D25"
4087
4400
  echo ""
4088
4401
  echo "--- \u65E5\u5FD7 ---"
4089
4402
  tail -25 "\${LOG_FILE}" 2>/dev/null
4090
- exit 1
4403
+ exit 2
4091
4404
  `;
4092
4405
  const scriptPath = path4.join(DIRS.runtime, "launch-tun.sh");
4093
4406
  fs5.writeFileSync(scriptPath, scriptContent, { mode: 448 });
@@ -4145,7 +4458,7 @@ async function startMixedMode(staleState) {
4145
4458
  if (staleState.needsCleanup) {
4146
4459
  if (staleState.needsSudo) {
4147
4460
  console.log("\n\u53D1\u73B0\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559\u8FDB\u7A0B/\u6587\u4EF6");
4148
- console.log("\u8BF7\u5148\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo");
4461
+ console.log(`\u8BF7\u5148\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo && sudo rm -f ${PATHS.pidFile}`);
4149
4462
  console.log("\u6216\u8005\u5207\u6362\u5230 TUN \u6A21\u5F0F\uFF0C\u542F\u52A8\u65F6\u4F1A\u81EA\u52A8\u6E05\u7406");
4150
4463
  throw new Error("\u5B58\u5728\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559");
4151
4464
  }
@@ -4176,9 +4489,15 @@ async function startMixedMode(staleState) {
4176
4489
  detached: true,
4177
4490
  stdio: ["ignore", logFd, logFd]
4178
4491
  });
4492
+ child.on("error", () => {
4493
+ });
4179
4494
  fs5.closeSync(logFd);
4180
4495
  child.unref();
4181
4496
  const pid = child.pid;
4497
+ if (!pid) {
4498
+ clearPid();
4499
+ throw new Error("\u542F\u52A8\u5931\u8D25\uFF1A\u65E0\u6CD5\u521B\u5EFA\u5185\u6838\u8FDB\u7A0B\uFF08\u5185\u6838\u4E8C\u8FDB\u5236\u53EF\u80FD\u4E0D\u53EF\u6267\u884C\uFF09");
4500
+ }
4182
4501
  savePid(pid);
4183
4502
  await new Promise((resolve) => setTimeout(resolve, STARTUP_WAIT_MS));
4184
4503
  if (!isRunning()) {
@@ -4219,6 +4538,9 @@ async function startTunMode(staleState) {
4219
4538
  if (e.status === 1) {
4220
4539
  throw new Error("\u5BC6\u7801\u9519\u8BEF\u6216\u53D6\u6D88");
4221
4540
  }
4541
+ if (e.status === 2) {
4542
+ throw new Error("TUN \u542F\u52A8\u5931\u8D25\uFF08\u8BE6\u89C1\u4E0A\u65B9\u65E5\u5FD7\uFF09");
4543
+ }
4222
4544
  throw new Error(e.message);
4223
4545
  }
4224
4546
  try {
@@ -4351,7 +4673,7 @@ function getLogPathByName(name) {
4351
4673
  }
4352
4674
  function openUrl(url) {
4353
4675
  try {
4354
- const child = spawn("open", [url], { stdio: "ignore", detached: true });
4676
+ const child = spawn("open", ["--", url], { stdio: "ignore", detached: true });
4355
4677
  child.unref();
4356
4678
  child.on("error", () => {
4357
4679
  });
@@ -4383,6 +4705,7 @@ function viewLogWithTail(logPath, options) {
4383
4705
  tailArgs.push("-n", lines.toString());
4384
4706
  tailArgs.push(logPath);
4385
4707
  const tail = spawn("tail", tailArgs, { stdio: "inherit" });
4708
+ if (follow) setSilentSigint(true);
4386
4709
  tail.on("close", () => process.exit(0));
4387
4710
  tail.on("error", (e) => {
4388
4711
  console.error(`\u65E0\u6CD5\u8BFB\u53D6\u65E5\u5FD7: ${e.message}`);
@@ -4538,10 +4861,13 @@ function disableDaemon() {
4538
4861
  async function tryHotReload() {
4539
4862
  const controller = new AbortController();
4540
4863
  const timer = setTimeout(() => controller.abort(), HOT_RELOAD_TIMEOUT_MS);
4864
+ const secret = readSettings().controller_secret;
4865
+ const headers = { "Content-Type": "application/json" };
4866
+ if (secret) headers.Authorization = `Bearer ${secret}`;
4541
4867
  try {
4542
4868
  const res = await fetch(`${CONTROLLER_BASE_URL}/configs?force=true`, {
4543
4869
  method: "PUT",
4544
- headers: { "Content-Type": "application/json" },
4870
+ headers,
4545
4871
  body: "{}",
4546
4872
  signal: controller.signal
4547
4873
  });
@@ -4581,7 +4907,11 @@ async function restartDaemon() {
4581
4907
 
4582
4908
  // src/subscription.ts
4583
4909
  function isGithubUrl(url) {
4584
- return /github\.com|raw\.githubusercontent\.com/i.test(url);
4910
+ const githubRe = /github\.com|raw\.githubusercontent\.com/i;
4911
+ if (isMultiUrl(url)) {
4912
+ return splitUrls(url).every((u) => githubRe.test(u));
4913
+ }
4914
+ return githubRe.test(url);
4585
4915
  }
4586
4916
  function getDefaultUpdateInterval(url) {
4587
4917
  return isGithubUrl(url) ? DEFAULT_UPDATE_INTERVAL_HOURS_GITHUB : DEFAULT_UPDATE_INTERVAL_HOURS;
@@ -4593,6 +4923,14 @@ var HTTP_CLIENT = createHttpClient({ timeout: 6e4 });
4593
4923
  function isMultiUrl(url) {
4594
4924
  return url.includes(",");
4595
4925
  }
4926
+ function isValidHttpUrl(url) {
4927
+ try {
4928
+ const u = new URL(url.trim());
4929
+ return u.protocol === "http:" || u.protocol === "https:";
4930
+ } catch {
4931
+ return false;
4932
+ }
4933
+ }
4596
4934
  function splitUrls(url) {
4597
4935
  return url.split(",").map((u) => u.trim()).filter(Boolean);
4598
4936
  }
@@ -4708,7 +5046,7 @@ function pickSingleSubscription(subs, pattern) {
4708
5046
  for (const s of subs) console.log(` ${s.name}`);
4709
5047
  process.exit(1);
4710
5048
  }
4711
- async function downloadSubscription(url, subName = "default", signal) {
5049
+ async function downloadSubscription(url, subName = "default", signal, persist = true) {
4712
5050
  let response;
4713
5051
  try {
4714
5052
  response = await HTTP_CLIENT.get(url, { responseType: "text", signal });
@@ -4729,9 +5067,13 @@ async function downloadSubscription(url, subName = "default", signal) {
4729
5067
  }
4730
5068
  const parsed = parseYamlOrJson(content, "\u8BA2\u9605\u5185\u5BB9");
4731
5069
  if (!parsed) throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
4732
- saveSubscriptionRawConfig(subName, content);
5070
+ if (persist) {
5071
+ saveSubscriptionRawConfig(subName, content);
5072
+ }
4733
5073
  const meta = extractSubscriptionMeta(response.headers);
4734
- saveSubscriptionMeta(subName, meta);
5074
+ if (persist) {
5075
+ saveSubscriptionMeta(subName, meta);
5076
+ }
4735
5077
  const proxies = parsed.proxies;
4736
5078
  const proxyGroups = parsed["proxy-groups"];
4737
5079
  return {
@@ -4743,13 +5085,16 @@ async function downloadSubscription(url, subName = "default", signal) {
4743
5085
  username: meta.username
4744
5086
  };
4745
5087
  }
4746
- async function downloadMergedSubscription(urls, subName, signal) {
5088
+ async function downloadMergedSubscription(urls, subName, signal, persist = true) {
5089
+ const internal = new AbortController();
5090
+ const combinedSignal = signal ? AbortSignal.any([signal, internal.signal]) : internal.signal;
4747
5091
  const responses = await Promise.all(
4748
5092
  urls.map(async (url, index) => {
4749
5093
  try {
4750
- const response = await HTTP_CLIENT.get(url, { responseType: "text", signal });
5094
+ const response = await HTTP_CLIENT.get(url, { responseType: "text", signal: combinedSignal });
4751
5095
  return { url, index, response, error: null };
4752
5096
  } catch (e) {
5097
+ internal.abort();
4753
5098
  return { url, index, response: null, error: e };
4754
5099
  }
4755
5100
  })
@@ -4780,9 +5125,13 @@ async function downloadMergedSubscription(urls, subName, signal) {
4780
5125
  }
4781
5126
  base.proxies = baseProxies;
4782
5127
  const mergedContent = dumpYaml(base);
4783
- saveSubscriptionRawConfig(subName, mergedContent);
5128
+ if (persist) {
5129
+ saveSubscriptionRawConfig(subName, mergedContent);
5130
+ }
4784
5131
  const meta = extractSubscriptionMeta(responses[0].response?.headers);
4785
- saveSubscriptionMeta(subName, meta);
5132
+ if (persist) {
5133
+ saveSubscriptionMeta(subName, meta);
5134
+ }
4786
5135
  const proxyGroups = base["proxy-groups"];
4787
5136
  return {
4788
5137
  proxies: baseProxies.length,
@@ -4798,7 +5147,8 @@ function prepareConfigForStart(mode, subName = "default") {
4798
5147
  if (!rawContent) {
4799
5148
  throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605`);
4800
5149
  }
4801
- const buildResult = buildConfig(rawContent, mode);
5150
+ const subUrl = getSubscriptions().find((s) => s.name === subName)?.url;
5151
+ const buildResult = buildConfig(rawContent, mode, { subName, subUrl });
4802
5152
  if (buildResult.warnings.length > 0) {
4803
5153
  for (const warning of buildResult.warnings) {
4804
5154
  console.log(`${colors.yellow("\u81EA\u52A8\u4FEE\u590D:")} ${warning}`);
@@ -4857,16 +5207,23 @@ async function autoUpdateStaleSubscription(options = {}) {
4857
5207
  }
4858
5208
  const timeoutMs = options.timeout ?? DEFAULT_AUTO_UPDATE_TIMEOUT;
4859
5209
  const controller = new AbortController();
4860
- let results;
5210
+ const results = [];
5211
+ const updatePromise = Promise.all(
5212
+ staleSubs.map(
5213
+ (sub) => tryUpdateOne(sub, controller.signal).then((r) => {
5214
+ results.push(r);
5215
+ return r;
5216
+ })
5217
+ )
5218
+ );
4861
5219
  try {
4862
- results = await withTimeout(Promise.all(staleSubs.map((sub) => tryUpdateOne(sub, controller.signal))), timeoutMs);
5220
+ await withTimeout(updatePromise, timeoutMs);
4863
5221
  } catch (e) {
4864
- if (e instanceof TimeoutError) {
4865
- controller.abort();
4866
- console.log(colors.yellow(`\u81EA\u52A8\u66F4\u65B0\u8D85\u65F6 (${timeoutMs / 1e3}s)\uFF0C\u8DF3\u8FC7\u66F4\u65B0\uFF0C\u4F7F\u7528\u7F13\u5B58\u914D\u7F6E`));
4867
- return { total: staleSubs.length, updated: 0, failed: staleSubs.length };
4868
- }
4869
- throw e;
5222
+ if (!(e instanceof TimeoutError)) throw e;
5223
+ controller.abort();
5224
+ await updatePromise.catch(() => {
5225
+ });
5226
+ console.log(colors.yellow(`\u81EA\u52A8\u66F4\u65B0\u8D85\u65F6 (${timeoutMs / 1e3}s)\uFF0C\u5DF2\u5B8C\u6210\u7684\u66F4\u65B0\u751F\u6548\uFF0C\u5176\u4F59\u4F7F\u7528\u7F13\u5B58\u914D\u7F6E`));
4870
5227
  }
4871
5228
  let updatedCount = 0;
4872
5229
  for (const r of results) {
@@ -4908,7 +5265,8 @@ async function testSubscriptionProxies(subName, options = {}) {
4908
5265
  if (proxies.length === 0) {
4909
5266
  return { total: 0, alive: 0, dead: 0, results: [] };
4910
5267
  }
4911
- const client = createHttpClient({ timeout: timeout + 3e3 });
5268
+ const secret = apiBase === CONTROLLER_BASE_URL ? readSettings().controller_secret : void 0;
5269
+ const client = createHttpClient({ timeout: timeout + 3e3, secret });
4912
5270
  const results = new Array(proxies.length);
4913
5271
  let completedCount = 0;
4914
5272
  let nextIndex = 0;
@@ -4949,6 +5307,19 @@ function normalizeProxyNamesBeforeSave(parsed) {
4949
5307
  group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
4950
5308
  }
4951
5309
  }
5310
+ const rules = parsed.raw.rules;
5311
+ if (Array.isArray(rules)) {
5312
+ parsed.raw.rules = rules.map((rule) => {
5313
+ if (typeof rule !== "string") return rule;
5314
+ const parts = rule.split(",");
5315
+ if (parts.length < 2) return rule;
5316
+ const targetIdx = parts[parts.length - 1].trim().toLowerCase() === "no-resolve" && parts.length >= 3 ? parts.length - 2 : parts.length - 1;
5317
+ const target = parts[targetIdx].trim();
5318
+ const renamed = renameMap.get(target);
5319
+ if (renamed) parts[targetIdx] = renamed;
5320
+ return parts.join(",");
5321
+ });
5322
+ }
4952
5323
  return renameMap.size;
4953
5324
  }
4954
5325
  function cleanDeadProxies(parsed, deadNames) {
@@ -4978,11 +5349,14 @@ function cleanDeadProxies(parsed, deadNames) {
4978
5349
  group.proxies = group.proxies.filter((name) => !removedGroupNames.has(name));
4979
5350
  }
4980
5351
  }
5352
+ }
5353
+ const removedTargets = /* @__PURE__ */ new Set([...removedGroupNames, ...deadNames]);
5354
+ if (removedTargets.size > 0) {
4981
5355
  const rules = parsed.raw.rules;
4982
5356
  if (Array.isArray(rules)) {
4983
5357
  parsed.raw.rules = rules.filter((rule) => {
4984
5358
  if (typeof rule !== "string") return true;
4985
- return !removedGroupNames.has(getRuleTarget(rule));
5359
+ return !removedTargets.has(getRuleTarget(rule));
4986
5360
  });
4987
5361
  }
4988
5362
  }
@@ -5344,7 +5718,7 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
5344
5718
  \u5E73\u53F0: ${platform}, \u67B6\u6784: ${arch}${hint}`);
5345
5719
  }
5346
5720
  const downloadUrl = withMirror(asset.browser_download_url, mirror);
5347
- const tempPath = path6.join(DIRS.kernel, asset.name);
5721
+ const tempPath = path6.join(DIRS.kernel, path6.basename(asset.name));
5348
5722
  const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
5349
5723
  if (mirror && progressCallback) {
5350
5724
  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");
@@ -5357,6 +5731,12 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
5357
5731
  ["-L", "--progress-bar", "--connect-timeout", "30", "--max-time", String(Math.floor(KERNEL_DOWNLOAD_TIMEOUT / 1e3)), "-o", tempPath, downloadUrl],
5358
5732
  { stdio: "inherit" }
5359
5733
  );
5734
+ if (curlResult.error) {
5735
+ if (curlResult.error.code === "ENOENT") {
5736
+ throw new Error("\u672A\u627E\u5230 curl \u547D\u4EE4\uFF0C\u8BF7\u5148\u5B89\u88C5 curl \u540E\u91CD\u8BD5");
5737
+ }
5738
+ throw new Error(`\u4E0B\u8F7D\u5931\u8D25: ${curlResult.error.message}`);
5739
+ }
5360
5740
  if (curlResult.status !== 0) {
5361
5741
  try {
5362
5742
  fs7.unlinkSync(tempPath);
@@ -5374,6 +5754,15 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
5374
5754
  let extractedBinary = null;
5375
5755
  try {
5376
5756
  if (tempPath.endsWith(".tar.gz") || tempPath.endsWith(".tgz")) {
5757
+ const listResult = spawnSync5("tar", ["-tzf", tempPath], { encoding: "utf8", timeout: 6e4 });
5758
+ if (listResult.error) throw listResult.error;
5759
+ if (listResult.status !== 0) throw new Error(`tar \u5217\u8868\u9000\u51FA\u7801 ${listResult.status}`);
5760
+ const entries = (listResult.stdout || "").split("\n").filter(Boolean);
5761
+ for (const entry of entries) {
5762
+ if (entry.startsWith("/") || entry.split("/").includes("..")) {
5763
+ throw new Error(`\u5F52\u6863\u542B\u975E\u6CD5\u8DEF\u5F84\u6761\u76EE: ${entry}`);
5764
+ }
5765
+ }
5377
5766
  const tarResult = spawnSync5("tar", ["-xzf", tempPath, "-C", extractPath], { stdio: ["ignore", "ignore", "inherit"], timeout: 6e4 });
5378
5767
  if (tarResult.error) throw tarResult.error;
5379
5768
  if (tarResult.status !== 0) throw new Error(`tar \u9000\u51FA\u7801 ${tarResult.status}`);
@@ -5568,7 +5957,7 @@ function cmdLogs(args) {
5568
5957
  }
5569
5958
 
5570
5959
  // src/commands/overwrite.ts
5571
- import path8 from "path";
5960
+ import path7 from "path";
5572
5961
 
5573
5962
  // src/runtime.ts
5574
5963
  function getRuntimeMode() {
@@ -5596,256 +5985,30 @@ async function launchOrRestart(mode) {
5596
5985
  return result.pid;
5597
5986
  }
5598
5987
 
5599
- // src/commands/status.ts
5600
- function printStatus() {
5601
- const status = getStatus();
5602
- const state = getRunningState();
5603
- const info = getConfigInfo();
5604
- const overwriteEnabled = isOverwriteEnabled();
5605
- const overwriteFiles = listOverwriteFile().files;
5606
- const activeSub = getActiveSubscription();
5607
- const { running, pid, daemon: daemonManaged } = state;
5608
- console.log("");
5609
- let modeLabel = "";
5610
- if (info && running) {
5611
- modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
5612
- }
5613
- const statusText = running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
5614
- console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}${modeLabel}`);
5615
- console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
5616
- if (pid) {
5617
- console.log(`${colors.gray("PID: ")}${pid}`);
5618
- if (!daemonManaged && status.processInfo) {
5619
- console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
5620
- }
5621
- }
5622
- if (info) {
5623
- if (info.mixedPort) {
5624
- console.log(`${colors.gray("\u7AEF\u53E3: ")}${info.mixedPort}`);
5625
- } else {
5626
- const ports = [];
5627
- if (info.httpPort) ports.push(`HTTP:${info.httpPort}`);
5628
- if (info.socksPort) ports.push(`SOCKS:${info.socksPort}`);
5629
- console.log(`${colors.gray("\u7AEF\u53E3: ")}${ports.length > 0 ? ports.join(", ") : "\u672A\u77E5"}`);
5630
- }
5631
- }
5632
- if (activeSub) {
5633
- let subLine = `${colors.gray("\u8BA2\u9605: ")}${activeSub.name}`;
5634
- if (info) {
5635
- subLine += ` (${formatProxySummary(info)})`;
5636
- }
5637
- console.log(subLine);
5638
- } else {
5639
- console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
5640
- }
5641
- if (overwriteEnabled && overwriteFiles.length > 0) {
5642
- const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "").replace(/\.ya?ml$/, "") || "\u4E3B\u6587\u4EF6").join(", ");
5643
- console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (${names})`);
5644
- } else if (overwriteEnabled) {
5645
- console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (\u65E0\u6587\u4EF6)`);
5646
- } else {
5647
- console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
5648
- }
5649
- if (isDaemonEnabled()) {
5650
- console.log(`${colors.gray("\u4FDD\u6D3B: ")}${colors.green("\u5DF2\u542F\u7528")} ${colors.gray("(\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F)")}`);
5651
- }
5652
- console.log("");
5653
- }
5654
-
5655
- // src/commands/stop.ts
5656
- function handleStopResult(result) {
5657
- if (result.remaining && result.remaining.length > 0) {
5658
- console.error(`${colors.red("\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62:")} ${result.remaining.join(", ")}`);
5659
- console.error("\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo");
5660
- process.exit(1);
5661
- }
5662
- }
5663
- async function cmdStop() {
5664
- if (isDaemonEnabled()) {
5665
- console.log(colors.yellow("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF0C\u4EE3\u7406\u7531 launchd \u6258\u7BA1"));
5666
- console.log("\u76F4\u63A5\u505C\u6B62\u4F1A\u88AB\u81EA\u52A8\u91CD\u65B0\u62C9\u8D77\uFF0C\u8BF7\u7528: mihomo daemon off");
5667
- return;
5668
- }
5669
- const pids = getMihomoPids();
5670
- if (pids.length === 0) {
5671
- console.log(colors.yellow("\u4E0D\u5728\u8FD0\u884C"));
5672
- return;
5673
- }
5674
- console.log(`\u505C\u6B62 ${pids.length} \u4E2A\u8FDB\u7A0B...`);
5675
- handleStopResult(stop());
5676
- console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
5677
- }
5678
-
5679
- // src/test-instance.ts
5680
- import { spawn as spawn2 } from "child_process";
5681
- import fs8 from "fs";
5682
- import path7 from "path";
5683
-
5684
- // src/lifecycle.ts
5685
- var cleanupFns = /* @__PURE__ */ new Set();
5686
- function registerCleanup(fn) {
5687
- cleanupFns.add(fn);
5688
- return () => {
5689
- cleanupFns.delete(fn);
5690
- };
5691
- }
5692
- function runCleanup() {
5693
- for (const fn of cleanupFns) {
5694
- try {
5695
- fn();
5696
- } catch {
5697
- }
5698
- }
5699
- cleanupFns.clear();
5700
- }
5701
-
5702
- // src/test-instance.ts
5703
- var TEST_DIR = path7.join(USER_DATA_DIR, "test");
5704
- var TEST_DIRS = {
5705
- data: path7.join(TEST_DIR, "data"),
5706
- runtime: path7.join(TEST_DIR, "runtime")
5707
- };
5708
- var TEST_PATHS = {
5709
- configFile: path7.join(TEST_DIRS.runtime, "config.yaml"),
5710
- pidFile: path7.join(TEST_DIRS.runtime, "pid"),
5711
- logFile: path7.join(TEST_DIR, "test.log")
5712
- };
5713
- var TEST_API = `http://${TEST_CONFIG["external-controller"]}`;
5714
- function ensureTestDirs() {
5715
- for (const dir of Object.values(TEST_DIRS)) {
5716
- fs8.mkdirSync(dir, { recursive: true, mode: 448 });
5717
- }
5718
- }
5719
- function cleanupTestDir() {
5720
- rmrf(TEST_DIR);
5721
- }
5722
- function buildTestConfig(subName) {
5723
- ensureTestDirs();
5724
- const rawContent = readSubscriptionRawConfig(subName);
5725
- if (!rawContent) {
5726
- throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
5727
- }
5728
- const parsed = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
5729
- const proxies = (parsed.proxies || []).filter(isProxyValid);
5730
- if (proxies.length === 0) {
5731
- throw new Error(`\u8BA2\u9605 "${subName}" \u6CA1\u6709\u6709\u6548\u8282\u70B9`);
5732
- }
5733
- const nameCount = /* @__PURE__ */ new Map();
5734
- for (const proxy of proxies) {
5735
- const count = (nameCount.get(proxy.name) || 0) + 1;
5736
- nameCount.set(proxy.name, count);
5737
- if (count > 1) {
5738
- proxy.name = `${proxy.name} #${count}`;
5739
- }
5740
- }
5741
- const config = {
5742
- ...TEST_CONFIG,
5743
- proxies,
5744
- "proxy-groups": [
5745
- {
5746
- name: "PROXY",
5747
- type: "select",
5748
- proxies: proxies.map((p) => p.name)
5749
- }
5750
- ],
5751
- rules: ["MATCH,PROXY"]
5752
- };
5753
- const content = dumpYaml(config);
5754
- fs8.writeFileSync(TEST_PATHS.configFile, content, { mode: 384 });
5755
- }
5756
- async function startTestInstance() {
5757
- const binary = PATHS.mihomoBinary;
5758
- if (!fs8.existsSync(binary)) throw new Error("\u672A\u627E\u5230 mihomo \u5185\u6838");
5759
- stopTestInstance();
5760
- const logFd = fs8.openSync(TEST_PATHS.logFile, "a");
5761
- const child = spawn2(binary, ["-d", TEST_DIRS.data, "-f", TEST_PATHS.configFile], {
5762
- detached: true,
5763
- stdio: ["ignore", logFd, logFd]
5764
- });
5765
- fs8.closeSync(logFd);
5766
- child.unref();
5767
- const pid = child.pid;
5768
- fs8.writeFileSync(TEST_PATHS.pidFile, pid.toString(), { mode: 384 });
5769
- const client = createHttpClient({ timeout: 2e3 });
5770
- let ready = false;
5771
- for (let i = 0; i < 60; i++) {
5772
- if (!isProcessRunning(pid)) break;
5773
- try {
5774
- await client.get(`${TEST_API}/version`);
5775
- ready = true;
5776
- break;
5777
- } catch {
5778
- await sleep(500);
5779
- }
5780
- }
5781
- if (!isProcessRunning(pid)) {
5782
- let errorDetail = "";
5783
- try {
5784
- errorDetail = fs8.readFileSync(TEST_PATHS.logFile, "utf8").slice(-1e3);
5785
- } catch {
5786
- }
5787
- throw new Error(`\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
5788
- ${errorDetail}` : ""}`);
5789
- }
5790
- if (!ready) {
5791
- throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
5792
- }
5793
- }
5794
- function stopTestInstance() {
5795
- let pid;
5796
- try {
5797
- pid = parseInt(fs8.readFileSync(TEST_PATHS.pidFile, "utf8").trim(), 10);
5798
- } catch {
5799
- return;
5800
- }
5801
- if (pid > 0 && isProcessRunning(pid)) {
5802
- process.kill(pid, "SIGKILL");
5803
- for (let i = 0; i < 20; i++) {
5804
- if (!isProcessRunning(pid)) break;
5805
- sleepSync(100);
5806
- }
5807
- }
5808
- try {
5809
- fs8.unlinkSync(TEST_PATHS.pidFile);
5810
- } catch {
5811
- }
5812
- }
5813
- async function withTestInstance(subName, fn) {
5814
- cleanupTestDir();
5815
- buildTestConfig(subName);
5816
- const unregister = registerCleanup(() => {
5817
- stopTestInstance();
5818
- cleanupTestDir();
5819
- });
5820
- try {
5821
- await startTestInstance();
5822
- return await fn(TEST_API);
5823
- } finally {
5824
- unregister();
5825
- stopTestInstance();
5826
- cleanupTestDir();
5827
- }
5828
- }
5829
-
5830
- // src/commands/subscription.ts
5831
- var IS_TTY = process.stdout.isTTY === true;
5832
- var BAR_WIDTH = 20;
5833
- function createProgressPrinter(totalRounds = 1) {
5834
- let alive = 0;
5835
- let dead = 0;
5836
- const resultMap = /* @__PURE__ */ new Map();
5837
- function render(done, total) {
5838
- if (!IS_TTY) return;
5839
- const pct = Math.round(done / total * 100);
5840
- const filled = Math.round(done / total * BAR_WIDTH);
5841
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
5842
- process.stdout.write(`\r${bar} ${done}/${total} (${pct}%) | ${colors.green(`\u2713${alive}`)} ${colors.red(`\u2717${dead}`)}`);
5988
+ // src/progress.ts
5989
+ var IS_TTY = process.stdout.isTTY === true;
5990
+ var BAR_WIDTH = 20;
5991
+ function createProgressPrinter(totalRounds = 1) {
5992
+ let alive = 0;
5993
+ let dead = 0;
5994
+ const resultMap = /* @__PURE__ */ new Map();
5995
+ function render(done, total) {
5996
+ if (!IS_TTY) return;
5997
+ const pct = Math.round(done / total * 100);
5998
+ const filled = Math.round(done / total * BAR_WIDTH);
5999
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
6000
+ process.stdout.write(`\r${bar} ${done}/${total} (${pct}%) | ${colors.green(`\u2713${alive}`)} ${colors.red(`\u2717${dead}`)}`);
5843
6001
  }
5844
6002
  return {
5845
6003
  onResult(result, index, total, round = 1) {
5846
6004
  if (resultMap.size === 0 && totalRounds > 1) {
5847
6005
  console.log(`--- \u7B2C 1 \u8F6E\u6D4B\u8BD5 (${total} \u4E2A\u8282\u70B9) ---`);
5848
6006
  }
6007
+ const prev = resultMap.get(result.name);
6008
+ if (prev) {
6009
+ if (prev.result.delay !== null) alive--;
6010
+ else dead--;
6011
+ }
5849
6012
  if (result.delay !== null) alive++;
5850
6013
  else dead++;
5851
6014
  resultMap.set(result.name, { result, round });
@@ -5864,6 +6027,7 @@ function createProgressPrinter(totalRounds = 1) {
5864
6027
  process.stdout.write("\n");
5865
6028
  }
5866
6029
  console.log("");
6030
+ if (!IS_TTY) return;
5867
6031
  const entries = [...resultMap.values()];
5868
6032
  entries.sort((a, b) => a.result.name.localeCompare(b.result.name));
5869
6033
  const total = entries.length;
@@ -5892,687 +6056,935 @@ function formatCleanSummary(result) {
5892
6056
  function formatTestSummary(summary) {
5893
6057
  return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
5894
6058
  }
5895
- function githubRepoUrl(rawUrl) {
5896
- const match = rawUrl.match(/raw\.githubusercontent\.com\/([^/]+\/[^/]+)/);
5897
- if (match) return `https://github.com/${match[1]}`;
5898
- return null;
5899
- }
5900
- function resolveTestTarget(args) {
5901
- const subs = getSubscriptions();
5902
- if (subs.length === 0) {
5903
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
5904
- process.exit(1);
6059
+
6060
+ // src/commands/status.ts
6061
+ function printStatus() {
6062
+ const status = getStatus();
6063
+ const state = getRunningState();
6064
+ const info = getConfigInfo();
6065
+ const overwriteEnabled = isOverwriteEnabled();
6066
+ const overwriteFiles = listOverwriteFile().files;
6067
+ const activeSub = getActiveSubscription();
6068
+ const { running, pid, daemon: daemonManaged } = state;
6069
+ console.log("");
6070
+ let modeLabel = "";
6071
+ if (info) {
6072
+ modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
5905
6073
  }
5906
- const nameArg = getNonFlagArg(args, 2);
5907
- const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
5908
- const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
5909
- let target;
5910
- if (nameArg) {
5911
- const matches = findSubscriptionFuzzy(subs, nameArg);
5912
- target = pickSingleSubscription(matches, nameArg);
5913
- } else {
5914
- const activeSub = getActiveSubscription();
5915
- if (!activeSub) {
5916
- console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
5917
- process.exit(1);
6074
+ const statusText = running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
6075
+ console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}${modeLabel}`);
6076
+ console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
6077
+ if (pid) {
6078
+ console.log(`${colors.gray("PID: ")}${pid}`);
6079
+ if (!daemonManaged && status.processInfo) {
6080
+ console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
5918
6081
  }
5919
- target = activeSub;
5920
- }
5921
- return { target, timeout, concurrency };
5922
- }
5923
- function printRestartHintIfRunning() {
5924
- if (getRunningState().running) {
5925
- console.log(colors.yellow("\u63D0\u793A: \u8FD0\u884C\u4E2D\u7684\u5B9E\u4F8B\u4ECD\u4F7F\u7528\u65E7\u914D\u7F6E\uFF0C\u6267\u884C mihomo start \u4F7F\u66F4\u65B0\u751F\u6548"));
5926
- console.log("");
5927
6082
  }
5928
- }
5929
- async function printSubscriptionList(options) {
5930
- if (options?.autoUpdate !== false) {
5931
- const updateResult = await autoUpdateStaleSubscription();
5932
- if (updateResult.total > 0) console.log("");
6083
+ if (info) {
6084
+ if (info.tun) {
6085
+ const extra = info.mixedPort ? `\uFF0C\u53E6\u76D1\u542C ${info.mixedPort}` : "";
6086
+ console.log(`${colors.gray("\u7AEF\u53E3: ")}TUN \u63A5\u7BA1${extra}`);
6087
+ } else if (info.mixedPort) {
6088
+ console.log(`${colors.gray("\u7AEF\u53E3: ")}${info.mixedPort}`);
6089
+ } else {
6090
+ const ports = [];
6091
+ if (info.httpPort) ports.push(`HTTP:${info.httpPort}`);
6092
+ if (info.socksPort) ports.push(`SOCKS:${info.socksPort}`);
6093
+ console.log(`${colors.gray("\u7AEF\u53E3: ")}${ports.length > 0 ? ports.join(", ") : "\u672A\u77E5"}`);
6094
+ }
5933
6095
  }
5934
- const subs = getSubscriptionsWithCache();
5935
- if (subs.length === 0) {
5936
- console.log("\u6CA1\u6709\u8BA2\u9605");
5937
- console.log("");
5938
- console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
5939
- console.log("");
5940
- return;
6096
+ if (activeSub) {
6097
+ let subLine = `${colors.gray("\u8BA2\u9605: ")}${activeSub.name}`;
6098
+ if (info) {
6099
+ subLine += ` (${formatProxySummary(info)})`;
6100
+ }
6101
+ console.log(subLine);
6102
+ } else {
6103
+ console.log(`${colors.gray("\u8BA2\u9605: ")}\u672A\u914D\u7F6E`);
6104
+ }
6105
+ if (overwriteEnabled && overwriteFiles.length > 0) {
6106
+ const names = overwriteFiles.map((f) => f.name.replace(/^overwrite\.?/, "").replace(/\.ya?ml$/, "") || "\u4E3B\u6587\u4EF6").join(", ");
6107
+ console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (${names})`);
6108
+ } else if (overwriteEnabled) {
6109
+ console.log(`${colors.gray("\u8986\u5199: ")}${colors.green("\u5DF2\u542F\u7528")} (\u65E0\u6587\u4EF6)`);
6110
+ } else {
6111
+ console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
6112
+ }
6113
+ if (isDaemonEnabled()) {
6114
+ console.log(`${colors.gray("\u4FDD\u6D3B: ")}${colors.green("\u5DF2\u542F\u7528")} ${colors.gray("(\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F)")}`);
5941
6115
  }
5942
- const activeSub = getActiveSubscription();
5943
- console.log(colors.cyan("\u8BA2\u9605\u5217\u8868:"));
5944
- subs.forEach((s, i) => {
5945
- const time = formatDate(s.updated_at);
5946
- const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
5947
- const mergeBadge = isMultiUrl(s.url) ? colors.cyan(` [\u5408\u5E76 ${splitUrls(s.url).length} \u6E90]`) : "";
5948
- const interval = resolveUpdateInterval(s.url, s.update_interval);
5949
- console.log(` ${i + 1}. ${s.name}${defaultMark}${mergeBadge}`);
5950
- console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
5951
- if (s.username) {
5952
- console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
5953
- }
5954
- if (s.download !== void 0 || s.total !== void 0) {
5955
- const used = (s.upload || 0) + (s.download || 0);
5956
- const usedStr = formatBytes(used);
5957
- const totalStr = formatBytes(s.total);
5958
- let percentStr = "";
5959
- if (s.total && s.total > 0) {
5960
- const percent = Math.min(used / s.total * 100, 100);
5961
- percentStr = ` (${percent.toFixed(1)}%)`;
5962
- }
5963
- console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
5964
- }
5965
- if (s.expire !== void 0) {
5966
- console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
5967
- }
5968
- if (s.web_page_url) {
5969
- console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
5970
- }
5971
- });
5972
- console.log("");
5973
- console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
5974
- console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
5975
- console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
5976
- console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
5977
- console.log("\u6D4B\u8BD5\u8282\u70B9: mihomo sub test [name]");
5978
- console.log("\u6E05\u7406\u8282\u70B9: mihomo sub clean [name]");
5979
- console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
5980
6116
  console.log("");
5981
6117
  }
5982
- async function cmdSubscription(args) {
5983
- const action = args[1];
5984
- if (!action || action === "list") {
5985
- await printSubscriptionList();
6118
+
6119
+ // src/commands/stop.ts
6120
+ function handleStopResult(result) {
6121
+ if (result.remaining && result.remaining.length > 0) {
6122
+ console.error(`${colors.red("\u90E8\u5206\u8FDB\u7A0B\u672A\u7EC8\u6B62:")} ${result.remaining.join(", ")}`);
6123
+ console.error("\u8BF7\u624B\u52A8\u8FD0\u884C: sudo pkill -9 mihomo");
6124
+ process.exit(1);
6125
+ }
6126
+ }
6127
+ async function cmdStop() {
6128
+ if (isDaemonEnabled()) {
6129
+ console.log(colors.yellow("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF0C\u4EE3\u7406\u7531 launchd \u6258\u7BA1"));
6130
+ console.log("\u76F4\u63A5\u505C\u6B62\u4F1A\u88AB\u81EA\u52A8\u91CD\u65B0\u62C9\u8D77\uFF0C\u8BF7\u7528: mihomo daemon off");
5986
6131
  return;
5987
6132
  }
5988
- if (action === "add") {
5989
- const url = args[2];
5990
- const name = args[3] || "default";
5991
- if (!url) {
5992
- console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
6133
+ const pids = getMihomoPids();
6134
+ if (pids.length === 0) {
6135
+ console.log(colors.yellow("\u4E0D\u5728\u8FD0\u884C"));
6136
+ return;
6137
+ }
6138
+ console.log(`\u505C\u6B62 ${pids.length} \u4E2A\u8FDB\u7A0B...`);
6139
+ handleStopResult(stop());
6140
+ console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
6141
+ }
6142
+
6143
+ // src/commands/start.ts
6144
+ async function cmdStart(args) {
6145
+ if (!hasKernel()) {
6146
+ console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
6147
+ process.exit(1);
6148
+ }
6149
+ const targetMode = args[1] === "tun" ? "tun" : "mixed";
6150
+ const daemonEnabled = isDaemonEnabled();
6151
+ if (targetMode === "tun" && daemonEnabled) {
6152
+ console.error(`${colors.red("\u9519\u8BEF:")} \u4FDD\u6D3B\u5DF2\u542F\u7528\uFF08\u4EC5\u652F\u6301 Mixed \u6A21\u5F0F\uFF09\uFF0C\u65E0\u6CD5\u542F\u52A8 TUN`);
6153
+ console.error("\u8BF7\u5148\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off");
6154
+ process.exit(1);
6155
+ }
6156
+ const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
6157
+ const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
6158
+ const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
6159
+ const skipUpdate = hasFlag(args, "-s", "--no-update");
6160
+ const skipClean = hasFlag(args, "--no-clean");
6161
+ const updateTimeout = parseIntArg(args, "-u", "--update-timeout", DEFAULT_AUTO_UPDATE_TIMEOUT);
6162
+ const sub = getActiveSubscription();
6163
+ if (!sub) {
6164
+ console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
6165
+ process.exit(1);
6166
+ }
6167
+ if (!skipUpdate) {
6168
+ await autoUpdateStaleSubscription({ timeout: updateTimeout });
6169
+ }
6170
+ if (!daemonEnabled) {
6171
+ if (hasRootResidue()) {
6172
+ console.error(`${colors.red("\u9519\u8BEF:")} \u5B58\u5728\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559\u8FDB\u7A0B/\u6587\u4EF6`);
6173
+ console.error(`\u8BF7\u5148\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo && sudo rm -f ${PATHS.pidFile}`);
6174
+ console.error("\u6216\u5207\u6362\u5230 TUN \u6A21\u5F0F\u542F\u52A8\uFF08\u81EA\u52A8\u6E05\u7406\uFF09: mihomo start tun");
5993
6175
  process.exit(1);
5994
6176
  }
5995
- if (isMultiUrl(url)) {
5996
- const urls = splitUrls(url);
5997
- for (const u of urls) {
5998
- if (!u.startsWith("http")) {
5999
- console.error(`\u9519\u8BEF: \u65E0\u6548\u7684 URL: ${u}`);
6177
+ const status = getStatus();
6178
+ const hasProcess = status.running || status.allProcesses.length > 0;
6179
+ if (hasProcess) {
6180
+ const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
6181
+ console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
6182
+ }
6183
+ handleStopResult(stop());
6184
+ if (hasProcess) {
6185
+ console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
6186
+ `);
6187
+ }
6188
+ }
6189
+ let configInfo;
6190
+ try {
6191
+ configInfo = prepareConfigForStart(targetMode, sub.name);
6192
+ } catch (e) {
6193
+ console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
6194
+ process.exit(1);
6195
+ }
6196
+ const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
6197
+ console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
6198
+ try {
6199
+ const pid = await launchOrRestart(targetMode);
6200
+ const label = daemonEnabled ? "\u5DF2\u542F\u52A8 (\u4FDD\u6D3B)" : "\u5DF2\u542F\u52A8";
6201
+ console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""}`);
6202
+ } catch (e) {
6203
+ const msg = e.message;
6204
+ const lines = msg.split("\n");
6205
+ console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${lines[0]}`);
6206
+ if (lines.length > 1) {
6207
+ for (const line of lines.slice(1)) console.error(line);
6208
+ }
6209
+ process.exit(1);
6210
+ }
6211
+ const cleanThreshold = isGithubUrl(sub.url) ? AUTO_CLEAN_THRESHOLD_GITHUB : AUTO_CLEAN_THRESHOLD;
6212
+ if (!skipClean && configInfo.proxies > cleanThreshold) {
6213
+ const cache = readSubscriptionCache();
6214
+ const lastCleanAt = cache[sub.name]?.last_auto_clean_at;
6215
+ const withinCooldown = !!lastCleanAt && Date.now() - new Date(lastCleanAt).getTime() < AUTO_CLEAN_COOLDOWN_HOURS * 60 * 60 * 1e3;
6216
+ if (!withinCooldown) {
6217
+ console.log("");
6218
+ console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${cleanThreshold}\uFF0C\u81EA\u52A8\u6E05\u7406\uFF08${AUTO_CLEAN_COOLDOWN_HOURS}h \u5185\u4EC5\u4E00\u6B21\uFF0C--no-clean \u8DF3\u8FC7\uFF09...`);
6219
+ console.log("");
6220
+ await sleep(1e3);
6221
+ const progress = createProgressPrinter(rounds);
6222
+ const cleanResult = await autoCleanSubscription(sub.name, {
6223
+ timeout,
6224
+ concurrency,
6225
+ rounds,
6226
+ onResult: progress.onResult,
6227
+ onRetryRound: progress.onRetryRound
6228
+ });
6229
+ progress.finish();
6230
+ console.log(formatTestSummary(cleanResult.summary));
6231
+ if (cleanResult.skipped) {
6232
+ console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
6233
+ } else if (cleanResult.removedProxies > 0) {
6234
+ console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
6235
+ console.log("");
6236
+ console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
6237
+ if (!daemonEnabled) handleStopResult(stop());
6238
+ try {
6239
+ configInfo = prepareConfigForStart(targetMode, sub.name);
6240
+ const pid = await launchOrRestart(targetMode);
6241
+ console.log(`${colors.green("\u5DF2\u91CD\u542F")}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
6242
+ } catch (e) {
6243
+ console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
6000
6244
  process.exit(1);
6001
6245
  }
6002
6246
  }
6003
- console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
6004
- try {
6005
- addSubscription(url, name);
6006
- setDefaultSubscription(name);
6007
- const info = await downloadMergedSubscription(urls, name);
6008
- console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
6009
- } catch (e) {
6010
- console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
6011
- process.exit(1);
6012
- }
6013
- } else {
6014
- if (!url.startsWith("http")) {
6015
- console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
6016
- process.exit(1);
6247
+ saveSubscriptionCache(sub.name, { last_auto_clean_at: (/* @__PURE__ */ new Date()).toISOString() });
6248
+ }
6249
+ }
6250
+ printStatus();
6251
+ }
6252
+
6253
+ // src/commands/overwrite.ts
6254
+ function printOverwriteList() {
6255
+ const info = listOverwriteFile();
6256
+ const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
6257
+ console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}`);
6258
+ console.log(`${colors.gray("\u4F4D\u7F6E: ")}${info.dir}`);
6259
+ console.log("");
6260
+ if (info.files.length === 0) {
6261
+ console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
6262
+ console.log("");
6263
+ console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path7.join(info.dir, "overwrite.yaml")}`);
6264
+ console.log(` \u6216 ${path7.join(info.dir, "overwrite.dns.yaml")}`);
6265
+ console.log("");
6266
+ } else {
6267
+ console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
6268
+ console.log("");
6269
+ info.files.forEach((f, i) => {
6270
+ const num = i < 10 ? ` ${i}` : `${i}`;
6271
+ console.log(` ${num}. ${f.name}`);
6272
+ if (f.scope) {
6273
+ console.log(` ${colors.gray("\u4F5C\u7528\u57DF: ")}${f.scope}`);
6017
6274
  }
6018
- console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
6019
- try {
6020
- addSubscription(url, name);
6021
- setDefaultSubscription(name);
6022
- const info = await downloadSubscription(url, name);
6023
- const repoUrl = githubRepoUrl(url);
6024
- if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
6025
- console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
6026
- } catch (e) {
6027
- console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
6028
- process.exit(1);
6275
+ if (f.keys.length > 0) {
6276
+ console.log(` ${colors.gray("\u5B57\u6BB5: ")}${f.keys.join(", ")}`);
6029
6277
  }
6030
- }
6278
+ });
6031
6279
  console.log("");
6032
- await printSubscriptionList();
6033
- return;
6034
6280
  }
6035
- if (action === "update") {
6036
- const name = args[2];
6037
- const subs = getSubscriptions();
6038
- if (subs.length === 0) {
6039
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
6040
- process.exit(1);
6041
- }
6042
- if (!name) {
6043
- console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
6044
- const results = await Promise.all(subs.map((sub) => tryUpdateOne(sub)));
6045
- let ok = 0;
6046
- for (const r of results) {
6047
- if (r.success) ok++;
6048
- printUpdateResult(r);
6049
- }
6050
- if (ok === 0) process.exit(1);
6281
+ console.log("\u542F\u7528\u8986\u5199: mihomo ow on");
6282
+ console.log("\u7981\u7528\u8986\u5199: mihomo ow off");
6283
+ console.log("");
6284
+ }
6285
+ async function cmdOverwrite(args) {
6286
+ const action = args?.[1];
6287
+ const currentMode = getRuntimeMode();
6288
+ const restartNeeded = isRestartNeededOnChange();
6289
+ if (action === "on" || action === "enable") {
6290
+ if (isOverwriteEnabled()) {
6291
+ console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
6051
6292
  console.log("");
6052
- printRestartHintIfRunning();
6053
- await printSubscriptionList();
6293
+ printOverwriteList();
6054
6294
  return;
6055
6295
  }
6056
- const matches = findSubscriptionFuzzy(subs, name);
6057
- const target = pickSingleSubscription(matches, name);
6058
- console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
6059
- const result = await tryUpdateOne(target);
6060
- if (!result.success) {
6061
- console.error(`\u66F4\u65B0\u5931\u8D25: ${(result.error || "").split("\n")[0]}`);
6062
- process.exit(1);
6296
+ setOverwriteEnabled(true);
6297
+ console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
6298
+ if (restartNeeded) {
6299
+ console.log("");
6300
+ await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
6301
+ return;
6063
6302
  }
6064
- console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
6065
6303
  console.log("");
6066
- printRestartHintIfRunning();
6067
- await printSubscriptionList();
6304
+ printOverwriteList();
6068
6305
  return;
6069
6306
  }
6070
- if (action === "use") {
6071
- const name = args[2];
6072
- const subs = getSubscriptions();
6073
- if (!name) {
6074
- console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
6075
- if (subs.length > 0) {
6076
- console.log("\n\u53EF\u7528\u8BA2\u9605:");
6077
- for (const s of subs) console.log(` ${s.name}`);
6078
- }
6079
- process.exit(1);
6080
- }
6081
- const matches = findSubscriptionFuzzy(subs, name);
6082
- const target = pickSingleSubscription(matches, name);
6083
- const currentDefault = getActiveSubscription();
6084
- const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
6085
- if (isAlreadyDefault) {
6086
- console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
6307
+ if (action === "off" || action === "disable") {
6308
+ if (!isOverwriteEnabled()) {
6309
+ console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u7981\u7528\u72B6\u6001");
6087
6310
  console.log("");
6088
- await printSubscriptionList();
6311
+ printOverwriteList();
6089
6312
  return;
6090
6313
  }
6091
- const currentMode = getRuntimeMode();
6092
- const restartNeeded = isRestartNeededOnChange();
6093
- const success = setDefaultSubscription(target.name);
6094
- if (success) {
6095
- console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
6096
- } else {
6097
- console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
6098
- process.exit(1);
6099
- }
6314
+ setOverwriteEnabled(false);
6315
+ console.log("\u5DF2\u7981\u7528\u8986\u5199\u914D\u7F6E");
6100
6316
  if (restartNeeded) {
6101
6317
  console.log("");
6102
- await cmdStart(["start", currentMode]);
6318
+ await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
6103
6319
  return;
6104
6320
  }
6105
6321
  console.log("");
6106
- await printSubscriptionList();
6322
+ printOverwriteList();
6107
6323
  return;
6108
6324
  }
6109
- if (action === "web" || action === "open") {
6110
- const name = args[2];
6111
- const subs = getSubscriptionsWithCache();
6112
- if (subs.length === 0) {
6113
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
6114
- process.exit(1);
6115
- }
6116
- let target;
6117
- if (name) {
6118
- const matches = findSubscriptionFuzzy(subs, name);
6119
- target = pickSingleSubscription(matches, name);
6325
+ console.log("");
6326
+ printOverwriteList();
6327
+ }
6328
+
6329
+ // src/commands/reset.ts
6330
+ import fs8 from "fs";
6331
+ import readline from "readline";
6332
+ var RESET_TARGETS = [
6333
+ {
6334
+ id: "subs",
6335
+ aliases: ["sub", "subs", "subscription", "subscriptions"],
6336
+ label: "\u8BA2\u9605",
6337
+ paths: () => [DIRS.subscriptions],
6338
+ needsStop: true,
6339
+ // 同步清空 settings 里的订阅列表:只删缓存文件会留下"列表存在但无配置"的半重置状态
6340
+ // (start 会报"未找到订阅配置")。active_subscription 一并清除
6341
+ onAfter: () => writeSettings({ subscriptions: void 0, active_subscription: void 0 })
6342
+ },
6343
+ {
6344
+ id: "logs",
6345
+ aliases: ["log", "logs"],
6346
+ label: "\u65E5\u5FD7",
6347
+ paths: () => [DIRS.logs],
6348
+ needsStop: false
6349
+ },
6350
+ {
6351
+ id: "data",
6352
+ aliases: ["data"],
6353
+ label: "\u8FD0\u884C\u6570\u636E",
6354
+ paths: () => [DIRS.data],
6355
+ needsStop: true
6356
+ },
6357
+ {
6358
+ id: "runtime",
6359
+ aliases: ["runtime"],
6360
+ label: "\u8FD0\u884C\u65F6",
6361
+ paths: () => [DIRS.runtime],
6362
+ needsStop: true
6363
+ },
6364
+ {
6365
+ id: "settings",
6366
+ aliases: ["setting", "settings", "config"],
6367
+ label: "\u8BBE\u7F6E",
6368
+ paths: () => [PATHS.settingsFile],
6369
+ needsStop: false
6370
+ },
6371
+ {
6372
+ id: "kernel",
6373
+ aliases: ["kernel", "core"],
6374
+ label: "\u5185\u6838",
6375
+ paths: () => [DIRS.kernel],
6376
+ needsStop: false,
6377
+ onAfter: () => clearKernelVersionCache(),
6378
+ checkEmpty: () => !hasKernel(),
6379
+ emptyMsg: "\u5185\u6838\u672A\u5B89\u88C5\uFF0C\u65E0\u9700\u5220\u9664",
6380
+ warnIfRunning: true
6381
+ },
6382
+ {
6383
+ id: "overwrites",
6384
+ aliases: ["overwrite", "overwrites", "ow"],
6385
+ label: "\u8986\u5199",
6386
+ paths: () => {
6387
+ const dir = USER_DATA_DIR;
6388
+ if (!fs8.existsSync(dir)) return [];
6389
+ return fs8.readdirSync(dir).filter(isOverwriteFilename).map((f) => `${dir}/${f}`);
6390
+ },
6391
+ needsStop: false
6392
+ },
6393
+ {
6394
+ id: "daemon",
6395
+ aliases: ["daemon"],
6396
+ label: "\u4FDD\u6D3B",
6397
+ // 卸载由确认后的 disablesDaemon 段统一处理(需 sudo,受取消保护);
6398
+ // 此处 paths 返回空(plist 在系统目录,用户态删不掉,且不应提前删破坏卸载),
6399
+ // onAfter 因幂等守卫(plist 已删)成为 no-op,仅作单独 reset 未走前段时的兜底。
6400
+ paths: () => [],
6401
+ needsStop: false,
6402
+ onAfter: () => disableDaemon(),
6403
+ checkEmpty: () => !isDaemonEnabled(),
6404
+ emptyMsg: "\u4FDD\u6D3B\u672A\u542F\u7528\uFF0C\u65E0\u9700\u5220\u9664"
6405
+ }
6406
+ ];
6407
+ function resolveResetTargets(names) {
6408
+ const matched = [];
6409
+ const unmatched = [];
6410
+ for (const name of names) {
6411
+ const t = RESET_TARGETS.find((t2) => t2.aliases.includes(name.toLowerCase()));
6412
+ if (t) {
6413
+ if (!matched.find((m) => m.id === t.id)) matched.push(t);
6120
6414
  } else {
6121
- target = getActiveSubscription() || subs[0];
6122
- }
6123
- const cached = subs.find((s) => s.name === target.name);
6124
- let webPageUrl = cached?.web_page_url;
6125
- if (!webPageUrl) {
6126
- console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
6127
- try {
6128
- await downloadSubscription(target.url, target.name);
6129
- const cache = readSubscriptionCache();
6130
- if (cache[target.name]?.web_page_url) {
6131
- webPageUrl = cache[target.name].web_page_url;
6132
- } else {
6133
- console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
6134
- process.exit(1);
6135
- }
6136
- } catch (e) {
6137
- console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
6138
- process.exit(1);
6139
- }
6140
- }
6141
- console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
6142
- const opened = openUrl(webPageUrl);
6143
- if (!opened) {
6144
- console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
6415
+ unmatched.push(name);
6145
6416
  }
6146
- return;
6147
6417
  }
6148
- if (action === "remove" || action === "rm" || action === "delete") {
6149
- const name = args[2];
6150
- const subs = getSubscriptions();
6151
- if (!name) {
6152
- console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0");
6153
- if (subs.length > 0) {
6154
- console.log("\n\u53EF\u7528\u8BA2\u9605:");
6155
- for (const s of subs) console.log(` ${s.name}`);
6156
- }
6157
- process.exit(1);
6158
- }
6159
- const matches = findSubscriptionFuzzy(subs, name);
6160
- const target = pickSingleSubscription(matches, name);
6161
- const switchedTo = removeSubscription(target.name);
6162
- console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
6163
- if (switchedTo) {
6164
- console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
6165
- }
6418
+ return { matched, unmatched };
6419
+ }
6420
+ async function confirmPrompt(question) {
6421
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6422
+ const answer = await new Promise((resolve) => {
6423
+ rl.question(`${question} (y/N) `, (a) => {
6424
+ rl.close();
6425
+ resolve(a);
6426
+ });
6427
+ });
6428
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
6429
+ }
6430
+ async function cmdReset(args) {
6431
+ const flags = (args || []).filter((a) => a.startsWith("-"));
6432
+ const names = (args || []).slice(1).filter((a) => !a.startsWith("-"));
6433
+ const KNOWN_FLAGS = /* @__PURE__ */ new Set(["--full", "--yes", "-y"]);
6434
+ const unknownFlags = flags.filter((f) => !KNOWN_FLAGS.has(f));
6435
+ if (unknownFlags.length > 0) {
6436
+ console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u9009\u9879: ${unknownFlags.join(", ")}`);
6166
6437
  console.log("");
6167
- await printSubscriptionList({ autoUpdate: false });
6168
- return;
6438
+ console.log("\u53EF\u7528\u9009\u9879: --full\uFF08\u5220\u5168\u90E8\uFF09, -y/--yes\uFF08\u8DF3\u8FC7\u786E\u8BA4\uFF09");
6439
+ process.exit(1);
6169
6440
  }
6170
- if (action === "clean") {
6171
- const { target, timeout, concurrency } = resolveTestTarget(args);
6172
- const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
6173
- console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
6174
- console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
6175
- console.log("");
6176
- const progress = createProgressPrinter(rounds);
6177
- const result = await withTestInstance(target.name, async (apiBase) => {
6178
- return autoCleanSubscription(target.name, {
6179
- timeout,
6180
- concurrency,
6181
- rounds,
6182
- apiBase,
6183
- onResult: progress.onResult,
6184
- onRetryRound: progress.onRetryRound
6185
- });
6186
- });
6187
- progress.finish();
6188
- console.log(formatTestSummary(result.summary));
6189
- if (result.skipped) {
6441
+ const fullReset = flags.includes("--full");
6442
+ const skipConfirm = flags.includes("--yes") || flags.includes("-y");
6443
+ let targets;
6444
+ if (fullReset) {
6445
+ targets = RESET_TARGETS;
6446
+ } else if (names.length > 0) {
6447
+ const { matched, unmatched } = resolveResetTargets(names);
6448
+ if (unmatched.length > 0) {
6449
+ console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u91CD\u7F6E\u76EE\u6807: ${unmatched.join(", ")}`);
6190
6450
  console.log("");
6191
- 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"));
6192
- } else if (result.removedProxies > 0) {
6193
- console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
6194
- if (getRunningState().running) {
6195
- console.log("");
6196
- console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
6451
+ console.log(`\u53EF\u7528\u76EE\u6807: ${RESET_TARGETS.map((t) => t.aliases[0]).join(", ")}`);
6452
+ console.log("");
6453
+ console.log("\u793A\u4F8B:");
6454
+ console.log(" mihomo reset sub log # \u5220\u9664\u8BA2\u9605\u548C\u65E5\u5FD7");
6455
+ console.log(" mihomo reset kernel # \u53EA\u5220\u5185\u6838");
6456
+ console.log(" mihomo reset --full # \u5220\u9664\u5168\u90E8");
6457
+ console.log(" mihomo reset # \u5220\u9664\u5168\u90E8\uFF08\u4FDD\u7559\u8BBE\u7F6E\u3001\u5185\u6838\u3001\u8986\u5199\uFF09");
6458
+ process.exit(1);
6459
+ }
6460
+ targets = matched;
6461
+ } else {
6462
+ targets = RESET_TARGETS.filter((t) => !["settings", "kernel", "overwrites", "daemon"].includes(t.id));
6463
+ }
6464
+ for (const t of targets) {
6465
+ if (t.checkEmpty?.()) {
6466
+ if (targets.length === 1) {
6467
+ console.log(t.emptyMsg);
6468
+ return;
6197
6469
  }
6198
6470
  }
6199
- return;
6200
6471
  }
6201
- if (action === "test") {
6202
- const { target, timeout, concurrency } = resolveTestTarget(args);
6203
- console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
6204
- console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
6205
- console.log("");
6206
- const progress = createProgressPrinter();
6207
- const summary = await withTestInstance(target.name, async (apiBase) => {
6208
- return testSubscriptionProxies(target.name, {
6209
- timeout,
6210
- concurrency,
6211
- apiBase,
6212
- onResult: progress.onResult
6213
- });
6214
- });
6215
- progress.finish();
6216
- console.log(formatTestSummary(summary));
6217
- return;
6472
+ const needsStop = targets.some((t) => t.needsStop);
6473
+ const warnRunning = targets.some((t) => t.warnIfRunning);
6474
+ const kernelTargeted = targets.some((t) => t.id === "kernel");
6475
+ const daemonTargeted = targets.some((t) => t.id === "daemon");
6476
+ const disablesDaemon = needsStop || kernelTargeted || daemonTargeted;
6477
+ const pids = needsStop || warnRunning ? getMihomoPids() : [];
6478
+ if (warnRunning && pids.length > 0) {
6479
+ console.log(colors.yellow(`\u8B66\u544A: mihomo \u6B63\u5728\u8FD0\u884C (PID ${pids.join(", ")})\uFF0C\u5220\u9664\u5185\u6838\u540E\u5C06\u65E0\u6CD5\u91CD\u65B0\u542F\u52A8`));
6218
6480
  }
6219
- console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
6220
- console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
6221
- process.exit(1);
6222
- }
6223
-
6224
- // src/commands/start.ts
6225
- async function cmdStart(args) {
6226
- if (!hasKernel()) {
6227
- console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
6228
- process.exit(1);
6229
- }
6230
- const targetMode = args[1] === "tun" ? "tun" : "mixed";
6231
- const daemonEnabled = isDaemonEnabled();
6232
- if (targetMode === "tun" && daemonEnabled) {
6233
- 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`);
6234
- console.error("\u8BF7\u5148\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off");
6235
- process.exit(1);
6481
+ if (disablesDaemon && isDaemonEnabled()) {
6482
+ console.log(colors.yellow("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF0C\u91CD\u7F6E\u5C06\u4E00\u5E76\u5173\u95ED\u4FDD\u6D3B\uFF08\u79FB\u9664\u5F00\u673A\u81EA\u542F\uFF09"));
6236
6483
  }
6237
- const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
6238
- const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
6239
- const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
6240
- const skipUpdate = hasFlag(args, "-s", "--no-update");
6241
- const updateTimeout = parseIntArg(args, "-u", "--update-timeout", DEFAULT_AUTO_UPDATE_TIMEOUT);
6242
- const sub = getActiveSubscription();
6243
- if (!sub) {
6244
- console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
6245
- process.exit(1);
6484
+ console.log(`\u5C06\u5220\u9664: ${targets.map((t) => t.label).join("\u3001")}`);
6485
+ if (!skipConfirm && !await confirmPrompt("\u786E\u8BA4?")) {
6486
+ console.log("\u5DF2\u53D6\u6D88");
6487
+ return;
6246
6488
  }
6247
- if (!skipUpdate) {
6248
- await autoUpdateStaleSubscription({ timeout: updateTimeout });
6489
+ if (disablesDaemon && isDaemonEnabled()) {
6490
+ try {
6491
+ disableDaemon();
6492
+ } catch (e) {
6493
+ console.error(`${colors.red("\u4FDD\u6D3B\u5173\u95ED\u5DF2\u53D6\u6D88\uFF0C\u91CD\u7F6E\u4E2D\u6B62:")} ${e.message.split("\n")[0]}`);
6494
+ return;
6495
+ }
6249
6496
  }
6250
- if (!daemonEnabled) {
6251
- const status = getStatus();
6252
- const hasProcess = status.running || status.allProcesses.length > 0;
6253
- if (hasProcess) {
6254
- const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
6255
- console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
6497
+ if (needsStop && getMihomoPids().length > 0) {
6498
+ console.log("\u505C\u6B62\u8FDB\u7A0B...");
6499
+ cleanupAll();
6500
+ for (let i = 0; i < PROCESS_WAIT_ATTEMPTS; i++) {
6501
+ if (getMihomoPids().length === 0) break;
6502
+ await new Promise((r) => setTimeout(r, PROCESS_WAIT_INTERVAL));
6256
6503
  }
6257
- handleStopResult(stop());
6258
- if (hasProcess) {
6259
- console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
6260
- `);
6504
+ }
6505
+ for (const t of targets) {
6506
+ for (const p of t.paths()) {
6507
+ if (fs8.existsSync(p)) {
6508
+ try {
6509
+ rmrf(p);
6510
+ } catch (e) {
6511
+ console.warn(` \u8B66\u544A: \u65E0\u6CD5\u5220\u9664 ${p}: ${e.message}`);
6512
+ }
6513
+ }
6261
6514
  }
6515
+ t.onAfter?.();
6262
6516
  }
6263
- let configInfo;
6264
- try {
6265
- configInfo = prepareConfigForStart(targetMode, sub.name);
6266
- } catch (e) {
6267
- console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
6268
- process.exit(1);
6517
+ ensureDirs();
6518
+ if (targets.some((t) => t.id === "settings")) {
6519
+ invalidateSettingsCache();
6269
6520
  }
6270
- const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
6271
- console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
6272
- try {
6273
- const pid = await launchOrRestart(targetMode);
6274
- const label = daemonEnabled ? "\u5DF2\u542F\u52A8 (\u4FDD\u6D3B)" : "\u5DF2\u542F\u52A8";
6275
- console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""}`);
6276
- } catch (e) {
6277
- const msg = e.message;
6278
- const lines = msg.split("\n");
6279
- console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${lines[0]}`);
6280
- if (lines.length > 1) {
6281
- for (const line of lines.slice(1)) console.error(line);
6521
+ console.log(colors.green(`\u5DF2\u91CD\u7F6E: ${targets.map((t) => t.label).join("\u3001")}`));
6522
+ }
6523
+
6524
+ // src/test-instance.ts
6525
+ import { spawn as spawn2 } from "child_process";
6526
+ import fs9 from "fs";
6527
+ import path8 from "path";
6528
+ var TEST_DIR = path8.join(USER_DATA_DIR, "test");
6529
+ var TEST_DIRS = {
6530
+ data: path8.join(TEST_DIR, "data"),
6531
+ runtime: path8.join(TEST_DIR, "runtime")
6532
+ };
6533
+ var TEST_PATHS = {
6534
+ configFile: path8.join(TEST_DIRS.runtime, "config.yaml"),
6535
+ pidFile: path8.join(TEST_DIRS.runtime, "pid"),
6536
+ logFile: path8.join(TEST_DIR, "test.log")
6537
+ };
6538
+ var TEST_API = `http://${TEST_CONFIG["external-controller"]}`;
6539
+ function ensureTestDirs() {
6540
+ for (const dir of Object.values(TEST_DIRS)) {
6541
+ fs9.mkdirSync(dir, { recursive: true, mode: 448 });
6542
+ }
6543
+ }
6544
+ function cleanupTestDir() {
6545
+ rmrf(TEST_DIR);
6546
+ }
6547
+ function buildTestConfig(subName) {
6548
+ ensureTestDirs();
6549
+ const rawContent = readSubscriptionRawConfig(subName);
6550
+ if (!rawContent) {
6551
+ throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
6552
+ }
6553
+ const parsed = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
6554
+ const proxies = (parsed.proxies || []).filter(isProxyValid);
6555
+ if (proxies.length === 0) {
6556
+ throw new Error(`\u8BA2\u9605 "${subName}" \u6CA1\u6709\u6709\u6548\u8282\u70B9`);
6557
+ }
6558
+ const nameCount = /* @__PURE__ */ new Map();
6559
+ for (const proxy of proxies) {
6560
+ const count = (nameCount.get(proxy.name) || 0) + 1;
6561
+ nameCount.set(proxy.name, count);
6562
+ if (count > 1) {
6563
+ proxy.name = `${proxy.name} #${count}`;
6282
6564
  }
6283
- process.exit(1);
6284
6565
  }
6285
- const cleanThreshold = isGithubUrl(sub.url) ? AUTO_CLEAN_THRESHOLD_GITHUB : AUTO_CLEAN_THRESHOLD;
6286
- if (configInfo.proxies > cleanThreshold) {
6287
- console.log("");
6288
- console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${cleanThreshold}\uFF0C\u81EA\u52A8\u6E05\u7406...`);
6289
- console.log("");
6290
- await sleep(1e3);
6291
- const progress = createProgressPrinter(rounds);
6292
- const cleanResult = await autoCleanSubscription(sub.name, {
6293
- timeout,
6294
- concurrency,
6295
- rounds,
6296
- onResult: progress.onResult,
6297
- onRetryRound: progress.onRetryRound
6298
- });
6299
- progress.finish();
6300
- console.log(formatTestSummary(cleanResult.summary));
6301
- if (cleanResult.skipped) {
6302
- 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"));
6303
- } else if (cleanResult.removedProxies > 0) {
6304
- console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
6305
- console.log("");
6306
- console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
6307
- if (!daemonEnabled) handleStopResult(stop());
6308
- try {
6309
- configInfo = prepareConfigForStart(targetMode, sub.name);
6310
- const pid = await launchOrRestart(targetMode);
6311
- console.log(`${colors.green("\u5DF2\u91CD\u542F")}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
6312
- } catch (e) {
6313
- console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
6314
- process.exit(1);
6566
+ const config = {
6567
+ ...TEST_CONFIG,
6568
+ proxies,
6569
+ "proxy-groups": [
6570
+ {
6571
+ name: "PROXY",
6572
+ type: "select",
6573
+ proxies: proxies.map((p) => p.name)
6315
6574
  }
6575
+ ],
6576
+ rules: ["MATCH,PROXY"]
6577
+ };
6578
+ const content = dumpYaml(config);
6579
+ fs9.writeFileSync(TEST_PATHS.configFile, content, { mode: 384 });
6580
+ }
6581
+ async function startTestInstance() {
6582
+ const binary = PATHS.mihomoBinary;
6583
+ if (!fs9.existsSync(binary)) throw new Error('\u672A\u627E\u5230 mihomo \u5185\u6838\uFF0C\u8BF7\u5148\u8FD0\u884C "mihomo kernel" \u4E0B\u8F7D');
6584
+ stopTestInstance();
6585
+ const logFd = fs9.openSync(TEST_PATHS.logFile, "a");
6586
+ const child = spawn2(binary, ["-d", TEST_DIRS.data, "-f", TEST_PATHS.configFile], {
6587
+ detached: true,
6588
+ stdio: ["ignore", logFd, logFd]
6589
+ });
6590
+ child.on("error", () => {
6591
+ });
6592
+ fs9.closeSync(logFd);
6593
+ child.unref();
6594
+ const pid = child.pid;
6595
+ if (!pid) throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25\uFF1A\u65E0\u6CD5\u521B\u5EFA\u8FDB\u7A0B\uFF08\u5185\u6838\u4E8C\u8FDB\u5236\u53EF\u80FD\u4E0D\u53EF\u6267\u884C\uFF09");
6596
+ fs9.writeFileSync(TEST_PATHS.pidFile, pid.toString(), { mode: 384 });
6597
+ const client = createHttpClient({ timeout: 2e3 });
6598
+ let ready = false;
6599
+ for (let i = 0; i < 60; i++) {
6600
+ if (!isProcessRunning(pid)) break;
6601
+ try {
6602
+ await client.get(`${TEST_API}/version`);
6603
+ ready = true;
6604
+ break;
6605
+ } catch {
6606
+ await sleep(500);
6316
6607
  }
6317
6608
  }
6318
- printStatus();
6609
+ if (!isProcessRunning(pid)) {
6610
+ let errorDetail = "";
6611
+ try {
6612
+ errorDetail = fs9.readFileSync(TEST_PATHS.logFile, "utf8").slice(-1e3);
6613
+ } catch {
6614
+ }
6615
+ throw new Error(`\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
6616
+ ${errorDetail}` : ""}`);
6617
+ }
6618
+ if (!ready) {
6619
+ throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
6620
+ }
6621
+ }
6622
+ function stopTestInstance() {
6623
+ let pid;
6624
+ try {
6625
+ pid = parseInt(fs9.readFileSync(TEST_PATHS.pidFile, "utf8").trim(), 10);
6626
+ } catch {
6627
+ return;
6628
+ }
6629
+ if (pid > 0 && isProcessRunning(pid) && isProcessCommandMatching(pid, TEST_PATHS.configFile)) {
6630
+ process.kill(pid, "SIGKILL");
6631
+ for (let i = 0; i < 20; i++) {
6632
+ if (!isProcessRunning(pid)) break;
6633
+ sleepSync(100);
6634
+ }
6635
+ }
6636
+ try {
6637
+ fs9.unlinkSync(TEST_PATHS.pidFile);
6638
+ } catch {
6639
+ }
6640
+ }
6641
+ async function withTestInstance(subName, fn) {
6642
+ cleanupTestDir();
6643
+ buildTestConfig(subName);
6644
+ const unregister = registerCleanup(() => {
6645
+ stopTestInstance();
6646
+ cleanupTestDir();
6647
+ });
6648
+ try {
6649
+ await startTestInstance();
6650
+ return await fn(TEST_API);
6651
+ } finally {
6652
+ unregister();
6653
+ stopTestInstance();
6654
+ cleanupTestDir();
6655
+ }
6319
6656
  }
6320
6657
 
6321
- // src/commands/overwrite.ts
6322
- function printOverwriteList() {
6323
- const info = listOverwriteFile();
6324
- const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
6325
- console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}`);
6326
- console.log(`${colors.gray("\u4F4D\u7F6E: ")}${info.dir}`);
6327
- console.log("");
6328
- if (info.files.length === 0) {
6329
- console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
6330
- console.log("");
6331
- console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path8.join(info.dir, "overwrite.yaml")}`);
6332
- console.log(` \u6216 ${path8.join(info.dir, "overwrite.dns.yaml")}`);
6333
- console.log("");
6658
+ // src/commands/subscription.ts
6659
+ function githubRepoUrl(rawUrl) {
6660
+ const match = rawUrl.match(/raw\.githubusercontent\.com\/([^/]+\/[^/]+)/);
6661
+ if (match) return `https://github.com/${match[1]}`;
6662
+ return null;
6663
+ }
6664
+ function resolveTestTarget(args) {
6665
+ const subs = getSubscriptions();
6666
+ if (subs.length === 0) {
6667
+ console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
6668
+ process.exit(1);
6669
+ }
6670
+ const nameArg = getNonFlagArg(args, 2);
6671
+ const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
6672
+ const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
6673
+ let target;
6674
+ if (nameArg) {
6675
+ const matches = findSubscriptionFuzzy(subs, nameArg);
6676
+ target = pickSingleSubscription(matches, nameArg);
6334
6677
  } else {
6335
- console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
6336
- console.log("");
6337
- info.files.forEach((f, i) => {
6338
- const num = i < 10 ? ` ${i}` : `${i}`;
6339
- console.log(` ${num}. ${f.name}`);
6340
- if (f.keys.length > 0) {
6341
- console.log(` ${colors.gray("\u5B57\u6BB5: ")}${f.keys.join(", ")}`);
6342
- }
6343
- });
6678
+ const activeSub = getActiveSubscription();
6679
+ if (!activeSub) {
6680
+ console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
6681
+ process.exit(1);
6682
+ }
6683
+ target = activeSub;
6684
+ }
6685
+ return { target, timeout, concurrency };
6686
+ }
6687
+ function printRestartHintIfRunning() {
6688
+ if (getRunningState().running) {
6689
+ console.log(colors.yellow("\u63D0\u793A: \u8FD0\u884C\u4E2D\u7684\u5B9E\u4F8B\u4ECD\u4F7F\u7528\u65E7\u914D\u7F6E\uFF0C\u6267\u884C mihomo start \u4F7F\u66F4\u65B0\u751F\u6548"));
6344
6690
  console.log("");
6345
6691
  }
6346
- console.log("\u542F\u7528\u8986\u5199: mihomo ow on");
6347
- console.log("\u7981\u7528\u8986\u5199: mihomo ow off");
6348
- console.log("");
6349
6692
  }
6350
- async function cmdOverwrite(args) {
6351
- const action = args?.[1];
6352
- const currentMode = getRuntimeMode();
6353
- const restartNeeded = isRestartNeededOnChange();
6354
- if (action === "on" || action === "enable") {
6355
- if (isOverwriteEnabled()) {
6356
- console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
6357
- console.log("");
6358
- printOverwriteList();
6359
- return;
6360
- }
6361
- setOverwriteEnabled(true);
6362
- console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
6363
- if (restartNeeded) {
6364
- console.log("");
6365
- await cmdStart(["start", currentMode]);
6366
- return;
6367
- }
6693
+ function printSubscriptionList() {
6694
+ const subs = getSubscriptionsWithCache();
6695
+ if (subs.length === 0) {
6696
+ console.log("\u6CA1\u6709\u8BA2\u9605");
6697
+ console.log("");
6698
+ console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
6368
6699
  console.log("");
6369
- printOverwriteList();
6370
6700
  return;
6371
6701
  }
6372
- if (action === "off" || action === "disable") {
6373
- if (!isOverwriteEnabled()) {
6374
- console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u7981\u7528\u72B6\u6001");
6375
- console.log("");
6376
- printOverwriteList();
6377
- return;
6702
+ const activeSub = getActiveSubscription();
6703
+ console.log(colors.cyan("\u8BA2\u9605\u5217\u8868:"));
6704
+ subs.forEach((s, i) => {
6705
+ const time = formatDate(s.updated_at);
6706
+ const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
6707
+ const mergeBadge = isMultiUrl(s.url) ? colors.cyan(` [\u5408\u5E76 ${splitUrls(s.url).length} \u6E90]`) : "";
6708
+ const interval = resolveUpdateInterval(s.url, s.update_interval);
6709
+ console.log(` ${i + 1}. ${s.name}${defaultMark}${mergeBadge}`);
6710
+ console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
6711
+ if (s.username) {
6712
+ console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
6378
6713
  }
6379
- setOverwriteEnabled(false);
6380
- console.log("\u5DF2\u7981\u7528\u8986\u5199\u914D\u7F6E");
6381
- if (restartNeeded) {
6382
- console.log("");
6383
- await cmdStart(["start", currentMode]);
6384
- return;
6714
+ if (s.download !== void 0 || s.total !== void 0) {
6715
+ const used = (s.upload || 0) + (s.download || 0);
6716
+ const usedStr = formatBytes(used);
6717
+ const totalStr = formatBytes(s.total);
6718
+ let percentStr = "";
6719
+ if (s.total && s.total > 0) {
6720
+ const percent = Math.min(used / s.total * 100, 100);
6721
+ percentStr = ` (${percent.toFixed(1)}%)`;
6722
+ }
6723
+ console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
6385
6724
  }
6386
- console.log("");
6387
- printOverwriteList();
6388
- return;
6389
- }
6725
+ if (s.expire !== void 0) {
6726
+ console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
6727
+ }
6728
+ if (s.web_page_url) {
6729
+ console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
6730
+ }
6731
+ });
6732
+ console.log("");
6733
+ console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
6734
+ console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
6735
+ console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
6736
+ console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
6737
+ console.log("\u6D4B\u8BD5\u8282\u70B9: mihomo sub test [name]");
6738
+ console.log("\u6E05\u7406\u8282\u70B9: mihomo sub clean [name]");
6739
+ console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
6390
6740
  console.log("");
6391
- printOverwriteList();
6392
6741
  }
6393
-
6394
- // src/commands/reset.ts
6395
- import fs9 from "fs";
6396
- import readline from "readline";
6397
- var RESET_TARGETS = [
6398
- {
6399
- id: "subs",
6400
- aliases: ["sub", "subs", "subscription", "subscriptions"],
6401
- label: "\u8BA2\u9605",
6402
- paths: () => [DIRS.subscriptions],
6403
- needsStop: true
6404
- },
6405
- {
6406
- id: "logs",
6407
- aliases: ["log", "logs"],
6408
- label: "\u65E5\u5FD7",
6409
- paths: () => [DIRS.logs],
6410
- needsStop: false
6411
- },
6412
- {
6413
- id: "data",
6414
- aliases: ["data"],
6415
- label: "\u8FD0\u884C\u6570\u636E",
6416
- paths: () => [DIRS.data],
6417
- needsStop: true
6418
- },
6419
- {
6420
- id: "runtime",
6421
- aliases: ["runtime"],
6422
- label: "\u8FD0\u884C\u65F6",
6423
- paths: () => [DIRS.runtime],
6424
- needsStop: true
6425
- },
6426
- {
6427
- id: "settings",
6428
- aliases: ["setting", "settings", "config"],
6429
- label: "\u8BBE\u7F6E",
6430
- paths: () => [PATHS.settingsFile],
6431
- needsStop: false
6432
- },
6433
- {
6434
- id: "kernel",
6435
- aliases: ["kernel", "core"],
6436
- label: "\u5185\u6838",
6437
- paths: () => [DIRS.kernel],
6438
- needsStop: false,
6439
- onAfter: () => clearKernelVersionCache(),
6440
- checkEmpty: () => !hasKernel(),
6441
- emptyMsg: "\u5185\u6838\u672A\u5B89\u88C5\uFF0C\u65E0\u9700\u5220\u9664",
6442
- warnIfRunning: true
6443
- },
6444
- {
6445
- id: "overwrites",
6446
- aliases: ["overwrite", "overwrites", "ow"],
6447
- label: "\u8986\u5199",
6448
- paths: () => {
6449
- const dir = USER_DATA_DIR;
6450
- if (!fs9.existsSync(dir)) return [];
6451
- return fs9.readdirSync(dir).filter(isOverwriteFilename).map((f) => `${dir}/${f}`);
6452
- },
6453
- needsStop: false
6454
- },
6455
- {
6456
- id: "daemon",
6457
- aliases: ["daemon"],
6458
- label: "\u4FDD\u6D3B",
6459
- // 卸载由确认后的 disablesDaemon 段统一处理(需 sudo,受取消保护);
6460
- // 此处 paths 返回空(plist 在系统目录,用户态删不掉,且不应提前删破坏卸载),
6461
- // onAfter 因幂等守卫(plist 已删)成为 no-op,仅作单独 reset 未走前段时的兜底。
6462
- paths: () => [],
6463
- needsStop: false,
6464
- onAfter: () => disableDaemon(),
6465
- checkEmpty: () => !isDaemonEnabled(),
6466
- emptyMsg: "\u4FDD\u6D3B\u672A\u542F\u7528\uFF0C\u65E0\u9700\u5220\u9664"
6742
+ async function cmdSubscription(args) {
6743
+ const action = args[1];
6744
+ if (!action || action === "list") {
6745
+ printSubscriptionList();
6746
+ return;
6467
6747
  }
6468
- ];
6469
- function resolveResetTargets(names) {
6470
- const matched = [];
6471
- const unmatched = [];
6472
- for (const name of names) {
6473
- const t = RESET_TARGETS.find((t2) => t2.aliases.includes(name.toLowerCase()));
6474
- if (t) {
6475
- if (!matched.find((m) => m.id === t.id)) matched.push(t);
6748
+ if (action === "add") {
6749
+ const url = args[2]?.trim();
6750
+ const name = args[3] || "default";
6751
+ if (!url) {
6752
+ console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
6753
+ process.exit(1);
6754
+ }
6755
+ if (isMultiUrl(url)) {
6756
+ const urls = splitUrls(url);
6757
+ if (urls.length === 0) {
6758
+ console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
6759
+ process.exit(1);
6760
+ }
6761
+ for (const u of urls) {
6762
+ if (!isValidHttpUrl(u)) {
6763
+ console.error(`\u9519\u8BEF: \u65E0\u6548\u7684 URL: ${u}`);
6764
+ process.exit(1);
6765
+ }
6766
+ }
6767
+ const normalizedUrl = urls.join(",");
6768
+ console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
6769
+ try {
6770
+ addSubscription(normalizedUrl, name);
6771
+ setDefaultSubscription(name);
6772
+ const info = await downloadMergedSubscription(urls, name);
6773
+ console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
6774
+ } catch (e) {
6775
+ removeSubscription(name);
6776
+ console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
6777
+ process.exit(1);
6778
+ }
6476
6779
  } else {
6477
- unmatched.push(name);
6780
+ if (!isValidHttpUrl(url)) {
6781
+ console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL\uFF08\u9700\u4EE5 http:// \u6216 https:// \u5F00\u5934\uFF09");
6782
+ process.exit(1);
6783
+ }
6784
+ console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
6785
+ try {
6786
+ addSubscription(url, name);
6787
+ setDefaultSubscription(name);
6788
+ const info = await downloadSubscription(url, name);
6789
+ const repoUrl = githubRepoUrl(url);
6790
+ if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
6791
+ console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
6792
+ } catch (e) {
6793
+ removeSubscription(name);
6794
+ console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
6795
+ process.exit(1);
6796
+ }
6478
6797
  }
6798
+ console.log("");
6799
+ printSubscriptionList();
6800
+ return;
6479
6801
  }
6480
- return { matched, unmatched };
6481
- }
6482
- async function confirmPrompt(question) {
6483
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6484
- const answer = await new Promise((resolve) => {
6485
- rl.question(`${question} (y/N) `, (a) => {
6486
- rl.close();
6487
- resolve(a);
6488
- });
6489
- });
6490
- return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
6491
- }
6492
- async function cmdReset(args) {
6493
- const flags = (args || []).filter((a) => a.startsWith("-"));
6494
- const names = (args || []).slice(1).filter((a) => !a.startsWith("-"));
6495
- const fullReset = flags.includes("--full") || flags.includes("-f");
6496
- const skipConfirm = flags.includes("--yes") || flags.includes("-y");
6497
- let targets;
6498
- if (fullReset) {
6499
- targets = RESET_TARGETS;
6500
- } else if (names.length > 0) {
6501
- const { matched, unmatched } = resolveResetTargets(names);
6502
- if (unmatched.length > 0) {
6503
- console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u91CD\u7F6E\u76EE\u6807: ${unmatched.join(", ")}`);
6504
- console.log("");
6505
- console.log(`\u53EF\u7528\u76EE\u6807: ${RESET_TARGETS.map((t) => t.aliases[0]).join(", ")}`);
6802
+ if (action === "update") {
6803
+ const name = args[2];
6804
+ const subs = getSubscriptions();
6805
+ if (subs.length === 0) {
6806
+ console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
6807
+ process.exit(1);
6808
+ }
6809
+ if (!name) {
6810
+ console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
6811
+ const results = await Promise.all(subs.map((sub) => tryUpdateOne(sub)));
6812
+ let ok = 0;
6813
+ for (const r of results) {
6814
+ if (r.success) ok++;
6815
+ printUpdateResult(r);
6816
+ }
6817
+ if (ok === 0) process.exit(1);
6506
6818
  console.log("");
6507
- console.log("\u793A\u4F8B:");
6508
- console.log(" mihomo reset sub log # \u5220\u9664\u8BA2\u9605\u548C\u65E5\u5FD7");
6509
- console.log(" mihomo reset kernel # \u53EA\u5220\u5185\u6838");
6510
- console.log(" mihomo reset --full # \u5220\u9664\u5168\u90E8");
6511
- console.log(" mihomo reset # \u5220\u9664\u5168\u90E8\uFF08\u4FDD\u7559\u8BBE\u7F6E\u3001\u5185\u6838\u3001\u8986\u5199\uFF09");
6819
+ printRestartHintIfRunning();
6820
+ printSubscriptionList();
6821
+ return;
6822
+ }
6823
+ const matches = findSubscriptionFuzzy(subs, name);
6824
+ const target = pickSingleSubscription(matches, name);
6825
+ console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
6826
+ const result = await tryUpdateOne(target);
6827
+ if (!result.success) {
6828
+ console.error(`\u66F4\u65B0\u5931\u8D25: ${(result.error || "").split("\n")[0]}`);
6512
6829
  process.exit(1);
6513
6830
  }
6514
- targets = matched;
6515
- } else {
6516
- targets = RESET_TARGETS.filter((t) => !["settings", "kernel", "overwrites", "daemon"].includes(t.id));
6831
+ console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
6832
+ console.log("");
6833
+ printRestartHintIfRunning();
6834
+ printSubscriptionList();
6835
+ return;
6517
6836
  }
6518
- for (const t of targets) {
6519
- if (t.checkEmpty?.()) {
6520
- if (targets.length === 1) {
6521
- console.log(t.emptyMsg);
6522
- return;
6837
+ if (action === "use") {
6838
+ const name = args[2];
6839
+ const subs = getSubscriptions();
6840
+ if (!name) {
6841
+ console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
6842
+ if (subs.length > 0) {
6843
+ console.log("\n\u53EF\u7528\u8BA2\u9605:");
6844
+ for (const s of subs) console.log(` ${s.name}`);
6523
6845
  }
6846
+ process.exit(1);
6524
6847
  }
6525
- }
6526
- const needsStop = targets.some((t) => t.needsStop);
6527
- const warnRunning = targets.some((t) => t.warnIfRunning);
6528
- const kernelTargeted = targets.some((t) => t.id === "kernel");
6529
- const daemonTargeted = targets.some((t) => t.id === "daemon");
6530
- const disablesDaemon = needsStop || kernelTargeted || daemonTargeted;
6531
- const pids = needsStop || warnRunning ? getMihomoPids() : [];
6532
- if (warnRunning && pids.length > 0) {
6533
- console.log(colors.yellow(`\u8B66\u544A: mihomo \u6B63\u5728\u8FD0\u884C (PID ${pids.join(", ")})\uFF0C\u5220\u9664\u5185\u6838\u540E\u5C06\u65E0\u6CD5\u91CD\u65B0\u542F\u52A8`));
6534
- }
6535
- if (disablesDaemon && isDaemonEnabled()) {
6536
- console.log(colors.yellow("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF0C\u91CD\u7F6E\u5C06\u4E00\u5E76\u5173\u95ED\u4FDD\u6D3B\uFF08\u79FB\u9664\u5F00\u673A\u81EA\u542F\uFF09"));
6537
- }
6538
- console.log(`\u5C06\u5220\u9664: ${targets.map((t) => t.label).join("\u3001")}`);
6539
- if (!skipConfirm && !await confirmPrompt("\u786E\u8BA4?")) {
6540
- console.log("\u5DF2\u53D6\u6D88");
6848
+ const matches = findSubscriptionFuzzy(subs, name);
6849
+ const target = pickSingleSubscription(matches, name);
6850
+ const currentDefault = getActiveSubscription();
6851
+ const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
6852
+ if (isAlreadyDefault) {
6853
+ console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
6854
+ console.log("");
6855
+ printSubscriptionList();
6856
+ return;
6857
+ }
6858
+ const currentMode = getRuntimeMode();
6859
+ const restartNeeded = isRestartNeededOnChange();
6860
+ const success = setDefaultSubscription(target.name);
6861
+ if (success) {
6862
+ console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
6863
+ } else {
6864
+ console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
6865
+ process.exit(1);
6866
+ }
6867
+ if (restartNeeded) {
6868
+ console.log("");
6869
+ await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
6870
+ return;
6871
+ }
6872
+ console.log("");
6873
+ printSubscriptionList();
6541
6874
  return;
6542
6875
  }
6543
- if (disablesDaemon && isDaemonEnabled()) {
6544
- try {
6545
- disableDaemon();
6546
- } catch (e) {
6547
- console.error(`${colors.red("\u4FDD\u6D3B\u5173\u95ED\u5DF2\u53D6\u6D88\uFF0C\u91CD\u7F6E\u4E2D\u6B62:")} ${e.message.split("\n")[0]}`);
6548
- return;
6876
+ if (action === "web" || action === "open") {
6877
+ const name = args[2];
6878
+ const subs = getSubscriptionsWithCache();
6879
+ if (subs.length === 0) {
6880
+ console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
6881
+ process.exit(1);
6882
+ }
6883
+ let target;
6884
+ if (name) {
6885
+ const matches = findSubscriptionFuzzy(subs, name);
6886
+ target = pickSingleSubscription(matches, name);
6887
+ } else {
6888
+ target = getActiveSubscription() || subs[0];
6889
+ }
6890
+ const cached = subs.find((s) => s.name === target.name);
6891
+ let webPageUrl = cached?.web_page_url;
6892
+ if (!webPageUrl) {
6893
+ console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u67E5\u8BE2\u8BA2\u9605...");
6894
+ try {
6895
+ const info = isMultiUrl(target.url) ? await downloadMergedSubscription(splitUrls(target.url), target.name, void 0, false) : await downloadSubscription(target.url, target.name, void 0, false);
6896
+ if (info.webPageUrl) {
6897
+ webPageUrl = info.webPageUrl;
6898
+ } else {
6899
+ console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
6900
+ process.exit(1);
6901
+ }
6902
+ } catch (e) {
6903
+ console.error(`\u67E5\u8BE2\u5931\u8D25: ${e.message}`);
6904
+ process.exit(1);
6905
+ }
6906
+ }
6907
+ console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
6908
+ const opened = openUrl(webPageUrl);
6909
+ if (!opened) {
6910
+ console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
6549
6911
  }
6912
+ return;
6550
6913
  }
6551
- if (needsStop && getMihomoPids().length > 0) {
6552
- console.log("\u505C\u6B62\u8FDB\u7A0B...");
6553
- cleanupAll();
6554
- for (let i = 0; i < PROCESS_WAIT_ATTEMPTS; i++) {
6555
- if (getMihomoPids().length === 0) break;
6556
- await new Promise((r) => setTimeout(r, PROCESS_WAIT_INTERVAL));
6914
+ if (action === "remove" || action === "rm" || action === "delete") {
6915
+ const name = args[2];
6916
+ const subs = getSubscriptions();
6917
+ if (!name) {
6918
+ console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0");
6919
+ if (subs.length > 0) {
6920
+ console.log("\n\u53EF\u7528\u8BA2\u9605:");
6921
+ for (const s of subs) console.log(` ${s.name}`);
6922
+ }
6923
+ process.exit(1);
6924
+ }
6925
+ const matches = findSubscriptionFuzzy(subs, name);
6926
+ const target = pickSingleSubscription(matches, name);
6927
+ const switchedTo = removeSubscription(target.name);
6928
+ console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
6929
+ if (switchedTo) {
6930
+ console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
6557
6931
  }
6932
+ console.log("");
6933
+ printSubscriptionList();
6934
+ return;
6558
6935
  }
6559
- for (const t of targets) {
6560
- for (const p of t.paths()) {
6561
- if (fs9.existsSync(p)) {
6562
- try {
6563
- rmrf(p);
6564
- } catch (e) {
6565
- console.warn(` \u8B66\u544A: \u65E0\u6CD5\u5220\u9664 ${p}: ${e.message}`);
6566
- }
6936
+ if (action === "clean") {
6937
+ const { target, timeout, concurrency } = resolveTestTarget(args);
6938
+ const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
6939
+ console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
6940
+ console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
6941
+ console.log("");
6942
+ const progress = createProgressPrinter(rounds);
6943
+ const result = await withTestInstance(target.name, async (apiBase) => {
6944
+ return autoCleanSubscription(target.name, {
6945
+ timeout,
6946
+ concurrency,
6947
+ rounds,
6948
+ apiBase,
6949
+ onResult: progress.onResult,
6950
+ onRetryRound: progress.onRetryRound
6951
+ });
6952
+ });
6953
+ progress.finish();
6954
+ console.log(formatTestSummary(result.summary));
6955
+ if (result.skipped) {
6956
+ console.log("");
6957
+ console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
6958
+ } else if (result.removedProxies > 0) {
6959
+ console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
6960
+ if (getRunningState().running) {
6961
+ console.log("");
6962
+ console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
6567
6963
  }
6568
6964
  }
6569
- t.onAfter?.();
6965
+ return;
6570
6966
  }
6571
- ensureDirs();
6572
- if (targets.some((t) => t.id === "settings")) {
6573
- invalidateSettingsCache();
6967
+ if (action === "test") {
6968
+ const { target, timeout, concurrency } = resolveTestTarget(args);
6969
+ console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
6970
+ console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
6971
+ console.log("");
6972
+ const progress = createProgressPrinter();
6973
+ const summary = await withTestInstance(target.name, async (apiBase) => {
6974
+ return testSubscriptionProxies(target.name, {
6975
+ timeout,
6976
+ concurrency,
6977
+ apiBase,
6978
+ onResult: progress.onResult
6979
+ });
6980
+ });
6981
+ progress.finish();
6982
+ console.log(formatTestSummary(summary));
6983
+ return;
6574
6984
  }
6575
- console.log(colors.green(`\u5DF2\u91CD\u7F6E: ${targets.map((t) => t.label).join("\u3001")}`));
6985
+ console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
6986
+ console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
6987
+ process.exit(1);
6576
6988
  }
6577
6989
 
6578
6990
  // src/commands/test.ts
@@ -6640,7 +7052,14 @@ async function cmdClean(args) {
6640
7052
  const mode = getRuntimeMode();
6641
7053
  const daemonManaged = isDaemonEnabled();
6642
7054
  try {
6643
- if (!daemonManaged) handleStopResult(stop());
7055
+ if (!daemonManaged) {
7056
+ if (hasRootResidue()) {
7057
+ console.error(`${colors.red("\u9519\u8BEF:")} \u4E3B\u5B9E\u4F8B\u4EE5 root \u8FD0\u884C\uFF08TUN\uFF09\uFF0C\u505C\u6B62\u5B83\u9700\u8981 sudo`);
7058
+ console.error("\u8BF7\u6539\u7528 mihomo sub clean\uFF08\u9694\u79BB\u5B9E\u4F8B\u6D4B\u901F\uFF0C\u65E0\u9700\u505C\u6B62\u4E3B\u5B9E\u4F8B\uFF09");
7059
+ process.exit(1);
7060
+ }
7061
+ handleStopResult(stop());
7062
+ }
6644
7063
  const configInfo = prepareConfigForStart(mode, activeSub.name);
6645
7064
  const pid = await launchOrRestart(mode);
6646
7065
  const label = daemonManaged ? "\u5DF2\u91CD\u542F (\u4FDD\u6D3B)" : "\u5DF2\u91CD\u542F";
@@ -6663,6 +7082,10 @@ function cmdUI(args) {
6663
7082
  const url = UI_URLS[uiName];
6664
7083
  console.log(`\u6253\u5F00 Web UI: ${uiName}`);
6665
7084
  console.log(`\u5730\u5740: ${url}`);
7085
+ const secret = readSettings().controller_secret;
7086
+ if (secret) {
7087
+ console.log("\u5DF2\u914D\u7F6E\u8BBF\u95EE\u5BC6\u94A5\uFF08UI \u8FDE\u63A5 127.0.0.1:9090 \u65F6\u9700\u8F93\u5165\uFF0C\u5BC6\u94A5\u89C1 settings.json\uFF09");
7088
+ }
6666
7089
  const success = openUrl(url);
6667
7090
  if (!success) {
6668
7091
  console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
@@ -6684,11 +7107,16 @@ async function cmdUpdate() {
6684
7107
  if (code === 0) {
6685
7108
  resolve();
6686
7109
  } else {
7110
+ console.error("\u66F4\u65B0\u5931\u8D25\u3002\u82E5\u4E3A\u6743\u9650\u95EE\u9898\uFF08EACCES\uFF09\uFF0C\u53EF\u5C1D\u8BD5: sudo npm install -g mihomo-cli");
6687
7111
  process.exit(code || 1);
6688
7112
  }
6689
7113
  });
6690
7114
  npm.on("error", (e) => {
6691
- console.error(`\u6267\u884C\u5931\u8D25: ${e.message}`);
7115
+ if (e.message.includes("EACCES") || e.message.includes("permission")) {
7116
+ console.error("\u6743\u9650\u4E0D\u8DB3\uFF0C\u53EF\u5C1D\u8BD5: sudo npm install -g mihomo-cli");
7117
+ } else {
7118
+ console.error(`\u6267\u884C\u5931\u8D25: ${e.message}`);
7119
+ }
6692
7120
  process.exit(1);
6693
7121
  });
6694
7122
  });
@@ -6716,7 +7144,7 @@ var COMMANDS = [
6716
7144
  aliases: ["up"],
6717
7145
  handler: cmdStart,
6718
7146
  group: "control",
6719
- usage: ["start [tun|mixed] [-s] [-u ms] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)", " [-r N] [-t ms] [-j N]"]
7147
+ usage: ["start [tun|mixed] [-s] [-u ms] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)", " [-r N] [-t ms] [-j N] [--no-clean]"]
6720
7148
  },
6721
7149
  {
6722
7150
  name: "tun",
@@ -6775,8 +7203,8 @@ var COMMANDS = [
6775
7203
  "subscription update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09",
6776
7204
  "subscription remove <name> \u5220\u9664\u8BA2\u9605",
6777
7205
  "subscription web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762",
6778
- "subscription test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027",
6779
- "subscription clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9"
7206
+ "subscription test [name] \u6D4B\u8BD5\u8282\u70B9\uFF08\u72EC\u7ACB\u5B9E\u4F8B\uFF0C\u65E0\u9700\u8FD0\u884C\uFF09",
7207
+ "subscription clean [name] \u6D4B\u901F\u6E05\u7406\uFF08\u72EC\u7ACB\u5B9E\u4F8B\uFF0C\u4E0D\u52A8\u4E3B\u5B9E\u4F8B\uFF09"
6780
7208
  ]
6781
7209
  },
6782
7210
  {
@@ -6792,14 +7220,14 @@ var COMMANDS = [
6792
7220
  aliases: [],
6793
7221
  handler: cmdTest,
6794
7222
  group: "subscription",
6795
- usage: ["test [-t ms] [-j N] \u5FEB\u901F\u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\u8FDE\u901A\u6027"]
7223
+ usage: ["test [-t ms] [-j N] \u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\uFF08\u7ECF\u8FD0\u884C\u4E2D\u7684\u4E3B\u5B9E\u4F8B\uFF09"]
6796
7224
  },
6797
7225
  {
6798
7226
  name: "clean",
6799
7227
  aliases: [],
6800
7228
  handler: cmdClean,
6801
7229
  group: "subscription",
6802
- usage: ["clean [-t ms] [-j N] [-r N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u81EA\u52A8\u91CD\u542F"]
7230
+ usage: ["clean [-t ms] [-j N] [-r N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u91CD\u542F\uFF08\u7ECF\u4E3B\u5B9E\u4F8B\uFF09"]
6803
7231
  },
6804
7232
  // === 配置 ===
6805
7233
  {
@@ -6867,7 +7295,7 @@ var COMMANDS = [
6867
7295
  aliases: [],
6868
7296
  handler: cmdReset,
6869
7297
  group: "system",
6870
- usage: ["reset [\u76EE\u6807...] [--full] \u91CD\u7F6E: \u7559\u7A7A\u4FDD\u7559\u8BBE\u7F6E/\u5185\u6838/\u8986\u5199, \u6307\u5B9A\u76EE\u6807\u5220\u5BF9\u5E94\u9879, --full \u5220\u5168\u90E8"]
7298
+ usage: ["reset [\u76EE\u6807...] [--full] [-y] \u91CD\u7F6E: \u7559\u7A7A\u4FDD\u7559\u8BBE\u7F6E/\u5185\u6838/\u8986\u5199, \u6307\u5B9A\u76EE\u6807\u5220\u5BF9\u5E94\u9879, --full \u5220\u5168\u90E8, -y \u8DF3\u8FC7\u786E\u8BA4"]
6871
7299
  },
6872
7300
  // === meta(不在分组清单展示,help 末尾单列) ===
6873
7301
  {
@@ -6903,7 +7331,9 @@ function findCommand(token) {
6903
7331
 
6904
7332
  // src/index.ts
6905
7333
  process.on("SIGINT", () => {
6906
- console.log("\n\u6B63\u5728\u9000\u51FA...");
7334
+ if (!isSilentSigint()) {
7335
+ console.log("\n\u6B63\u5728\u9000\u51FA...");
7336
+ }
6907
7337
  runCleanup();
6908
7338
  process.exit(130);
6909
7339
  });
@@ -6961,5 +7391,5 @@ main().catch((e) => {
6961
7391
  /*! Bundled license information:
6962
7392
 
6963
7393
  js-yaml/dist/js-yaml.mjs:
6964
- (*! js-yaml 5.2.1 https://github.com/nodeca/js-yaml @license MIT *)
7394
+ (*! js-yaml 5.3.0 https://github.com/nodeca/js-yaml @license MIT *)
6965
7395
  */