@pheem49/mint 1.4.1 → 1.5.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.
Files changed (61) hide show
  1. package/GUIDE_TH.md +113 -0
  2. package/README.md +214 -142
  3. package/assets/CLI_Screen.png +0 -0
  4. package/docs/assets/CLI_Screen.png +0 -0
  5. package/docs/guide.html +632 -0
  6. package/docs/index.html +5 -4
  7. package/main.js +66 -894
  8. package/mint-cli-logic.js +15 -8
  9. package/mint-cli.js +305 -195
  10. package/package.json +12 -4
  11. package/src/AI_Brain/Gemini_API.js +77 -20
  12. package/src/AI_Brain/agent_orchestrator.js +6 -6
  13. package/src/AI_Brain/autonomous_brain.js +10 -0
  14. package/src/AI_Brain/behavior_memory.js +26 -5
  15. package/src/AI_Brain/headless_agent.js +4 -0
  16. package/src/AI_Brain/knowledge_base.js +61 -8
  17. package/src/AI_Brain/memory_store.js +55 -7
  18. package/src/Automation_Layer/file_operations.js +14 -3
  19. package/src/CLI/chat_router.js +21 -7
  20. package/src/CLI/chat_ui.js +264 -710
  21. package/src/CLI/code_agent.js +370 -124
  22. package/src/CLI/gmail_auth.js +210 -0
  23. package/src/CLI/list_features.js +5 -1
  24. package/src/CLI/onboarding.js +307 -55
  25. package/src/CLI/updater.js +208 -0
  26. package/src/Channels/brave_search_bridge.js +35 -0
  27. package/src/Channels/discord_bridge.js +68 -0
  28. package/src/Channels/google_search_bridge.js +38 -0
  29. package/src/Channels/line_bridge.js +60 -0
  30. package/src/Channels/slack_bridge.js +53 -0
  31. package/src/Channels/telegram_bridge.js +49 -0
  32. package/src/Channels/whatsapp_bridge.js +55 -0
  33. package/src/Command_Parser/parser.js +12 -1
  34. package/src/Plugins/gmail.js +251 -0
  35. package/src/Plugins/google_calendar.js +245 -19
  36. package/src/Plugins/notion.js +256 -0
  37. package/src/System/action_executor.js +129 -0
  38. package/src/System/bridge_manager.js +76 -0
  39. package/src/System/chat_history_manager.js +23 -5
  40. package/src/System/config_manager.js +41 -7
  41. package/src/System/custom_workflows.js +31 -2
  42. package/src/System/google_tts_urls.js +51 -0
  43. package/src/System/ipc_handlers.js +238 -0
  44. package/src/System/proactive_loop.js +137 -0
  45. package/src/System/safety_manager.js +165 -0
  46. package/src/System/screen_capture.js +175 -0
  47. package/src/System/task_manager.js +15 -5
  48. package/src/System/window_manager.js +210 -0
  49. package/src/UI/renderer.js +33 -7
  50. package/src/UI/settings.html +24 -0
  51. package/src/UI/settings.js +14 -4
  52. package/src/UI/styles.css +14 -1
  53. package/tests/action_executor_safety.test.js +67 -0
  54. package/tests/gmail.test.js +135 -0
  55. package/tests/gmail_auth.test.js +129 -0
  56. package/tests/google_calendar.test.js +113 -0
  57. package/tests/google_tts_urls.test.js +24 -0
  58. package/tests/notion.test.js +121 -0
  59. package/tests/provider_routing.test.js +17 -1
  60. package/tests/safety_manager.test.js +40 -0
  61. package/tests/updater.test.js +32 -0
package/mint-cli-logic.js CHANGED
@@ -2,14 +2,23 @@
2
2
  const { openApp } = require('./src/Automation_Layer/open_app');
3
3
  const { openWebsite, openSearch } = require('./src/Automation_Layer/open_website');
4
4
  const { createFolder, openFile, deleteFile, findPath } = require('./src/Automation_Layer/file_operations');
5
- const { indexFile } = require('./src/AI_Brain/knowledge_base');
6
- const SystemAutomation = require('./src/System/system_automation');
7
- const pluginManager = require('./src/Plugins/plugin_manager');
5
+ const safetyManager = require('./src/System/safety_manager');
8
6
 
