k0ntext 3.2.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +28 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/repl/core/parser.d.ts +84 -0
- package/dist/cli/repl/core/parser.d.ts.map +1 -0
- package/dist/cli/repl/core/parser.js +309 -0
- package/dist/cli/repl/core/parser.js.map +1 -0
- package/dist/cli/repl/core/session.d.ts +124 -0
- package/dist/cli/repl/core/session.d.ts.map +1 -0
- package/dist/cli/repl/core/session.js +196 -0
- package/dist/cli/repl/core/session.js.map +1 -0
- package/dist/cli/repl/index.d.ts +56 -0
- package/dist/cli/repl/index.d.ts.map +1 -0
- package/dist/cli/repl/index.js +468 -0
- package/dist/cli/repl/index.js.map +1 -0
- package/dist/cli/repl/init/wizard.d.ts +61 -0
- package/dist/cli/repl/init/wizard.d.ts.map +1 -0
- package/dist/cli/repl/init/wizard.js +245 -0
- package/dist/cli/repl/init/wizard.js.map +1 -0
- package/dist/cli/repl/tui/theme.d.ts +109 -0
- package/dist/cli/repl/tui/theme.d.ts.map +1 -0
- package/dist/cli/repl/tui/theme.js +225 -0
- package/dist/cli/repl/tui/theme.js.map +1 -0
- package/dist/cli/repl/update/checker.d.ts +64 -0
- package/dist/cli/repl/update/checker.d.ts.map +1 -0
- package/dist/cli/repl/update/checker.js +166 -0
- package/dist/cli/repl/update/checker.js.map +1 -0
- package/dist/db/client.d.ts +1 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +2 -1
- package/dist/db/client.js.map +1 -1
- package/package.json +4 -1
- package/src/cli/index.ts +28 -2
- package/src/cli/repl/core/parser.ts +373 -0
- package/src/cli/repl/core/session.ts +269 -0
- package/src/cli/repl/index.ts +554 -0
- package/src/cli/repl/init/wizard.ts +300 -0
- package/src/cli/repl/tui/theme.ts +276 -0
- package/src/cli/repl/update/checker.ts +209 -0
- package/src/db/client.ts +9 -7
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* K0ntext REPL Shell
|
|
3
|
+
*
|
|
4
|
+
* Interactive shell for managing k0ntext context
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import readline from 'readline';
|
|
8
|
+
import { REPLSessionManager, ProjectType } from './core/session.js';
|
|
9
|
+
import { REPLCommandParser } from './core/parser.js';
|
|
10
|
+
import { InitWizard } from './init/wizard.js';
|
|
11
|
+
import { UpdateChecker } from './update/checker.js';
|
|
12
|
+
import { K0NTEXT_THEME, terminal } from './tui/theme.js';
|
|
13
|
+
import { createIntelligentAnalyzer } from '../../analyzer/intelligent-analyzer.js';
|
|
14
|
+
import { DatabaseClient } from '../../db/client.js';
|
|
15
|
+
import { hasOpenRouterKey } from '../../embeddings/openrouter.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* REPL options
|
|
19
|
+
*/
|
|
20
|
+
export interface REPLOptions {
|
|
21
|
+
projectRoot: string;
|
|
22
|
+
version: string;
|
|
23
|
+
noTUI?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* K0ntext REPL Shell
|
|
28
|
+
*/
|
|
29
|
+
export class REPLShell {
|
|
30
|
+
private session: REPLSessionManager;
|
|
31
|
+
private parser: REPLCommandParser;
|
|
32
|
+
private updateChecker: UpdateChecker;
|
|
33
|
+
private projectRoot: string;
|
|
34
|
+
private version: string;
|
|
35
|
+
private readline: readline.Interface;
|
|
36
|
+
private isActive: boolean = false;
|
|
37
|
+
private noTUI: boolean;
|
|
38
|
+
|
|
39
|
+
constructor(options: REPLOptions) {
|
|
40
|
+
this.projectRoot = options.projectRoot;
|
|
41
|
+
this.version = options.version;
|
|
42
|
+
this.noTUI = options.noTUI || false;
|
|
43
|
+
|
|
44
|
+
this.session = new REPLSessionManager(this.projectRoot);
|
|
45
|
+
this.parser = new REPLCommandParser();
|
|
46
|
+
this.updateChecker = new UpdateChecker(options.version);
|
|
47
|
+
|
|
48
|
+
// Create readline interface
|
|
49
|
+
this.readline = readline.createInterface({
|
|
50
|
+
input: process.stdin,
|
|
51
|
+
output: process.stdout,
|
|
52
|
+
prompt: this.getPrompt()
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
this.setupEventHandlers();
|
|
56
|
+
this.registerCommands();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get the command prompt
|
|
61
|
+
*/
|
|
62
|
+
private getPrompt(): string {
|
|
63
|
+
const isInit = this.session.isInitialized();
|
|
64
|
+
const symbol = isInit ? '█' : '?';
|
|
65
|
+
return K0NTEXT_THEME.primary(`k0ntext${symbol} `);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Setup event handlers
|
|
70
|
+
*/
|
|
71
|
+
private setupEventHandlers(): void {
|
|
72
|
+
this.readline.on('line', async (input) => {
|
|
73
|
+
if (!this.isActive) return;
|
|
74
|
+
|
|
75
|
+
const trimmed = input.trim();
|
|
76
|
+
|
|
77
|
+
if (!trimmed) {
|
|
78
|
+
this.readline.prompt();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Handle exit commands
|
|
83
|
+
if (trimmed.toLowerCase() === 'exit' || trimmed.toLowerCase() === 'quit') {
|
|
84
|
+
await this.stop();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Parse and execute command
|
|
89
|
+
const parsed = this.parser.parse(trimmed);
|
|
90
|
+
if (parsed) {
|
|
91
|
+
const startTime = Date.now();
|
|
92
|
+
const result = await this.parser.execute(parsed);
|
|
93
|
+
const duration = Date.now() - startTime;
|
|
94
|
+
|
|
95
|
+
this.session.addCommand(trimmed, result.output, duration);
|
|
96
|
+
|
|
97
|
+
if (result.output) {
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log(result.output);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (result.error) {
|
|
103
|
+
console.log('');
|
|
104
|
+
console.log(K0NTEXT_THEME.error(`✖ ${result.error}`));
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
console.log(K0NTEXT_THEME.warning('\n⚠ Invalid command. Type "help" for available commands.'));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
this.readline.prompt();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this.readline.on('SIGINT', async () => {
|
|
114
|
+
console.log('');
|
|
115
|
+
console.log(K0NTEXT_THEME.warning('\n⚠ Use "exit" to quit the REPL.'));
|
|
116
|
+
this.readline.prompt();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
this.readline.on('close', async () => {
|
|
120
|
+
await this.stop();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Register REPL-specific commands
|
|
126
|
+
*/
|
|
127
|
+
private registerCommands(): void {
|
|
128
|
+
// Stats command
|
|
129
|
+
this.parser.registerCommand({
|
|
130
|
+
name: 'stats',
|
|
131
|
+
description: 'Show database and session statistics',
|
|
132
|
+
usage: 'stats',
|
|
133
|
+
examples: ['stats'],
|
|
134
|
+
handler: async () => {
|
|
135
|
+
const db = new DatabaseClient(this.projectRoot);
|
|
136
|
+
const dbStats = db.getStats();
|
|
137
|
+
const sessionStats = this.session.getStats();
|
|
138
|
+
const duration = this.session.getDuration();
|
|
139
|
+
|
|
140
|
+
const output = [
|
|
141
|
+
'',
|
|
142
|
+
K0NTEXT_THEME.header('━━━ Database Statistics ━━━'),
|
|
143
|
+
` ${K0NTEXT_THEME.cyan('•')} Context Items: ${dbStats.items}`,
|
|
144
|
+
` ${K0NTEXT_THEME.cyan('•')} Relations: ${dbStats.relations}`,
|
|
145
|
+
` ${K0NTEXT_THEME.cyan('•')} Git Commits: ${dbStats.commits}`,
|
|
146
|
+
` ${K0NTEXT_THEME.cyan('•')} Embeddings: ${dbStats.embeddings}`,
|
|
147
|
+
` ${K0NTEXT_THEME.cyan('•')} Tool Configs: ${dbStats.toolConfigs}`,
|
|
148
|
+
` ${K0NTEXT_THEME.cyan('•')} Database Path: ${dbStats.path || '.k0ntext.db'}`,
|
|
149
|
+
'',
|
|
150
|
+
K0NTEXT_THEME.header('━━━ Session Statistics ━━━'),
|
|
151
|
+
` ${K0NTEXT_THEME.cyan('•')} Duration: ${duration.human}`,
|
|
152
|
+
` ${K0NTEXT_THEME.cyan('•')} Commands Run: ${sessionStats.commandsExecuted}`,
|
|
153
|
+
` ${K0NTEXT_THEME.cyan('•')} Searches: ${sessionStats.searchesPerformed}`,
|
|
154
|
+
` ${K0NTEXT_THEME.cyan('•')} Files Indexed: ${sessionStats.filesIndexed}`,
|
|
155
|
+
` ${K0NTEXT_THEME.cyan('•')} Embeddings: ${sessionStats.embeddingsGenerated}`,
|
|
156
|
+
''
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
db.close();
|
|
160
|
+
|
|
161
|
+
return { success: true, output: output.join('\n') };
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Index command
|
|
166
|
+
this.parser.registerCommand({
|
|
167
|
+
name: 'index',
|
|
168
|
+
description: 'Index codebase into database',
|
|
169
|
+
usage: 'index [options]',
|
|
170
|
+
examples: ['index', 'index --code', 'index --all'],
|
|
171
|
+
handler: async (args, flags) => {
|
|
172
|
+
const analyzer = createIntelligentAnalyzer(this.projectRoot);
|
|
173
|
+
const db = new DatabaseClient(this.projectRoot);
|
|
174
|
+
|
|
175
|
+
let indexedCount = 0;
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
const [docs, code, tools] = await Promise.all([
|
|
179
|
+
analyzer.discoverDocs(),
|
|
180
|
+
analyzer.discoverCode(),
|
|
181
|
+
analyzer.discoverToolConfigs()
|
|
182
|
+
]);
|
|
183
|
+
|
|
184
|
+
const docsCount = docs.length;
|
|
185
|
+
const codeCount = Math.min(code.length, 500); // Limit for now
|
|
186
|
+
const toolsCount = tools.length;
|
|
187
|
+
|
|
188
|
+
// Index docs
|
|
189
|
+
for (const doc of docs) {
|
|
190
|
+
const content = require('fs').readFileSync(doc.path, 'utf-8').slice(0, 50000);
|
|
191
|
+
db.upsertItem({
|
|
192
|
+
type: 'doc',
|
|
193
|
+
name: require('path').basename(doc.relativePath),
|
|
194
|
+
content,
|
|
195
|
+
filePath: doc.relativePath,
|
|
196
|
+
metadata: { size: doc.size }
|
|
197
|
+
});
|
|
198
|
+
indexedCount++;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Index code
|
|
202
|
+
for (const codeFile of code.slice(0, 500)) {
|
|
203
|
+
const content = require('fs').existsSync(codeFile.path)
|
|
204
|
+
? require('fs').readFileSync(codeFile.path, 'utf-8').slice(0, 20000)
|
|
205
|
+
: '';
|
|
206
|
+
if (content) {
|
|
207
|
+
db.upsertItem({
|
|
208
|
+
type: 'code',
|
|
209
|
+
name: require('path').basename(codeFile.relativePath),
|
|
210
|
+
content,
|
|
211
|
+
filePath: codeFile.relativePath,
|
|
212
|
+
metadata: { size: codeFile.size }
|
|
213
|
+
});
|
|
214
|
+
indexedCount++;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Index tools
|
|
219
|
+
for (const tool of tools) {
|
|
220
|
+
const content = require('fs').existsSync(tool.path)
|
|
221
|
+
? require('fs').readFileSync(tool.path, 'utf-8').slice(0, 50000)
|
|
222
|
+
: '';
|
|
223
|
+
if (content) {
|
|
224
|
+
db.upsertItem({
|
|
225
|
+
type: 'tool_config',
|
|
226
|
+
name: `${tool.tool}:${require('path').basename(tool.relativePath)}`,
|
|
227
|
+
content,
|
|
228
|
+
filePath: tool.relativePath,
|
|
229
|
+
metadata: { tool: tool.tool, size: tool.size }
|
|
230
|
+
});
|
|
231
|
+
indexedCount++;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
this.session.updateStats({
|
|
236
|
+
filesIndexed: this.session.getStats().filesIndexed + indexedCount
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const output = [
|
|
240
|
+
'',
|
|
241
|
+
K0NTEXT_THEME.success('✓ Indexing complete'),
|
|
242
|
+
` ${K0NTEXT_THEME.cyan('•')} Documents: ${docsCount}`,
|
|
243
|
+
` ${K0NTEXT_THEME.cyan('•')} Code Files: ${codeCount}`,
|
|
244
|
+
` ${K0NTEXT_THEME.cyan('•')} Tool Configs: ${toolsCount}`,
|
|
245
|
+
` ${K0NTEXT_THEME.cyan('•')} Total Indexed: ${indexedCount}`,
|
|
246
|
+
''
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
db.close();
|
|
250
|
+
|
|
251
|
+
return { success: true, output: output.join('\n') };
|
|
252
|
+
} catch (error) {
|
|
253
|
+
db.close();
|
|
254
|
+
return {
|
|
255
|
+
success: false,
|
|
256
|
+
error: error instanceof Error ? error.message : String(error)
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Search command
|
|
263
|
+
this.parser.registerCommand({
|
|
264
|
+
name: 'search',
|
|
265
|
+
description: 'Search indexed content',
|
|
266
|
+
usage: 'search <query>',
|
|
267
|
+
examples: ['search auth', 'search "user login"'],
|
|
268
|
+
completions: () => [],
|
|
269
|
+
handler: async (args) => {
|
|
270
|
+
const query = args.join(' ');
|
|
271
|
+
if (!query) {
|
|
272
|
+
return {
|
|
273
|
+
success: false,
|
|
274
|
+
error: 'Please provide a search query'
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const db = new DatabaseClient(this.projectRoot);
|
|
279
|
+
const results = db.searchText(query);
|
|
280
|
+
|
|
281
|
+
this.session.updateStats({
|
|
282
|
+
searchesPerformed: this.session.getStats().searchesPerformed + 1
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
if (results.length === 0) {
|
|
286
|
+
db.close();
|
|
287
|
+
return {
|
|
288
|
+
success: true,
|
|
289
|
+
output: K0NTEXT_THEME.dim('\nNo results found.')
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const output = [
|
|
294
|
+
'',
|
|
295
|
+
K0NTEXT_THEME.header(`━━━ Search Results: "${query}" ━━━`),
|
|
296
|
+
''
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
for (let i = 0; i < Math.min(results.length, 10); i++) {
|
|
300
|
+
const result = results[i];
|
|
301
|
+
output.push(` ${K0NTEXT_THEME.primary(`${i + 1}.`)} ${result.name} [${result.type}]`);
|
|
302
|
+
if (result.filePath) {
|
|
303
|
+
output.push(` ${K0NTEXT_THEME.dim(result.filePath)}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (results.length > 10) {
|
|
308
|
+
output.push(` ${K0NTEXT_THEME.dim(`... and ${results.length - 10} more`)}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
output.push('');
|
|
312
|
+
|
|
313
|
+
db.close();
|
|
314
|
+
|
|
315
|
+
return { success: true, output: output.join('\n') };
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Init command (re-run wizard)
|
|
320
|
+
this.parser.registerCommand({
|
|
321
|
+
name: 'init',
|
|
322
|
+
description: 'Re-run initialization wizard',
|
|
323
|
+
usage: 'init',
|
|
324
|
+
examples: ['init'],
|
|
325
|
+
handler: async () => {
|
|
326
|
+
const wizard = new InitWizard(this.projectRoot);
|
|
327
|
+
const config = await wizard.run();
|
|
328
|
+
|
|
329
|
+
if (config) {
|
|
330
|
+
this.session.setInitialized(config.apiKey, config.projectType);
|
|
331
|
+
this.session.updateConfig({
|
|
332
|
+
apiKey: config.apiKey,
|
|
333
|
+
projectType: config.projectType,
|
|
334
|
+
aiTools: config.aiTools,
|
|
335
|
+
features: config.features
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
wizard.showSuccess(config);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return { success: true };
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Update command
|
|
346
|
+
this.parser.registerCommand({
|
|
347
|
+
name: 'update',
|
|
348
|
+
description: 'Check for k0ntext updates',
|
|
349
|
+
usage: 'update',
|
|
350
|
+
examples: ['update'],
|
|
351
|
+
handler: async () => {
|
|
352
|
+
const hasUpdate = await this.updateChecker.checkAndPrompt();
|
|
353
|
+
return {
|
|
354
|
+
success: true,
|
|
355
|
+
output: hasUpdate ? '' : K0NTEXT_THEME.success('\n✓ Already on latest version')
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Drift command
|
|
361
|
+
this.parser.registerCommand({
|
|
362
|
+
name: 'drift',
|
|
363
|
+
description: 'Check for documentation drift',
|
|
364
|
+
usage: 'drift',
|
|
365
|
+
examples: ['drift'],
|
|
366
|
+
handler: async () => {
|
|
367
|
+
const db = new DatabaseClient(this.projectRoot);
|
|
368
|
+
const items = db.getAllItems();
|
|
369
|
+
|
|
370
|
+
const now = new Date();
|
|
371
|
+
const driftDays = 7;
|
|
372
|
+
const driftThreshold = new Date(now.getTime() - driftDays * 24 * 60 * 60 * 1000);
|
|
373
|
+
|
|
374
|
+
const drifted = items.filter(item => {
|
|
375
|
+
if (!item.updatedAt) return false;
|
|
376
|
+
const updated = new Date(item.updatedAt);
|
|
377
|
+
return updated < driftThreshold;
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
db.close();
|
|
381
|
+
|
|
382
|
+
const output = [
|
|
383
|
+
'',
|
|
384
|
+
K0NTEXT_THEME.header('━━━ Documentation Drift Check ━━━'),
|
|
385
|
+
''
|
|
386
|
+
];
|
|
387
|
+
|
|
388
|
+
if (drifted.length === 0) {
|
|
389
|
+
output.push(K0NTEXT_THEME.success('✓ All context files are up to date'));
|
|
390
|
+
} else {
|
|
391
|
+
output.push(K0NTEXT_THEME.warning(`⚠ Found ${drifted.length} files that may be out of sync:`));
|
|
392
|
+
output.push('');
|
|
393
|
+
|
|
394
|
+
for (const item of drifted.slice(0, 10)) {
|
|
395
|
+
output.push(` ${K0NTEXT_THEME.primary('•')} ${item.name} ${K0NTEXT_THEME.dim(`(${item.updatedAt})`)}`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (drifted.length > 10) {
|
|
399
|
+
output.push(` ${K0NTEXT_THEME.dim(`... and ${drifted.length - 10} more`)}`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
output.push('');
|
|
403
|
+
output.push(K0NTEXT_THEME.info('Run "index" to update your context.'));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
output.push('');
|
|
407
|
+
|
|
408
|
+
return { success: true, output: output.join('\n') };
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Config command
|
|
413
|
+
this.parser.registerCommand({
|
|
414
|
+
name: 'config',
|
|
415
|
+
description: 'View or set configuration',
|
|
416
|
+
usage: 'config [get|set|list] [key] [value]',
|
|
417
|
+
examples: ['config list', 'config get projectType', 'config set projectType webapp'],
|
|
418
|
+
handler: async (args) => {
|
|
419
|
+
const action = args[0] || 'list';
|
|
420
|
+
const state = this.session.getState();
|
|
421
|
+
|
|
422
|
+
if (action === 'list') {
|
|
423
|
+
const output = [
|
|
424
|
+
'',
|
|
425
|
+
K0NTEXT_THEME.header('━━━ Configuration ━━━'),
|
|
426
|
+
'',
|
|
427
|
+
` ${K0NTEXT_THEME.cyan('projectType:')} ${state.config.projectType || 'not set'}`,
|
|
428
|
+
` ${K0NTEXT_THEME.cyan('apiKey:')} ${state.config.apiKey ? '✓ set' : '○ not set'}`,
|
|
429
|
+
` ${K0NTEXT_THEME.cyan('aiTools:')} ${state.config.aiTools.join(', ') || 'none'}`,
|
|
430
|
+
` ${K0NTEXT_THEME.cyan('features:')} ${state.config.features.join(', ') || 'none'}`,
|
|
431
|
+
` ${K0NTEXT_THEME.cyan('autoUpdate:')} ${state.config.autoUpdate}`,
|
|
432
|
+
''
|
|
433
|
+
];
|
|
434
|
+
return { success: true, output: output.join('\n') };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (action === 'get') {
|
|
438
|
+
const key = args[1];
|
|
439
|
+
if (!key) return { success: false, error: 'Please specify a key' };
|
|
440
|
+
const value = (state.config as any)[key];
|
|
441
|
+
return { success: true, output: `${key}: ${value || 'not set'}` };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (action === 'set') {
|
|
445
|
+
const key = args[1];
|
|
446
|
+
const value = args.slice(2).join(' ');
|
|
447
|
+
if (!key || !value) return { success: false, error: 'Usage: config set <key> <value>' };
|
|
448
|
+
|
|
449
|
+
this.session.updateConfig({ [key]: value });
|
|
450
|
+
return { success: true, output: K0NTEXT_THEME.success(`✓ Set ${key} = ${value}`) };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return { success: false, error: 'Unknown action. Use: get, set, or list' };
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Start the REPL
|
|
460
|
+
*/
|
|
461
|
+
async start(): Promise<void> {
|
|
462
|
+
this.isActive = true;
|
|
463
|
+
|
|
464
|
+
// Show banner
|
|
465
|
+
this.showBanner();
|
|
466
|
+
|
|
467
|
+
// Check for updates
|
|
468
|
+
if (this.session.getState().config.autoUpdate) {
|
|
469
|
+
await this.updateChecker.showNotification({ showIfCurrent: false, checkInterval: 24 * 60 * 60 * 1000 });
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Check if initialized
|
|
473
|
+
if (!this.session.isInitialized()) {
|
|
474
|
+
console.log('');
|
|
475
|
+
console.log(K0NTEXT_THEME.info('🔧 First-time setup detected. Running initialization wizard...\n'));
|
|
476
|
+
|
|
477
|
+
const wizard = new InitWizard(this.projectRoot);
|
|
478
|
+
const config = await wizard.run();
|
|
479
|
+
|
|
480
|
+
if (config) {
|
|
481
|
+
this.session.setInitialized(config.apiKey, config.projectType);
|
|
482
|
+
this.session.updateConfig({
|
|
483
|
+
apiKey: config.apiKey,
|
|
484
|
+
projectType: config.projectType,
|
|
485
|
+
aiTools: config.aiTools,
|
|
486
|
+
features: config.features
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
wizard.showSuccess(config);
|
|
490
|
+
} else {
|
|
491
|
+
console.log(K0NTEXT_THEME.info('\n✓ Skipping initialization. You can run "init" later.\n'));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Show project stats if initialized
|
|
496
|
+
if (this.session.isInitialized()) {
|
|
497
|
+
const analyzer = createIntelligentAnalyzer(this.projectRoot);
|
|
498
|
+
const analysis = await analyzer.analyze();
|
|
499
|
+
|
|
500
|
+
console.log('');
|
|
501
|
+
console.log(K0NTEXT_THEME.header('━━━ Project Overview ━━━'));
|
|
502
|
+
console.log(` ${K0NTEXT_THEME.primary('Type:')} ${this.session.getState().config.projectType}`);
|
|
503
|
+
console.log(` ${K0NTEXT_THEME.primary('Docs:')} ${analysis.existingContext.files.filter(f => f.type === 'doc').length}`);
|
|
504
|
+
console.log(` ${K0NTEXT_THEME.primary('Code:')} ${analysis.existingContext.files.filter(f => f.type === 'code').length}`);
|
|
505
|
+
console.log(` ${K0NTEXT_THEME.primary('Tech:')} ${analysis.techStack.languages.slice(0, 3).join(', ')}`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
console.log('');
|
|
509
|
+
console.log(K0NTEXT_THEME.dim('Type "help" for available commands, "exit" to quit.'));
|
|
510
|
+
|
|
511
|
+
// Start readline
|
|
512
|
+
this.readline.prompt();
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Stop the REPL
|
|
517
|
+
*/
|
|
518
|
+
async stop(): Promise<void> {
|
|
519
|
+
this.isActive = false;
|
|
520
|
+
this.session.end();
|
|
521
|
+
this.readline.close();
|
|
522
|
+
console.log('');
|
|
523
|
+
console.log(K0NTEXT_THEME.success('✓ Session saved. Goodbye!'));
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Show welcome banner
|
|
528
|
+
*/
|
|
529
|
+
private showBanner(): void {
|
|
530
|
+
const { supportsColor } = K0NTEXT_THEME.detectCapabilities();
|
|
531
|
+
|
|
532
|
+
if (!supportsColor) {
|
|
533
|
+
console.log(`\n K0ntext v${this.version}\n`);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
console.log('');
|
|
538
|
+
console.log(K0NTEXT_THEME.box(
|
|
539
|
+
`K0NTEXT v${this.version}`,
|
|
540
|
+
`${K0NTEXT_THEME.dim('Interactive shell for AI context engineering')}
|
|
541
|
+
${K0NTEXT_THEME.dim('Type "help" for commands, "exit" to quit')}`,
|
|
542
|
+
'primary'
|
|
543
|
+
));
|
|
544
|
+
console.log('');
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* Create and start a REPL shell
|
|
550
|
+
*/
|
|
551
|
+
export async function startREPL(projectRoot: string = process.cwd(), version: string, noTUI = false): Promise<void> {
|
|
552
|
+
const shell = new REPLShell({ projectRoot, version, noTUI });
|
|
553
|
+
await shell.start();
|
|
554
|
+
}
|