@pheem49/mint 1.2.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,9 +6,9 @@ const blessed = require('blessed');
6
6
  const path = require('path');
7
7
  const { execSync } = require('child_process');
8
8
  const { readConfig } = require('../System/config_manager');
9
- const fs = require('fs');
10
9
 
11
10
  const SLASH_COMMANDS = [
11
+ { name: '/code', desc: 'Force workspace code mode for a task' },
12
12
  { name: '/models', desc: 'List or switch Gemini models' },
13
13
  { name: '/config', desc: 'Show current configuration' },
14
14
  { name: '/copy', desc: 'Copy last response to clipboard' },
@@ -29,6 +29,9 @@ function createChatUI({ onSubmit, onExit }) {
29
29
  const config = readConfig();
30
30
  const modelName = config.geminiModel || 'gemini';
31
31
  const workspaceName = path.basename(process.cwd());
32
+ const HINT_DEFAULT = `{gray-fg} Enter send · Ctrl+Y copy · /help commands{/}`;
33
+ const INPUT_FG = '#f8fafc';
34
+ const INPUT_BG = '#10141c';
32
35
 
33
36
  // ─── Screen ───────────────────────────────────────────────────────────────
34
37
  const screen = blessed.screen({
@@ -40,82 +43,80 @@ function createChatUI({ onSubmit, onExit }) {
40
43
 
41
44
  // ─── Banner ───────────────────────────────────────────────────────────────
42
45
  const banner = blessed.box({
43
- top: 0, left: 0, width: '100%', height: 9,
46
+ top: 0, left: 1, width: '100%-2', height: 4,
44
47
  tags: true,
45
- style: { bg: 'default' }
48
+ padding: { left: 1, right: 1 },
49
+ style: { bg: 'default', fg: '#d7dde8' }
46
50
  });
47
51
  banner.setContent([
48
- `{bold}{#88e0b0-fg} __ __ _ _ _____ _ _____ {/}`,
49
- `{bold}{#88e0b0-fg} | \\/ (_) | | / ____| | |_ _|{/}`,
50
- `{bold}{#88e0b0-fg} | \\ / |_ _ __ | |_ | | | | | | {/}`,
51
- `{bold}{#88e0b0-fg} | |\\/| | | '_ \\| __| | | | | | | {/}`,
52
- `{bold}{#88e0b0-fg} | | | | | | | | |_ | |____| |____ _| |_ {/}`,
53
- `{bold}{#88e0b0-fg} |_| |_|_|_| |_|\\__| \\_____|______|_____|{/}`,
54
- ``,
55
- `{bold} Welcome to Mint Interactive AI!{/} {gray-fg}Type '/help' for commands · 'exit' or Esc to quit{/}`
52
+ `{#88e0b0-fg} __ __ _ _ ___ _ ___ {/}`,
53
+ `{#88e0b0-fg}| \\/ (_)_ __ | |_ / __| | |_ _|{/}`,
54
+ `{#88e0b0-fg}| |\\/| | | '_ \\| _| (__| |__ | | {/}`,
55
+ `{#88e0b0-fg}|_| |_|_|_| |_|\\__|\\___|____|___|{/}`
56
56
  ].join('\n'));
57
57
 
58
- // ─── Divider under banner ─────────────────────────────────────────────────
59
- const divider1 = blessed.line({
60
- top: 9, left: 0, width: '100%',
61
- orientation: 'horizontal',
62
- style: { fg: '#333333' }
58
+ const subBanner = blessed.box({
59
+ top: 4, left: 2, width: '100%-4', height: 2,
60
+ tags: true,
61
+ content: `{gray-fg}Type naturally to chat. Coding requests can auto-enter {/}{#ffd166-fg}Code Mode{/}{gray-fg}. Use {/}{#88e0b0-fg}/help{/}{gray-fg}, {/}{#88e0b0-fg}/code{/}{gray-fg}, or {/}{#88e0b0-fg}Esc{/}{gray-fg}.{/}`,
62
+ style: { bg: 'default', fg: '#9aa6bf' }
63
63
  });
64
64
 
65
65
  // ─── Chat log (scrollable) ────────────────────────────────────────────────
66
66
  const chatBox = blessed.log({
67
- top: 10, left: 0, width: '100%',
68
- bottom: 8, // statusbar(3) + hint(1) + inputBox(3) + divider(1)
67
+ top: 6, left: 1, width: '100%-2',
68
+ bottom: 8,
69
69
  tags: true,
70
70
  scrollable: true,
71
71
  alwaysScroll: true,
72
- scrollbar: { ch: '', style: { fg: '#334433' } },
73
- style: { bg: 'default', fg: '#ffffff' },
72
+ scrollbar: { ch: '', style: { fg: '#335d52' } },
73
+ style: { bg: '#171b24', fg: '#ffffff', border: { fg: '#2f3747' } },
74
74
  mouse: true,
75
- scrollable: true
76
- });
77
-
78
- // ─── Divider above input ──────────────────────────────────────────────────
79
- const divider2 = blessed.line({
80
- bottom: 7, left: 0, width: '100%',
81
- orientation: 'horizontal',
82
- style: { fg: '#333333' }
75
+ scrollable: true,
76
+ border: { type: 'line' },
77
+ padding: { left: 1, right: 1, top: 0, bottom: 0 },
78
+ label: ' Conversation '
83
79
  });
84
80
 
85
81
  // ─── Hint bar ─────────────────────────────────────────────────────────────
86
82
  const hintBar = blessed.box({
87
- bottom: 6, left: 0, width: '100%', height: 1,
83
+ bottom: 6, left: 1, width: '100%-2', height: 1,
88
84
  tags: true,
89
- content: `{gray-fg} Shift+Drag to select text · Scroll to view history · /help for commands{/}`,
85
+ content: HINT_DEFAULT,
90
86
  style: { bg: 'default' }
91
87
  });
92
88
 
93
89
  // ─── Input area ───────────────────────────────────────────────────────────
94
- const inputBox = blessed.textarea({
95
- bottom: 3, left: 0, width: '100%', height: 3,
90
+ const inputBox = blessed.textbox({
91
+ bottom: 3, left: 1, width: '100%-2', height: 3,
96
92
  tags: false,
97
93
  inputOnFocus: true,
98
94
  keys: true,
99
95
  style: {
100
- bg: '#111111',
101
- fg: '#ffffff',
102
- border: { fg: '#334433' },
103
- focus: { border: { fg: '#88e0b0' } }
96
+ bg: INPUT_BG,
97
+ fg: INPUT_FG,
98
+ border: { fg: '#335d52' },
99
+ focus: {
100
+ fg: INPUT_FG,
101
+ bg: INPUT_BG,
102
+ border: { fg: '#88e0b0' }
103
+ }
104
104
  },
105
105
  border: { type: 'line' },
106
- padding: { left: 1 }
106
+ padding: { left: 1 },
107
+ label: ' Message '
107
108
  });
108
109
 
109
110
  // ─── Placeholder (SIBLING widget floating over input content area) ─────────
110
111
  // inputBox: bottom=3, height=3, border=1 → content row at bottom=4, left=2
111
112
  const placeholderWidget = blessed.text({
112
113
  bottom: 4, // inside input content area (border offset)
113
- left: 2, // border(1) + padding(1)
114
- width: '100%-4', // minus borders and padding
114
+ left: 3,
115
+ width: '100%-6',
115
116
  height: 1,
116
- content: '> Type your message or @path/to/file',
117
+ content: '> Ask anything, or describe a coding task for this workspace',
117
118
  tags: false,
118
- style: { fg: '#555555', bg: '#111111' }
119
+ style: { fg: '#5d6678', bg: '#10141c' }
119
120
  });
120
121
 
121
122
  let placeholderVisible = true;
@@ -136,12 +137,40 @@ function createChatUI({ onSubmit, onExit }) {
136
137
  }
137
138
  }
138
139
 
140
+ function refreshInputStyles() {
141
+ inputBox.style.fg = INPUT_FG;
142
+ inputBox.style.bg = INPUT_BG;
143
+ if (inputBox.style.focus) {
144
+ inputBox.style.focus.fg = INPUT_FG;
145
+ inputBox.style.focus.bg = INPUT_BG;
146
+ }
147
+ if (Array.isArray(inputBox.children)) {
148
+ inputBox.children.forEach((child) => {
149
+ if (child.style) {
150
+ child.style.fg = INPUT_FG;
151
+ child.style.bg = INPUT_BG;
152
+ }
153
+ });
154
+ }
155
+ applyTerminalInputAttrs();
156
+ }
157
+
158
+ function applyTerminalInputAttrs() {
159
+ try {
160
+ if (!screen || !screen.program || typeof inputBox.sattr !== 'function' || typeof screen.codeAttr !== 'function') {
161
+ return;
162
+ }
163
+ const attr = inputBox.sattr(inputBox.style);
164
+ screen.program.write(screen.codeAttr(attr));
165
+ } catch (_) {}
166
+ }
167
+
139
168
  // ─── Status bar (3 columns: left / center / right) ──────────────────────
140
169
  const statusBar = blessed.box({
141
- bottom: 0, left: 0, width: '100%', height: 3,
170
+ bottom: 0, left: 1, width: '100%-2', height: 3,
142
171
  tags: true,
143
- style: { bg: '#111111', fg: '#888888' },
144
- border: { type: 'line', fg: '#222222' }
172
+ style: { bg: '#10141c', fg: '#888888' },
173
+ border: { type: 'line', fg: '#222c38' }
145
174
  });
146
175
 
147
176
  // Left: workspace info
@@ -152,20 +181,20 @@ function createChatUI({ onSubmit, onExit }) {
152
181
  height: 1,
153
182
  tags: true,
154
183
  content: ` workspace {bold}(${workspaceName}){/bold}`,
155
- style: { bg: '#111111', fg: '#888888' }
184
+ style: { bg: '#10141c', fg: '#93a0b7' }
156
185
  });
157
186
 
158
- // Center: sandbox status
187
+ // Center: mode + status
159
188
  const statusCenter = blessed.text({
160
189
  parent: statusBar,
161
190
  top: 0,
162
191
  left: 'center',
163
- width: '34%',
192
+ width: '44%',
164
193
  height: 1,
165
194
  align: 'center',
166
195
  tags: true,
167
- content: `{#cc4444-fg}no sandbox{/}`,
168
- style: { bg: '#111111', fg: '#888888' }
196
+ content: `{#88aaff-fg}[Chat]{/} {#cc4444-fg}no sandbox{/}`,
197
+ style: { bg: '#10141c', fg: '#888888' }
169
198
  });
170
199
 
171
200
  // Right: current model
@@ -177,18 +206,30 @@ function createChatUI({ onSubmit, onExit }) {
177
206
  align: 'right',
178
207
  tags: true,
179
208
  content: `{#88e0b0-fg}${modelName}{/}`,
180
- style: { bg: '#111111', fg: '#888888' }
209
+ style: { bg: '#10141c', fg: '#88e0b0' }
181
210
  });
182
211
 
212
+ let activeMode = 'Chat';
213
+
214
+ function formatModeTag(mode) {
215
+ if (mode === 'Code') return `{#ffd166-fg}[Code]{/}`;
216
+ return `{#88aaff-fg}[Chat]{/}`;
217
+ }
218
+
183
219
  function updateStatusBar(thinkingText = null) {
184
220
  if (thinkingText) {
185
- statusCenter.setContent(`{#88e0b0-fg}${thinkingText}{/}`);
221
+ statusCenter.setContent(`${formatModeTag(activeMode)} {#88e0b0-fg}${thinkingText}{/}`);
186
222
  } else {
187
- statusCenter.setContent(`{#cc4444-fg}no sandbox{/}`);
223
+ statusCenter.setContent(`${formatModeTag(activeMode)} {#cc4444-fg}no sandbox{/}`);
188
224
  }
189
225
  screen.render();
190
226
  }
191
227
 
228
+ function setMode(mode) {
229
+ activeMode = mode === 'Code' ? 'Code' : 'Chat';
230
+ updateStatusBar(null);
231
+ }
232
+
192
233
  /** Update model name in status bar (called after /models switch) */
193
234
  function updateStatusModel(newModel) {
194
235
  statusRight.setContent(`{#88e0b0-fg}${newModel}{/}`);
@@ -198,9 +239,8 @@ function createChatUI({ onSubmit, onExit }) {
198
239
 
199
240
  // ─── Append widgets to screen ─────────────────────────────────────────────
200
241
  screen.append(banner);
201
- screen.append(divider1);
242
+ screen.append(subBanner);
202
243
  screen.append(chatBox);
203
- screen.append(divider2);
204
244
  screen.append(hintBar);
205
245
  screen.append(inputBox);
206
246
  screen.append(statusBar);
@@ -209,9 +249,9 @@ function createChatUI({ onSubmit, onExit }) {
209
249
  // ─── Suggestion List ──────────────────────────────────────────────────────
210
250
  const commandList = blessed.list({
211
251
  parent: screen,
212
- bottom: 6, // Above hintBar
213
- left: 2,
214
- width: '70%',
252
+ bottom: 6,
253
+ left: 3,
254
+ width: '64%',
215
255
  height: 8,
216
256
  tags: true,
217
257
  keys: false, // We will handle keys manually to keep focus on input
@@ -219,10 +259,10 @@ function createChatUI({ onSubmit, onExit }) {
219
259
  hidden: true,
220
260
  border: { type: 'line', fg: '#88e0b0' },
221
261
  style: {
222
- bg: '#111111',
262
+ bg: '#10141c',
223
263
  fg: '#ffffff',
224
264
  selected: {
225
- bg: '#334433',
265
+ bg: '#22352f',
226
266
  fg: '#88e0b0',
227
267
  bold: true
228
268
  }
@@ -230,6 +270,22 @@ function createChatUI({ onSubmit, onExit }) {
230
270
  });
231
271
 
232
272
  let activeSuggestions = [];
273
+ const approvalDialog = blessed.question({
274
+ parent: screen,
275
+ tags: true,
276
+ border: { type: 'line', fg: '#88e0b0' },
277
+ style: {
278
+ bg: '#10141c',
279
+ fg: '#ffffff',
280
+ border: { fg: '#88e0b0' }
281
+ },
282
+ width: '80%',
283
+ height: 'shrink',
284
+ top: 'center',
285
+ left: 'center',
286
+ label: ' Approval ',
287
+ hidden: true
288
+ });
233
289
 
234
290
  function updateSuggestions(filter = '') {
235
291
  activeSuggestions = SLASH_COMMANDS.filter(cmd =>
@@ -260,6 +316,7 @@ function createChatUI({ onSubmit, onExit }) {
260
316
 
261
317
  // Consolidated key handling
262
318
  inputBox.on('element keypress', (el, ch, key) => {
319
+ refreshInputStyles();
263
320
  // 1. Handle placeholder visibility
264
321
  if (!key.ctrl && !key.meta && key.name !== 'enter' && key.name !== 'tab') {
265
322
  if (ch) hidePlaceholder();
@@ -287,6 +344,7 @@ function createChatUI({ onSubmit, onExit }) {
287
344
 
288
345
  // 3. Logic for suggestions and placeholder after key is processed
289
346
  setImmediate(() => {
347
+ refreshInputStyles();
290
348
  const val = (inputBox.getValue ? inputBox.getValue() : inputBox.value) || '';
291
349
  const isCommand = val.startsWith('/') && !val.includes(' ');
292
350
 
@@ -308,6 +366,15 @@ function createChatUI({ onSubmit, onExit }) {
308
366
  });
309
367
  });
310
368
 
369
+ inputBox.on('focus', () => {
370
+ refreshInputStyles();
371
+ screen.render();
372
+ });
373
+
374
+ inputBox.on('keypress', () => {
375
+ applyTerminalInputAttrs();
376
+ });
377
+
311
378
 
312
379
  // Submit or Select Suggestion on Enter
313
380
  inputBox.key(['enter'], () => {
@@ -318,6 +385,7 @@ function createChatUI({ onSubmit, onExit }) {
318
385
  commandList.hide();
319
386
  hidePlaceholder();
320
387
  inputBox.focus();
388
+ refreshInputStyles();
321
389
  screen.render();
322
390
  return; // Don't submit yet, let user add args or press enter again
323
391
  }
@@ -325,12 +393,20 @@ function createChatUI({ onSubmit, onExit }) {
325
393
 
326
394
  const raw = (inputBox.getValue ? inputBox.getValue() : inputBox.value) || '';
327
395
  const text = raw.trim();
328
- if (!text) return;
396
+ if (!text) {
397
+ inputBox.clearValue();
398
+ showPlaceholder();
399
+ inputBox.focus();
400
+ refreshInputStyles();
401
+ screen.render();
402
+ return;
403
+ }
329
404
 
330
405
  // Clear input and restore placeholder
331
406
  inputBox.clearValue();
332
407
  showPlaceholder();
333
408
  inputBox.focus();
409
+ refreshInputStyles();
334
410
  screen.render();
335
411
 
336
412
  if (text.toLowerCase() === 'exit' || text.toLowerCase() === 'quit') {
@@ -342,18 +418,9 @@ function createChatUI({ onSubmit, onExit }) {
342
418
  });
343
419
 
344
420
  // Shift+Enter = newline in input
345
- inputBox.key(['S-enter'], () => {
346
- hidePlaceholder();
347
- const val = (inputBox.getValue ? inputBox.getValue() : inputBox.value) || '';
348
- inputBox.setValue(val + '\n');
349
- screen.render();
350
- });
351
-
352
421
  // Ctrl+C — double-press to exit
353
422
  let ctrlCPressed = false;
354
423
  let ctrlCTimer = null;
355
- const HINT_DEFAULT = `{gray-fg} Shift+Drag to select text · Ctrl+Y to copy · /help for commands{/}`;
356
-
357
424
  screen.key(['C-c'], () => {
358
425
  if (ctrlCPressed) {
359
426
  clearTimeout(ctrlCTimer);
@@ -415,6 +482,7 @@ function createChatUI({ onSubmit, onExit }) {
415
482
 
416
483
  // ─── Initial render ───────────────────────────────────────────────────────
417
484
  inputBox.focus();
485
+ refreshInputStyles();
418
486
  screen.render();
419
487
 
420
488
  // ─── Public API ───────────────────────────────────────────────────────────
@@ -427,59 +495,95 @@ function createChatUI({ onSubmit, onExit }) {
427
495
  * @param {string} text
428
496
  * @param {string} timestamp - ISO string or Date object
429
497
  */
430
- function appendMessage(role, text, timestamp = null) {
431
- const now = timestamp ? new Date(timestamp) : new Date();
432
- const timeStr = now.toLocaleTimeString('th-TH', { hour: '2-digit', minute: '2-digit', hour12: false });
498
+ function wrapLineSmart(line, width) {
499
+ if (line.length <= width) return [line];
500
+ if (!line.includes(' ')) {
501
+ const pieces = [];
502
+ for (let index = 0; index < line.length; index += width) {
503
+ pieces.push(line.slice(index, index + width));
504
+ }
505
+ return pieces;
506
+ }
433
507
 
434
- // Helper to wrap text manually since blessed.log doesn't support indenting wrapped lines
435
- const wrapText = (str, width) => {
436
- const lines = [];
437
- const originalLines = str.split('\n');
438
-
439
- for (let line of originalLines) {
440
- if (line.length === 0) {
441
- lines.push('');
442
- continue;
508
+ const words = line.split(/\s+/);
509
+ const lines = [];
510
+ let current = '';
511
+ for (const word of words) {
512
+ if (word.length > width) {
513
+ if (current) {
514
+ lines.push(current);
515
+ current = '';
443
516
  }
444
-
445
- let current = '';
446
- for (let i = 0; i < line.length; i++) {
447
- current += line[i];
448
- // Simple wrap based on character count.
449
- // Note: This is an approximation for Thai, but better than terminal auto-wrap.
450
- if (current.length >= width) {
451
- lines.push(current);
452
- current = '';
517
+ for (let index = 0; index < word.length; index += width) {
518
+ const slice = word.slice(index, index + width);
519
+ if (slice.length === width) {
520
+ lines.push(slice);
521
+ } else {
522
+ current = slice;
453
523
  }
454
524
  }
455
- if (current) lines.push(current);
525
+ continue;
456
526
  }
457
- return lines;
458
- };
459
527
 
460
- const maxLineWidth = Math.max(screen.width - 15, 40);
528
+ if (!current) {
529
+ current = word;
530
+ continue;
531
+ }
532
+
533
+ if (`${current} ${word}`.length <= width) {
534
+ current += ` ${word}`;
535
+ } else {
536
+ lines.push(current);
537
+ current = word;
538
+ }
539
+ }
540
+ if (current) lines.push(current);
541
+ return lines;
542
+ }
543
+
544
+ function wrapText(str, width) {
545
+ const lines = [];
546
+ const originalLines = String(str).split('\n');
547
+ for (const line of originalLines) {
548
+ if (line.length === 0) {
549
+ lines.push('');
550
+ continue;
551
+ }
552
+ lines.push(...wrapLineSmart(line, width));
553
+ }
554
+ return lines;
555
+ }
556
+
557
+ function appendMessage(role, text, timestamp = null) {
558
+ const now = timestamp ? new Date(timestamp) : new Date();
559
+ const timeStr = now.toLocaleTimeString('th-TH', { hour: '2-digit', minute: '2-digit', hour12: false });
560
+ const maxLineWidth = Math.max(screen.width - 20, 36);
561
+ const lines = wrapText(text, maxLineWidth);
461
562
 
462
563
  if (role === 'user') {
463
- chatBox.log(`\n {bold}{#88e0b0-fg}● You{/}`);
464
- const lines = wrapText(text, maxLineWidth);
465
- lines.forEach(l => chatBox.log(` {#ffffff-fg}${l}{/}`));
466
- chatBox.log(` {gray-fg}${timeStr}{/}`);
564
+ chatBox.log(``);
565
+ chatBox.log(` {bold}{#88e0b0-fg}You{/} {gray-fg}${timeStr}{/}`);
566
+ lines.forEach(l => chatBox.log(` {#88e0b0-fg}▏{/} {#ffffff-fg}${l}{/}`));
467
567
  } else if (role === 'assistant') {
468
568
  lastAssistantResponse = text;
469
- chatBox.log(`\n {bold}{#d4a8ff-fg}● Mint{/}`);
470
- const lines = wrapText(text, maxLineWidth);
471
- lines.forEach(l => chatBox.log(` {#444444-fg}{/} {#ffffff-fg}${l}{/}`));
472
- chatBox.log(` {#444444-fg}┕${'─'.repeat(4)}{/} {gray-fg}${timeStr}{/}`);
569
+ chatBox.log(``);
570
+ chatBox.log(` {bold}{#d4a8ff-fg}Mint{/} {gray-fg}${timeStr}{/}`);
571
+ lines.forEach(l => chatBox.log(` {#5a456d-fg}{/} {#ffffff-fg}${l}{/}`));
473
572
  } else if (role === 'system') {
474
- const displayTag = text.startsWith('Action:') ? '{#88e0b0-fg}✦ Action:{/}' : '{#888888-fg}ℹ System:{/}';
573
+ const displayTag = text.startsWith('Action:')
574
+ ? '{#88e0b0-fg}Action{/}'
575
+ : text.startsWith('[Code]')
576
+ ? '{#ffd166-fg}Code{/}'
577
+ : '{#8ba0ff-fg}System{/}';
475
578
  const cleanText = text.replace(/^(Action:|System:)\s*/, '');
476
- chatBox.log(`\n ${displayTag}`);
477
- const lines = wrapText(cleanText, maxLineWidth - 2);
478
- lines.forEach(l => chatBox.log(` {#ffffff-fg}${l}{/}`));
579
+ const systemLines = wrapText(cleanText, maxLineWidth - 4);
580
+ chatBox.log(``);
581
+ chatBox.log(` {bold}${displayTag}{/}`);
582
+ systemLines.forEach(l => chatBox.log(` {#95a2b8-fg}${l}{/}`));
479
583
  } else if (role === 'error') {
480
- chatBox.log(`\n {#ff5555-fg}✖ Error:{/}`);
481
- const lines = wrapText(text, maxLineWidth - 2);
482
- lines.forEach(l => chatBox.log(` {#ff5555-fg}${l}{/}`));
584
+ chatBox.log(``);
585
+ chatBox.log(` {bold}{#ff6b6b-fg}Error{/} {gray-fg}${timeStr}{/}`);
586
+ lines.forEach(l => chatBox.log(` {#7a2e2e-fg}▏{/} {#ff7d7d-fg}${l}{/}`));
483
587
  }
484
588
  screen.render();
485
589
  }
@@ -499,7 +603,32 @@ function createChatUI({ onSubmit, onExit }) {
499
603
  return copyToClipboard(lastAssistantResponse);
500
604
  }
501
605
 
502
- return { screen, appendMessage, setThinking, updateStatusModel, copyLastResponse };
606
+ function requestApproval(request) {
607
+ return new Promise((resolve) => {
608
+ const typeLabel = request.type === 'shell'
609
+ ? 'Shell Command'
610
+ : request.type === 'patch'
611
+ ? 'Patch Edit'
612
+ : 'File Write';
613
+ const preview = request.preview || request.label || '';
614
+ const message = [
615
+ `{bold}${typeLabel}{/bold}`,
616
+ '',
617
+ preview,
618
+ '',
619
+ 'Approve this action?'
620
+ ].join('\n');
621
+
622
+ approvalDialog.ask(message, (approved) => {
623
+ inputBox.focus();
624
+ refreshInputStyles();
625
+ screen.render();
626
+ resolve(Boolean(approved));
627
+ });
628
+ });
629
+ }
630
+
631
+ return { screen, appendMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode };
503
632
  }
504
633
 
505
634
  module.exports = { createChatUI };