@indiccoder/mentis-cli 1.1.4 → 1.1.5
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/.claude/settings.local.json +8 -0
- package/.mentis/session.json +15 -0
- package/.mentis/sessions/1769189035730.json +23 -0
- package/.mentis/sessions/1769189569160.json +23 -0
- package/.mentis/sessions/1769767538672.json +23 -0
- package/.mentis/sessions/1769767785155.json +23 -0
- package/.mentis/sessions/1769768745802.json +23 -0
- package/.mentis/sessions/1769769600884.json +31 -0
- package/.mentis/sessions/1769770030160.json +31 -0
- package/.mentis/sessions/1769770606004.json +78 -0
- package/.mentis/sessions/1769771084515.json +141 -0
- package/.mentis/sessions/1769881926630.json +57 -0
- package/README.md +17 -0
- package/dist/checkpoint/CheckpointManager.js +92 -0
- package/dist/debug_google.js +61 -0
- package/dist/debug_lite.js +49 -0
- package/dist/debug_lite_headers.js +57 -0
- package/dist/debug_search.js +16 -0
- package/dist/index.js +10 -0
- package/dist/mcp/JsonRpcClient.js +16 -0
- package/dist/mcp/McpConfig.js +132 -0
- package/dist/mcp/McpManager.js +189 -0
- package/dist/repl/PersistentShell.js +20 -1
- package/dist/repl/ReplManager.js +410 -138
- package/dist/tools/AskQuestionTool.js +172 -0
- package/dist/tools/EditFileTool.js +141 -0
- package/dist/tools/FileTools.js +7 -1
- package/dist/tools/PlanModeTool.js +53 -0
- package/dist/tools/WebSearchTool.js +190 -27
- package/dist/ui/DiffViewer.js +110 -0
- package/dist/ui/InputBox.js +16 -2
- package/dist/ui/MultiFileSelector.js +123 -0
- package/dist/ui/PlanModeUI.js +105 -0
- package/dist/ui/ToolExecutor.js +154 -0
- package/dist/ui/UIManager.js +12 -2
- package/docs/MCP_INTEGRATION.md +290 -0
- package/google_dump.html +18 -0
- package/lite_dump.html +176 -0
- package/lite_headers_dump.html +176 -0
- package/package.json +16 -5
- package/scripts/test_exa_mcp.ts +90 -0
- package/src/checkpoint/CheckpointManager.ts +102 -0
- package/src/debug_google.ts +30 -0
- package/src/debug_lite.ts +18 -0
- package/src/debug_lite_headers.ts +25 -0
- package/src/debug_search.ts +18 -0
- package/src/index.ts +12 -0
- package/src/mcp/JsonRpcClient.ts +19 -0
- package/src/mcp/McpConfig.ts +153 -0
- package/src/mcp/McpManager.ts +224 -0
- package/src/repl/PersistentShell.ts +24 -1
- package/src/repl/ReplManager.ts +1521 -1204
- package/src/tools/AskQuestionTool.ts +197 -0
- package/src/tools/EditFileTool.ts +172 -0
- package/src/tools/FileTools.ts +3 -0
- package/src/tools/PlanModeTool.ts +50 -0
- package/src/tools/WebSearchTool.ts +235 -63
- package/src/ui/DiffViewer.ts +117 -0
- package/src/ui/InputBox.ts +17 -2
- package/src/ui/MultiFileSelector.ts +135 -0
- package/src/ui/PlanModeUI.ts +121 -0
- package/src/ui/ToolExecutor.ts +182 -0
- package/src/ui/UIManager.ts +15 -2
- package/console.log(tick) +0 -0
package/src/repl/ReplManager.ts
CHANGED
|
@@ -1,1204 +1,1521 @@
|
|
|
1
|
-
import inquirer from 'inquirer';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import ora from 'ora';
|
|
4
|
-
import { ConfigManager } from '../config/ConfigManager';
|
|
5
|
-
import { ModelClient, ChatMessage } from '../llm/ModelInterface';
|
|
6
|
-
import { OpenAIClient } from '../llm/OpenAIClient';
|
|
7
|
-
|
|
8
|
-
import { ContextManager } from '../context/ContextManager';
|
|
9
|
-
import { UIManager } from '../ui/UIManager';
|
|
10
|
-
import { InputBox } from '../ui/InputBox';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import
|
|
29
|
-
import
|
|
30
|
-
import
|
|
31
|
-
import
|
|
32
|
-
import {
|
|
33
|
-
import
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
private
|
|
51
|
-
private
|
|
52
|
-
private
|
|
53
|
-
private
|
|
54
|
-
private
|
|
55
|
-
private
|
|
56
|
-
private
|
|
57
|
-
private
|
|
58
|
-
private
|
|
59
|
-
private
|
|
60
|
-
private
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.
|
|
70
|
-
this.
|
|
71
|
-
this.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
new
|
|
83
|
-
new
|
|
84
|
-
new
|
|
85
|
-
new
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
'
|
|
146
|
-
'
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
await this.
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
console.log(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
this.
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
this.
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
updates
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
private async
|
|
963
|
-
if (args.length
|
|
964
|
-
console.log(chalk.red('Usage: /
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
break;
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { ConfigManager } from '../config/ConfigManager';
|
|
5
|
+
import { ModelClient, ChatMessage } from '../llm/ModelInterface';
|
|
6
|
+
import { OpenAIClient } from '../llm/OpenAIClient';
|
|
7
|
+
|
|
8
|
+
import { ContextManager } from '../context/ContextManager';
|
|
9
|
+
import { UIManager } from '../ui/UIManager';
|
|
10
|
+
import { InputBox } from '../ui/InputBox';
|
|
11
|
+
import { DiffViewer } from '../ui/DiffViewer';
|
|
12
|
+
import { MultiFileSelector } from '../ui/MultiFileSelector';
|
|
13
|
+
import { ToolExecutor } from '../ui/ToolExecutor';
|
|
14
|
+
import { PlanModeUI } from '../ui/PlanModeUI';
|
|
15
|
+
import { WriteFileTool, ReadFileTool, ListDirTool, EditFileTool, AskQuestionTool, PlanModeTool } from '../tools/FileTools';
|
|
16
|
+
import { SearchFileTool } from '../tools/SearchTools';
|
|
17
|
+
import { PersistentShellTool } from '../tools/PersistentShellTool';
|
|
18
|
+
import { PersistentShell } from './PersistentShell';
|
|
19
|
+
import { WebSearchTool } from '../tools/WebSearchTool';
|
|
20
|
+
import { GitStatusTool, GitDiffTool, GitCommitTool, GitPushTool, GitPullTool } from '../tools/GitTools';
|
|
21
|
+
import { Tool } from '../tools/Tool';
|
|
22
|
+
import { McpClient } from '../mcp/McpClient';
|
|
23
|
+
import { McpManager } from '../mcp/McpManager';
|
|
24
|
+
|
|
25
|
+
import { CheckpointManager } from '../checkpoint/CheckpointManager';
|
|
26
|
+
import { SkillsManager } from '../skills/SkillsManager';
|
|
27
|
+
import { LoadSkillTool, ListSkillsTool, ReadSkillFileTool } from '../skills/LoadSkillTool';
|
|
28
|
+
import { ContextVisualizer } from '../utils/ContextVisualizer';
|
|
29
|
+
import { ProjectInitializer } from '../utils/ProjectInitializer';
|
|
30
|
+
import { ConversationCompacter } from '../utils/ConversationCompacter';
|
|
31
|
+
import { CommandManager } from '../commands/CommandManager';
|
|
32
|
+
import { SlashCommandTool, ListCommandsTool } from '../commands/SlashCommandTool';
|
|
33
|
+
import * as readline from 'readline';
|
|
34
|
+
import * as fs from 'fs';
|
|
35
|
+
import * as path from 'path';
|
|
36
|
+
import * as os from 'os';
|
|
37
|
+
import { marked } from 'marked';
|
|
38
|
+
import TerminalRenderer from 'marked-terminal';
|
|
39
|
+
|
|
40
|
+
const HISTORY_FILE = path.join(os.homedir(), '.mentis_history');
|
|
41
|
+
|
|
42
|
+
export interface CliOptions {
|
|
43
|
+
resume: boolean;
|
|
44
|
+
yolo: boolean;
|
|
45
|
+
headless: boolean;
|
|
46
|
+
headlessPrompt?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class ReplManager {
|
|
50
|
+
private configManager: ConfigManager;
|
|
51
|
+
private modelClient!: ModelClient;
|
|
52
|
+
private contextManager: ContextManager;
|
|
53
|
+
private checkpointManager: CheckpointManager;
|
|
54
|
+
private skillsManager: SkillsManager;
|
|
55
|
+
private contextVisualizer: ContextVisualizer;
|
|
56
|
+
private conversationCompacter: ConversationCompacter;
|
|
57
|
+
private commandManager: CommandManager;
|
|
58
|
+
private history: ChatMessage[] = [];
|
|
59
|
+
private mode: 'PLAN' | 'BUILD' = 'BUILD';
|
|
60
|
+
private tools: Tool[] = [];
|
|
61
|
+
private mcpClients: McpClient[] = [];
|
|
62
|
+
private mcpManager: McpManager;
|
|
63
|
+
private shell: PersistentShell;
|
|
64
|
+
private currentModelName: string = 'Unknown';
|
|
65
|
+
private activeSkill: string | null = null; // Track currently active skill for allowed-tools
|
|
66
|
+
private options: CliOptions;
|
|
67
|
+
|
|
68
|
+
constructor(options: CliOptions = { resume: false, yolo: false, headless: false }) {
|
|
69
|
+
this.options = options;
|
|
70
|
+
this.configManager = new ConfigManager();
|
|
71
|
+
this.contextManager = new ContextManager();
|
|
72
|
+
this.checkpointManager = new CheckpointManager();
|
|
73
|
+
this.skillsManager = new SkillsManager();
|
|
74
|
+
this.contextVisualizer = new ContextVisualizer();
|
|
75
|
+
this.conversationCompacter = new ConversationCompacter();
|
|
76
|
+
this.commandManager = new CommandManager();
|
|
77
|
+
this.mcpManager = new McpManager();
|
|
78
|
+
this.shell = new PersistentShell();
|
|
79
|
+
|
|
80
|
+
// Create tools array without skill tools first
|
|
81
|
+
this.tools = [
|
|
82
|
+
new PlanModeTool(), // AI can suggest plan mode for complex tasks
|
|
83
|
+
new AskQuestionTool(), // For plan mode questions
|
|
84
|
+
new WriteFileTool(),
|
|
85
|
+
new EditFileTool(),
|
|
86
|
+
new ReadFileTool(),
|
|
87
|
+
new ListDirTool(),
|
|
88
|
+
new SearchFileTool(), // grep
|
|
89
|
+
new WebSearchTool(),
|
|
90
|
+
new GitStatusTool(),
|
|
91
|
+
new GitDiffTool(),
|
|
92
|
+
new GitCommitTool(),
|
|
93
|
+
new GitPushTool(),
|
|
94
|
+
new GitPullTool(),
|
|
95
|
+
new PersistentShellTool(this.shell) // /run
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
// Configure Markdown Renderer
|
|
99
|
+
marked.setOptions({
|
|
100
|
+
// @ts-ignore
|
|
101
|
+
renderer: new TerminalRenderer()
|
|
102
|
+
});
|
|
103
|
+
// Default to Ollama if not specified, assuming compatible endpoint
|
|
104
|
+
this.initializeClient();
|
|
105
|
+
|
|
106
|
+
// Initialize skills system after client is ready
|
|
107
|
+
this.initializeSkills();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Initialize the skills and custom commands system
|
|
112
|
+
*/
|
|
113
|
+
private async initializeSkills() {
|
|
114
|
+
// Initialize skills
|
|
115
|
+
this.skillsManager.ensureDirectoriesExist();
|
|
116
|
+
await this.skillsManager.discoverSkills();
|
|
117
|
+
|
|
118
|
+
// Initialize custom commands
|
|
119
|
+
this.commandManager.ensureDirectoriesExist();
|
|
120
|
+
await this.commandManager.discoverCommands();
|
|
121
|
+
|
|
122
|
+
// Add skill tools to the tools list
|
|
123
|
+
// Pass callback to LoadSkillTool to track active skill
|
|
124
|
+
this.tools.push(
|
|
125
|
+
new LoadSkillTool(this.skillsManager, (skill) => {
|
|
126
|
+
this.activeSkill = skill ? skill.name : null;
|
|
127
|
+
}),
|
|
128
|
+
new ListSkillsTool(this.skillsManager),
|
|
129
|
+
new ReadSkillFileTool(this.skillsManager),
|
|
130
|
+
new SlashCommandTool(this.commandManager),
|
|
131
|
+
new ListCommandsTool(this.commandManager)
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Auto-connect to MCP servers
|
|
135
|
+
await this.mcpManager.autoConnect();
|
|
136
|
+
this.refreshToolsFromMcp();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Refresh tools list from MCP connections
|
|
141
|
+
*/
|
|
142
|
+
private refreshToolsFromMcp() {
|
|
143
|
+
// Remove existing MCP tools (keep core tools)
|
|
144
|
+
this.tools = this.tools.filter(tool =>
|
|
145
|
+
!tool.name.startsWith('mcp_') &&
|
|
146
|
+
!['load_skill', 'list_skills', 'read_skill_file', 'slash_command', 'list_commands'].includes(tool.name)
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Add MCP tools
|
|
150
|
+
const mcpTools = this.mcpManager.getAllTools();
|
|
151
|
+
this.tools.push(...mcpTools);
|
|
152
|
+
|
|
153
|
+
// Re-add skill tools
|
|
154
|
+
this.tools.push(
|
|
155
|
+
new LoadSkillTool(this.skillsManager, (skill) => {
|
|
156
|
+
this.activeSkill = skill ? skill.name : null;
|
|
157
|
+
}),
|
|
158
|
+
new ListSkillsTool(this.skillsManager),
|
|
159
|
+
new ReadSkillFileTool(this.skillsManager),
|
|
160
|
+
new SlashCommandTool(this.commandManager),
|
|
161
|
+
new ListCommandsTool(this.commandManager)
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Check if a tool is allowed by the currently active skill
|
|
167
|
+
* Returns true if tool is allowed, false if it requires confirmation
|
|
168
|
+
*/
|
|
169
|
+
private isToolAllowedBySkill(toolName: string): boolean {
|
|
170
|
+
if (!this.activeSkill) {
|
|
171
|
+
// No active skill, all tools require confirmation as per normal flow
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const skill = this.skillsManager.getSkill(this.activeSkill);
|
|
176
|
+
if (!skill || !skill.allowedTools || skill.allowedTools.length === 0) {
|
|
177
|
+
// No skill or no allowed-tools restriction
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Map tool names to allowed tool names
|
|
182
|
+
const toolMapping: Record<string, string> = {
|
|
183
|
+
'write_file': 'Write',
|
|
184
|
+
'read_file': 'Read',
|
|
185
|
+
'edit_file': 'Edit',
|
|
186
|
+
'search_files': 'Grep',
|
|
187
|
+
'list_dir': 'ListDir',
|
|
188
|
+
'search_file': 'SearchFile',
|
|
189
|
+
'run_shell': 'RunShell',
|
|
190
|
+
'search_web': 'WebSearch',
|
|
191
|
+
'git_status': 'GitStatus',
|
|
192
|
+
'git_diff': 'GitDiff',
|
|
193
|
+
'git_commit': 'GitCommit',
|
|
194
|
+
'git_push': 'GitPush',
|
|
195
|
+
'git_pull': 'GitPull',
|
|
196
|
+
'load_skill': 'Read',
|
|
197
|
+
'list_skills': 'Read',
|
|
198
|
+
'read_skill_file': 'Read',
|
|
199
|
+
'slash_command': 'Read',
|
|
200
|
+
'list_commands': 'Read'
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const mappedToolName = toolMapping[toolName] || toolName;
|
|
204
|
+
return skill.allowedTools.includes(mappedToolName);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private initializeClient() {
|
|
208
|
+
const config = this.configManager.getConfig();
|
|
209
|
+
const provider = config.defaultProvider || 'ollama';
|
|
210
|
+
|
|
211
|
+
let baseUrl: string | undefined;
|
|
212
|
+
let apiKey: string;
|
|
213
|
+
let model: string;
|
|
214
|
+
|
|
215
|
+
if (provider === 'gemini') {
|
|
216
|
+
baseUrl = 'https://generativelanguage.googleapis.com/v1beta/openai/';
|
|
217
|
+
apiKey = config.gemini?.apiKey || '';
|
|
218
|
+
model = config.gemini?.model || 'gemini-2.5-flash';
|
|
219
|
+
} else if (provider === 'openai') {
|
|
220
|
+
baseUrl = config.openai?.baseUrl || 'https://api.openai.com/v1';
|
|
221
|
+
apiKey = config.openai?.apiKey || '';
|
|
222
|
+
model = config.openai?.model || 'gpt-4o';
|
|
223
|
+
} else if (provider === 'glm') {
|
|
224
|
+
// Use the "Coding Plan" endpoint which supports glm-4.6 and this specific key type
|
|
225
|
+
baseUrl = config.glm?.baseUrl || 'https://api.z.ai/api/coding/paas/v4/';
|
|
226
|
+
apiKey = config.glm?.apiKey || '';
|
|
227
|
+
model = config.glm?.model || 'glm-4.6';
|
|
228
|
+
} else { // Default to Ollama
|
|
229
|
+
baseUrl = config.ollama?.baseUrl || 'http://localhost:11434/v1';
|
|
230
|
+
apiKey = 'ollama'; // Ollama typically doesn't use an API key in the same way
|
|
231
|
+
model = config.ollama?.model || 'llama3:latest';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
this.currentModelName = model;
|
|
235
|
+
this.modelClient = new OpenAIClient(baseUrl, apiKey, model);
|
|
236
|
+
// console.log(chalk.dim(`Initialized ${provider} client with model ${model}`));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public async start() {
|
|
240
|
+
// Headless mode: non-interactive, process prompt and exit
|
|
241
|
+
if (this.options.headless && this.options.headlessPrompt) {
|
|
242
|
+
await this.handleChat(this.options.headlessPrompt);
|
|
243
|
+
process.exit(0);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
UIManager.renderDashboard({
|
|
248
|
+
model: this.currentModelName,
|
|
249
|
+
mode: this.mode,
|
|
250
|
+
cwd: process.cwd()
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Auto-resume if --resume flag is set
|
|
254
|
+
if (this.options.resume) {
|
|
255
|
+
// Prefer local (per-directory) session, fall back to global
|
|
256
|
+
const cwd = process.cwd();
|
|
257
|
+
let cp = this.checkpointManager.loadLocalSession(cwd);
|
|
258
|
+
let source = 'local';
|
|
259
|
+
if (!cp) {
|
|
260
|
+
cp = this.checkpointManager.load('latest');
|
|
261
|
+
source = 'global';
|
|
262
|
+
}
|
|
263
|
+
if (cp) {
|
|
264
|
+
this.history = cp.history;
|
|
265
|
+
console.log(chalk.green(`\n✓ Resumed ${source} session from ${new Date(cp.timestamp).toLocaleString()}`));
|
|
266
|
+
console.log(chalk.dim(` Messages: ${this.history.length}\n`));
|
|
267
|
+
} else {
|
|
268
|
+
console.log(chalk.yellow('\n⚠ No previous session found to resume.\n'));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Load History
|
|
273
|
+
let commandHistory: string[] = [];
|
|
274
|
+
if (fs.existsSync(HISTORY_FILE)) {
|
|
275
|
+
try {
|
|
276
|
+
commandHistory = fs.readFileSync(HISTORY_FILE, 'utf-8').split('\n').filter(Boolean).reverse();
|
|
277
|
+
} catch (e) { }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Initialize InputBox with history
|
|
281
|
+
const inputBox = new InputBox(commandHistory);
|
|
282
|
+
|
|
283
|
+
while (true) {
|
|
284
|
+
// Calculate context usage for display
|
|
285
|
+
const usage = this.contextVisualizer.calculateUsage(this.history);
|
|
286
|
+
|
|
287
|
+
// Display enhanced input frame
|
|
288
|
+
inputBox.displayFrame({
|
|
289
|
+
messageCount: this.history.length,
|
|
290
|
+
contextPercent: usage.percentage
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Get styled input
|
|
294
|
+
const answer = await inputBox.prompt({
|
|
295
|
+
showHint: this.history.length === 0,
|
|
296
|
+
hint: 'Type your message or /help for commands'
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const input = answer.trim();
|
|
300
|
+
|
|
301
|
+
if (input) {
|
|
302
|
+
// Update history via InputBox
|
|
303
|
+
inputBox.addToHistory(input);
|
|
304
|
+
|
|
305
|
+
// Append to file
|
|
306
|
+
try {
|
|
307
|
+
fs.appendFileSync(HISTORY_FILE, input + '\n');
|
|
308
|
+
} catch (e) { }
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (!input) continue;
|
|
312
|
+
|
|
313
|
+
if (input.startsWith('/')) {
|
|
314
|
+
await this.handleCommand(input);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Wrap handleChat in try-catch to prevent REPL from entering inconsistent state
|
|
319
|
+
try {
|
|
320
|
+
await this.handleChat(input);
|
|
321
|
+
} catch (error: any) {
|
|
322
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
323
|
+
console.error(chalk.dim('The REPL has recovered. You can continue using the CLI.'));
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private async handleCommand(input: string) {
|
|
329
|
+
const [command, ...args] = input.split(' ');
|
|
330
|
+
switch (command) {
|
|
331
|
+
case '/help':
|
|
332
|
+
console.log(chalk.yellow('Available commands:'));
|
|
333
|
+
console.log(' /help - Show this help message');
|
|
334
|
+
console.log(' /clear - Clear chat history');
|
|
335
|
+
console.log(' /exit - Exit the application');
|
|
336
|
+
console.log(' /update - Check for and install updates');
|
|
337
|
+
console.log(' /config - Configure settings');
|
|
338
|
+
console.log(' /add <file> - Add file to context');
|
|
339
|
+
console.log(' /drop <file> - Remove file from context');
|
|
340
|
+
console.log(' /plan - Switch to PLAN mode');
|
|
341
|
+
console.log(' /build - Switch to BUILD mode');
|
|
342
|
+
console.log(' /model - Interactively select Provider & Model');
|
|
343
|
+
console.log(' /use <provider> [model] - Quick switch (legacy)');
|
|
344
|
+
console.log(' /mcp <cmd> - Manage MCP servers');
|
|
345
|
+
console.log(' /skills <list|show|create|validate> - Manage Agent Skills');
|
|
346
|
+
console.log(' /commands <list|create|validate> - Manage Custom Commands');
|
|
347
|
+
console.log(' /resume - Resume last session');
|
|
348
|
+
console.log(' /search <query> - Search codebase');
|
|
349
|
+
console.log(' /run <cmd> - Run shell command');
|
|
350
|
+
console.log(' /commit [msg] - Git commit all changes');
|
|
351
|
+
console.log(' /init - Initialize project with .mentis.md');
|
|
352
|
+
break;
|
|
353
|
+
case '/plan':
|
|
354
|
+
this.mode = 'PLAN';
|
|
355
|
+
UIManager.logBullet('Entered plan mode', 'magenta');
|
|
356
|
+
PlanModeUI.showPlanHeader();
|
|
357
|
+
PlanModeUI.showQAHistory();
|
|
358
|
+
break;
|
|
359
|
+
case '/build':
|
|
360
|
+
this.mode = 'BUILD';
|
|
361
|
+
UIManager.logBullet('Entered build mode', 'green');
|
|
362
|
+
PlanModeUI.showPlanSummary();
|
|
363
|
+
UIManager.logSystem('Mentis is building the solution...');
|
|
364
|
+
break;
|
|
365
|
+
case '/model':
|
|
366
|
+
await this.handleModelCommand(args);
|
|
367
|
+
break;
|
|
368
|
+
case '/connect':
|
|
369
|
+
console.log(chalk.dim('Tip: Use /model for an interactive menu.'));
|
|
370
|
+
await this.handleConnectCommand(args);
|
|
371
|
+
break;
|
|
372
|
+
case '/use':
|
|
373
|
+
await this.handleUseCommand(args);
|
|
374
|
+
break;
|
|
375
|
+
case '/mcp':
|
|
376
|
+
await this.handleMcpCommand(args);
|
|
377
|
+
break;
|
|
378
|
+
case '/resume':
|
|
379
|
+
await this.handleResumeCommand();
|
|
380
|
+
break;
|
|
381
|
+
case '/clear':
|
|
382
|
+
this.history = [];
|
|
383
|
+
this.contextManager.clear();
|
|
384
|
+
UIManager.displayLogo(); // Redraw logo on clear
|
|
385
|
+
console.log(chalk.yellow('Chat history and context cleared.'));
|
|
386
|
+
break;
|
|
387
|
+
case '/add':
|
|
388
|
+
if (args.length === 0) {
|
|
389
|
+
console.log(chalk.red('Usage: /add <file_path>'));
|
|
390
|
+
} else {
|
|
391
|
+
const result = await this.contextManager.addFile(args[0]);
|
|
392
|
+
console.log(chalk.yellow(result));
|
|
393
|
+
}
|
|
394
|
+
break;
|
|
395
|
+
case '/drop':
|
|
396
|
+
if (args.length === 0) {
|
|
397
|
+
console.log(chalk.red('Usage: /drop <file_path>'));
|
|
398
|
+
} else {
|
|
399
|
+
const result = await this.contextManager.removeFile(args[0]);
|
|
400
|
+
console.log(chalk.yellow(result));
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
case '/config':
|
|
404
|
+
await this.handleConfigCommand();
|
|
405
|
+
break;
|
|
406
|
+
case '/exit':
|
|
407
|
+
// Auto-save on exit (both local and global)
|
|
408
|
+
const cwd = process.cwd();
|
|
409
|
+
this.checkpointManager.saveLocalSession(cwd, this.history, this.contextManager.getFiles());
|
|
410
|
+
this.checkpointManager.save('latest', this.history, this.contextManager.getFiles());
|
|
411
|
+
this.shell.kill(); // Kill the shell process
|
|
412
|
+
this.mcpManager.disconnectAll(); // Disconnect all MCP connections
|
|
413
|
+
console.log(chalk.green('Session saved to .mentis/sessions/. Goodbye!'));
|
|
414
|
+
process.exit(0);
|
|
415
|
+
break;
|
|
416
|
+
case '/update':
|
|
417
|
+
const UpdateManager = require('../utils/UpdateManager').UpdateManager;
|
|
418
|
+
const updater = new UpdateManager();
|
|
419
|
+
await updater.checkAndPerformUpdate(true);
|
|
420
|
+
break;
|
|
421
|
+
case '/clear':
|
|
422
|
+
this.history = [];
|
|
423
|
+
console.log(chalk.green('\n✓ Context cleared\n'));
|
|
424
|
+
break;
|
|
425
|
+
case '/init':
|
|
426
|
+
await this.handleInitCommand();
|
|
427
|
+
break;
|
|
428
|
+
case '/skills':
|
|
429
|
+
await this.handleSkillsCommand(args);
|
|
430
|
+
break;
|
|
431
|
+
case '/commands':
|
|
432
|
+
await this.handleCommandsCommand(args);
|
|
433
|
+
break;
|
|
434
|
+
default:
|
|
435
|
+
console.log(chalk.red(`Unknown command: ${command}`));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private getLoadingMessage(): string {
|
|
440
|
+
const messages = [
|
|
441
|
+
"Reticulating splines...",
|
|
442
|
+
"Consulting the silicon oracle...",
|
|
443
|
+
"Compiling neural pathways...",
|
|
444
|
+
"Optimizing flux capacitors...",
|
|
445
|
+
"Analyzing project structure...",
|
|
446
|
+
"Deciphering your intent...",
|
|
447
|
+
"Brewing digital coffee...",
|
|
448
|
+
"Checking for infinite loops...",
|
|
449
|
+
"Connecting to the matrix...",
|
|
450
|
+
"Calculating the answer (42?)...",
|
|
451
|
+
"Refactoring the universe...",
|
|
452
|
+
"Downloading more RAM...",
|
|
453
|
+
"Searching for bugs...",
|
|
454
|
+
"Asking the rubber duck...",
|
|
455
|
+
"Hyperspacing..."
|
|
456
|
+
];
|
|
457
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private async handleChat(input: string) {
|
|
461
|
+
const context = this.contextManager.getContextString();
|
|
462
|
+
const skillsContext = this.skillsManager.getSkillsContext();
|
|
463
|
+
const commandsContext = this.commandManager.getCommandsContext();
|
|
464
|
+
let fullInput = input;
|
|
465
|
+
|
|
466
|
+
let modeInstruction = '';
|
|
467
|
+
if (this.mode === 'PLAN') {
|
|
468
|
+
modeInstruction = '\n[SYSTEM: You are in PLAN mode. Focus on high-level architecture, requirements analysis, and creating a sturdy plan. Do not write full code implementation yet, just scaffolds or pseudocode if needed.]';
|
|
469
|
+
} else {
|
|
470
|
+
modeInstruction = '\n[SYSTEM: You are in BUILD mode. Focus on implementing working code that solves the user request efficiently.]';
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
fullInput = `${input}${modeInstruction}`;
|
|
474
|
+
|
|
475
|
+
// Add skills context if available
|
|
476
|
+
if (skillsContext) {
|
|
477
|
+
fullInput = `${skillsContext}\n\n${fullInput}`;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Add commands context if available
|
|
481
|
+
if (commandsContext) {
|
|
482
|
+
fullInput = `${commandsContext}\n\n${fullInput}`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (context) {
|
|
486
|
+
fullInput = `${context}\n\nUser Question: ${fullInput}`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
this.history.push({ role: 'user', content: fullInput });
|
|
490
|
+
|
|
491
|
+
const msg = this.getLoadingMessage();
|
|
492
|
+
let spinner = ora({ text: ` ${msg}`, color: 'cyan' }).start();
|
|
493
|
+
const controller = new AbortController();
|
|
494
|
+
|
|
495
|
+
// Setup cancellation listener
|
|
496
|
+
const keyListener = (str: string, key: any) => {
|
|
497
|
+
if (key.name === 'escape') {
|
|
498
|
+
controller.abort();
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
if (process.stdin.isTTY) {
|
|
503
|
+
readline.emitKeypressEvents(process.stdin);
|
|
504
|
+
process.stdin.setRawMode(true);
|
|
505
|
+
process.stdin.on('keypress', keyListener);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
// First call
|
|
510
|
+
let response = await this.modelClient.chat(this.history, this.tools.map(t => ({
|
|
511
|
+
type: 'function',
|
|
512
|
+
function: {
|
|
513
|
+
name: t.name,
|
|
514
|
+
description: t.description,
|
|
515
|
+
parameters: t.parameters
|
|
516
|
+
}
|
|
517
|
+
})), controller.signal);
|
|
518
|
+
|
|
519
|
+
// Loop for tool calls
|
|
520
|
+
while (response.tool_calls && response.tool_calls.length > 0) {
|
|
521
|
+
if (controller.signal.aborted) throw new Error('Request cancelled by user');
|
|
522
|
+
|
|
523
|
+
spinner.stop();
|
|
524
|
+
|
|
525
|
+
// Add the assistant's request to use tool to history
|
|
526
|
+
this.history.push({
|
|
527
|
+
role: 'assistant',
|
|
528
|
+
content: response.content,
|
|
529
|
+
tool_calls: response.tool_calls
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
// Execute tools
|
|
533
|
+
for (const toolCall of response.tool_calls) {
|
|
534
|
+
if (controller.signal.aborted) break;
|
|
535
|
+
|
|
536
|
+
const toolName = toolCall.function.name;
|
|
537
|
+
const toolArgsStr = toolCall.function.arguments;
|
|
538
|
+
const toolArgs = JSON.parse(toolArgsStr);
|
|
539
|
+
|
|
540
|
+
// Show tool execution with visual feedback
|
|
541
|
+
ToolExecutor.showInline(toolName, toolArgs);
|
|
542
|
+
|
|
543
|
+
// Safety checks for write/edit operations
|
|
544
|
+
// Skip confirmation if tool is allowed by active skill
|
|
545
|
+
let approved = true;
|
|
546
|
+
const needsApproval = (toolName === 'write_file' || toolName === 'edit_file' || toolName === 'read_file')
|
|
547
|
+
&& !this.isToolAllowedBySkill(toolName === 'read_file' ? 'Read' : 'Write');
|
|
548
|
+
|
|
549
|
+
if (needsApproval) {
|
|
550
|
+
// Pause cancellation listener during user interaction
|
|
551
|
+
if (process.stdin.isTTY) {
|
|
552
|
+
process.stdin.removeListener('keypress', keyListener);
|
|
553
|
+
process.stdin.setRawMode(false);
|
|
554
|
+
process.stdin.pause();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Handle write_file with diff preview
|
|
558
|
+
if (toolName === 'write_file') {
|
|
559
|
+
approved = await this.handleWriteApproval(toolArgs.filePath, toolArgs.content);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Handle edit_file with diff preview
|
|
563
|
+
if (toolName === 'edit_file') {
|
|
564
|
+
approved = await this.handleEditApproval(toolArgs);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Handle read_file with multi-file selector
|
|
568
|
+
if (toolName === 'read_file') {
|
|
569
|
+
approved = await this.handleReadApproval(toolArgs.filePath);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Resume cancellation listener
|
|
573
|
+
if (process.stdin.isTTY) {
|
|
574
|
+
process.stdin.setRawMode(true);
|
|
575
|
+
process.stdin.resume();
|
|
576
|
+
process.stdin.on('keypress', keyListener);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (!approved) {
|
|
581
|
+
this.history.push({
|
|
582
|
+
role: 'tool',
|
|
583
|
+
tool_call_id: toolCall.id,
|
|
584
|
+
name: toolName,
|
|
585
|
+
content: 'Error: User rejected operation.'
|
|
586
|
+
});
|
|
587
|
+
console.log(chalk.red(' Action cancelled by user.'));
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
const tool = this.tools.find(t => t.name === toolName);
|
|
592
|
+
let result = '';
|
|
593
|
+
|
|
594
|
+
if (tool) {
|
|
595
|
+
try {
|
|
596
|
+
result = await tool.execute(toolArgs);
|
|
597
|
+
|
|
598
|
+
// Record Q&A for ask_question tool in plan mode
|
|
599
|
+
if (toolName === 'ask_question' && this.mode === 'PLAN') {
|
|
600
|
+
PlanModeUI.recordQA(toolArgs.question, result);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Handle plan mode switch approval
|
|
604
|
+
if (toolName === 'enter_plan_mode' && result.includes('User approved')) {
|
|
605
|
+
this.mode = 'PLAN';
|
|
606
|
+
UIManager.logBullet('Auto-switched to plan mode', 'magenta');
|
|
607
|
+
}
|
|
608
|
+
} catch (e: any) {
|
|
609
|
+
result = `Error: ${e.message}`;
|
|
610
|
+
}
|
|
611
|
+
} else {
|
|
612
|
+
result = `Error: Tool ${toolName} not found.`;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
this.history.push({
|
|
616
|
+
role: 'tool',
|
|
617
|
+
tool_call_id: toolCall.id,
|
|
618
|
+
name: toolName,
|
|
619
|
+
content: result
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (controller.signal.aborted) throw new Error('Request cancelled by user');
|
|
624
|
+
|
|
625
|
+
const msg = this.getLoadingMessage();
|
|
626
|
+
spinner = ora({ text: ` ${msg}`, color: 'cyan' }).start();
|
|
627
|
+
|
|
628
|
+
// Get next response
|
|
629
|
+
response = await this.modelClient.chat(this.history, this.tools.map(t => ({
|
|
630
|
+
type: 'function',
|
|
631
|
+
function: {
|
|
632
|
+
name: t.name,
|
|
633
|
+
description: t.description,
|
|
634
|
+
parameters: t.parameters
|
|
635
|
+
}
|
|
636
|
+
})), controller.signal);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
spinner.stop();
|
|
640
|
+
|
|
641
|
+
console.log('');
|
|
642
|
+
if (response.content) {
|
|
643
|
+
UIManager.logBullet('Mentis:', 'magenta');
|
|
644
|
+
console.log(marked(response.content));
|
|
645
|
+
|
|
646
|
+
if (response.usage) {
|
|
647
|
+
const { input_tokens, output_tokens } = response.usage;
|
|
648
|
+
const totalCost = this.estimateCost(input_tokens, output_tokens);
|
|
649
|
+
console.log(chalk.dim(`\n(Tokens: ${input_tokens} in / ${output_tokens} out | Est. Cost: $${totalCost.toFixed(5)})`));
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Display context bar
|
|
653
|
+
const contextBar = this.contextVisualizer.getContextBar(this.history);
|
|
654
|
+
console.log(chalk.dim(`\n${contextBar}`));
|
|
655
|
+
|
|
656
|
+
console.log('');
|
|
657
|
+
this.history.push({ role: 'assistant', content: response.content });
|
|
658
|
+
|
|
659
|
+
// Auto-compact prompt when context is at 80%
|
|
660
|
+
const usage = this.contextVisualizer.calculateUsage(this.history);
|
|
661
|
+
if (usage.percentage >= 80) {
|
|
662
|
+
this.history = await this.conversationCompacter.promptIfCompactNeeded(
|
|
663
|
+
usage.percentage,
|
|
664
|
+
this.history,
|
|
665
|
+
this.modelClient,
|
|
666
|
+
this.options.yolo
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
} catch (error: any) {
|
|
671
|
+
spinner.stop();
|
|
672
|
+
if (error.message === 'Request cancelled by user') {
|
|
673
|
+
console.log(chalk.yellow('\nRequest cancelled by user.'));
|
|
674
|
+
} else {
|
|
675
|
+
spinner.fail('Error getting response from model.');
|
|
676
|
+
console.error(error.message);
|
|
677
|
+
}
|
|
678
|
+
} finally {
|
|
679
|
+
if (process.stdin.isTTY) {
|
|
680
|
+
process.stdin.removeListener('keypress', keyListener);
|
|
681
|
+
process.stdin.setRawMode(false);
|
|
682
|
+
process.stdin.pause(); // Reset flow
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
private async handleConfigCommand() {
|
|
688
|
+
const config = this.configManager.getConfig();
|
|
689
|
+
const { action } = await inquirer.prompt([
|
|
690
|
+
{
|
|
691
|
+
type: 'list',
|
|
692
|
+
name: 'action',
|
|
693
|
+
message: 'Configuration',
|
|
694
|
+
prefix: '',
|
|
695
|
+
choices: [
|
|
696
|
+
'Show Current Configuration',
|
|
697
|
+
'Set Active Provider',
|
|
698
|
+
'Set API Key (for active provider)',
|
|
699
|
+
'Set Base URL (for active provider)',
|
|
700
|
+
'Back'
|
|
701
|
+
]
|
|
702
|
+
}
|
|
703
|
+
]);
|
|
704
|
+
|
|
705
|
+
if (action === 'Back') return;
|
|
706
|
+
|
|
707
|
+
if (action === 'Show Current Configuration') {
|
|
708
|
+
console.log(JSON.stringify(config, null, 2));
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
if (action === 'Set Active Provider') {
|
|
713
|
+
const { provider } = await inquirer.prompt([{
|
|
714
|
+
type: 'list',
|
|
715
|
+
name: 'provider',
|
|
716
|
+
message: 'Select Provider:',
|
|
717
|
+
choices: ['Gemini', 'Ollama', 'OpenAI', 'GLM']
|
|
718
|
+
}]);
|
|
719
|
+
const key = provider.toLowerCase();
|
|
720
|
+
this.configManager.updateConfig({ defaultProvider: key });
|
|
721
|
+
console.log(chalk.green(`Active provider set to: ${provider}`));
|
|
722
|
+
this.initializeClient();
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
const currentProvider = config.defaultProvider;
|
|
727
|
+
|
|
728
|
+
if (action === 'Set API Key (for active provider)') {
|
|
729
|
+
if (currentProvider === 'ollama') {
|
|
730
|
+
console.log(chalk.yellow('Ollama typically does not require an API key.'));
|
|
731
|
+
}
|
|
732
|
+
const { value } = await inquirer.prompt([{
|
|
733
|
+
type: 'password',
|
|
734
|
+
name: 'value',
|
|
735
|
+
message: `Enter API Key for ${currentProvider}:`,
|
|
736
|
+
mask: '*'
|
|
737
|
+
}]);
|
|
738
|
+
|
|
739
|
+
const updates: any = {};
|
|
740
|
+
updates[currentProvider] = { ...((config as any)[currentProvider] || {}), apiKey: value };
|
|
741
|
+
this.configManager.updateConfig(updates);
|
|
742
|
+
console.log(chalk.green(`API Key updated for ${currentProvider}.`));
|
|
743
|
+
this.initializeClient();
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (action === 'Set Base URL (for active provider)') {
|
|
747
|
+
const defaultUrl = (config as any)[currentProvider]?.baseUrl || '';
|
|
748
|
+
const { value } = await inquirer.prompt([{
|
|
749
|
+
type: 'input',
|
|
750
|
+
name: 'value',
|
|
751
|
+
message: `Enter Base URL for ${currentProvider}:`,
|
|
752
|
+
default: defaultUrl
|
|
753
|
+
}]);
|
|
754
|
+
|
|
755
|
+
const updates: any = {};
|
|
756
|
+
updates[currentProvider] = { ...((config as any)[currentProvider] || {}), baseUrl: value };
|
|
757
|
+
this.configManager.updateConfig(updates);
|
|
758
|
+
console.log(chalk.green(`Base URL updated for ${currentProvider}.`));
|
|
759
|
+
this.initializeClient();
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
private async handleModelCommand(args: string[]) {
|
|
764
|
+
const config = this.configManager.getConfig();
|
|
765
|
+
const currentProvider = config.defaultProvider || 'ollama';
|
|
766
|
+
|
|
767
|
+
// Direct argument: /model gpt-4o (updates active provider's model)
|
|
768
|
+
if (args.length > 0) {
|
|
769
|
+
const modelName = args[0];
|
|
770
|
+
const updates: any = {};
|
|
771
|
+
updates[currentProvider] = { ...((config as any)[currentProvider] || {}), model: modelName };
|
|
772
|
+
this.configManager.updateConfig(updates);
|
|
773
|
+
this.initializeClient(); // Re-init with new model
|
|
774
|
+
console.log(chalk.green(`\nModel set to ${chalk.bold(modelName)} for ${currentProvider}!`));
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// Interactive Mode: Streamlined Provider -> Model Flow
|
|
779
|
+
console.log(chalk.cyan('Configure Model & Provider'));
|
|
780
|
+
|
|
781
|
+
const { provider } = await inquirer.prompt([
|
|
782
|
+
{
|
|
783
|
+
type: 'list',
|
|
784
|
+
name: 'provider',
|
|
785
|
+
message: 'Select Provider:',
|
|
786
|
+
choices: ['Gemini', 'Ollama', 'OpenAI', 'GLM'],
|
|
787
|
+
default: currentProvider.charAt(0).toUpperCase() + currentProvider.slice(1) // Capitalize for default selection
|
|
788
|
+
}
|
|
789
|
+
]);
|
|
790
|
+
|
|
791
|
+
const selectedProvider = provider.toLowerCase();
|
|
792
|
+
|
|
793
|
+
let models: string[] = [];
|
|
794
|
+
if (selectedProvider === 'gemini') {
|
|
795
|
+
models = ['gemini-2.5-flash', 'gemini-1.5-pro', 'gemini-1.0-pro', 'Other...'];
|
|
796
|
+
} else if (selectedProvider === 'ollama') {
|
|
797
|
+
models = ['llama3:latest', 'deepseek-r1:latest', 'mistral:latest', 'qwen2.5-coder', 'Other...'];
|
|
798
|
+
} else if (selectedProvider === 'openai') {
|
|
799
|
+
models = ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'Other...'];
|
|
800
|
+
} else if (selectedProvider === 'glm') {
|
|
801
|
+
models = ['glm-4.6', 'glm-4-plus', 'glm-4', 'glm-4-air', 'glm-4-flash', 'Other...'];
|
|
802
|
+
} else {
|
|
803
|
+
models = ['Other...'];
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
let { model } = await inquirer.prompt([
|
|
807
|
+
{
|
|
808
|
+
type: 'list',
|
|
809
|
+
name: 'model',
|
|
810
|
+
message: `Select Model for ${provider}:`,
|
|
811
|
+
choices: models,
|
|
812
|
+
// Try to find current model in list to set default
|
|
813
|
+
default: (config as any)[selectedProvider]?.model
|
|
814
|
+
}
|
|
815
|
+
]);
|
|
816
|
+
|
|
817
|
+
if (model === 'Other...') {
|
|
818
|
+
const { customModel } = await inquirer.prompt([{
|
|
819
|
+
type: 'input',
|
|
820
|
+
name: 'customModel',
|
|
821
|
+
message: 'Enter model name:'
|
|
822
|
+
}]);
|
|
823
|
+
model = customModel;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Check for missing API Key (except for Ollama)
|
|
827
|
+
let newApiKey = undefined;
|
|
828
|
+
const currentKey = (config as any)[selectedProvider]?.apiKey;
|
|
829
|
+
|
|
830
|
+
if (selectedProvider !== 'ollama' && !currentKey) {
|
|
831
|
+
console.log(chalk.yellow(`\n⚠️ No API Key found for ${provider}.`));
|
|
832
|
+
const { apiKey } = await inquirer.prompt([{
|
|
833
|
+
type: 'password',
|
|
834
|
+
name: 'apiKey',
|
|
835
|
+
message: `Enter API Key for ${provider} (or leave empty to skip):`,
|
|
836
|
+
mask: '*'
|
|
837
|
+
}]);
|
|
838
|
+
if (apiKey && apiKey.trim()) {
|
|
839
|
+
newApiKey = apiKey.trim();
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
const updates: any = {};
|
|
844
|
+
updates.defaultProvider = selectedProvider;
|
|
845
|
+
updates[selectedProvider] = {
|
|
846
|
+
...((config as any)[selectedProvider] || {}),
|
|
847
|
+
model: model
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
if (newApiKey) {
|
|
851
|
+
updates[selectedProvider].apiKey = newApiKey;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
this.configManager.updateConfig(updates);
|
|
855
|
+
this.initializeClient();
|
|
856
|
+
console.log(chalk.green(`\nSwitched to ${chalk.bold(provider)} (${model})!`));
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
private async handleConnectCommand(args: string[]) {
|
|
860
|
+
if (args.length < 1) {
|
|
861
|
+
console.log(chalk.red('Usage: /connect <provider> [key_or_url]'));
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const provider = args[0].toLowerCase();
|
|
866
|
+
const value = args[1]; // Optional for ollama (defaults), required for others usually
|
|
867
|
+
|
|
868
|
+
const config = this.configManager.getConfig();
|
|
869
|
+
|
|
870
|
+
if (provider === 'gemini') {
|
|
871
|
+
if (!value) {
|
|
872
|
+
console.log(chalk.red('Error: API Key required for Gemini. usage: /connect gemini <api_key>'));
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
this.configManager.updateConfig({
|
|
876
|
+
gemini: { ...config.gemini, apiKey: value },
|
|
877
|
+
defaultProvider: 'gemini'
|
|
878
|
+
});
|
|
879
|
+
console.log(chalk.green(`Connected to Gemini with key: ${value.substring(0, 8)}...`));
|
|
880
|
+
} else if (provider === 'ollama') {
|
|
881
|
+
const url = value || 'http://localhost:11434/v1';
|
|
882
|
+
this.configManager.updateConfig({
|
|
883
|
+
ollama: { ...config.ollama, baseUrl: url },
|
|
884
|
+
defaultProvider: 'ollama'
|
|
885
|
+
});
|
|
886
|
+
console.log(chalk.green(`Connected to Ollama at ${url}`));
|
|
887
|
+
} else if (provider === 'openai') { // Support OpenAI since client supports it
|
|
888
|
+
if (!value) {
|
|
889
|
+
console.log(chalk.red('Error: API Key required for OpenAI. usage: /connect openai <api_key>'));
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
this.configManager.updateConfig({
|
|
893
|
+
openai: { ...config.openai, apiKey: value },
|
|
894
|
+
defaultProvider: 'openai' // We might need to handle 'openai' in initializeClient if we add it officially
|
|
895
|
+
});
|
|
896
|
+
console.log(chalk.green(`Connected to OpenAI.`));
|
|
897
|
+
} else if (provider === 'glm') {
|
|
898
|
+
if (!value) {
|
|
899
|
+
console.log(chalk.red('Error: API Key required for GLM. usage: /connect glm <api_key>'));
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
this.configManager.updateConfig({
|
|
903
|
+
glm: { ...config.glm, apiKey: value },
|
|
904
|
+
defaultProvider: 'glm'
|
|
905
|
+
});
|
|
906
|
+
console.log(chalk.green(`Connected to GLM (ZhipuAI).`));
|
|
907
|
+
} else {
|
|
908
|
+
console.log(chalk.red(`Unknown provider: ${provider}. Use 'gemini', 'ollama', 'openai', or 'glm'.`));
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
this.initializeClient();
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
private async handleUseCommand(args: string[]) {
|
|
916
|
+
if (args.length < 1) {
|
|
917
|
+
console.log(chalk.red('Usage: /use <provider> [model_name]'));
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
const provider = args[0].toLowerCase();
|
|
922
|
+
const model = args[1]; // Optional
|
|
923
|
+
|
|
924
|
+
const config = this.configManager.getConfig();
|
|
925
|
+
|
|
926
|
+
if (provider === 'gemini') {
|
|
927
|
+
const updates: any = { defaultProvider: 'gemini' };
|
|
928
|
+
if (model) {
|
|
929
|
+
updates.gemini = { ...config.gemini, model: model };
|
|
930
|
+
}
|
|
931
|
+
this.configManager.updateConfig(updates);
|
|
932
|
+
} else if (provider === 'ollama') {
|
|
933
|
+
const updates: any = { defaultProvider: 'ollama' };
|
|
934
|
+
if (model) {
|
|
935
|
+
updates.ollama = { ...config.ollama, model: model };
|
|
936
|
+
}
|
|
937
|
+
this.configManager.updateConfig(updates);
|
|
938
|
+
} else if (provider === 'glm') {
|
|
939
|
+
const updates: any = { defaultProvider: 'glm' };
|
|
940
|
+
if (model) {
|
|
941
|
+
updates.glm = { ...config.glm, model: model };
|
|
942
|
+
}
|
|
943
|
+
this.configManager.updateConfig(updates);
|
|
944
|
+
|
|
945
|
+
// Auto switch if connecting to a new provider
|
|
946
|
+
if ((provider as string) === 'gemini') {
|
|
947
|
+
updates.defaultProvider = 'gemini';
|
|
948
|
+
this.configManager.updateConfig(updates);
|
|
949
|
+
} else if ((provider as string) === 'ollama') {
|
|
950
|
+
updates.defaultProvider = 'ollama';
|
|
951
|
+
this.configManager.updateConfig(updates);
|
|
952
|
+
}
|
|
953
|
+
} else {
|
|
954
|
+
console.log(chalk.red(`Unknown provider: ${provider}`));
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
this.initializeClient();
|
|
959
|
+
console.log(chalk.green(`Switched to ${provider} ${model ? `using model ${model}` : ''}`));
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
private async handleMcpCommand(args: string[]) {
|
|
963
|
+
if (args.length === 0) {
|
|
964
|
+
console.log(chalk.red('Usage: /mcp <list|connect|disconnect|add|remove|test|config> [args]'));
|
|
965
|
+
console.log(chalk.dim('\nExamples:'));
|
|
966
|
+
console.log(chalk.dim(' /mcp list - List all MCP servers'));
|
|
967
|
+
console.log(chalk.dim(' /mcp connect Exa\\ Search - Connect to Exa Search'));
|
|
968
|
+
console.log(chalk.dim(' /mcp disconnect all - Disconnect all servers'));
|
|
969
|
+
console.log(chalk.dim(' /mcp add MyServer npx -y @my/mcp-server'));
|
|
970
|
+
console.log(chalk.dim(' /mcp test Exa\\ Search - Test connection'));
|
|
971
|
+
console.log(chalk.dim(' /mcp config - Show configuration'));
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const action = args[0];
|
|
976
|
+
|
|
977
|
+
switch (action) {
|
|
978
|
+
case 'list':
|
|
979
|
+
await this.mcpManager.listServers();
|
|
980
|
+
break;
|
|
981
|
+
|
|
982
|
+
case 'connect':
|
|
983
|
+
if (args.length < 2) {
|
|
984
|
+
// Show interactive list of available servers
|
|
985
|
+
const availableServers = this.mcpManager.getAvailableServers();
|
|
986
|
+
if (availableServers.length === 0) {
|
|
987
|
+
console.log(chalk.yellow('No MCP servers configured. Use /mcp add to add one.'));
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
const { serverName } = await inquirer.prompt([{
|
|
992
|
+
type: 'list',
|
|
993
|
+
name: 'serverName',
|
|
994
|
+
message: 'Select server to connect:',
|
|
995
|
+
choices: availableServers.map(s => s.name)
|
|
996
|
+
}]);
|
|
997
|
+
|
|
998
|
+
await this.mcpManager.connectToServer(serverName);
|
|
999
|
+
this.refreshToolsFromMcp();
|
|
1000
|
+
} else {
|
|
1001
|
+
const serverName = args.slice(1).join(' ');
|
|
1002
|
+
await this.mcpManager.connectToServer(serverName);
|
|
1003
|
+
this.refreshToolsFromMcp();
|
|
1004
|
+
}
|
|
1005
|
+
break;
|
|
1006
|
+
|
|
1007
|
+
case 'disconnect':
|
|
1008
|
+
if (args.length < 2) {
|
|
1009
|
+
// Interactive disconnect
|
|
1010
|
+
const connectedServers = this.mcpManager.getServerNames();
|
|
1011
|
+
if (connectedServers.length === 0) {
|
|
1012
|
+
console.log(chalk.yellow('No MCP connections to disconnect.'));
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
const { serverName } = await inquirer.prompt([{
|
|
1017
|
+
type: 'list',
|
|
1018
|
+
name: 'serverName',
|
|
1019
|
+
message: 'Select server to disconnect:',
|
|
1020
|
+
choices: [...connectedServers, 'all']
|
|
1021
|
+
}]);
|
|
1022
|
+
|
|
1023
|
+
if (serverName === 'all') {
|
|
1024
|
+
this.mcpManager.disconnectAll();
|
|
1025
|
+
this.refreshToolsFromMcp();
|
|
1026
|
+
} else {
|
|
1027
|
+
await this.mcpManager.disconnectFromServer(serverName);
|
|
1028
|
+
this.refreshToolsFromMcp();
|
|
1029
|
+
}
|
|
1030
|
+
} else {
|
|
1031
|
+
const serverName = args.slice(1).join(' ');
|
|
1032
|
+
if (serverName === 'all') {
|
|
1033
|
+
this.mcpManager.disconnectAll();
|
|
1034
|
+
this.refreshToolsFromMcp();
|
|
1035
|
+
} else {
|
|
1036
|
+
await this.mcpManager.disconnectFromServer(serverName);
|
|
1037
|
+
this.refreshToolsFromMcp();
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
break;
|
|
1041
|
+
|
|
1042
|
+
case 'add':
|
|
1043
|
+
if (args.length < 3) {
|
|
1044
|
+
console.log(chalk.red('Usage: /mcp add <name> <command> [args...]'));
|
|
1045
|
+
console.log(chalk.dim('\nExamples:'));
|
|
1046
|
+
console.log(chalk.dim(' /mcp add "My Server" npx -y @my/mcp-server'));
|
|
1047
|
+
console.log(chalk.dim(' /mcp add "Local Server" node /path/to/server.js'));
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const name = args[1];
|
|
1052
|
+
const command = args[2];
|
|
1053
|
+
const mcpArgs = args.slice(3);
|
|
1054
|
+
|
|
1055
|
+
const { description } = await inquirer.prompt([{
|
|
1056
|
+
type: 'input',
|
|
1057
|
+
name: 'description',
|
|
1058
|
+
message: 'Description (optional):',
|
|
1059
|
+
default: ''
|
|
1060
|
+
}]);
|
|
1061
|
+
|
|
1062
|
+
await this.mcpManager.addServer(name, command, mcpArgs, description);
|
|
1063
|
+
break;
|
|
1064
|
+
|
|
1065
|
+
case 'remove':
|
|
1066
|
+
if (args.length < 2) {
|
|
1067
|
+
// Interactive remove
|
|
1068
|
+
const availableServers = this.mcpManager.getAvailableServers();
|
|
1069
|
+
if (availableServers.length === 0) {
|
|
1070
|
+
console.log(chalk.yellow('No MCP servers configured.'));
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
const { serverName } = await inquirer.prompt([{
|
|
1075
|
+
type: 'list',
|
|
1076
|
+
name: 'serverName',
|
|
1077
|
+
message: 'Select server to remove:',
|
|
1078
|
+
choices: availableServers.map(s => s.name)
|
|
1079
|
+
}]);
|
|
1080
|
+
|
|
1081
|
+
await this.mcpManager.removeServer(serverName);
|
|
1082
|
+
} else {
|
|
1083
|
+
const serverName = args.slice(1).join(' ');
|
|
1084
|
+
await this.mcpManager.removeServer(serverName);
|
|
1085
|
+
}
|
|
1086
|
+
break;
|
|
1087
|
+
|
|
1088
|
+
case 'test':
|
|
1089
|
+
if (args.length < 2) {
|
|
1090
|
+
// Interactive test
|
|
1091
|
+
const connectedServers = this.mcpManager.getServerNames();
|
|
1092
|
+
if (connectedServers.length === 0) {
|
|
1093
|
+
console.log(chalk.yellow('No MCP connections to test.'));
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
const { serverName } = await inquirer.prompt([{
|
|
1098
|
+
type: 'list',
|
|
1099
|
+
name: 'serverName',
|
|
1100
|
+
message: 'Select server to test:',
|
|
1101
|
+
choices: connectedServers
|
|
1102
|
+
}]);
|
|
1103
|
+
|
|
1104
|
+
await this.mcpManager.testConnection(serverName);
|
|
1105
|
+
} else {
|
|
1106
|
+
const serverName = args.slice(1).join(' ');
|
|
1107
|
+
await this.mcpManager.testConnection(serverName);
|
|
1108
|
+
}
|
|
1109
|
+
break;
|
|
1110
|
+
|
|
1111
|
+
case 'config':
|
|
1112
|
+
const config = this.mcpManager.getConfig().getConfig();
|
|
1113
|
+
console.log(chalk.cyan('\nMCP Configuration:\n'));
|
|
1114
|
+
console.log(JSON.stringify(config, null, 2));
|
|
1115
|
+
console.log(chalk.dim(`\nConfig file: ${require('os').homedir()}/.mentis/mcp.json`));
|
|
1116
|
+
break;
|
|
1117
|
+
|
|
1118
|
+
default:
|
|
1119
|
+
console.log(chalk.red(`Unknown MCP action: ${action}`));
|
|
1120
|
+
console.log(chalk.yellow('Available actions: list, connect, disconnect, add, remove, test, config'));
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
private async handleResumeCommand() {
|
|
1125
|
+
const cwd = process.cwd();
|
|
1126
|
+
const localSessions = this.checkpointManager.listLocalSessions(cwd);
|
|
1127
|
+
const globalCheckpoints = this.checkpointManager.list();
|
|
1128
|
+
|
|
1129
|
+
if (localSessions.length === 0 && globalCheckpoints.length === 0) {
|
|
1130
|
+
console.log(chalk.yellow('No previous sessions found to resume.'));
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// Header like Claude's "Resume Session"
|
|
1135
|
+
console.log('');
|
|
1136
|
+
console.log(chalk.cyan.bold('Resume Session'));
|
|
1137
|
+
console.log('');
|
|
1138
|
+
|
|
1139
|
+
// Helper for relative time
|
|
1140
|
+
const relativeTime = (ts: number) => {
|
|
1141
|
+
const diff = Date.now() - ts;
|
|
1142
|
+
const mins = Math.floor(diff / 60000);
|
|
1143
|
+
const hours = Math.floor(diff / 3600000);
|
|
1144
|
+
const days = Math.floor(diff / 86400000);
|
|
1145
|
+
if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`;
|
|
1146
|
+
if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
|
|
1147
|
+
if (mins > 0) return `${mins} min${mins > 1 ? 's' : ''} ago`;
|
|
1148
|
+
return 'just now';
|
|
1149
|
+
};
|
|
1150
|
+
|
|
1151
|
+
// Build choices with simple single-line formatting
|
|
1152
|
+
const choices: Array<{ name: string; value: { type: string; id: string } }> = [];
|
|
1153
|
+
|
|
1154
|
+
// Local sessions first
|
|
1155
|
+
for (const session of localSessions) {
|
|
1156
|
+
const timeAgo = relativeTime(session.timestamp);
|
|
1157
|
+
const shortPreview = session.preview.substring(0, 30).replace(/\n/g, ' ');
|
|
1158
|
+
choices.push({
|
|
1159
|
+
name: `${shortPreview}... (${timeAgo}, ${session.messageCount} msgs)`,
|
|
1160
|
+
value: { type: 'local', id: session.id }
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// Global checkpoints as fallback
|
|
1165
|
+
if (localSessions.length === 0 && globalCheckpoints.length > 0) {
|
|
1166
|
+
for (const name of globalCheckpoints) {
|
|
1167
|
+
const cp = this.checkpointManager.load(name);
|
|
1168
|
+
const timeAgo = cp ? relativeTime(cp.timestamp) : '';
|
|
1169
|
+
const msgCount = cp?.history?.length || 0;
|
|
1170
|
+
const preview = cp?.history?.find(m => m.role === 'user')?.content?.substring(0, 40)?.replace(/\n/g, ' ') || name;
|
|
1171
|
+
choices.push({
|
|
1172
|
+
name: `${preview}${preview.length >= 40 ? '...' : ''} — ${timeAgo} · ${msgCount} msgs`,
|
|
1173
|
+
value: { type: 'global', id: name }
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// Show hint
|
|
1179
|
+
console.log(chalk.dim(' ↑/↓ to navigate · Enter to select · Esc to cancel'));
|
|
1180
|
+
console.log('');
|
|
1181
|
+
|
|
1182
|
+
// Guard against empty choices (would crash inquirer)
|
|
1183
|
+
if (choices.length === 0) {
|
|
1184
|
+
console.log(chalk.yellow('No sessions available. Start a conversation and /exit to save.'));
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
const { selected } = await inquirer.prompt([{
|
|
1189
|
+
type: 'rawlist',
|
|
1190
|
+
name: 'selected',
|
|
1191
|
+
message: 'Pick a session:',
|
|
1192
|
+
choices,
|
|
1193
|
+
pageSize: 10
|
|
1194
|
+
}]);
|
|
1195
|
+
|
|
1196
|
+
if (selected.type === 'local') {
|
|
1197
|
+
await this.loadLocalCheckpoint(cwd, selected.id);
|
|
1198
|
+
} else if (selected.type === 'global') {
|
|
1199
|
+
await this.loadCheckpoint(selected.id);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
private async loadLocalCheckpoint(cwd: string, sessionId?: string) {
|
|
1204
|
+
const cp = this.checkpointManager.loadLocalSession(cwd, sessionId);
|
|
1205
|
+
if (!cp) {
|
|
1206
|
+
console.log(chalk.red('Session not found.'));
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
this.history = cp.history;
|
|
1211
|
+
this.contextManager.clear();
|
|
1212
|
+
|
|
1213
|
+
// Restore context files
|
|
1214
|
+
if (cp.files && cp.files.length > 0) {
|
|
1215
|
+
console.log(chalk.dim('Restoring context files...'));
|
|
1216
|
+
for (const file of cp.files) {
|
|
1217
|
+
await this.contextManager.addFile(file);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
console.log(chalk.green(`✓ Resumed session (${new Date(cp.timestamp).toLocaleString()})`));
|
|
1221
|
+
console.log(chalk.dim(` Messages: ${this.history.length}`));
|
|
1222
|
+
|
|
1223
|
+
// Re-display last assistant message if any
|
|
1224
|
+
const lastMsg = this.history[this.history.length - 1];
|
|
1225
|
+
if (lastMsg && lastMsg.role === 'assistant' && lastMsg.content) {
|
|
1226
|
+
console.log(chalk.blue('\nLast response:'));
|
|
1227
|
+
const preview = lastMsg.content.length > 200
|
|
1228
|
+
? lastMsg.content.substring(0, 200) + '...'
|
|
1229
|
+
: lastMsg.content;
|
|
1230
|
+
console.log(chalk.dim(preview));
|
|
1231
|
+
}
|
|
1232
|
+
console.log('');
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
private async loadCheckpoint(name: string) {
|
|
1236
|
+
const cp = this.checkpointManager.load(name);
|
|
1237
|
+
if (!cp) {
|
|
1238
|
+
console.log(chalk.red(`Checkpoint '${name}' not found.`));
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
this.history = cp.history;
|
|
1243
|
+
this.contextManager.clear();
|
|
1244
|
+
|
|
1245
|
+
// Restore context files
|
|
1246
|
+
if (cp.files && cp.files.length > 0) {
|
|
1247
|
+
console.log(chalk.dim('Restoring context files...'));
|
|
1248
|
+
for (const file of cp.files) {
|
|
1249
|
+
await this.contextManager.addFile(file);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
console.log(chalk.green(`Resumed session '${name}' (${new Date(cp.timestamp).toLocaleString()})`));
|
|
1253
|
+
// Re-display last assistant message if any
|
|
1254
|
+
const lastMsg = this.history[this.history.length - 1];
|
|
1255
|
+
if (lastMsg && lastMsg.role === 'assistant' && lastMsg.content) {
|
|
1256
|
+
console.log(chalk.blue('\nLast message:'));
|
|
1257
|
+
console.log(lastMsg.content);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
private async handleSkillsCommand(args: string[]) {
|
|
1262
|
+
const { SkillCreator, validateSkills } = await import('../skills/SkillCreator');
|
|
1263
|
+
|
|
1264
|
+
if (args.length < 1) {
|
|
1265
|
+
// Show skills list by default
|
|
1266
|
+
await this.handleSkillsCommand(['list']);
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
const action = args[0];
|
|
1271
|
+
|
|
1272
|
+
switch (action) {
|
|
1273
|
+
case 'list':
|
|
1274
|
+
await this.handleSkillsList();
|
|
1275
|
+
break;
|
|
1276
|
+
case 'show':
|
|
1277
|
+
if (args.length < 2) {
|
|
1278
|
+
console.log(chalk.red('Usage: /skills show <name>'));
|
|
1279
|
+
return;
|
|
1280
|
+
}
|
|
1281
|
+
await this.handleSkillsShow(args[1]);
|
|
1282
|
+
break;
|
|
1283
|
+
case 'create':
|
|
1284
|
+
const creator = new SkillCreator(this.skillsManager);
|
|
1285
|
+
await creator.run(args[1]);
|
|
1286
|
+
// Re-discover skills after creation
|
|
1287
|
+
await this.skillsManager.discoverSkills();
|
|
1288
|
+
break;
|
|
1289
|
+
case 'validate':
|
|
1290
|
+
await validateSkills(this.skillsManager);
|
|
1291
|
+
break;
|
|
1292
|
+
default:
|
|
1293
|
+
console.log(chalk.red(`Unknown skills action: ${action}`));
|
|
1294
|
+
console.log(chalk.yellow('Available actions: list, show, create, validate'));
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
private async handleSkillsList(): Promise<void> {
|
|
1299
|
+
const skills = this.skillsManager.getAllSkills();
|
|
1300
|
+
|
|
1301
|
+
if (skills.length === 0) {
|
|
1302
|
+
console.log(chalk.yellow('No skills available.'));
|
|
1303
|
+
console.log(chalk.dim('Create skills with: /skills create'));
|
|
1304
|
+
console.log(chalk.dim('Add skills to: ~/.mentis/skills/ or .mentis/skills/'));
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
console.log(chalk.cyan(`\nAvailable Skills (${skills.length}):\n`));
|
|
1309
|
+
|
|
1310
|
+
for (const skill of skills) {
|
|
1311
|
+
const statusIcon = skill.isValid ? '✓' : '✗';
|
|
1312
|
+
const typeLabel = skill.type === 'personal' ? 'Personal' : 'Project';
|
|
1313
|
+
|
|
1314
|
+
console.log(`${statusIcon} ${chalk.bold(skill.name)} (${typeLabel})`);
|
|
1315
|
+
console.log(` ${skill.description}`);
|
|
1316
|
+
|
|
1317
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
1318
|
+
console.log(chalk.dim(` Allowed tools: ${skill.allowedTools.join(', ')}`));
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
if (!skill.isValid && skill.errors) {
|
|
1322
|
+
console.log(chalk.red(` Errors: ${skill.errors.join(', ')}`));
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
console.log('');
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
private async handleSkillsShow(name: string): Promise<void> {
|
|
1330
|
+
const skill = await this.skillsManager.loadFullSkill(name);
|
|
1331
|
+
|
|
1332
|
+
if (!skill) {
|
|
1333
|
+
console.log(chalk.red(`Skill "${name}" not found.`));
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
console.log(chalk.cyan(`\n# ${skill.name}\n`));
|
|
1338
|
+
console.log(chalk.dim(`Type: ${skill.type}`));
|
|
1339
|
+
console.log(chalk.dim(`Path: ${skill.path}`));
|
|
1340
|
+
|
|
1341
|
+
if (skill.allowedTools && skill.allowedTools.length > 0) {
|
|
1342
|
+
console.log(chalk.dim(`Allowed tools: ${skill.allowedTools.join(', ')}`));
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
console.log('');
|
|
1346
|
+
console.log(skill.content || 'No content available');
|
|
1347
|
+
|
|
1348
|
+
// List supporting files
|
|
1349
|
+
const files = this.skillsManager.listSkillFiles(name);
|
|
1350
|
+
if (files.length > 0) {
|
|
1351
|
+
console.log(chalk.dim(`\nSupporting files: ${files.join(', ')}`));
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
private async handleInitCommand(): Promise<void> {
|
|
1356
|
+
const initializer = new ProjectInitializer();
|
|
1357
|
+
await initializer.run();
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
private async handleCommandsCommand(args: string[]) {
|
|
1361
|
+
if (args.length < 1) {
|
|
1362
|
+
// Show commands list by default
|
|
1363
|
+
await this.handleCommandsCommand(['list']);
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
const action = args[0];
|
|
1368
|
+
|
|
1369
|
+
switch (action) {
|
|
1370
|
+
case 'list':
|
|
1371
|
+
await this.handleCommandsList();
|
|
1372
|
+
break;
|
|
1373
|
+
case 'create':
|
|
1374
|
+
await this.handleCommandsCreate(args[1]);
|
|
1375
|
+
break;
|
|
1376
|
+
case 'validate':
|
|
1377
|
+
await this.handleCommandsValidate();
|
|
1378
|
+
break;
|
|
1379
|
+
default:
|
|
1380
|
+
console.log(chalk.red(`Unknown commands action: ${action}`));
|
|
1381
|
+
console.log(chalk.yellow('Available actions: list, create, validate'));
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
private async handleCommandsList(): Promise<void> {
|
|
1386
|
+
const commands = this.commandManager.getAllCommands();
|
|
1387
|
+
|
|
1388
|
+
if (commands.length === 0) {
|
|
1389
|
+
console.log(chalk.yellow('No custom commands available.'));
|
|
1390
|
+
console.log(chalk.dim('Create commands with: /commands create'));
|
|
1391
|
+
console.log(chalk.dim('Add commands to: ~/.mentis/commands/ or .mentis/commands/'));
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
console.log(chalk.cyan(`\nCustom Commands (${commands.length}):\n`));
|
|
1396
|
+
|
|
1397
|
+
// Group by namespace
|
|
1398
|
+
const grouped = new Map<string, any[]>();
|
|
1399
|
+
for (const cmd of commands) {
|
|
1400
|
+
const ns = cmd.description.match(/\(([^)]+)\)/)?.[1] || cmd.type;
|
|
1401
|
+
if (!grouped.has(ns)) {
|
|
1402
|
+
grouped.set(ns, []);
|
|
1403
|
+
}
|
|
1404
|
+
grouped.get(ns)!.push(cmd);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
for (const [namespace, cmds] of grouped) {
|
|
1408
|
+
console.log(chalk.bold(`\n${namespace}`));
|
|
1409
|
+
for (const cmd of cmds) {
|
|
1410
|
+
const params = cmd.frontmatter['argument-hint'] ? ` ${cmd.frontmatter['argument-hint']}` : '';
|
|
1411
|
+
console.log(` /${cmd.name}${params}`);
|
|
1412
|
+
console.log(` ${cmd.description.replace(/\s*\([^)]+\)/, '')}`);
|
|
1413
|
+
|
|
1414
|
+
if (cmd.frontmatter['allowed-tools'] && cmd.frontmatter['allowed-tools'].length > 0) {
|
|
1415
|
+
console.log(chalk.dim(` Allowed tools: ${cmd.frontmatter['allowed-tools'].join(', ')}`));
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
console.log('');
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
private async handleCommandsCreate(name?: string): Promise<void> {
|
|
1423
|
+
const { CommandCreator } = await import('../commands/CommandCreator');
|
|
1424
|
+
const creator = new CommandCreator(this.commandManager);
|
|
1425
|
+
await creator.run(name);
|
|
1426
|
+
// Re-discover commands after creation
|
|
1427
|
+
await this.commandManager.discoverCommands();
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
private async handleCommandsValidate(): Promise<void> {
|
|
1431
|
+
const { validateCommands } = await import('../commands/CommandCreator');
|
|
1432
|
+
await validateCommands(this.commandManager);
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
/**
|
|
1436
|
+
* Handle write_file approval with diff preview
|
|
1437
|
+
*/
|
|
1438
|
+
private async handleWriteApproval(filePath: string, content: string): Promise<boolean> {
|
|
1439
|
+
// Check if file exists and show diff
|
|
1440
|
+
const fullPath = path.resolve(filePath);
|
|
1441
|
+
if (fs.existsSync(fullPath)) {
|
|
1442
|
+
const oldContent = fs.readFileSync(fullPath, 'utf-8');
|
|
1443
|
+
DiffViewer.showDiff(filePath, oldContent, content);
|
|
1444
|
+
} else {
|
|
1445
|
+
console.log('');
|
|
1446
|
+
console.log(chalk.cyan(`📄 Creating new file: ${filePath}`));
|
|
1447
|
+
console.log(chalk.dim('─'.repeat(60)));
|
|
1448
|
+
console.log(chalk.green('New file content:'));
|
|
1449
|
+
const preview = content.split('\n').slice(0, 10).join('\n');
|
|
1450
|
+
console.log(chalk.dim(preview));
|
|
1451
|
+
if (content.split('\n').length > 10) {
|
|
1452
|
+
console.log(chalk.dim('...'));
|
|
1453
|
+
}
|
|
1454
|
+
console.log(chalk.dim('─'.repeat(60)));
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
const { confirm } = await inquirer.prompt([
|
|
1458
|
+
{
|
|
1459
|
+
type: 'confirm',
|
|
1460
|
+
name: 'confirm',
|
|
1461
|
+
message: `Allow writing to ${chalk.yellow(filePath)}?`,
|
|
1462
|
+
default: true
|
|
1463
|
+
}
|
|
1464
|
+
]);
|
|
1465
|
+
|
|
1466
|
+
return confirm;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
/**
|
|
1470
|
+
* Handle edit_file approval with diff preview
|
|
1471
|
+
*/
|
|
1472
|
+
private async handleEditApproval(args: { file_path: string; old_string: string; new_string: string }): Promise<boolean> {
|
|
1473
|
+
// Get diff from EditFileTool (which shows preview)
|
|
1474
|
+
const editTool = this.tools.find(t => t.name === 'edit_file') as any;
|
|
1475
|
+
if (editTool) {
|
|
1476
|
+
const diffResult = await editTool.execute(args);
|
|
1477
|
+
console.log(diffResult);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
const { confirm } = await inquirer.prompt([
|
|
1481
|
+
{
|
|
1482
|
+
type: 'confirm',
|
|
1483
|
+
name: 'confirm',
|
|
1484
|
+
message: `Apply edit to ${chalk.yellow(args.file_path)}?`,
|
|
1485
|
+
default: true
|
|
1486
|
+
}
|
|
1487
|
+
]);
|
|
1488
|
+
|
|
1489
|
+
return confirm;
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
/**
|
|
1493
|
+
* Handle read_file approval (currently auto-approves, but can be enhanced)
|
|
1494
|
+
*/
|
|
1495
|
+
private async handleReadApproval(filePath: string): Promise<boolean> {
|
|
1496
|
+
// For now, auto-approve single file reads
|
|
1497
|
+
// In the future, could batch multiple reads into a single selector
|
|
1498
|
+
return true;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
private estimateCost(input: number, output: number): number {
|
|
1502
|
+
const config = this.configManager.getConfig();
|
|
1503
|
+
const provider = config.defaultProvider;
|
|
1504
|
+
|
|
1505
|
+
let rateIn = 0;
|
|
1506
|
+
let rateOut = 0;
|
|
1507
|
+
|
|
1508
|
+
if (provider === 'openai') {
|
|
1509
|
+
rateIn = 5.00 / 1000000;
|
|
1510
|
+
rateOut = 15.00 / 1000000;
|
|
1511
|
+
} else if (provider === 'gemini') {
|
|
1512
|
+
rateIn = 0.35 / 1000000;
|
|
1513
|
+
rateOut = 0.70 / 1000000;
|
|
1514
|
+
} else if (provider === 'glm') {
|
|
1515
|
+
rateIn = 14.00 / 1000000; // Approximate for GLM-4
|
|
1516
|
+
rateOut = 14.00 / 1000000;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
return (input * rateIn) + (output * rateOut);
|
|
1520
|
+
}
|
|
1521
|
+
}
|