k0ntext 3.3.0 → 3.3.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.
- package/README.md +15 -0
- package/dist/cli/repl/index.d.ts +3 -0
- package/dist/cli/repl/index.d.ts.map +1 -1
- package/dist/cli/repl/index.js +95 -89
- package/dist/cli/repl/index.js.map +1 -1
- package/dist/cli/repl/tui/panels/config.d.ts +71 -0
- package/dist/cli/repl/tui/panels/config.d.ts.map +1 -0
- package/dist/cli/repl/tui/panels/config.js +392 -0
- package/dist/cli/repl/tui/panels/config.js.map +1 -0
- package/dist/cli/repl/tui/panels/drift.d.ts +95 -0
- package/dist/cli/repl/tui/panels/drift.d.ts.map +1 -0
- package/dist/cli/repl/tui/panels/drift.js +353 -0
- package/dist/cli/repl/tui/panels/drift.js.map +1 -0
- package/dist/cli/repl/tui/panels/indexing.d.ts +86 -0
- package/dist/cli/repl/tui/panels/indexing.d.ts.map +1 -0
- package/dist/cli/repl/tui/panels/indexing.js +254 -0
- package/dist/cli/repl/tui/panels/indexing.js.map +1 -0
- package/dist/cli/repl/tui/panels/search.d.ts +66 -0
- package/dist/cli/repl/tui/panels/search.d.ts.map +1 -0
- package/dist/cli/repl/tui/panels/search.js +215 -0
- package/dist/cli/repl/tui/panels/search.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/repl/index.ts +102 -99
- package/src/cli/repl/tui/panels/config.ts +457 -0
- package/src/cli/repl/tui/panels/drift.ts +458 -0
- package/src/cli/repl/tui/panels/indexing.ts +324 -0
- package/src/cli/repl/tui/panels/search.ts +272 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Indexing Progress Visualization
|
|
3
|
+
*
|
|
4
|
+
* Real-time progress display for indexing operations
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { K0NTEXT_THEME } from '../theme.js';
|
|
9
|
+
import ora, { Ora } from 'ora';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Indexing progress state
|
|
13
|
+
*/
|
|
14
|
+
interface IndexingProgress {
|
|
15
|
+
total: number;
|
|
16
|
+
processed: number;
|
|
17
|
+
currentFile?: string;
|
|
18
|
+
stage: 'discovering' | 'indexing_docs' | 'indexing_code' | 'indexing_tools' | 'generating_embeddings' | 'complete';
|
|
19
|
+
startTime: number;
|
|
20
|
+
errors: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Indexing statistics
|
|
25
|
+
*/
|
|
26
|
+
interface IndexingStats {
|
|
27
|
+
docsIndexed: number;
|
|
28
|
+
codeIndexed: number;
|
|
29
|
+
configsIndexed: number;
|
|
30
|
+
embeddingsGenerated: number;
|
|
31
|
+
filesSkipped: number;
|
|
32
|
+
duration: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Indexing Progress Visualizer
|
|
37
|
+
*/
|
|
38
|
+
export class IndexingProgressVisualizer {
|
|
39
|
+
private progress: IndexingProgress;
|
|
40
|
+
private stats: IndexingStats;
|
|
41
|
+
private spinner: Ora | null = null;
|
|
42
|
+
|
|
43
|
+
constructor() {
|
|
44
|
+
this.progress = {
|
|
45
|
+
total: 0,
|
|
46
|
+
processed: 0,
|
|
47
|
+
stage: 'discovering',
|
|
48
|
+
startTime: Date.now(),
|
|
49
|
+
errors: []
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
this.stats = {
|
|
53
|
+
docsIndexed: 0,
|
|
54
|
+
codeIndexed: 0,
|
|
55
|
+
configsIndexed: 0,
|
|
56
|
+
embeddingsGenerated: 0,
|
|
57
|
+
filesSkipped: 0,
|
|
58
|
+
duration: 0
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Start a new indexing operation
|
|
64
|
+
*/
|
|
65
|
+
start(totalFiles: number): void {
|
|
66
|
+
this.progress = {
|
|
67
|
+
total: totalFiles,
|
|
68
|
+
processed: 0,
|
|
69
|
+
stage: 'discovering',
|
|
70
|
+
startTime: Date.now(),
|
|
71
|
+
errors: []
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
this.stats = {
|
|
75
|
+
docsIndexed: 0,
|
|
76
|
+
codeIndexed: 0,
|
|
77
|
+
configsIndexed: 0,
|
|
78
|
+
embeddingsGenerated: 0,
|
|
79
|
+
filesSkipped: 0,
|
|
80
|
+
duration: 0
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
this.spinner = ora('Preparing to index...').start();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Update progress
|
|
88
|
+
*/
|
|
89
|
+
update(stage: IndexingProgress['stage'], updates: {
|
|
90
|
+
processed?: number;
|
|
91
|
+
currentFile?: string;
|
|
92
|
+
error?: string;
|
|
93
|
+
}): void {
|
|
94
|
+
this.progress.stage = stage;
|
|
95
|
+
|
|
96
|
+
if (updates.processed !== undefined) {
|
|
97
|
+
this.progress.processed = updates.processed;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (updates.currentFile) {
|
|
101
|
+
this.progress.currentFile = updates.currentFile;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (updates.error) {
|
|
105
|
+
this.progress.errors.push(updates.error);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this.render();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Render current progress
|
|
113
|
+
*/
|
|
114
|
+
private render(): void {
|
|
115
|
+
if (!this.spinner) return;
|
|
116
|
+
|
|
117
|
+
const percentage = Math.min(100, Math.round((this.progress.processed / this.progress.total) * 100));
|
|
118
|
+
const elapsed = Date.now() - this.progress.startTime;
|
|
119
|
+
const eta = this.progress.processed > 0
|
|
120
|
+
? (elapsed / this.progress.processed) * (this.progress.total - this.progress.processed)
|
|
121
|
+
: 0;
|
|
122
|
+
|
|
123
|
+
// Update spinner text
|
|
124
|
+
const stageEmoji = {
|
|
125
|
+
discovering: '🔍',
|
|
126
|
+
indexing_docs: '📄',
|
|
127
|
+
indexing_code: '💻',
|
|
128
|
+
indexing_tools: '⚙️',
|
|
129
|
+
generating_embeddings: '🧠',
|
|
130
|
+
complete: '✓'
|
|
131
|
+
}[this.progress.stage];
|
|
132
|
+
|
|
133
|
+
let spinnerText = `${stageEmoji} ${this.getStageName(this.progress.stage)}: ${percentage}%`;
|
|
134
|
+
|
|
135
|
+
if (this.progress.currentFile) {
|
|
136
|
+
const fileName = this.progress.currentFile.split('/').pop()!;
|
|
137
|
+
spinnerText += ` (${K0NTEXT_THEME.dim(fileName.slice(0, 30))}${this.progress.currentFile.length > 30 ? '...' : ''})`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (eta > 1000) {
|
|
141
|
+
const etaText = this.formatDuration(eta);
|
|
142
|
+
spinnerText += ` ${K0NTEXT_THEME.dim(`ETA: ${etaText}`)}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.spinner.text = spinnerText;
|
|
146
|
+
|
|
147
|
+
// Show detailed progress on intervals
|
|
148
|
+
if (this.progress.processed % 50 === 0 || this.progress.stage === 'complete') {
|
|
149
|
+
this.showDetailedProgress();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get stage name
|
|
155
|
+
*/
|
|
156
|
+
private getStageName(stage: IndexingProgress['stage']): string {
|
|
157
|
+
const names = {
|
|
158
|
+
discovering: 'Discovering files',
|
|
159
|
+
indexing_docs: 'Indexing documents',
|
|
160
|
+
indexing_code: 'Indexing code',
|
|
161
|
+
indexing_tools: 'Indexing configs',
|
|
162
|
+
generating_embeddings: 'Generating embeddings',
|
|
163
|
+
complete: 'Complete'
|
|
164
|
+
};
|
|
165
|
+
return names[stage];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Show detailed progress panel
|
|
170
|
+
*/
|
|
171
|
+
showDetailedProgress(): void {
|
|
172
|
+
const lines: string[] = [];
|
|
173
|
+
|
|
174
|
+
lines.push('');
|
|
175
|
+
lines.push(K0NTEXT_THEME.header('━━━ Indexing Progress ━━━'));
|
|
176
|
+
|
|
177
|
+
// Progress bar
|
|
178
|
+
lines.push(` Progress: ${this.renderProgressBar()}`);
|
|
179
|
+
lines.push(` Stage: ${this.getStageName(this.progress.stage)}`);
|
|
180
|
+
lines.push('');
|
|
181
|
+
|
|
182
|
+
// Stats
|
|
183
|
+
lines.push(K0NTEXT_THEME.header('━━━ Statistics ━──'));
|
|
184
|
+
lines.push(` Documents Indexed: ${K0NTEXT_THEME.cyan(this.stats.docsIndexed.toString())}`);
|
|
185
|
+
lines.push(` Code Files Indexed: ${K0NTEXT_THEME.cyan(this.stats.codeIndexed.toString())}`);
|
|
186
|
+
lines.push(` Configs Indexed: ${K0NTEXT_THEME.cyan(this.stats.configsIndexed.toString())}`);
|
|
187
|
+
lines.push(` Embeddings: ${K0NTEXT_THEME.cyan(this.stats.embeddingsGenerated.toString())}`);
|
|
188
|
+
lines.push(` Files Skipped: ${this.stats.filesSkipped > 0 ? K0NTEXT_THEME.warning(this.stats.filesSkipped.toString()) : K0NTEXT_THEME.dim('0')}`);
|
|
189
|
+
lines.push('');
|
|
190
|
+
|
|
191
|
+
// Errors
|
|
192
|
+
if (this.progress.errors.length > 0) {
|
|
193
|
+
lines.push(K0NTEXT_THEME.header('━━━ Errors ━──'));
|
|
194
|
+
for (const error of this.progress.errors.slice(-5)) {
|
|
195
|
+
lines.push(` ${K0NTEXT_THEME.error('✖')} ${error}`);
|
|
196
|
+
}
|
|
197
|
+
lines.push('');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Clear and render
|
|
201
|
+
console.log(lines.join('\n'));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Render progress bar
|
|
206
|
+
*/
|
|
207
|
+
private renderProgressBar(): string {
|
|
208
|
+
const percentage = Math.min(100, Math.round((this.progress.processed / this.progress.total) * 100));
|
|
209
|
+
const width = 30;
|
|
210
|
+
const filled = Math.floor((percentage / 100) * width);
|
|
211
|
+
|
|
212
|
+
const bar = K0NTEXT_THEME.primary('█'.repeat(filled)) + K0NTEXT_THEME.dim('░'.repeat(width - filled));
|
|
213
|
+
const pct = K0NTEXT_THEME.primary(`${percentage.toString().padStart(3)}%`);
|
|
214
|
+
const counts = `${this.progress.processed}/${this.progress.total}`;
|
|
215
|
+
|
|
216
|
+
return `${bar} ${pct} (${K0NTEXT_THEME.dim(counts)})`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Format duration
|
|
221
|
+
*/
|
|
222
|
+
private formatDuration(ms: number): string {
|
|
223
|
+
const seconds = Math.floor(ms / 1000);
|
|
224
|
+
const minutes = Math.floor(seconds / 60);
|
|
225
|
+
const hours = Math.floor(minutes / 60);
|
|
226
|
+
|
|
227
|
+
if (hours > 0) {
|
|
228
|
+
return `${hours}h ${minutes % 60}m`;
|
|
229
|
+
} else if (minutes > 0) {
|
|
230
|
+
return `${minutes}m ${seconds % 60}s`;
|
|
231
|
+
} else {
|
|
232
|
+
return `${seconds}s`;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Complete indexing
|
|
238
|
+
*/
|
|
239
|
+
complete(stats: Partial<IndexingStats>): void {
|
|
240
|
+
this.progress.stage = 'complete';
|
|
241
|
+
this.progress.processed = this.progress.total;
|
|
242
|
+
|
|
243
|
+
this.stats = {
|
|
244
|
+
docsIndexed: stats.docsIndexed || 0,
|
|
245
|
+
codeIndexed: stats.codeIndexed || 0,
|
|
246
|
+
configsIndexed: stats.configsIndexed || 0,
|
|
247
|
+
embeddingsGenerated: stats.embeddingsGenerated || 0,
|
|
248
|
+
filesSkipped: stats.filesSkipped || 0,
|
|
249
|
+
duration: Date.now() - this.progress.startTime
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (this.spinner) {
|
|
253
|
+
this.spinner.succeed('Indexing complete!');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.showFinalStats();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Show final statistics
|
|
261
|
+
*/
|
|
262
|
+
showFinalStats(): void {
|
|
263
|
+
const lines: string[] = [];
|
|
264
|
+
|
|
265
|
+
lines.push('');
|
|
266
|
+
lines.push(K0NTEXT_THEME.success('━━━ Indexing Complete ━━━'));
|
|
267
|
+
lines.push('');
|
|
268
|
+
|
|
269
|
+
lines.push(` ${K0NTEXT_THEME.header('Files Processed:')}`);
|
|
270
|
+
lines.push(` Documents: ${K0NTEXT_THEME.cyan(this.stats.docsIndexed.toString())}`);
|
|
271
|
+
lines.push(` Code Files: ${K0NTEXT_THEME.cyan(this.stats.codeIndexed.toString())}`);
|
|
272
|
+
lines.push(` Configs: ${K0NTEXT_THEME.cyan(this.stats.configsIndexed.toString())}`);
|
|
273
|
+
lines.push(` Total: ${K0NTEXT_THEME.primary((this.stats.docsIndexed + this.stats.codeIndexed + this.stats.configsIndexed).toString())}`);
|
|
274
|
+
lines.push('');
|
|
275
|
+
|
|
276
|
+
if (this.stats.embeddingsGenerated > 0) {
|
|
277
|
+
lines.push(` ${K0NTEXT_THEME.header('Embeddings:')}`);
|
|
278
|
+
lines.push(` Generated: ${K0NTEXT_THEME.cyan(this.stats.embeddingsGenerated.toString())}`);
|
|
279
|
+
lines.push('');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (this.stats.filesSkipped > 0) {
|
|
283
|
+
lines.push(` ${K0NTEXT_THEME.warning('⚠ Files Skipped:')}`);
|
|
284
|
+
lines.push(` ${this.stats.filesSkipped} files hit limit (use --max-files to increase)`);
|
|
285
|
+
lines.push('');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
lines.push(` ${K0NTEXT_THEME.header('Time:')}`);
|
|
289
|
+
lines.push(` Duration: ${K0NTEXT_THEME.formatTimestamp(new Date(Date.now() - this.stats.duration))}`);
|
|
290
|
+
lines.push('');
|
|
291
|
+
|
|
292
|
+
console.log(lines.join('\n'));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Cancel indexing
|
|
297
|
+
*/
|
|
298
|
+
cancel(): void {
|
|
299
|
+
if (this.spinner) {
|
|
300
|
+
this.spinner.warn('Indexing cancelled');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
console.log('');
|
|
304
|
+
console.log(K0NTEXT_THEME.info('Indexing was cancelled.'));
|
|
305
|
+
console.log(K0NTEXT_THEME.dim('Partial results may have been saved.'));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Format a timestamp relative to now
|
|
310
|
+
*/
|
|
311
|
+
private formatTimestamp(ms: number): string {
|
|
312
|
+
const seconds = Math.floor(ms / 1000);
|
|
313
|
+
const minutes = Math.floor(seconds / 60);
|
|
314
|
+
const hours = Math.floor(minutes / 60);
|
|
315
|
+
|
|
316
|
+
if (hours > 0) {
|
|
317
|
+
return `${hours}h ${minutes % 60}m ${seconds % 60}s ago`;
|
|
318
|
+
} else if (minutes > 0) {
|
|
319
|
+
return `${minutes}m ${seconds % 60}s ago`;
|
|
320
|
+
} else {
|
|
321
|
+
return `${seconds}s ago`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Search Panel
|
|
3
|
+
*
|
|
4
|
+
* Enhanced search with filtering, sorting, and detailed results
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { K0NTEXT_THEME } from '../theme.js';
|
|
9
|
+
import type { ContextItem } from '../../../../db/client.js';
|
|
10
|
+
import type { ContextType } from '../../../../db/schema.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Search filter options
|
|
14
|
+
*/
|
|
15
|
+
export interface SearchFilters {
|
|
16
|
+
type?: ContextType;
|
|
17
|
+
sortBy?: 'relevance' | 'name' | 'date' | 'size';
|
|
18
|
+
sortOrder?: 'asc' | 'desc';
|
|
19
|
+
limit?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Enhanced search result with metadata
|
|
24
|
+
*/
|
|
25
|
+
export interface EnhancedSearchResult {
|
|
26
|
+
item: ContextItem;
|
|
27
|
+
score?: number;
|
|
28
|
+
highlights: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Advanced Search Panel
|
|
33
|
+
*/
|
|
34
|
+
export class AdvancedSearchPanel {
|
|
35
|
+
/**
|
|
36
|
+
* Display advanced search results with formatting
|
|
37
|
+
*/
|
|
38
|
+
displayResults(results: EnhancedSearchResult[], query: string, filters: SearchFilters): string {
|
|
39
|
+
const lines: string[] = [];
|
|
40
|
+
|
|
41
|
+
// Header with query and filters
|
|
42
|
+
lines.push('');
|
|
43
|
+
lines.push(K0NTEXT_THEME.header(`━━━ Search Results: "${query}" ━━━`));
|
|
44
|
+
|
|
45
|
+
if (filters.type) {
|
|
46
|
+
lines.push(` ${K0NTEXT_THEME.dim('Filter:')} ${K0NTEXT_THEME.cyan(filters.type)}`);
|
|
47
|
+
}
|
|
48
|
+
if (filters.sortBy) {
|
|
49
|
+
lines.push(` ${K0NTEXT_THEME.dim('Sorted by:')} ${K0NTEXT_THEME.cyan(filters.sortBy)} ${K0NTEXT_THEME.dim('(')}${filters.sortOrder}${K0NTEXT_THEME.dim(')')}`);
|
|
50
|
+
}
|
|
51
|
+
lines.push(` ${K0NTEXT_THEME.dim('Found:')} ${K0NTEXT_THEME.highlight(results.length.toString())} results`);
|
|
52
|
+
lines.push('');
|
|
53
|
+
|
|
54
|
+
// Type legend
|
|
55
|
+
lines.push(K0NTEXT_THEME.dim('Types: 📄=doc 💻=code ⚙️=config 📋=workflow 🤖=agent 🔧=command'));
|
|
56
|
+
lines.push('');
|
|
57
|
+
|
|
58
|
+
// Display results
|
|
59
|
+
for (let i = 0; i < Math.min(results.length, filters.limit || 20); i++) {
|
|
60
|
+
const { item, score, highlights } = results[i];
|
|
61
|
+
|
|
62
|
+
// Type emoji
|
|
63
|
+
const typeEmoji = this.getTypeEmoji(item.type);
|
|
64
|
+
const relevance = score !== undefined ? ` ${K0NTEXT_THEME.dim(`(${Math.round(score * 100)}%)`)}` : '';
|
|
65
|
+
|
|
66
|
+
lines.push(`${K0NTEXT_THEME.primary(`${i + 1}.`)} ${typeEmoji} ${K0NTEXT_THEME.highlight(item.name)}${relevance}`);
|
|
67
|
+
|
|
68
|
+
if (item.filePath) {
|
|
69
|
+
lines.push(` ${K0NTEXT_THEME.dim(item.filePath)}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Show content preview
|
|
73
|
+
if (item.content) {
|
|
74
|
+
const preview = this.getContentPreview(item.content, query);
|
|
75
|
+
if (preview) {
|
|
76
|
+
lines.push(` ${K0NTEXT_THEME.dim(preview)}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Show metadata
|
|
81
|
+
if (item.metadata) {
|
|
82
|
+
const meta = this.formatMetadata(item.metadata as Record<string, unknown>);
|
|
83
|
+
if (meta) {
|
|
84
|
+
lines.push(` ${K0NTEXT_THEME.dim(meta)}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
lines.push('');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (results.length > (filters.limit || 20)) {
|
|
92
|
+
const remaining = results.length - (filters.limit || 20);
|
|
93
|
+
lines.push(K0NTEXT_THEME.dim(` ... and ${remaining} more results`));
|
|
94
|
+
lines.push('');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Search tips
|
|
98
|
+
lines.push(K0NTEXT_THEME.header('━━━ Search Tips ━━━'));
|
|
99
|
+
lines.push(` ${K0NTEXT_THEME.cyan('•')} Use ${K0NTEXT_THEME.highlight('search <query> --type <type>')} to filter`);
|
|
100
|
+
lines.push(` ${K0NTEXT_THEME.cyan('•')} Use ${K0NTEXT_THEME.highlight('search <query> --sort <field>')} to sort`);
|
|
101
|
+
lines.push(` ${K0NTEXT_THEME.cyan('•')} Use ${K0NTEXT_THEME.highlight('search <query> --limit <n>')} for more results`);
|
|
102
|
+
lines.push('');
|
|
103
|
+
|
|
104
|
+
return lines.join('\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get emoji for context type
|
|
109
|
+
*/
|
|
110
|
+
private getTypeEmoji(type: ContextType): string {
|
|
111
|
+
const emojis: Record<string, string> = {
|
|
112
|
+
doc: '📄',
|
|
113
|
+
code: '💻',
|
|
114
|
+
tool_config: '⚙️',
|
|
115
|
+
workflow: '📋',
|
|
116
|
+
agent: '🤖',
|
|
117
|
+
command: '🔧',
|
|
118
|
+
commit: '📝',
|
|
119
|
+
knowledge: '🧠',
|
|
120
|
+
config: '⚙️'
|
|
121
|
+
};
|
|
122
|
+
return emojis[type] || '📄';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get content preview with highlighted query terms
|
|
127
|
+
*/
|
|
128
|
+
private getContentPreview(content: string, query: string): string {
|
|
129
|
+
const maxLength = 100;
|
|
130
|
+
const preview = content.slice(0, maxLength);
|
|
131
|
+
|
|
132
|
+
// Highlight query terms
|
|
133
|
+
const terms = query.toLowerCase().split(/\s+/).filter(t => t.length > 2);
|
|
134
|
+
let highlighted = preview;
|
|
135
|
+
|
|
136
|
+
for (const term of terms) {
|
|
137
|
+
const regex = new RegExp(`(${this.escapeRegex(term)})`, 'gi');
|
|
138
|
+
highlighted = highlighted.replace(regex, K0NTEXT_THEME.highlight('$1'));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return highlighted + (content.length > maxLength ? '...' : '');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Escape special regex characters
|
|
146
|
+
*/
|
|
147
|
+
private escapeRegex(string: string): string {
|
|
148
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Format metadata for display
|
|
153
|
+
*/
|
|
154
|
+
private formatMetadata(metadata: Record<string, unknown>): string | null {
|
|
155
|
+
const parts: string[] = [];
|
|
156
|
+
|
|
157
|
+
if (metadata.size !== undefined) {
|
|
158
|
+
parts.push(K0NTEXT_THEME.formatFileSize(Number(metadata.size)));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (metadata.module) {
|
|
162
|
+
parts.push(`module:${metadata.module}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (metadata.tool) {
|
|
166
|
+
parts.push(`tool:${metadata.tool}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return parts.length > 0 ? parts.join(' • ') : null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Sort results by various criteria
|
|
174
|
+
*/
|
|
175
|
+
sortResults(results: EnhancedSearchResult[], sortBy: 'relevance' | 'name' | 'date' | 'size', order: 'asc' | 'desc'): EnhancedSearchResult[] {
|
|
176
|
+
return results.sort((a, b) => {
|
|
177
|
+
let comparison = 0;
|
|
178
|
+
|
|
179
|
+
switch (sortBy) {
|
|
180
|
+
case 'relevance':
|
|
181
|
+
comparison = (b.score || 0) - (a.score || 0);
|
|
182
|
+
break;
|
|
183
|
+
case 'name':
|
|
184
|
+
comparison = a.item.name.localeCompare(b.item.name);
|
|
185
|
+
break;
|
|
186
|
+
case 'date':
|
|
187
|
+
const aDate = a.item.updatedAt ? new Date(a.item.updatedAt).getTime() : 0;
|
|
188
|
+
const bDate = b.item.updatedAt ? new Date(b.item.updatedAt).getTime() : 0;
|
|
189
|
+
comparison = bDate - aDate;
|
|
190
|
+
break;
|
|
191
|
+
case 'size':
|
|
192
|
+
const aSize = (a.item.metadata as Record<string, unknown>)?.size || 0;
|
|
193
|
+
const bSize = (b.item.metadata as Record<string, unknown>)?.size || 0;
|
|
194
|
+
comparison = Number(bSize) - Number(aSize);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return order === 'desc' ? -comparison : comparison;
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Filter results by type
|
|
204
|
+
*/
|
|
205
|
+
filterByType(results: EnhancedSearchResult[], type: ContextType): EnhancedSearchResult[] {
|
|
206
|
+
return results.filter(r => r.item.type === type);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Parse search flags from command arguments
|
|
211
|
+
*/
|
|
212
|
+
parseSearchFlags(args: string[]): SearchFilters {
|
|
213
|
+
const filters: SearchFilters = {};
|
|
214
|
+
|
|
215
|
+
for (let i = 0; i < args.length; i++) {
|
|
216
|
+
const arg = args[i];
|
|
217
|
+
|
|
218
|
+
switch (arg) {
|
|
219
|
+
case '--type':
|
|
220
|
+
case '-t':
|
|
221
|
+
filters.type = (args[++i] || 'all') as ContextType;
|
|
222
|
+
break;
|
|
223
|
+
case '--sort':
|
|
224
|
+
case '-s':
|
|
225
|
+
filters.sortBy = (args[++i] || 'relevance') as SearchFilters['sortBy'];
|
|
226
|
+
break;
|
|
227
|
+
case '--order':
|
|
228
|
+
case '-o':
|
|
229
|
+
filters.sortOrder = (args[++i] || 'desc') as SearchFilters['sortOrder'];
|
|
230
|
+
break;
|
|
231
|
+
case '--limit':
|
|
232
|
+
case '-l':
|
|
233
|
+
filters.limit = Number(args[++i]) || 20;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return filters;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Show search help
|
|
243
|
+
*/
|
|
244
|
+
showSearchHelp(): string {
|
|
245
|
+
const lines = [
|
|
246
|
+
'',
|
|
247
|
+
K0NTEXT_THEME.header('━━━ Advanced Search Help ━━━'),
|
|
248
|
+
'',
|
|
249
|
+
' Usage:',
|
|
250
|
+
' search <query> [options]',
|
|
251
|
+
'',
|
|
252
|
+
' Options:',
|
|
253
|
+
' --type, -t <type> Filter by type (doc, code, tool_config, workflow, agent, command)',
|
|
254
|
+
' --sort, -s <field> Sort by (relevance, name, date, size)',
|
|
255
|
+
' --order, -o <dir> Sort order (asc, desc)',
|
|
256
|
+
' --limit, -l <n> Max results (default: 20)',
|
|
257
|
+
'',
|
|
258
|
+
' Examples:',
|
|
259
|
+
' search auth --type code',
|
|
260
|
+
' search "user login" --sort date --order desc',
|
|
261
|
+
' search config --limit 50',
|
|
262
|
+
'',
|
|
263
|
+
' Shortcuts:',
|
|
264
|
+
' s <query> Alias for search',
|
|
265
|
+
' f <query> Search and filter',
|
|
266
|
+
' ? Show this help',
|
|
267
|
+
''
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
return lines.join('\n');
|
|
271
|
+
}
|
|
272
|
+
}
|