vigthoria-cli 1.0.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.
Files changed (75) hide show
  1. package/README.md +413 -0
  2. package/dist/commands/auth.d.ts +24 -0
  3. package/dist/commands/auth.d.ts.map +1 -0
  4. package/dist/commands/auth.js +194 -0
  5. package/dist/commands/auth.js.map +1 -0
  6. package/dist/commands/chat.d.ts +64 -0
  7. package/dist/commands/chat.d.ts.map +1 -0
  8. package/dist/commands/chat.js +596 -0
  9. package/dist/commands/chat.js.map +1 -0
  10. package/dist/commands/config.d.ts +25 -0
  11. package/dist/commands/config.d.ts.map +1 -0
  12. package/dist/commands/config.js +291 -0
  13. package/dist/commands/config.js.map +1 -0
  14. package/dist/commands/edit.d.ts +28 -0
  15. package/dist/commands/edit.d.ts.map +1 -0
  16. package/dist/commands/edit.js +257 -0
  17. package/dist/commands/edit.js.map +1 -0
  18. package/dist/commands/explain.d.ts +21 -0
  19. package/dist/commands/explain.d.ts.map +1 -0
  20. package/dist/commands/explain.js +98 -0
  21. package/dist/commands/explain.js.map +1 -0
  22. package/dist/commands/generate.d.ts +25 -0
  23. package/dist/commands/generate.d.ts.map +1 -0
  24. package/dist/commands/generate.js +155 -0
  25. package/dist/commands/generate.js.map +1 -0
  26. package/dist/commands/review.d.ts +24 -0
  27. package/dist/commands/review.d.ts.map +1 -0
  28. package/dist/commands/review.js +153 -0
  29. package/dist/commands/review.js.map +1 -0
  30. package/dist/index.d.ts +16 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +205 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/utils/api.d.ts +88 -0
  35. package/dist/utils/api.d.ts.map +1 -0
  36. package/dist/utils/api.js +431 -0
  37. package/dist/utils/api.js.map +1 -0
  38. package/dist/utils/config.d.ts +57 -0
  39. package/dist/utils/config.d.ts.map +1 -0
  40. package/dist/utils/config.js +167 -0
  41. package/dist/utils/config.js.map +1 -0
  42. package/dist/utils/files.d.ts +31 -0
  43. package/dist/utils/files.d.ts.map +1 -0
  44. package/dist/utils/files.js +217 -0
  45. package/dist/utils/files.js.map +1 -0
  46. package/dist/utils/logger.d.ts +23 -0
  47. package/dist/utils/logger.d.ts.map +1 -0
  48. package/dist/utils/logger.js +104 -0
  49. package/dist/utils/logger.js.map +1 -0
  50. package/dist/utils/session.d.ts +61 -0
  51. package/dist/utils/session.d.ts.map +1 -0
  52. package/dist/utils/session.js +172 -0
  53. package/dist/utils/session.js.map +1 -0
  54. package/dist/utils/tools.d.ts +145 -0
  55. package/dist/utils/tools.d.ts.map +1 -0
  56. package/dist/utils/tools.js +781 -0
  57. package/dist/utils/tools.js.map +1 -0
  58. package/install.sh +248 -0
  59. package/package.json +52 -0
  60. package/src/commands/auth.ts +225 -0
  61. package/src/commands/chat.ts +690 -0
  62. package/src/commands/config.ts +297 -0
  63. package/src/commands/edit.ts +310 -0
  64. package/src/commands/explain.ts +115 -0
  65. package/src/commands/generate.ts +177 -0
  66. package/src/commands/review.ts +186 -0
  67. package/src/index.ts +221 -0
  68. package/src/types/marked-terminal.d.ts +31 -0
  69. package/src/utils/api.ts +531 -0
  70. package/src/utils/config.ts +224 -0
  71. package/src/utils/files.ts +212 -0
  72. package/src/utils/logger.ts +125 -0
  73. package/src/utils/session.ts +167 -0
  74. package/src/utils/tools.ts +933 -0
  75. package/tsconfig.json +20 -0