9
- async function executeAction(action) {
7
+ async function executeAction(action, options = {}) {
10
8
  if (!action || action.type === 'none') return null;
11
9
 
12
10
  try {
11
+ const safety = safetyManager.assertActionAllowed(action, {
12
+ allowDangerous: options.allowDangerous === true
13
+ });
14
+ safetyManager.appendActionLog({
15
+ source: options.source || 'mint_cli_logic',
16
+ action: action.type,
17
+ target: action.target || action.path || '',
18
+ tier: safety.tier,
19
+ approved: options.allowDangerous === true || safety.tier !== safetyManager.TIERS.DANGEROUS
20
+ });
21
+
13
22
  switch (action.type) {
14
23
  case 'open_url':
15
24
  openWebsite(action.target);
@@ -24,11 +33,9 @@ async function executeAction(action) {
24
33
  createFolder(action.target);
25
34
  return `Created folder: ${action.target}`;
26
35
  case 'open_file':
27
- await openFile(action.target);
28
- return `Opening: ${action.target}`;
29
36
  case 'open_folder':
30
- await openFile(action.target);
31
- return `Opening folder: ${action.target}`;
37
+ const res = await openFile(action.target);
38
+ return res === true ? `Opening: ${action.target}` : res;
32
39
  case 'delete_file':
33
40
  await deleteFile(action.target);
34
41
  return `Deleted: ${action.target}`;
package/mint-cli.js CHANGED
@@ -23,16 +23,27 @@ const { executeCodeTask } = require('./src/CLI/code_agent');
23
23
  const { detectCodeIntent, runChatRoutedTask } = require('./src/CLI/chat_router');
24
24
  const readline = require('readline');
25
25
  const { createChatUI } = require('./src/CLI/chat_ui');
26
+ const { runUpdate, runStartupAutoUpdate, shouldRunAutoUpdate } = require('./src/CLI/updater');
27
+ const { runGmailAuth } = require('./src/CLI/gmail_auth');
26
28
 
27
29
  // Startup Info
28
30
  const startupConfig = readConfig();
29
- const startupModel = startupConfig.geminiModel || 'gemini-2.5-flash';
31
+ const startupProvider = startupConfig.aiProvider || 'gemini';
32
+ const startupModel = startupProvider === 'openai'
33
+ ? (startupConfig.openaiModel || 'gpt-4o')
34
+ : startupProvider === 'anthropic'
35
+ ? (startupConfig.anthropicModel || 'claude-3-5-sonnet-latest')
36
+ : startupProvider === 'local_openai'
37
+ ? (startupConfig.localModelName || 'local-model')
38
+ : startupProvider === 'ollama'
39
+ ? (startupConfig.ollamaModel || 'llama3:latest')
40
+ : (startupConfig.geminiModel || 'gemini-2.5-flash');
30
41
  const startupNow = new Date();
31
42
  const startupTime = startupNow.toLocaleString('th-TH', {
32
43
  day: '2-digit', month: '2-digit', year: 'numeric',
33
44
  hour: '2-digit', minute: '2-digit', hour12: false
34
45
  }).replace(',', '');
35
- console.log(`\x1b[38;5;121m[Mint] v${pkg.version} | ${startupTime} | Active Model: ${startupModel}\x1b[0m`);
46
+ console.log(`\x1b[38;5;121m[Mint] v${pkg.version} | ${startupTime} | Active AI: ${startupProvider} • ${startupModel}\x1b[0m`);
36
47
 
37
48
  // ANSI Colors
38
49
  const colors = {
@@ -45,13 +56,68 @@ const colors = {
45
56
  yellow: "\x1b[33m"
46
57
  };
47
58
 
59
+ function formatProgress(info) {
60
+ if (typeof info === 'string') return `${colors.gray}[Mint Code] ${info}${colors.reset}`;
61
+
62
+ const { step, phase, action, target, message } = info;
63
+
64
+ if (action === 'ask_user') {
65
+ return `\n${colors.mint}✓${colors.reset} ${colors.bright}Ask User${colors.reset}\n${colors.gray} ${target || message || ''}${colors.reset}`;
66
+ }
67
+
68
+ let icon = `${colors.mint}✓${colors.reset}`;
69
+ let label = action || phase;
70
+ let color = colors.reset;
71
+
72
+ switch (action) {
73
+ case 'thinking':
74
+ return `\n${colors.yellow}* ${colors.bright}Thinking${colors.reset}`;
75
+ case 'web_search': label = 'WebSearch'; break;
76
+ case 'list_files':
77
+ case 'find_path': label = 'Explored'; break;
78
+ case 'read_file': label = 'ReadFile'; break;
79
+ case 'search_code': label = 'SearchText'; break;
80
+ case 'apply_patch':
81
+ case 'write_file': label = 'Edited'; break;
82
+ case 'run_shell': label = 'Ran command'; break;
83
+ case 'json_repair': icon = '*'; label = 'Repairing JSON'; break;
84
+ case 'reviewer_start': label = 'Reviewing'; break;
85
+ }
86
+
87
+ const content = target || message || '';
88
+ return ` ${icon} ${colors.bright}${label}${colors.reset} ${color}${content}${colors.reset}`;
89
+ }
90
+
48
91
  const program = new Command();
49
92
 
50
93
  program
51
- .name('mint-ai')
94
+ .name('mint')
52
95
  .description('Mint - Your Personal AI Assistant CLI')
53
96
  .version(pkg.version);
54
97
 
98
+ program.hook('preAction', async (thisCommand, actionCommand) => {
99
+ if (actionCommand.name() === 'update' || process.env.MINT_SKIP_AUTO_UPDATE === '1') {
100
+ return;
101
+ }
102
+
103
+ const config = readConfig();
104
+ if (config.enableAutoUpdate === false) {
105
+ return;
106
+ }
107
+
108
+ if (!shouldRunAutoUpdate(config)) {
109
+ return;
110
+ }
111
+
112
+ console.log(`${colors.gray}[Mint Update] Checking for updates...${colors.reset}`);
113
+ const result = await runStartupAutoUpdate(config, writeConfig);
114
+ if (result.status === 'updated') {
115
+ console.log(`${colors.mint}[Mint Update] ${result.message}${colors.reset}`);
116
+ } else if (result.status === 'error') {
117
+ console.log(`${colors.gray}[Mint Update] ${result.message}${colors.reset}`);
118
+ }
119
+ });
120
+
55
121
  // Chat Command (Interactive Mode)
56
122
  program
57
123
  .command('chat', { isDefault: true })
@@ -107,6 +173,128 @@ program
107
173
  console.log(`${colors.gray}You will receive a notification when it's done.${colors.reset}\n`);
108
174
  });
109
175
 
176
+ program
177
+ .command('update')
178
+ .description('Check for and install the latest Mint CLI version from npm')
179
+ .option('--check', 'Only check whether an update is available')
180
+ .option('--dry-run', 'Show the npm update operation without installing')
181
+ .action(async (options) => {
182
+ console.log(`\n${colors.mint}${colors.bright}[Mint Update]${colors.reset} Checking npm for updates...`);
183
+
184
+ try {
185
+ const result = await runUpdate({
186
+ checkOnly: options.check === true,
187
+ dryRun: options.dryRun === true
188
+ });
189
+
190
+ const color = result.status === 'error' ? colors.pink : colors.mint;
191
+ console.log(`${color}${result.message}${colors.reset}\n`);
192
+
193
+ if (result.status === 'error') {
194
+ process.exitCode = 1;
195
+ }
196
+ } catch (error) {
197
+ console.error(`${colors.pink}Update failed: ${error.message}${colors.reset}\n`);
198
+ process.exitCode = 1;
199
+ }
200
+ });
201
+
202
+ program
203
+ .command('mcp')
204
+ .description('Manage MCP (Model Context Protocol) servers')
205
+ .addCommand(new Command('add')
206
+ .description('Add a new MCP server')
207
+ .argument('<name>', 'Server name')
208
+ .argument('<command>', 'Command to run (e.g. npx)')
209
+ .option('-a, --args <args...>', 'Command arguments')
210
+ .option('-e, --env <env...>', 'Environment variables (KEY=VALUE)')
211
+ .action((name, command, options) => {
212
+ const config = readConfig();
213
+ const mcpServers = config.mcpServers || {};
214
+
215
+ const env = {};
216
+ if (options.env) {
217
+ options.env.forEach(kv => {
218
+ const [k, v] = kv.split('=');
219
+ if (k && v) env[k] = v;
220
+ });
221
+ }
222
+
223
+ mcpServers[name] = {
224
+ command,
225
+ args: options.args || [],
226
+ env
227
+ };
228
+
229
+ config.mcpServers = mcpServers;
230
+ writeConfig(config);
231
+ console.log(`\n${colors.mint}✓${colors.reset} MCP server "${name}" added successfully.`);
232
+ })
233
+ )
234
+ .addCommand(new Command('remove')
235
+ .description('Remove an MCP server')
236
+ .argument('<name>', 'Server name')
237
+ .action((name) => {
238
+ const config = readConfig();
239
+ if (config.mcpServers && config.mcpServers[name]) {
240
+ delete config.mcpServers[name];
241
+ writeConfig(config);
242
+ console.log(`\n${colors.mint}✓${colors.reset} MCP server "${name}" removed.`);
243
+ } else {
244
+ console.log(`\n${colors.pink}✗${colors.reset} MCP server "${name}" not found.`);
245
+ }
246
+ })
247
+ )
248
+ .addCommand(new Command('list')
249
+ .description('List configured MCP servers')
250
+ .action(() => {
251
+ const config = readConfig();
252
+ const servers = Object.keys(config.mcpServers || {});
253
+ if (servers.length === 0) {
254
+ console.log(`\n${colors.gray}No MCP servers configured.${colors.reset}`);
255
+ } else {
256
+ console.log(`\n${colors.bright}Configured MCP Servers:${colors.reset}`);
257
+ servers.forEach(name => {
258
+ const s = config.mcpServers[name];
259
+ console.log(`${colors.mint}• ${colors.bright}${name}${colors.reset}`);
260
+ console.log(` ${colors.gray}Command:${colors.reset} ${s.command} ${(s.args || []).join(' ')}`);
261
+ });
262
+ }
263
+ })
264
+ )
265
+ .addCommand(new Command('clear')
266
+ .description('Remove all MCP servers')
267
+ .action(() => {
268
+ const config = readConfig();
269
+ config.mcpServers = {};
270
+ writeConfig(config);
271
+ console.log(`\n${colors.mint}✓${colors.reset} All MCP servers cleared.`);
272
+ })
273
+ );
274
+
275
+ program
276
+ .command('gmail')
277
+ .description('Manage Gmail integration')
278
+ .addCommand(new Command('auth')
279
+ .description('Open Google OAuth login and save a Gmail refresh token')
280
+ .option('--port <port>', 'Local callback port, defaults to a random available port')
281
+ .option('--no-open', 'Print the auth link without opening a browser')
282
+ .action(async (options) => {
283
+ try {
284
+ const result = await runGmailAuth({
285
+ port: options.port ? Number(options.port) : 0,
286
+ openBrowser: options.open,
287
+ logger: console
288
+ });
289
+ console.log(`\n${colors.mint}✓${colors.reset} Gmail connected for ${result.userId}. Refresh token saved.`);
290
+ console.log(`${colors.gray}Scopes: ${result.scopes.join(', ')}${colors.reset}\n`);
291
+ } catch (error) {
292
+ console.error(`\n${colors.pink}Gmail auth failed:${colors.reset} ${error.message}\n`);
293
+ process.exitCode = 1;
294
+ }
295
+ })
296
+ );
297
+
110
298
  program
111
299
  .command('code')
112
300
  .description('Run Mint in workspace-aware coding mode for the current project')
@@ -118,8 +306,8 @@ program
118
306
  try {
119
307
  const result = await executeCodeTask(task, {
120
308
  cwd: process.cwd(),
121
- onProgress: (message) => {
122
- console.log(`${colors.gray}[Mint Code] ${message}${colors.reset}`);
309
+ onProgress: (info) => {
310
+ console.log(formatProgress(info));
123
311
  },
124
312
  requestApproval: requestCodeApproval
125
313
  });
@@ -134,15 +322,21 @@ program
134
322
  }
135
323
  });
136
324
 
137
- program.parse(process.argv);
325
+ program.parseAsync(process.argv).catch((error) => {
326
+ console.error(`${colors.pink}${error.message}${colors.reset}`);
327
+ process.exitCode = 1;
328
+ });
138
329
 
139
330
  /**
140
331
  * The Interactive Chat Loop — Gemini-style TUI
141
332
  */
142
333
  async function startInteractiveChat(initialMessage = null) {
143
334
  let lastResponseText = "";
144
- const { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode } = createChatUI({
335
+ const formatErrorMessage = (err) => err && err.message ? err.message : String(err || 'Unknown error');
336
+
337
+ const ui = await createChatUI({
145
338
  onSubmit: async (text) => {
339
+ const { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode, appendCodeStep, updateWorkspace, askUser } = ui;
146
340
  if (text.startsWith('/')) {
147
341
  if (text.startsWith('/agent')) {
148
342
  const args = text.split(' ');
@@ -200,9 +394,25 @@ async function startInteractiveChat(initialMessage = null) {
200
394
  } else {
201
395
  appendMessage('error', `Workspace "${name}" not found.`);
202
396
  }
397
+ } else if (subCmd === 'use' || subCmd === 'switch') {
398
+ const name = args[2];
399
+ const all = workspaceManager.listWorkspaces();
400
+ if (all[name]) {
401
+ const newPath = all[name].path;
402
+ try {
403
+ process.chdir(newPath);
404
+ updateWorkspace(newPath);
405
+ appendMessage('system', `✓ Switched to workspace "${name}" at ${newPath}`);
406
+ resetChat();
407
+ } catch (e) {
408
+ appendMessage('error', `Failed to change directory: ${e.message}`);
409
+ }
410
+ } else {
411
+ appendMessage('error', `Workspace "${name}" not found. Try /workspace list`);
412
+ }
203
413
  } else {
204
414
  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>");
415
+ 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
416
  }
207
417
  return;
208
418
  }
@@ -219,43 +429,15 @@ async function startInteractiveChat(initialMessage = null) {
219
429
  // Other slash commands
220
430
  const fakeRl = { close: () => { } };
221
431
  appendMessage('user', text);
222
- await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode);
432
+ await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace);
223
433
  return;
224
434
  }
225
435
  }
226
436
  appendMessage('user', text);
227
437
 
228
438
  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');
439
+ if (setMode) setMode('Agent');
257
440
 
258
- // Start thinking timer
259
441
  let seconds = 0;
260
442
  setThinking(true, seconds);
261
443
  const timer = setInterval(() => {
@@ -265,100 +447,34 @@ async function startInteractiveChat(initialMessage = null) {
265
447
 
266
448
  try {
267
449
  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;
450
+ const availableProviders = require('./src/System/config_manager').getAvailableProviders(config);
451
+ const preferredProvider = require('./src/CLI/code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
452
+
453
+ const result = await executeCodeTask(text, {
454
+ cwd: process.cwd(),
455
+ requestApproval,
456
+ askUser,
457
+ provider: preferredProvider,
458
+ history: transcript,
459
+ onProgress: (info) => {
460
+ if (appendCodeStep) appendCodeStep(info);
329
461
  }
462
+ });
330
463
 
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
- }
464
+ clearInterval(timer);
465
+ setThinking(false);
466
+ lastResponseText = result.summary;
467
+ appendMessage('assistant', result.summary, { providerInfo: result.providerInfo });
339
468
 
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
469
  } catch (err) {
355
470
  clearInterval(timer);
356
471
  setThinking(false);
357
- appendMessage('error', err.message);
472
+ appendMessage('error', formatErrorMessage(err));
473
+ } finally {
474
+ if (setMode) setMode('Chat');
358
475
  }
359
476
  },
360
477
  onExit: () => {
361
- screen.destroy();
362
478
  // Explicitly restore terminal state and disable ALL mouse tracking modes
363
479
  process.stdout.write('\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l');
364
480
  process.stdout.write('\x1b[?25h'); // Show cursor
@@ -369,73 +485,45 @@ async function startInteractiveChat(initialMessage = null) {
369
485
 
370
486
  // Handle initial message if passed via CLI arg
371
487
  if (initialMessage) {
488
+ const { appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode, appendCodeStep, updateWorkspace, askUser } = ui;
372
489
  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;
490
+ const transcript = await getChatTranscript();
491
+ if (setMode) setMode('Agent');
492
+
493
+ let seconds = 0;
494
+ setThinking(true, seconds);
495
+ const timer = setInterval(() => {
496
+ seconds++;
421
497
  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}`);
498
+ }, 1000);
499
+
500
+ try {
501
+ const config = require('./src/System/config_manager').readConfig();
502
+ const availableProviders = require('./src/System/config_manager').getAvailableProviders(config);
503
+ const preferredProvider = require('./src/CLI/code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
504
+
505
+ const result = await executeCodeTask(initialMessage, {
506
+ cwd: process.cwd(),
507
+ requestApproval,
508
+ askUser,
509
+ provider: preferredProvider,
510
+ history: transcript,
511
+ onProgress: (info) => {
512
+ if (appendCodeStep) appendCodeStep(info);
433
513
  }
434
- } catch (err) {
435
- clearInterval(timer);
436
- setThinking(false);
437
- appendMessage('error', err.message);
438
- }
514
+ });
515
+
516
+ clearInterval(timer);
517
+ setThinking(false);
518
+ lastResponseText = result.summary;
519
+ appendMessage('assistant', result.summary, { providerInfo: result.providerInfo });
520
+
521
+ } catch (err) {
522
+ clearInterval(timer);
523
+ setThinking(false);
524
+ appendMessage('error', formatErrorMessage(err));
525
+ } finally {
526
+ if (setMode) setMode('Chat');
439
527
  }
440
528
  }
441
529
  }
@@ -443,7 +531,7 @@ async function startInteractiveChat(initialMessage = null) {
443
531
  /**
444
532
  * Handles slash commands within the TUI context
445
533
  */
446
- async function handleSlashCommandUI(input, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode) {
534
+ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace) {
447
535
  const parts = input.split(' ');
448
536
  const command = parts[0].toLowerCase();
449
537
  const args = parts.slice(1);
@@ -453,16 +541,36 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
453
541
  case '/?':
454
542
  appendMessage('system', [
455
543
  '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'
544
+ ' /code <task> — Force workspace Code Mode',
545
+ ' /cd <path> Change current working directory',
546
+ ' /models [name] List or switch Gemini models',
547
+ ' /config Show current configuration',
548
+ ' /copy Copy last response to clipboard',
549
+ ' /clear Clear conversation history',
550
+ ' /resetReset conversation history',
551
+ ' /exit — Exit Mint'
463
552
  ].join('\n'));
464
553
  break;
465
554
 
555
+ case '/cd':
556
+ if (args.length === 0) {
557
+ appendMessage('system', `Current Directory: ${process.cwd()}`);
558
+ break;
559
+ }
560
+ try {
561
+ const newPath = path.resolve(process.cwd(), args[0]);
562
+ if (fs.existsSync(newPath) && fs.lstatSync(newPath).isDirectory()) {
563
+ process.chdir(newPath);
564
+ if (updateWorkspace) updateWorkspace(newPath);
565
+ appendMessage('system', `✓ Directory changed to: ${newPath}`);
566
+ } else {
567
+ appendMessage('error', `Directory not found: ${newPath}`);
568
+ }
569
+ } catch (err) {
570
+ appendMessage('error', `Error: ${err.message}`);
571
+ }
572
+ break;
573
+
466
574
  case '/model':
467
575
  case '/models':
468
576
  const config = readConfig();
@@ -521,7 +629,9 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
521
629
  appendMessage,
522
630
  setThinking,
523
631
  requestApproval,
632
+ appendCodeStep,
524
633
  setMode,
634
+ askUser: () => Promise.resolve(''),
525
635
  history: await getChatTranscript()
526
636
  });
527
637
  break;