recker 1.0.12-alpha.91ed191 → 1.0.12-alpha.a858d02
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.
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { SearchResult } from '../../mcp/search/types.js';
|
|
2
|
+
export declare class ShellSearch {
|
|
3
|
+
private hybridSearch;
|
|
4
|
+
private docsIndex;
|
|
5
|
+
private codeExamples;
|
|
6
|
+
private typeDefinitions;
|
|
7
|
+
private initialized;
|
|
8
|
+
private idleTimer;
|
|
9
|
+
private docsPath;
|
|
10
|
+
private examplesPath;
|
|
11
|
+
private srcPath;
|
|
12
|
+
constructor();
|
|
13
|
+
private ensureInitialized;
|
|
14
|
+
private resetIdleTimer;
|
|
15
|
+
private unload;
|
|
16
|
+
search(query: string, options?: {
|
|
17
|
+
limit?: number;
|
|
18
|
+
category?: string;
|
|
19
|
+
}): Promise<SearchResult[]>;
|
|
20
|
+
suggest(useCase: string): Promise<string>;
|
|
21
|
+
getExamples(feature: string, options?: {
|
|
22
|
+
limit?: number;
|
|
23
|
+
complexity?: string;
|
|
24
|
+
}): Promise<string>;
|
|
25
|
+
getStats(): {
|
|
26
|
+
documents: number;
|
|
27
|
+
examples: number;
|
|
28
|
+
types: number;
|
|
29
|
+
loaded: boolean;
|
|
30
|
+
};
|
|
31
|
+
private findDocsPath;
|
|
32
|
+
private findExamplesPath;
|
|
33
|
+
private findSrcPath;
|
|
34
|
+
private buildIndex;
|
|
35
|
+
private indexDocs;
|
|
36
|
+
private indexExamples;
|
|
37
|
+
private indexTypes;
|
|
38
|
+
private extractKeywords;
|
|
39
|
+
private parseExampleMeta;
|
|
40
|
+
private inferFeature;
|
|
41
|
+
private extractMainCode;
|
|
42
|
+
private parseTypeDefinitions;
|
|
43
|
+
}
|
|
44
|
+
export declare function getShellSearch(): ShellSearch;
|
|
45
|
+
//# sourceMappingURL=shell-search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell-search.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/shell-search.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAmC1E,qBAAa,WAAW;IACtB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,eAAe,CAAwB;IAC/C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,OAAO,CAAS;;YAWV,iBAAiB;IAgB/B,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,MAAM;IAaR,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAenG,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqGzC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAwD1G,QAAQ,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE;IAWnF,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,SAAS;IAuCjB,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,eAAe;IA8BvB,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,oBAAoB;CA4C7B;AAKD,wBAAgB,cAAc,IAAI,WAAW,CAK5C"}
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { createHybridSearch } from '../../mcp/search/index.js';
|
|
2
|
+
import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
|
|
3
|
+
import { join, relative, extname, basename, dirname } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
const IDLE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
6
|
+
export class ShellSearch {
|
|
7
|
+
hybridSearch = null;
|
|
8
|
+
docsIndex = [];
|
|
9
|
+
codeExamples = [];
|
|
10
|
+
typeDefinitions = [];
|
|
11
|
+
initialized = false;
|
|
12
|
+
idleTimer = null;
|
|
13
|
+
docsPath;
|
|
14
|
+
examplesPath;
|
|
15
|
+
srcPath;
|
|
16
|
+
constructor() {
|
|
17
|
+
this.docsPath = this.findDocsPath();
|
|
18
|
+
this.examplesPath = this.findExamplesPath();
|
|
19
|
+
this.srcPath = this.findSrcPath();
|
|
20
|
+
}
|
|
21
|
+
async ensureInitialized() {
|
|
22
|
+
this.resetIdleTimer();
|
|
23
|
+
if (this.initialized && this.hybridSearch) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.hybridSearch = createHybridSearch({ debug: false });
|
|
27
|
+
this.buildIndex();
|
|
28
|
+
await this.hybridSearch.initialize(this.docsIndex);
|
|
29
|
+
this.initialized = true;
|
|
30
|
+
}
|
|
31
|
+
resetIdleTimer() {
|
|
32
|
+
if (this.idleTimer) {
|
|
33
|
+
clearTimeout(this.idleTimer);
|
|
34
|
+
}
|
|
35
|
+
this.idleTimer = setTimeout(() => this.unload(), IDLE_TIMEOUT_MS);
|
|
36
|
+
}
|
|
37
|
+
unload() {
|
|
38
|
+
if (this.initialized) {
|
|
39
|
+
this.hybridSearch = null;
|
|
40
|
+
this.docsIndex = [];
|
|
41
|
+
this.codeExamples = [];
|
|
42
|
+
this.typeDefinitions = [];
|
|
43
|
+
this.initialized = false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async search(query, options = {}) {
|
|
47
|
+
await this.ensureInitialized();
|
|
48
|
+
const { limit = 5, category } = options;
|
|
49
|
+
if (!this.hybridSearch) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
return this.hybridSearch.search(query, { limit, category, mode: 'hybrid' });
|
|
53
|
+
}
|
|
54
|
+
async suggest(useCase) {
|
|
55
|
+
await this.ensureInitialized();
|
|
56
|
+
const results = await this.search(useCase, { limit: 3 });
|
|
57
|
+
if (results.length === 0) {
|
|
58
|
+
return `No suggestions found for: "${useCase}"\n\nTry searching for specific features like:\n - retry\n - cache\n - streaming\n - websocket\n - pagination`;
|
|
59
|
+
}
|
|
60
|
+
const useCaseLower = useCase.toLowerCase();
|
|
61
|
+
const suggestions = [];
|
|
62
|
+
if (useCaseLower.includes('retry') || useCaseLower.includes('fail') || useCaseLower.includes('error')) {
|
|
63
|
+
suggestions.push(`\n**Retry Configuration:**
|
|
64
|
+
\`\`\`typescript
|
|
65
|
+
import { createClient } from 'recker';
|
|
66
|
+
|
|
67
|
+
const client = createClient({
|
|
68
|
+
baseUrl: 'https://api.example.com',
|
|
69
|
+
retry: {
|
|
70
|
+
attempts: 3,
|
|
71
|
+
backoff: 'exponential',
|
|
72
|
+
jitter: true,
|
|
73
|
+
retryOn: [429, 500, 502, 503, 504]
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
\`\`\``);
|
|
77
|
+
}
|
|
78
|
+
if (useCaseLower.includes('cache') || useCaseLower.includes('storage')) {
|
|
79
|
+
suggestions.push(`\n**Cache Configuration:**
|
|
80
|
+
\`\`\`typescript
|
|
81
|
+
import { createClient } from 'recker';
|
|
82
|
+
|
|
83
|
+
const client = createClient({
|
|
84
|
+
baseUrl: 'https://api.example.com',
|
|
85
|
+
cache: {
|
|
86
|
+
storage: 'memory', // or 'file'
|
|
87
|
+
ttl: 300000, // 5 minutes
|
|
88
|
+
strategy: 'stale-while-revalidate'
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
\`\`\``);
|
|
92
|
+
}
|
|
93
|
+
if (useCaseLower.includes('stream') || useCaseLower.includes('sse') || useCaseLower.includes('ai') || useCaseLower.includes('openai')) {
|
|
94
|
+
suggestions.push(`\n**Streaming Configuration:**
|
|
95
|
+
\`\`\`typescript
|
|
96
|
+
import { createClient } from 'recker';
|
|
97
|
+
|
|
98
|
+
const client = createClient({ baseUrl: 'https://api.openai.com' });
|
|
99
|
+
|
|
100
|
+
// SSE streaming
|
|
101
|
+
for await (const event of client.post('/v1/chat/completions', { body, stream: true }).sse()) {
|
|
102
|
+
console.log(event.data);
|
|
103
|
+
}
|
|
104
|
+
\`\`\``);
|
|
105
|
+
}
|
|
106
|
+
if (useCaseLower.includes('parallel') || useCaseLower.includes('batch') || useCaseLower.includes('concurrent')) {
|
|
107
|
+
suggestions.push(`\n**Batch/Parallel Requests:**
|
|
108
|
+
\`\`\`typescript
|
|
109
|
+
import { createClient } from 'recker';
|
|
110
|
+
|
|
111
|
+
const client = createClient({
|
|
112
|
+
baseUrl: 'https://api.example.com',
|
|
113
|
+
concurrency: { max: 10 }
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const { results, stats } = await client.batch([
|
|
117
|
+
{ path: '/users/1' },
|
|
118
|
+
{ path: '/users/2' },
|
|
119
|
+
{ path: '/users/3' }
|
|
120
|
+
], { mapResponse: r => r.json() });
|
|
121
|
+
\`\`\``);
|
|
122
|
+
}
|
|
123
|
+
let output = `**Suggestion for: "${useCase}"**\n`;
|
|
124
|
+
if (suggestions.length > 0) {
|
|
125
|
+
output += suggestions.join('\n');
|
|
126
|
+
}
|
|
127
|
+
output += `\n\n**Related Documentation:**\n`;
|
|
128
|
+
for (const result of results) {
|
|
129
|
+
output += ` - ${result.title} (${result.path})\n`;
|
|
130
|
+
if (result.snippet) {
|
|
131
|
+
output += ` ${result.snippet.slice(0, 100)}...\n`;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return output;
|
|
135
|
+
}
|
|
136
|
+
async getExamples(feature, options = {}) {
|
|
137
|
+
await this.ensureInitialized();
|
|
138
|
+
const { limit = 3, complexity } = options;
|
|
139
|
+
const featureLower = feature.toLowerCase();
|
|
140
|
+
let examples = this.codeExamples.filter(ex => {
|
|
141
|
+
const matchesFeature = ex.feature.toLowerCase().includes(featureLower) ||
|
|
142
|
+
ex.keywords.some(k => k.toLowerCase().includes(featureLower)) ||
|
|
143
|
+
ex.title.toLowerCase().includes(featureLower);
|
|
144
|
+
const matchesComplexity = !complexity || ex.complexity === complexity;
|
|
145
|
+
return matchesFeature && matchesComplexity;
|
|
146
|
+
});
|
|
147
|
+
if (examples.length === 0) {
|
|
148
|
+
const searchResults = await this.search(`${feature} example`, { limit: 3 });
|
|
149
|
+
if (searchResults.length === 0) {
|
|
150
|
+
return `No examples found for: "${feature}"\n\nAvailable features:\n - retry, cache, streaming, websocket\n - pagination, middleware, batch\n - scraping, load-testing, whois`;
|
|
151
|
+
}
|
|
152
|
+
let output = `**Examples for "${feature}" (from docs):**\n\n`;
|
|
153
|
+
for (const result of searchResults) {
|
|
154
|
+
output += `### ${result.title}\n`;
|
|
155
|
+
const codeBlocks = result.content.match(/```[\s\S]*?```/g) || [];
|
|
156
|
+
if (codeBlocks.length > 0) {
|
|
157
|
+
output += codeBlocks.slice(0, 2).join('\n\n') + '\n\n';
|
|
158
|
+
}
|
|
159
|
+
else if (result.snippet) {
|
|
160
|
+
output += result.snippet + '\n\n';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return output;
|
|
164
|
+
}
|
|
165
|
+
examples = examples.slice(0, limit);
|
|
166
|
+
let output = `**Code Examples for "${feature}":**\n\n`;
|
|
167
|
+
for (const ex of examples) {
|
|
168
|
+
output += `### ${ex.title} (${ex.complexity})\n`;
|
|
169
|
+
output += `${ex.description}\n\n`;
|
|
170
|
+
output += `\`\`\`typescript\n${ex.code}\n\`\`\`\n\n`;
|
|
171
|
+
}
|
|
172
|
+
return output;
|
|
173
|
+
}
|
|
174
|
+
getStats() {
|
|
175
|
+
return {
|
|
176
|
+
documents: this.docsIndex.length,
|
|
177
|
+
examples: this.codeExamples.length,
|
|
178
|
+
types: this.typeDefinitions.length,
|
|
179
|
+
loaded: this.initialized,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
findDocsPath() {
|
|
183
|
+
const candidates = [
|
|
184
|
+
join(process.cwd(), 'docs'),
|
|
185
|
+
join(dirname(fileURLToPath(import.meta.url)), '../../../docs'),
|
|
186
|
+
join(dirname(fileURLToPath(import.meta.url)), '../../../../docs'),
|
|
187
|
+
];
|
|
188
|
+
for (const p of candidates) {
|
|
189
|
+
if (existsSync(p))
|
|
190
|
+
return p;
|
|
191
|
+
}
|
|
192
|
+
return candidates[0];
|
|
193
|
+
}
|
|
194
|
+
findExamplesPath() {
|
|
195
|
+
const candidates = [
|
|
196
|
+
join(process.cwd(), 'examples'),
|
|
197
|
+
join(dirname(fileURLToPath(import.meta.url)), '../../../examples'),
|
|
198
|
+
];
|
|
199
|
+
for (const p of candidates) {
|
|
200
|
+
if (existsSync(p))
|
|
201
|
+
return p;
|
|
202
|
+
}
|
|
203
|
+
return candidates[0];
|
|
204
|
+
}
|
|
205
|
+
findSrcPath() {
|
|
206
|
+
const candidates = [
|
|
207
|
+
join(process.cwd(), 'src'),
|
|
208
|
+
join(dirname(fileURLToPath(import.meta.url)), '../../'),
|
|
209
|
+
];
|
|
210
|
+
for (const p of candidates) {
|
|
211
|
+
if (existsSync(p))
|
|
212
|
+
return p;
|
|
213
|
+
}
|
|
214
|
+
return candidates[0];
|
|
215
|
+
}
|
|
216
|
+
buildIndex() {
|
|
217
|
+
this.indexDocs();
|
|
218
|
+
this.indexExamples();
|
|
219
|
+
this.indexTypes();
|
|
220
|
+
}
|
|
221
|
+
indexDocs() {
|
|
222
|
+
if (!existsSync(this.docsPath))
|
|
223
|
+
return;
|
|
224
|
+
const walkDir = (dir) => {
|
|
225
|
+
const entries = readdirSync(dir);
|
|
226
|
+
for (const entry of entries) {
|
|
227
|
+
const fullPath = join(dir, entry);
|
|
228
|
+
const stat = statSync(fullPath);
|
|
229
|
+
if (stat.isDirectory()) {
|
|
230
|
+
walkDir(fullPath);
|
|
231
|
+
}
|
|
232
|
+
else if (extname(entry) === '.md') {
|
|
233
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
234
|
+
const relPath = relative(this.docsPath, fullPath);
|
|
235
|
+
const parts = relPath.split('/');
|
|
236
|
+
const category = parts.length > 1 ? parts[0] : 'general';
|
|
237
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
238
|
+
const title = titleMatch ? titleMatch[1] : basename(entry, '.md');
|
|
239
|
+
const keywords = this.extractKeywords(content);
|
|
240
|
+
this.docsIndex.push({
|
|
241
|
+
id: relPath,
|
|
242
|
+
path: relPath,
|
|
243
|
+
title,
|
|
244
|
+
content,
|
|
245
|
+
category,
|
|
246
|
+
keywords,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
walkDir(this.docsPath);
|
|
252
|
+
}
|
|
253
|
+
indexExamples() {
|
|
254
|
+
if (!existsSync(this.examplesPath))
|
|
255
|
+
return;
|
|
256
|
+
const walkDir = (dir) => {
|
|
257
|
+
const entries = readdirSync(dir);
|
|
258
|
+
for (const entry of entries) {
|
|
259
|
+
const fullPath = join(dir, entry);
|
|
260
|
+
const stat = statSync(fullPath);
|
|
261
|
+
if (stat.isDirectory()) {
|
|
262
|
+
walkDir(fullPath);
|
|
263
|
+
}
|
|
264
|
+
else if (['.ts', '.js', '.mjs'].includes(extname(entry))) {
|
|
265
|
+
const code = readFileSync(fullPath, 'utf-8');
|
|
266
|
+
const relPath = relative(this.examplesPath, fullPath);
|
|
267
|
+
const meta = this.parseExampleMeta(code);
|
|
268
|
+
const filename = basename(entry, extname(entry));
|
|
269
|
+
this.codeExamples.push({
|
|
270
|
+
id: relPath,
|
|
271
|
+
path: relPath,
|
|
272
|
+
title: meta.title || filename,
|
|
273
|
+
feature: meta.feature || this.inferFeature(filename, code),
|
|
274
|
+
complexity: meta.complexity || 'basic',
|
|
275
|
+
code: this.extractMainCode(code),
|
|
276
|
+
description: meta.description || '',
|
|
277
|
+
keywords: meta.keywords || this.extractKeywords(code),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
walkDir(this.examplesPath);
|
|
283
|
+
}
|
|
284
|
+
indexTypes() {
|
|
285
|
+
if (!existsSync(this.srcPath))
|
|
286
|
+
return;
|
|
287
|
+
const typeFiles = ['types.ts', 'types/index.ts', 'core/types.ts'];
|
|
288
|
+
for (const tf of typeFiles) {
|
|
289
|
+
const fullPath = join(this.srcPath, tf);
|
|
290
|
+
if (!existsSync(fullPath))
|
|
291
|
+
continue;
|
|
292
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
293
|
+
this.parseTypeDefinitions(content, tf);
|
|
294
|
+
}
|
|
295
|
+
const mainFiles = ['index.ts', 'core/client.ts', 'mcp/server.ts'];
|
|
296
|
+
for (const mf of mainFiles) {
|
|
297
|
+
const fullPath = join(this.srcPath, mf);
|
|
298
|
+
if (!existsSync(fullPath))
|
|
299
|
+
continue;
|
|
300
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
301
|
+
this.parseTypeDefinitions(content, mf);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
extractKeywords(content) {
|
|
305
|
+
const keywords = new Set();
|
|
306
|
+
const headings = content.match(/^#+\s+(.+)$/gm) || [];
|
|
307
|
+
for (const h of headings) {
|
|
308
|
+
const text = h.replace(/^#+\s+/, '');
|
|
309
|
+
text.split(/\s+/).forEach(w => {
|
|
310
|
+
if (w.length > 3)
|
|
311
|
+
keywords.add(w.toLowerCase());
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
const identifiers = content.match(/`([a-zA-Z_][a-zA-Z0-9_]*)`/g) || [];
|
|
315
|
+
for (const id of identifiers) {
|
|
316
|
+
keywords.add(id.replace(/`/g, '').toLowerCase());
|
|
317
|
+
}
|
|
318
|
+
const terms = ['retry', 'cache', 'timeout', 'streaming', 'sse', 'websocket',
|
|
319
|
+
'middleware', 'plugin', 'hook', 'batch', 'pagination', 'http'];
|
|
320
|
+
for (const term of terms) {
|
|
321
|
+
if (content.toLowerCase().includes(term)) {
|
|
322
|
+
keywords.add(term);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return Array.from(keywords);
|
|
326
|
+
}
|
|
327
|
+
parseExampleMeta(code) {
|
|
328
|
+
const meta = {};
|
|
329
|
+
const docMatch = code.match(/\/\*\*[\s\S]*?\*\//);
|
|
330
|
+
if (docMatch) {
|
|
331
|
+
const doc = docMatch[0];
|
|
332
|
+
const titleMatch = doc.match(/@title\s+(.+)/);
|
|
333
|
+
if (titleMatch)
|
|
334
|
+
meta.title = titleMatch[1].trim();
|
|
335
|
+
const featureMatch = doc.match(/@feature\s+(.+)/);
|
|
336
|
+
if (featureMatch)
|
|
337
|
+
meta.feature = featureMatch[1].trim();
|
|
338
|
+
const complexityMatch = doc.match(/@complexity\s+(basic|intermediate|advanced)/);
|
|
339
|
+
if (complexityMatch)
|
|
340
|
+
meta.complexity = complexityMatch[1];
|
|
341
|
+
const descMatch = doc.match(/\*\s+([^@*\n].+)/);
|
|
342
|
+
if (descMatch)
|
|
343
|
+
meta.description = descMatch[1].trim();
|
|
344
|
+
}
|
|
345
|
+
return meta;
|
|
346
|
+
}
|
|
347
|
+
inferFeature(filename, code) {
|
|
348
|
+
const nameLower = filename.toLowerCase();
|
|
349
|
+
const codeLower = code.toLowerCase();
|
|
350
|
+
if (nameLower.includes('retry') || codeLower.includes('retry:'))
|
|
351
|
+
return 'retry';
|
|
352
|
+
if (nameLower.includes('cache') || codeLower.includes('cache:'))
|
|
353
|
+
return 'cache';
|
|
354
|
+
if (nameLower.includes('stream') || codeLower.includes('.sse('))
|
|
355
|
+
return 'streaming';
|
|
356
|
+
if (nameLower.includes('ws') || codeLower.includes('websocket'))
|
|
357
|
+
return 'websocket';
|
|
358
|
+
if (nameLower.includes('batch') || codeLower.includes('.batch('))
|
|
359
|
+
return 'batch';
|
|
360
|
+
if (nameLower.includes('pagin') || codeLower.includes('.paginate('))
|
|
361
|
+
return 'pagination';
|
|
362
|
+
return 'general';
|
|
363
|
+
}
|
|
364
|
+
extractMainCode(code) {
|
|
365
|
+
const lines = code.split('\n');
|
|
366
|
+
let startIndex = 0;
|
|
367
|
+
for (let i = 0; i < lines.length; i++) {
|
|
368
|
+
const line = lines[i].trim();
|
|
369
|
+
if (line.startsWith('//') || line.startsWith('/*') || line.startsWith('*') ||
|
|
370
|
+
line.startsWith('import') || line.startsWith('export') || line === '') {
|
|
371
|
+
startIndex = i + 1;
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return lines.slice(startIndex).join('\n').trim() || code;
|
|
378
|
+
}
|
|
379
|
+
parseTypeDefinitions(content, path) {
|
|
380
|
+
const interfaceRegex = /(?:\/\*\*[\s\S]*?\*\/\s*)?(export\s+)?interface\s+(\w+)(?:<[^>]+>)?\s*(?:extends\s+[^{]+)?\{[\s\S]*?\n\}/g;
|
|
381
|
+
let match;
|
|
382
|
+
while ((match = interfaceRegex.exec(content)) !== null) {
|
|
383
|
+
const name = match[2];
|
|
384
|
+
const definition = match[0];
|
|
385
|
+
const docMatch = definition.match(/\/\*\*\s*([\s\S]*?)\s*\*\//);
|
|
386
|
+
const description = docMatch
|
|
387
|
+
? docMatch[1].replace(/\s*\*\s*/g, ' ').trim()
|
|
388
|
+
: '';
|
|
389
|
+
this.typeDefinitions.push({
|
|
390
|
+
name,
|
|
391
|
+
kind: 'interface',
|
|
392
|
+
path,
|
|
393
|
+
definition: definition.replace(/\/\*\*[\s\S]*?\*\/\s*/, '').trim(),
|
|
394
|
+
description,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
const typeRegex = /(?:\/\*\*[\s\S]*?\*\/\s*)?(export\s+)?type\s+(\w+)(?:<[^>]+>)?\s*=\s*[^;]+;/g;
|
|
398
|
+
while ((match = typeRegex.exec(content)) !== null) {
|
|
399
|
+
const name = match[2];
|
|
400
|
+
const definition = match[0];
|
|
401
|
+
const docMatch = definition.match(/\/\*\*\s*([\s\S]*?)\s*\*\//);
|
|
402
|
+
const description = docMatch
|
|
403
|
+
? docMatch[1].replace(/\s*\*\s*/g, ' ').trim()
|
|
404
|
+
: '';
|
|
405
|
+
this.typeDefinitions.push({
|
|
406
|
+
name,
|
|
407
|
+
kind: 'type',
|
|
408
|
+
path,
|
|
409
|
+
definition: definition.replace(/\/\*\*[\s\S]*?\*\/\s*/, '').trim(),
|
|
410
|
+
description,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
let shellSearchInstance = null;
|
|
416
|
+
export function getShellSearch() {
|
|
417
|
+
if (!shellSearchInstance) {
|
|
418
|
+
shellSearchInstance = new ShellSearch();
|
|
419
|
+
}
|
|
420
|
+
return shellSearchInstance;
|
|
421
|
+
}
|
package/dist/cli/tui/shell.d.ts
CHANGED
|
@@ -53,6 +53,10 @@ export declare class RekShell {
|
|
|
53
53
|
private beautifyCSS;
|
|
54
54
|
private runBeautifySave;
|
|
55
55
|
private runSelectTable;
|
|
56
|
+
private runSearch;
|
|
57
|
+
private runSuggest;
|
|
58
|
+
private runExample;
|
|
59
|
+
private printMarkdown;
|
|
56
60
|
private printHelp;
|
|
57
61
|
}
|
|
58
62
|
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/shell.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/shell.ts"],"names":[],"mappings":"AAoCA,qBAAa,QAAQ;IACnB,OAAO,CAAC,EAAE,CAAsB;IAChC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,OAAO,CAA8B;IAC7C,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,aAAa,CAAc;;YAWrB,iBAAiB;IAe/B,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,aAAa;IAiBrB,OAAO,CAAC,SAAS;IAcJ,KAAK;IA8BlB,OAAO,CAAC,MAAM;YAKA,aAAa;YAwLb,kBAAkB;YAkBlB,SAAS;YAkBT,WAAW;IA0DzB,OAAO,CAAC,SAAS;IAMjB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,QAAQ;IAoCV,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM;IA6CnC,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,UAAU;YAeJ,cAAc;YAyEd,QAAQ;YA8GR,MAAM;YA2DN,MAAM;YA2EN,OAAO;YA+DP,OAAO;YA0CP,QAAQ;YAoER,SAAS;YAsCT,aAAa;YA8Bb,aAAa;YA+Bb,aAAa;YA6Bb,cAAc;YAkCd,eAAe;YA+Ef,gBAAgB;YAmEhB,YAAY;YAiEZ,mBAAmB;YAsFnB,QAAQ;YA0FR,YAAY;YAoCZ,YAAY;YA6CZ,WAAW;IA6CzB,OAAO,CAAC,UAAU;IA4GlB,OAAO,CAAC,WAAW;YAgFL,eAAe;YAkBf,cAAc;YAgDd,SAAS;YA2CT,UAAU;YAuBV,UAAU;IAwBxB,OAAO,CAAC,aAAa;IAuCrB,OAAO,CAAC,SAAS;CAwElB"}
|
package/dist/cli/tui/shell.js
CHANGED
|
@@ -11,6 +11,7 @@ import { getSecurityRecords } from '../../utils/dns-toolkit.js';
|
|
|
11
11
|
import { rdap } from '../../utils/rdap.js';
|
|
12
12
|
import { ScrapeDocument } from '../../scrape/document.js';
|
|
13
13
|
import colors from '../../utils/colors.js';
|
|
14
|
+
import { getShellSearch } from './shell-search.js';
|
|
14
15
|
let highlight;
|
|
15
16
|
async function initDependencies() {
|
|
16
17
|
if (!highlight) {
|
|
@@ -86,6 +87,7 @@ export class RekShell {
|
|
|
86
87
|
'ws', 'udp', 'load', 'chat', 'ai',
|
|
87
88
|
'whois', 'tls', 'ssl', 'dns', 'rdap', 'ping',
|
|
88
89
|
'scrap', '$', '$text', '$attr', '$html', '$links', '$images', '$scripts', '$css', '$sourcemaps', '$unmap', '$unmap:view', '$unmap:save', '$beautify', '$beautify:save', '$table',
|
|
90
|
+
'?', 'search', 'suggest', 'example',
|
|
89
91
|
'help', 'clear', 'exit', 'set', 'url', 'vars', 'env'
|
|
90
92
|
];
|
|
91
93
|
const hits = commands.filter((c) => c.startsWith(line));
|
|
@@ -217,6 +219,23 @@ export class RekShell {
|
|
|
217
219
|
case '$table':
|
|
218
220
|
await this.runSelectTable(parts.slice(1).join(' '));
|
|
219
221
|
return;
|
|
222
|
+
case '?':
|
|
223
|
+
case 'search':
|
|
224
|
+
await this.runSearch(parts.slice(1).join(' '));
|
|
225
|
+
return;
|
|
226
|
+
case 'suggest':
|
|
227
|
+
await this.runSuggest(parts.slice(1).join(' '));
|
|
228
|
+
return;
|
|
229
|
+
case 'example':
|
|
230
|
+
await this.runExample(parts.slice(1).join(' '));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (input.endsWith('?') && !input.startsWith('http')) {
|
|
234
|
+
const query = input.slice(0, -1).trim();
|
|
235
|
+
if (query) {
|
|
236
|
+
await this.runSearch(query);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
220
239
|
}
|
|
221
240
|
const methods = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
|
|
222
241
|
let method = 'GET';
|
|
@@ -1640,6 +1659,117 @@ export class RekShell {
|
|
|
1640
1659
|
}
|
|
1641
1660
|
console.log('');
|
|
1642
1661
|
}
|
|
1662
|
+
async runSearch(query) {
|
|
1663
|
+
if (!query.trim()) {
|
|
1664
|
+
console.log(colors.yellow('Usage: ? <query> or search <query>'));
|
|
1665
|
+
console.log(colors.gray(' Examples:'));
|
|
1666
|
+
console.log(colors.gray(' ? retry exponential backoff'));
|
|
1667
|
+
console.log(colors.gray(' search cache configuration'));
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
console.log(colors.gray('Searching documentation...'));
|
|
1671
|
+
try {
|
|
1672
|
+
const search = getShellSearch();
|
|
1673
|
+
const results = await search.search(query, { limit: 5 });
|
|
1674
|
+
if (results.length === 0) {
|
|
1675
|
+
console.log(colors.yellow(`No results for: "${query}"`));
|
|
1676
|
+
console.log(colors.gray('Try different keywords like: retry, cache, streaming, websocket, pagination'));
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
console.log(colors.bold(colors.cyan(`\nFound ${results.length} results for "${query}":\n`)));
|
|
1680
|
+
for (let i = 0; i < results.length; i++) {
|
|
1681
|
+
const result = results[i];
|
|
1682
|
+
const score = Math.round(result.score * 100);
|
|
1683
|
+
console.log(` ${colors.green(`${i + 1}.`)} ${colors.bold(result.title)} ${colors.gray(`(${score}%)`)}`);
|
|
1684
|
+
console.log(` ${colors.gray(result.path)}`);
|
|
1685
|
+
if (result.snippet) {
|
|
1686
|
+
const snippet = result.snippet.slice(0, 120).replace(/\n/g, ' ');
|
|
1687
|
+
console.log(` ${colors.gray(snippet)}${result.snippet.length > 120 ? '...' : ''}`);
|
|
1688
|
+
}
|
|
1689
|
+
console.log('');
|
|
1690
|
+
}
|
|
1691
|
+
console.log(colors.gray(' Tip: Use "get_doc <path>" to read full documentation'));
|
|
1692
|
+
}
|
|
1693
|
+
catch (error) {
|
|
1694
|
+
console.error(colors.red(`Search failed: ${error.message}`));
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
async runSuggest(useCase) {
|
|
1698
|
+
if (!useCase.trim()) {
|
|
1699
|
+
console.log(colors.yellow('Usage: suggest <use-case>'));
|
|
1700
|
+
console.log(colors.gray(' Examples:'));
|
|
1701
|
+
console.log(colors.gray(' suggest implement retry with exponential backoff'));
|
|
1702
|
+
console.log(colors.gray(' suggest cache API responses'));
|
|
1703
|
+
console.log(colors.gray(' suggest stream AI responses from OpenAI'));
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
console.log(colors.gray('Getting suggestions...'));
|
|
1707
|
+
try {
|
|
1708
|
+
const search = getShellSearch();
|
|
1709
|
+
const suggestion = await search.suggest(useCase);
|
|
1710
|
+
console.log('');
|
|
1711
|
+
this.printMarkdown(suggestion);
|
|
1712
|
+
}
|
|
1713
|
+
catch (error) {
|
|
1714
|
+
console.error(colors.red(`Suggest failed: ${error.message}`));
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
async runExample(feature) {
|
|
1718
|
+
if (!feature.trim()) {
|
|
1719
|
+
console.log(colors.yellow('Usage: example <feature>'));
|
|
1720
|
+
console.log(colors.gray(' Examples:'));
|
|
1721
|
+
console.log(colors.gray(' example retry'));
|
|
1722
|
+
console.log(colors.gray(' example streaming'));
|
|
1723
|
+
console.log(colors.gray(' example cache'));
|
|
1724
|
+
console.log(colors.gray(' example websocket'));
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
console.log(colors.gray('Finding examples...'));
|
|
1728
|
+
try {
|
|
1729
|
+
const search = getShellSearch();
|
|
1730
|
+
const examples = await search.getExamples(feature, { limit: 3 });
|
|
1731
|
+
console.log('');
|
|
1732
|
+
this.printMarkdown(examples);
|
|
1733
|
+
}
|
|
1734
|
+
catch (error) {
|
|
1735
|
+
console.error(colors.red(`Example lookup failed: ${error.message}`));
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
printMarkdown(text) {
|
|
1739
|
+
const lines = text.split('\n');
|
|
1740
|
+
for (const line of lines) {
|
|
1741
|
+
if (line.startsWith('**') && line.endsWith('**')) {
|
|
1742
|
+
console.log(colors.bold(line.replace(/\*\*/g, '')));
|
|
1743
|
+
}
|
|
1744
|
+
else if (line.startsWith('### ')) {
|
|
1745
|
+
console.log(colors.bold(colors.cyan(line.slice(4))));
|
|
1746
|
+
}
|
|
1747
|
+
else if (line.startsWith('## ')) {
|
|
1748
|
+
console.log(colors.bold(colors.cyan(line.slice(3))));
|
|
1749
|
+
}
|
|
1750
|
+
else if (line.startsWith('# ')) {
|
|
1751
|
+
console.log(colors.bold(colors.cyan(line.slice(2))));
|
|
1752
|
+
}
|
|
1753
|
+
else if (line.startsWith('```')) {
|
|
1754
|
+
if (line.length > 3) {
|
|
1755
|
+
console.log(colors.gray('─'.repeat(40)));
|
|
1756
|
+
}
|
|
1757
|
+
else {
|
|
1758
|
+
console.log(colors.gray('─'.repeat(40)));
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
else if (line.startsWith(' - ') || line.startsWith('- ')) {
|
|
1762
|
+
const content = line.replace(/^\s*-\s*/, '');
|
|
1763
|
+
console.log(` ${colors.green('•')} ${content}`);
|
|
1764
|
+
}
|
|
1765
|
+
else if (line.match(/^\s+/)) {
|
|
1766
|
+
console.log(colors.yellow(line));
|
|
1767
|
+
}
|
|
1768
|
+
else {
|
|
1769
|
+
console.log(line);
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1643
1773
|
printHelp() {
|
|
1644
1774
|
console.log(`
|
|
1645
1775
|
${colors.bold(colors.cyan('Rek Console Help'))}
|
|
@@ -1698,6 +1828,12 @@ export class RekShell {
|
|
|
1698
1828
|
${colors.green('$beautify:save [f]')} Save beautified code to file.
|
|
1699
1829
|
${colors.green('$table <selector>')} Extract table as data.
|
|
1700
1830
|
|
|
1831
|
+
${colors.bold('Documentation:')}
|
|
1832
|
+
${colors.green('? <query>')} Search Recker documentation.
|
|
1833
|
+
${colors.green('search <query>')} Alias for ? (hybrid fuzzy+semantic search).
|
|
1834
|
+
${colors.green('suggest <use-case>')} Get implementation suggestions.
|
|
1835
|
+
${colors.green('example <feature>')} Get code examples for a feature.
|
|
1836
|
+
|
|
1701
1837
|
${colors.bold('Examples:')}
|
|
1702
1838
|
› url httpbin.org
|
|
1703
1839
|
› get /json
|