@j0hanz/superfetch 1.2.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +131 -156
  2. package/dist/config/auth-config.d.ts +16 -0
  3. package/dist/config/auth-config.js +53 -0
  4. package/dist/config/constants.d.ts +11 -13
  5. package/dist/config/constants.js +1 -3
  6. package/dist/config/env-parsers.d.ts +7 -0
  7. package/dist/config/env-parsers.js +84 -0
  8. package/dist/config/formatting.d.ts +2 -2
  9. package/dist/config/index.d.ts +47 -53
  10. package/dist/config/index.js +35 -64
  11. package/dist/config/types/content.d.ts +1 -49
  12. package/dist/config/types/runtime.d.ts +8 -16
  13. package/dist/config/types/tools.d.ts +2 -28
  14. package/dist/http/accept-policy.d.ts +3 -0
  15. package/dist/http/accept-policy.js +45 -0
  16. package/dist/http/async-handler.d.ts +2 -0
  17. package/dist/http/async-handler.js +5 -0
  18. package/dist/http/auth-introspection.d.ts +2 -0
  19. package/dist/http/auth-introspection.js +141 -0
  20. package/dist/http/auth-static.d.ts +2 -0
  21. package/dist/http/auth-static.js +23 -0
  22. package/dist/http/auth.d.ts +3 -2
  23. package/dist/http/auth.js +254 -23
  24. package/dist/http/cors.d.ts +6 -6
  25. package/dist/http/cors.js +7 -42
  26. package/dist/http/download-routes.d.ts +0 -12
  27. package/dist/http/download-routes.js +21 -58
  28. package/dist/http/host-allowlist.d.ts +3 -0
  29. package/dist/http/host-allowlist.js +117 -0
  30. package/dist/http/jsonrpc-http.d.ts +2 -0
  31. package/dist/http/jsonrpc-http.js +10 -0
  32. package/dist/http/mcp-routes.d.ts +8 -3
  33. package/dist/http/mcp-routes.js +137 -31
  34. package/dist/http/mcp-session-eviction.d.ts +3 -0
  35. package/dist/http/mcp-session-eviction.js +24 -0
  36. package/dist/http/mcp-session-helpers.d.ts +0 -1
  37. package/dist/http/mcp-session-helpers.js +1 -1
  38. package/dist/http/mcp-session-init.d.ts +7 -0
  39. package/dist/http/mcp-session-init.js +94 -0
  40. package/dist/http/mcp-session-slots.d.ts +17 -0
  41. package/dist/http/mcp-session-slots.js +55 -0
  42. package/dist/http/mcp-session-transport-init.d.ts +7 -0
  43. package/dist/http/mcp-session-transport-init.js +41 -0
  44. package/dist/http/mcp-session-transport.d.ts +7 -0
  45. package/dist/http/mcp-session-transport.js +57 -0
  46. package/dist/http/mcp-session-types.d.ts +5 -0
  47. package/dist/http/mcp-session-types.js +1 -0
  48. package/dist/http/mcp-session.d.ts +9 -9
  49. package/dist/http/mcp-session.js +15 -137
  50. package/dist/http/mcp-sessions.d.ts +43 -0
  51. package/dist/http/mcp-sessions.js +392 -0
  52. package/dist/http/mcp-validation.d.ts +1 -0
  53. package/dist/http/mcp-validation.js +11 -10
  54. package/dist/http/protocol-policy.d.ts +2 -0
  55. package/dist/http/protocol-policy.js +31 -0
  56. package/dist/http/rate-limit.js +7 -4
  57. package/dist/http/server-config.d.ts +1 -0
  58. package/dist/http/server-config.js +40 -0
  59. package/dist/http/server-middleware.d.ts +7 -9
  60. package/dist/http/server-middleware.js +9 -70
  61. package/dist/http/server-shutdown.d.ts +4 -0
  62. package/dist/http/server-shutdown.js +43 -0
  63. package/dist/http/server.d.ts +10 -0
  64. package/dist/http/server.js +546 -61
  65. package/dist/http/session-cleanup.js +8 -5
  66. package/dist/middleware/error-handler.d.ts +1 -1
  67. package/dist/middleware/error-handler.js +32 -33
  68. package/dist/resources/cached-content-params.d.ts +5 -0
  69. package/dist/resources/cached-content-params.js +36 -0
  70. package/dist/resources/cached-content.js +67 -125
  71. package/dist/resources/index.js +0 -82
  72. package/dist/server.js +50 -29
  73. package/dist/services/cache-events.d.ts +8 -0
  74. package/dist/services/cache-events.js +19 -0
  75. package/dist/services/cache-keys.d.ts +7 -0
  76. package/dist/services/cache-keys.js +57 -0
  77. package/dist/services/cache.d.ts +4 -9
  78. package/dist/services/cache.js +77 -139
  79. package/dist/services/context.d.ts +0 -1
  80. package/dist/services/context.js +0 -7
  81. package/dist/services/extractor.js +55 -116
  82. package/dist/services/fetcher/agents.d.ts +2 -2
  83. package/dist/services/fetcher/agents.js +35 -96
  84. package/dist/services/fetcher/dns-selection.d.ts +2 -0
  85. package/dist/services/fetcher/dns-selection.js +72 -0
  86. package/dist/services/fetcher/interceptors.d.ts +0 -22
  87. package/dist/services/fetcher/interceptors.js +18 -32
  88. package/dist/services/fetcher/redirects.js +16 -7
  89. package/dist/services/fetcher/response.js +79 -34
  90. package/dist/services/fetcher.d.ts +22 -3
  91. package/dist/services/fetcher.js +544 -44
  92. package/dist/services/fifo-queue.d.ts +8 -0
  93. package/dist/services/fifo-queue.js +25 -0
  94. package/dist/services/logger.js +2 -2
  95. package/dist/services/metadata-collector.d.ts +1 -9
  96. package/dist/services/metadata-collector.js +71 -2
  97. package/dist/services/transform-worker-pool.d.ts +4 -14
  98. package/dist/services/transform-worker-pool.js +177 -129
  99. package/dist/services/transform-worker-types.d.ts +32 -0
  100. package/dist/services/transform-worker-types.js +14 -0
  101. package/dist/tools/handlers/fetch-markdown.tool.d.ts +3 -4
  102. package/dist/tools/handlers/fetch-markdown.tool.js +20 -72
  103. package/dist/tools/handlers/fetch-single.shared.d.ts +11 -22
  104. package/dist/tools/handlers/fetch-single.shared.js +175 -89
  105. package/dist/tools/handlers/fetch-url.tool.d.ts +7 -1
  106. package/dist/tools/handlers/fetch-url.tool.js +84 -119
  107. package/dist/tools/index.js +21 -40
  108. package/dist/tools/schemas.d.ts +1 -51
  109. package/dist/tools/schemas.js +1 -107
  110. package/dist/tools/utils/cached-markdown.d.ts +5 -0
  111. package/dist/tools/utils/cached-markdown.js +46 -0
  112. package/dist/tools/utils/content-shaping.d.ts +4 -0
  113. package/dist/tools/utils/content-shaping.js +67 -0
  114. package/dist/tools/utils/content-transform.d.ts +5 -17
  115. package/dist/tools/utils/content-transform.js +134 -114
  116. package/dist/tools/utils/fetch-pipeline.d.ts +0 -8
  117. package/dist/tools/utils/fetch-pipeline.js +57 -63
  118. package/dist/tools/utils/frontmatter.d.ts +3 -0
  119. package/dist/tools/utils/frontmatter.js +73 -0
  120. package/dist/tools/utils/inline-content.d.ts +1 -2
  121. package/dist/tools/utils/inline-content.js +4 -7
  122. package/dist/tools/utils/markdown-heuristics.d.ts +1 -0
  123. package/dist/tools/utils/markdown-heuristics.js +19 -0
  124. package/dist/tools/utils/markdown-signals.d.ts +1 -0
  125. package/dist/tools/utils/markdown-signals.js +19 -0
  126. package/dist/tools/utils/raw-markdown-frontmatter.d.ts +3 -0
  127. package/dist/tools/utils/raw-markdown-frontmatter.js +73 -0
  128. package/dist/tools/utils/raw-markdown.d.ts +6 -0
  129. package/dist/tools/utils/raw-markdown.js +135 -0
  130. package/dist/transformers/markdown/fenced-code-rule.d.ts +2 -0
  131. package/dist/transformers/markdown/fenced-code-rule.js +38 -0
  132. package/dist/transformers/markdown/frontmatter.d.ts +2 -0
  133. package/dist/transformers/markdown/frontmatter.js +45 -0
  134. package/dist/transformers/markdown/noise-rule.d.ts +2 -0
  135. package/dist/transformers/markdown/noise-rule.js +80 -0
  136. package/dist/transformers/markdown/turndown-instance.d.ts +2 -0
  137. package/dist/transformers/markdown/turndown-instance.js +19 -0
  138. package/dist/transformers/markdown.d.ts +2 -0
  139. package/dist/transformers/markdown.js +185 -0
  140. package/dist/transformers/markdown.transformer.js +5 -117
  141. package/dist/utils/cached-payload.d.ts +7 -0
  142. package/dist/utils/cached-payload.js +36 -0
  143. package/dist/utils/code-language-bash.d.ts +1 -0
  144. package/dist/utils/code-language-bash.js +48 -0
  145. package/dist/utils/code-language-core.d.ts +2 -0
  146. package/dist/utils/code-language-core.js +13 -0
  147. package/dist/utils/code-language-detectors.d.ts +5 -0
  148. package/dist/utils/code-language-detectors.js +142 -0
  149. package/dist/utils/code-language-helpers.d.ts +5 -0
  150. package/dist/utils/code-language-helpers.js +62 -0
  151. package/dist/utils/code-language-parsing.d.ts +5 -0
  152. package/dist/utils/code-language-parsing.js +62 -0
  153. package/dist/utils/code-language.d.ts +9 -0
  154. package/dist/utils/code-language.js +250 -46
  155. package/dist/utils/error-details.d.ts +3 -0
  156. package/dist/utils/error-details.js +12 -0
  157. package/dist/utils/error-utils.js +1 -1
  158. package/dist/utils/filename-generator.js +34 -12
  159. package/dist/utils/guards.d.ts +1 -0
  160. package/dist/utils/guards.js +3 -0
  161. package/dist/utils/header-normalizer.d.ts +0 -3
  162. package/dist/utils/header-normalizer.js +3 -3
  163. package/dist/utils/ip-address.d.ts +4 -0
  164. package/dist/utils/ip-address.js +6 -0
  165. package/dist/utils/tool-error-handler.d.ts +2 -2
  166. package/dist/utils/tool-error-handler.js +14 -46
  167. package/dist/utils/url-transformer.d.ts +7 -0
  168. package/dist/utils/url-transformer.js +147 -0
  169. package/dist/utils/url-validator.d.ts +1 -2
  170. package/dist/utils/url-validator.js +53 -114
  171. package/dist/workers/content-transform.worker.d.ts +1 -0
  172. package/dist/workers/content-transform.worker.js +40 -0
  173. package/package.json +17 -18
