@pheem49/mint 1.2.3 → 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.
package/main.js CHANGED
@@ -13,21 +13,22 @@ const { parseCommand } = require('./src/Command_Parser/parser');
13
13
  const pluginManager = require('./src/Plugins/plugin_manager');
14
14
  const { analyzeAndSuggest } = require('./src/AI_Brain/proactive_engine');
15
15
  const { recordBehavior, getBehaviorSummary } = require('./src/AI_Brain/behavior_memory');
16
- const { indexFile } = require('./src/AI_Brain/knowledge_base');
16
+ const { indexFile, indexFolder } = require('./src/AI_Brain/knowledge_base');
17
+
17
18
  const SystemAutomation = require('./src/System/system_automation');
18
19
  const systemEvents = require('./src/System/system_events');
19
20
  const customWorkflows = require('./src/System/custom_workflows');
21
+ const mcpManager = require('./src/Plugins/mcp_manager');
22
+ const granularAutomation = require('./src/System/granular_automation');
20
23
  const googleTTS = require('google-tts-api');
21
24
 
22
25
  let mainWindow;
23
26
  let settingsWindow = null;
24
27
  let screenPickerWindow = null;
25
28
  let spotlightWindow = null;
26
- let floatingWindow = null;
27
29
  let widgetWindow = null;
28
30
  let proactiveGlowWindow = null;
29
31
  let tray = null;
30
- let floatingUnreadCount = 0;
31
32
 
32
33
  // =====================
33
34
  // Proactive Loop
@@ -192,7 +193,7 @@ function createWindow() {
192
193
  });
193
194
 
194
195
  mainWindow.on('focus', () => {
195
- clearFloatingUnread();
196
+ // clearFloatingUnread(); // Disabled
196
197
  });
197
198
  }
198
199
 
@@ -291,41 +292,7 @@ function createSpotlightWindow() {
291
292
  });
292
293
  }
293
294
 
294
- function createFloatingWindow() {
295
- if (floatingWindow) return;
296
- const display = screen.getPrimaryDisplay();
297
- const { x, y, width, height } = display.workArea;
298
- const size = 72;
299
- const margin = 20;
300
- const posX = x + width - size - margin;
301
- const posY = y + height - size - margin - 40;
302
-
303
- floatingWindow = new BrowserWindow({
304
- width: size,
305
- height: size,
306
- x: posX,
307
- y: posY,
308
- frame: false,
309
- transparent: true,
310
- resizable: false,
311
- alwaysOnTop: true,
312
- skipTaskbar: true,
313
- show: true,
314
- webPreferences: {
315
- preload: path.join(__dirname, 'src/UI/preload-floating.js'),
316
- nodeIntegration: false,
317
- contextIsolation: true
318
- }
319
- });
320
-
321
- floatingWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
322
- floatingWindow.setAlwaysOnTop(true, 'floating');
323
- floatingWindow.loadFile('src/UI/floating.html');
324
-
325
- floatingWindow.on('closed', () => {
326
- floatingWindow = null;
327
- });
328
- }
295
+ // Floating window logic removed
329
296
 
330
297
  function createWidgetWindow() {
331
298
  if (widgetWindow) return;
@@ -363,28 +330,21 @@ function createWidgetWindow() {
363
330
  });
364
331
  }
365
332
 
366
- function updateFloatingUnread() {
367
- if (floatingWindow && !floatingWindow.isDestroyed()) {
368
- floatingWindow.webContents.send('floating-notify', floatingUnreadCount);
369
- }
370
- }
371
-
372
- function clearFloatingUnread() {
373
- if (floatingUnreadCount === 0) return;
374
- floatingUnreadCount = 0;
375
- updateFloatingUnread();
376
- }
333
+ // Floating unread logic removed
377
334
 
