@ladderkitlab/hub-kb 0.1.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 ADDED
@@ -0,0 +1,1683 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/main.ts
4
+ import fs3 from "node:fs/promises";
5
+ import path3 from "node:path";
6
+ import { createHash as createHash2 } from "node:crypto";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ // src/stdio.ts
10
+ import fs from "node:fs/promises";
11
+ import path from "node:path";
12
+ import { createHash } from "node:crypto";
13
+
14
+ // ../runtime-core/src/index.ts
15
+ function makeTableT2s(table) {
16
+ return (s) => {
17
+ let out = "";
18
+ for (const ch of s) out += table[ch] ?? ch;
19
+ return out;
20
+ };
21
+ }
22
+ function normalize(text, cfg, t2s) {
23
+ let t = text;
24
+ if (cfg.normalize.fullwidth_to_halfwidth) t = t.replace(/[!-~]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 65248)).replace(/ /g, " ");
25
+ if (cfg.normalize.lowercase) t = t.toLowerCase();
26
+ if (cfg.normalize.traditional_to_simplified) t = t2s(t);
27
+ if (cfg.normalize.strip_version_numbers) t = t.replace(/\d+(\.\d+)+/g, "");
28
+ return t.replace(/\s+/g, " ").trim();
29
+ }
30
+ var cjkRuns = (n) => n.split(/[^一-鿿]+/).filter(Boolean);
31
+ function tokens(text, cfg, t2s) {
32
+ return tokenize(normalize(text, cfg, t2s), cfg);
33
+ }
34
+ function tokenize(n, cfg) {
35
+ const out = /* @__PURE__ */ new Set();
36
+ for (const w of n.match(/[a-z0-9_]+/g) ?? []) out.add(w);
37
+ const k = cfg.tokenizer.cjk_ngram;
38
+ for (const run2 of cjkRuns(n)) {
39
+ for (let i = 0; i + k <= run2.length; i++) out.add(run2.slice(i, i + k));
40
+ if (run2.length < k) out.add(run2);
41
+ }
42
+ return [...out];
43
+ }
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 };
179
+ }
180
+
181
+ // ../redact/context.ts
182
+ var CONFIG_ROOT_KEYS = ["proxies", "proxy-providers", "outbounds", "inbounds", "rules"];
183
+ var ROOTS = new Set(CONFIG_ROOT_KEYS);
184
+ var MAX_DEPTH = 64;
185
+ var BLOCK_PREFIX = /[\s,,。::]/;
186
+ var isKeyChar = (c) => c !== void 0 && /[A-Za-z0-9_-]/.test(c);
187
+ var isNewline = (c) => c === "\n" || c === "\r";
188
+ function configRegions(s) {
189
+ const t = s.text;
190
+ const n = s.n;
191
+ const out = [];
192
+ let depth = 0;
193
+ let outerStart = -1;
194
+ let outerHasRoot = false;
195
+ let deep = false;
196
+ const opens = [];
197
+ let lastKey = null;
198
+ let i = 0;
199
+ const closeOuter = (end) => {
200
+ if (outerHasRoot) out.push({ index: outerStart, end: deep ? n : end });
201
+ outerStart = -1;
202
+ outerHasRoot = false;
203
+ deep = false;
204
+ };
205
+ while (i < n) {
206
+ if (!s.budget.tick()) {
207
+ s.unfinished = Math.min(s.unfinished, outerStart >= 0 ? outerStart : i);
208
+ return out;
209
+ }
210
+ const c = t[i];
211
+ if (c === '"' || c === "'") {
212
+ let j = i + 1;
213
+ while (j < n && !isNewline(t[j])) {
214
+ if (c === '"' && t[j] === "\\" && j + 1 < n && !isNewline(t[j + 1])) {
215
+ j += 2;
216
+ continue;
217
+ }
218
+ if (t[j] === c) {
219
+ if (c === "'" && t[j + 1] === "'") {
220
+ j += 2;
221
+ continue;
222
+ }
223
+ break;
224
+ }
225
+ j++;
226
+ }
227
+ s.budget.tick(j - i);
228
+ const closed = j < n && t[j] === c;
229
+ lastKey = depth > 0 ? { value: t.slice(i + 1, j) } : null;
230
+ i = closed ? j + 1 : j;
231
+ continue;
232
+ }
233
+ if (depth === 0 && c === "#" && (i === 0 || /\s/.test(t[i - 1]))) {
234
+ let j = i;
235
+ while (j < n && !isNewline(t[j])) j++;
236
+ s.budget.tick(j - i);
237
+ lastKey = null;
238
+ i = j;
239
+ continue;
240
+ }
241
+ if (c === "{" || c === "[") {
242
+ if (depth === 0) {
243
+ outerStart = i;
244
+ outerHasRoot = false;
245
+ deep = false;
246
+ }
247
+ opens.push(c);
248
+ depth++;
249
+ if (depth > MAX_DEPTH) deep = true;
250
+ lastKey = null;
251
+ i++;
252
+ continue;
253
+ }
254
+ if (c === "}" || c === "]") {
255
+ if (depth > 0 && opens[depth - 1] === (c === "}" ? "{" : "[")) {
256
+ opens.pop();
257
+ depth--;
258
+ if (depth === 0) {
259
+ const toEnd = deep && outerHasRoot;
260
+ closeOuter(i + 1);
261
+ if (toEnd) return out;
262
+ }
263
+ }
264
+ lastKey = null;
265
+ i++;
266
+ continue;
267
+ }
268
+ if (depth > 0) {
269
+ if (isKeyChar(c)) {
270
+ let j = i;
271
+ while (j < n && isKeyChar(t[j])) j++;
272
+ s.budget.tick(j - i);
273
+ lastKey = { value: t.slice(i, j) };
274
+ i = j;
275
+ continue;
276
+ }
277
+ if (c === ":") {
278
+ if (lastKey && ROOTS.has(lastKey.value.toLowerCase())) outerHasRoot = true;
279
+ lastKey = null;
280
+ i++;
281
+ continue;
282
+ }
283
+ if (!/\s/.test(c)) lastKey = null;
284
+ i++;
285
+ continue;
286
+ }
287
+ if (isKeyChar(c) && (i === 0 || BLOCK_PREFIX.test(t[i - 1]))) {
288
+ let j = i;
289
+ while (j < n && isKeyChar(t[j])) j++;
290
+ s.budget.tick(j - i);
291
+ if (ROOTS.has(t.slice(i, j).toLowerCase())) {
292
+ let k = j;
293
+ while (k < n && /\s/.test(t[k])) k++;
294
+ s.budget.tick(k - j);
295
+ if (t[k] === ":") {
296
+ out.push({ index: i === 0 ? 0 : i - 1, end: n });
297
+ return out;
298
+ }
299
+ }
300
+ i = j;
301
+ continue;
302
+ }
303
+ i++;
304
+ }
305
+ if (depth > 0 && outerHasRoot) out.push({ index: outerStart, end: n });
306
+ return out;
307
+ }
308
+
309
+ // ../redact/budget.ts
310
+ var BUDGET_FACTOR = 64;
311
+ var BUDGET_CONSTANT = 4096;
312
+ var Budget = class {
313
+ visits = 0;
314
+ exhausted = false;
315
+ limit;
316
+ constructor(n, limit) {
317
+ this.limit = limit ?? BUDGET_FACTOR * n + BUDGET_CONSTANT;
318
+ }
319
+ /** 计入 k 次访问;返回是否仍在预算之内。 */
320
+ tick(k = 1) {
321
+ this.visits += k;
322
+ if (this.visits > this.limit) this.exhausted = true;
323
+ return !this.exhausted;
324
+ }
325
+ };
326
+
327
+ // ../redact/placeholders.ts
328
+ var CREDENTIAL_MASK = "(\u7591\u4F3C\u51ED\u636E\uFF0C\u4E0D\u56DE\u663E)";
329
+ var RULE_LABELS = {
330
+ subscription: "[sub]",
331
+ url: "[url]",
332
+ email: "[email]",
333
+ uuid: "[uuid]",
334
+ ipv6: "[ip]",
335
+ ipv4: "[ip]",
336
+ token: "[token]",
337
+ urlcred: "[token]",
338
+ userinfo: "[token]",
339
+ invite: "\u9080\u8BF7\u7801 [invite]",
340
+ config: "\n[config]"
341
+ };
342
+ var ATOMS = [...new Set([...Object.values(RULE_LABELS), CREDENTIAL_MASK].map((s) => s.trim()))].sort((a, b) => b.length - a.length);
343
+ var FIRST = new Set(ATOMS.map((a) => a[0]));
344
+ function placeholderAt(text, i) {
345
+ if (i >= text.length || !FIRST.has(text[i])) return 0;
346
+ for (const a of ATOMS) if (text.startsWith(a, i)) return a.length;
347
+ return 0;
348
+ }
349
+
350
+ // ../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 };
353
+ }
354
+ var IP_LITERAL_MAX = 64;
355
+ var isHexCode = (c) => c >= 48 && c <= 57 || c >= 97 && c <= 102 || c >= 65 && c <= 70;
356
+ function isIpv6(a) {
357
+ if (a === "") return false;
358
+ const halves = a.split("::");
359
+ if (halves.length > 2) return false;
360
+ const compressed = halves.length === 2;
361
+ const parts = halves.map((h) => h === "" ? [] : h.split(":"));
362
+ if (!compressed && parts[0].length < 3) return false;
363
+ let count = 0;
364
+ for (const side of parts) {
365
+ for (let k = 0; k < side.length; k++) {
366
+ const seg = side[k];
367
+ const isLast = side === parts[parts.length - 1] && k === side.length - 1;
368
+ if (isLast && seg.includes(".")) {
369
+ const oct = seg.split(".");
370
+ if (oct.length !== 4 || !oct.every((o) => /^\d{1,3}$/.test(o) && Number(o) <= 255)) return false;
371
+ count += 2;
372
+ continue;
373
+ }
374
+ if (!/^[0-9a-f]{1,4}$/i.test(seg)) return false;
375
+ count++;
376
+ }
377
+ }
378
+ return compressed ? count < 8 : count === 8;
379
+ }
380
+ function ipLiteralAt(s, i) {
381
+ const t = s.text;
382
+ if (t[i] !== "[") return 0;
383
+ let j = i + 1;
384
+ while (j < s.n && j - i <= IP_LITERAL_MAX && (isHexCode(t.charCodeAt(j)) || t[j] === ":" || t[j] === ".")) j++;
385
+ s.budget.tick(j - i);
386
+ if (t[j] !== "]") return 0;
387
+ s.budget.tick(j - i - 1);
388
+ return isIpv6(t.slice(i + 1, j)) ? j + 1 - i : 0;
389
+ }
390
+ var CJK_STOPS = "\uFF0C\u3002\uFF1B\uFF01\uFF1F\u3001";
391
+ var isNewline2 = (c) => c === "\n" || c === "\r";
392
+ var isWs = (c) => c !== void 0 && /\s/.test(c);
393
+ var isInlineWs = (c) => c !== void 0 && c !== "\n" && c !== "\r" && /\s/.test(c);
394
+ var isPrintableAscii = (code) => code >= 33 && code <= 126;
395
+ function restEnd(s, from) {
396
+ const t = s.text;
397
+ let j = from;
398
+ while (j < s.n && !isNewline2(t[j]) && !CJK_STOPS.includes(t[j])) j++;
399
+ s.budget.tick(j - from + 1);
400
+ return j;
401
+ }
402
+ function isAdjacentTerminator(c) {
403
+ return isWs(c) || c === '"' || c === "'" || c === "<" || c === ">" || c === ")" || c === "]" || c === "&" || c === "#" || c === ";" || CJK_STOPS.includes(c);
404
+ }
405
+ var isCloser = (c) => c === ")" || c === "]" || c === ">";
406
+ var isAsciiAlnum = (c) => c !== void 0 && /^[A-Za-z0-9]$/.test(c);
407
+ function continuationStarts(t, n, pos) {
408
+ if (pos >= n) return false;
409
+ const c = t[pos];
410
+ if (isWs(c) || CJK_STOPS.includes(c) || c === "&" || c === "#" || c === ";") return false;
411
+ return !isCloser(c) || isAsciiAlnum(t[pos + 1]);
412
+ }
413
+ function readWrapped(s, open) {
414
+ const t = s.text;
415
+ const close = t[open] === "<" ? ">" : t[open];
416
+ let j = open + 1;
417
+ let raw = "";
418
+ let placeholder = false;
419
+ while (j < s.n) {
420
+ if (!s.budget.tick()) break;
421
+ const len = placeholderAt(t, j);
422
+ if (len) {
423
+ placeholder = true;
424
+ j += len;
425
+ continue;
426
+ }
427
+ const c = t[j];
428
+ if (c === close || isNewline2(c)) break;
429
+ raw += c;
430
+ j++;
431
+ }
432
+ const closed = j < s.n && t[j] === close;
433
+ return { kind: close === ">" ? "angle" : "quote", end: closed ? j + 1 : j, raw, placeholder, closed, atomTail: false };
434
+ }
435
+ function readAdjacent(s, pos, continuation = false) {
436
+ const t = s.text;
437
+ let j = pos;
438
+ let raw = "";
439
+ let placeholder = false;
440
+ let atomTail = false;
441
+ while (j < s.n) {
442
+ if (!s.budget.tick()) break;
443
+ const len = placeholderAt(t, j);
444
+ if (len) {
445
+ placeholder = true;
446
+ j += len;
447
+ atomTail = true;
448
+ continue;
449
+ }
450
+ if (t[j] === "[") {
451
+ const ip = ipLiteralAt(s, j);
452
+ if (ip) {
453
+ raw += t.slice(j, j + ip);
454
+ j += ip;
455
+ atomTail = false;
456
+ continue;
457
+ }
458
+ }
459
+ if (isAdjacentTerminator(t[j]) && !(continuation && j === pos)) break;
460
+ raw += t[j];
461
+ j++;
462
+ atomTail = false;
463
+ }
464
+ return j === pos ? null : { kind: "adjacent", end: j, raw, placeholder, closed: false, atomTail };
465
+ }
466
+ function readSegmentAt(s, pos, continuation = false) {
467
+ const c = s.text[pos];
468
+ return c === '"' || c === "'" || c === "<" ? readWrapped(s, pos) : readAdjacent(s, pos, continuation);
469
+ }
470
+ function isContent(s, raw) {
471
+ s.budget.tick(raw.length + 1);
472
+ let decoded;
473
+ try {
474
+ decoded = decodeURIComponent(raw.replace(/\+/g, " "));
475
+ } catch {
476
+ return true;
477
+ }
478
+ return decoded.trim() !== "";
479
+ }
480
+ function interpretUrlValue(s, start) {
481
+ const cached = s.values.get(start);
482
+ if (cached) {
483
+ s.budget.tick();
484
+ return cached;
485
+ }
486
+ const t = s.text;
487
+ const n = s.n;
488
+ let wide = false;
489
+ let seg = null;
490
+ let j = start;
491
+ while (j < n && isInlineWs(t[j])) j++;
492
+ s.budget.tick(j - start + 1);
493
+ if (j < n && (t[j] === '"' || t[j] === "'")) {
494
+ seg = readWrapped(s, j);
495
+ wide = true;
496
+ } else if (t[start] === "<") seg = readWrapped(s, start);
497
+ else if (j > start) {
498
+ if (j < n && (isPrintableAscii(t.charCodeAt(j)) || placeholderAt(t, j) > 0)) {
499
+ seg = readSegmentAt(s, j);
500
+ wide = true;
501
+ }
502
+ } else seg = readAdjacent(s, start);
503
+ let segments = 0;
504
+ let lastEnd = start;
505
+ let placeholder = false;
506
+ let content = false;
507
+ while (seg) {
508
+ segments++;
509
+ lastEnd = seg.end;
510
+ if (seg.placeholder) placeholder = true;
511
+ if (isContent(s, seg.raw)) {
512
+ content = true;
513
+ break;
514
+ }
515
+ if (s.budget.exhausted || seg.end >= n) break;
516
+ const c = t[seg.end];
517
+ if ((seg.kind !== "adjacent" && seg.closed || seg.atomTail) && continuationStarts(t, n, seg.end)) {
518
+ seg = readSegmentAt(s, seg.end, true);
519
+ continue;
520
+ }
521
+ if (isInlineWs(c)) {
522
+ let k = seg.end;
523
+ while (k < n && isInlineWs(t[k])) k++;
524
+ s.budget.tick(k - seg.end);
525
+ if (k >= n) break;
526
+ const d = t[k];
527
+ const ok = placeholderAt(t, k) > 0 || isPrintableAscii(t.charCodeAt(k)) && d !== "&" && d !== "#" && d !== ";" && d !== ")";
528
+ if (!ok) break;
529
+ seg = readSegmentAt(s, k);
530
+ if (seg) wide = true;
531
+ continue;
532
+ }
533
+ break;
534
+ }
535
+ let record;
536
+ if (s.budget.exhausted && !content) {
537
+ s.unfinished = Math.min(s.unfinished, start);
538
+ record = { state: "uncertain", judgeEnd: lastEnd, displayEnd: n, next: n };
539
+ } else if (segments === 0) {
540
+ record = { state: "none", judgeEnd: start, displayEnd: start, next: start };
541
+ } 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;
550
+ record = { state: "content", judgeEnd: lastEnd, displayEnd, next: displayEnd };
551
+ } else {
552
+ record = { state: placeholder ? "placeholder" : "blank", judgeEnd: lastEnd, displayEnd: lastEnd, next: lastEnd };
553
+ }
554
+ s.values.set(start, record);
555
+ return record;
556
+ }
557
+ var hasCredentialValue = (v) => v.state === "content" || v.state === "uncertain";
558
+
559
+ // ../redact/named-credential.ts
560
+ var URL_CREDENTIAL_NAMES = ["token", "access_token", "auth", "authorization", "key", "api_key", "apikey", "secret", "password", "pwd", "sig", "signature", "session", "sid"];
561
+ var PROSE_CREDENTIAL_NAMES = ["token", "secret", "password", "pwd", "session_id", "sessionid", "cookie", "\u5BC6\u7801", "\u53E3\u4EE4", "\u4EE4\u724C", "\u5BC6\u94A5", "\u79D8\u94A5"];
562
+ var PROSE_KEY_NAME = "key";
563
+ var PROSE_KEY_MIN_LENGTH = 8;
564
+ var AUTH_SCHEME_NAME = "bearer";
565
+ var PROXY_URI_SCHEMES = ["ss", "ssr", "vmess", "vless", "trojan", "hysteria", "hysteria2", "hy2", "tuic", "anytls"];
566
+ 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);
568
+ var PROSE_NAME_FIRST = new Set(PROSE_NAMES_LONGEST_FIRST.map((n) => n[0]));
569
+ var isAsciiName = (name) => name.charCodeAt(0) < 128;
570
+ var SEPARATORS = "=:\uFF1A";
571
+ var isAlnumCode = (x) => x >= 48 && x <= 57 || x >= 65 && x <= 90 || x >= 97 && x <= 122;
572
+ var isAlnumAt = (t, i) => i >= 0 && i < t.length && isAlnumCode(t.charCodeAt(i));
573
+ var isConnector = (c) => c === "_" || c === "-" || c === ".";
574
+ var isTokenCharAt = (t, i) => i < t.length && (isAlnumCode(t.charCodeAt(i)) || t[i] === "_" || t[i] === "-");
575
+ function compoundNameStart(text, nameStart, s) {
576
+ let start = nameStart;
577
+ for (; ; ) {
578
+ let j = start;
579
+ while (j > 0 && isConnector(text[j - 1])) j--;
580
+ if (j === start) break;
581
+ let k = j;
582
+ while (k > 0 && isAlnumAt(text, k - 1)) k--;
583
+ s?.budget.tick(start - k + 1);
584
+ if (k === j) {
585
+ let u = start;
586
+ while (u > 0 && text[u - 1] === "_") u--;
587
+ return u;
588
+ }
589
+ start = k;
590
+ }
591
+ return start;
592
+ }
593
+ var URL_NAME_SET = new Set(URL_CREDENTIAL_NAMES);
594
+ var COMPOUND_URL_NAME = new RegExp(`^_*(?:[a-z0-9]+[_.\\-]+)*(${[...PROSE_CREDENTIAL_NAMES, PROSE_KEY_NAME].join("|")})$`);
595
+ function decodeOnce(s) {
596
+ try {
597
+ return decodeURIComponent(s.replace(/\+/g, " "));
598
+ } catch {
599
+ return s;
600
+ }
601
+ }
602
+ function urlNameClass(rawName) {
603
+ const name = decodeOnce(rawName).toLowerCase();
604
+ const m = COMPOUND_URL_NAME.exec(name);
605
+ if (m) return m[1] === PROSE_KEY_NAME ? "key" : "prose";
606
+ return URL_NAME_SET.has(name) ? "url" : null;
607
+ }
608
+ function keyThresholdEnd(s, from) {
609
+ const t = s.text;
610
+ let k = from;
611
+ while (k < s.n && isWs(t[k])) k++;
612
+ if (k < s.n && (t[k] === '"' || t[k] === "'")) k++;
613
+ let c = 0;
614
+ while (c < PROSE_KEY_MIN_LENGTH && isTokenCharAt(t, k + c)) c++;
615
+ s.budget.tick(k - from + c + 1);
616
+ return c < PROSE_KEY_MIN_LENGTH ? -1 : k + PROSE_KEY_MIN_LENGTH;
617
+ }
618
+ function proseNameAt(text, i) {
619
+ if (!PROSE_NAME_FIRST.has(text[i].toLowerCase())) return null;
620
+ for (const name of PROSE_NAMES_LONGEST_FIRST) if (text.length - i >= name.length && text.slice(i, i + name.length).toLowerCase() === name) return name;
621
+ return null;
622
+ }
623
+ function breakBefore(s) {
624
+ if (s.breakBefore) return s.breakBefore;
625
+ const t = s.text;
626
+ const arr = new Int32Array(s.n + 1);
627
+ let last = -1;
628
+ for (let i = 0; i <= s.n; i++) {
629
+ arr[i] = last;
630
+ if (i < s.n) {
631
+ const code = t.charCodeAt(i);
632
+ if (!isPrintableAscii(code) || `?&#;=<>"'()[]{}`.includes(t[i])) last = i;
633
+ }
634
+ }
635
+ s.budget.tick(s.n + 1);
636
+ s.breakBefore = arr;
637
+ return arr;
638
+ }
639
+ function inUrlParamName(s, pos) {
640
+ const b = breakBefore(s)[pos];
641
+ return b >= 0 && "?&#;".includes(s.text[b]);
642
+ }
643
+ function proseNamedHits(s) {
644
+ if (s.named) return s.named;
645
+ const t = s.text;
646
+ const n = s.n;
647
+ const out = [];
648
+ let i = 0;
649
+ while (i < n) {
650
+ if (!s.budget.tick()) {
651
+ s.unfinished = Math.min(s.unfinished, i);
652
+ break;
653
+ }
654
+ const name = proseNameAt(t, i);
655
+ if (!name || isAsciiName(name) && isAlnumAt(t, i - 1)) {
656
+ i++;
657
+ continue;
658
+ }
659
+ const nameEnd = i + name.length;
660
+ if (name === AUTH_SCHEME_NAME) {
661
+ let k = nameEnd;
662
+ while (k < n && isInlineWs(t[k])) k++;
663
+ let c = 0;
664
+ while (c < PROSE_KEY_MIN_LENGTH && isTokenCharAt(t, k + c)) c++;
665
+ s.budget.tick(k - nameEnd + c + 1);
666
+ if (k === nameEnd || c < PROSE_KEY_MIN_LENGTH) {
667
+ i++;
668
+ continue;
669
+ }
670
+ const end2 = restEnd(s, k + PROSE_KEY_MIN_LENGTH);
671
+ out.push({ index: i, end: end2 });
672
+ i = end2;
673
+ continue;
674
+ }
675
+ let j = nameEnd;
676
+ while (j < n && isWs(t[j])) j++;
677
+ if (j < n && (t[j] === '"' || t[j] === "'")) {
678
+ j++;
679
+ while (j < n && isWs(t[j])) j++;
680
+ }
681
+ s.budget.tick(j - nameEnd + 1);
682
+ if (j >= n || !SEPARATORS.includes(t[j])) {
683
+ i++;
684
+ continue;
685
+ }
686
+ const sepEnd = j + 1;
687
+ let headEnd;
688
+ if (name === PROSE_KEY_NAME) {
689
+ headEnd = keyThresholdEnd(s, sepEnd);
690
+ if (headEnd < 0) {
691
+ i++;
692
+ continue;
693
+ }
694
+ } else {
695
+ let k = sepEnd;
696
+ while (k < n && isInlineWs(t[k])) k++;
697
+ for (let a = placeholderAt(t, k); a > 0; a = placeholderAt(t, k)) k += a;
698
+ s.budget.tick(k - sepEnd + 1);
699
+ if (k >= n || !isPrintableAscii(t.charCodeAt(k))) {
700
+ i++;
701
+ continue;
702
+ }
703
+ headEnd = k + 1;
704
+ }
705
+ const start = compoundNameStart(t, i, s);
706
+ let urlEnd = -1;
707
+ if (t[j] === "=" && j === nameEnd && inUrlParamName(s, start)) {
708
+ const v = interpretUrlValue(s, sepEnd);
709
+ if (!hasCredentialValue(v) && v.state !== "none") {
710
+ i = nameEnd;
711
+ continue;
712
+ }
713
+ urlEnd = v.displayEnd;
714
+ }
715
+ const end = Math.max(restEnd(s, headEnd), urlEnd);
716
+ out.push({ index: start, end });
717
+ i = end > i ? end : i + 1;
718
+ }
719
+ s.named = out;
720
+ return out;
721
+ }
722
+ var PARAM_NAME_CHAR = (t, i) => isAlnumAt(t, i) || t[i] === "_" || t[i] === "." || t[i] === "-";
723
+ var isHex = (c) => c !== void 0 && /[0-9A-Fa-f]/.test(c);
724
+ function paramNameEnd(t, at) {
725
+ let j = at;
726
+ while (j < t.length) {
727
+ if (PARAM_NAME_CHAR(t, j)) {
728
+ j++;
729
+ continue;
730
+ }
731
+ if (t[j] === "%" && isHex(t[j + 1]) && isHex(t[j + 2])) {
732
+ j += 3;
733
+ continue;
734
+ }
735
+ break;
736
+ }
737
+ return j > at && t[j] === "=" ? j : -1;
738
+ }
739
+ function urlCredentialSpans(s) {
740
+ if (s.urlcred) return s.urlcred;
741
+ const t = s.text;
742
+ const n = s.n;
743
+ const out = [];
744
+ let i = 0;
745
+ while (i < n) {
746
+ if (!s.budget.tick()) {
747
+ s.unfinished = Math.min(s.unfinished, i);
748
+ break;
749
+ }
750
+ const c = t[i];
751
+ if (c !== "?" && c !== "&" && c !== "#" && c !== ";") {
752
+ i++;
753
+ continue;
754
+ }
755
+ const eq = paramNameEnd(t, i + 1);
756
+ if (eq < 0) {
757
+ i++;
758
+ continue;
759
+ }
760
+ s.budget.tick(eq - i);
761
+ const cls = urlNameClass(t.slice(i + 1, eq));
762
+ if (!cls) {
763
+ i = eq;
764
+ continue;
765
+ }
766
+ const v = interpretUrlValue(s, eq + 1);
767
+ if (hasCredentialValue(v)) {
768
+ const withName = cls === "prose" || cls === "key" && keyThresholdEnd(s, eq + 1) >= 0;
769
+ out.push({ index: withName ? i + 1 : eq + 1, end: v.displayEnd });
770
+ i = Math.max(v.next, eq + 1);
771
+ } else i = Math.max(v.next, eq + 1);
772
+ }
773
+ s.urlcred = out;
774
+ return out;
775
+ }
776
+ function proxySchemeStart(t, colon) {
777
+ for (const scheme of SCHEMES_LONGEST_FIRST) {
778
+ const from = colon - scheme.length;
779
+ if (from >= 0 && t.slice(from, colon).toLowerCase() === scheme) return from;
780
+ }
781
+ return -1;
782
+ }
783
+
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 === "'";
793
+ function httpHeadAt(t, i) {
794
+ if (t[i] !== "h" && t[i] !== "H") return 0;
795
+ const head = t.slice(i, i + 8).toLowerCase();
796
+ return head.startsWith("https://") ? 8 : head.startsWith("http://") ? 7 : 0;
797
+ }
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
+ };
806
+ }
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 };
812
+ }
813
+ return { len: 0, scanned: stop - i };
814
+ }
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;
819
+ }
820
+ function urlSpans(s) {
821
+ const t = s.text;
822
+ const n = s.n;
823
+ 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;
830
+ };
831
+ const limitAt = valueLimits(s, creds);
832
+ let i = 0;
833
+ while (i < n) {
834
+ if (!s.budget.tick()) {
835
+ s.unfinished = Math.min(s.unfinished, i);
836
+ break;
837
+ }
838
+ const len = httpHeadAt(t, i);
839
+ if (!len) {
840
+ i++;
841
+ continue;
842
+ }
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;
861
+ }
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;
875
+ continue;
876
+ }
877
+ j++;
878
+ }
879
+ s.budget.tick(j - i);
880
+ if (j > i + len) {
881
+ out.push({ index: i, end: j });
882
+ i = j;
883
+ } else i++;
884
+ }
885
+ return out;
886
+ }
887
+ function userinfoSpans(s) {
888
+ const t = s.text;
889
+ const n = s.n;
890
+ const out = [];
891
+ let i = 0;
892
+ while (i < n) {
893
+ if (!s.budget.tick()) {
894
+ s.unfinished = Math.min(s.unfinished, i);
895
+ break;
896
+ }
897
+ const len = httpHeadAt(t, i);
898
+ if (!len) {
899
+ i++;
900
+ continue;
901
+ }
902
+ const from = i + len;
903
+ let j = from;
904
+ let at = -1;
905
+ while (j < n && !isWs(t[j]) && t[j] !== "/" && t[j] !== "?" && t[j] !== "#") {
906
+ if (t[j] === "@") at = j;
907
+ j++;
908
+ }
909
+ s.budget.tick(j - i);
910
+ if (at > from && userinfoHasContent(s, from, at)) out.push({ index: from, end: at });
911
+ i = from;
912
+ }
913
+ return out;
914
+ }
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 === "'";
930
+ function subscriptionKeywordEnd(t, i) {
931
+ if (i > 0 && isWordCode(t.charCodeAt(i - 1))) return -1;
932
+ const c = t[i];
933
+ if (c === "s" || c === "S") {
934
+ const head = t.slice(i, i + 12).toLowerCase();
935
+ if (head === "subscription") return i + 12;
936
+ if (head.startsWith("subscribe")) return i + 9;
937
+ return -1;
938
+ }
939
+ if ((c === "a" || c === "A") && t.slice(i, i + 5).toLowerCase() === "api/v") {
940
+ let j = i + 5;
941
+ const d = j;
942
+ while (j < t.length && t.charCodeAt(j) >= 48 && t.charCodeAt(j) <= 57) j++;
943
+ if (j > d && t.slice(j, j + 7).toLowerCase() === "/client") return j + 7;
944
+ }
945
+ return -1;
946
+ }
947
+ function subscriptionSpans(s) {
948
+ const t = s.text;
949
+ const n = s.n;
950
+ const cands = [];
951
+ const stops = stopPositions(s);
952
+ const limitAt = valueLimits(s);
953
+ const endAt = (pos) => {
954
+ const l = limitAt(pos);
955
+ return l >= 0 ? l : n;
956
+ };
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
+ };
990
+ let i = 0;
991
+ while (i < n) {
992
+ if (!s.budget.tick()) {
993
+ s.unfinished = Math.min(s.unfinished, i);
994
+ break;
995
+ }
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++;
1008
+ continue;
1009
+ }
1010
+ 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;
1015
+ continue;
1016
+ }
1017
+ i++;
1018
+ }
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;
1029
+ }
1030
+ var REGISTERED_ERROR_CODE = /^(?:INSTALL_FAILED|INSTALL_PARSE_FAILED|ERR)(?:_[A-Z]{2,})+$/;
1031
+ function tokenSpans(s) {
1032
+ const t = s.text;
1033
+ const n = s.n;
1034
+ const named = proseNamedHits(s);
1035
+ const runs = [];
1036
+ let i = 0;
1037
+ while (i < n) {
1038
+ if (!s.budget.tick()) {
1039
+ s.unfinished = Math.min(s.unfinished, i);
1040
+ break;
1041
+ }
1042
+ if (!isTokenCode(t.charCodeAt(i))) {
1043
+ i++;
1044
+ continue;
1045
+ }
1046
+ const runStart = i;
1047
+ while (i < n && isTokenCode(t.charCodeAt(i))) i++;
1048
+ s.budget.tick(i - runStart);
1049
+ let p = runStart;
1050
+ while (p < i && t[p] === "-") p++;
1051
+ let e = i;
1052
+ while (e > p && t[e - 1] === "-") e--;
1053
+ const exempt = REGISTERED_ERROR_CODE.test(t.slice(p, e));
1054
+ while (p < e) {
1055
+ let q = p;
1056
+ while (q < e && t[q] !== "-") q++;
1057
+ let hex = q - p >= 32;
1058
+ for (let k2 = p; hex && k2 < q; k2++) hex = isHexCode2(t.charCodeAt(k2));
1059
+ if (hex) {
1060
+ runs.push({ index: p, end: q, branch: "hex" });
1061
+ p = q;
1062
+ while (p < e && t[p] === "-") p++;
1063
+ continue;
1064
+ }
1065
+ if (e - p >= 24 && !exempt) runs.push({ index: p, end: e, branch: "long" });
1066
+ break;
1067
+ }
1068
+ }
1069
+ const pieces = [];
1070
+ let k = 0;
1071
+ for (const r of runs) {
1072
+ let from = r.index;
1073
+ while (k < named.length && named[k].end <= from) k++;
1074
+ for (let m = k; m < named.length && named[m].index < r.end; m++) {
1075
+ if (named[m].index > from) pieces.push({ index: from, end: named[m].index, branch: r.branch });
1076
+ from = Math.max(from, named[m].end);
1077
+ }
1078
+ if (from < r.end) pieces.push({ index: from, end: r.end, branch: r.branch });
1079
+ }
1080
+ return [...named.map((h) => ({ ...h, branch: "named" })), ...pieces].sort((a, b) => a.index - b.index);
1081
+ }
1082
+ function regexInRuns(isClass, re, minLen) {
1083
+ return (s) => {
1084
+ const t = s.text;
1085
+ const n = s.n;
1086
+ const out = [];
1087
+ let i = 0;
1088
+ while (i < n) {
1089
+ if (!s.budget.tick()) {
1090
+ s.unfinished = Math.min(s.unfinished, i);
1091
+ break;
1092
+ }
1093
+ if (!isClass(t.charCodeAt(i))) {
1094
+ i++;
1095
+ continue;
1096
+ }
1097
+ const a = i;
1098
+ while (i < n && isClass(t.charCodeAt(i))) i++;
1099
+ s.budget.tick(i - a);
1100
+ if (i - a < minLen) continue;
1101
+ const from = Math.max(0, a - 1);
1102
+ const slice = t.slice(from, Math.min(n, i + 1));
1103
+ s.budget.tick(slice.length);
1104
+ for (const m of slice.matchAll(re)) {
1105
+ const index = from + (m.index ?? 0);
1106
+ const end = index + m[0].length;
1107
+ if (end > index && index >= a && end <= i) out.push({ index, end });
1108
+ }
1109
+ }
1110
+ return out;
1111
+ };
1112
+ }
1113
+ var EMAIL = regexInRuns((x) => isAlnumCode2(x) || x === 46 || x === 95 || x === 37 || x === 43 || x === 45 || x === 64, /(?<![A-Za-z0-9._%+-])[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+/g, 5);
1114
+ var UUID = regexInRuns((x) => isHexCode2(x) || x === 45, /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, 36);
1115
+ var IPV6 = regexInRuns((x) => isHexCode2(x) || x === 58 || x === 46, /(?<![0-9a-f:])(?:[0-9a-f]{0,4}:){2,}(?:\d{1,3}\.){3}\d{1,3}|(?<![0-9a-f:])(?!(?<=[a-z])::[0-9a-f:]*[g-z])(?:[0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}(?![0-9a-f:])/gi, 2);
1116
+ var IPV4 = regexInRuns((x) => x >= 48 && x <= 57 || x === 46, /(?<!\d)(?:\d{1,3}\.){3}\d{1,3}(?!\d)/g, 7);
1117
+ function inviteSpans(s) {
1118
+ if (s.invites) return s.invites;
1119
+ const t = s.text;
1120
+ const n = s.n;
1121
+ const out = [];
1122
+ const valueEnd = (from) => {
1123
+ let k = from;
1124
+ while (k < n && isWs(t[k])) k++;
1125
+ if (k < n && (t[k] === ":" || t[k] === "\uFF1A" || t[k] === "=")) {
1126
+ k++;
1127
+ while (k < n && isWs(t[k])) k++;
1128
+ }
1129
+ let c = 0;
1130
+ while (k + c < n && isAlnumCode2(t.charCodeAt(k + c))) c++;
1131
+ s.budget.tick(k - from + c + 1);
1132
+ if (c < 6 || c > 12 || k + c < n && t[k + c] === "_") return -1;
1133
+ return k + c;
1134
+ };
1135
+ let i = 0;
1136
+ while (i < n) {
1137
+ if (!s.budget.tick()) {
1138
+ s.unfinished = Math.min(s.unfinished, i);
1139
+ break;
1140
+ }
1141
+ let end = -1;
1142
+ if (t.startsWith("\u9080\u8BF7\u7801", i)) end = valueEnd(i + 3);
1143
+ else if ((t[i] === "i" || t[i] === "I") && t.slice(i, i + 6).toLowerCase() === "invite") {
1144
+ let k = i + 6;
1145
+ while (k < n && isWs(t[k])) k++;
1146
+ s.budget.tick(k - i);
1147
+ if (t.slice(k, k + 4).toLowerCase() === "code") end = valueEnd(k + 4);
1148
+ if (end < 0) end = valueEnd(i + 6);
1149
+ }
1150
+ if (end < 0) {
1151
+ i++;
1152
+ continue;
1153
+ }
1154
+ out.push({ index: i, end });
1155
+ i = end;
1156
+ }
1157
+ s.invites = out;
1158
+ return out;
1159
+ }
1160
+ var RULES = [
1161
+ ["subscription", subscriptionSpans],
1162
+ ["url", urlSpans],
1163
+ ["email", EMAIL],
1164
+ ["uuid", UUID],
1165
+ ["ipv6", IPV6],
1166
+ ["ipv4", IPV4],
1167
+ ["token", tokenSpans],
1168
+ ["urlcred", urlCredentialSpans],
1169
+ ["userinfo", userinfoSpans],
1170
+ ["invite", inviteSpans],
1171
+ ["config", configRegions]
1172
+ ];
1173
+ var RULE_NAMES = RULES.map(([name]) => name);
1174
+ var TOKEN_ORDER = RULE_NAMES.indexOf("token");
1175
+ function sensitiveMatches(text, opts = {}) {
1176
+ const s = newScan(text, opts.budget);
1177
+ const out = [];
1178
+ let exhaustedAt = -1;
1179
+ for (let order = 0; order < RULES.length; order++) {
1180
+ const [name, run2] = RULES[order];
1181
+ for (const sp of run2(s)) if (sp.end > sp.index) out.push({ rule: name, text: text.slice(sp.index, sp.end), index: sp.index, order, ..."branch" in sp ? { branch: sp.branch } : {} });
1182
+ if (s.budget.exhausted) {
1183
+ exhaustedAt = order;
1184
+ break;
1185
+ }
1186
+ }
1187
+ let coverFrom = null;
1188
+ if (exhaustedAt >= 0) {
1189
+ coverFrom = exhaustedAt < RULES.length - 1 ? 0 : Math.min(s.unfinished, text.length);
1190
+ if (coverFrom < text.length) out.push({ rule: "token", text: text.slice(coverFrom), index: coverFrom, order: TOKEN_ORDER, branch: "cover" });
1191
+ }
1192
+ if (opts.stats) {
1193
+ opts.stats.visits = s.budget.visits;
1194
+ opts.stats.exhausted = s.budget.exhausted;
1195
+ opts.stats.coverFrom = coverFrom;
1196
+ }
1197
+ return out;
1198
+ }
1199
+ function render(text, ms, label) {
1200
+ if (ms.length === 0) return text;
1201
+ const owner = new Array(text.length);
1202
+ for (const m of [...ms].sort((a, b) => b.order - a.order)) for (let i2 = m.index; i2 < m.index + m.text.length; i2++) owner[i2] = m;
1203
+ let out = "";
1204
+ let i = 0;
1205
+ while (i < text.length) {
1206
+ if (!owner[i]) {
1207
+ out += text[i];
1208
+ i++;
1209
+ continue;
1210
+ }
1211
+ let last;
1212
+ while (i < text.length && owner[i]) {
1213
+ const cur = owner[i];
1214
+ const l = label(cur);
1215
+ if (l !== last) {
1216
+ out += l;
1217
+ last = l;
1218
+ }
1219
+ while (i < text.length && owner[i] === cur) i++;
1220
+ }
1221
+ }
1222
+ return out;
1223
+ }
1224
+ function redactSensitive(text, mask, opts = {}) {
1225
+ const ms = sensitiveMatches(text, opts);
1226
+ const hits = RULE_NAMES.filter((name) => ms.some((m) => m.rule === name));
1227
+ return { text: render(text, ms, (m) => mask ?? RULE_LABELS[m.rule]), hits };
1228
+ }
1229
+ var SECRET_RULES = /* @__PURE__ */ new Set(["token", "urlcred", "userinfo", "subscription", "ipv4", "ipv6", "invite", "config", "uuid", "email"]);
1230
+ var SHORT_WORD_KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)+$/;
1231
+ function isKnownFalsePositive(m) {
1232
+ if (m.rule === "subscription") return /^subscri/i.test(m.text) && !m.text.includes("://");
1233
+ if (m.rule === "token") return SHORT_WORD_KEBAB.test(m.text) && m.text.split("-").every((p) => p.length <= 16);
1234
+ return false;
1235
+ }
1236
+ function credentialRules(text) {
1237
+ return [...new Set(sensitiveMatches(text).filter((m) => SECRET_RULES.has(m.rule) && !isKnownFalsePositive(m)).map((m) => m.rule))];
1238
+ }
1239
+ function maskSensitive(text, policy, opts = {}) {
1240
+ const ms = sensitiveMatches(text, opts).filter((m) => policy === "exception" || SECRET_RULES.has(m.rule) && !isKnownFalsePositive(m));
1241
+ return render(text, ms, () => CREDENTIAL_MASK);
1242
+ }
1243
+
1244
+ // ../runtime-core/src/usage-rules.ts
1245
+ var PARTIAL_PREFIX = "\u6761\u76EE\u6CA1\u6709\u8986\u76D6\uFF1A";
1246
+ var NOT_KB_LABEL = "\u4EE5\u4E0B\u4E0D\u662F\u77E5\u8BC6\u5E93\u5185\u5BB9\uFF0C\u672A\u7ECF\u6838\u5B9E\uFF1A";
1247
+ var OUT_OF_SCOPE_REPLY = "\u8FD9\u7C7B\u95EE\u9898\u4E0D\u5728\u672C\u77E5\u8BC6\u5E93\u7684\u8303\u56F4\u5185\u3002";
1248
+ var ADJACENT_SCOPE = ["\u63A8\u8350\u6216\u6BD4\u8F83\u673A\u573A\u3001\u670D\u52A1\u5546\uFF08\u542B\u5BBD\u5E26\u8FD0\u8425\u5546\uFF09", "\u670D\u52A1\u5546\uFF08\u673A\u573A\u3001VPS / \u4E91\u670D\u52A1\u5668\u5546\uFF09\u7684\u8D26\u53F7\u4E0E\u552E\u540E\uFF08\u7EED\u8D39\u3001\u9000\u6B3E\u3001\u5C01\u53F7\uFF0C\u542B\u670D\u52A1\u5546\u81EA\u5DF1\u7684\u7FA4\u7EC4\u4E0E\u9891\u9053\uFF09", "\u670D\u52A1\u7AEF\u642D\u5EFA\u4E0E\u8FD0\u7EF4\uFF08VPS\u3001\u9762\u677F\u3001xray / sing-box \u670D\u52A1\u7AEF\uFF09"];
1249
+ var UNRELATED_SCOPE = ["\u7FA4\u52A1\uFF08\u7981\u8A00\u3001\u5165\u7FA4\u9A8C\u8BC1\uFF09", "\u5F00\u53D1\u4E0E\u7F16\u8BD1", "\u8D21\u732E\u4EE3\u7801", "\u653F\u6CBB\u4E0E\u95F2\u804A", "\u4E0E\u4EE3\u7406\u5BA2\u6237\u7AEF\u65E0\u5173\u7684\u8F6F\u786C\u4EF6"];
1250
+ var USAGE_RULES = [
1251
+ `**\u9002\u7528\u8303\u56F4**\uFF1A\u95EE\u9898\u4E0E\u4EE3\u7406\u5BA2\u6237\u7AEF\u7684\u4F7F\u7528\u6709\u5173\uFF0C\u6216\u5C5E\u4E8E\u7B2C 4 \u6B65\u300C\u76F8\u90BB\u4F46\u4E0D\u505A\u300D\u7684\u90A3\u51E0\u7C7B\u65F6\uFF0C\u6309\u4E0B\u9762\u7684\u6D41\u7A0B\u505A\u2014\u2014\u540E\u8005\u6B63\u8981\u9760\u8FD9\u4E2A\u6D41\u7A0B\u56DE\u56FA\u5B9A\u53E5\u3002\u4E0E\u77E5\u8BC6\u5E93\u65E0\u5173\u7684\u95EE\u9898\u2014\u2014${UNRELATED_SCOPE.join("\u3001")}\u2014\u2014\u4E0D\u8C03\u7528\u8FD9\u4E9B\u5DE5\u5177\uFF0C\u4E5F\u4E0D\u7528\u4E0B\u9762\u7684\u56FA\u5B9A\u6587\u5B57\uFF0C\u7167\u5E38\u56DE\u7B54\u3002\u8D70\u8FD9\u4E2A\u6D41\u7A0B\u65F6\uFF0C\u8C03\u7528\u5DE5\u5177\u524D\u540E\u90FD\u4E0D\u5199\u8BF4\u660E\u6216\u8FC7\u7A0B\u6587\u5B57\uFF08\u4E2D\u82F1\u6587\u90FD\u4E0D\u884C\uFF0C\u4F8B\u5982\u300CMost relevant: \u2026\u300D\u300CStep 1 \u2026\u300D\uFF09\uFF0C\u7528\u6237\u770B\u5230\u7684\u53EA\u6709\u6700\u7EC8\u56DE\u7B54\u3002`,
1252
+ "",
1253
+ "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
+ `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",
1257
+ " - \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
+ ` - \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
+ ` - \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
+ ` - \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
+ "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
+ ].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`;
1264
+
1265
+ // ../runtime-core/src/mcp.ts
1266
+ var ID = /^[a-z0-9]+(-[a-z0-9]+)*$/;
1267
+ var MAX_BYTES = 131072;
1268
+ var MAX_ID_UNITS = 256;
1269
+ function channelOf(v, cfg) {
1270
+ return typeof v === "string" && (cfg.channels ?? []).includes(v) ? v : null;
1271
+ }
1272
+ 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
1274
+
1275
+ ${USAGE_RULES}`;
1276
+ 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
+ { 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 } }
1280
+ ];
1281
+ 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) {
1283
+ 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) => {
1285
+ const e = projection.entries[id];
1286
+ return { id: e.id, type: e.type, title: e.title, aliases: [...e.aliases] };
1287
+ }) };
1288
+ }
1289
+ var field = (s) => s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r\n|\r|\n/g, " ");
1290
+ 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");
1292
+ }
1293
+ var bytes = (s) => new TextEncoder().encode(s).length;
1294
+ function vocabulary(index) {
1295
+ const v = /* @__PURE__ */ new Set();
1296
+ for (const d of index.docs) for (const toks of Object.values(d.fields)) for (const t of toks) v.add(t);
1297
+ return v;
1298
+ }
1299
+ var vocabCache = null;
1300
+ function queryText(query, redact) {
1301
+ return redact(query, " ").text;
1302
+ }
1303
+ var LOG_WAIT_MS = 1e3;
1304
+ async function boundedWait(p, ms) {
1305
+ let t;
1306
+ await Promise.race([p, new Promise((resolve) => {
1307
+ t = setTimeout(resolve, ms);
1308
+ })]);
1309
+ clearTimeout(t);
1310
+ }
1311
+ async function logQuery(deps, cleanQuery, rec) {
1312
+ if (!deps.log) return;
1313
+ const index = deps.index;
1314
+ const log = deps.log;
1315
+ const task = (async () => {
1316
+ if (!vocabCache || vocabCache.key !== index.content_version) vocabCache = { key: index.content_version, set: vocabulary(index) };
1317
+ const vocab = vocabCache.set;
1318
+ const terms = tokens(cleanQuery, index.config, deps.t2s).filter((t) => vocab.has(t)).sort().join(" ");
1319
+ await log({ day: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), terms, content_version: index.content_version, fingerprint: await deps.sha256(`${deps.salt}|${terms}`), ...rec });
1320
+ })().catch(() => {
1321
+ });
1322
+ if (deps.waitUntil) deps.waitUntil(task);
1323
+ else await boundedWait(task, LOG_WAIT_MS);
1324
+ }
1325
+ async function handleToolCall(name, args, deps, measure = (r) => bytes(JSON.stringify(r))) {
1326
+ const a = args ?? {};
1327
+ const tool = TOOLS.find((t) => t.name === name);
1328
+ if (!tool) return { ok: false, code: -32602, message: "unknown tool" };
1329
+ const props = tool.inputSchema.properties;
1330
+ for (const k of Object.keys(a)) if (!(k in props)) return { ok: false, code: -32602, message: "unexpected argument" };
1331
+ for (const [k, v] of Object.entries(a)) {
1332
+ 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}` : ""}` };
1334
+ }
1335
+ 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
+ if (name === "list_entries") {
1348
+ const query = typeof a.query === "string" ? a.query : "";
1349
+ if (query.length < 1 || query.length > 200) return { ok: false, code: -32602, message: "query must be 1..200 chars" };
1350
+ 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) };
1352
+ }
1353
+ if (name === "get_entry") {
1354
+ const id = typeof a.id === "string" ? a.id : "";
1355
+ if (!ID.test(id)) return { ok: false, code: -32602, message: "bad id" };
1356
+ const e = Object.hasOwn(deps.projection.entries, id) ? deps.projection.entries[id] : void 0;
1357
+ 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
+ }
1372
+ return { ok: true, result };
1373
+ }
1374
+ return { ok: false, code: -32602, message: "unknown tool" };
1375
+ }
1376
+ function toolEnvelope(name, result) {
1377
+ const text = name === "get_entry" ? result.markdown : name === "list_entries" ? catalogText(result) : JSON.stringify(result);
1378
+ return { content: [{ type: "text", text }], structuredContent: result };
1379
+ }
1380
+ var MODERN_VERSIONS = ["2026-07-28"];
1381
+ var LEGACY_VERSIONS = ["2025-11-25", "2025-06-18"];
1382
+ var SUPPORTED_VERSIONS = [...MODERN_VERSIONS, ...LEGACY_VERSIONS];
1383
+ var PV = "io.modelcontextprotocol/protocolVersion";
1384
+ var CAPS = "io.modelcontextprotocol/clientCapabilities";
1385
+ var SERVER_INFO = "io.modelcontextprotocol/serverInfo";
1386
+ var VERSION_SHAPE = /^\d{4}-\d{2}-\d{2}$/;
1387
+ var CACHE_HINT = { ttlMs: 36e5, cacheScope: "public" };
1388
+ var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1389
+ function metaVersion(m) {
1390
+ if (m.method === "initialize" || !isObj(m.params) || !isObj(m.params._meta)) return void 0;
1391
+ return m.params._meta[PV];
1392
+ }
1393
+ 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;
1395
+ return rawId;
1396
+ }
1397
+ function modernMetaError(meta) {
1398
+ const pv = meta[PV];
1399
+ if (typeof pv !== "string" || !VERSION_SHAPE.test(pv)) return { code: -32602, message: "invalid protocol version" };
1400
+ if (!MODERN_VERSIONS.includes(pv)) return { code: -32022, message: "unsupported protocol version", data: { supported: SUPPORTED_VERSIONS, requested: pv } };
1401
+ if (!isObj(meta[CAPS])) return { code: -32602, message: "missing client capabilities" };
1402
+ return null;
1403
+ }
1404
+ async function callTool(params, deps, measure) {
1405
+ const p = isObj(params) ? params : {};
1406
+ const name = typeof p.name === "string" ? p.name : "";
1407
+ if (p.arguments !== void 0 && !isObj(p.arguments)) return { error: { code: -32602, message: "invalid params" } };
1408
+ if (!MCP_TOOLS.some((t) => t.name === name)) return { error: { code: -32602, message: "unknown tool" } };
1409
+ const r = await handleToolCall(name, p.arguments, deps, (result) => measure(toolEnvelope(name, result)));
1410
+ if (r.ok && name === "get_entry" && deps.openLog) {
1411
+ const res = r.result;
1412
+ const openLog = deps.openLog;
1413
+ const task = openLog({ day: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), entry_id: res.id, content_version: res.content_version, channel: channelOf(deps.channel, deps.index.config) }).catch(() => {
1414
+ });
1415
+ if (deps.waitUntil) deps.waitUntil(task);
1416
+ else await boundedWait(task, LOG_WAIT_MS);
1417
+ }
1418
+ if (r.ok) return { result: toolEnvelope(name, r.result) };
1419
+ if (r.code === -32004 || r.code === -32602 && MCP_TOOLS.some((t) => t.name === name)) return { result: { content: [{ type: "text", text: r.message }], isError: true } };
1420
+ return { error: { code: r.code, message: r.message } };
1421
+ }
1422
+ async function handleJsonRpc(message, deps) {
1423
+ if (Array.isArray(message)) return { jsonrpc: "2.0", id: null, error: { code: -32600, message: "batch requests are not supported (MCP 2025-06-18)" } };
1424
+ const m = message;
1425
+ const id = acceptedId(m?.id);
1426
+ if (id === false) return { jsonrpc: "2.0", id: null, error: { code: -32600, message: "invalid request" } };
1427
+ if (!m || m.jsonrpc !== "2.0" || typeof m.method !== "string") return { jsonrpc: "2.0", id: id ?? null, error: { code: -32600, message: "invalid request" } };
1428
+ if (id === void 0) return null;
1429
+ const reply = (result) => ({ jsonrpc: "2.0", id, result });
1430
+ const fail2 = (e) => ({ jsonrpc: "2.0", id, error: e });
1431
+ const serverInfo = () => ({ name: "hub-kb", version: deps.index.content_version });
1432
+ const pv = metaVersion(m);
1433
+ if (pv === void 0 && m.method === "server/discover") return fail2({ code: -32602, message: "invalid params" });
1434
+ if (pv !== void 0) {
1435
+ const bad = modernMetaError(m.params._meta);
1436
+ if (bad) return fail2(bad);
1437
+ const done = (result) => reply({ ...result, resultType: "complete", _meta: { [SERVER_INFO]: serverInfo() } });
1438
+ switch (m.method) {
1439
+ case "server/discover":
1440
+ return done({ supportedVersions: SUPPORTED_VERSIONS, capabilities: { tools: {} }, ...CACHE_HINT });
1441
+ case "tools/list":
1442
+ return done({ tools: MCP_TOOLS, ...CACHE_HINT });
1443
+ case "tools/call": {
1444
+ const r = await callTool(m.params, deps, (env) => bytes(JSON.stringify(done(env))));
1445
+ return "error" in r ? fail2(r.error) : done(r.result);
1446
+ }
1447
+ // ISS-135(cycle 16):不回显客户端给的方法名。新版删掉了 ping 与 initialize 之外的握手方法,一律 -32601。
1448
+ default:
1449
+ return fail2({ code: -32601, message: "method not found" });
1450
+ }
1451
+ }
1452
+ switch (m.method) {
1453
+ // 旧版版本协商:客户端要的版本我们支持就回同一个,否则回我们支持的最新旧版;只回我们自己的常量,不回显客户端给的串。
1454
+ case "initialize": {
1455
+ const want = m.params?.protocolVersion;
1456
+ return reply({ protocolVersion: typeof want === "string" && LEGACY_VERSIONS.includes(want) ? want : LEGACY_VERSIONS[0], capabilities: { tools: {} }, serverInfo: serverInfo() });
1457
+ }
1458
+ case "ping":
1459
+ return reply({});
1460
+ case "tools/list":
1461
+ return reply({ tools: MCP_TOOLS });
1462
+ case "tools/call": {
1463
+ const r = await callTool(m.params, deps, (env) => bytes(JSON.stringify(reply(env))));
1464
+ return "error" in r ? fail2(r.error) : reply(r.result);
1465
+ }
1466
+ // ISS-135(cycle 16):不回显客户端给的方法名。
1467
+ default:
1468
+ return fail2({ code: -32601, message: "method not found" });
1469
+ }
1470
+ }
1471
+
1472
+ // ../runtime-core/src/body.ts
1473
+ var MAX_JSON_BODY_BYTES = 64 * 1024;
1474
+
1475
+ // src/stdio.ts
1476
+ var ASSET_FILES = ["index.json", "projection.json", "t2s-min.json"];
1477
+ async function loadDepsFromDir(dir, extra) {
1478
+ const manifestText = await fs.readFile(path.join(dir, "release-manifest.json"), "utf8").catch((e) => {
1479
+ if (e.code === "ENOENT") throw new Error(`release-manifest.json missing in ${dir}`);
1480
+ throw e;
1481
+ });
1482
+ const manifest = JSON.parse(manifestText);
1483
+ const [index, projection, table] = await Promise.all(ASSET_FILES.map(async (f) => {
1484
+ const buf = await fs.readFile(path.join(dir, f));
1485
+ const want = manifest?.assets?.[f];
1486
+ 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
+ return JSON.parse(buf.toString("utf8"));
1488
+ }));
1489
+ return { index, projection, t2s: makeTableT2s(table), salt: extra.salt, sha256: extra.sha256, redact: redactSensitive };
1490
+ }
1491
+ var TOO_LARGE = Symbol("request too large");
1492
+ async function* boundedLines(input, maxBytes = MAX_JSON_BODY_BYTES) {
1493
+ const enc = new TextEncoder();
1494
+ const dec = new TextDecoder();
1495
+ let parts = [];
1496
+ let size = 0;
1497
+ let skipping = false;
1498
+ const take = () => {
1499
+ const buf = new Uint8Array(size);
1500
+ let o = 0;
1501
+ for (const p of parts) {
1502
+ buf.set(p, o);
1503
+ o += p.length;
1504
+ }
1505
+ parts = [];
1506
+ size = 0;
1507
+ const line = dec.decode(buf);
1508
+ return line.endsWith("\r") ? line.slice(0, -1) : line;
1509
+ };
1510
+ for await (const raw of input) {
1511
+ const chunk = typeof raw === "string" ? enc.encode(raw) : raw;
1512
+ let start = 0;
1513
+ for (; ; ) {
1514
+ const nl = chunk.indexOf(10, start);
1515
+ const end = nl < 0 ? chunk.length : nl;
1516
+ if (!skipping) {
1517
+ const piece = chunk.subarray(start, end);
1518
+ if (size + piece.length > maxBytes) {
1519
+ parts = [];
1520
+ size = 0;
1521
+ skipping = true;
1522
+ yield TOO_LARGE;
1523
+ } else if (piece.length) {
1524
+ parts.push(new Uint8Array(piece));
1525
+ size += piece.length;
1526
+ }
1527
+ }
1528
+ if (nl < 0) break;
1529
+ if (skipping) skipping = false;
1530
+ else yield take();
1531
+ start = nl + 1;
1532
+ }
1533
+ }
1534
+ if (!skipping && size > 0) yield take();
1535
+ }
1536
+ async function createStdioServer(deps, io) {
1537
+ for (; ; ) {
1538
+ const line = await io.readLine();
1539
+ if (line === null) return;
1540
+ if (line === TOO_LARGE) {
1541
+ await io.write(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "request too large" } }));
1542
+ continue;
1543
+ }
1544
+ const trimmed = line.trim();
1545
+ if (!trimmed) continue;
1546
+ let msg;
1547
+ try {
1548
+ msg = JSON.parse(trimmed);
1549
+ } catch {
1550
+ await io.write(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } }));
1551
+ continue;
1552
+ }
1553
+ const r = await handleJsonRpc(msg, deps);
1554
+ if (r !== null) await io.write(JSON.stringify(r));
1555
+ }
1556
+ }
1557
+
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
+ // src/exit.ts
1598
+ var FALLBACK = "\u52A0\u8F7D\u6216\u8FD0\u884C\u5931\u8D25\uFF08\u8BE6\u60C5\u4E0D\u56DE\u663E\uFF09";
1599
+ var FLUSH_TIMEOUT_MS = 2e3;
1600
+ function makeFail(raw) {
1601
+ const deps = {
1602
+ ...raw,
1603
+ timer: raw.timer ?? ((cb, ms) => setTimeout(cb, ms)),
1604
+ clear: raw.clear ?? ((h) => clearTimeout(h))
1605
+ };
1606
+ let exiting = null;
1607
+ const report = async (e) => {
1608
+ let msg = FALLBACK;
1609
+ try {
1610
+ msg = maskSensitive(String(e?.message ?? e), "exception");
1611
+ } catch {
1612
+ }
1613
+ 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
+ }
1626
+ };
1627
+ return (e) => {
1628
+ if (exiting) return exiting;
1629
+ exiting = Promise.resolve().then(() => report(e)).catch(() => {
1630
+ }).then(() => deps.exit(1));
1631
+ return exiting;
1632
+ };
1633
+ }
1634
+ function installProcessHandlers(proc, fail2) {
1635
+ proc.on("unhandledRejection", fail2);
1636
+ proc.on("uncaughtException", fail2);
1637
+ }
1638
+
1639
+ // src/main.ts
1640
+ var [cmd, ...rest] = process.argv.slice(2);
1641
+ var opt = (name, def) => {
1642
+ const i = rest.indexOf(`--${name}`);
1643
+ return i >= 0 && rest[i + 1] ? rest[i + 1] : def;
1644
+ };
1645
+ var fail = makeFail({ error: (m) => console.error(m), exit: (c) => process.exit(c), streams: [process.stdout, process.stderr] });
1646
+ installProcessHandlers(process, fail);
1647
+ async function run() {
1648
+ 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") });
1651
+ const it = boundedLines(process.stdin)[Symbol.asyncIterator]();
1652
+ await createStdioServer(deps, { readLine: async () => {
1653
+ const n = await it.next();
1654
+ return n.done ? null : n.value;
1655
+ }, write: (l) => new Promise((resolve, reject) => {
1656
+ const onDrain = () => {
1657
+ process.stdout.off("error", onError);
1658
+ resolve();
1659
+ };
1660
+ const onError = (e) => {
1661
+ process.stdout.off("drain", onDrain);
1662
+ reject(e);
1663
+ };
1664
+ if (process.stdout.write(l + "\n")) resolve();
1665
+ else {
1666
+ process.stdout.once("drain", onDrain);
1667
+ process.stdout.once("error", onError);
1668
+ }
1669
+ }) });
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
+ } else {
1675
+ console.log("usage: hub-kb mcp [--assets <dir>, default: bundled snapshot/] | hub-kb open [--dir ./site/dist] [--port 4321]");
1676
+ process.exit(1);
1677
+ }
1678
+ }
1679
+ try {
1680
+ await run();
1681
+ } catch (e) {
1682
+ await fail(e);
1683
+ }