recker 1.0.68 → 1.0.70-next.9b4eebc
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/ai/index.d.ts +0 -1
- package/dist/ai/index.js +0 -1
- package/dist/browser/ai/index.d.ts +0 -1
- package/dist/browser/ai/index.js +0 -1
- package/dist/browser/index.iife.min.js +51 -51
- package/dist/browser/index.min.js +51 -51
- package/dist/browser/index.umd.min.js +51 -51
- package/dist/cli/index.js +0 -2
- package/dist/cli/tui/components/command-palette.d.ts +0 -3
- package/dist/cli/tui/components/command-palette.js +0 -16
- package/dist/cli/tui/shell-search.d.ts +10 -4
- package/dist/cli/tui/shell-search.js +55 -92
- package/dist/mcp/index.d.ts +0 -1
- package/dist/mcp/index.js +0 -1
- package/dist/mcp/server.d.ts +0 -6
- package/dist/mcp/server.js +27 -138
- package/dist/mcp/tools/ai.js +0 -82
- package/dist/mini.d.ts +0 -6
- package/dist/mini.js +0 -3
- package/dist/version.js +1 -1
- package/package.json +12 -16
- package/dist/ai/vector/index.d.ts +0 -2
- package/dist/ai/vector/index.js +0 -2
- package/dist/ai/vector/similarity.d.ts +0 -2
- package/dist/ai/vector/similarity.js +0 -27
- package/dist/ai/vector/store.d.ts +0 -27
- package/dist/ai/vector/store.js +0 -82
- package/dist/browser/ai/vector/index.d.ts +0 -2
- package/dist/browser/ai/vector/index.js +0 -2
- package/dist/browser/ai/vector/similarity.d.ts +0 -2
- package/dist/browser/ai/vector/similarity.js +0 -27
- package/dist/browser/ai/vector/store.d.ts +0 -27
- package/dist/browser/ai/vector/store.js +0 -82
- package/dist/cli/commands/vector.d.ts +0 -8
- package/dist/cli/commands/vector.js +0 -214
- package/dist/mcp/embeddings-loader.d.ts +0 -17
- package/dist/mcp/embeddings-loader.js +0 -162
- package/dist/mcp/search/embedder.d.ts +0 -9
- package/dist/mcp/search/embedder.js +0 -83
- package/dist/mcp/search/hybrid-search.d.ts +0 -30
- package/dist/mcp/search/hybrid-search.js +0 -402
- package/dist/mcp/search/index.d.ts +0 -4
- package/dist/mcp/search/index.js +0 -3
- package/dist/mcp/search/math.d.ts +0 -5
- package/dist/mcp/search/math.js +0 -63
- package/dist/mcp/search/types.d.ts +0 -51
- package/dist/mcp/search/types.js +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -15,7 +15,6 @@ import { registerNetworkCommands } from './commands/network.js';
|
|
|
15
15
|
import { registerProtocolCommands } from './commands/protocols.js';
|
|
16
16
|
import { registerUtilsCommands } from './commands/utils.js';
|
|
17
17
|
import { registerHarCommand } from './commands/har.js';
|
|
18
|
-
import { registerVectorCommand } from './commands/vector.js';
|
|
19
18
|
import { registerSeoCommand } from './commands/seo.js';
|
|
20
19
|
import { registerVideoCommand } from './commands/video.js';
|
|
21
20
|
import { registerLiveCommand } from './commands/live.js';
|
|
@@ -320,7 +319,6 @@ ${formatColumns(PRESET_NAMES, { prefix: '@', indent: 2, minWidth: 16, transform:
|
|
|
320
319
|
registerDnsCommands(program);
|
|
321
320
|
registerProtocolCommands(program);
|
|
322
321
|
registerHarCommand(program);
|
|
323
|
-
registerVectorCommand(program);
|
|
324
322
|
registerHlsCommand(program);
|
|
325
323
|
registerVideoCommand(program);
|
|
326
324
|
registerLiveCommand(program);
|
|
@@ -40,7 +40,4 @@ export interface QuickActionBarProps {
|
|
|
40
40
|
width?: number;
|
|
41
41
|
}
|
|
42
42
|
export declare function QuickActionBar(props: QuickActionBarProps): VNode;
|
|
43
|
-
export declare function navigatePalette(direction: 'up' | 'down', _maxItems: number): void;
|
|
44
|
-
export declare function getPaletteSelection(): number;
|
|
45
|
-
export declare function getPaletteQuery(): string;
|
|
46
43
|
export {};
|
|
@@ -228,19 +228,3 @@ export function QuickActionBar(props) {
|
|
|
228
228
|
paddingX: 1,
|
|
229
229
|
}, ...actionTexts);
|
|
230
230
|
}
|
|
231
|
-
export function navigatePalette(direction, _maxItems) {
|
|
232
|
-
if (!paletteState)
|
|
233
|
-
return;
|
|
234
|
-
if (direction === 'up') {
|
|
235
|
-
paletteState.selectPrev();
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
paletteState.selectNext();
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
export function getPaletteSelection() {
|
|
242
|
-
return paletteState?.selectedIndex() ?? 0;
|
|
243
|
-
}
|
|
244
|
-
export function getPaletteQuery() {
|
|
245
|
-
return paletteState?.query() ?? '';
|
|
246
|
-
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import type { SearchResult } from '../../mcp/search/types.js';
|
|
2
1
|
export type ProgressCallback = (stage: string, percent?: number) => void;
|
|
2
|
+
export interface SearchResult {
|
|
3
|
+
id: string;
|
|
4
|
+
path: string;
|
|
5
|
+
title: string;
|
|
6
|
+
content: string;
|
|
7
|
+
snippet: string;
|
|
8
|
+
score: number;
|
|
9
|
+
source: 'fuzzy';
|
|
10
|
+
}
|
|
3
11
|
export declare class ShellSearch {
|
|
4
|
-
private hybridSearch;
|
|
5
12
|
private docsIndex;
|
|
6
13
|
private codeExamples;
|
|
7
14
|
private typeDefinitions;
|
|
@@ -12,7 +19,6 @@ export declare class ShellSearch {
|
|
|
12
19
|
private examplesPath;
|
|
13
20
|
private srcPath;
|
|
14
21
|
private spinner;
|
|
15
|
-
private hasSemanticSearch;
|
|
16
22
|
constructor();
|
|
17
23
|
private updateSpinner;
|
|
18
24
|
private ensureInitialized;
|
|
@@ -23,6 +29,7 @@ export declare class ShellSearch {
|
|
|
23
29
|
category?: string;
|
|
24
30
|
silent?: boolean;
|
|
25
31
|
}): Promise<SearchResult[]>;
|
|
32
|
+
private extractSnippet;
|
|
26
33
|
suggest(useCase: string): Promise<string>;
|
|
27
34
|
getExamples(feature: string, options?: {
|
|
28
35
|
limit?: number;
|
|
@@ -39,7 +46,6 @@ export declare class ShellSearch {
|
|
|
39
46
|
private findExamplesPath;
|
|
40
47
|
private findSrcPath;
|
|
41
48
|
private buildIndex;
|
|
42
|
-
private indexDocsFromEmbeddings;
|
|
43
49
|
private indexDocsFromFilesystem;
|
|
44
50
|
private indexExamples;
|
|
45
51
|
private indexTypes;
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import { createHybridSearch, createEmbedder, isFastembedAvailable } from '../../mcp/search/index.js';
|
|
2
|
-
import { loadEmbeddings } from '../../mcp/embeddings-loader.js';
|
|
3
1
|
import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
|
|
4
2
|
import { join, relative, extname, basename, dirname } from 'path';
|
|
5
3
|
import { fileURLToPath } from 'url';
|
|
6
4
|
import { createSpinner } from './spinner.js';
|
|
7
5
|
const IDLE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
8
6
|
export class ShellSearch {
|
|
9
|
-
hybridSearch = null;
|
|
10
7
|
docsIndex = [];
|
|
11
8
|
codeExamples = [];
|
|
12
9
|
typeDefinitions = [];
|
|
@@ -17,7 +14,6 @@ export class ShellSearch {
|
|
|
17
14
|
examplesPath;
|
|
18
15
|
srcPath;
|
|
19
16
|
spinner = null;
|
|
20
|
-
hasSemanticSearch = false;
|
|
21
17
|
constructor() {
|
|
22
18
|
this.docsPath = this.findDocsPath();
|
|
23
19
|
this.examplesPath = this.findExamplesPath();
|
|
@@ -30,7 +26,7 @@ export class ShellSearch {
|
|
|
30
26
|
}
|
|
31
27
|
async ensureInitialized() {
|
|
32
28
|
this.resetIdleTimer();
|
|
33
|
-
if (this.initialized
|
|
29
|
+
if (this.initialized) {
|
|
34
30
|
return;
|
|
35
31
|
}
|
|
36
32
|
if (this.initializing) {
|
|
@@ -42,21 +38,10 @@ export class ShellSearch {
|
|
|
42
38
|
this.initializing = true;
|
|
43
39
|
this.spinner = createSpinner({ text: 'Initializing search...' }).start();
|
|
44
40
|
try {
|
|
45
|
-
this.updateSpinner('Creating search index...');
|
|
46
|
-
this.hybridSearch = createHybridSearch({ debug: false });
|
|
47
|
-
this.updateSpinner('Checking semantic search availability...');
|
|
48
|
-
const fastembedAvailable = await isFastembedAvailable();
|
|
49
|
-
if (fastembedAvailable) {
|
|
50
|
-
this.updateSpinner('Loading AI embedding model (first time may take a while)...');
|
|
51
|
-
this.hybridSearch.setEmbedder(createEmbedder());
|
|
52
|
-
this.hasSemanticSearch = true;
|
|
53
|
-
}
|
|
54
41
|
this.updateSpinner('Indexing documentation...');
|
|
55
42
|
await this.buildIndex();
|
|
56
|
-
this.updateSpinner('Finalizing search index...');
|
|
57
|
-
await this.hybridSearch.initialize(this.docsIndex);
|
|
58
43
|
this.initialized = true;
|
|
59
|
-
this.spinner.succeed(`Search ready (${this.docsIndex.length} docs
|
|
44
|
+
this.spinner.succeed(`Search ready (${this.docsIndex.length} docs)`);
|
|
60
45
|
}
|
|
61
46
|
catch (error) {
|
|
62
47
|
this.spinner.fail(`Search initialization failed: ${error}`);
|
|
@@ -75,7 +60,6 @@ export class ShellSearch {
|
|
|
75
60
|
}
|
|
76
61
|
unload() {
|
|
77
62
|
if (this.initialized) {
|
|
78
|
-
this.hybridSearch = null;
|
|
79
63
|
this.docsIndex = [];
|
|
80
64
|
this.codeExamples = [];
|
|
81
65
|
this.typeDefinitions = [];
|
|
@@ -84,30 +68,59 @@ export class ShellSearch {
|
|
|
84
68
|
}
|
|
85
69
|
async search(query, options = {}) {
|
|
86
70
|
await this.ensureInitialized();
|
|
87
|
-
const { limit = 5, category
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
71
|
+
const { limit = 5, category } = options;
|
|
72
|
+
const queryLower = query.toLowerCase();
|
|
73
|
+
const queryTerms = queryLower.split(/\s+/);
|
|
74
|
+
const scored = this.docsIndex
|
|
75
|
+
.filter(doc => !category || doc.category.toLowerCase().includes(category.toLowerCase()))
|
|
76
|
+
.map(doc => {
|
|
77
|
+
let score = 0;
|
|
78
|
+
for (const term of queryTerms) {
|
|
79
|
+
if (doc.title.toLowerCase().includes(term))
|
|
80
|
+
score += 10;
|
|
81
|
+
if (doc.path.toLowerCase().includes(term))
|
|
82
|
+
score += 5;
|
|
83
|
+
if (doc.keywords.some(k => k.includes(term)))
|
|
84
|
+
score += 3;
|
|
85
|
+
if (doc.content.toLowerCase().includes(term))
|
|
86
|
+
score += 1;
|
|
102
87
|
}
|
|
103
|
-
return
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
88
|
+
return { doc, score };
|
|
89
|
+
})
|
|
90
|
+
.filter(r => r.score > 0)
|
|
91
|
+
.sort((a, b) => b.score - a.score)
|
|
92
|
+
.slice(0, limit);
|
|
93
|
+
return scored.map(r => ({
|
|
94
|
+
id: r.doc.id,
|
|
95
|
+
path: r.doc.path,
|
|
96
|
+
title: r.doc.title,
|
|
97
|
+
content: r.doc.content,
|
|
98
|
+
snippet: this.extractSnippet(r.doc.content, query),
|
|
99
|
+
score: Math.min(1, r.score / 20),
|
|
100
|
+
source: 'fuzzy',
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
extractSnippet(content, query) {
|
|
104
|
+
const lowerContent = content.toLowerCase();
|
|
105
|
+
const queryTerms = query.toLowerCase().split(/\s+/);
|
|
106
|
+
let bestIndex = -1;
|
|
107
|
+
for (const term of queryTerms) {
|
|
108
|
+
const idx = lowerContent.indexOf(term);
|
|
109
|
+
if (idx !== -1 && (bestIndex === -1 || idx < bestIndex)) {
|
|
110
|
+
bestIndex = idx;
|
|
108
111
|
}
|
|
109
|
-
throw error;
|
|
110
112
|
}
|
|
113
|
+
if (bestIndex === -1) {
|
|
114
|
+
return content.slice(0, 150) + '...';
|
|
115
|
+
}
|
|
116
|
+
const start = Math.max(0, bestIndex - 50);
|
|
117
|
+
const end = Math.min(content.length, bestIndex + 150);
|
|
118
|
+
let snippet = content.slice(start, end);
|
|
119
|
+
if (start > 0)
|
|
120
|
+
snippet = '...' + snippet;
|
|
121
|
+
if (end < content.length)
|
|
122
|
+
snippet = snippet + '...';
|
|
123
|
+
return snippet.replace(/\n/g, ' ');
|
|
111
124
|
}
|
|
112
125
|
async suggest(useCase) {
|
|
113
126
|
const spinner = createSpinner({ text: 'Generating suggestions...' }).start();
|
|
@@ -146,8 +159,8 @@ import { createClient } from 'recker';
|
|
|
146
159
|
const client = createClient({
|
|
147
160
|
baseUrl: 'https://api.example.com',
|
|
148
161
|
cache: {
|
|
149
|
-
storage: 'memory',
|
|
150
|
-
ttl: 300000,
|
|
162
|
+
storage: 'memory',
|
|
163
|
+
ttl: 300000,
|
|
151
164
|
strategy: 'stale-while-revalidate'
|
|
152
165
|
}
|
|
153
166
|
});
|
|
@@ -160,27 +173,9 @@ import { createClient } from 'recker';
|
|
|
160
173
|
|
|
161
174
|
const client = createClient({ baseUrl: 'https://api.openai.com' });
|
|
162
175
|
|
|
163
|
-
// SSE streaming
|
|
164
176
|
for await (const event of client.post('/v1/chat/completions', { body, stream: true }).sse()) {
|
|
165
177
|
console.log(event.data);
|
|
166
178
|
}
|
|
167
|
-
\`\`\``);
|
|
168
|
-
}
|
|
169
|
-
if (useCaseLower.includes('parallel') || useCaseLower.includes('batch') || useCaseLower.includes('concurrent')) {
|
|
170
|
-
suggestions.push(`\n**Batch/Parallel Requests:**
|
|
171
|
-
\`\`\`typescript
|
|
172
|
-
import { createClient } from 'recker';
|
|
173
|
-
|
|
174
|
-
const client = createClient({
|
|
175
|
-
baseUrl: 'https://api.example.com',
|
|
176
|
-
concurrency: { max: 10 }
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
const { results, stats } = await client.batch([
|
|
180
|
-
{ path: '/users/1' },
|
|
181
|
-
{ path: '/users/2' },
|
|
182
|
-
{ path: '/users/3' }
|
|
183
|
-
], { mapResponse: r => r.json() });
|
|
184
179
|
\`\`\``);
|
|
185
180
|
}
|
|
186
181
|
let output = `**Suggestion for: "${useCase}"**\n`;
|
|
@@ -299,42 +294,10 @@ const { results, stats } = await client.batch([
|
|
|
299
294
|
return candidates[0];
|
|
300
295
|
}
|
|
301
296
|
async buildIndex() {
|
|
302
|
-
|
|
297
|
+
this.indexDocsFromFilesystem();
|
|
303
298
|
this.indexExamples();
|
|
304
299
|
this.indexTypes();
|
|
305
300
|
}
|
|
306
|
-
async indexDocsFromEmbeddings() {
|
|
307
|
-
try {
|
|
308
|
-
this.updateSpinner('Loading documentation index...');
|
|
309
|
-
const data = await loadEmbeddings({ debug: false });
|
|
310
|
-
if (data && data.documents && data.documents.length > 0) {
|
|
311
|
-
for (const doc of data.documents) {
|
|
312
|
-
let content = doc.content || '';
|
|
313
|
-
if (!content && !doc.section) {
|
|
314
|
-
const fullPath = join(this.docsPath, doc.path);
|
|
315
|
-
if (existsSync(fullPath)) {
|
|
316
|
-
content = readFileSync(fullPath, 'utf-8');
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
this.docsIndex.push({
|
|
320
|
-
id: doc.id,
|
|
321
|
-
path: doc.path,
|
|
322
|
-
title: doc.title,
|
|
323
|
-
content,
|
|
324
|
-
category: doc.category,
|
|
325
|
-
keywords: doc.keywords || [],
|
|
326
|
-
section: doc.section,
|
|
327
|
-
parentPath: doc.parentPath,
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
this.updateSpinner('Downloading documentation index failed, trying filesystem...');
|
|
335
|
-
}
|
|
336
|
-
this.indexDocsFromFilesystem();
|
|
337
|
-
}
|
|
338
301
|
indexDocsFromFilesystem() {
|
|
339
302
|
if (!existsSync(this.docsPath))
|
|
340
303
|
return;
|
package/dist/mcp/index.d.ts
CHANGED
package/dist/mcp/index.js
CHANGED
package/dist/mcp/server.d.ts
CHANGED
|
@@ -19,7 +19,6 @@ export interface MCPServerOptions {
|
|
|
19
19
|
export declare class MCPServer {
|
|
20
20
|
private options;
|
|
21
21
|
private server?;
|
|
22
|
-
private hybridSearch;
|
|
23
22
|
private docsIndex;
|
|
24
23
|
private codeExamples;
|
|
25
24
|
private typeDefinitions;
|
|
@@ -28,21 +27,16 @@ export declare class MCPServer {
|
|
|
28
27
|
private toolRegistry;
|
|
29
28
|
private promptRegistry;
|
|
30
29
|
private resourceRegistry;
|
|
31
|
-
private aiClient?;
|
|
32
30
|
constructor(options?: MCPServerOptions);
|
|
33
31
|
private applyCategoryFiltering;
|
|
34
32
|
private indexReady;
|
|
35
33
|
private ensureIndexReady;
|
|
36
|
-
private initAI;
|
|
37
|
-
private fastEmbedModel;
|
|
38
|
-
private generateEmbedding;
|
|
39
34
|
private log;
|
|
40
35
|
private findDocsPath;
|
|
41
36
|
private findExamplesPath;
|
|
42
37
|
private findSrcPath;
|
|
43
38
|
private buildIndex;
|
|
44
39
|
private indexDocs;
|
|
45
|
-
private loadDocsFromEmbeddings;
|
|
46
40
|
private indexCodeExamples;
|
|
47
41
|
private parseCodeExample;
|
|
48
42
|
private humanizeFilename;
|
package/dist/mcp/server.js
CHANGED
|
@@ -3,8 +3,6 @@ import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
|
|
|
3
3
|
import { join, relative, extname, basename, dirname } from 'path';
|
|
4
4
|
import { createInterface } from 'readline';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
import { createAI } from '../ai/index.js';
|
|
7
|
-
import { createHybridSearch } from './search/index.js';
|
|
8
6
|
import { UnsupportedError } from '../core/errors.js';
|
|
9
7
|
import { getIpInfo, isValidIP, isGeoIPAvailable, isBogon, isIPv6 } from './ip-intel.js';
|
|
10
8
|
import { networkTools, networkToolHandlers } from './tools/network.js';
|
|
@@ -26,7 +24,6 @@ import { resolveCategories, DEFAULT_CATEGORY, } from './profiles.js';
|
|
|
26
24
|
export class MCPServer {
|
|
27
25
|
options;
|
|
28
26
|
server;
|
|
29
|
-
hybridSearch;
|
|
30
27
|
docsIndex = [];
|
|
31
28
|
codeExamples = [];
|
|
32
29
|
typeDefinitions = [];
|
|
@@ -35,7 +32,6 @@ export class MCPServer {
|
|
|
35
32
|
toolRegistry;
|
|
36
33
|
promptRegistry;
|
|
37
34
|
resourceRegistry;
|
|
38
|
-
aiClient;
|
|
39
35
|
constructor(options = {}) {
|
|
40
36
|
this.options = {
|
|
41
37
|
name: options.name || 'recker',
|
|
@@ -51,12 +47,6 @@ export class MCPServer {
|
|
|
51
47
|
toolPaths: options.toolPaths || [],
|
|
52
48
|
category: options.category,
|
|
53
49
|
};
|
|
54
|
-
this.aiClient = this.initAI();
|
|
55
|
-
this.hybridSearch = createHybridSearch({
|
|
56
|
-
debug: this.options.debug,
|
|
57
|
-
offline: this.options.offline,
|
|
58
|
-
embedder: (text, model) => this.generateEmbedding(text, model || 'BGESmallENV15'),
|
|
59
|
-
});
|
|
60
50
|
this.toolRegistry = new ToolRegistry();
|
|
61
51
|
this.promptRegistry = new PromptRegistry();
|
|
62
52
|
this.resourceRegistry = new ResourceRegistry();
|
|
@@ -137,56 +127,6 @@ export class MCPServer {
|
|
|
137
127
|
}
|
|
138
128
|
await this.indexReady;
|
|
139
129
|
}
|
|
140
|
-
initAI() {
|
|
141
|
-
const hasKeys = process.env.OPENAI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
142
|
-
const hasLocal = process.env.OLLAMA_HOST || true;
|
|
143
|
-
if (!hasKeys) {
|
|
144
|
-
return undefined;
|
|
145
|
-
}
|
|
146
|
-
try {
|
|
147
|
-
return createAI({ debug: this.options.debug });
|
|
148
|
-
}
|
|
149
|
-
catch (e) {
|
|
150
|
-
this.log('Failed to initialize AI client:', e);
|
|
151
|
-
return undefined;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
fastEmbedModel = null;
|
|
155
|
-
async generateEmbedding(text, model) {
|
|
156
|
-
if (model.toLowerCase().includes('bge')) {
|
|
157
|
-
if (!this.fastEmbedModel) {
|
|
158
|
-
try {
|
|
159
|
-
const fastembed = await import('fastembed');
|
|
160
|
-
const { FlagEmbedding } = fastembed;
|
|
161
|
-
this.fastEmbedModel = await FlagEmbedding.init({
|
|
162
|
-
model: model,
|
|
163
|
-
showDownloadProgress: false
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
catch (e) {
|
|
167
|
-
throw new Error(`FastEmbed required for model ${model}. Install with: pnpm add fastembed`);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
const vectors = this.fastEmbedModel.embed([text]);
|
|
171
|
-
for await (const v of vectors)
|
|
172
|
-
return Array.from(v);
|
|
173
|
-
throw new Error('No vector generated');
|
|
174
|
-
}
|
|
175
|
-
if (!this.aiClient) {
|
|
176
|
-
throw new Error('No AI client available for external embeddings');
|
|
177
|
-
}
|
|
178
|
-
let provider = 'openai';
|
|
179
|
-
if (model.includes('google') || model.includes('gecko'))
|
|
180
|
-
provider = 'google';
|
|
181
|
-
else if (model.includes('nomic') || model.includes('llama'))
|
|
182
|
-
provider = 'ollama';
|
|
183
|
-
const res = await this.aiClient.embed({
|
|
184
|
-
input: text,
|
|
185
|
-
provider,
|
|
186
|
-
model
|
|
187
|
-
});
|
|
188
|
-
return res.embeddings[0];
|
|
189
|
-
}
|
|
190
130
|
log(message, data) {
|
|
191
131
|
if (this.options.debug) {
|
|
192
132
|
if (this.options.transport === 'stdio') {
|
|
@@ -250,78 +190,36 @@ export class MCPServer {
|
|
|
250
190
|
await this.indexDocs();
|
|
251
191
|
this.indexCodeExamples();
|
|
252
192
|
this.indexTypeDefinitions();
|
|
253
|
-
|
|
254
|
-
const stats = this.hybridSearch.getStats();
|
|
255
|
-
this.log(`Indexed ${stats.documents} docs, ${this.codeExamples.length} examples, ${this.typeDefinitions.length} types`);
|
|
256
|
-
if (stats.embeddings > 0) {
|
|
257
|
-
this.log(`Loaded ${stats.embeddings} embeddings (model: ${stats.model})`);
|
|
258
|
-
}
|
|
193
|
+
this.log(`Indexed ${this.docsIndex.length} docs, ${this.codeExamples.length} examples, ${this.typeDefinitions.length} types`);
|
|
259
194
|
}
|
|
260
195
|
async indexDocs() {
|
|
261
|
-
if (existsSync(this.options.docsPath)) {
|
|
262
|
-
|
|
263
|
-
for (let i = 0; i < files.length; i++) {
|
|
264
|
-
const file = files[i];
|
|
265
|
-
if (!file.endsWith('.md'))
|
|
266
|
-
continue;
|
|
267
|
-
try {
|
|
268
|
-
const content = readFileSync(file, 'utf-8');
|
|
269
|
-
const relativePath = relative(this.options.docsPath, file);
|
|
270
|
-
const category = relativePath.split('/')[0] || 'root';
|
|
271
|
-
const title = this.extractTitle(content) || relativePath;
|
|
272
|
-
const keywords = this.extractKeywords(content);
|
|
273
|
-
this.docsIndex.push({
|
|
274
|
-
id: `doc-${i}`,
|
|
275
|
-
path: relativePath,
|
|
276
|
-
title,
|
|
277
|
-
category,
|
|
278
|
-
content,
|
|
279
|
-
keywords,
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
catch (err) {
|
|
283
|
-
this.log(`Failed to index ${file}:`, err);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
196
|
+
if (!existsSync(this.options.docsPath)) {
|
|
197
|
+
this.log(`Docs path not found: ${this.options.docsPath}`);
|
|
286
198
|
return;
|
|
287
199
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
this.
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
const { loadEmbeddings } = await import('./embeddings-loader.js');
|
|
302
|
-
embeddingsData = await loadEmbeddings({ offline: this.options.offline });
|
|
303
|
-
if (embeddingsData) {
|
|
304
|
-
this.log(`Loaded ${embeddingsData.documents?.length || 0} docs from cached embeddings`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
if (!embeddingsData?.documents) {
|
|
308
|
-
this.log('No embeddings data available');
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
for (const doc of embeddingsData.documents) {
|
|
200
|
+
const files = this.walkDir(this.options.docsPath);
|
|
201
|
+
for (let i = 0; i < files.length; i++) {
|
|
202
|
+
const file = files[i];
|
|
203
|
+
if (!file.endsWith('.md'))
|
|
204
|
+
continue;
|
|
205
|
+
try {
|
|
206
|
+
const content = readFileSync(file, 'utf-8');
|
|
207
|
+
const relativePath = relative(this.options.docsPath, file);
|
|
208
|
+
const category = relativePath.split('/')[0] || 'root';
|
|
209
|
+
const title = this.extractTitle(content) || relativePath;
|
|
210
|
+
const keywords = this.extractKeywords(content);
|
|
312
211
|
this.docsIndex.push({
|
|
313
|
-
id: doc
|
|
314
|
-
path:
|
|
315
|
-
title
|
|
316
|
-
category
|
|
317
|
-
content
|
|
318
|
-
keywords
|
|
319
|
-
section: doc.section,
|
|
212
|
+
id: `doc-${i}`,
|
|
213
|
+
path: relativePath,
|
|
214
|
+
title,
|
|
215
|
+
category,
|
|
216
|
+
content,
|
|
217
|
+
keywords,
|
|
320
218
|
});
|
|
321
219
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
220
|
+
catch (err) {
|
|
221
|
+
this.log(`Failed to index ${file}:`, err);
|
|
222
|
+
}
|
|
325
223
|
}
|
|
326
224
|
}
|
|
327
225
|
indexCodeExamples() {
|
|
@@ -732,15 +630,12 @@ export class MCPServer {
|
|
|
732
630
|
const query = String(args.query || '');
|
|
733
631
|
const category = args.category ? String(args.category) : undefined;
|
|
734
632
|
const limit = Math.min(Number(args.limit) || 5, 10);
|
|
735
|
-
const mode = args.mode || 'hybrid';
|
|
736
633
|
if (!query) {
|
|
737
634
|
return {
|
|
738
635
|
content: [{ type: 'text', text: 'Error: query is required' }],
|
|
739
636
|
isError: true,
|
|
740
637
|
};
|
|
741
638
|
}
|
|
742
|
-
const searchPromise = this.hybridSearch.search(query, { limit, category, mode });
|
|
743
|
-
let results = [];
|
|
744
639
|
const queryLower = query.toLowerCase();
|
|
745
640
|
const queryTerms = queryLower.split(/\s+/);
|
|
746
641
|
const scored = this.docsIndex
|
|
@@ -762,7 +657,7 @@ export class MCPServer {
|
|
|
762
657
|
.filter(r => r.score > 0)
|
|
763
658
|
.sort((a, b) => b.score - a.score)
|
|
764
659
|
.slice(0, limit);
|
|
765
|
-
results = scored.map(r => ({
|
|
660
|
+
const results = scored.map(r => ({
|
|
766
661
|
id: r.doc.id,
|
|
767
662
|
path: r.doc.path,
|
|
768
663
|
title: r.doc.title,
|
|
@@ -779,13 +674,11 @@ export class MCPServer {
|
|
|
779
674
|
}],
|
|
780
675
|
};
|
|
781
676
|
}
|
|
782
|
-
const stats = this.hybridSearch.getStats();
|
|
783
|
-
const searchMode = stats.embeddings > 0 ? mode : 'fuzzy';
|
|
784
677
|
const output = results.map((r, i) => `${i + 1}. **${r.title}** (${(r.score * 100).toFixed(0)}% match)\n Path: \`${r.path}\`\n ${r.snippet}`).join('\\n\\n');
|
|
785
678
|
return {
|
|
786
679
|
content: [{
|
|
787
680
|
type: 'text',
|
|
788
|
-
text: `Found ${results.length} result(s) for "${query}"
|
|
681
|
+
text: `Found ${results.length} result(s) for "${query}":\n\n${output}\n\nUse get_doc with the path to read full content.`,
|
|
789
682
|
}],
|
|
790
683
|
};
|
|
791
684
|
}
|
|
@@ -1323,16 +1216,14 @@ const client = createClient({
|
|
|
1323
1216
|
return;
|
|
1324
1217
|
}
|
|
1325
1218
|
if (req.method === 'GET' && req.url === '/health') {
|
|
1326
|
-
const stats = this.hybridSearch.getStats();
|
|
1327
1219
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1328
1220
|
res.end(JSON.stringify({
|
|
1329
1221
|
status: 'ok',
|
|
1330
1222
|
name: this.options.name,
|
|
1331
1223
|
version: this.options.version,
|
|
1332
|
-
docsCount:
|
|
1224
|
+
docsCount: this.docsIndex.length,
|
|
1333
1225
|
examplesCount: this.codeExamples.length,
|
|
1334
1226
|
typesCount: this.typeDefinitions.length,
|
|
1335
|
-
embeddingsLoaded: stats.embeddings > 0,
|
|
1336
1227
|
sseClients: this.sseClients.size,
|
|
1337
1228
|
}));
|
|
1338
1229
|
return;
|
|
@@ -1416,16 +1307,14 @@ const client = createClient({
|
|
|
1416
1307
|
return;
|
|
1417
1308
|
}
|
|
1418
1309
|
if (req.method === 'GET' && url === '/health') {
|
|
1419
|
-
const stats = this.hybridSearch.getStats();
|
|
1420
1310
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
1421
1311
|
res.end(JSON.stringify({
|
|
1422
1312
|
status: 'ok',
|
|
1423
1313
|
name: this.options.name,
|
|
1424
1314
|
version: this.options.version,
|
|
1425
|
-
docsCount:
|
|
1315
|
+
docsCount: this.docsIndex.length,
|
|
1426
1316
|
examplesCount: this.codeExamples.length,
|
|
1427
1317
|
typesCount: this.typeDefinitions.length,
|
|
1428
|
-
embeddingsLoaded: stats.embeddings > 0,
|
|
1429
1318
|
sseClients: this.sseClients.size,
|
|
1430
1319
|
}));
|
|
1431
1320
|
return;
|