378
335
  app.whenReady().then(() => {
379
336
  const config = readConfig();
380
337
  createWindow();
381
338
  createTray();
382
- createFloatingWindow();
339
+ // createFloatingWindow(); // Removed
383
340
 
384
341
  // Only show AI widget if enabled in settings
385
342
  if (config.showDesktopWidget !== false) {
386
343
  createWidgetWindow();
387
344
  }
345
+
346
+ // Initialize MCP Servers
347
+ mcpManager.init().catch(err => console.error('[MCP] Init Error:', err));
388
348
 
389
349
  // Start monitoring system events (battery, wifi, etc.)
390
350
  systemEvents.startMonitoring();
@@ -395,7 +355,7 @@ app.whenReady().then(() => {
395
355
  systemEvents.on('low-battery', (level) => {
396
356
  if (mainWindow && !mainWindow.isDestroyed()) {
397
357
  mainWindow.webContents.send('proactive-notification', {
398
- message: `⚠️ แบตเตอรี่เหลือน้อยแล้วนะคะ (${level}%) อย่าลืมเสียบสายชาร์จนะค๊า ✨`,
358
+ message: `⚠️ Battery is low (${level}%). Please plug in your charger. ✨`,
399
359
  type: 'warning'
400
360
  });
401
361
  }
@@ -403,7 +363,7 @@ app.whenReady().then(() => {
403
363
 
404
364
  systemEvents.on('connection-change', (isOnline) => {
405
365
  if (mainWindow && !mainWindow.isDestroyed()) {
406
- const msg = isOnline ? '✅ เชื่อมต่ออินเทอร์เน็ตได้แล้วค่ะ ✨' : '❌ การเชื่อมต่ออินเทอร์เน็ตขาดหายไปนะคะ';
366
+ const msg = isOnline ? '✅ Internet connection restored. ✨' : '❌ Internet connection lost.';
407
367
  mainWindow.webContents.send('proactive-notification', { message: msg, type: 'info' });
408
368
  }
409
369
  });
@@ -438,6 +398,7 @@ app.on('window-all-closed', (e) => {
438
398
 
439
399
  app.on('will-quit', () => {
440
400
  globalShortcut.unregisterAll();
401
+ mcpManager.shutdown();
441
402
  });
442
403
 
443
404
  // =====================
@@ -473,7 +434,7 @@ ipcMain.on('close-window', (event) => {
473
434
  });
474
435
 
475
436
  ipcMain.on('minimize-window', (event) => {
476
- if (mainWindow) mainWindow.hide();
437
+ if (mainWindow) mainWindow.minimize();
477
438
  });
478
439
 
479
440
  ipcMain.on('quit-app', (event) => {
@@ -512,6 +473,7 @@ ipcMain.handle('get-settings', () => {
512
473
  });
513
474
 
514
475
  ipcMain.handle('save-settings', (event, config) => {
476
+ console.log('[Settings] Saving new config. MCP Servers count:', Object.keys(config.mcpServers || {}).length);
515
477
  const result = writeConfig(config);
516
478
  // Refresh API key if user updated it in settings
517
479
  refreshApiKeyFromConfig();
@@ -597,33 +559,7 @@ ipcMain.on('spotlight-resize', (event, width, height) => {
597
559
  }
598
560
  });
599
561
 
600
- // =====================
601
- // IPC — Floating Icon
602
- // =====================
603
- ipcMain.on('floating-click', () => {
604
- if (mainWindow) {
605
- mainWindow.show();
606
- mainWindow.focus();
607
- }
608
- clearFloatingUnread();
609
- });
610
-
611
- ipcMain.on('ai-notify', () => {
612
- floatingUnreadCount += 1;
613
- updateFloatingUnread();
614
- });
615
-
616
- ipcMain.on('ai-notify-clear', () => {
617
- clearFloatingUnread();
618
- });
619
-
620
- ipcMain.on('floating-drag-move', (_event, x, y) => {
621
- if (!floatingWindow || floatingWindow.isDestroyed()) return;
622
- const [width, height] = floatingWindow.getSize();
623
- const posX = Math.round(x - width / 2);
624
- const posY = Math.round(y - height / 2);
625
- floatingWindow.setBounds({ x: posX, y: posY, width, height });
626
- });
562
+ // Floating IPC removed
627
563
 
628
564
  ipcMain.handle('open-external', (event, url) => {
629
565
  shell.openExternal(url);
@@ -924,8 +860,11 @@ async function executeAction(action) {
924
860
  createFolder(action.target);
925
861
  break;
926
862
  case 'open_file':
927
- await openFile(action.target);
928
- break;
863
+ const fileRes = await openFile(action.target);
864
+ return fileRes || `Successfully opened file: ${action.target} ✅`;
865
+ case 'open_folder':
866
+ const folderRes = await openFile(action.target);
867
+ return folderRes || `Successfully opened folder: ${action.target} ✅`;
929
868
  case 'delete_file':
930
869
  await deleteFile(action.target);
931
870
  break;
@@ -934,6 +873,20 @@ async function executeAction(action) {
934
873
  break;
935
874
  case 'learn_file':
936
875
  return await indexFile(action.target);
876
+ case 'learn_folder':
877
+ const { indexFolder } = require('./src/AI_Brain/knowledge_base');
878
+ return await indexFolder(action.target);
879
+ case 'mcp_tool':
880
+ const mcpResult = await mcpManager.callTool(action.server, action.target, action.args);
881
+ return JSON.stringify(mcpResult.content);
882
+ case 'mouse_move':
883
+ return await granularAutomation.mouseMove(action.x, action.y);
884
+ case 'mouse_click':
885
+ return await granularAutomation.mouseClick(action.x, action.y, action.button || 1);
886
+ case 'type_text':
887
+ return await granularAutomation.typeText(action.target);
888
+ case 'key_tap':
889
+ return await granularAutomation.keyTap(action.target);
937
890
  case 'plugin':
938
891
  return await pluginManager.executePlugin(action.pluginName, action.target);
939
892
  case 'system_automation':
package/mint-cli-logic.js CHANGED
@@ -26,11 +26,30 @@ async function executeAction(action) {
26
26
  case 'open_file':
27
27
  await openFile(action.target);
28
28
  return `Opening: ${action.target}`;
29
+ case 'open_folder':
30
+ await openFile(action.target);
31
+ return `Opening folder: ${action.target}`;
29
32
  case 'delete_file':
30
33
  await deleteFile(action.target);
31
34
  return `Deleted: ${action.target}`;
32
35
  case 'learn_file':
33
36
  return await indexFile(action.target);
37
+ case 'mcp_tool':
38
+ const mcpManager = require('./src/Plugins/mcp_manager');
39
+ const mcpResult = await mcpManager.callTool(action.server, action.target, action.args);
40
+ return JSON.stringify(mcpResult.content);
41
+ case 'mouse_move':
42
+ const granularAutomation = require('./src/System/granular_automation');
43
+ return await granularAutomation.mouseMove(action.x, action.y);
44
+ case 'mouse_click':
45
+ const granularAutomationClick = require('./src/System/granular_automation');
46
+ return await granularAutomationClick.mouseClick(action.x, action.y, action.button || 1);
47
+ case 'type_text':
48
+ const granularAutomationType = require('./src/System/granular_automation');
49
+ return await granularAutomationType.typeText(action.target);
50
+ case 'key_tap':
51
+ const granularAutomationKey = require('./src/System/granular_automation');
52
+ return await granularAutomationKey.keyTap(action.target);
34
53
  case 'plugin':
35
54
  return await pluginManager.executePlugin(action.pluginName, action.target);
36
55
  case 'system_automation':
package/mint-cli.js CHANGED
@@ -2,10 +2,13 @@
2
2
  require('dotenv').config({ quiet: true });
3
3
  const { Command } = require('commander');
4
4
  const { handleChat, resetChat } = require('./src/AI_Brain/Gemini_API');
5
+ const pkg = require('./package.json');
5
6
  const { runOnboarding } = require('./src/CLI/onboarding');
6
7
  const { startAgent } = require('./src/AI_Brain/headless_agent');
7
8
  const { displayFeatures } = require('./src/CLI/list_features');
8
9
  const { readConfig, writeConfig } = require('./src/System/config_manager');
10
+ const { executeCodeTask } = require('./src/CLI/code_agent');
11
+ const { detectCodeIntent, runChatRoutedTask } = require('./src/CLI/chat_router');
9
12
  const readline = require('readline');
10
13
  const { createChatUI } = require('./src/CLI/chat_ui');
11
14
 
@@ -17,7 +20,7 @@ const startupTime = startupNow.toLocaleString('th-TH', {
17
20
  day: '2-digit', month: '2-digit', year: 'numeric',
18
21
  hour: '2-digit', minute: '2-digit', hour12: false
19
22
  }).replace(',', '');
20
- console.log(`\x1b[38;5;121m[Mint] ${startupTime} | Active Model: ${startupModel}\x1b[0m`);
23
+ console.log(`\x1b[38;5;121m[Mint] v${pkg.version} | ${startupTime} | Active Model: ${startupModel}\x1b[0m`);
21
24
 
22
25
  // ANSI Colors
23
26
  const colors = {
@@ -35,7 +38,7 @@ const program = new Command();
35
38
  program
36
39
  .name('mint-ai')
37
40
  .description('Mint - Your Personal AI Assistant CLI')
38
- .version('1.0.0');
41
+ .version(pkg.version);
39
42
 
40
43
  // Chat Command (Interactive Mode)
41
44
  program
@@ -92,23 +95,64 @@ program
92
95
  console.log(`${colors.gray}You will receive a notification when it's done.${colors.reset}\n`);
93
96
  });
94
97
 
98
+ program
99
+ .command('code')
100
+ .description('Run Mint in workspace-aware coding mode for the current project')
101
+ .argument('<task>', 'Coding task to execute in the current working directory')
102
+ .action(async (task) => {
103
+ console.log(`\n${colors.mint}${colors.bright}[Mint Code]${colors.reset} Workspace: ${process.cwd()}`);
104
+ console.log(`${colors.gray}[Mint Code] Task: ${task}${colors.reset}\n`);
105
+
106
+ try {
107
+ const result = await executeCodeTask(task, {
108
+ cwd: process.cwd(),
109
+ onProgress: (message) => {
110
+ console.log(`${colors.gray}[Mint Code] ${message}${colors.reset}`);
111
+ },
112
+ requestApproval: requestCodeApproval
113
+ });
114
+
115
+ console.log(`\n${colors.mint}${colors.bright}Summary${colors.reset}`);
116
+ console.log(result.summary);
117
+ console.log(`\n${colors.cyan}Verification:${colors.reset} ${result.verification}`);
118
+ console.log(`${colors.gray}Completed in ${result.steps} step(s).${colors.reset}\n`);
119
+ } catch (error) {
120
+ console.error(`\n${colors.pink}[Mint Code Error]${colors.reset} ${error.message}\n`);
121
+ process.exitCode = 1;
122
+ }
123
+ });
124
+
95
125
  program.parse(process.argv);
96
126
 
97
127
  /**
98
128
  * The Interactive Chat Loop — Gemini-style TUI
99
129
  */
100
130
  async function startInteractiveChat(initialMessage = null) {
101
- const { screen, appendMessage, setThinking, updateStatusModel, copyLastResponse } = createChatUI({
131
+ const { screen, appendMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode } = createChatUI({
102
132
  onSubmit: async (text) => {
103
133
  if (text.startsWith('/')) {
104
134
  // Slash commands via fake rl-compatible object
105
135
  const fakeRl = { close: () => { } };
106
136
  appendMessage('user', text);
107
- await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse);
137
+ await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode);
108
138
  return;
109
139
  }
110
140
  appendMessage('user', text);
111
141
 
142
+ const routeDecision = await detectCodeIntent(text, process.cwd());
143
+ if (routeDecision.route === 'code') {
144
+ appendMessage('system', `Router: entering Code Mode. ${routeDecision.reason}`);
145
+ await runChatRoutedTask(text, {
146
+ appendMessage,
147
+ setThinking,
148
+ requestApproval,
149
+ setMode
150
+ });
151
+ return;
152
+ }
153
+
154
+ setMode('Chat');
155
+
112
156
  // Start thinking timer
113
157
  let seconds = 0;
114
158
  setThinking(true, seconds);
@@ -148,18 +192,30 @@ async function startInteractiveChat(initialMessage = null) {
148
192
  // Handle initial message if passed via CLI arg
149
193
  if (initialMessage) {
150
194
  appendMessage('user', initialMessage);
151
- let seconds = 0;
152
- setThinking(true, seconds);
153
- const timer = setInterval(() => { seconds++; setThinking(true, seconds); }, 1000);
154
- try {
155
- const response = await handleChat(initialMessage);
156
- clearInterval(timer);
157
- setThinking(false);
158
- appendMessage('assistant', response.response, response.timestamp);
159
- } catch (err) {
160
- clearInterval(timer);
161
- setThinking(false);
162
- appendMessage('error', err.message);
195
+ const routeDecision = await detectCodeIntent(initialMessage, process.cwd());
196
+ if (routeDecision.route === 'code') {
197
+ appendMessage('system', `Router: entering Code Mode. ${routeDecision.reason}`);
198
+ await runChatRoutedTask(initialMessage, {
199
+ appendMessage,
200
+ setThinking,
201
+ requestApproval,
202
+ setMode
203
+ });
204
+ } else {
205
+ setMode('Chat');
206
+ let seconds = 0;
207
+ setThinking(true, seconds);
208
+ const timer = setInterval(() => { seconds++; setThinking(true, seconds); }, 1000);
209
+ try {
210
+ const response = await handleChat(initialMessage);
211
+ clearInterval(timer);
212
+ setThinking(false);
213
+ appendMessage('assistant', response.response, response.timestamp);
214
+ } catch (err) {
215
+ clearInterval(timer);
216
+ setThinking(false);
217
+ appendMessage('error', err.message);
218
+ }
163
219
  }
164
220
  }
165
221
  }
@@ -167,7 +223,7 @@ async function startInteractiveChat(initialMessage = null) {
167
223
  /**
168
224
  * Handles slash commands within the TUI context
169
225
  */
170
- async function handleSlashCommandUI(input, appendMessage, updateStatusModel, copyLastResponse) {
226
+ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode) {
171
227
  const parts = input.split(' ');
172
228
  const command = parts[0].toLowerCase();
173
229
  const args = parts.slice(1);
@@ -177,6 +233,7 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
177
233
  case '/?':
178
234
  appendMessage('system', [
179
235
  'Mint Slash Commands:',
236
+ ' /code <task> — Force workspace Code Mode',
180
237
  ' /models [name] — List or switch Gemini models',
181
238
  ' /config — Show current configuration',
182
239
  ' /copy — Copy last response to clipboard',
@@ -214,10 +271,24 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
214
271
  }
215
272
  break;
216
273
 
274
+ case '/code':
275
+ if (args.length === 0) {
276
+ appendMessage('system', 'Usage: /code <task>');
277
+ break;
278
+ }
279
+ await runChatRoutedTask(`/code ${args.join(' ')}`, {
280
+ appendMessage,
281
+ setThinking,
282
+ requestApproval,
283
+ setMode
284
+ });
285
+ break;
286
+
217
287
  case '/config':
218
288
  const currentCfg = readConfig();
219
289
  appendMessage('system', [
220
290
  'Current Configuration:',
291
+ ` Version : v${pkg.version}`,
221
292
  ` Provider : ${currentCfg.aiProvider}`,
222
293
  ` Model : ${currentCfg.geminiModel}`,
223
294
  ` Ollama : ${currentCfg.ollamaModel}`,
@@ -251,3 +322,36 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
251
322
  }
252
323
  }
253
324
 
325
+ async function requestCodeApproval(request) {
326
+ const typeLabel = request.type === 'shell'
327
+ ? 'Shell Command'
328
+ : request.type === 'patch'
329
+ ? 'Patch Edit'
330
+ : 'File Write';
331
+
332
+ console.log(`\n${colors.yellow}${colors.bright}[Approval Required]${colors.reset} ${typeLabel}`);
333
+ if (request.label) {
334
+ console.log(`${colors.gray}${request.label}${colors.reset}`);
335
+ }
336
+ if (request.preview) {
337
+ console.log(`${colors.gray}${request.preview}${colors.reset}\n`);
338
+ }
339
+
340
+ const rl = readline.createInterface({
341
+ input: process.stdin,
342
+ output: process.stdout
343
+ });
344
+
345
+ const answer = await new Promise((resolve) => {
346
+ rl.question('Approve this action? [y/N]: ', (value) => {
347
+ rl.close();
348
+ resolve((value || '').trim().toLowerCase());
349
+ });
350
+ });
351
+
352
+ const approved = answer === 'y' || answer === 'yes';
353
+ console.log(approved
354
+ ? `${colors.mint}[Mint Code] Approved.${colors.reset}\n`
355
+ : `${colors.pink}[Mint Code] Denied.${colors.reset}\n`);
356
+ return approved;
357
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pheem49/mint",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
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": {
@@ -22,24 +22,30 @@
22
22
  "dependencies": {
23
23
  "@google/genai": "^1.44.0",
24
24
  "@inkjs/ui": "^2.0.0",
25
+ "@modelcontextprotocol/sdk": "^1.29.0",
25
26
  "axios": "^1.13.6",
26
27
  "blessed": "^0.1.81",
27
28
  "cheerio": "^1.2.0",
28
29
  "commander": "^14.0.3",
29
30
  "dotenv": "^17.3.1",
31
+ "framer-motion": "^12.38.0",
30
32
  "google-tts-api": "^2.0.2",
31
33
  "ink": "^7.0.1",
32
34
  "ink-text-input": "^6.0.0",
33
35
  "inquirer": "^13.4.1",
36
+ "lucide-react": "^1.9.0",
34
37
  "mammoth": "^1.12.0",
35
38
  "pdf-parse": "^2.4.5",
36
39
  "puppeteer": "^24.38.0",
37
40
  "react": "^19.2.5",
41
+ "react-dom": "^19.2.5",
38
42
  "xlsx": "^0.18.5"
39
43
  },
40
44
  "devDependencies": {
45
+ "@vitejs/plugin-react": "^6.0.1",
41
46
  "electron": "^40.7.0",
42
- "electron-builder": "^26.8.1"
47
+ "electron-builder": "^26.8.1",
48
+ "vite": "^8.0.10"
43
49
  },
44
50
  "build": {
45
51
  "appId": "com.pheem49.mint",