@@ -0,0 +1,5 @@
1
+ export declare function containsJsxTag(code: string): boolean;
2
+ export declare function containsWord(source: string, word: string): boolean;
3
+ export declare function splitLines(content: string): string[];
4
+ export declare function extractLanguageFromClassName(className: string): string | undefined;
5
+ export declare function resolveLanguageFromDataAttribute(dataLang: string): string | undefined;
@@ -0,0 +1,62 @@
1
+ export function containsJsxTag(code) {
2
+ for (let index = 0; index < code.length - 1; index += 1) {
3
+ if (code[index] !== '<')
4
+ continue;
5
+ const next = code[index + 1];
6
+ if (!next)
7
+ continue;
8
+ if (next >= 'A' && next <= 'Z')
9
+ return true;
10
+ }
11
+ return false;
12
+ }
13
+ export function containsWord(source, word) {
14
+ let startIndex = source.indexOf(word);
15
+ while (startIndex !== -1) {
16
+ const before = startIndex === 0 ? '' : source[startIndex - 1];
17
+ const afterIndex = startIndex + word.length;
18
+ const after = afterIndex >= source.length ? '' : source[afterIndex];
19
+ if (!isWordChar(before) && !isWordChar(after))
20
+ return true;
21
+ startIndex = source.indexOf(word, startIndex + word.length);
22
+ }
23
+ return false;
24
+ }
25
+ export function splitLines(content) {
26
+ return content.split('\n');
27
+ }
28
+ export function extractLanguageFromClassName(className) {
29
+ const tokens = className.match(/\S+/g);
30
+ if (!tokens)
31
+ return undefined;
32
+ for (const token of tokens) {
33
+ const lower = token.toLowerCase();
34
+ if (lower.startsWith('language-'))
35
+ return token.slice('language-'.length);
36
+ if (lower.startsWith('lang-'))
37
+ return token.slice('lang-'.length);
38
+ if (lower.startsWith('highlight-')) {
39
+ return token.slice('highlight-'.length);
40
+ }
41
+ }
42
+ return undefined;
43
+ }
44
+ export function resolveLanguageFromDataAttribute(dataLang) {
45
+ const trimmed = dataLang.trim();
46
+ if (!trimmed)
47
+ return undefined;
48
+ for (const char of trimmed) {
49
+ if (!isWordChar(char))
50
+ return undefined;
51
+ }
52
+ return trimmed;
53
+ }
54
+ function isWordChar(char) {
55
+ if (!char)
56
+ return false;
57
+ const code = char.charCodeAt(0);
58
+ return ((code >= 48 && code <= 57) ||
59
+ (code >= 65 && code <= 90) ||
60
+ (code >= 97 && code <= 122) ||
61
+ char === '_');
62
+ }
@@ -0,0 +1,5 @@
1
+ export declare function containsJsxTag(code: string): boolean;
2
+ export declare function containsWord(source: string, word: string): boolean;
3
+ export declare function splitLines(content: string): string[];
4
+ export declare function extractLanguageFromClassName(className: string): string | undefined;
5
+ export declare function resolveLanguageFromDataAttribute(dataLang: string): string | undefined;
@@ -0,0 +1,62 @@
1
+ export function containsJsxTag(code) {
2
+ for (let index = 0; index < code.length - 1; index += 1) {
3
+ if (code[index] !== '<')
4
+ continue;
5
+ const next = code[index + 1];
6
+ if (!next)
7
+ continue;
8
+ if (next >= 'A' && next <= 'Z')
9
+ return true;
10
+ }
11
+ return false;
12
+ }
13
+ export function containsWord(source, word) {
14
+ let startIndex = source.indexOf(word);
15
+ while (startIndex !== -1) {
16
+ const before = startIndex === 0 ? '' : source[startIndex - 1];
17
+ const afterIndex = startIndex + word.length;
18
+ const after = afterIndex >= source.length ? '' : source[afterIndex];
19
+ if (!isWordChar(before) && !isWordChar(after))
20
+ return true;
21
+ startIndex = source.indexOf(word, startIndex + word.length);
22
+ }
23
+ return false;
24
+ }
25
+ export function splitLines(content) {
26
+ return content.split('\n');
27
+ }
28
+ export function extractLanguageFromClassName(className) {
29
+ const tokens = className.match(/\S+/g);
30
+ if (!tokens)
31
+ return undefined;
32
+ for (const token of tokens) {
33
+ const lower = token.toLowerCase();
34
+ if (lower.startsWith('language-'))
35
+ return token.slice('language-'.length);
36
+ if (lower.startsWith('lang-'))
37
+ return token.slice('lang-'.length);
38
+ if (lower.startsWith('highlight-')) {
39
+ return token.slice('highlight-'.length);
40
+ }
41
+ }
42
+ return undefined;
43
+ }
44
+ export function resolveLanguageFromDataAttribute(dataLang) {
45
+ const trimmed = dataLang.trim();
46
+ if (!trimmed)
47
+ return undefined;
48
+ for (const char of trimmed) {
49
+ if (!isWordChar(char))
50
+ return undefined;
51
+ }
52
+ return trimmed;
53
+ }
54
+ function isWordChar(char) {
55
+ if (!char)
56
+ return false;
57
+ const code = char.charCodeAt(0);
58
+ return ((code >= 48 && code <= 57) ||
59
+ (code >= 65 && code <= 90) ||
60
+ (code >= 97 && code <= 122) ||
61
+ char === '_');
62
+ }
@@ -1,2 +1,11 @@
1
+ export declare function containsJsxTag(code: string): boolean;
2
+ export declare function containsWord(source: string, word: string): boolean;
3
+ export declare function splitLines(content: string): string[];
4
+ export declare function extractLanguageFromClassName(className: string): string | undefined;
5
+ export declare function resolveLanguageFromDataAttribute(dataLang: string): string | undefined;
6
+ export interface CodeDetector {
7
+ language: string;
8
+ detect: (code: string) => boolean;
9
+ }
1
10
  export declare function detectLanguageFromCode(code: string): string | undefined;
