@pheem49/mint 1.4.1 → 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.
@@ -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 workspaceName = path.basename(process.cwd());
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';
@@ -178,7 +178,7 @@ function createChatUI({ onSubmit, onExit }) {
178
178
  } catch (_) {}
179
179
  }
180
180
 
181
- // ─── Status bar (3 columns: left / center / right) ──────────────────────
181
+ // ─── Status bar (2 lines as per screenshot) ──────────────────────
182
182
  const statusBar = blessed.box({
183
183
  bottom: 0, left: 1, width: '100%-2', height: 3,
184
184
  tags: true,
@@ -186,54 +186,62 @@ function createChatUI({ onSubmit, onExit }) {
186
186
  border: { type: 'line', fg: '#222c38' }
187
187
  });
188
188
 
189
- // Left: workspace info
190
- const statusLeft = blessed.text({
189
+ // Line 1: Thinking / Status (Left) and Shortcut (Right)
190
+ const statusLine1 = blessed.text({
191
191
  parent: statusBar,
192
- top: 0, left: 1,
193
- width: '33%',
192
+ top: 0, left: 1, right: 1,
194
193
  height: 1,
195
194
  tags: true,
196
- content: ` workspace {bold}(${workspaceName}){/bold}`,
197
- style: { bg: '#10141c', fg: '#93a0b7' }
195
+ content: `{#88aaff-fg}[Chat]{/} {#cc4444-fg}no sandbox{/}`,
196
+ style: { bg: '#10141c' }
198
197
  });
199
198
 
200
- // Center: mode + status
201
- const statusCenter = blessed.text({
199
+ const shortcutHint = blessed.text({
202
200
  parent: statusBar,
203
- top: 0,
204
- left: 'center',
205
- width: '44%',
201
+ top: 0, right: 1,
206
202
  height: 1,
207
- align: 'center',
208
203
  tags: true,
209
- content: `{#88aaff-fg}[Chat]{/} {#cc4444-fg}no sandbox{/}`,
210
- style: { bg: '#10141c', fg: '#888888' }
204
+ content: `{gray-fg}? for shortcuts{/}`,
205
+ style: { bg: '#10141c' }
211
206
  });
212
207
 
213
- // Right: current model
214
- const statusRight = blessed.text({
208
+ // Line 2: Action Hint (Left) and File Info (Right)
209
+ const statusLine2 = blessed.text({
215
210
  parent: statusBar,
216
- top: 0, right: 1,
217
- width: '33%',
211
+ top: 1, left: 1, right: 1,
218
212
  height: 1,
219
- align: 'right',
220
213
  tags: true,
221
- content: `{#88e0b0-fg}${modelName}{/}`,
222
- style: { bg: '#10141c', fg: '#88e0b0' }
214
+ content: `{gray-fg}Shift+Tab to accept edits{/}`,
215
+ style: { bg: '#10141c' }
223
216
  });
224
217
 
225
- let activeMode = 'Chat';
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
+ });
226
226
 
227
- function formatModeTag(mode) {
228
- if (mode === 'Code') return `{#ffd166-fg}[Code]{/}`;
229
- return `{#88aaff-fg}[Chat]{/}`;
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`;
230
236
  }
231
237
 
232
- function updateStatusBar(thinkingText = null) {
238
+ function updateStatusBar(thinkingText = null, secondsElapsed = 0) {
233
239
  if (thinkingText) {
234
- statusCenter.setContent(`${formatModeTag(activeMode)} {#88e0b0-fg}${thinkingText}{/}`);
240
+ const char = spinnerChars[spinnerIdx % spinnerChars.length];
241
+ spinnerIdx++;
242
+ statusLine1.setContent(`{#88e0b0-fg}${char}{/} Thinking... {gray-fg}(esc to cancel, ${formatTime(secondsElapsed)}){/}`);
235
243
  } else {
236
- statusCenter.setContent(`${formatModeTag(activeMode)} {#cc4444-fg}no sandbox{/}`);
244
+ statusLine1.setContent(`${activeMode === 'Code' ? '{#ffd166-fg}[Code]{/}' : '{#88aaff-fg}[Chat]{/}'} {#cc4444-fg}no sandbox{/}`);
237
245
  }
238
246
  screen.render();
239
247
  }
@@ -243,10 +251,16 @@ function createChatUI({ onSubmit, onExit }) {
243
251
  updateStatusBar(null);
244
252
  }
245
253
 
246
- /** Update model name in status bar (called after /models switch) */
247
254
  function updateStatusModel(newModel) {
248
255
  if (!newModel) return;
249
- statusRight.setContent(`{#88e0b0-fg}${newModel}{/}`);
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}{/}`);
250
264
  screen.render();
251
265
  }
252
266
  updateStatusBar();
@@ -260,6 +274,8 @@ function createChatUI({ onSubmit, onExit }) {
260
274
  screen.append(statusBar);
261
275
  screen.append(placeholderWidget); // sibling on top of inputBox
262
276
 
277
+ // Suggestion List and Approval Dialog remain same ...
278
+
263
279
  // ─── Suggestion List ──────────────────────────────────────────────────────
264
280
  const commandList = blessed.list({
265
281
  parent: screen,
@@ -389,6 +405,19 @@ function createChatUI({ onSubmit, onExit }) {
389
405
  applyTerminalInputAttrs();
390
406
  });
391
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
+
392
421
 
393
422
  // Submit or Select Suggestion on Enter
394
423
  inputBox.on('submit', (value) => {
@@ -684,10 +713,72 @@ function createChatUI({ onSubmit, onExit }) {
684
713
  return { appendChunk, finalize };
685
714
  }
686
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
+
687
778
  /** Show/hide thinking indicator in status bar */
688
779
  function setThinking(active, secondsElapsed = 0) {
689
780
  if (active) {
690
- updateStatusBar(`Thinking... {gray-fg}(esc to cancel, ${secondsElapsed}s){/}`);
781
+ updateStatusBar('Thinking...', secondsElapsed);
691
782
  } else {
692
783
  updateStatusBar(null);
693
784
  }
@@ -734,7 +825,45 @@ function createChatUI({ onSubmit, onExit }) {
734
825
  });
735
826
  }
736
827
 
737
- return { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode };
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 };
738
867
  }
739
868
 
740
869
  module.exports = { createChatUI };