@ladderkitlab/hub-kb 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/main.ts
4
- import fs3 from "node:fs/promises";
5
- import path3 from "node:path";
4
+ import path2 from "node:path";
6
5
  import { createHash as createHash2 } from "node:crypto";
7
6
  import { fileURLToPath } from "node:url";
8
7
 
@@ -41,141 +40,10 @@ function tokenize(n, cfg) {
41
40
  }
42
41
  return [...out];
43
42
  }
44
- var QUESTION_WORDS = ["\u4EC0\u4E48\u610F\u601D", "\u4E3A\u4EC0\u4E48", "\u600E\u4E48\u6837", "\u4EC0\u4E48\u6837", "\u600E\u4E48", "\u4EC0\u4E48", "\u600E\u6837", "\u4E3A\u5565", "\u5982\u4F55", "\u662F\u5426", "\u54EA\u4E9B", "\u54EA\u4E2A", "\u54EA\u91CC", "\u5417", "\u5462", "\u5565", "\u548B"];
45
- var UNKNOWN_TOKEN_WEIGHT = 0.5;
46
- function questionTokens(n, cfg) {
47
- const out = /* @__PURE__ */ new Set();
48
- const k = cfg.tokenizer.cjk_ngram;
49
- for (const run2 of cjkRuns(n)) {
50
- const mark = new Array(run2.length).fill(false);
51
- for (const w of QUESTION_WORDS) for (let i = run2.indexOf(w); i >= 0; i = run2.indexOf(w, i + 1)) mark.fill(true, i, i + w.length);
52
- for (let i = 0; i + k <= run2.length; i++) if (mark.slice(i, i + k).some(Boolean)) out.add(run2.slice(i, i + k));
53
- if (run2.length < k && mark.some(Boolean)) out.add(run2);
54
- }
55
- return out;
56
- }
57
- function dimMatchAny(d, vs, cfg) {
58
- if (!vs?.some(Boolean)) return cfg.filters.applicability_context_missing === "drop" && d !== "any" ? null : 1;
59
- if (d === "any") return 1;
60
- if (d === "unknown") return cfg.filters.unknown_applicability_multiplier;
61
- return vs.some((v) => v && d.includes(v)) ? 1 : null;
62
- }
63
- var dimMatch = (d, v, cfg) => dimMatchAny(d, v === void 0 ? void 0 : [v], cfg);
64
- function intersect(sets) {
65
- const common = sets.length ? sets[0].filter((v) => sets.every((s) => s.includes(v))) : [];
66
- return common.length ? common : void 0;
67
- }
68
- var MAX_SEARCH_RESULTS = 5;
69
- function osMentions(n, terms, groups) {
70
- const os = /* @__PURE__ */ new Set();
71
- const mentions = [];
72
- const byWord = /* @__PURE__ */ new Map();
73
- for (const [id, words] of Object.entries(terms ?? {})) for (const w of words) byWord.set(w, [id]);
74
- for (const g of Object.values(groups ?? {})) for (const w of g.aliases) byWord.set(w, g.members);
75
- if (byWord.size === 0) return { text: n, os, mentions };
76
- const alt = [...byWord.keys()].sort((a, b) => b.length - a.length).join("|");
77
- const text = n.replace(new RegExp(`(?<![a-z0-9_])(${alt})(?:\\s*\\d+)?(?![a-z0-9_])`, "g"), (m, w) => {
78
- const set = byWord.get(w);
79
- mentions.push(set);
80
- if (set.length === 1) os.add(set[0]);
81
- return /^[a-z0-9 ]+$/.test(w) ? " " : m === w ? w : `${w} `;
82
- });
83
- return { text, os, mentions };
84
- }
85
- var MAX_GAP = 12;
86
- var MIN_FIXED = 3;
87
- var wordSeq = (n) => n.match(/[a-z0-9_]+|[一-鿿]+/g) ?? [];
88
- var segAt = (q, seg, i) => seg.every((w, j) => q[i + j] === w);
89
- function matchSegments(q, segs, from, first) {
90
- if (segs.length === 0) return true;
91
- const [seg, ...rest2] = segs;
92
- const last = first ? q.length - seg.length : Math.min(q.length - seg.length, from + MAX_GAP);
93
- for (let i = from; i <= last; i++) if (segAt(q, seg, i) && matchSegments(q, rest2, i + seg.length, false)) return true;
94
- return false;
95
- }
96
- var fixedWords = (p) => p.segments.reduce((a, s) => a + s.length, 0);
97
- function errorLineHits(patterns, n) {
98
- const q = wordSeq(n);
99
- return (patterns ?? []).filter((p) => fixedWords(p) >= MIN_FIXED && matchSegments(q, p.segments, 0, true)).sort((a, b) => fixedWords(b) - fixedWords(a));
100
- }
101
- function explicitContext(ctx) {
102
- return { client: ctx.client?.trim() ? ctx.client : void 0, os: ctx.os?.trim() ? ctx.os : void 0 };
103
- }
104
- function search(index, query, ctx, t2s) {
105
- const cfg = index.config;
106
- const normalized = normalize(query, cfg, t2s);
107
- const mentioned = osMentions(normalized, index.os_terms, index.os_groups);
108
- const clients = osMentions(mentioned.text, index.client_terms);
109
- const q = tokenize(clients.text, cfg);
110
- if (q.length === 0) return { results: [], abstained: true };
111
- const { os: ctxOs, client: ctxClient } = explicitContext(ctx);
112
- const os = ctxOs !== void 0 ? [ctxOs] : intersect(mentioned.mentions);
113
- const client = ctxClient ?? (clients.os.size === 1 ? [...clients.os][0] : void 0);
114
- const hits = errorLineHits(index.error_patterns, normalized).filter((h) => dimMatch(h.client ?? "any", client, cfg) !== null && dimMatchAny(h.os ?? "any", os, cfg) !== null);
115
- if (hits.length) {
116
- const byId = new Map(index.docs.map((d) => [d.id, d]));
117
- const lines = [];
118
- const add = (id) => {
119
- const d = byId.get(id);
120
- if (!d || lines.some((r) => r.id === id)) return;
121
- const mc = dimMatch(d.client, client, cfg), mo = dimMatchAny(d.os, os, cfg);
122
- if (mc === null || mo === null) return;
123
- lines.push({ id, type: d.type, score: (cfg.weights.error_messages ?? 0) * mc * mo * (d.stale ? cfg.freshness.stale_multiplier : 1) });
124
- };
125
- for (const h of hits) {
126
- if (h.maps_to !== "unmapped") add(h.maps_to);
127
- add(h.owner);
128
- }
129
- if (lines.length) {
130
- const results2 = lines.slice(0, Math.min(cfg.max_results, MAX_SEARCH_RESULTS));
131
- const abstained2 = results2[0].score < cfg.abstain.min_score;
132
- return { results: abstained2 ? [] : results2, abstained: abstained2 };
133
- }
134
- }
135
- const latin = q.filter((t) => /^[a-z0-9_]+$/.test(t));
136
- const question = questionTokens(clients.text, cfg);
137
- const candidates = index.docs.flatMap((d) => {
138
- const mc = dimMatch(d.client, client, cfg), mo = dimMatchAny(d.os, os, cfg);
139
- return mc === null || mo === null ? [] : [{ d, mc, mo, tokens: new Set(Object.entries(d.fields).filter(([f]) => cfg.weights[f]).flatMap(([, toks]) => toks)) }];
140
- });
141
- const n = candidates.length;
142
- const weigh = (has) => new Map(q.map((t) => {
143
- const df = candidates.filter((c) => c.tokens.has(t)).length;
144
- const shared = candidates.filter((c) => has(c, t)).length;
145
- return [t, question.has(t) || n > 1 && shared === n ? 0 : df === 0 ? UNKNOWN_TOKEN_WEIGHT : 1];
146
- }));
147
- let weight = weigh((c, t) => c.tokens.has(t));
148
- if (q.every((t) => weight.get(t) === 0 || !candidates.some((c) => c.tokens.has(t)))) {
149
- const strong = new Map(candidates.map((c) => [c, new Set(Object.entries(c.d.fields).filter(([f]) => f !== "body" && cfg.weights[f]).flatMap(([, toks]) => toks))]));
150
- weight = weigh((c, t) => strong.get(c).has(t));
151
- }
152
- const total = q.reduce((a, t) => a + weight.get(t), 0);
153
- if (total === 0) return { results: [], abstained: true };
154
- const scored = [];
155
- for (const { d, mc, mo } of candidates) {
156
- const matched = /* @__PURE__ */ new Set();
157
- let s = 0;
158
- for (const [field2, toks] of Object.entries(d.fields)) {
159
- const w = cfg.weights[field2] ?? 0;
160
- if (!w) continue;
161
- const set = new Set(toks);
162
- let hit = 0;
163
- for (const t of q) if (set.has(t)) {
164
- hit++;
165
- matched.add(t);
166
- }
167
- s += w * (hit / q.length);
168
- }
169
- if ([...matched].reduce((a, t) => a + weight.get(t), 0) / total < cfg.abstain.min_token_coverage) continue;
170
- if (latin.some((t) => !matched.has(t))) continue;
171
- s *= mc * mo;
172
- if (d.stale) s *= cfg.freshness.stale_multiplier;
173
- if (s > 0) scored.push({ id: d.id, type: d.type, score: s, lv: d.last_verified });
174
- }
175
- scored.sort((a, b) => b.score - a.score || (b.lv > a.lv ? 1 : b.lv < a.lv ? -1 : 0) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
176
- const results = scored.slice(0, Math.min(cfg.max_results, MAX_SEARCH_RESULTS)).map(({ id, type, score }) => ({ id, type, score }));
177
- const abstained = results.length === 0 || results[0].score < cfg.abstain.min_score;
178
- return { results: abstained ? [] : results, abstained };
43
+ function freshnessAt(e, now) {
44
+ if (!e.fresh_windows) return e.freshness;
45
+ const t = now.getTime();
46
+ return e.fresh_windows.some(([from, until]) => (from === null || t >= Date.parse(from)) && t < Date.parse(until)) ? "current" : "stale";
179
47
  }
180
48
 
181
49
  // ../redact/context.ts
@@ -185,6 +53,11 @@ var MAX_DEPTH = 64;
185
53
  var BLOCK_PREFIX = /[\s,,。::]/;
186
54
  var isKeyChar = (c) => c !== void 0 && /[A-Za-z0-9_-]/.test(c);
187
55
  var isNewline = (c) => c === "\n" || c === "\r";
56
+ var JSON_ESC = { '"': '"', "\\": "\\", "/": "/", b: "\b", f: "\f", n: "\n", r: "\r", t: " " };
57
+ function decodeJsonKey(raw) {
58
+ if (!raw.includes("\\")) return raw;
59
+ return raw.replace(/\\(u[0-9A-Fa-f]{4}|.)/g, (m, e) => e.length === 5 ? String.fromCharCode(parseInt(e.slice(1), 16)) : JSON_ESC[e] ?? m);
60
+ }
188
61
  function configRegions(s) {
189
62
  const t = s.text;
190
63
  const n = s.n;
@@ -226,7 +99,17 @@ function configRegions(s) {
226
99
  }
227
100
  s.budget.tick(j - i);
228
101
  const closed = j < n && t[j] === c;
229
- lastKey = depth > 0 ? { value: t.slice(i + 1, j) } : null;
102
+ const keyText = c === '"' ? decodeJsonKey(t.slice(i + 1, j)) : t.slice(i + 1, j);
103
+ lastKey = depth > 0 ? { value: keyText } : null;
104
+ if (depth === 0 && closed && (i === 0 || BLOCK_PREFIX.test(t[i - 1])) && ROOTS.has(keyText.toLowerCase())) {
105
+ let k = j + 1;
106
+ while (k < n && /\s/.test(t[k])) k++;
107
+ s.budget.tick(k - j);
108
+ if (t[k] === ":") {
109
+ out.push({ index: i === 0 ? 0 : i - 1, end: n });
110
+ return out;
111
+ }
112
+ }
230
113
  i = closed ? j + 1 : j;
231
114
  continue;
232
115
  }
@@ -341,6 +224,10 @@ var RULE_LABELS = {
341
224
  };
342
225
  var ATOMS = [...new Set([...Object.values(RULE_LABELS), CREDENTIAL_MASK].map((s) => s.trim()))].sort((a, b) => b.length - a.length);
343
226
  var FIRST = new Set(ATOMS.map((a) => a[0]));
227
+ function placeholderEndingAt(text, end) {
228
+ for (const a of ATOMS) if (end >= a.length && text.startsWith(a, end - a.length)) return a.length;
229
+ return 0;
230
+ }
344
231
  function placeholderAt(text, i) {
345
232
  if (i >= text.length || !FIRST.has(text[i])) return 0;
346
233
  for (const a of ATOMS) if (text.startsWith(a, i)) return a.length;
@@ -348,8 +235,8 @@ function placeholderAt(text, i) {
348
235
  }
349
236
 
350
237
  // ../redact/value.ts
351
- function newScan(text, limit) {
352
- return { text, n: text.length, budget: new Budget(text.length, limit), values: /* @__PURE__ */ new Map(), unfinished: text.length };
238
+ function newScan(text, limit, raw = text) {
239
+ return { text, raw, n: text.length, budget: new Budget(text.length, limit), values: /* @__PURE__ */ new Map(), unfinished: text.length };
353
240
  }
354
241
  var IP_LITERAL_MAX = 64;
355
242
  var isHexCode = (c) => c >= 48 && c <= 57 || c >= 97 && c <= 102 || c >= 65 && c <= 70;
@@ -477,6 +364,16 @@ function isContent(s, raw) {
477
364
  }
478
365
  return decoded.trim() !== "";
479
366
  }
367
+ function hasRawValueContent(s, start, end) {
368
+ let raw = "";
369
+ for (let k = start; k < end; ) {
370
+ s.budget.tick();
371
+ const a = placeholderAt(s.text, k);
372
+ if (a > 0 && k + a <= end) k += a;
373
+ else raw += s.text[k++];
374
+ }
375
+ return s.budget.exhausted || isContent(s, raw);
376
+ }
480
377
  function interpretUrlValue(s, start) {
481
378
  const cached = s.values.get(start);
482
379
  if (cached) {
@@ -539,14 +436,7 @@ function interpretUrlValue(s, start) {
539
436
  } else if (segments === 0) {
540
437
  record = { state: "none", judgeEnd: start, displayEnd: start, next: start };
541
438
  } else if (content) {
542
- if (!wide && lastEnd < n && isInlineWs(t[lastEnd])) {
543
- let k = lastEnd;
544
- while (k < n && isInlineWs(t[k])) k++;
545
- s.budget.tick(k - lastEnd);
546
- const d = t[k];
547
- if (k < n && (placeholderAt(t, k) > 0 || isPrintableAscii(t.charCodeAt(k)) && d !== "&" && d !== "#" && d !== ";" && d !== ")")) wide = true;
548
- } else if (!wide && continuationStarts(t, n, lastEnd)) wide = true;
549
- const displayEnd = wide ? restEnd(s, lastEnd) : lastEnd;
439
+ const displayEnd = contentDisplayEnd(s, lastEnd, wide);
550
440
  record = { state: "content", judgeEnd: lastEnd, displayEnd, next: displayEnd };
551
441
  } else {
552
442
  record = { state: placeholder ? "placeholder" : "blank", judgeEnd: lastEnd, displayEnd: lastEnd, next: lastEnd };
@@ -554,6 +444,18 @@ function interpretUrlValue(s, start) {
554
444
  s.values.set(start, record);
555
445
  return record;
556
446
  }
447
+ function contentDisplayEnd(s, judgeEnd, wide = false) {
448
+ const t = s.text;
449
+ const n = s.n;
450
+ if (!wide && judgeEnd < n && isInlineWs(t[judgeEnd])) {
451
+ let k = judgeEnd;
452
+ while (k < n && isInlineWs(t[k])) k++;
453
+ s.budget.tick(k - judgeEnd);
454
+ const d = t[k];
455
+ if (k < n && (placeholderAt(t, k) > 0 || isPrintableAscii(t.charCodeAt(k)) && d !== "&" && d !== "#" && d !== ";" && d !== ")")) wide = true;
456
+ } else if (!wide && continuationStarts(t, n, judgeEnd)) wide = true;
457
+ return wide ? restEnd(s, judgeEnd) : judgeEnd;
458
+ }
557
459
  var hasCredentialValue = (v) => v.state === "content" || v.state === "uncertain";
558
460
 
559
461
  // ../redact/named-credential.ts
@@ -562,9 +464,11 @@ var PROSE_CREDENTIAL_NAMES = ["token", "secret", "password", "pwd", "session_id"
562
464
  var PROSE_KEY_NAME = "key";
563
465
  var PROSE_KEY_MIN_LENGTH = 8;
564
466
  var AUTH_SCHEME_NAME = "bearer";
467
+ var AUTH_HEADER_NAME = "authorization";
565
468
  var PROXY_URI_SCHEMES = ["ss", "ssr", "vmess", "vless", "trojan", "hysteria", "hysteria2", "hy2", "tuic", "anytls"];
469
+ var SOCKS_URI_SCHEMES = ["socks", "socks4", "socks4a", "socks5", "socks5h"];
566
470
  var SCHEMES_LONGEST_FIRST = [...PROXY_URI_SCHEMES].sort((a, b) => b.length - a.length);
567
- var PROSE_NAMES_LONGEST_FIRST = [...PROSE_CREDENTIAL_NAMES, PROSE_KEY_NAME, AUTH_SCHEME_NAME].sort((a, b) => b.length - a.length);
471
+ var PROSE_NAMES_LONGEST_FIRST = [...PROSE_CREDENTIAL_NAMES, PROSE_KEY_NAME, AUTH_SCHEME_NAME, AUTH_HEADER_NAME].sort((a, b) => b.length - a.length);
568
472
  var PROSE_NAME_FIRST = new Set(PROSE_NAMES_LONGEST_FIRST.map((n) => n[0]));
569
473
  var isAsciiName = (name) => name.charCodeAt(0) < 128;
570
474
  var SEPARATORS = "=:\uFF1A";
@@ -572,6 +476,18 @@ var isAlnumCode = (x) => x >= 48 && x <= 57 || x >= 65 && x <= 90 || x >= 97 &&
572
476
  var isAlnumAt = (t, i) => i >= 0 && i < t.length && isAlnumCode(t.charCodeAt(i));
573
477
  var isConnector = (c) => c === "_" || c === "-" || c === ".";
574
478
  var isTokenCharAt = (t, i) => i < t.length && (isAlnumCode(t.charCodeAt(i)) || t[i] === "_" || t[i] === "-");
479
+ var isB64TokenCharAt = (t, i) => isTokenCharAt(t, i) || i < t.length && ".~+/".includes(t[i]);
480
+ var isUpperCode = (x) => x >= 65 && x <= 90;
481
+ var isLowerOrDigitCode = (x) => x >= 97 && x <= 122 || x >= 48 && x <= 57;
482
+ var camelBoundaryAt = (t, i) => i > 0 && isUpperCode(t.charCodeAt(i)) && isLowerOrDigitCode(t.charCodeAt(i - 1));
483
+ var B64_LONG_MIN = 40;
484
+ function b64LongEnd(s, k) {
485
+ const t = s.text;
486
+ let j = k;
487
+ while (j < s.n && (isAlnumAt(t, j) || t[j] === "+" || t[j] === "/")) j++;
488
+ s.budget.tick(j - k + 1);
489
+ return j - k >= B64_LONG_MIN ? j : -1;
490
+ }
575
491
  function compoundNameStart(text, nameStart, s) {
576
492
  let start = nameStart;
577
493
  for (; ; ) {
@@ -605,6 +521,9 @@ function urlNameClass(rawName) {
605
521
  if (m) return m[1] === PROSE_KEY_NAME ? "key" : "prose";
606
522
  return URL_NAME_SET.has(name) ? "url" : null;
607
523
  }
524
+ function isUrlCredentialName(rawName) {
525
+ return urlNameClass(rawName) !== null;
526
+ }
608
527
  function keyThresholdEnd(s, from) {
609
528
  const t = s.text;
610
529
  let k = from;
@@ -613,7 +532,8 @@ function keyThresholdEnd(s, from) {
613
532
  let c = 0;
614
533
  while (c < PROSE_KEY_MIN_LENGTH && isTokenCharAt(t, k + c)) c++;
615
534
  s.budget.tick(k - from + c + 1);
616
- return c < PROSE_KEY_MIN_LENGTH ? -1 : k + PROSE_KEY_MIN_LENGTH;
535
+ if (c >= PROSE_KEY_MIN_LENGTH) return k + PROSE_KEY_MIN_LENGTH;
536
+ return b64LongEnd(s, k) < 0 ? -1 : k + PROSE_KEY_MIN_LENGTH;
617
537
  }
618
538
  function proseNameAt(text, i) {
619
539
  if (!PROSE_NAME_FIRST.has(text[i].toLowerCase())) return null;
@@ -652,7 +572,8 @@ function proseNamedHits(s) {
652
572
  break;
653
573
  }
654
574
  const name = proseNameAt(t, i);
655
- if (!name || isAsciiName(name) && isAlnumAt(t, i - 1)) {
575
+ const camel = name !== null && isAsciiName(name) && isAlnumAt(t, i - 1) && camelBoundaryAt(t, i);
576
+ if (!name || isAsciiName(name) && isAlnumAt(t, i - 1) && !camel) {
656
577
  i++;
657
578
  continue;
658
579
  }
@@ -661,7 +582,7 @@ function proseNamedHits(s) {
661
582
  let k = nameEnd;
662
583
  while (k < n && isInlineWs(t[k])) k++;
663
584
  let c = 0;
664
- while (c < PROSE_KEY_MIN_LENGTH && isTokenCharAt(t, k + c)) c++;
585
+ while (c < PROSE_KEY_MIN_LENGTH && isB64TokenCharAt(t, k + c)) c++;
665
586
  s.budget.tick(k - nameEnd + c + 1);
666
587
  if (k === nameEnd || c < PROSE_KEY_MIN_LENGTH) {
667
588
  i++;
@@ -700,9 +621,43 @@ function proseNamedHits(s) {
700
621
  i++;
701
622
  continue;
702
623
  }
703
- headEnd = k + 1;
624
+ let first = k;
625
+ if (name === AUTH_HEADER_NAME) {
626
+ let w = k;
627
+ while (w < n && /[A-Za-z]/.test(t[w])) w++;
628
+ let v = w;
629
+ while (v < n && isInlineWs(t[v])) v++;
630
+ s.budget.tick(v - k + 1);
631
+ if (w > k && v > w) {
632
+ const v0 = v;
633
+ for (; ; ) {
634
+ const a = placeholderAt(t, v);
635
+ if (a > 0) {
636
+ v += a;
637
+ continue;
638
+ }
639
+ if (v < n && isInlineWs(t[v])) {
640
+ v++;
641
+ continue;
642
+ }
643
+ break;
644
+ }
645
+ s.budget.tick(v - v0 + 1);
646
+ if (v >= n || !isPrintableAscii(t.charCodeAt(v)) || isWs(t[v])) {
647
+ i++;
648
+ continue;
649
+ }
650
+ first = v;
651
+ }
652
+ }
653
+ headEnd = first + 1;
704
654
  }
705
- const start = compoundNameStart(t, i, s);
655
+ let runStart = i;
656
+ if (camel) {
657
+ while (runStart > 0 && isAlnumAt(t, runStart - 1)) runStart--;
658
+ s.budget.tick(i - runStart + 1);
659
+ }
660
+ const start = compoundNameStart(t, runStart, s);
706
661
  let urlEnd = -1;
707
662
  if (t[j] === "=" && j === nameEnd && inUrlParamName(s, start)) {
708
663
  const v = interpretUrlValue(s, sepEnd);
@@ -748,6 +703,44 @@ function urlCredentialSpans(s) {
748
703
  break;
749
704
  }
750
705
  const c = t[i];
706
+ if (c === "%" && i + 2 < n) {
707
+ const head = t.slice(i, i + 3).toUpperCase();
708
+ if (head === "%3F" || head === "%26" || head === "%23") {
709
+ let j = i + 3;
710
+ while (j < n) {
711
+ if (PARAM_NAME_CHAR(t, j)) {
712
+ j++;
713
+ continue;
714
+ }
715
+ if (t[j] === "%" && isHex(t[j + 1]) && isHex(t[j + 2]) && !["%3D", "%26", "%3F", "%23"].includes(t.slice(j, j + 3).toUpperCase())) {
716
+ j += 3;
717
+ continue;
718
+ }
719
+ break;
720
+ }
721
+ s.budget.tick(j - i + 1);
722
+ if (j > i + 3 && t.slice(j, j + 3).toUpperCase() === "%3D") {
723
+ const cls2 = urlNameClass(t.slice(i + 3, j).replace(/%25/gi, "%"));
724
+ if (!cls2) {
725
+ i = j;
726
+ continue;
727
+ }
728
+ const vStart = j + 3;
729
+ let k = vStart;
730
+ while (k < n && t[k] !== "&" && t[k] !== "#" && !isWs(t[k]) && !(t[k] === "%" && t.slice(k, k + 3).toUpperCase() === "%26")) k++;
731
+ s.budget.tick(k - vStart + 1);
732
+ if (k > vStart && hasRawValueContent(s, vStart, k)) {
733
+ out.push({ index: cls2 === "url" ? vStart : i + 3, end: k });
734
+ i = k;
735
+ continue;
736
+ }
737
+ i = k > vStart ? k : j;
738
+ continue;
739
+ }
740
+ }
741
+ i++;
742
+ continue;
743
+ }
751
744
  if (c !== "?" && c !== "&" && c !== "#" && c !== ";") {
752
745
  i++;
753
746
  continue;
@@ -781,110 +774,312 @@ function proxySchemeStart(t, colon) {
781
774
  return -1;
782
775
  }
783
776
 
784
- // ../redact/index.ts
785
- var isAlnumCode2 = (x) => x >= 48 && x <= 57 || x >= 65 && x <= 90 || x >= 97 && x <= 122;
786
- var isWordCode = (x) => isAlnumCode2(x) || x === 95;
787
- var isTokenCode = (x) => isWordCode(x) || x === 45;
788
- var isHexCode2 = (x) => x >= 48 && x <= 57 || x >= 65 && x <= 70 || x >= 97 && x <= 102;
789
- function stopPositions(s) {
790
- return /* @__PURE__ */ new Set([...proseNamedHits(s).map((h) => h.index), ...inviteSpans(s).map((h) => h.index)]);
791
- }
792
- var LINK_END = (c) => isWs(c) || c === ")" || c === "]" || c === ">" || c === '"' || c === "'";
777
+ // ../redact/link.ts
778
+ var AMP = 1;
779
+ var HASH = 2;
780
+ var SEMI = 4;
793
781
  function httpHeadAt(t, i) {
794
782
  if (t[i] !== "h" && t[i] !== "H") return 0;
795
783
  const head = t.slice(i, i + 8).toLowerCase();
796
784
  return head.startsWith("https://") ? 8 : head.startsWith("http://") ? 7 : 0;
797
785
  }
798
- function valueLimits(s, creds = urlCredentialSpans(s)) {
799
- const t = s.text;
800
- const ends = creds.filter((sp) => sp.end < s.n && (t[sp.end] === "&" || t[sp.end] === "#" || t[sp.end] === ";" || CJK_STOPS.includes(t[sp.end])));
801
- let k = 0;
802
- return (pos) => {
803
- while (k < ends.length && ends[k].end <= pos) k++;
804
- return k < ends.length && ends[k].index <= pos ? ends[k].end : -1;
805
- };
786
+ var ENCODED_HEAD = /https?(?::|%(?:25)?3a)(?:\/|%(?:25)?2f)(?:\/|%(?:25)?2f)/iy;
787
+ function encodedHeadAt(t, i) {
788
+ if (t[i] !== "h" && t[i] !== "H") return 0;
789
+ ENCODED_HEAD.lastIndex = i;
790
+ if (!ENCODED_HEAD.test(t)) return 0;
791
+ const len = ENCODED_HEAD.lastIndex - i;
792
+ return t.slice(i, i + len).includes("%") ? len : 0;
793
+ }
794
+ var SOCKS_HEADS = [...SOCKS_URI_SCHEMES].sort((x, y) => y.length - x.length).map((x) => `${x}://`);
795
+ function socksHeadAt(t, i) {
796
+ if (t[i] !== "s" && t[i] !== "S") return 0;
797
+ const head = t.slice(i, i + 10).toLowerCase();
798
+ for (const p of SOCKS_HEADS) if (head.startsWith(p)) return p.length;
799
+ return 0;
800
+ }
801
+ function hasSchemeSeparator(text) {
802
+ return text.includes("://");
806
803
  }
807
- function bracketSpan(t, i, end) {
808
- const stop = Math.min(end, i + IP_LITERAL_MAX + 2);
809
- for (let j = i + 1; j < stop; j++) {
810
- if (t[j] === "]") return { len: j - i + 1, scanned: j - i };
811
- if (t[j] === "[" || /\s/.test(t[j])) return { len: 0, scanned: j - i };
804
+ var ENCODED_UNIT = { "3A": ":", "2F": "/", "3F": "?", "23": "#", "40": "@", "5B": "[", "5D": "]", "28": "(", "29": ")" };
805
+ var ONCE = 1;
806
+ var TWICE = 2;
807
+ function headDepth(head) {
808
+ return (/%(?:3a|2f)/i.test(head) ? ONCE : 0) | (/%25(?:3a|2f)/i.test(head) ? TWICE : 0);
809
+ }
810
+ function unitAt(t, j, depth) {
811
+ if (depth && t[j] === "%") {
812
+ const hex = t.slice(j + 1, j + 3).toUpperCase();
813
+ if (depth & ONCE && ENCODED_UNIT[hex]) return { ch: ENCODED_UNIT[hex], len: 3, encodedChar: true };
814
+ if (depth & TWICE && hex === "25") {
815
+ const h2 = t.slice(j + 3, j + 5).toUpperCase();
816
+ if (ENCODED_UNIT[h2]) return { ch: ENCODED_UNIT[h2], len: 5, encodedChar: true };
817
+ }
812
818
  }
813
- return { len: 0, scanned: stop - i };
819
+ return { ch: t[j], len: 1, encodedChar: false };
814
820
  }
815
- var AUTHORITY_END = /* @__PURE__ */ new Set(["/", "?", "#"]);
816
- function spanHasStop(stops, from, to) {
817
- for (let k = from; k < to; k++) if (stops.has(k)) return true;
818
- return false;
821
+ function valueContextAt(s, start) {
822
+ const t = s.text;
823
+ if (start === 0 || t[start - 1] !== "=") return false;
824
+ let p = start - 1;
825
+ while (p > 0 && /[A-Za-z0-9_.%-]/.test(t[p - 1])) p--;
826
+ s.budget.tick(start - p + 1);
827
+ if (p === 0 || !"?&#;".includes(t[p - 1])) return false;
828
+ return paramNameEnd(t, p) === start - 1 && isUrlCredentialName(t.slice(p, start - 1));
829
+ }
830
+ function headAt(s, i, inherited) {
831
+ const t = s.text;
832
+ let kind = null;
833
+ let start = i;
834
+ let contentStart = 0;
835
+ const hl = httpHeadAt(t, i);
836
+ if (hl) {
837
+ kind = "http";
838
+ contentStart = i + hl;
839
+ } else {
840
+ const el = encodedHeadAt(t, i);
841
+ if (el) {
842
+ kind = "encoded";
843
+ contentStart = i + el;
844
+ } else if (t[i] === ":" && t[i + 1] === "/" && t[i + 2] === "/") {
845
+ const ps = proxySchemeStart(t, i);
846
+ s.budget.tick(9);
847
+ if (ps >= 0) {
848
+ kind = "proxy";
849
+ start = ps;
850
+ contentStart = i + 3;
851
+ }
852
+ }
853
+ }
854
+ if (!kind) return null;
855
+ const extra = inherited | (valueContextAt(s, start) ? AMP | HASH | SEMI : 0) | (kind === "encoded" ? AMP | HASH : 0);
856
+ const displayable = kind === "proxy" || placeholderAt(t, contentStart) === 0;
857
+ const depth = kind === "encoded" ? headDepth(t.slice(i, contentStart)) : 0;
858
+ return { start, kind, contentStart, extra, userinfo: null, scanEnd: contentStart, displayEnd: contentStart, displayable, depth };
859
+ }
860
+ var isNonAscii = (s, j) => s.raw.charCodeAt(j) > 127;
861
+ var isExtraEnd = (c, extra) => (extra & AMP) !== 0 && c === "&" || (extra & HASH) !== 0 && c === "#" || (extra & SEMI) !== 0 && c === ";";
862
+ var KI_WRAP = (c) => c === ")" || c === "]" || c === ">" || c === '"' || c === "'";
863
+ function locateUserinfo(s, from, encoded) {
864
+ const t = s.text;
865
+ const n = s.n;
866
+ let j = from;
867
+ let at = null;
868
+ let afterAtom = false;
869
+ while (j < n && !isWs(t[j])) {
870
+ const a = placeholderAt(t, j);
871
+ if (a) {
872
+ afterAtom = true;
873
+ j += a;
874
+ continue;
875
+ }
876
+ if (afterAtom && isNonAscii(s, j)) break;
877
+ if (encoded && (t[j] === "&" || t[j] === "#")) break;
878
+ const u = unitAt(t, j, encoded);
879
+ if (u.ch === "/" || u.ch === "?" || u.ch === "#") break;
880
+ if (u.ch === "@") at = [j, j + u.len];
881
+ j += u.len;
882
+ }
883
+ s.budget.tick(j - from + 1);
884
+ return at;
819
885
  }
820
- function urlSpans(s) {
886
+ function linkTable(s) {
887
+ if (s.links) return s.links;
821
888
  const t = s.text;
822
889
  const n = s.n;
823
890
  const out = [];
824
- const stops = stopPositions(s);
825
- const creds = urlCredentialSpans(s);
826
- let ci = 0;
827
- const inCredValue = (pos) => {
828
- while (ci < creds.length && creds[ci].end <= pos) ci++;
829
- return ci < creds.length && creds[ci].index <= pos;
891
+ const backoff = /* @__PURE__ */ new Map();
892
+ const open = (rec) => {
893
+ out.push(rec);
894
+ const encoded = rec.depth;
895
+ const at = locateUserinfo(s, rec.contentStart, encoded);
896
+ if (at) rec.userinfo = { index: rec.contentStart, end: at[0] };
897
+ return { rec, encoded, kiT62: !at && rec.kind === "proxy", j: at ? at[1] : rec.contentStart, paren: 0, bracket: 0 };
898
+ };
899
+ const close = (f) => {
900
+ const rec = f.rec;
901
+ rec.scanEnd = f.j;
902
+ if (f.kiT62) {
903
+ let b = backoff.get(f.j);
904
+ if (b === void 0) {
905
+ b = f.j;
906
+ while (b > 0 && KI_WRAP(t[b - 1]) && placeholderEndingAt(t, b) === 0) b--;
907
+ s.budget.tick(f.j - b + 1);
908
+ backoff.set(f.j, b);
909
+ }
910
+ rec.displayEnd = Math.max(b, rec.contentStart);
911
+ } else rec.displayEnd = f.j;
830
912
  };
831
- const limitAt = valueLimits(s, creds);
832
913
  let i = 0;
833
914
  while (i < n) {
834
915
  if (!s.budget.tick()) {
835
916
  s.unfinished = Math.min(s.unfinished, i);
836
917
  break;
837
918
  }
838
- const len = httpHeadAt(t, i);
839
- if (!len) {
919
+ const top = headAt(s, i, 0);
920
+ if (!top) {
840
921
  i++;
841
922
  continue;
842
923
  }
843
- if (placeholderAt(t, i + len) > 0) {
844
- i++;
845
- continue;
846
- }
847
- const limit = limitAt(i);
848
- const end = limit >= 0 ? limit : n;
849
- let j = i + len;
850
- let lastAt = -1;
851
- let k = i + len;
852
- for (; k < end && !stops.has(k) && !AUTHORITY_END.has(t[k]); k++) {
853
- if (t[k] === "[") {
854
- const b = bracketSpan(t, k, end);
855
- if (!b.len) {
856
- s.budget.tick(b.scanned);
857
- break;
858
- }
859
- k += b.len - 1;
860
- continue;
924
+ const stack = [open(top)];
925
+ while (stack.length) {
926
+ const f = stack[stack.length - 1];
927
+ const j = f.j;
928
+ if (!s.budget.tick()) {
929
+ s.unfinished = Math.min(s.unfinished, j);
930
+ f.j = n;
861
931
  }
862
- if (LINK_END(t[k])) break;
863
- if (t[k] === "@") lastAt = k;
864
- }
865
- s.budget.tick(k - (i + len));
866
- const ipl = lastAt >= 0 ? 0 : ipLiteralAt(s, j);
867
- if (ipl && j + ipl <= end && !spanHasStop(stops, j, j + ipl)) j += ipl;
868
- let inAuthority = true;
869
- while (j < end && !LINK_END(t[j]) && !stops.has(j)) {
870
- if (AUTHORITY_END.has(t[j])) inAuthority = false;
871
- if (t[j] === "[") {
872
- const host = inAuthority && j - 1 === lastAt ? ipLiteralAt(s, j) : 0;
873
- if (!host || j + host > end || spanHasStop(stops, j, j + host)) break;
874
- j += host;
932
+ const atom = j < n ? placeholderAt(t, j) : 0;
933
+ if (atom && f.rec.kind === "proxy") {
934
+ f.j = j + atom;
875
935
  continue;
876
936
  }
877
- j++;
937
+ let ended = j >= n || isWs(t[j]) || isNonAscii(s, j) || isExtraEnd(t[j], f.rec.extra);
938
+ if (!ended && !f.kiT62) {
939
+ const c = t[j];
940
+ if (c === '"' || c === ">" || !f.encoded && c === "'") ended = true;
941
+ else if (atom) ended = true;
942
+ }
943
+ if (!ended) {
944
+ const child = headAt(s, j, f.rec.extra);
945
+ if (child && child.start >= f.rec.contentStart) {
946
+ stack.push(open(child));
947
+ continue;
948
+ }
949
+ const u = unitAt(t, j, f.encoded);
950
+ if (u.ch === "(") f.paren++;
951
+ else if (u.ch === "[") f.bracket++;
952
+ else if (u.ch === ")" || u.ch === "]") {
953
+ const isParen = u.ch === ")";
954
+ const count = isParen ? f.paren : f.bracket;
955
+ if (count > 0) {
956
+ if (isParen) f.paren--;
957
+ else f.bracket--;
958
+ } else if (!u.encodedChar && !f.kiT62) ended = true;
959
+ }
960
+ if (!ended) {
961
+ f.j = j + u.len;
962
+ continue;
963
+ }
964
+ }
965
+ f.j = Math.min(f.j, n);
966
+ close(f);
967
+ stack.pop();
968
+ const parent = stack[stack.length - 1];
969
+ if (parent) {
970
+ parent.j = Math.max(parent.j, f.rec.scanEnd);
971
+ parent.paren += f.paren;
972
+ parent.bracket += f.bracket;
973
+ }
878
974
  }
879
- s.budget.tick(j - i);
880
- if (j > i + len) {
881
- out.push({ index: i, end: j });
882
- i = j;
883
- } else i++;
975
+ i = Math.max(top.scanEnd, i + 1);
884
976
  }
977
+ out.sort((a, b) => a.start - b.start);
978
+ s.links = out;
885
979
  return out;
886
980
  }
981
+ function linkStartingAt(s, pos) {
982
+ const links = s.links;
983
+ if (!links || links.length === 0) return null;
984
+ let lo = 0, hi = links.length - 1;
985
+ while (lo <= hi) {
986
+ const mid = lo + hi >> 1;
987
+ if (links[mid].start < pos) lo = mid + 1;
988
+ else hi = mid - 1;
989
+ }
990
+ s.budget.tick(Math.ceil(Math.log2(links.length + 1)) + 1);
991
+ const r = links[lo];
992
+ return r && r.start === pos && r.displayEnd > r.contentStart ? r : null;
993
+ }
994
+ function userinfoHasContent(s, from, end, encoded) {
995
+ const t = s.text;
996
+ s.budget.tick(end - from);
997
+ for (let k = from; k < end; ) {
998
+ const a = placeholderAt(t, k);
999
+ if (a > 0 && k + a <= end) {
1000
+ k += a;
1001
+ continue;
1002
+ }
1003
+ const u = unitAt(t, k, encoded);
1004
+ if (u.ch !== ":") return true;
1005
+ k += u.len;
1006
+ }
1007
+ return false;
1008
+ }
1009
+
1010
+ // ../redact/index.ts
1011
+ var isAlnumCode2 = (x) => x >= 48 && x <= 57 || x >= 65 && x <= 90 || x >= 97 && x <= 122;
1012
+ var isWordCode = (x) => isAlnumCode2(x) || x === 95;
1013
+ var isTokenCode = (x) => isWordCode(x) || x === 45;
1014
+ var isHexCode2 = (x) => x >= 48 && x <= 57 || x >= 65 && x <= 70 || x >= 97 && x <= 102;
1015
+ function stopPositions(s) {
1016
+ return /* @__PURE__ */ new Set([...proseNamedHits(s).map((h) => h.index), ...inviteSpans(s).map((h) => h.index)]);
1017
+ }
1018
+ var linkedCreds = /* @__PURE__ */ new WeakMap();
1019
+ var isSeparatorEnd = (s, pos) => pos < s.n && (s.text[pos] === "&" || s.text[pos] === "#" || s.text[pos] === ";" || CJK_STOPS.includes(s.text[pos]));
1020
+ function credSpans(s) {
1021
+ const hit = linkedCreds.get(s);
1022
+ if (hit) return hit;
1023
+ const creds = urlCredentialSpans(s).map((sp) => ({ index: sp.index, end: sp.end }));
1024
+ const recs = [];
1025
+ for (const r of s.links) recs.push({ start: r.start, end: r.displayEnd, userinfo: r.userinfo, socks: false });
1026
+ for (const k of socksScan(s)) recs.push({ start: k.start, end: k.authEnd, userinfo: k.userinfo, socks: true });
1027
+ recs.sort((a, b) => a.start - b.start);
1028
+ let ci = 0;
1029
+ for (const r of recs) {
1030
+ s.budget.tick();
1031
+ while (ci < creds.length && creds[ci].end <= r.start) ci++;
1032
+ const c = creds[ci];
1033
+ if (!c || c.index > r.start || r.end <= c.end) continue;
1034
+ const u = r.userinfo;
1035
+ const crossesUserinfo = u !== null && u.index <= c.end && c.end < u.end;
1036
+ if (crossesUserinfo || !r.socks && !isSeparatorEnd(s, c.end)) c.end = contentDisplayEnd(s, r.end);
1037
+ }
1038
+ const out = mergeSpans(creds, false);
1039
+ linkedCreds.set(s, out);
1040
+ return out;
1041
+ }
1042
+ function valueLimits(s) {
1043
+ const creds = credSpans(s);
1044
+ let k = 0;
1045
+ return (pos) => {
1046
+ while (k < creds.length && creds[k].end <= pos) k++;
1047
+ return k < creds.length && creds[k].index <= pos ? creds[k].end : -1;
1048
+ };
1049
+ }
1050
+ function mergeSpans(spans, touching) {
1051
+ const out = [];
1052
+ for (const sp of [...spans].sort((a, b) => a.index - b.index || b.end - a.end)) {
1053
+ const last = out[out.length - 1];
1054
+ if (last && (sp.index < last.end || touching && sp.index === last.end)) last.end = Math.max(last.end, sp.end);
1055
+ else if (sp.end > sp.index) out.push({ index: sp.index, end: sp.end });
1056
+ }
1057
+ return out;
1058
+ }
1059
+ function urlSpans(s) {
1060
+ const limitAt = valueLimits(s);
1061
+ const out = [];
1062
+ for (const r of s.links) {
1063
+ if (r.kind === "proxy" || !r.displayable || r.displayEnd <= r.contentStart) continue;
1064
+ s.budget.tick();
1065
+ const lim = limitAt(r.start);
1066
+ out.push({ index: r.start, end: lim >= 0 ? Math.min(r.displayEnd, lim) : r.displayEnd });
1067
+ }
1068
+ return mergeSpans(out, true);
1069
+ }
887
1070
  function userinfoSpans(s) {
1071
+ const out = [];
1072
+ for (const r of s.links) {
1073
+ const u = r.userinfo;
1074
+ if (u && u.end > u.index && userinfoHasContent(s, u.index, u.end, r.depth)) out.push({ index: u.index, end: u.end });
1075
+ }
1076
+ for (const k of socksScan(s)) if (k.userinfo && userinfoHasContent(s, k.userinfo.index, k.userinfo.end, 0)) out.push({ ...k.userinfo });
1077
+ return mergeSpans(out, false);
1078
+ }
1079
+ var socksScans = /* @__PURE__ */ new WeakMap();
1080
+ function socksScan(s) {
1081
+ const hit = socksScans.get(s);
1082
+ if (hit) return hit;
888
1083
  const t = s.text;
889
1084
  const n = s.n;
890
1085
  const out = [];
@@ -894,7 +1089,7 @@ function userinfoSpans(s) {
894
1089
  s.unfinished = Math.min(s.unfinished, i);
895
1090
  break;
896
1091
  }
897
- const len = httpHeadAt(t, i);
1092
+ const len = socksHeadAt(t, i);
898
1093
  if (!len) {
899
1094
  i++;
900
1095
  continue;
@@ -902,31 +1097,20 @@ function userinfoSpans(s) {
902
1097
  const from = i + len;
903
1098
  let j = from;
904
1099
  let at = -1;
905
- while (j < n && !isWs(t[j]) && t[j] !== "/" && t[j] !== "?" && t[j] !== "#") {
1100
+ const lead = placeholderAt(t, j);
1101
+ if (lead) j += lead;
1102
+ while (j < n && !isWs(t[j]) && t[j] !== "?" && t[j] !== "#" && placeholderAt(t, j) === 0 && !(lead && s.raw.charCodeAt(j) > 127)) {
906
1103
  if (t[j] === "@") at = j;
907
1104
  j++;
908
1105
  }
909
1106
  s.budget.tick(j - i);
910
- if (at > from && userinfoHasContent(s, from, at)) out.push({ index: from, end: at });
911
- i = from;
1107
+ out.push({ start: i, userinfo: at > from ? { index: from, end: at } : null, authEnd: j });
1108
+ i = j > from ? j : from;
912
1109
  }
1110
+ socksScans.set(s, out);
913
1111
  return out;
914
1112
  }
915
- function userinfoHasContent(s, from, end) {
916
- const t = s.text;
917
- s.budget.tick(end - from);
918
- for (let k = from; k < end; ) {
919
- const a = placeholderAt(t, k);
920
- if (a > 0 && k + a <= end) {
921
- k += a;
922
- continue;
923
- }
924
- if (t[k] !== ":") return true;
925
- k++;
926
- }
927
- return false;
928
- }
929
- var SUB_END = (c) => isWs(c) || c === ")" || c === "]" || c === ">" || c === '"' || c === "'";
1113
+ var KEYWORD_VALUE_END = (c) => isWs(c) || c === ")" || c === "]" || c === ">" || c === '"' || c === "'";
930
1114
  function subscriptionKeywordEnd(t, i) {
931
1115
  if (i > 0 && isWordCode(t.charCodeAt(i - 1))) return -1;
932
1116
  const c = t[i];
@@ -948,84 +1132,58 @@ function subscriptionSpans(s) {
948
1132
  const t = s.text;
949
1133
  const n = s.n;
950
1134
  const cands = [];
1135
+ const proxies = [];
1136
+ const proxyLimit = valueLimits(s);
1137
+ for (const r of s.links) {
1138
+ if (r.kind !== "proxy" || r.displayEnd <= r.contentStart) continue;
1139
+ s.budget.tick();
1140
+ const lim = proxyLimit(r.start);
1141
+ proxies.push({ index: r.start, end: lim >= 0 ? Math.min(r.displayEnd, lim) : r.displayEnd });
1142
+ }
1143
+ for (const sp of proxies) cands.push(sp);
951
1144
  const stops = stopPositions(s);
952
1145
  const limitAt = valueLimits(s);
953
1146
  const endAt = (pos) => {
954
1147
  const l = limitAt(pos);
955
1148
  return l >= 0 ? l : n;
956
1149
  };
957
- const extend = (from, end) => {
958
- let j = from;
959
- while (j < end && !SUB_END(t[j]) && !stops.has(j)) j++;
960
- s.budget.tick(j - from + 1);
961
- return j;
962
- };
963
- const extendNoStops = (from, end) => {
964
- let j = from;
965
- while (j < end && !SUB_END(t[j]) && !CJK_STOPS.includes(t[j])) j++;
966
- s.budget.tick(j - from + 1);
967
- return j;
968
- };
969
- const authorityAt = (from, end) => {
970
- let j = from, at = -1;
971
- while (j < end && !isWs(t[j]) && t[j] !== "/" && t[j] !== "?" && t[j] !== "#") {
972
- if (t[j] === "@") at = j;
973
- j++;
974
- }
975
- s.budget.tick(j - from + 1);
976
- return at;
977
- };
978
- const extendNoAt = (from, end) => {
979
- let j = from;
980
- while (j < end && !isWs(t[j]) && !CJK_STOPS.includes(t[j])) j++;
981
- const scanned = j - from;
982
- while (j > from && SUB_END(t[j - 1])) j--;
983
- s.budget.tick(scanned * 2 + 1);
984
- return j;
985
- };
986
- const proxyExtend = (from, end) => {
987
- const at = authorityAt(from, end);
988
- return at < 0 ? extendNoAt(from, end) : extendNoStops(at + 1, end);
989
- };
1150
+ let p = 0;
990
1151
  let i = 0;
991
1152
  while (i < n) {
992
1153
  if (!s.budget.tick()) {
993
1154
  s.unfinished = Math.min(s.unfinished, i);
994
1155
  break;
995
1156
  }
996
- if (t[i] === ":" && t[i + 1] === "/" && t[i + 2] === "/") {
997
- const start = proxySchemeStart(t, i);
998
- s.budget.tick(9);
999
- if (start >= 0) {
1000
- const j = proxyExtend(i + 3, endAt(i));
1001
- if (j > i + 3) {
1002
- cands.push({ index: start, end: j });
1003
- i = j;
1004
- continue;
1005
- }
1006
- }
1007
- i++;
1157
+ while (p < proxies.length && proxies[p].end <= i) p++;
1158
+ if (p < proxies.length && proxies[p].index <= i) {
1159
+ i = proxies[p].end;
1008
1160
  continue;
1009
1161
  }
1010
1162
  const kw = subscriptionKeywordEnd(t, i);
1011
- if (kw >= 0) {
1012
- const j = extend(kw, endAt(i));
1013
- cands.push({ index: i, end: j });
1014
- i = j;
1163
+ if (kw < 0) {
1164
+ i++;
1015
1165
  continue;
1016
1166
  }
1017
- i++;
1167
+ const end = endAt(i);
1168
+ let q = kw;
1169
+ while (q < end && isInlineWs(t[q])) q++;
1170
+ if (q < end && (t[q] === "=" || t[q] === ":" || t[q] === "\uFF1A")) {
1171
+ q++;
1172
+ while (q < end && isInlineWs(t[q])) q++;
1173
+ }
1174
+ s.budget.tick(q - kw + 1);
1175
+ const link = linkStartingAt(s, q);
1176
+ let j;
1177
+ if (link) j = Math.min(link.displayEnd, end);
1178
+ else {
1179
+ j = kw;
1180
+ while (j < end && !KEYWORD_VALUE_END(t[j]) && !stops.has(j)) j++;
1181
+ s.budget.tick(j - kw + 1);
1182
+ }
1183
+ cands.push({ index: i, end: j });
1184
+ i = Math.max(j, i + 1);
1018
1185
  }
1019
- return disjoint(cands);
1020
- }
1021
- function disjoint(spans) {
1022
- const out = [];
1023
- let last = -1;
1024
- for (const sp of [...spans].sort((a, b) => a.index - b.index)) if (sp.index >= last && sp.end > sp.index) {
1025
- out.push(sp);
1026
- last = sp.end;
1027
- }
1028
- return out;
1186
+ return mergeSpans(cands, false);
1029
1187
  }
1030
1188
  var REGISTERED_ERROR_CODE = /^(?:INSTALL_FAILED|INSTALL_PARSE_FAILED|ERR)(?:_[A-Z]{2,})+$/;
1031
1189
  function tokenSpans(s) {
@@ -1157,23 +1315,35 @@ function inviteSpans(s) {
1157
1315
  s.invites = out;
1158
1316
  return out;
1159
1317
  }
1318
+ function emailSpans(s) {
1319
+ const seps = /* @__PURE__ */ new Set();
1320
+ for (const r of s.links) if (r.userinfo) seps.add(r.userinfo.end);
1321
+ for (const k of socksScan(s)) if (k.userinfo) seps.add(k.userinfo.end);
1322
+ if (seps.size === 0) return EMAIL(s);
1323
+ return EMAIL(s).filter((sp) => !seps.has(s.text.indexOf("@", sp.index)));
1324
+ }
1160
1325
  var RULES = [
1161
1326
  ["subscription", subscriptionSpans],
1162
1327
  ["url", urlSpans],
1163
- ["email", EMAIL],
1328
+ ["email", emailSpans],
1164
1329
  ["uuid", UUID],
1165
1330
  ["ipv6", IPV6],
1166
1331
  ["ipv4", IPV4],
1167
1332
  ["token", tokenSpans],
1168
- ["urlcred", urlCredentialSpans],
1333
+ ["urlcred", credSpans],
1169
1334
  ["userinfo", userinfoSpans],
1170
1335
  ["invite", inviteSpans],
1171
1336
  ["config", configRegions]
1172
1337
  ];
1173
1338
  var RULE_NAMES = RULES.map(([name]) => name);
1174
1339
  var TOKEN_ORDER = RULE_NAMES.indexOf("token");
1340
+ function foldFullwidth(text) {
1341
+ if (!/[\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF1D]/.test(text)) return text;
1342
+ return text.replace(/[\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF1D]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 65248));
1343
+ }
1175
1344
  function sensitiveMatches(text, opts = {}) {
1176
- const s = newScan(text, opts.budget);
1345
+ const s = newScan(foldFullwidth(text), opts.budget, text);
1346
+ linkTable(s);
1177
1347
  const out = [];
1178
1348
  let exhaustedAt = -1;
1179
1349
  for (let order = 0; order < RULES.length; order++) {
@@ -1229,7 +1399,7 @@ function redactSensitive(text, mask, opts = {}) {
1229
1399
  var SECRET_RULES = /* @__PURE__ */ new Set(["token", "urlcred", "userinfo", "subscription", "ipv4", "ipv6", "invite", "config", "uuid", "email"]);
1230
1400
  var SHORT_WORD_KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)+$/;
1231
1401
  function isKnownFalsePositive(m) {
1232
- if (m.rule === "subscription") return /^subscri/i.test(m.text) && !m.text.includes("://");
1402
+ if (m.rule === "subscription") return /^subscri/i.test(m.text) && !hasSchemeSeparator(m.text);
1233
1403
  if (m.rule === "token") return SHORT_WORD_KEBAB.test(m.text) && m.text.split("-").every((p) => p.length <= 16);
1234
1404
  return false;
1235
1405
  }
@@ -1252,45 +1422,190 @@ var USAGE_RULES = [
1252
1422
  "",
1253
1423
  "1. \u8C03 `list_entries`\uFF0C`query` \u7528\u7528\u6237\u539F\u8BDD\uFF1B\u539F\u8BDD\u8D85\u8FC7 200 \u5B57\u65F6\uFF0C\u622A\u53D6\u6700\u80FD\u4EE3\u8868\u95EE\u9898\u7684\u4E00\u6BB5\u2014\u2014\u6709\u62A5\u9519\u539F\u6587\u5C31\u53D6\u62A5\u9519\u539F\u6587\u2014\u2014\u4E0D\u6539\u5199\u63AA\u8F9E\u3002\u8C03\u7528\u672C skill \u65F6\u4F20\u5165\u7684\u53C2\u6570\u662F\u4F60\u81EA\u5DF1\u7684\u8F6C\u8FF0\uFF0C\u4E0D\u80FD\u4EE3\u66FF\u7528\u6237\u539F\u8BDD\u3002\u7528\u6237\u9644\u4E86\u622A\u56FE\u3001\u4F60\u770B\u5F97\u5230\u65F6\uFF0C\u628A\u56FE\u91CC\u7684\u62A5\u9519\u539F\u6587\u9010\u5B57\u3001\u8FDE\u540C\u5BA2\u6237\u7AEF\u540D\u79F0\u63A5\u5728\u539F\u8BDD\u540E\u9762\u4E00\u8D77\u4F5C\u4E3A `query`\uFF1B\u8D85\u8FC7 200 \u5B57\u65F6\u4F18\u5148\u4FDD\u7559\u62A5\u9519\u539F\u6587\u3002",
1254
1424
  `2. \u5148\u5224\u65AD\u95EE\u9898\u662F\u5426\u5C5E\u4E8E\u7B2C 4 \u6B65\u300C\u76F8\u90BB\u4F46\u4E0D\u505A\u300D\u7684\u90A3\u51E0\u7C7B\u2014\u2014\u770B\u95EE\u9898\u8981\u7684\u7B54\u6848\u5728\u54EA\u4E00\u4FA7\uFF0C\u4E0D\u770B\u95EE\u9898\u91CC\u63D0\u5230\u4E86\u4EC0\u4E48\uFF1A\u8981\u7684\u662F\u5BA2\u6237\u7AEF\u600E\u4E48\u7528\u3001\u600E\u4E48\u8BBE\u7F6E\u3001\u600E\u4E48\u6392\u9519\uFF0C\u6216\u5BA2\u6237\u7AEF\u600E\u4E48\u53D6\u7528\u3001\u663E\u793A\u670D\u52A1\u65B9\u7ED9\u7684\u8BA2\u9605\u3001\u6D41\u91CF\u4FE1\u606F\u4E0E\u8282\u70B9\u51ED\u636E\uFF0C\u662F\u77E5\u8BC6\u5E93\u7684\u4E8B\uFF0C\u8D77\u56E0\u5728\u670D\u52A1\u5546\u6216\u670D\u52A1\u7AEF\u4E5F\u4E00\u6837\uFF1B\u8981\u7684\u662F\u670D\u52A1\u5546\uFF08\u542B\u5BBD\u5E26\u8FD0\u8425\u5546\uFF09\u81EA\u5DF1\u7684\u89C4\u5219\u6216\u51B3\u5B9A\uFF08\u5982\u5957\u9910\u3001\u8BA1\u8D39\u3001\u8D26\u53F7\u4E0E\u552E\u540E\uFF09\uFF0C\u6216\u53EA\u95EE\u5B83\u7684\u7EBF\u8DEF\u3001\u7F51\u7EDC\u662F\u4E0D\u662F\u51FA\u4E86\u6545\u969C\uFF0C\u6216\u670D\u52A1\u7AEF\u4E0E\u4E2D\u8F6C\u7EBF\u8DEF\u65B9\u6848\u672C\u8EAB\uFF08\u600E\u4E48\u642D\u5EFA\u3001\u6548\u679C\u4E0E\u53D6\u820D\u3001\u9009\u54EA\u79CD\uFF09\uFF0C\u5C31\u7B97\u76F8\u90BB\uFF0C\u95EE\u6CD5\u518D\u6280\u672F\u3001\u63D0\u5230\u7684\u5BA2\u6237\u7AEF\u6216\u73B0\u8C61\u518D\u5177\u4F53\u4E5F\u4E00\u6837\uFF1B\u5E26\u7740\u5BA2\u6237\u7AEF\u91CC\u7684\u4F7F\u7528\u5F02\u5E38\u95EE\u662F\u4E0D\u662F\u670D\u52A1\u5546\u6216\u7F51\u7EDC\u90A3\u8FB9\u51FA\u4E86\u95EE\u9898\u7684\uFF0C\u8981\u7684\u662F\u6392\u9519\uFF0C\u5F52\u77E5\u8BC6\u5E93\u3002\u662F\u5C31\u4E0D\u6311\u6761\u76EE\u3001\u4E0D\u8BFB\u6B63\u6587\uFF0C\u76F4\u63A5\u56DE\u300C${OUT_OF_SCOPE_REPLY}\u300D\uFF0C\u9664\u8FD9\u4E00\u53E5\u5916\u4E0D\u8F93\u51FA\u4EFB\u4F55\u6587\u5B57\uFF08\u7B2C 1 \u6B65\u7684 \`list_entries\` \u7167\u8C03\uFF09\u3002\u95EE\u9898\u542B\u7CCA\u2014\u2014\u4E0D\u8FFD\u95EE\u5C31\u4E0D\u77E5\u9053\u5177\u4F53\u95EE\u7684\u662F\u4EC0\u4E48\uFF0C\u4F8B\u5982\u53EA\u8BF4\u300C\u7528\u4E0D\u4E86\u4E86\u300D\uFF0C\u6216\u8BF4\u4E86\u300C\u6709\u4E00\u5904\u7A7A\u767D\u300D\u5374\u6CA1\u8BF4\u662F\u54EA\u4E00\u5904\uFF0C\u6216\u53EA\u8BF4\u62A5\u9519\u53CD\u590D\u5F39\u51FA\u5374\u6CA1\u7ED9\u51FA\u539F\u6587\u2014\u2014\u65F6\uFF0C\u4E0D\u6311\u6761\u76EE\u3001\u4E0D\u8BFB\u6B63\u6587\uFF0C\u53EA\u7528\u4E00\u53E5\u8BDD\u8FFD\u95EE\u7528\u7684\u662F\u4EC0\u4E48\u5BA2\u6237\u7AEF\u3001\u9047\u5230\u4E86\u4EC0\u4E48\u73B0\u8C61\u3001\u662F\u54EA\u4E00\u5904\u3002\u7528\u6237\u53EA\u8BF4\u9644\u4E86\u622A\u56FE\u3001\u6CA1\u5199\u51FA\u73B0\u8C61\u6216\u62A5\u9519\u539F\u6587\uFF0C\u800C\u4F60\u770B\u4E0D\u5230\u56FE\u65F6\uFF0C\u4E5F\u6309\u542B\u7CCA\u5904\u7406\uFF1A\u4E0D\u6311\u6761\u76EE\u3001\u4E0D\u8BFB\u6B63\u6587\uFF0C\u53EA\u7528\u4E00\u53E5\u8BDD\u8BF7\u7528\u6237\u628A\u56FE\u91CC\u7684\u62A5\u9519\u6587\u5B57\u590D\u5236\u8FC7\u6765\uFF0C\u4E0D\u95EE\u522B\u7684\uFF1B\u95EE\u9898\u672C\u8EAB\u5DF2\u5199\u6E05\u73B0\u8C61\u6216\u62A5\u9519\u7684\uFF0C\u770B\u4E0D\u5230\u56FE\u4E5F\u7167\u5E38\u5F80\u4E0B\u8D70\uFF0C\u4E0D\u7B97\u542B\u7CCA\u3002\u770B\u5F97\u5230\u622A\u56FE\u65F6\uFF0C\u6309\u56FE\u91CC\u7684\u73B0\u8C61\u5904\u7406\uFF0C\u4E0D\u7B97\u542B\u7CCA\uFF1B\u770B\u5F97\u51FA\u4E0E\u4EE3\u7406\u5BA2\u6237\u7AEF\u6709\u5173\u65F6\u7167\u5E38\u5148\u8C03 \`list_entries\`\uFF0C\u770B\u4E0D\u51FA\u65F6\u76F4\u63A5\u8FFD\u95EE\u3002\u5426\u5219\u4ECE\u76EE\u5F55\u6311 1\u20133 \u6761\u53EF\u80FD\u76F8\u5173\u7684\u6761\u76EE\uFF1B\u4E00\u6761\u90FD\u4E0D\u6CBE\u8FB9\u5C31\u8DF3\u5230\u7B2C 4 \u6B65\u3002`,
1255
- "3. \u7528 `get_entry` \u9010\u6761\u8BFB\u6B63\u6587\uFF1B\u8FD4\u56DE\u300C\u6761\u76EE\u4E0D\u5B58\u5728\u300D\u65F6\uFF0C\u91CD\u65B0\u8C03\u4E00\u6B21 `list_entries` \u53D6\u5F53\u524D\u76EE\u5F55\uFF0C\u4ECD\u6CA1\u6709\u5C31\u6309\u7B2C 4 \u6B65\u300C\u6CA1\u6709\u6761\u76EE\u80FD\u56DE\u7B54\u300D\u5904\u7406\u3002",
1256
- "4. \u56DE\u7B54\uFF08\u56DE\u7B54\u6B63\u6587\u4E0D\u5199\u6761\u76EE id\uFF0C\u4F9D\u636E\u653E\u5728\u672B\u5C3E\u7684\u6761\u76EE\u9875\u9762\u94FE\u63A5\u91CC\uFF1B\u5F15\u7528\u6761\u76EE\u65F6\uFF0C\u4F5C\u4E3A\u4F9D\u636E\u7684\u53EA\u80FD\u662F\u8BFB\u8FC7\u6B63\u6587\u7684\u6761\u76EE\uFF1B\u6761\u76EE\u6B63\u6587\u91CC\u63D0\u5230\u7684\u5176\u4ED6\u6761\u76EE\u53EF\u4EE5\u70B9\u540D\u4F9B\u7528\u6237\u5EF6\u4F38\u9605\u8BFB\uFF0C\u4F46\u4E0D\u5F53\u4F5C\u4F9D\u636E\uFF1B\u76EE\u5F55\u91CC\u770B\u5230\u4F46\u6CA1\u8BFB\u8FC7\u6B63\u6587\u7684\u6761\u76EE\u4E0D\u70B9\u540D\u3002\u8BFB\u8FC7\u6B63\u6587\u540E\u8FD8\u9700\u8981\u8FFD\u95EE\u7684\uFF0C\u4E5F\u5148\u6309\u4E0B\u9762\u7684\u4E09\u79CD\u56DE\u7B54\u4E4B\u4E00\u4F5C\u7B54\uFF0C\u518D\u8FFD\u95EE\uFF09\u3002\u56DE\u7B54\u8981\u77ED\uFF0C\u7EC6\u8282\u7B49\u7528\u6237\u8FFD\u95EE\u518D\u7ED9\u3002\u7B2C\u4E00\u6B21\u56DE\u7B54\u8981\u80FD\u4E00\u773C\u770B\u5B8C\uFF1A\u4E0D\u8D85\u8FC7 8 \u884C\u3001300 \u5B57\uFF08\u672B\u5C3E\u7684\u6761\u76EE\u94FE\u63A5\u4E0D\u7B97\uFF09\uFF0C\u6BCF\u884C\u53EA\u5199\u4E00\u53E5\u8BDD\uFF1B\u653E\u4E0D\u4E0B\u7684\u7559\u7ED9\u8FFD\u95EE\u3002\u5199\u6CD5\uFF1A\u7528\u81EA\u7136\u7684\u51E0\u6BB5\u8BDD\uFF0C\u4E0D\u52A0\u300C\u7ED3\u8BBA\u300D\u300C\u786E\u8BA4\u300D\u300C\u4F9D\u636E\u300D\u8FD9\u7C7B\u5C0F\u6807\u9898\u6216\u6807\u7B7E\uFF0C\u4E0D\u7528\u8868\u683C\u3001\u5D4C\u5957\u5217\u8868\u548C\u7C97\u4F53\uFF1B\u4E00\u53E5\u53EA\u8BF4\u4E00\u4EF6\u4E8B\uFF1B\u7528\u7948\u4F7F\u53E5\uFF1B\u540C\u4E00\u4E2A\u4E1C\u897F\u59CB\u7EC8\u7528\u540C\u4E00\u4E2A\u8BCD\uFF1B\u4E0D\u7528\u300C\u5927\u6982\u300D\u300C\u4E00\u4E9B\u300D\u8FD9\u7C7B\u542B\u7CCA\u7684\u8BCD\uFF1B\u6309\u94AE\u540D\u3001\u83DC\u5355\u540D\u3001\u62A5\u9519\u539F\u6587\u548C\u547D\u4EE4\u7167\u539F\u6837\u5199\uFF1B\u4E0D\u4E3B\u52A8\u8BB2\u539F\u7406\uFF0C\u7528\u6237\u95EE\u4E3A\u4EC0\u4E48\u518D\u8BB2\u3002\u7528\u4E86\u6761\u76EE\u5185\u5BB9\u7684\u56DE\u7B54\uFF08\u80FD\u5B8C\u6574\u56DE\u7B54\u3001\u53EA\u80FD\u90E8\u5206\u56DE\u7B54\uFF09\uFF0C\u77E5\u9053\u672C\u77E5\u8BC6\u5E93\u4E3B\u7AD9\u5730\u5740\u65F6\uFF08\u89C1\u672C skill \u7684\u300C\u56DE\u9000\u300D\u4E00\u8282\uFF09\uFF0C\u5FC5\u987B\u5728\u672B\u5C3E\u5355\u72EC\u4E00\u884C\u7ED9\u51FA\u7528\u5230\u7684\u6761\u76EE\u9875\u9762\u94FE\u63A5 `<\u4E3B\u7AD9>/<type>/<id>/`\uFF0C`<type>` \u53D6\u76EE\u5F55\u91CC\u7684\u6761\u76EE\u7C7B\u578B\uFF1B\u4E0D\u77E5\u9053\u5C31\u4E0D\u7ED9\u3002\u7528\u6237\u8FFD\u95EE\u7EC6\u8282\u65F6\uFF0C\u6309\u6761\u76EE\u7ED9\u51FA\u5B8C\u6574\u6B65\u9AA4\uFF0C\u4E0D\u53D7\u4E0B\u9762\u7684\u957F\u5EA6\u9650\u5236\uFF1A",
1425
+ "3. \u7528 `get_entry` \u9010\u6761\u8BFB\u6B63\u6587\uFF1B\u8FD4\u56DE\u91CC\u6CE8\u660E\u6761\u76EE\u5206\u4E86\u51E0\u6BB5\u65F6\uFF0C\u6309\u6BB5\u6807\u9898\u7528 `part` \u53C2\u6570\u518D\u8BFB\u4E0E\u95EE\u9898\u6709\u5173\u7684\u6BB5\u3002\u8FD4\u56DE\u300C\u6761\u76EE\u4E0D\u5B58\u5728\u300D\u65F6\uFF0C\u91CD\u65B0\u8C03\u4E00\u6B21 `list_entries` \u53D6\u5F53\u524D\u76EE\u5F55\uFF0C\u4ECD\u6CA1\u6709\u5C31\u6309\u7B2C 4 \u6B65\u300C\u6CA1\u6709\u6761\u76EE\u80FD\u56DE\u7B54\u300D\u5904\u7406\u3002",
1426
+ "4. \u56DE\u7B54\uFF08\u56DE\u7B54\u6B63\u6587\u4E0D\u5199\u6761\u76EE id\uFF0C\u4F9D\u636E\u653E\u5728\u672B\u5C3E\u7684\u6761\u76EE\u9875\u9762\u94FE\u63A5\u91CC\uFF1B\u5F15\u7528\u6761\u76EE\u65F6\uFF0C\u4F5C\u4E3A\u4F9D\u636E\u7684\u53EA\u80FD\u662F\u8BFB\u8FC7\u6B63\u6587\u7684\u6761\u76EE\uFF1B\u6761\u76EE\u6B63\u6587\u91CC\u63D0\u5230\u7684\u5176\u4ED6\u6761\u76EE\u53EF\u4EE5\u70B9\u540D\u4F9B\u7528\u6237\u5EF6\u4F38\u9605\u8BFB\uFF0C\u4F46\u4E0D\u5F53\u4F5C\u4F9D\u636E\uFF1B\u76EE\u5F55\u91CC\u770B\u5230\u4F46\u6CA1\u8BFB\u8FC7\u6B63\u6587\u7684\u6761\u76EE\u4E0D\u70B9\u540D\u3002\u8BFB\u8FC7\u6B63\u6587\u540E\u8FD8\u9700\u8981\u8FFD\u95EE\u7684\uFF0C\u4E5F\u5148\u6309\u4E0B\u9762\u7684\u4E09\u79CD\u56DE\u7B54\u4E4B\u4E00\u4F5C\u7B54\uFF0C\u518D\u8FFD\u95EE\uFF09\u3002\u56DE\u7B54\u8981\u77ED\uFF0C\u7EC6\u8282\u7B49\u7528\u6237\u8FFD\u95EE\u518D\u7ED9\u3002\u7B2C\u4E00\u6B21\u56DE\u7B54\u8981\u80FD\u4E00\u773C\u770B\u5B8C\uFF1A\u4E0D\u8D85\u8FC7 8 \u884C\u3001300 \u5B57\uFF08\u672B\u5C3E\u7684\u6761\u76EE\u94FE\u63A5\u4E0D\u7B97\uFF09\uFF0C\u6BCF\u884C\u53EA\u5199\u4E00\u53E5\u8BDD\uFF1B\u653E\u4E0D\u4E0B\u7684\u7559\u7ED9\u8FFD\u95EE\u3002\u5199\u6CD5\uFF1A\u7528\u81EA\u7136\u7684\u51E0\u6BB5\u8BDD\uFF0C\u4E0D\u52A0\u300C\u7ED3\u8BBA\u300D\u300C\u786E\u8BA4\u300D\u300C\u4F9D\u636E\u300D\u8FD9\u7C7B\u5C0F\u6807\u9898\u6216\u6807\u7B7E\uFF0C\u4E0D\u7528\u8868\u683C\u3001\u5D4C\u5957\u5217\u8868\u548C\u7C97\u4F53\uFF1B\u4E00\u53E5\u53EA\u8BF4\u4E00\u4EF6\u4E8B\uFF1B\u7528\u7948\u4F7F\u53E5\uFF1B\u540C\u4E00\u4E2A\u4E1C\u897F\u59CB\u7EC8\u7528\u540C\u4E00\u4E2A\u8BCD\uFF1B\u4E0D\u7528\u300C\u5927\u6982\u300D\u300C\u4E00\u4E9B\u300D\u8FD9\u7C7B\u542B\u7CCA\u7684\u8BCD\uFF1B\u6309\u94AE\u540D\u3001\u83DC\u5355\u540D\u3001\u62A5\u9519\u539F\u6587\u548C\u547D\u4EE4\u7167\u539F\u6837\u5199\uFF1B\u4E0D\u4E3B\u52A8\u8BB2\u539F\u7406\uFF0C\u7528\u6237\u95EE\u4E3A\u4EC0\u4E48\u518D\u8BB2\u3002\u7528\u4E86\u6761\u76EE\u5185\u5BB9\u7684\u56DE\u7B54\uFF08\u80FD\u5B8C\u6574\u56DE\u7B54\u3001\u53EA\u80FD\u90E8\u5206\u56DE\u7B54\uFF09\uFF0C`get_entry` \u8FD4\u56DE\u7684\u6B63\u6587\u7B2C\u4E00\u884C\u5199\u7740\u300C\u6761\u76EE\u9875\u9762\uFF1A\u300D\u5730\u5740\u65F6\uFF0C\u5FC5\u987B\u5728\u672B\u5C3E\u5355\u72EC\u4E00\u884C\u7ED9\u51FA\u7528\u5230\u7684\u6761\u76EE\u9875\u9762\u94FE\u63A5\uFF0C\u7167\u6284\u90A3\u4E2A\u5730\u5740\uFF1B\u6CA1\u6709\u8FD9\u4E00\u884C\u800C\u672C skill \u7684\u300C\u56DE\u9000\u300D\u4E00\u8282\u7ED9\u4E86\u4E3B\u7AD9\u5730\u5740\u65F6\uFF0C\u6309 `<\u4E3B\u7AD9>/<type>/<id>/` \u62FC\u51FA\uFF0C`<type>` \u53D6\u76EE\u5F55\u91CC\u7684\u6761\u76EE\u7C7B\u578B\uFF1B\u4E24\u6837\u90FD\u6CA1\u6709\u5C31\u4E0D\u7ED9\u3002\u7528\u6237\u8FFD\u95EE\u7EC6\u8282\u65F6\uFF0C\u6309\u6761\u76EE\u7ED9\u51FA\u5B8C\u6574\u6B65\u9AA4\uFF0C\u4E0D\u53D7\u4E0B\u9762\u7684\u957F\u5EA6\u9650\u5236\uFF1A",
1257
1427
  " - \u80FD\u5B8C\u6574\u56DE\u7B54\uFF1A\u8981\u52A8\u624B\u64CD\u4F5C\u7684\u95EE\u9898\uFF0C\u5148\u7528\u4E00\u53E5\u8BDD\u8BF4\u662F\u4EC0\u4E48\u60C5\u51B5\u3001\u8BE5\u505A\u4EC0\u4E48\uFF0C\u518D\u5217\u6700\u591A 3 \u6B65\uFF0C\u6BCF\u6B65\u4E00\u4E2A\u52A8\u4F5C\uFF0C\u7136\u540E\u7528\u4E00\u53E5\u8BDD\u8BF4\u505A\u5B8C\u540E\u770B\u5230\u4EC0\u4E48\u5C31\u8BF4\u660E\u597D\u4E86\uFF08\u53D6\u81EA\u6761\u76EE\u7684\u300C\u9A8C\u8BC1\u300D\uFF09\uFF1B\u95EE\u300C\u80FD\u4E0D\u80FD\u300D\u300C\u662F\u4E0D\u662F\u300D\u300C\u662F\u4EC0\u4E48\u300D\u7684\u95EE\u9898\uFF0C\u76F4\u63A5\u7528 2\u20133 \u53E5\u8BDD\u56DE\u7B54\uFF0C\u4E0D\u5217\u6B65\u9AA4\uFF0C\u4E5F\u4E0D\u5199\u600E\u4E48\u786E\u8BA4\u3002\u6761\u76EE\u6709\u591A\u4E2A\u5206\u652F\u65F6\uFF0C\u6309\u7528\u6237\u8BF4\u7684\u73B0\u8C61\u6311\u4E00\u652F\uFF1B\u7528\u6237\u6CA1\u8BF4\u73B0\u8C61\uFF0C\u6311\u6700\u5E38\u89C1\u7684\u4E00\u652F\uFF1B\u5176\u4F59\u5206\u652F\u4E0D\u5C55\u5F00\uFF0C\u53EA\u5728\u6700\u540E\u4E00\u884C\u70B9\u540D 2\u20133 \u79CD\u522B\u7684\u60C5\u51B5\u8BA9\u7528\u6237\u9009\uFF08\u4F8B\u5982\u300C\u5982\u679C\u4F60\u770B\u5230\u7684\u662F A \u6216 B\uFF0C\u544A\u8BC9\u6211\u662F\u54EA\u4E2A\u300D\uFF09\uFF1B\u6CA1\u6709\u522B\u7684\u5206\u652F\u65F6\uFF0C\u6700\u540E\u4E00\u53E5\u544A\u8BC9\u7528\u6237\u8FD8\u80FD\u600E\u4E48\u7EE7\u7EED\u3002\u4E0D\u5199\u7528\u6237\u6CA1\u95EE\u7684\u63D0\u9192\u548C\u6CE8\u610F\u4E8B\u9879\uFF1B",
1258
1428
  ` - \u53EA\u80FD\u90E8\u5206\u56DE\u7B54\uFF1A\u6761\u76EE\u5BF9\u8FD9\u4E2A\u95EE\u9898\u6709\u5B9E\u8D28\u5E2E\u52A9\uFF08\u7B54\u6848\u6216\u6392\u67E5\u6B65\u9AA4\u7684\u4E00\u90E8\u5206\uFF09\u3001\u4F46\u7B54\u4E0D\u5168\u2014\u2014\u56DE\u7B54\u91CC\u7528\u4E86\u6761\u76EE\u91CC\u7684\u6392\u67E5\u6B65\u9AA4\uFF0C\u5C31\u6309\u8FD9\u4E00\u79CD\u4F5C\u7B54\uFF1B\u6761\u76EE\u8986\u76D6\u7684\u90E8\u5206\u6309\u4E0A\u9762\u7684\u77ED\u7B54\u5199\uFF0C\u5E76\u4EE5\u56FA\u5B9A\u524D\u7F00\u300C${PARTIAL_PREFIX}\u300D\u7528\u4E00\u53E5\u8BDD\u5199\u51FA\u7F3A\u7684\u90E8\u5206\uFF08\u524D\u7F00\u5355\u72EC\u8D77\u4E00\u884C\u5199\u5728\u884C\u9996\uFF0C\u4E0D\u5199\u6210\u6807\u9898\uFF09\uFF1B\u7F3A\u7684\u90E8\u5206\u8981\u7528\u81EA\u5DF1\u7684\u77E5\u8BC6\u8865\uFF0C\u5C31\u5728\u524D\u7F00\u540E\u5148\u5199\u56FA\u5B9A\u6807\u6CE8\u300C${NOT_KB_LABEL}\u300D\u518D\u8865\uFF0C\u81EA\u5DF1\u8865\u7684\u53EA\u5199 1\u20132 \u53E5\uFF1B`,
1259
1429
  ` - \u6CA1\u6709\u6761\u76EE\u80FD\u56DE\u7B54\uFF08\u4E3B\u9898\u76F8\u8FD1\u3001\u5374\u7B54\u4E0D\u4E86\u7528\u6237\u5177\u4F53\u95EE\u9898\u7684\u6761\u76EE\u4E5F\u7B97\u6CA1\u6709\uFF09\uFF1A\u56DE\u7B54\u4EE5\u56FA\u5B9A\u6807\u6CE8\u300C${NOT_KB_LABEL}\u300D\u5F00\u5934\u2014\u2014\u8981\u81EA\u7B54\uFF0C\u5C31\u5728\u6807\u6CE8\u540E\u7528\u81EA\u5DF1\u7684\u77E5\u8BC6\u56DE\u7B54\uFF0C\u53EA\u5199 1\u20132 \u53E5\uFF0C\u5E76\u4E0E\u6761\u76EE\u5185\u5BB9\u5206\u5F00\uFF1B\u4E0D\u81EA\u7B54\uFF0C\u5C31\u5728\u6807\u6CE8\u540E\u8BF4\u660E\u77E5\u8BC6\u5E93\u6CA1\u6709\u8986\u76D6\uFF1B`,
1260
1430
  ` - \u4E0E\u77E5\u8BC6\u5E93\u76F8\u90BB\u4F46\u523B\u610F\u4E0D\u505A\u2014\u2014${ADJACENT_SCOPE.join("\uFF1B")}\u2014\u2014\u4E0D\u4F5C\u7B54\uFF0C\u4E5F\u4E0D\u7528\u81EA\u5DF1\u7684\u77E5\u8BC6\u8865\uFF0C\u53EA\u56DE\u7B54\u300C${OUT_OF_SCOPE_REPLY}\u300D\u3002\u8FD9\u6761\u4F18\u5148\u4E8E\u524D\u9762\u4E09\u79CD\u56DE\u7B54\uFF1A\u5373\u4F7F\u76EE\u5F55\u91CC\u6709\u770B\u8D77\u6765\u76F8\u5173\u7684\u6761\u76EE\uFF0C\u4E5F\u4E0D\u6309\u6761\u76EE\u4F5C\u7B54\u3001\u4E0D\u90E8\u5206\u4F5C\u7B54\u3001\u4E0D\u81EA\u7B54\uFF1B\u76F8\u90BB\u95EE\u9898\u4E0D\u8D70\u300C\u6CA1\u6709\u6761\u76EE\u80FD\u56DE\u7B54\u300D\u90A3\u6761\u8DEF\uFF0C\u5E26\u4E0A\u300C${NOT_KB_LABEL}\u300D\u6807\u6CE8\u81EA\u7B54\u540C\u6837\u4E0D\u884C\u3002`,
1261
1431
  "5. \u4E0D\u56DE\u663E\u8BA2\u9605\u94FE\u63A5\u3001\u5BC6\u94A5\u3001\u8282\u70B9 IP\uFF0C\u622A\u56FE\u91CC\u7684\u4E5F\u4E00\u6837\uFF1B\u53EA\u94FE\u63A5\u6761\u76EE\u91CC\u5217\u51FA\u7684\u5B98\u65B9\u6765\u6E90\u548C\u672C\u77E5\u8BC6\u5E93\u4E3B\u7AD9\u4E0A\u7684\u6761\u76EE\u9875\u9762\uFF1B\u6807\u4E3A\u300C\u4FE1\u606F\u8BF4\u660E\u300D\u7684\u6761\u76EE\u4E0D\u7ED9\u64CD\u4F5C\u6B65\u9AA4\u3002"
1262
1432
  ].join("\n");
1263
- var SKILL_DESCRIPTION = `\u4E2D\u7ACB\u7684\u4EE3\u7406\u5BA2\u6237\u7AEF\u77E5\u8BC6\u5E93\u3002\u7528\u6237\u95EE Clash Verge Rev\u3001Clash Party\u3001FlClash\u3001v2rayN\u3001v2rayNG\u3001Clash Meta for Android\u3001sing-box\u3001Karing\u3001Shadowrocket \u7B49\u4EE3\u7406\u5BA2\u6237\u7AEF\u6216 mihomo\u3001Xray \u5185\u6838\u7684\u95EE\u9898\u65F6\u4F7F\u7528\uFF1A\u8BA2\u9605\u3001\u8282\u70B9\u3001\u534F\u8BAE\u3001\u5206\u6D41\u89C4\u5219\u3001TUN\uFF08\u865A\u62DF\u7F51\u5361\uFF09\u3001\u7CFB\u7EDF\u4EE3\u7406\u662F\u4EC0\u4E48\uFF0C\u5BA2\u6237\u7AEF\u4E0E\u5185\u6838\u7684\u7248\u672C\u3001\u66F4\u65B0\u3001\u4E0B\u8F7D\uFF08\u542B\u9884\u53D1\u5E03\u4E0E autobuild\uFF09\u600E\u4E48\u9009\uFF0C\u60F3\u5B8C\u6210\u5BFC\u5165\u8BA2\u9605\u3001\u5207\u6362\u8282\u70B9\u7B49\u64CD\u4F5C\uFF0C\u6216\u9047\u5230\u8282\u70B9\u4E3A\u7A7A\u3001\u8BA2\u9605\u66F4\u65B0\u5931\u8D25\u3001\u8FDE\u4E0A\u4F46\u6253\u4E0D\u5F00\u7F51\u9875\u7B49\u95EE\u9898\uFF08\u9644\u622A\u56FE\u6C42\u52A9\u4E5F\u7B97\uFF09\uFF1B\u7528\u6237\u95EE${ADJACENT_SCOPE.join("\u3001")}\u65F6\u540C\u6837\u4F7F\u7528\uFF08\u6309\u89C4\u5219\u53EA\u56DE\u4E00\u53E5\u56FA\u5B9A\u8BDD\uFF09\u3002\u53EA\u8BFB\uFF0C\u4E0D\u8BFB\u53D6\u7528\u6237\u673A\u5668\u72B6\u6001\uFF0C\u4E0D\u5199\u5165\u3002`;
1433
+ var SKILL_DESCRIPTION = `\u4EE3\u7406\u5BA2\u6237\u7AEF\u77E5\u8BC6\u5E93\u3002\u7528\u6237\u95EE Clash Verge Rev\u3001Clash Party\u3001FlClash\u3001v2rayN\u3001v2rayNG\u3001Clash Meta for Android\u3001sing-box\u3001Karing\u3001Shadowrocket \u7B49\u4EE3\u7406\u5BA2\u6237\u7AEF\u6216 mihomo\u3001Xray \u5185\u6838\u7684\u95EE\u9898\u65F6\u4F7F\u7528\uFF1A\u8BA2\u9605\u3001\u8282\u70B9\u3001\u534F\u8BAE\u3001\u5206\u6D41\u89C4\u5219\u3001TUN\uFF08\u865A\u62DF\u7F51\u5361\uFF09\u3001\u7CFB\u7EDF\u4EE3\u7406\u662F\u4EC0\u4E48\uFF0C\u5BA2\u6237\u7AEF\u4E0E\u5185\u6838\u7684\u7248\u672C\u3001\u66F4\u65B0\u3001\u4E0B\u8F7D\uFF08\u542B\u9884\u53D1\u5E03\u4E0E autobuild\uFF09\u600E\u4E48\u9009\uFF0C\u60F3\u5B8C\u6210\u5BFC\u5165\u8BA2\u9605\u3001\u5207\u6362\u8282\u70B9\u7B49\u64CD\u4F5C\uFF0C\u6216\u9047\u5230\u8282\u70B9\u4E3A\u7A7A\u3001\u8BA2\u9605\u66F4\u65B0\u5931\u8D25\u3001\u8FDE\u4E0A\u4F46\u6253\u4E0D\u5F00\u7F51\u9875\u7B49\u95EE\u9898\uFF08\u9644\u622A\u56FE\u6C42\u52A9\u4E5F\u7B97\uFF09\uFF1B\u7528\u6237\u95EE${ADJACENT_SCOPE.join("\u3001")}\u65F6\u540C\u6837\u4F7F\u7528\uFF08\u6309\u89C4\u5219\u53EA\u56DE\u4E00\u53E5\u56FA\u5B9A\u8BDD\uFF09\u3002\u53EA\u8BFB\uFF0C\u4E0D\u8BFB\u53D6\u7528\u6237\u673A\u5668\u72B6\u6001\uFF0C\u4E0D\u5199\u5165\u3002`;
1264
1434
 
1265
1435
  // ../runtime-core/src/mcp.ts
1436
+ function siteOriginOf(manifest) {
1437
+ const v = typeof manifest === "object" && manifest !== null ? manifest.site_origin : void 0;
1438
+ if (typeof v !== "string" || !/^https:\/\/[A-Za-z0-9.-]+(:[0-9]+)?$/.test(v)) return void 0;
1439
+ try {
1440
+ if (new URL(v).port === "0") return void 0;
1441
+ } catch {
1442
+ return void 0;
1443
+ }
1444
+ return v;
1445
+ }
1266
1446
  var ID = /^[a-z0-9]+(-[a-z0-9]+)*$/;
1267
1447
  var MAX_BYTES = 131072;
1268
1448
  var MAX_ID_UNITS = 256;
1269
1449
  function channelOf(v, cfg) {
1270
1450
  return typeof v === "string" && (cfg.channels ?? []).includes(v) ? v : null;
1271
1451
  }
1452
+ var LIST_ENTRIES_BUDGET = 38e3;
1272
1453
  var RESULT_SIZE_CHARS = 131072;
1273
- var LIST_ENTRIES_DESCRIPTION = `\u4ECE\u8FD9\u91CC\u5F00\u59CB\uFF1A\u8FD4\u56DE\u77E5\u8BC6\u5E93\u7684\u6761\u76EE\u76EE\u5F55\uFF08\u6BCF\u884C\u4E00\u6761\uFF1Aid | \u7C7B\u578B | \u6807\u9898 | \u522B\u540D\u2026\uFF09\uFF0C\u518D\u7528 get_entry \u8BFB\u6B63\u6587\u3002query \u586B\u7528\u6237\u539F\u8BDD\uFF081\u2013200 \u5B57\uFF09\u3002
1454
+ var LIST_ENTRIES_DESCRIPTION = `\u4ECE\u8FD9\u91CC\u5F00\u59CB\uFF1A\u8FD4\u56DE\u77E5\u8BC6\u5E93\u7684\u6761\u76EE\u76EE\u5F55\uFF08\u6BCF\u884C\u4E00\u6761\uFF1Aid | \u7C7B\u578B | \u6807\u9898\uFF0C\u4E0E query \u76F8\u5173\u7684\u6761\u76EE\u540E\u9762\u8FD8\u5217\u51FA\u522B\u540D\uFF09\uFF0C\u518D\u7528 get_entry \u8BFB\u6B63\u6587\u3002query \u586B\u7528\u6237\u539F\u8BDD\uFF081\u2013200 \u5B57\uFF09\u3002
1274
1455
 
1275
1456
  ${USAGE_RULES}`;
1276
1457
  var TOOLS = [
1277
- { name: "search", description: "\u6309\u7528\u6237\u539F\u8BDD\u68C0\u7D22\u4EE3\u7406\u5BA2\u6237\u7AEF\u77E5\u8BC6\u5E93\u6761\u76EE\u3002\u53EF\u9009 client\u3001os \u7F29\u5C0F\u8303\u56F4\u3002", inputSchema: { type: "object", properties: { query: { type: "string", minLength: 1, maxLength: 200 }, client: { type: "string" }, os: { type: "string" }, channel: { type: "string", maxLength: 32 } }, required: ["query"], additionalProperties: false } },
1278
1458
  { name: "list_entries", description: LIST_ENTRIES_DESCRIPTION, inputSchema: { type: "object", properties: { query: { type: "string", minLength: 1, maxLength: 200 } }, required: ["query"], additionalProperties: false } },
1279
- { name: "get_entry", description: "\u6309\u6761\u76EE id \u53D6\u56DE\u5B8C\u6574\u5185\u5BB9\uFF08Markdown\uFF09\u3002", inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"], additionalProperties: false } }
1459
+ { name: "get_entry", description: "\u6309\u6761\u76EE id \u53D6\u56DE\u5185\u5BB9\uFF08Markdown\uFF09\u3002\u957F\u6761\u76EE\u5206\u6BB5\u8FD4\u56DE\uFF1A\u6B63\u6587\u524D\u9762\u7684\u8BF4\u660E\u5199\u660E\u5171\u51E0\u6BB5\u548C\u5404\u6BB5\u6807\u9898\uFF0C\u7528 part \u53C2\u6570\u8BFB\u5176\u4F59\u5404\u6BB5\u3002", inputSchema: { type: "object", properties: { id: { type: "string" }, part: { type: "integer", minimum: 1 } }, required: ["id"], additionalProperties: false } }
1280
1460
  ];
1281
1461
  var MCP_TOOLS = ["list_entries", "get_entry"].map((n) => ({ ...TOOLS.find((t) => t.name === n), name: n, _meta: { "anthropic/maxResultSizeChars": RESULT_SIZE_CHARS } }));
1282
- function catalogOf(projection) {
1462
+ function aliasOrder(ids, index, query, t2s) {
1463
+ const score = /* @__PURE__ */ new Map();
1464
+ if (index.docs.length > 0) {
1465
+ const qt = [...new Set(tokens(query, index.config, t2s))];
1466
+ const n = index.docs.length;
1467
+ const df = /* @__PURE__ */ new Map();
1468
+ for (const d of index.docs) for (const t of new Set(Object.values(d.fields).flat())) df.set(t, (df.get(t) ?? 0) + 1);
1469
+ for (const d of index.docs) {
1470
+ let s = 0;
1471
+ for (const [f, toks] of Object.entries(d.fields)) {
1472
+ const w = index.config.weights[f] ?? 0;
1473
+ if (!w) continue;
1474
+ const set = new Set(toks);
1475
+ for (const t of qt) if (set.has(t)) s += w * Math.log(1 + n / (df.get(t) ?? 1));
1476
+ }
1477
+ score.set(d.id, s);
1478
+ }
1479
+ }
1480
+ return [...ids].sort((a, b) => (score.get(b) ?? 0) - (score.get(a) ?? 0) || (a < b ? -1 : a > b ? 1 : 0));
1481
+ }
1482
+ function catalogOf(projection, index, query, t2s) {
1283
1483
  const ids = Object.keys(projection.entries).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
1284
- return { content_version: projection.content_version, entries: ids.map((id) => {
1484
+ const entries = ids.map((id) => {
1285
1485
  const e = projection.entries[id];
1286
- return { id: e.id, type: e.type, title: e.title, aliases: [...e.aliases] };
1287
- }) };
1486
+ return { id: e.id, type: e.type, title: e.title };
1487
+ });
1488
+ const byId = new Map(entries.map((e) => [e.id, e]));
1489
+ const base = { content_version: projection.content_version, entries };
1490
+ let size = bytes(catalogText(base)) + bytes(JSON.stringify(base));
1491
+ for (const id of aliasOrder(ids, index, query, t2s)) {
1492
+ const e = byId.get(id);
1493
+ const aliases = projection.entries[id]?.aliases ?? [];
1494
+ if (!e || aliases.length === 0) continue;
1495
+ const extra = aliases.reduce((a, x) => a + bytes(" | " + field(x)), 0) + bytes(`,"aliases":${JSON.stringify(aliases)}`);
1496
+ if (size + extra > LIST_ENTRIES_BUDGET) break;
1497
+ e.aliases = [...aliases];
1498
+ size += extra;
1499
+ }
1500
+ return { content_version: projection.content_version, entries };
1288
1501
  }
1289
1502
  var field = (s) => s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r\n|\r|\n/g, " ");
1290
1503
  function catalogText(c) {
1291
- return [`content_version: ${c.content_version}`, ...c.entries.map((e) => [e.id, e.type, e.title, ...e.aliases].map(field).join(" | "))].join("\n");
1504
+ return [`content_version: ${c.content_version}`, ...c.entries.map((e) => [e.id, e.type, e.title, ...e.aliases ?? []].map(field).join(" | "))].join("\n");
1292
1505
  }
1293
1506
  var bytes = (s) => new TextEncoder().encode(s).length;
1507
+ var PART_BYTES = 17e3;
1508
+ var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;
1509
+ var FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*\r?\n?$/;
1510
+ function linesWithFence(s, start = null) {
1511
+ const lines = [];
1512
+ let fence = start;
1513
+ for (const line of s.match(/[^\n]*\n|[^\n]+$/g) ?? []) {
1514
+ lines.push({ line, inFence: fence !== null });
1515
+ if (fence === null) {
1516
+ const m = FENCE_OPEN.exec(line);
1517
+ if (m) fence = { ch: m[1][0], len: m[1].length };
1518
+ } else {
1519
+ const m = FENCE_CLOSE.exec(line);
1520
+ if (m && m[1][0] === fence.ch && m[1].length >= fence.len) fence = null;
1521
+ }
1522
+ }
1523
+ return { lines, end: fence };
1524
+ }
1525
+ function groupLines(s, isStart) {
1526
+ const ls = linesWithFence(s).lines;
1527
+ const out = [];
1528
+ ls.forEach((l, i) => {
1529
+ if (i === 0 || isStart(ls, i)) out.push(l.line);
1530
+ else out[out.length - 1] += l.line;
1531
+ });
1532
+ return out;
1533
+ }
1534
+ var cost = (s) => bytes(JSON.stringify(s)) - 2;
1535
+ function codePointChunks(s, limit) {
1536
+ const out = [];
1537
+ let cur = "";
1538
+ let n = 0;
1539
+ for (const cp of s) {
1540
+ const b = cost(cp);
1541
+ if (n + b > limit && cur) {
1542
+ out.push(cur);
1543
+ cur = "";
1544
+ n = 0;
1545
+ }
1546
+ cur += cp;
1547
+ n += b;
1548
+ }
1549
+ if (cur) out.push(cur);
1550
+ return out;
1551
+ }
1552
+ function pack(units, limit, split) {
1553
+ const out = [];
1554
+ let cur = "";
1555
+ let n = 0;
1556
+ const add = (u) => {
1557
+ const b = cost(u);
1558
+ if (n + b <= limit) {
1559
+ cur += u;
1560
+ n += b;
1561
+ return;
1562
+ }
1563
+ if (cur) out.push(cur);
1564
+ cur = u;
1565
+ n = b;
1566
+ };
1567
+ for (const u of units) {
1568
+ if (cost(u) <= limit) add(u);
1569
+ else for (const piece of split(u)) add(piece);
1570
+ }
1571
+ if (cur || out.length === 0) out.push(cur);
1572
+ return out;
1573
+ }
1574
+ function entryParts(markdown, limit) {
1575
+ const byLine = (u) => pack(groupLines(u, () => true), limit, (x) => codePointChunks(x, limit));
1576
+ const byParagraph = (u) => pack(groupLines(u, (ls, i) => !ls[i - 1].inFence && ls[i - 1].line.trim() === ""), limit, byLine);
1577
+ return pack(groupLines(markdown, (ls, i) => !ls[i].inFence && ls[i].line.startsWith("## ")), limit, byParagraph);
1578
+ }
1579
+ function partHeadings(segs) {
1580
+ const clip = (line) => {
1581
+ const cps = [...line];
1582
+ return cps.length > 40 ? cps.slice(0, 40).join("") + "\u2026" : line;
1583
+ };
1584
+ const { lines } = linesWithFence(segs.join(""));
1585
+ const starts = [];
1586
+ let off = 0;
1587
+ for (const l of lines) {
1588
+ starts.push(off);
1589
+ off += l.line.length;
1590
+ }
1591
+ let lastH2 = "";
1592
+ let segStart = 0;
1593
+ let i = 0;
1594
+ return segs.map((seg) => {
1595
+ const segEnd = segStart + seg.length;
1596
+ while (i < lines.length && starts[i] + lines[i].line.length <= segStart) i++;
1597
+ let heading;
1598
+ if (i < lines.length && starts[i] === segStart) {
1599
+ let k = i;
1600
+ while (k < lines.length && starts[k] < segEnd && lines[k].line.trim() === "") k++;
1601
+ if (k < lines.length && starts[k] < segEnd && !lines[k].inFence && /^#{1,2} /.test(lines[k].line)) heading = lines[k].line.trim();
1602
+ }
1603
+ const label = heading ?? (lastH2 ? `${lastH2}\uFF08\u7EED\uFF09` : seg.split("\n").find((l) => l.trim() !== "")?.trim() ?? "");
1604
+ for (let k = i; k < lines.length && starts[k] < segEnd; k++) if (starts[k] >= segStart && !lines[k].inFence && lines[k].line.startsWith("## ")) lastH2 = lines[k].line.trim();
1605
+ segStart = segEnd;
1606
+ return clip(label);
1607
+ });
1608
+ }
1294
1609
  function vocabulary(index) {
1295
1610
  const v = /* @__PURE__ */ new Set();
1296
1611
  for (const d of index.docs) for (const toks of Object.values(d.fields)) for (const t of toks) v.add(t);
@@ -1328,47 +1643,38 @@ async function handleToolCall(name, args, deps, measure = (r) => bytes(JSON.stri
1328
1643
  if (!tool) return { ok: false, code: -32602, message: "unknown tool" };
1329
1644
  const props = tool.inputSchema.properties;
1330
1645
  for (const k of Object.keys(a)) if (!(k in props)) return { ok: false, code: -32602, message: "unexpected argument" };
1646
+ const typeOk = (v, p) => p.type === "integer" ? Number.isInteger(v) && (p.minimum === void 0 || v >= p.minimum) : typeof v === p.type;
1331
1647
  for (const [k, v] of Object.entries(a)) {
1332
1648
  const p = props[k];
1333
- if (v !== void 0 && (typeof v !== p.type || p.maxLength !== void 0 && v.length > p.maxLength)) return { ok: false, code: -32602, message: `argument ${k} must be ${p.type}${p.maxLength ? ` \u2264${p.maxLength}` : ""}` };
1649
+ if (v !== void 0 && (!typeOk(v, p) || p.maxLength !== void 0 && v.length > p.maxLength)) return { ok: false, code: -32602, message: `argument ${k} must be ${p.type}${p.maxLength ? ` \u2264${p.maxLength}` : ""}${p.minimum !== void 0 ? ` \u2265${p.minimum}` : ""}` };
1334
1650
  }
1335
1651
  for (const k of tool.inputSchema.required) if (a[k] === void 0) return { ok: false, code: -32602, message: `missing argument ${k}` };
1336
- if (name === "search") {
1337
- const query = typeof a.query === "string" ? a.query : "";
1338
- if (query.length < 1 || query.length > 200) return { ok: false, code: -32602, message: "query must be 1..200 chars" };
1339
- const ctx = { client: typeof a.client === "string" ? a.client : void 0, os: typeof a.os === "string" ? a.os : void 0 };
1340
- const cleanQuery = queryText(query, deps.redact);
1341
- const r = search(deps.index, cleanQuery, ctx, deps.t2s);
1342
- const results = r.results.map((x) => ({ id: x.id, type: x.type, title: deps.projection.entries[x.id]?.title ?? x.id, score: Number(x.score.toFixed(4)) }));
1343
- const top = results[0] ? deps.projection.entries[results[0].id] : void 0;
1344
- await logQuery(deps, cleanQuery, { tool: "search", intent: top?.intents[0] ?? null, top_id: results[0]?.id ?? null, top_type: results[0]?.type ?? null, abstained: r.abstained ? 1 : 0, channel: channelOf(a.channel, deps.index.config) });
1345
- return { ok: true, result: { results, abstained: r.abstained, content_version: deps.index.content_version, sponsored: null } };
1346
- }
1347
1652
  if (name === "list_entries") {
1348
1653
  const query = typeof a.query === "string" ? a.query : "";
1349
1654
  if (query.length < 1 || query.length > 200) return { ok: false, code: -32602, message: "query must be 1..200 chars" };
1350
1655
  await logQuery(deps, queryText(query, deps.redact), { tool: "list_entries", intent: null, top_id: null, top_type: null, abstained: 0, channel: channelOf(deps.channel, deps.index.config) });
1351
- return { ok: true, result: catalogOf(deps.projection) };
1656
+ return { ok: true, result: catalogOf(deps.projection, deps.index, query, deps.t2s) };
1352
1657
  }
1353
1658
  if (name === "get_entry") {
1354
1659
  const id = typeof a.id === "string" ? a.id : "";
1355
1660
  if (!ID.test(id)) return { ok: false, code: -32602, message: "bad id" };
1356
1661
  const e = Object.hasOwn(deps.projection.entries, id) ? deps.projection.entries[id] : void 0;
1357
1662
  if (!e) return { ok: false, code: -32004, message: "entry not found" };
1358
- const make = (md, truncated) => ({ id: e.id, type: e.type, title: e.title, markdown: md, truncated, last_verified_at: e.last_verified_at, freshness: e.freshness, content_version: deps.projection.content_version, sponsored: null });
1359
- if (measure(make("", true)) > MAX_BYTES) return { ok: false, code: -32603, message: "entry too large" };
1360
- let result = make(e.markdown, false);
1361
- if (measure(result) > MAX_BYTES) {
1362
- const cps = [...e.markdown];
1363
- if (cps.length === 0) return { ok: false, code: -32603, message: "entry too large" };
1364
- let lo = 0, hi = cps.length - 1;
1365
- while (lo < hi) {
1366
- const mid = Math.ceil((lo + hi) / 2);
1367
- if (measure(make(cps.slice(0, mid).join(""), true)) <= MAX_BYTES) lo = mid;
1368
- else hi = mid - 1;
1369
- }
1370
- result = make(cps.slice(0, lo).join(""), true);
1371
- }
1663
+ const segs = entryParts(e.markdown, PART_BYTES);
1664
+ const part = typeof a.part === "number" ? a.part : 1;
1665
+ if (part > segs.length) return { ok: false, code: -32602, message: "part out of range" };
1666
+ const headings = partHeadings(segs);
1667
+ const others = headings.map((h, i) => `${i + 1}\u300C${h}\u300D`).filter((_, i) => i + 1 !== part).join("\u3001");
1668
+ const url = deps.siteOrigin ? `${deps.siteOrigin}/${e.type}/${e.id}/` : void 0;
1669
+ const freshness = freshnessAt(e, deps.now?.() ?? /* @__PURE__ */ new Date());
1670
+ const staleNote = freshness === "stale" ? `\uFF08\u8FD9\u6761\u5185\u5BB9\u6700\u540E\u9A8C\u8BC1\u4E8E ${e.last_verified_at}\uFF0C\u5DF2\u8D85\u8FC7\u6709\u6548\u671F\uFF0C\u53EF\u80FD\u5DF2\u8FC7\u671F\u3002\uFF09` : "";
1671
+ const lead = [url ? `\u6761\u76EE\u9875\u9762\uFF1A${url}` : "", staleNote, segs.length === 1 ? "" : `\uFF08\u672C\u6761\u76EE\u5206 ${segs.length} \u6BB5\uFF0C\u8FD9\u662F\u7B2C ${part} \u6BB5\u3002\u5176\u4F59\u5404\u6BB5\uFF1A${others}\uFF1B\u7528 get_entry \u7684 part \u53C2\u6570\u8BFB\u3002\uFF09`].filter(Boolean).join("\n");
1672
+ const md0 = lead ? `${lead}
1673
+
1674
+ ${segs[part - 1]}` : segs[part - 1];
1675
+ const make = (md, truncated) => ({ id: e.id, type: e.type, title: e.title, ...url ? { url } : {}, markdown: md, truncated, part, parts: segs.length, part_headings: headings, last_verified_at: e.last_verified_at, freshness, content_version: deps.projection.content_version, sponsored: null });
1676
+ const result = make(md0, false);
1677
+ if (measure(result) > MAX_BYTES) return { ok: false, code: -32603, message: "entry too large" };
1372
1678
  return { ok: true, result };
1373
1679
  }
1374
1680
  return { ok: false, code: -32602, message: "unknown tool" };
@@ -1391,7 +1697,7 @@ function metaVersion(m) {
1391
1697
  return m.params._meta[PV];
1392
1698
  }
1393
1699
  function acceptedId(rawId) {
1394
- if (rawId !== void 0 && rawId !== null && typeof rawId !== "number" && (typeof rawId !== "string" || rawId.length > MAX_ID_UNITS || credentialRules(rawId).length > 0)) return false;
1700
+ if (rawId !== void 0 && rawId !== null && typeof rawId !== "number" && (typeof rawId !== "string" || rawId.length > MAX_ID_UNITS || credentialRules(rawId).some((r) => r !== "uuid"))) return false;
1395
1701
  return rawId;
1396
1702
  }
1397
1703
  function modernMetaError(meta) {
@@ -1486,12 +1792,14 @@ async function loadDepsFromDir(dir, extra) {
1486
1792
  if (!want || want.bytes !== buf.length || want.sha256 !== createHash("sha256").update(buf).digest("hex")) throw new Error(`${f} does not match release-manifest.json in ${dir}`);
1487
1793
  return JSON.parse(buf.toString("utf8"));
1488
1794
  }));
1489
- return { index, projection, t2s: makeTableT2s(table), salt: extra.salt, sha256: extra.sha256, redact: redactSensitive };
1795
+ const siteOrigin = siteOriginOf(manifest);
1796
+ return { index, projection, t2s: makeTableT2s(table), salt: extra.salt, sha256: extra.sha256, redact: redactSensitive, ...siteOrigin ? { siteOrigin } : {} };
1490
1797
  }
1491
1798
  var TOO_LARGE = Symbol("request too large");
1799
+ var BAD_UTF8 = Symbol("invalid utf-8");
1492
1800
  async function* boundedLines(input, maxBytes = MAX_JSON_BODY_BYTES) {
1493
1801
  const enc = new TextEncoder();
1494
- const dec = new TextDecoder();
1802
+ const dec = new TextDecoder("utf-8", { fatal: true });
1495
1803
  let parts = [];
1496
1804
  let size = 0;
1497
1805
  let skipping = false;
@@ -1504,7 +1812,12 @@ async function* boundedLines(input, maxBytes = MAX_JSON_BODY_BYTES) {
1504
1812
  }
1505
1813
  parts = [];
1506
1814
  size = 0;
1507
- const line = dec.decode(buf);
1815
+ let line;
1816
+ try {
1817
+ line = dec.decode(buf);
1818
+ } catch {
1819
+ return BAD_UTF8;
1820
+ }
1508
1821
  return line.endsWith("\r") ? line.slice(0, -1) : line;
1509
1822
  };
1510
1823
  for await (const raw of input) {
@@ -1541,13 +1854,18 @@ async function createStdioServer(deps, io) {
1541
1854
  await io.write(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "request too large" } }));
1542
1855
  continue;
1543
1856
  }
1857
+ const parseError = JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } });
1858
+ if (line === BAD_UTF8) {
1859
+ await io.write(parseError);
1860
+ continue;
1861
+ }
1544
1862
  const trimmed = line.trim();
1545
1863
  if (!trimmed) continue;
1546
1864
  let msg;
1547
1865
  try {
1548
1866
  msg = JSON.parse(trimmed);
1549
1867
  } catch {
1550
- await io.write(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } }));
1868
+ await io.write(parseError);
1551
1869
  continue;
1552
1870
  }
1553
1871
  const r = await handleJsonRpc(msg, deps);
@@ -1555,48 +1873,23 @@ async function createStdioServer(deps, io) {
1555
1873
  }
1556
1874
  }
1557
1875
 
1558
- // src/static.ts
1559
- import http from "node:http";
1560
- import fs2 from "node:fs/promises";
1561
- import path2 from "node:path";
1562
- var TYPES = { ".html": "text/html; charset=utf-8", ".js": "text/javascript", ".css": "text/css", ".json": "application/json", ".svg": "image/svg+xml", ".png": "image/png", ".webp": "image/webp", ".mp4": "video/mp4", ".webm": "video/webm", ".txt": "text/plain; charset=utf-8" };
1563
- function createStaticServer(root) {
1564
- return http.createServer(async (req, res) => {
1565
- let p;
1566
- try {
1567
- p = decodeURIComponent((req.url ?? "/").split("?")[0]);
1568
- } catch {
1569
- res.writeHead(400);
1570
- res.end("bad request");
1571
- return;
1572
- }
1573
- if (p.endsWith("/")) p += "index.html";
1574
- const candidate = path2.resolve(root, `.${p}`);
1575
- if (candidate !== root && !candidate.startsWith(root + path2.sep)) {
1576
- res.writeHead(403);
1577
- res.end();
1578
- return;
1579
- }
1580
- const file = await fs2.realpath(candidate).catch(() => null);
1581
- if (!file || file !== root && !file.startsWith(root + path2.sep)) {
1582
- res.writeHead(404);
1583
- res.end("not found");
1584
- return;
1585
- }
1586
- try {
1587
- const data = await fs2.readFile(file);
1588
- res.writeHead(200, { "content-type": TYPES[path2.extname(file)] ?? "application/octet-stream" });
1589
- res.end(data);
1590
- } catch {
1591
- res.writeHead(404);
1592
- res.end("not found");
1593
- }
1594
- });
1595
- }
1596
-
1597
1876
  // src/exit.ts
1598
1877
  var FALLBACK = "\u52A0\u8F7D\u6216\u8FD0\u884C\u5931\u8D25\uFF08\u8BE6\u60C5\u4E0D\u56DE\u663E\uFF09";
1599
1878
  var FLUSH_TIMEOUT_MS = 2e3;
1879
+ async function flushStreams(streams, timer, clear) {
1880
+ for (const st of streams) {
1881
+ if (st.writableEnded || st.destroyed) continue;
1882
+ await new Promise((resolve) => {
1883
+ const done = timer(resolve, FLUSH_TIMEOUT_MS);
1884
+ const finish = () => {
1885
+ clear(done);
1886
+ resolve();
1887
+ };
1888
+ st.write("", finish);
1889
+ st.once("error", finish);
1890
+ });
1891
+ }
1892
+ }
1600
1893
  function makeFail(raw) {
1601
1894
  const deps = {
1602
1895
  ...raw,
@@ -1611,18 +1904,7 @@ function makeFail(raw) {
1611
1904
  } catch {
1612
1905
  }
1613
1906
  deps.error(msg);
1614
- for (const st of deps.streams) {
1615
- if (st.writableEnded || st.destroyed) continue;
1616
- await new Promise((resolve) => {
1617
- const done = deps.timer(resolve, FLUSH_TIMEOUT_MS);
1618
- const finish = () => {
1619
- deps.clear(done);
1620
- resolve();
1621
- };
1622
- st.write("", finish);
1623
- st.once("error", finish);
1624
- });
1625
- }
1907
+ await flushStreams(deps.streams, deps.timer, deps.clear);
1626
1908
  };
1627
1909
  return (e) => {
1628
1910
  if (exiting) return exiting;
@@ -1646,8 +1928,8 @@ var fail = makeFail({ error: (m) => console.error(m), exit: (c) => process.exit(
1646
1928
  installProcessHandlers(process, fail);
1647
1929
  async function run() {
1648
1930
  if (cmd === "mcp") {
1649
- const defaultAssets = path3.join(path3.dirname(fileURLToPath(import.meta.url)), "..", "snapshot");
1650
- const deps = await loadDepsFromDir(path3.resolve(opt("assets", defaultAssets)), { salt: "local", sha256: async (s) => createHash2("sha256").update(s).digest("hex") });
1931
+ const defaultAssets = path2.join(path2.dirname(fileURLToPath(import.meta.url)), "..", "snapshot");
1932
+ const deps = await loadDepsFromDir(path2.resolve(opt("assets", defaultAssets)), { salt: "local", sha256: async (s) => createHash2("sha256").update(s).digest("hex") });
1651
1933
  const it = boundedLines(process.stdin)[Symbol.asyncIterator]();
1652
1934
  await createStdioServer(deps, { readLine: async () => {
1653
1935
  const n = await it.next();
@@ -1667,12 +1949,8 @@ async function run() {
1667
1949
  process.stdout.once("error", onError);
1668
1950
  }
1669
1951
  }) });
1670
- } else if (cmd === "open") {
1671
- const root = await fs3.realpath(path3.resolve(opt("dir", "./site/dist")));
1672
- const port = Number(opt("port", "4321"));
1673
- createStaticServer(root).on("error", fail).listen(port, "127.0.0.1", () => console.log(`http://127.0.0.1:${port}/`));
1674
1952
  } else {
1675
- console.log("usage: hub-kb mcp [--assets <dir>, default: bundled snapshot/] | hub-kb open [--dir ./site/dist] [--port 4321]");
1953
+ console.log("usage: hub-kb mcp [--assets <dir>, default: bundled snapshot/]");
1676
1954
  process.exit(1);
1677
1955
  }
1678
1956
  }