@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.
package/mint-cli.js CHANGED
@@ -45,10 +45,42 @@ const colors = {
45
45
  yellow: "\x1b[33m"
46
46
  };
47
47
 
48
+ function formatProgress(info) {
49
+ if (typeof info === 'string') return `${colors.gray}[Mint Code] ${info}${colors.reset}`;
50
+
51
+ const { step, phase, action, target, message } = info;
52
+
53
+ if (action === 'ask_user') {
54
+ return `\n${colors.mint}✓${colors.reset} ${colors.bright}Ask User${colors.reset}\n${colors.gray} ${target || message || ''}${colors.reset}`;
55
+ }
56
+
57
+ let icon = `${colors.mint}✓${colors.reset}`;
58
+ let label = action || phase;
59
+ let color = colors.reset;
60
+
61
+ switch (action) {
62
+ case 'thinking':
63
+ return `\n${colors.yellow}* ${colors.bright}Thinking${colors.reset}`;
64
+ case 'web_search': label = 'WebSearch'; break;
65
+ case 'list_files':
66
+ case 'find_path': label = 'Explored'; break;
67
+ case 'read_file': label = 'ReadFile'; break;
68
+ case 'search_code': label = 'SearchText'; break;
69
+ case 'apply_patch':
70
+ case 'write_file': label = 'Edited'; break;
71
+ case 'run_shell': label = 'Ran command'; break;
72
+ case 'json_repair': icon = '*'; label = 'Repairing JSON'; break;
73
+ case 'reviewer_start': label = 'Reviewing'; break;
74
+ }
75
+
76
+ const content = target || message || '';
77
+ return ` ${icon} ${colors.bright}${label}${colors.reset} ${color}${content}${colors.reset}`;
78
+ }
79
+
48
80
  const program = new Command();
49
81
 
50
82
  program
51
- .name('mint-ai')
83
+ .name('mint')
52
84
  .description('Mint - Your Personal AI Assistant CLI')
53
85
  .version(pkg.version);
54
86
 
@@ -107,6 +139,79 @@ program
107
139
  console.log(`${colors.gray}You will receive a notification when it's done.${colors.reset}\n`);
108
140
  });
109
141
 
142
+ program
143
+ .command('mcp')
144
+ .description('Manage MCP (Model Context Protocol) servers')
145
+ .addCommand(new Command('add')
146
+ .description('Add a new MCP server')
147
+ .argument('<name>', 'Server name')
148
+ .argument('<command>', 'Command to run (e.g. npx)')
149
+ .option('-a, --args <args...>', 'Command arguments')
150
+ .option('-e, --env <env...>', 'Environment variables (KEY=VALUE)')
151
+ .action((name, command, options) => {
152
+ const config = readConfig();
153
+ const mcpServers = config.mcpServers || {};
154
+
155
+ const env = {};
156
+ if (options.env) {
157
+ options.env.forEach(kv => {
158
+ const [k, v] = kv.split('=');
159
+ if (k && v) env[k] = v;
160
+ });
161
+ }
162
+
163
+ mcpServers[name] = {
164
+ command,
165
+ args: options.args || [],
166
+ env
167
+ };
168
+
169
+ config.mcpServers = mcpServers;
170
+ writeConfig(config);
171
+ console.log(`\n${colors.mint}✓${colors.reset} MCP server "${name}" added successfully.`);
172
+ })
173
+ )
174
+ .addCommand(new Command('remove')
175
+ .description('Remove an MCP server')
176
+ .argument('<name>', 'Server name')
177
+ .action((name) => {
178
+ const config = readConfig();
179
+ if (config.mcpServers && config.mcpServers[name]) {
180
+ delete config.mcpServers[name];
181
+ writeConfig(config);
182
+ console.log(`\n${colors.mint}✓${colors.reset} MCP server "${name}" removed.`);
183
+ } else {
184
+ console.log(`\n${colors.pink}✗${colors.reset} MCP server "${name}" not found.`);
185
+ }
186
+ })
187
+ )
188
+ .addCommand(new Command('list')
189
+ .description('List configured MCP servers')
190
+ .action(() => {
191
+ const config = readConfig();
192
+ const servers = Object.keys(config.mcpServers || {});
193
+ if (servers.length === 0) {
194
+ console.log(`\n${colors.gray}No MCP servers configured.${colors.reset}`);
195
+ } else {
196
+ console.log(`\n${colors.bright}Configured MCP Servers:${colors.reset}`);
197
+ servers.forEach(name => {
198
+ const s = config.mcpServers[name];
199
+ console.log(`${colors.mint}• ${colors.bright}${name}${colors.reset}`);
200
+ console.log(` ${colors.gray}Command:${colors.reset} ${s.command} ${(s.args || []).join(' ')}`);
201
+ });
202
+ }
203
+ })
204
+ )
205
+ .addCommand(new Command('clear')
206
+ .description('Remove all MCP servers')
207
+ .action(() => {
208
+ const config = readConfig();
209
+ config.mcpServers = {};
210
+ writeConfig(config);
211
+ console.log(`\n${colors.mint}✓${colors.reset} All MCP servers cleared.`);
212
+ })
213
+ );
214
+
110
215
  program