2
11
  export declare function resolveLanguageFromAttributes(className: string, dataLang: string): string | undefined;
@@ -1,56 +1,260 @@
1
- const CODE_PATTERNS = [
2
- [
3
- /^\s*import\s+.*\s+from\s+['"]react['"]|<[A-Z][a-zA-Z]*[\s/>]|jsx\s*:|className=/m,
4
- 'jsx',
5
- ],
6
- [
7
- /:\s*(string|number|boolean|void|any|unknown|never)\b|interface\s+\w+|type\s+\w+\s*=/m,
8
- 'typescript',
9
- ],
10
- [/^\s*(fn|let\s+mut|impl|struct|enum|use\s+\w+::)/m, 'rust'],
11
- [
12
- /^\s*(export|const|let|var|function|class|async|await)\b|^\s*import\s+.*['"]]/m,
13
- 'javascript',
14
- ],
15
- [/^\s*(def|class|import|from|if __name__|print\()/m, 'python'],
16
- [
17
- /^\s*(npm|yarn|pnpm|npx|brew|apt|pip|cargo|go )\s+(install|add|run|build|start)/m,
18
- 'bash',
19
- ],
20
- [/^\s*[$#]\s+\w+|^\s*#!|^\s*(sudo|chmod|mkdir|cd|ls|cat|echo)\s+/m, 'bash'],
21
- [/^\s*[.#@]?[\w-]+\s*\{[^}]*\}|@media|@import|@keyframes/m, 'css'],
22
- [/^\s*<(!DOCTYPE|html|head|body|div|span|p|a|script|style)\b/im, 'html'],
23
- [/^\s*\{\s*"|^\s*\[\s*("|\d|true|false|null)/m, 'json'],
24
- [/^\s*[\w-]+:\s*.+$/m, 'yaml'],
25
- [/^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|ALTER|DROP)\s+/im, 'sql'],
26
- [/^\s*(func|package|import\s+")/m, 'go'],
1
+ export function containsJsxTag(code) {
2
+ for (let index = 0; index < code.length - 1; index += 1) {
3
+ if (code[index] !== '<')
4
+ continue;
5
+ const next = code[index + 1];
6
+ if (!next)
7
+ continue;
8
+ if (next >= 'A' && next <= 'Z')
9
+ return true;
10
+ }
11
+ return false;
12
+ }
13
+ export function containsWord(source, word) {
14
+ let startIndex = source.indexOf(word);
15
+ while (startIndex !== -1) {
16
+ const before = startIndex === 0 ? '' : source[startIndex - 1];
17
+ const afterIndex = startIndex + word.length;
18
+ const after = afterIndex >= source.length ? '' : source[afterIndex];
19
+ if (!isWordChar(before) && !isWordChar(after))
20
+ return true;
21
+ startIndex = source.indexOf(word, startIndex + word.length);
22
+ }
23
+ return false;
24
+ }
25
+ export function splitLines(content) {
26
+ return content.split('\n');
27
+ }
28
+ export function extractLanguageFromClassName(className) {
29
+ const tokens = className.match(/\S+/g);
30
+ if (!tokens)
31
+ return undefined;
32
+ for (const token of tokens) {
33
+ const lower = token.toLowerCase();
34
+ if (lower.startsWith('language-'))
35
+ return token.slice('language-'.length);
36
+ if (lower.startsWith('lang-'))
37
+ return token.slice('lang-'.length);
38
+ if (lower.startsWith('highlight-')) {
39
+ return token.slice('highlight-'.length);
40
+ }
41
+ }
42
+ return undefined;
43
+ }
44
+ export function resolveLanguageFromDataAttribute(dataLang) {
45
+ const trimmed = dataLang.trim();
46
+ if (!trimmed)
47
+ return undefined;
48
+ for (const char of trimmed) {
49
+ if (!isWordChar(char))
50
+ return undefined;
51
+ }
52
+ return trimmed;
53
+ }
54
+ function isWordChar(char) {
55
+ if (!char)
56
+ return false;
57
+ const code = char.charCodeAt(0);
58
+ return ((code >= 48 && code <= 57) ||
59
+ (code >= 65 && code <= 90) ||
60
+ (code >= 97 && code <= 122) ||
61
+ char === '_');
62
+ }
63
+ const BASH_PACKAGE_MANAGERS = [
64
+ 'npm',
65
+ 'yarn',
66
+ 'pnpm',
67
+ 'npx',
68
+ 'brew',
69
+ 'apt',
70
+ 'pip',
71
+ 'cargo',
72
+ 'go',
73
+ ];
74
+ const BASH_VERBS = ['install', 'add', 'run', 'build', 'start'];
75
+ const BASH_COMMANDS = ['sudo', 'chmod', 'mkdir', 'cd', 'ls', 'cat', 'echo'];
76
+ function detectBash(code) {
77
+ const lines = splitLines(code);
78
+ for (const line of lines) {
79
+ const trimmed = line.trimStart();
80
+ if (!trimmed)
81
+ continue;
82
+ if (isBashIndicator(trimmed))
83
+ return true;
84
+ }
85
+ return false;
86
+ }
87
+ function startsWithCommand(line, commands) {
88
+ return commands.some((command) => line === command || line.startsWith(`${command} `));
89
+ }
90
+ function isBashIndicator(line) {
91
+ return (isShebang(line) ||
92
+ isPromptLine(line) ||
93
+ startsWithCommand(line, BASH_COMMANDS) ||
94
+ startsWithPackageManagerCommand(line));
95
+ }
96
+ function isShebang(line) {
97
+ return line.startsWith('#!');
98
+ }
99
+ function isPromptLine(line) {
100
+ return line.startsWith('$ ') || line.startsWith('# ');
101
+ }
102
+ function startsWithPackageManagerCommand(line) {
103
+ return BASH_PACKAGE_MANAGERS.some((manager) => {
104
+ if (!line.startsWith(`${manager} `))
105
+ return false;
106
+ const rest = line.slice(manager.length + 1);
107
+ return BASH_VERBS.some((verb) => rest === verb || rest.startsWith(`${verb} `));
108
+ });
109
+ }
110
+ const TYPE_HINTS = [
111
+ 'string',
112
+ 'number',
113
+ 'boolean',
114
+ 'void',
115
+ 'any',
116
+ 'unknown',
117
+ 'never',
118
+ ];
119
+ const HTML_TAGS = [
120
+ '<!doctype',
121
+ '<html',
122
+ '<head',
123
+ '<body',
124
+ '<div',
125
+ '<span',
126
+ '<p',
127
+ '<a',
128
+ '<script',
129
+ '<style',
130
+ ];
131
+ const SQL_KEYWORDS = [
132
+ 'select',
133
+ 'insert',
134
+ 'update',
135
+ 'delete',
136
+ 'create',
137
+ 'alter',
138
+ 'drop',
27
139
  ];
28
- const CLASS_PATTERNS = [
29
- /language-(\w+)/,
30
- /lang-(\w+)/,
31
- /highlight-(\w+)/,
140
+ const JS_WORD_REGEX = /\b(?:const|let|var|function|class|async|await|export|import)\b/;
141
+ const PYTHON_WORD_REGEX = /\b(?:def|class|import|from)\b/;
142
+ const RUST_WORD_REGEX = /\b(?:fn|impl|struct|enum)\b/;
143
+ const CSS_DIRECTIVE_REGEX = /@media|@import|@keyframes/;
144
+ const CODE_DETECTORS = [
145
+ { language: 'jsx', detect: detectJsx },
146
+ { language: 'typescript', detect: detectTypescript },
147
+ { language: 'rust', detect: detectRust },
148
+ { language: 'javascript', detect: detectJavascript },
149
+ { language: 'python', detect: detectPython },
150
+ { language: 'bash', detect: detectBash },
151
+ { language: 'css', detect: detectCss },
152
+ { language: 'html', detect: detectHtml },
153
+ { language: 'json', detect: detectJson },
154
+ { language: 'yaml', detect: detectYaml },
155
+ { language: 'sql', detect: detectSql },
156
+ { language: 'go', detect: detectGo },
32
157
  ];
158
+ function detectJsx(code) {
159
+ const lower = code.toLowerCase();
160
+ if (lower.includes('classname='))
161
+ return true;
162
+ if (lower.includes('jsx:'))
163
+ return true;
164
+ if (lower.includes("from 'react'") || lower.includes('from "react"')) {
165
+ return true;
166
+ }
167
+ return containsJsxTag(code);
168
+ }
169
+ function detectTypescript(code) {
170
+ const lower = code.toLowerCase();
171
+ if (containsWord(lower, 'interface'))
172
+ return true;
173
+ if (containsWord(lower, 'type'))
174
+ return true;
175
+ return TYPE_HINTS.some((hint) => lower.includes(`: ${hint}`) || lower.includes(`:${hint}`));
176
+ }
177
+ function detectRust(code) {
178
+ const lower = code.toLowerCase();
179
+ return (RUST_WORD_REGEX.test(lower) ||
180
+ lower.includes('let mut') ||
181
+ (lower.includes('use ') && lower.includes('::')));
182
+ }
183
+ function detectJavascript(code) {
184
+ const lower = code.toLowerCase();
185
+ return JS_WORD_REGEX.test(lower);
186
+ }
187
+ function detectPython(code) {
188
+ const lower = code.toLowerCase();
189
+ return (PYTHON_WORD_REGEX.test(lower) ||
190
+ lower.includes('print(') ||
191
+ lower.includes('__name__'));
192
+ }
193
+ function detectCss(code) {
194
+ const lower = code.toLowerCase();
195
+ if (CSS_DIRECTIVE_REGEX.test(lower))
196
+ return true;
197
+ const lines = splitLines(code);
198
+ for (const line of lines) {
199
+ const trimmed = line.trimStart();
200
+ if (!trimmed)
201
+ continue;
202
+ if (isCssSelectorLine(trimmed) || isCssPropertyLine(trimmed))
203
+ return true;
204
+ }
205
+ return false;
206
+ }
207
+ function detectHtml(code) {
208
+ const lower = code.toLowerCase();
209
+ return HTML_TAGS.some((tag) => lower.includes(tag));
210
+ }
211
+ function detectJson(code) {
212
+ const trimmed = code.trimStart();
213
+ if (!trimmed)
214
+ return false;
215
+ return trimmed.startsWith('{') || trimmed.startsWith('[');
216
+ }
217
+ function detectYaml(code) {
218
+ const lines = splitLines(code);
219
+ for (const line of lines) {
220
+ const trimmed = line.trim();
221
+ if (!trimmed)
222
+ continue;
223
+ const colonIndex = trimmed.indexOf(':');
224
+ if (colonIndex <= 0)
225
+ continue;
226
+ const after = trimmed[colonIndex + 1];
227
+ if (after === ' ' || after === '\t')
228
+ return true;
229
+ }
230
+ return false;
231
+ }
232
+ function detectSql(code) {
233
+ const lower = code.toLowerCase();
234
+ return SQL_KEYWORDS.some((keyword) => containsWord(lower, keyword));
235
+ }
236
+ function detectGo(code) {
237
+ const lower = code.toLowerCase();
238
+ return (containsWord(lower, 'package') ||
239
+ containsWord(lower, 'func') ||
240
+ lower.includes('import "'));
241
+ }
242
+ function isCssSelectorLine(line) {
243
+ if (!line.startsWith('.') && !line.startsWith('#'))
244
+ return false;
245
+ return line.includes('{');
246
+ }
247
+ function isCssPropertyLine(line) {
248
+ return line.includes(':') && line.includes(';');
249
+ }
33
250
  export function detectLanguageFromCode(code) {
34
- for (const [pattern, language] of CODE_PATTERNS) {
35
- if (pattern.test(code)) {
251
+ for (const { language, detect } of CODE_DETECTORS) {
252
+ if (detect(code))
36
253
  return language;
37
- }
38
254
  }
39
255
  return undefined;
40
256
  }
41
257
  export function resolveLanguageFromAttributes(className, dataLang) {
42
- const classMatch = matchFirstCapture(className, CLASS_PATTERNS);
258
+ const classMatch = extractLanguageFromClassName(className);
43
259
  return classMatch ?? resolveLanguageFromDataAttribute(dataLang);
44
260
  }
45
- function matchFirstCapture(value, patterns) {
46
- for (const pattern of patterns) {
47
- const match = pattern.exec(value);
48
- if (match?.[1])
49
- return match[1];
50
- }
51
- return undefined;
52
- }
53
- function resolveLanguageFromDataAttribute(dataLang) {
54
- const match = /^(\w+)$/.exec(dataLang);
55
- return match?.[1];
56
- }
@@ -0,0 +1,3 @@
1
+ export declare function getErrorMessage(error: unknown): string;
2
+ export declare function createErrorWithCode(message: string, code: string): NodeJS.ErrnoException;
3
+ export declare function isSystemError(error: unknown): error is NodeJS.ErrnoException;
@@ -0,0 +1,12 @@
1
+ export function getErrorMessage(error) {
2
+ return error instanceof Error ? error.message : 'Unknown error';
3
+ }
4
+ export function createErrorWithCode(message, code) {
5
+ const error = new Error(message);
6
+ return Object.assign(error, { code });
7
+ }
8
+ export function isSystemError(error) {
9
+ return (error instanceof Error &&
10
+ 'code' in error &&
11
+ typeof Reflect.get(error, 'code') === 'string');
12
+ }
@@ -8,5 +8,5 @@ export function createErrorWithCode(message, code) {
8
8
  export function isSystemError(error) {
9
9
  return (error instanceof Error &&
10
10
  'code' in error &&
11
- typeof error.code === 'string');
11
+ typeof Reflect.get(error, 'code') === 'string');
12
12
  }
@@ -2,6 +2,17 @@ const MAX_FILENAME_LENGTH = 200;
2
2
  const UNSAFE_CHARS_REGEX = /[<>:"/\\|?*]|\p{C}/gu;
3
3
  const WHITESPACE_REGEX = /\s+/g;
4
4
  const DEFAULT_EXTENSION = '.md';
5
+ function trimHyphens(value) {
6
+ let start = 0;
7
+ let end = value.length;
8
+ while (start < end && value[start] === '-') {
9
+ start += 1;
10
+ }
11
+ while (end > start && value[end - 1] === '-') {
12
+ end -= 1;
13
+ }
14
+ return value.slice(start, end);
15
+ }
5
16
  export function generateSafeFilename(url, title, hashFallback, extension = DEFAULT_EXTENSION) {
6
17
  const fromUrl = extractFilenameFromUrl(url);
7
18
  if (fromUrl)
@@ -16,20 +27,31 @@ export function generateSafeFilename(url, title, hashFallback, extension = DEFAU
16
27
  }
17
28
  return `download-${Date.now()}${extension}`;
18
29
  }
30
+ function getLastPathSegment(url) {
31
+ const segments = url.pathname.split('/').filter(Boolean);
32
+ if (segments.length === 0)
33
+ return null;
34
+ const lastSegment = segments[segments.length - 1];
35
+ return lastSegment ?? null;
36
+ }
37
+ function stripCommonPageExtension(segment) {
38
+ return segment.replace(/\.(html?|php|aspx?|jsp)$/i, '');
39
+ }
40
+ function normalizeUrlFilenameSegment(segment) {
41
+ const cleaned = stripCommonPageExtension(segment);
42
+ if (!cleaned)
43
+ return null;
44
+ if (cleaned === 'index')
45
+ return null;
46
+ return cleaned;
47
+ }
19
48
  function extractFilenameFromUrl(url) {
20
49
  try {
21
50
  const urlObj = new URL(url);
22
- const { pathname } = urlObj;
23
- const segments = pathname.split('/').filter(Boolean);
24
- if (segments.length === 0)
25
- return null;
26
- const lastSegment = segments[segments.length - 1];
51
+ const lastSegment = getLastPathSegment(urlObj);
27
52
  if (!lastSegment)
28
53
  return null;
29
- const cleaned = lastSegment.replace(/\.(html?|php|aspx?|jsp)$/i, '');
30
- if (!cleaned || cleaned === 'index')
31
- return null;
32
- return cleaned;
54
+ return normalizeUrlFilenameSegment(lastSegment);
33
55
  }
34
56
  catch {
35
57
  return null;
@@ -41,9 +63,9 @@ function slugifyTitle(title) {
41
63
  .trim()
42
64
  .replace(UNSAFE_CHARS_REGEX, '')
43
65
  .replace(WHITESPACE_REGEX, '-')
44
- .replace(/-+/g, '-')
45
- .replace(/^-|-$/g, '');
46
- return slug.length > 0 ? slug : null;
66
+ .replace(/-+/g, '-');
67
+ const trimmed = trimHyphens(slug);
68
+ return trimmed || null;
47
69
  }
48
70
  function sanitizeFilename(name, extension) {
49
71
  let sanitized = name
@@ -0,0 +1 @@
1
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
@@ -0,0 +1,3 @@
1
+ export function isRecord(value) {
2
+ return typeof value === 'object' && value !== null;
3
+ }
@@ -2,7 +2,4 @@ interface NormalizeOptions {
2
2
  readonly trimValues?: boolean;
3
3
  }
4
4
  export declare function normalizeHeaderRecord(headers: Record<string, string> | undefined, blockedHeaders: Set<string>, options?: NormalizeOptions): Record<string, string> | undefined;
5
- export declare function normalizeHeaderEntries(headers: Record<string, string>, blockedHeaders: Set<string>, options?: NormalizeOptions): Headers;
6
- export declare function hasHeaderEntries(headers: Headers): boolean;
7
- export declare function headersToRecord(headers: Headers): Record<string, string>;
8
5
  export {};
@@ -6,7 +6,7 @@ export function normalizeHeaderRecord(headers, blockedHeaders, options = {}) {
6
6
  return undefined;
7
7
  return headersToRecord(normalized);
8
8
  }
9
- export function normalizeHeaderEntries(headers, blockedHeaders, options = {}) {
9
+ function normalizeHeaderEntries(headers, blockedHeaders, options = {}) {
10
10
  const normalized = new Headers();
11
11
  for (const [key, value] of Object.entries(headers)) {
12
12
  if (blockedHeaders.has(key.toLowerCase()))
@@ -15,10 +15,10 @@ export function normalizeHeaderEntries(headers, blockedHeaders, options = {}) {
15
15
  }
16
16
  return normalized;
17
17
  }
18
- export function hasHeaderEntries(headers) {
18
+ function hasHeaderEntries(headers) {
19
19
  return !headers.keys().next().done;
20
20
  }
21
- export function headersToRecord(headers) {
21
+ function headersToRecord(headers) {
22
22
  return Object.fromEntries(headers.entries());
23
23
  }
24
24
  function setHeaderValue(headers, key, value, trimValue) {
@@ -0,0 +1,4 @@
1
+ type IpSegment = number | string;
2
+ export declare function buildIpv4(parts: readonly [number, number, number, number]): string;
3
+ export declare function buildIpv6(parts: readonly IpSegment[]): string;
4
+ export {};
@@ -0,0 +1,6 @@
1
+ export function buildIpv4(parts) {
2
+ return parts.join('.');
3
+ }
4
+ export function buildIpv6(parts) {
5
+ return parts.map(String).join(':');
6
+ }
@@ -1,3 +1,3 @@
1
1
  import type { ToolErrorResponse } from '../config/types/tools.js';
2
- export declare function createToolErrorResponse(message: string, url: string, code: string, details?: Record<string, unknown>): ToolErrorResponse;
3
- export declare function handleToolError(error: unknown, url: string, fallbackMessage?: string, details?: Record<string, unknown>): ToolErrorResponse;
2
+ export declare function createToolErrorResponse(message: string, url: string): ToolErrorResponse;
3
+ export declare function handleToolError(error: unknown, url: string, fallbackMessage?: string): ToolErrorResponse;