@pheem49/mint 1.4.0 → 1.4.2
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/.codex +0 -0
- package/README.md +101 -148
- package/main.js +21 -1
- package/mint-cli-logic.js +23 -8
- package/mint-cli.js +223 -137
- package/package.json +1 -1
- package/src/AI_Brain/Gemini_API.js +38 -24
- package/src/AI_Brain/agent_orchestrator.js +6 -6
- package/src/AI_Brain/proactive_engine.js +2 -8
- package/src/Automation_Layer/file_operations.js +136 -6
- package/src/Automation_Layer/open_app.js +72 -43
- package/src/Automation_Layer/open_website.js +3 -3
- package/src/CLI/chat_router.js +70 -24
- package/src/CLI/chat_ui.js +197 -44
- package/src/CLI/code_agent.js +337 -93
- package/src/CLI/list_features.js +3 -1
- package/src/CLI/workspace_manager.js +15 -6
- package/src/Plugins/docker.js +12 -10
- package/src/System/config_manager.js +1 -1
- package/src/System/custom_workflows.js +9 -2
- package/tests/chat_router.test.js +42 -0
- package/tests/code_agent.test.js +69 -0
- package/tests/docker.test.js +46 -0
- package/tests/file_operations.test.js +57 -0
- package/tests/provider_routing.test.js +67 -0
- package/tests/workspace_manager.test.js +15 -0
package/src/CLI/chat_ui.js
CHANGED
|
@@ -32,7 +32,7 @@ const SLASH_COMMANDS = [
|
|
|
32
32
|
function createChatUI({ onSubmit, onExit }) {
|
|
33
33
|
const config = readConfig();
|
|
34
34
|
const modelName = config.geminiModel || 'gemini';
|
|
35
|
-
const
|
|
35
|
+
const workspacePath = process.cwd();
|
|
36
36
|
const HINT_DEFAULT = `{gray-fg} Enter send · Ctrl+Y copy · /help commands{/}`;
|
|
37
37
|
const INPUT_FG = '#f8fafc';
|
|
38
38
|
const INPUT_BG = '#10141c';
|
|
@@ -90,11 +90,10 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
90
90
|
style: { bg: 'default' }
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
-
// ─── Input area ───────────────────────────────────────────────────────────
|
|
94
93
|
const inputBox = blessed.textbox({
|
|
95
94
|
bottom: 3, left: 1, width: '100%-2', height: 3,
|
|
96
95
|
tags: false,
|
|
97
|
-
inputOnFocus:
|
|
96
|
+
inputOnFocus: false, // We'll manage this manually for stability
|
|
98
97
|
keys: true,
|
|
99
98
|
style: {
|
|
100
99
|
bg: INPUT_BG,
|
|
@@ -111,6 +110,16 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
111
110
|
label: ' Message '
|
|
112
111
|
});
|
|
113
112
|
|
|
113
|
+
// --- SAFETY PATCH ---
|
|
114
|
+
// Prevent "TypeError: done is not a function" if a listener survives a blur/focus cycle.
|
|
115
|
+
const originalListener = inputBox._listener;
|
|
116
|
+
inputBox._listener = function(ch, key) {
|
|
117
|
+
if (typeof this._done !== 'function') return;
|
|
118
|
+
return originalListener.call(this, ch, key);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
114
123
|
// ─── Placeholder (SIBLING widget floating over input content area) ─────────
|
|
115
124
|
// inputBox: bottom=3, height=3, border=1 → content row at bottom=4, left=2
|
|
116
125
|
const placeholderWidget = blessed.text({
|
|
@@ -169,7 +178,7 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
169
178
|
} catch (_) {}
|
|
170
179
|
}
|
|
171
180
|
|
|
172
|
-
// ─── Status bar (
|
|
181
|
+
// ─── Status bar (2 lines as per screenshot) ──────────────────────
|
|
173
182
|
const statusBar = blessed.box({
|
|
174
183
|
bottom: 0, left: 1, width: '100%-2', height: 3,
|
|
175
184
|
tags: true,
|
|
@@ -177,54 +186,62 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
177
186
|
border: { type: 'line', fg: '#222c38' }
|
|
178
187
|
});
|
|
179
188
|
|
|
180
|
-
//
|
|
181
|
-
const
|
|
189
|
+
// Line 1: Thinking / Status (Left) and Shortcut (Right)
|
|
190
|
+
const statusLine1 = blessed.text({
|
|
182
191
|
parent: statusBar,
|
|
183
|
-
top: 0, left: 1,
|
|
184
|
-
width: '33%',
|
|
192
|
+
top: 0, left: 1, right: 1,
|
|
185
193
|
height: 1,
|
|
186
194
|
tags: true,
|
|
187
|
-
content: `
|
|
188
|
-
style: { bg: '#10141c'
|
|
195
|
+
content: `{#88aaff-fg}[Chat]{/} {#cc4444-fg}no sandbox{/}`,
|
|
196
|
+
style: { bg: '#10141c' }
|
|
189
197
|
});
|
|
190
198
|
|
|
191
|
-
|
|
192
|
-
const statusCenter = blessed.text({
|
|
199
|
+
const shortcutHint = blessed.text({
|
|
193
200
|
parent: statusBar,
|
|
194
|
-
top: 0,
|
|
195
|
-
left: 'center',
|
|
196
|
-
width: '44%',
|
|
201
|
+
top: 0, right: 1,
|
|
197
202
|
height: 1,
|
|
198
|
-
align: 'center',
|
|
199
203
|
tags: true,
|
|
200
|
-
content: `{
|
|
201
|
-
style: { bg: '#10141c'
|
|
204
|
+
content: `{gray-fg}? for shortcuts{/}`,
|
|
205
|
+
style: { bg: '#10141c' }
|
|
202
206
|
});
|
|
203
207
|
|
|
204
|
-
//
|
|
205
|
-
const
|
|
208
|
+
// Line 2: Action Hint (Left) and File Info (Right)
|
|
209
|
+
const statusLine2 = blessed.text({
|
|
206
210
|
parent: statusBar,
|
|
207
|
-
top:
|
|
208
|
-
width: '33%',
|
|
211
|
+
top: 1, left: 1, right: 1,
|
|
209
212
|
height: 1,
|
|
210
|
-
align: 'right',
|
|
211
213
|
tags: true,
|
|
212
|
-
content: `{
|
|
213
|
-
style: { bg: '#10141c'
|
|
214
|
+
content: `{gray-fg}Shift+Tab to accept edits{/}`,
|
|
215
|
+
style: { bg: '#10141c' }
|
|
214
216
|
});
|
|
215
217
|
|
|
216
|
-
|
|
218
|
+
const fileInfo = blessed.text({
|
|
219
|
+
parent: statusBar,
|
|
220
|
+
top: 1, right: 1,
|
|
221
|
+
height: 1,
|
|
222
|
+
tags: true,
|
|
223
|
+
content: `{gray-fg}path: ${workspacePath}{/}`,
|
|
224
|
+
style: { bg: '#10141c' }
|
|
225
|
+
});
|
|
217
226
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
227
|
+
let activeMode = 'Chat';
|
|
228
|
+
let spinnerIdx = 0;
|
|
229
|
+
const spinnerChars = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
230
|
+
|
|
231
|
+
function formatTime(seconds) {
|
|
232
|
+
if (seconds < 60) return `${seconds}s`;
|
|
233
|
+
const mins = Math.floor(seconds / 60);
|
|
234
|
+
const secs = seconds % 60;
|
|
235
|
+
return `${mins}m ${secs}s`;
|
|
221
236
|
}
|
|
222
237
|
|
|
223
|
-
function updateStatusBar(thinkingText = null) {
|
|
238
|
+
function updateStatusBar(thinkingText = null, secondsElapsed = 0) {
|
|
224
239
|
if (thinkingText) {
|
|
225
|
-
|
|
240
|
+
const char = spinnerChars[spinnerIdx % spinnerChars.length];
|
|
241
|
+
spinnerIdx++;
|
|
242
|
+
statusLine1.setContent(`{#88e0b0-fg}${char}{/} Thinking... {gray-fg}(esc to cancel, ${formatTime(secondsElapsed)}){/}`);
|
|
226
243
|
} else {
|
|
227
|
-
|
|
244
|
+
statusLine1.setContent(`${activeMode === 'Code' ? '{#ffd166-fg}[Code]{/}' : '{#88aaff-fg}[Chat]{/}'} {#cc4444-fg}no sandbox{/}`);
|
|
228
245
|
}
|
|
229
246
|
screen.render();
|
|
230
247
|
}
|
|
@@ -234,9 +251,16 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
234
251
|
updateStatusBar(null);
|
|
235
252
|
}
|
|
236
253
|
|
|
237
|
-
/** Update model name in status bar (called after /models switch) */
|
|
238
254
|
function updateStatusModel(newModel) {
|
|
239
|
-
|
|
255
|
+
if (!newModel) return;
|
|
256
|
+
shortcutHint.setContent(`{gray-fg}${newModel} · ? for shortcuts{/}`);
|
|
257
|
+
screen.render();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Update workspace name in status bar */
|
|
261
|
+
function updateWorkspace(newPath) {
|
|
262
|
+
if (!newPath) return;
|
|
263
|
+
fileInfo.setContent(`{gray-fg}path: ${newPath}{/}`);
|
|
240
264
|
screen.render();
|
|
241
265
|
}
|
|
242
266
|
updateStatusBar();
|
|
@@ -250,6 +274,8 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
250
274
|
screen.append(statusBar);
|
|
251
275
|
screen.append(placeholderWidget); // sibling on top of inputBox
|
|
252
276
|
|
|
277
|
+
// Suggestion List and Approval Dialog remain same ...
|
|
278
|
+
|
|
253
279
|
// ─── Suggestion List ──────────────────────────────────────────────────────
|
|
254
280
|
const commandList = blessed.list({
|
|
255
281
|
parent: screen,
|
|
@@ -284,7 +310,7 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
284
310
|
border: { fg: '#88e0b0' }
|
|
285
311
|
},
|
|
286
312
|
width: '80%',
|
|
287
|
-
height: 'shrink'
|
|
313
|
+
height: 12, // Fixed height to avoid 'shrink' miscalculation with buttons
|
|
288
314
|
top: 'center',
|
|
289
315
|
left: 'center',
|
|
290
316
|
label: ' Approval ',
|
|
@@ -379,28 +405,43 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
379
405
|
applyTerminalInputAttrs();
|
|
380
406
|
});
|
|
381
407
|
|
|
408
|
+
// Restore focus to inputBox when clicked or when screen is clicked
|
|
409
|
+
screen.on('click', () => {
|
|
410
|
+
if (!approvalDialog.visible) {
|
|
411
|
+
inputBox.focus();
|
|
412
|
+
screen.render();
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
inputBox.on('click', () => {
|
|
417
|
+
inputBox.focus();
|
|
418
|
+
screen.render();
|
|
419
|
+
});
|
|
420
|
+
|
|
382
421
|
|
|
383
422
|
// Submit or Select Suggestion on Enter
|
|
384
|
-
inputBox.
|
|
423
|
+
inputBox.on('submit', (value) => {
|
|
385
424
|
if (!commandList.hidden) {
|
|
386
425
|
const selected = activeSuggestions[commandList.selected];
|
|
387
426
|
if (selected) {
|
|
388
427
|
inputBox.setValue(selected.name + ' ');
|
|
389
428
|
commandList.hide();
|
|
390
429
|
hidePlaceholder();
|
|
391
|
-
inputBox.focus();
|
|
430
|
+
inputBox.focus();
|
|
431
|
+
inputBox.readInput(); // Re-focus to continue typing
|
|
392
432
|
refreshInputStyles();
|
|
393
433
|
screen.render();
|
|
394
434
|
return; // Don't submit yet, let user add args or press enter again
|
|
395
435
|
}
|
|
396
436
|
}
|
|
397
437
|
|
|
398
|
-
const raw =
|
|
438
|
+
const raw = value || '';
|
|
399
439
|
const text = raw.trim();
|
|
400
440
|
if (!text) {
|
|
401
441
|
inputBox.clearValue();
|
|
402
442
|
showPlaceholder();
|
|
403
|
-
inputBox.focus();
|
|
443
|
+
inputBox.focus();
|
|
444
|
+
inputBox.readInput(); // Re-focus to continue typing
|
|
404
445
|
refreshInputStyles();
|
|
405
446
|
screen.render();
|
|
406
447
|
return;
|
|
@@ -409,7 +450,8 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
409
450
|
// Clear input and restore placeholder
|
|
410
451
|
inputBox.clearValue();
|
|
411
452
|
showPlaceholder();
|
|
412
|
-
inputBox.focus();
|
|
453
|
+
inputBox.focus();
|
|
454
|
+
inputBox.readInput(); // Explicitly restart reading
|
|
413
455
|
refreshInputStyles();
|
|
414
456
|
screen.render();
|
|
415
457
|
|
|
@@ -486,6 +528,7 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
486
528
|
|
|
487
529
|
// ─── Initial render ───────────────────────────────────────────────────────
|
|
488
530
|
inputBox.focus();
|
|
531
|
+
inputBox.readInput(); // Initial start
|
|
489
532
|
refreshInputStyles();
|
|
490
533
|
screen.render();
|
|
491
534
|
|
|
@@ -670,10 +713,72 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
670
713
|
return { appendChunk, finalize };
|
|
671
714
|
}
|
|
672
715
|
|
|
716
|
+
function appendCodeStep(info) {
|
|
717
|
+
if (typeof info === 'string') {
|
|
718
|
+
appendMessage('system', `[Code] ${info}`);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const { step, phase, action, target, message, thought } = info;
|
|
723
|
+
const maxLineWidth = Math.max(screen.width - 20, 36);
|
|
724
|
+
|
|
725
|
+
// Special handling for ask_user which needs a box style
|
|
726
|
+
if (action === 'ask_user') {
|
|
727
|
+
chatBox.log('');
|
|
728
|
+
chatBox.log(` {#88e0b0-fg}✓{/} {bold}Ask User{/}`);
|
|
729
|
+
const questionLines = wrapText(target || message || '', maxLineWidth - 6);
|
|
730
|
+
questionLines.forEach(l => chatBox.log(` {#95a2b8-fg}${l}{/}`));
|
|
731
|
+
screen.render();
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
let icon = '{#88e0b0-fg}✓{/}';
|
|
736
|
+
let label = action || phase;
|
|
737
|
+
let color = '{#ffffff-fg}';
|
|
738
|
+
|
|
739
|
+
// Map internal action names to display names seen in the screenshot
|
|
740
|
+
switch (action) {
|
|
741
|
+
case 'thinking':
|
|
742
|
+
if (phase === 'thinking' && !thought) {
|
|
743
|
+
// Initial "Thinking..." without a bubble
|
|
744
|
+
chatBox.log('');
|
|
745
|
+
chatBox.log(` {#ffd166-fg}* {bold}Thinking{/}`);
|
|
746
|
+
} else if (thought) {
|
|
747
|
+
// Show reasoning bubble
|
|
748
|
+
const thoughtLines = wrapText(thought, maxLineWidth - 6);
|
|
749
|
+
thoughtLines.forEach(l => chatBox.log(` {gray-fg}> ${l}{/}`));
|
|
750
|
+
}
|
|
751
|
+
screen.render();
|
|
752
|
+
return;
|
|
753
|
+
case 'ask_user': label = 'AskUser'; break;
|
|
754
|
+
case 'open_url': label = 'OpenURL'; break;
|
|
755
|
+
case 'open_app': label = 'OpenApp'; break;
|
|
756
|
+
case 'open_file': label = 'OpenFile'; break;
|
|
757
|
+
case 'open_folder': label = 'OpenFolder'; break;
|
|
758
|
+
case 'create_folder': label = 'CreateFolder'; break;
|
|
759
|
+
case 'system_info': label = 'SystemInfo'; break;
|
|
760
|
+
case 'system_automation': label = 'SystemAction'; break;
|
|
761
|
+
case 'web_search': label = 'WebSearch'; break;
|
|
762
|
+
case 'list_files':
|
|
763
|
+
case 'find_path': label = 'Explored'; break;
|
|
764
|
+
case 'read_file': label = 'ReadFile'; break;
|
|
765
|
+
case 'search_code': label = 'SearchText'; break;
|
|
766
|
+
case 'apply_patch':
|
|
767
|
+
case 'write_file': label = 'Edited'; break;
|
|
768
|
+
case 'run_shell': label = 'Ran command'; break;
|
|
769
|
+
case 'json_repair': icon = '*'; label = 'Repairing JSON'; color = '{#ffd166-fg}'; break;
|
|
770
|
+
case 'reviewer_start': label = 'Reviewing'; break;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
const content = target || message || '';
|
|
774
|
+
chatBox.log(` ${icon} {bold}${label}{/} ${color}${content}{/}`);
|
|
775
|
+
screen.render();
|
|
776
|
+
}
|
|
777
|
+
|
|
673
778
|
/** Show/hide thinking indicator in status bar */
|
|
674
779
|
function setThinking(active, secondsElapsed = 0) {
|
|
675
780
|
if (active) {
|
|
676
|
-
updateStatusBar(
|
|
781
|
+
updateStatusBar('Thinking...', secondsElapsed);
|
|
677
782
|
} else {
|
|
678
783
|
updateStatusBar(null);
|
|
679
784
|
}
|
|
@@ -691,18 +796,28 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
691
796
|
? 'Shell Command'
|
|
692
797
|
: request.type === 'patch'
|
|
693
798
|
? 'Patch Edit'
|
|
694
|
-
:
|
|
799
|
+
: request.type === 'code_mode'
|
|
800
|
+
? 'Enter Code Mode'
|
|
801
|
+
: 'File Write';
|
|
695
802
|
const preview = request.preview || request.label || '';
|
|
696
803
|
const message = [
|
|
697
804
|
`{bold}${typeLabel}{/bold}`,
|
|
698
805
|
'',
|
|
699
806
|
preview,
|
|
700
807
|
'',
|
|
701
|
-
'Approve this action?'
|
|
808
|
+
'Approve this action?',
|
|
809
|
+
'', // Extra lines to push buttons down and avoid overlapping
|
|
810
|
+
''
|
|
702
811
|
].join('\n');
|
|
703
812
|
|
|
813
|
+
// Temporarily stop reading input so the dialog can receive keys
|
|
814
|
+
if (inputBox._reading) {
|
|
815
|
+
inputBox.cancel();
|
|
816
|
+
}
|
|
817
|
+
|
|
704
818
|
approvalDialog.ask(message, (approved) => {
|
|
705
819
|
inputBox.focus();
|
|
820
|
+
inputBox.readInput(); // Ensure we resume reading after dialog
|
|
706
821
|
refreshInputStyles();
|
|
707
822
|
screen.render();
|
|
708
823
|
resolve(Boolean(approved));
|
|
@@ -710,7 +825,45 @@ function createChatUI({ onSubmit, onExit }) {
|
|
|
710
825
|
});
|
|
711
826
|
}
|
|
712
827
|
|
|
713
|
-
|
|
828
|
+
function askUser(question) {
|
|
829
|
+
return new Promise((resolve) => {
|
|
830
|
+
// Temporarily stop reading input so we can capture the answer
|
|
831
|
+
if (inputBox._reading) {
|
|
832
|
+
inputBox.cancel();
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// We use a simple textbox floating over the chat or reuse the main input?
|
|
836
|
+
// Reusing the main input is cleaner for CLI.
|
|
837
|
+
// But we need to change the label to ' Answer '
|
|
838
|
+
const oldLabel = inputBox._label.content;
|
|
839
|
+
inputBox._label.setContent(' Answer ');
|
|
840
|
+
|
|
841
|
+
// Clear input for the answer
|
|
842
|
+
inputBox.clearValue();
|
|
843
|
+
hidePlaceholder();
|
|
844
|
+
inputBox.focus();
|
|
845
|
+
inputBox.readInput();
|
|
846
|
+
screen.render();
|
|
847
|
+
|
|
848
|
+
const submitHandler = (value) => {
|
|
849
|
+
inputBox.removeListener('submit', submitHandler);
|
|
850
|
+
inputBox._label.setContent(oldLabel);
|
|
851
|
+
|
|
852
|
+
const answer = value || '';
|
|
853
|
+
chatBox.log('');
|
|
854
|
+
chatBox.log(` {bold}User answered:{/}`);
|
|
855
|
+
const lines = wrapText(answer, screen.width - 20);
|
|
856
|
+
lines.forEach(l => chatBox.log(` {#95a2b8-fg}${l}{/}`));
|
|
857
|
+
screen.render();
|
|
858
|
+
|
|
859
|
+
resolve(answer);
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
inputBox.on('submit', submitHandler);
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
return { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode, appendCodeStep, updateWorkspace, askUser };
|
|
714
867
|
}
|
|
715
868
|
|
|
716
869
|
module.exports = { createChatUI };
|