@@ -0,0 +1,690 @@
1
+ /**
2
+ * Interactive Chat Command for Vigthoria CLI
3
+ *
4
+ * Now with Claude Code-like agentic capabilities!
5
+ */
6
+
7
+ import chalk from 'chalk';
8
+ import ora from 'ora';
9
+ import * as readline from 'readline';
10
+ import { Marked } from 'marked';
11
+ import { markedTerminal } from 'marked-terminal';
12
+ import { Config } from '../utils/config.js';
13
+ import { Logger } from '../utils/logger.js';
14
+ import { APIClient, ChatMessage } from '../utils/api.js';
15
+ import { FileUtils } from '../utils/files.js';
16
+ import { AgenticTools, ToolCall, ToolResult } from '../utils/tools.js';
17
+ import { SessionManager, Session } from '../utils/session.js';
18
+
19
+ interface ChatOptions {
20
+ model: string;
21
+ project: string;
22
+ agent?: boolean;
23
+ autoApprove?: boolean;
24
+ resume?: boolean;
25
+ stream?: boolean;
26
+ local?: boolean; // Skip authentication for local testing
27
+ }
28
+
29
+ export class ChatCommand {
30
+ private config: Config;
31
+ private logger: Logger;
32
+ private api: APIClient;
33
+ private messages: ChatMessage[] = [];
34
+ private fileUtils: FileUtils;
35
+ private marked: Marked;
36
+ private tools: AgenticTools | null = null;
37
+ private agentMode: boolean = false;
38
+ private rl: readline.Interface | null = null;
39
+ private sessionManager: SessionManager;
40
+ private currentSession: Session | null = null;
41
+ private streamMode: boolean = true;
42
+ private localMode: boolean = false;
43
+
44
+ constructor(config: Config, logger: Logger) {
45
+ this.config = config;
46
+ this.logger = logger;
47
+ this.api = new APIClient(config, logger);
48
+ this.fileUtils = new FileUtils(process.cwd(), config.get('project').ignorePatterns);
49
+ this.sessionManager = new SessionManager();
50
+
51
+ // Setup marked for terminal rendering
52
+ this.marked = new Marked();
53
+ this.marked.use(markedTerminal() as any);
54
+ }
55
+
56
+ async run(options: ChatOptions): Promise<void> {
57
+ // Check authentication (skip for local testing mode)
58
+ if (!options.local && !this.config.isAuthenticated()) {
59
+ this.logger.error('Not authenticated. Run: vigthoria login');
60
+ this.logger.info('Tip: Use --local flag to test with local Ollama models');
61
+ return;
62
+ }
63
+
64
+ if (!options.local && !this.config.hasValidSubscription()) {
65
+ this.logger.warn('Your subscription has expired. Some features may be limited.');
66
+ }
67
+
68
+ if (options.local) {
69
+ this.logger.info('Local mode: Using Ollama for AI responses');
70
+ }
71
+
72
+ // Setup agentic tools if enabled
73
+ this.agentMode = options.agent || false;
74
+ this.streamMode = options.stream !== false; // Default to true
75
+ this.localMode = options.local || false;
76
+
77
+ if (this.agentMode) {
78
+ this.tools = new AgenticTools(
79
+ this.logger,
80
+ options.project,
81
+ this.askPermission.bind(this),
82
+ options.autoApprove || false
83
+ );
84
+ }
85
+
86
+ // Try to resume previous session or create new one
87
+ if (options.resume) {
88
+ this.currentSession = this.sessionManager.getLatest(options.project);
89
+ if (this.currentSession) {
90
+ this.messages = [...this.currentSession.messages];
91
+ this.logger.success(`Resumed session: ${this.currentSession.id}`);
92
+ }
93
+ }
94
+
95
+ if (!this.currentSession) {
96
+ this.currentSession = this.sessionManager.create(
97
+ options.project,
98
+ options.model,
99
+ this.agentMode
100
+ );
101
+ }
102
+
103
+ // Get project context
104
+ const projectContext = await this.fileUtils.getProjectContext();
105
+
106
+ // Setup system message
107
+ const systemMessage: ChatMessage = {
108
+ role: 'system',
109
+ content: this.buildSystemPrompt(projectContext, options),
110
+ };
111
+ this.messages.push(systemMessage);
112
+
113
+ this.printWelcome(options);
114
+
115
+ // Start REPL
116
+ await this.startRepl(options);
117
+ }
118
+
119
+ private buildSystemPrompt(
120
+ projectContext: { type: string; files: string[]; dependencies: Record<string, string> },
121
+ options: ChatOptions
122
+ ): string {
123
+ let prompt = `You are Vigthoria, a premium AI coding assistant. You help developers write, understand, and improve code.
124
+
125
+ Project Context:
126
+ - Type: ${projectContext.type}
127
+ - Root: ${options.project}
128
+ - Key files: ${projectContext.files.slice(0, 10).join(', ')}
129
+ ${projectContext.type === 'node' ? `- Dependencies: ${Object.keys(projectContext.dependencies).slice(0, 15).join(', ')}` : ''}
130
+
131
+ Guidelines:
132
+ - Provide complete, production-ready code
133
+ - Use modern best practices (2024-2026 standards)
134
+ - Include proper error handling
135
+ - Explain your reasoning when helpful
136
+ - Be concise but thorough
137
+
138
+ Special Commands (user may use these):
139
+ - /file <path> - Read and include a file in context
140
+ - /edit <path> - Switch to file editing mode
141
+ - /diff - Show pending changes
142
+ - /apply - Apply pending changes
143
+ - /clear - Clear conversation history
144
+ - /model <name> - Switch AI model
145
+ - /agent - Toggle agentic mode (Claude Code-like autonomous actions)
146
+ - /help - Show available commands`;
147
+
148
+ // Add tool instructions if in agent mode
149
+ if (this.agentMode && this.tools) {
150
+ prompt += `\n\n${AgenticTools.getToolsForPrompt()}`;
151
+ }
152
+
153
+ return prompt;
154
+ }
155
+
156
+ private printWelcome(options: ChatOptions): void {
157
+ const sub = this.config.get('subscription');
158
+
159
+ console.log();
160
+ this.logger.box(
161
+ `Model: ${chalk.cyan(options.model)}\n` +
162
+ `Plan: ${chalk.green(sub.plan || 'free')}\n` +
163
+ `Project: ${chalk.gray(options.project)}\n` +
164
+ `Agent Mode: ${this.agentMode ? chalk.green('ON ✓') : chalk.gray('OFF')}`,
165
+ 'Vigthoria Chat'
166
+ );
167
+ console.log();
168
+ if (this.agentMode) {
169
+ console.log(chalk.yellow('🤖 Agent Mode: AI can read files, edit code, and run commands autonomously.'));
170
+ }
171
+ console.log(chalk.gray('Type your message or /help for commands. Press Ctrl+C to exit.'));
172
+ console.log();
173
+ }
174
+
175
+ private async startRepl(options: ChatOptions): Promise<void> {
176
+ this.rl = readline.createInterface({
177
+ input: process.stdin,
178
+ output: process.stdout,
179
+ prompt: chalk.cyan('you › '),
180
+ });
181
+
182
+ let currentModel = options.model;
183
+ let pendingChanges: { file: string; content: string } | null = null;
184
+
185
+ this.rl.prompt();
186
+
187
+ for await (const line of this.rl) {
188
+ const input = line.trim();
189
+
190
+ if (!input) {
191
+ this.rl.prompt();
192
+ continue;
193
+ }
194
+
195
+ // Handle special commands
196
+ if (input.startsWith('/')) {
197
+ const [cmd, ...args] = input.slice(1).split(' ');
198
+
199
+ switch (cmd) {
200
+ case 'help':
201
+ this.printHelp();
202
+ break;
203
+
204
+ case 'clear':
205
+ this.messages = [this.messages[0]]; // Keep system message
206
+ this.logger.success('Conversation cleared');
207
+ break;
208
+
209
+ case 'model':
210
+ if (args[0]) {
211
+ currentModel = args[0];
212
+ this.logger.success(`Model switched to: ${currentModel}`);
213
+ } else {
214
+ this.printModels();
215
+ }
216
+ break;
217
+
218
+ case 'agent':
219
+ this.agentMode = !this.agentMode;
220
+ if (this.agentMode && !this.tools) {
221
+ this.tools = new AgenticTools(
222
+ this.logger,
223
+ options.project,
224
+ this.askPermission.bind(this),
225
+ options.autoApprove || false
226
+ );
227
+ }
228
+ // Rebuild system prompt with/without tools
229
+ this.messages[0] = {
230
+ role: 'system',
231
+ content: this.buildSystemPrompt(
232
+ await this.fileUtils.getProjectContext(),
233
+ { ...options, agent: this.agentMode }
234
+ ),
235
+ };
236
+ this.logger.success(`Agent mode: ${this.agentMode ? chalk.green('ON') : chalk.red('OFF')}`);
237
+ if (this.agentMode) {
238
+ console.log(chalk.yellow(' AI can now read files, edit code, and run commands.'));
239
+ }
240
+ break;
241
+
242
+ case 'approve':
243
+ if (this.tools) {
244
+ options.autoApprove = !options.autoApprove;
245
+ this.tools = new AgenticTools(
246
+ this.logger,
247
+ options.project,
248
+ this.askPermission.bind(this),
249
+ options.autoApprove
250
+ );
251
+ this.logger.success(`Auto-approve: ${options.autoApprove ? chalk.green('ON') : chalk.red('OFF')}`);
252
+ if (options.autoApprove) {
253
+ console.log(chalk.red(' ⚠️ AI actions will be executed without confirmation!'));
254
+ }
255
+ }
256
+ break;
257
+
258
+ case 'file':
259
+ if (args[0]) {
260
+ await this.addFileToContext(args[0]);
261
+ } else {
262
+ this.logger.error('Usage: /file <path>');
263
+ }
264
+ break;
265
+
266
+ case 'edit':
267
+ if (args[0]) {
268
+ pendingChanges = await this.startEditMode(args[0], currentModel);
269
+ } else {
270
+ this.logger.error('Usage: /edit <path>');
271
+ }
272
+ break;
273
+
274
+ case 'diff':
275
+ if (pendingChanges) {
276
+ this.showPendingDiff(pendingChanges);
277
+ } else {
278
+ this.logger.info('No pending changes');
279
+ }
280
+ break;
281
+
282
+ case 'apply':
283
+ if (pendingChanges) {
284
+ this.applyChanges(pendingChanges);
285
+ pendingChanges = null;
286
+ } else {
287
+ this.logger.info('No pending changes to apply');
288
+ }
289
+ break;
290
+
291
+ case 'sessions':
292
+ this.listSessions();
293
+ break;
294
+
295
+ case 'history':
296
+ this.showHistory();
297
+ break;
298
+
299
+ case 'save':
300
+ if (this.currentSession) {
301
+ this.sessionManager.save(this.currentSession);
302
+ this.logger.success(`Session saved: ${this.currentSession.id}`);
303
+ }
304
+ break;
305
+
306
+ case 'new':
307
+ // Start new session
308
+ this.currentSession = this.sessionManager.create(
309
+ options.project,
310
+ currentModel,
311
+ this.agentMode
312
+ );
313
+ this.messages = [this.messages[0]]; // Keep only system message
314
+ this.logger.success(`New session: ${this.currentSession.id}`);
315
+ break;
316
+
317
+ case 'compact':
318
+ // Compact context by summarizing older messages
319
+ await this.compactContext(currentModel);
320
+ break;
321
+
322
+ case 'undo':
323
+ // Undo last file operation
324
+ if (this.tools) {
325
+ const undoResult = await this.tools.undo();
326
+ if (undoResult.success) {
327
+ this.logger.success(undoResult.output || 'Undo completed');
328
+ if (undoResult.metadata?.remainingUndos !== undefined) {
329
+ console.log(chalk.gray(` ${undoResult.metadata.remainingUndos} more undo(s) available`));
330
+ }
331
+ } else {
332
+ this.logger.error(undoResult.error || 'Nothing to undo');
333
+ }
334
+ } else {
335
+ this.logger.info('Undo is only available in agent mode. Use /agent to enable.');
336
+ }
337
+ break;
338
+
339
+ case 'exit':
340
+ case 'quit':
341
+ // Auto-save session on exit
342
+ if (this.currentSession && this.messages.length > 1) {
343
+ this.sessionManager.save(this.currentSession);
344
+ console.log(chalk.gray(`Session saved: ${this.currentSession.id}`));
345
+ }
346
+ console.log(chalk.cyan('\nGoodbye! 👋\n'));
347
+ this.rl!.close();
348
+ return;
349
+
350
+ default:
351
+ this.logger.warn(`Unknown command: /${cmd}`);
352
+ }
353
+
354
+ this.rl!.prompt();
355
+ continue;
356
+ }
357
+
358
+ // Regular chat message
359
+ await this.chat(input, currentModel);
360
+
361
+ // Save message to session
362
+ if (this.currentSession) {
363
+ this.currentSession.messages = [...this.messages];
364
+ this.sessionManager.save(this.currentSession);
365
+ }
366
+
367
+ this.rl!.prompt();
368
+ }
369
+ }
370
+
371
+ private async chat(userInput: string, model: string): Promise<void> {
372
+ // Add user message (skip if empty - used for tool continuation)
373
+ if (userInput) {
374
+ this.messages.push({ role: 'user', content: userInput });
375
+ }
376
+
377
+ const spinner = ora({
378
+ text: chalk.gray('Thinking...'),
379
+ spinner: 'dots',
380
+ }).start();
381
+
382
+ try {
383
+ const response = await this.api.chat(this.messages, model, this.localMode);
384
+
385
+ spinner.stop();
386
+
387
+ // Add assistant message
388
+ this.messages.push({ role: 'assistant', content: response.message });
389
+
390
+ // Render markdown response
391
+ console.log();
392
+ console.log(chalk.cyan('vigthoria ›'));
393
+ console.log(this.marked.parse(response.message));
394
+
395
+ // Show token usage
396
+ if (response.usage) {
397
+ console.log(chalk.gray(`[${response.usage.total_tokens} tokens]`));
398
+ }
399
+ console.log();
400
+
401
+ // In agent mode, check for and execute tool calls
402
+ if (this.agentMode && this.tools) {
403
+ const toolCalls = AgenticTools.parseToolCalls(response.message);
404
+
405
+ if (toolCalls.length > 0) {
406
+ await this.executeToolCalls(toolCalls, model);
407
+ }
408
+ }
409
+
410
+ } catch (error) {
411
+ spinner.stop();
412
+ this.logger.error('Failed to get response:', (error as Error).message);
413
+ // Remove failed user message
414
+ this.messages.pop();
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Execute tool calls from AI response (Claude Code-like)
420
+ */
421
+ private async executeToolCalls(calls: ToolCall[], model: string): Promise<void> {
422
+ const results: { tool: string; result: ToolResult }[] = [];
423
+
424
+ for (const call of calls) {
425
+ console.log(chalk.yellow(`\n⚙ Executing: ${call.tool}`));
426
+
427
+ const result = await this.tools!.execute(call);
428
+ results.push({ tool: call.tool, result });
429
+
430
+ if (result.success) {
431
+ this.logger.success(`${call.tool}: Success`);
432
+ if (result.output) {
433
+ console.log(chalk.gray(this.truncateOutput(result.output)));
434
+ }
435
+ } else {
436
+ this.logger.error(`${call.tool}: ${result.error}`);
437
+ }
438
+ }
439
+
440
+ // Send tool results back to AI for continuation
441
+ const toolResultsMessage: ChatMessage = {
442
+ role: 'user',
443
+ content: this.formatToolResults(results),
444
+ };
445
+ this.messages.push(toolResultsMessage);
446
+
447
+ // Get AI's follow-up response
448
+ console.log();
449
+ await this.chat('', model);
450
+ }
451
+
452
+ /**
453
+ * Format tool results for AI context
454
+ */
455
+ private formatToolResults(
456
+ results: { tool: string; result: ToolResult }[]
457
+ ): string {
458
+ let msg = 'Tool execution results:\n\n';
459
+
460
+ for (const { tool, result } of results) {
461
+ msg += `## ${tool}\n`;
462
+ msg += `Status: ${result.success ? 'Success' : 'Failed'}\n`;
463
+ if (result.output) {
464
+ msg += `Output:\n\`\`\`\n${this.truncateOutput(result.output)}\n\`\`\`\n`;
465
+ }
466
+ if (result.error) {
467
+ msg += `Error: ${result.error}\n`;
468
+ }
469
+ msg += '\n';
470
+ }
471
+
472
+ msg += 'Continue with your analysis or next steps.';
473
+ return msg;
474
+ }
475
+
476
+ /**
477
+ * Truncate long output for context management
478
+ */
479
+ private truncateOutput(output: string, maxLines: number = 50): string {
480
+ const lines = output.split('\n');
481
+ if (lines.length <= maxLines) {
482
+ return output;
483
+ }
484
+ return lines.slice(0, maxLines).join('\n') + `\n... (${lines.length - maxLines} more lines)`;
485
+ }
486
+
487
+ /**
488
+ * Ask user for permission to execute dangerous action
489
+ */
490
+ private async askPermission(action: string): Promise<boolean> {
491
+ return new Promise((resolve) => {
492
+ console.log('\n' + action);
493
+
494
+ if (!this.rl) {
495
+ resolve(false);
496
+ return;
497
+ }
498
+
499
+ this.rl.question(chalk.yellow('Allow? [y/N] '), (answer) => {
500
+ const allowed = answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
501
+ resolve(allowed);
502
+ });
503
+ });
504
+ }
505
+
506
+ private async addFileToContext(filePath: string): Promise<void> {
507
+ const file = this.fileUtils.readFile(filePath);
508
+
509
+ if (!file) {
510
+ this.logger.error(`File not found: ${filePath}`);
511
+ return;
512
+ }
513
+
514
+ // Add file content to messages
515
+ const fileMessage: ChatMessage = {
516
+ role: 'user',
517
+ content: `Here is the content of ${file.relativePath} (${file.language}, ${file.lines} lines):\n\n\`\`\`${file.language}\n${file.content}\n\`\`\``,
518
+ };
519
+
520
+ this.messages.push(fileMessage);
521
+ this.logger.success(`Added ${file.relativePath} to context (${file.lines} lines)`);
522
+ }
523
+
524
+ private async startEditMode(
525
+ filePath: string,
526
+ model: string
527
+ ): Promise<{ file: string; content: string } | null> {
528
+ const file = this.fileUtils.readFile(filePath);
529
+
530
+ if (!file) {
531
+ this.logger.error(`File not found: ${filePath}`);
532
+ return null;
533
+ }
534
+
535
+ console.log();
536
+ this.logger.section(`Editing: ${file.relativePath}`);
537
+ console.log(chalk.gray('What changes would you like to make?'));
538
+ console.log();
539
+
540
+ // This would enter a sub-REPL for editing
541
+ // For now, return the file info
542
+ return { file: file.path, content: file.content };
543
+ }
544
+
545
+ private showPendingDiff(changes: { file: string; content: string }): void {
546
+ const file = this.fileUtils.readFile(changes.file);
547
+ if (!file) return;
548
+
549
+ const diff = this.fileUtils.createDiff(file.content, changes.content);
550
+
551
+ this.logger.section('Pending Changes');
552
+ this.logger.diff(diff.added, diff.removed);
553
+ }
554
+
555
+ private applyChanges(changes: { file: string; content: string }): void {
556
+ // Backup first
557
+ const backup = this.fileUtils.backupFile(changes.file);
558
+ if (backup) {
559
+ this.logger.info(`Backup created: ${backup}`);
560
+ }
561
+
562
+ // Apply
563
+ if (this.fileUtils.writeFile(changes.file, changes.content)) {
564
+ this.logger.success(`Changes applied to ${changes.file}`);
565
+ } else {
566
+ this.logger.error('Failed to apply changes');
567
+ }
568
+ }
569
+
570
+ private printHelp(): void {
571
+ console.log();
572
+ this.logger.section('Available Commands');
573
+ console.log(chalk.cyan('/file <path>') + ' - Add file to conversation context');
574
+ console.log(chalk.cyan('/edit <path>') + ' - Start editing a file');
575
+ console.log(chalk.cyan('/diff') + ' - Show pending changes');
576
+ console.log(chalk.cyan('/apply') + ' - Apply pending changes');
577
+ console.log(chalk.cyan('/model <name>') + ' - Switch AI model');
578
+ console.log(chalk.cyan('/agent') + ' - Toggle agentic mode (Claude Code-like)');
579
+ console.log(chalk.cyan('/approve') + ' - Toggle auto-approve for agent actions');
580
+ console.log(chalk.cyan('/undo') + ' - Undo last file operation (agent mode)');
581
+ console.log(chalk.cyan('/clear') + ' - Clear conversation history');
582
+ console.log(chalk.cyan('/compact') + ' - Compact context (summarize older messages)');
583
+ console.log(chalk.cyan('/sessions') + ' - List saved sessions');
584
+ console.log(chalk.cyan('/history') + ' - Show conversation history');
585
+ console.log(chalk.cyan('/save') + ' - Save current session');
586
+ console.log(chalk.cyan('/new') + ' - Start new session');
587
+ console.log(chalk.cyan('/help') + ' - Show this help');
588
+ console.log(chalk.cyan('/exit') + ' - Exit Vigthoria (auto-saves)');
589
+ console.log();
590
+
591
+ if (this.agentMode) {
592
+ console.log(chalk.yellow('Agent Mode Tools:'));
593
+ console.log(chalk.gray(' read_file, write_file, edit_file, bash, grep, list_dir, glob, git'));
594
+ console.log();
595
+ }
596
+ }
597
+
598
+ private listSessions(): void {
599
+ const sessions = this.sessionManager.list();
600
+
601
+ console.log();
602
+ this.logger.section('Saved Sessions');
603
+
604
+ if (sessions.length === 0) {
605
+ console.log(chalk.gray(' No saved sessions'));
606
+ } else {
607
+ sessions.slice(0, 10).forEach(s => {
608
+ const current = this.currentSession?.id === s.id ? chalk.green(' (current)') : '';
609
+ const agent = s.agentMode ? chalk.yellow(' [agent]') : '';
610
+ console.log(chalk.cyan(s.id) + agent + current);
611
+ console.log(chalk.gray(` ${s.project} - ${new Date(s.updatedAt).toLocaleString()}`));
612
+ });
613
+ }
614
+ console.log();
615
+ }
616
+
617
+ private showHistory(): void {
618
+ console.log();
619
+ this.logger.section('Conversation History');
620
+
621
+ const userMessages = this.messages.filter(m => m.role !== 'system');
622
+
623
+ if (userMessages.length === 0) {
624
+ console.log(chalk.gray(' No messages yet'));
625
+ } else {
626
+ userMessages.forEach((m, i) => {
627
+ const role = m.role === 'user' ? chalk.cyan('you') : chalk.green('vigthoria');
628
+ const preview = m.content.substring(0, 60).replace(/\n/g, ' ');
629
+ console.log(`${i + 1}. ${role}: ${chalk.gray(preview)}...`);
630
+ });
631
+ }
632
+ console.log();
633
+ }
634
+
635
+ private async compactContext(model: string): Promise<void> {
636
+ // If we have too many messages, summarize older ones
637
+ if (this.messages.length < 10) {
638
+ this.logger.info('Context is already compact');
639
+ return;
640
+ }
641
+
642
+ const spinner = ora({
643
+ text: chalk.gray('Compacting context...'),
644
+ spinner: 'dots',
645
+ }).start();
646
+
647
+ try {
648
+ // Keep system message and last 4 messages
649
+ const systemMessage = this.messages[0];
650
+ const recentMessages = this.messages.slice(-4);
651
+ const olderMessages = this.messages.slice(1, -4);
652
+
653
+ if (olderMessages.length === 0) {
654
+ spinner.stop();
655
+ this.logger.info('Nothing to compact');
656
+ return;
657
+ }
658
+
659
+ // Ask AI to summarize older conversation
660
+ const summaryResponse = await this.api.chat([
661
+ { role: 'system', content: 'Summarize this conversation in a concise way, preserving key context and decisions.' },
662
+ ...olderMessages,
663
+ ], model, this.localMode);
664
+
665
+ // Create compacted context
666
+ this.messages = [
667
+ systemMessage,
668
+ { role: 'system', content: `[Previous conversation summary]: ${summaryResponse.message}` },
669
+ ...recentMessages,
670
+ ];
671
+
672
+ spinner.stop();
673
+ this.logger.success(`Compacted ${olderMessages.length} messages into summary`);
674
+ } catch (error) {
675
+ spinner.stop();
676
+ this.logger.error('Failed to compact context:', (error as Error).message);
677
+ }
678
+ }
679
+
680
+ private printModels(): void {
681
+ const models = this.config.getAvailableModels();
682
+
683
+ console.log();
684
+ this.logger.section('Available Models');
685
+ models.forEach(m => {
686
+ console.log(chalk.cyan(m.id.padEnd(20)) + chalk.gray(m.description));
687
+ });
688
+ console.log();
689
+ }
690
+ }