111
216
  .command('code')
112
217
  .description('Run Mint in workspace-aware coding mode for the current project')
@@ -118,8 +223,8 @@ program
118
223
  try {
119
224
  const result = await executeCodeTask(task, {
120
225
  cwd: process.cwd(),
121
- onProgress: (message) => {
122
- console.log(`${colors.gray}[Mint Code] ${message}${colors.reset}`);
226
+ onProgress: (info) => {
227
+ console.log(formatProgress(info));
123
228
  },
124
229
  requestApproval: requestCodeApproval
125
230
  });
@@ -141,7 +246,7 @@ program.parse(process.argv);
141
246
  */
142
247
  async function startInteractiveChat(initialMessage = null) {
143
248
  let lastResponseText = "";
144
- const { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode } = createChatUI({
249
+ const { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode, appendCodeStep, updateWorkspace, askUser } = createChatUI({
145
250
  onSubmit: async (text) => {
146
251
  if (text.startsWith('/')) {
147
252
  if (text.startsWith('/agent')) {
@@ -200,9 +305,25 @@ async function startInteractiveChat(initialMessage = null) {
200
305
  } else {
201
306
  appendMessage('error', `Workspace "${name}" not found.`);
202
307
  }
308
+ } else if (subCmd === 'use' || subCmd === 'switch') {
309
+ const name = args[2];
310
+ const all = workspaceManager.listWorkspaces();
311
+ if (all[name]) {
312
+ const newPath = all[name].path;
313
+ try {
314
+ process.chdir(newPath);
315
+ updateWorkspace(newPath);
316
+ appendMessage('system', `✓ Switched to workspace "${name}" at ${newPath}`);
317
+ resetChat();
318
+ } catch (e) {
319
+ appendMessage('error', `Failed to change directory: ${e.message}`);
320
+ }
321
+ } else {
322
+ appendMessage('error', `Workspace "${name}" not found. Try /workspace list`);
323
+ }
203
324
  } else {
204
325
  const ws = workspaceManager.getWorkspaceByPath(process.cwd());
205
- appendMessage('system', ws ? `Current Workspace: ${ws.name}\nPath: ${ws.path}` : "Not currently in a registered workspace.\nUsage: /workspace <add|list|remove>");
326
+ appendMessage('system', ws ? `Current Workspace: ${ws.name}\nPath: ${ws.path}` : `Not currently in a registered workspace.\nActive Path: ${process.cwd()}\nUsage: /workspace <add|use|list|remove>`);
206
327
  }
207
328
  return;
208
329
  }
@@ -219,43 +340,15 @@ async function startInteractiveChat(initialMessage = null) {
219
340
  // Other slash commands
220
341
  const fakeRl = { close: () => { } };
221
342
  appendMessage('user', text);
222
- await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode);
343
+ await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace);
223
344
  return;
224
345
  }
225
346
  }
226
347
  appendMessage('user', text);
227
348
 
228
349
  const transcript = await getChatTranscript();
229
- const routeDecision = await detectCodeIntent(text, process.cwd(), transcript);
230
- if (routeDecision.route === 'code') {
231
- const approved = await requestApproval({
232
- type: 'code_mode',
233
- label: 'Mint wants to switch this request into Code Mode.',
234
- preview: [
235
- `Request: ${text}`,
236
- `Reason: ${routeDecision.reason}`,
237
- '',
238
- 'Code Mode is better for larger coding tasks that may inspect the workspace, run checks, or edit files.'
239
- ].join('\n')
240
- });
241
- if (!approved) {
242
- appendMessage('system', `Router stayed in Chat Mode. ${routeDecision.reason}`);
243
- } else {
244
- appendMessage('system', `Router: entering Code Mode. ${routeDecision.reason}`);
245
- await runChatRoutedTask(text, {
246
- appendMessage,
247
- setThinking,
248
- requestApproval,
249
- setMode,
250
- history: transcript
251
- });
252
- return;
253
- }
254
- }
255
-
256
- setMode('Chat');
350
+ if (setMode) setMode('Agent');
257
351
 
258
- // Start thinking timer
259
352
  let seconds = 0;
260
353
  setThinking(true, seconds);
261
354
  const timer = setInterval(() => {
@@ -265,96 +358,31 @@ async function startInteractiveChat(initialMessage = null) {
265
358
 
266
359
  try {
267
360
  const config = require('./src/System/config_manager').readConfig();
268
- const provider = config.aiProvider || 'gemini';
269
- const currentAgent = agentOrchestrator.getCurrentAgent();
270
- updateStatusModel(currentAgent.name);
271
- if (provider === 'gemini') {
272
- // ── Streaming path (Gemini only) ──────────────────────────────────
273
- // Gemini returns JSON so we buffer all chunks and progressively
274
- // extract the "response" field as more of the JSON arrives.
275
- clearInterval(timer);
276
-
277
- let jsonBuffer = '';
278
- let finalParsed = null;
279
- let streamer = null;
280
- let displayedChars = 0; // chars of response text already sent to TUI
281
-
282
- try {
283
- for await (const event of handleGeminiChatStream(text)) {
284
- if (event.chunk) {
285
- jsonBuffer += event.chunk;
286
-
287
- // Progressively extract readable text from the growing JSON buffer
288
- const match = jsonBuffer.match(/"response"\s*:\s*"((?:[^"\\]|\\.)*)"/s);
289
- if (match) {
290
- const fullText = match[1]
291
- .replace(/\\n/g, '\n')
292
- .replace(/\\"/g, '"')
293
- .replace(/\\\\/g, '\\');
294
- const newChars = fullText.slice(displayedChars);
295
- if (newChars.length > 0) {
296
- if (!streamer) {
297
- setThinking(false);
298
- streamer = streamMessage('assistant');
299
- }
300
- streamer.appendChunk(newChars);
301
- displayedChars = fullText.length;
302
- }
303
- }
304
- } else if (event.done) {
305
- finalParsed = event.parsed;
306
- // Flush any remaining response text not yet displayed
307
- if (finalParsed && finalParsed.response) {
308
- const remaining = finalParsed.response.slice(displayedChars);
309
- if (!streamer) {
310
- setThinking(false);
311
- streamer = streamMessage('assistant');
312
- }
313
- if (remaining) streamer.appendChunk(remaining);
314
- }
315
- if (streamer) {
316
- streamer.finalize(event.timestamp);
317
- } else {
318
- setThinking(false);
319
- appendMessage('assistant',
320
- finalParsed ? finalParsed.response : '',
321
- event.timestamp);
322
- }
323
- }
324
- }
325
- } catch (streamErr) {
326
- setThinking(false);
327
- appendMessage('error', streamErr.message);
328
- return;
361
+ const availableProviders = require('./src/System/config_manager').getAvailableProviders(config);
362
+ const preferredProvider = require('./src/CLI/code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
363
+
364
+ const result = await executeCodeTask(text, {
365
+ cwd: process.cwd(),
366
+ requestApproval,
367
+ askUser,
368
+ provider: preferredProvider,
369
+ history: transcript,
370
+ onProgress: (info) => {
371
+ if (appendCodeStep) appendCodeStep(info);
329
372
  }
373
+ });
330
374
 
331
- // Execute Actions from the final parsed response
332
- if (finalParsed) {
333
- const { executeAction } = require('./mint-cli-logic');
334
- if (finalParsed.action && finalParsed.action.type !== 'none') {
335
- const result = await executeAction(finalParsed.action);
336
- if (result) appendMessage('system', `Action: ${result}`);
337
- }
338
- }
375
+ clearInterval(timer);
376
+ setThinking(false);
377
+ lastResponseText = result.summary;
378
+ appendMessage('assistant', result.summary);
339
379
 
340
- } else {
341
- // ── Non-streaming fallback (Ollama, Anthropic, OpenAI, etc.) ──
342
- const response = await handleChat(text);
343
- clearInterval(timer);
344
- setThinking(false);
345
- lastResponseText = response.response;
346
- appendMessage('assistant', response.response, response.timestamp);
347
-
348
- const { executeAction } = require('./mint-cli-logic');
349
- if (response.action && response.action.type !== 'none') {
350
- const result = await executeAction(response.action);
351
- if (result) appendMessage('system', `Action: ${result}`);
352
- }
353
- }
354
380
  } catch (err) {
355
381
  clearInterval(timer);
356
382
  setThinking(false);
357
383
  appendMessage('error', err.message);
384
+ } finally {
385
+ if (setMode) setMode('Chat');
358
386
  }
359
387
  },
360
388
  onExit: () => {
@@ -370,72 +398,43 @@ async function startInteractiveChat(initialMessage = null) {
370
398
  // Handle initial message if passed via CLI arg
371
399
  if (initialMessage) {
372
400
  appendMessage('user', initialMessage);
373
- const transcript = await getChatTranscript();
374
- const routeDecision = await detectCodeIntent(initialMessage, process.cwd(), transcript);
375
- if (routeDecision.route === 'code') {
376
- const approved = await requestApproval({
377
- type: 'code_mode',
378
- label: 'Mint wants to switch this request into Code Mode.',
379
- preview: [
380
- `Request: ${initialMessage}`,
381
- `Reason: ${routeDecision.reason}`,
382
- '',
383
- 'Code Mode is better for larger coding tasks that may inspect the workspace, run checks, or edit files.'
384
- ].join('\n')
385
- });
386
- if (approved) {
387
- appendMessage('system', `Router: entering Code Mode. ${routeDecision.reason}`);
388
- await runChatRoutedTask(initialMessage, {
389
- appendMessage,
390
- setThinking,
391
- requestApproval,
392
- setMode,
393
- history: transcript
394
- });
395
- } else {
396
- appendMessage('system', `Router stayed in Chat Mode. ${routeDecision.reason}`);
397
- setMode('Chat');
398
- let seconds = 0;
399
- setThinking(true, seconds);
400
- const timer = setInterval(() => { seconds++; setThinking(true, seconds); }, 1000);
401
- try {
402
- const response = await handleChat(initialMessage);
403
- clearInterval(timer);
404
- setThinking(false);
405
- appendMessage('assistant', response.response, response.timestamp);
406
- lastResponseText = response.response;
407
- const { executeAction } = require('./mint-cli-logic');
408
- if (response.action && response.action.type !== 'none') {
409
- const result = await executeAction(response.action);
410
- if (result) appendMessage('system', `Action: ${result}`);
411
- }
412
- } catch (err) {
413
- clearInterval(timer);
414
- setThinking(false);
415
- appendMessage('error', err.message);
416
- }
417
- }
418
- } else {
419
- setMode('Chat');
420
- let seconds = 0;
401
+ const transcript = await getChatTranscript();
402
+ if (setMode) setMode('Agent');
403
+
404
+ let seconds = 0;
405
+ setThinking(true, seconds);
406
+ const timer = setInterval(() => {
407
+ seconds++;
421
408
  setThinking(true, seconds);
422
- const timer = setInterval(() => { seconds++; setThinking(true, seconds); }, 1000);
423
- try {
424
- const response = await handleChat(initialMessage);
425
- clearInterval(timer);
426
- setThinking(false);
427
- appendMessage('assistant', response.response, response.timestamp);
428
- lastResponseText = response.response;
429
- const { executeAction } = require('./mint-cli-logic');
430
- if (response.action && response.action.type !== 'none') {
431
- const result = await executeAction(response.action);
432
- if (result) appendMessage('system', `Action: ${result}`);
409
+ }, 1000);
410
+
411
+ try {
412
+ const config = require('./src/System/config_manager').readConfig();
413
+ const availableProviders = require('./src/System/config_manager').getAvailableProviders(config);
414
+ const preferredProvider = require('./src/CLI/code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
415
+
416
+ const result = await executeCodeTask(initialMessage, {
417
+ cwd: process.cwd(),
418
+ requestApproval,
419
+ askUser,
420
+ provider: preferredProvider,
421
+ history: transcript,
422
+ onProgress: (info) => {
423
+ if (appendCodeStep) appendCodeStep(info);
433
424
  }
434
- } catch (err) {
435
- clearInterval(timer);
436
- setThinking(false);
437
- appendMessage('error', err.message);
438
- }
425
+ });
426
+
427
+ clearInterval(timer);
428
+ setThinking(false);
429
+ lastResponseText = result.summary;
430
+ appendMessage('assistant', result.summary);
431
+
432
+ } catch (err) {
433
+ clearInterval(timer);
434
+ setThinking(false);
435
+ appendMessage('error', err.message);
436
+ } finally {
437
+ if (setMode) setMode('Chat');
439
438
  }
440
439
  }
441
440
  }
@@ -443,7 +442,7 @@ async function startInteractiveChat(initialMessage = null) {
443
442
  /**
444
443
  * Handles slash commands within the TUI context
445
444
  */
446
- async function handleSlashCommandUI(input, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode) {
445
+ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace) {
447
446
  const parts = input.split(' ');
448
447
  const command = parts[0].toLowerCase();
449
448
  const args = parts.slice(1);
@@ -453,16 +452,36 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
453
452
  case '/?':
454
453
  appendMessage('system', [
455
454
  'Mint Slash Commands:',
456
- ' /code <task> — Force workspace Code Mode',
457
- ' /models [name] List or switch Gemini models',
458
- ' /config Show current configuration',
459
- ' /copy Copy last response to clipboard',
460
- ' /clear Clear conversation history',
461
- ' /reset Reset conversation history',
462
- ' /exitExit Mint'
455
+ ' /code <task> — Force workspace Code Mode',
456
+ ' /cd <path> Change current working directory',
457
+ ' /models [name] List or switch Gemini models',
458
+ ' /config Show current configuration',
459
+ ' /copy Copy last response to clipboard',
460
+ ' /clear Clear conversation history',
461
+ ' /resetReset conversation history',
462
+ ' /exit — Exit Mint'
463
463
  ].join('\n'));
464
464
  break;
465
465
 
466
+ case '/cd':
467
+ if (args.length === 0) {
468
+ appendMessage('system', `Current Directory: ${process.cwd()}`);
469
+ break;
470
+ }
471
+ try {
472
+ const newPath = path.resolve(process.cwd(), args[0]);
473
+ if (fs.existsSync(newPath) && fs.lstatSync(newPath).isDirectory()) {
474
+ process.chdir(newPath);
475
+ if (updateWorkspace) updateWorkspace(newPath);
476
+ appendMessage('system', `✓ Directory changed to: ${newPath}`);
477
+ } else {
478
+ appendMessage('error', `Directory not found: ${newPath}`);
479
+ }
480
+ } catch (err) {
481
+ appendMessage('error', `Error: ${err.message}`);
482
+ }
483
+ break;
484
+
466
485
  case '/model':
467
486
  case '/models':
468
487
  const config = readConfig();
@@ -520,7 +539,7 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
520
539
  await runChatRoutedTask(`/code ${args.join(' ')}`, {
521
540
  appendMessage,
522
541
  setThinking,
523
- requestApproval,
542
+ appendCodeStep,
524
543
  setMode,
525
544
  history: await getChatTranscript()
526
545
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pheem49/mint",
3
- "version": "1.4.1",
3
+ "version": "1.4.2",
4
4
  "description": "A powerful Electron-based AI desktop assistant powered by Google Gemini, featuring screen vision, web automation, and proactive suggestions.",
5
5
  "main": "main.js",
6
6
  "scripts": {
@@ -9,32 +9,32 @@ const AGENT_PERSONAS = {
9
9
  'general': {
10
10
  name: 'Mint Default',
11
11
  icon: '💎',
12
- instruction: 'You are Mint, a versatile and helpful AI assistant. You maintain a friendly, professional, and slightly cheerful personality. Use emojis appropriately.'
12
+ instruction: 'You are Mint, a versatile and helpful female AI assistant. You maintain a friendly, professional, and slightly cheerful personality. Use emojis appropriately. WHEN RESPONDING IN THAI: ALWAYS use female polite particles such as "ค่ะ", "นะคะ". Refer to yourself as "มิ้นท์" or "หนู".'
13
13
  },
14
14
  'coder': {
15
15
  name: 'Mint Coder',
16
16
  icon: '💻',
17
- instruction: 'You are Mint Coder, an expert software engineer. Your responses should be technically precise, focus on best practices, and provide optimized code snippets. Explain complex logic clearly.'
17
+ instruction: 'You are Mint Coder, an expert female software engineer. Your responses should be technically precise, focus on best practices, and provide optimized code snippets. Explain complex logic clearly. WHEN RESPONDING IN THAI: ALWAYS use female polite particles such as "ค่ะ", "นะคะ". Refer to yourself as "มิ้นท์" or "หนู".'
18
18
  },
19
19
  'researcher': {
20
20
  name: 'Mint Researcher',
21
21
  icon: '🔍',
22
- instruction: 'You are Mint Researcher, an academic and analytical assistant. Focus on citations, data-driven facts, and objective analysis. Avoid speculation and be highly detailed.'
22
+ instruction: 'You are Mint Researcher, an academic and analytical female assistant. Focus on citations, data-driven facts, and objective analysis. Avoid speculation and be highly detailed. WHEN RESPONDING IN THAI: ALWAYS use female polite particles such as "ค่ะ", "นะคะ". Refer to yourself as "มิ้นท์" or "หนู".'
23
23
  },
24
24
  'creative': {
25
25
  name: 'Mint Creative',
26
26
  icon: '🎨',
27
- instruction: 'You are Mint Creative, a storytelling and brainstorming partner. Use vivid language, poetic descriptions, and think outside the box. Be highly expressive and encouraging.'
27
+ instruction: 'You are Mint Creative, a storytelling and brainstorming female partner. Use vivid language, poetic descriptions, and think outside the box. Be highly expressive and encouraging. WHEN RESPONDING IN THAI: ALWAYS use female polite particles such as "ค่ะ", "นะคะ". Refer to yourself as "มิ้นท์" or "หนู".'
28
28
  },
29
29
  'manager': {
30
30
  name: 'Mint Manager',
31
31
  icon: '💼',
32
- instruction: 'You are Mint Manager, a productivity and project management expert. Focus on task lists, deadlines, efficiency, and clear action plans. Be concise and goal-oriented.'
32
+ instruction: 'You are Mint Manager, a productivity and project management female expert. Focus on task lists, deadlines, efficiency, and clear action plans. Be concise and goal-oriented. WHEN RESPONDING IN THAI: ALWAYS use female polite particles such as "ค่ะ", "นะคะ". Refer to yourself as "มิ้นท์" or "หนู".'
33
33
  },
34
34
  'reviewer': {
35
35
  name: 'Mint Reviewer',
36
36
  icon: '⚖️',
37
- instruction: 'You are Mint Reviewer, a senior code critic. Your job is to find flaws, security vulnerabilities, performance bottlenecks, and logic errors in any provided content. Be brutal but constructive. Use a formal, objective tone.'
37
+ instruction: 'You are Mint Reviewer, a senior female code critic. Your job is to find flaws, security vulnerabilities, performance bottlenecks, and logic errors in any provided content. Be brutal but constructive. Use a formal, objective tone. WHEN RESPONDING IN THAI: ALWAYS use female polite particles such as "ค่ะ", "นะคะ". Refer to yourself as "มิ้นท์" or "หนู".'
38
38
  }
39
39
  };
40
40
 
@@ -216,12 +216,23 @@ async function openFile(target) {
216
216
  console.error('openFile error:', result);
217
217
  return `เกิดข้อผิดพลาดในการเปิดไฟล์: ${result}`;
218
218
  }
219
+ return true;
219
220
  } else {
220
221
  return new Promise((resolve) => {
221
- execFile('xdg-open', [resolvedPath], (err) => {
222
+ // บน Linux ลอง xdg-open แล้วค่อย gio open ถ้าอันแรกไม่ทำงาน
223
+ const { exec } = require('child_process');
224
+ const platformCmd = process.platform === 'darwin' ? 'open' : (process.platform === 'win32' ? 'start' : 'xdg-open');
225
+
226
+ // ใช้ exec เพื่อให้รันผ่าน shell และรองรับการทำ fallback
227
+ let cmd = `${platformCmd} "${resolvedPath}"`;
228
+ if (process.platform === 'linux') {
229
+ cmd = `xdg-open "${resolvedPath}" || gio open "${resolvedPath}" || nautilus "${resolvedPath}"`;
230
+ }
231
+
232
+ exec(cmd, (err) => {
222
233
  if (err) {
223
- console.error("Failed to open path via xdg-open:", err);
224
- resolve(`ไม่สามารถเปิดไฟล์ได้ค่ะ: ${err.message}`);
234
+ console.error("Failed to open path:", err);
235
+ resolve(`ไม่สามารถเปิดได้ค่ะ: ${err.message}`);
225
236
  } else {
226
237
  resolve(true);
227
238
  }
@@ -9,12 +9,15 @@ const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
9
9
  const CODE_KEYWORDS = [
10
10
  'code', 'repo', 'repository', 'project', 'workspace', 'file', 'files', 'readme',
11
11
  'package.json', 'bug', 'fix', 'refactor', 'test', 'tests', 'build', 'lint',
12
- 'implement', 'feature', 'cli', 'function', 'module', 'component', 'diff'
12
+ 'implement', 'feature', 'cli', 'function', 'module', 'component', 'diff',
13
+ 'list', 'show', 'ls', 'dir', 'directory', 'folders' // เพิ่มคำเหล่านี้ค่ะ
13
14
  ];
14
15
 
16
+
15
17
  const THAI_CODE_KEYWORDS = [
16
18
  'โค้ด', 'โปรเจค', 'โปรเจ็กต์', 'ไฟล์', 'รีโป', 'บั๊ก', 'แก้', 'ทดสอบ', 'เทสต์',
17
- 'รีแฟกเตอร์', 'ฟีเจอร์', 'คอมโพเนนต์', 'ฟังก์ชัน', 'อ่าน', 'สำรวจ', 'โครงสร้าง'
19
+ 'รีแฟกเตอร์', 'ฟีเจอร์', 'คอมโพเนนต์', 'ฟังก์ชัน', 'อ่าน', 'สำรวจ', 'โครงสร้าง',
20
+ 'ไดเรกทอรี', 'โฟลเดอร์'
18
21
  ];
19
22
 
20
23
  const ROUTER_PROMPT = `You classify whether a chat message should be routed to a coding agent for the current local workspace.
@@ -69,11 +72,15 @@ function isLargeCodeTaskRequest(text, workspaceRoot = process.cwd()) {
69
72
 
70
73
  const hasCodeKeyword = CODE_KEYWORDS.some(keyword => input.includes(keyword));
71
74
  const hasThaiCodeKeyword = THAI_CODE_KEYWORDS.some(keyword => input.includes(keyword));
72
- const referencesProject = /โปรเจคนี้|โปรเจ็กต์นี้|this project|this repo|this repository|codebase|workspace/.test(input);
73
- const asksForAction = /สำรวจ|ดู|แก้|เพิ่ม|ลบ|ปรับ|ตรวจ|วิเคราะห์|implement|inspect|explore|fix|update|change|refactor|review|explain|debug/.test(input);
75
+ const referencesProject = /โปรเจคนี้|โปรเจ็กต์นี้|this project|this repo|this repository|codebase|workspace|โฟลเดอร์นี้|ในนี้/.test(input);
76
+ const asksForAction = /สำรวจ|ดู|แก้|เพิ่ม|ลบ|ปรับ|ตรวจ|วิเคราะห์|ลิสต์|โชว์|แสดง|มี|implement|inspect|explore|fix|update|change|refactor|review|explain|debug|list|show/.test(input);
74
77
  const strongTaskSignal = /failing tests?|run tests?|verify|verification|bug|issue|error|refactor|implement|feature|patch|edit|modify|analyze the project|แก้บั๊ก|รันเทสต์|ทดสอบ|ตรวจสอบ|ยืนยันผล|รีแฟกเตอร์|เพิ่มฟีเจอร์|แก้โค้ด|วิเคราะห์โปรเจค/.test(input);
75
78
  const multiStepSignal = /and|then|พร้อม|แล้ว|จากนั้น|ทั้ง|ทั่วทั้ง|ทั้งโปรเจค|project-wide|entire project|whole project/.test(input);
76
79
 
80
+ // If they ask for files/folder content specifically, it's a code task because Chat can't do it accurately
81
+ const isListFilesRequest = (hasCodeKeyword || hasThaiCodeKeyword) && /มี|โชว์|แสดง|ลิสต์|list|show|what|anything|อะไรบ้าง/.test(input) && /ไฟล์|file|folder|dir|โฟลเดอร์/.test(input);
82
+
83
+ if (isListFilesRequest) return true;
77
84
  if (referencesProject && strongTaskSignal) return true;
78
85
  if ((hasCodeKeyword || hasThaiCodeKeyword) && asksForAction && strongTaskSignal) return true;
79
86
  if ((hasCodeKeyword || hasThaiCodeKeyword) && multiStepSignal && asksForAction) return true;
@@ -185,7 +192,13 @@ async function runChatRoutedTask(input, context) {
185
192
  requestApproval,
186
193
  provider: preferredProvider,
187
194
  history: history,
188
- onProgress: (message) => appendMessage('system', `[Code] ${message}`)
195
+ onProgress: (info) => {
196
+ if (context.appendCodeStep) {
197
+ context.appendCodeStep(info);
198
+ } else {
199
+ appendMessage('system', `[Code] ${typeof info === 'string' ? info : (info.action || info.phase)}`);
200
+ }
201
+ }
189
202
  });
190
203
  clearInterval(timer);
191
204
  setThinking(false);