@pheem49/mint 1.3.0 β†’ 1.4.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/README.md CHANGED
@@ -17,13 +17,17 @@
17
17
 
18
18
  ---
19
19
 
20
- **Mint** is a powerful AI Assistant built with **Electron**, **Node.js**, and **Google Gemini**. It bridges the gap between your Desktop and Terminal, featuring real-time screen vision, web automation, local knowledge search, and a professional CLI for developers who love the command line.
20
+ **Mint** is a powerful AI Assistant built with **Electron**, **Node.js**, and **Google Gemini** (with extended support for Hugging Face, Anthropic, OpenAI, and Local Models). It bridges the gap between your Desktop and Terminal, featuring real-time screen vision, web automation, local knowledge search, and a professional CLI for developers who love the command line.
21
21
 
22
- ## 🌟 Highlights
22
+ ## Highlights
23
23
 
24
24
  - **Dual-Mode AI**: Switch between a beautiful **Desktop GUI** and a professional **CLI**.
25
25
  - **Code Agent Mode**: Use `mint code "<task>"` to inspect, edit, and verify a project directly from the current terminal workspace.
26
26
  - **Chat-First Workflow**: Regular Mint chat can now auto-route coding requests into workspace Code Mode, so the main chat acts as the control surface.
27
+ - **Advanced Multi-Agent Hub**:
28
+ - **Smart Routing**: Intelligently routes coding/general tasks to the most capable configured provider (e.g., Claude, OpenAI, Gemini).
29
+ - **Agent Collaboration**: Enable Multi-Agent Review so that a primary AI writes the code while a secondary AI automatically reviews it in the same loop.
30
+ - **Resilient Fallback**: Automatically fallbacks to alternative AI providers (Gemini, Anthropic, OpenAI, Hugging Face, LM Studio, Ollama) if your active provider fails.
27
31
  - **Visible Mode State**: The CLI status bar now shows whether Mint is currently in `Chat` or `Code` mode.
28
32
  - **Interactive Slash Commands**: Manage models and settings in the terminal with `/model`, `/config`, `/clear`, etc.
29
33
  - **Smart TUI Experience**: Professional message framing, character-wrapped Thai text support, and mouse scroll wheel navigation.
@@ -36,7 +40,7 @@
36
40
 
37
41
  ---
38
42
 
39
- ## πŸ“Έ Screenshots
43
+ ## Screenshots
40
44
 
41
45
  <p align="center">
42
46
  <img src="assets/Agent_Mint.png" alt="Agent Mint UI" width="48%">
@@ -48,7 +52,7 @@
48
52
 
49
53
  ---
50
54
 
51
- ## ⌨️ CLI Usage (Power Users)
55
+ ## CLI Usage (Power Users)
52
56
 
53
57
  Mint CLI is built for speed and efficiency. Use the **`mint`** command from anywhere.
54
58
 
@@ -56,21 +60,21 @@ Mint CLI is built for speed and efficiency. Use the **`mint`** command from anyw
56
60
  <img src="assets/CLI_Screen.png" alt="Mint CLI Preview" width="100%">
57
61
  </p>
58
62
 
59
- ### πŸš€ Professional Commands
63
+ ### Professional Commands
60
64
  - **`mint`** : Start interactive chat mode (Default).
61
65
  - **`mint code "task"`** : Run Mint as a workspace-aware coding agent in the current project folder.
62
66
  - **`mint agent`** : Run Mint as a headless background agent (Monitoring mode).
63
- - **`mint agent "task"`** : **[NEW]** Start agent and execute an autonomous task immediately.
67
+ - **`mint agent "task"`** : Start agent and execute an autonomous task immediately.
64
68
  - **`mint task "task"`** : Delegate a multi-step task to an already running background agent.
65
69
  - **`mint onboard`** : Setup API Key, Model, and initialize global configuration in `~/.mint/`.
66
70
  - **`mint list`** : See full list of automation actions and plugins.
67
71
 
68
72
  ---
69
73
 
70
- ### πŸ€– Autonomous Agent (Task Delegate)
74
+ ### Autonomous Agent (Task Delegate)
71
75
  Mint isn't just a chatbotβ€”it's an autonomous worker. Assign complex tasks that require multiple steps of reasoning.
72
76
 
73
- ### πŸ‘¨β€πŸ’» Code Agent Mode
77
+ ### Code Agent Mode
74
78
  Mint now includes a dedicated coding workflow as the first step toward a Claude-Code-like experience in the terminal.
75
79
 
76
80
  **What it can do now:**
@@ -95,10 +99,10 @@ mint
95
99
  Mint will classify the request, switch the status bar to `Code`, and return to `Chat` mode after the coding flow finishes.
96
100
 
97
101
  **Supported Autonomous Tools:**
98
- - 🌐 **Web Automation**: Full Puppeteer-based browsing, info extraction, and research.
99
- - πŸ“ **File System**: Create, Write, Delete, and Manage folders using `~/` path expansion.
100
- - πŸ” **Knowledge Search**: Query local files and documentation (RAG).
101
- - πŸ›‘οΈ **Safety Mode (Bash)**: Mint proposes commands via notifications; you choose whether to run them.
102
+ - **Web Automation**: Full Puppeteer-based browsing, info extraction, and research.
103
+ - **File System**: Create, Write, Delete, and Manage folders using `~/` path expansion.
104
+ - **Knowledge Search**: Query local files and documentation (RAG).
105
+ - **Safety Mode (Bash)**: Mint proposes commands via notifications; you choose whether to run them.
102
106
 
103
107
  **Usage Examples:**
104
108
  ```bash
@@ -111,28 +115,28 @@ mint task 'Process these 5 files and move them to ~/Documents/Archive'
111
115
 
112
116
  ---
113
117
 
114
- ### ⚑ Slash Commands (Interactive Chat)
118
+ ### Slash Commands (Interactive Chat)
115
119
  While in terminal chat, type **`/`** to access advanced tools with the new visual autocomplete system.
116
120
  > [!TIP]
117
121
  > Use **Up/Down arrow keys** to navigate the suggestion list and press **Enter** to select!
118
122
 
119
123
  - `/help` : View all commands and descriptions.
120
- - `/models` : List and switch between Gemini/Ollama models.
124
+ - `/models` : List and switch between Gemini, Ollama, Hugging Face, Anthropic, OpenAI, and Local models.
121
125
  - `/config` : Check your active API keys and preferences.
122
126
  - `/clear` / `/reset` : Clean terminal or reset AI context.
123
127
 
124
128
  ---
125
129
 
126
- ### πŸ•’ Proactive Monitoring
130
+ ### Proactive Monitoring
127
131
  When running in `agent` mode, Mint monitors your system in the background:
128
- - πŸ”‹ **Battery Alerts**: Notifies you when power is low or charging status changes.
129
- - 🌐 **Network Status**: Alerts you when connection status changes.
130
- - πŸ“Š **Resource Usage**: Proactive tips if system load is too high (System Metrics).
132
+ - **Battery Alerts**: Notifies you when power is low or charging status changes.
133
+ - **Network Status**: Alerts you when connection status changes.
134
+ - **Resource Usage**: Proactive tips if system load is too high (System Metrics).
131
135
 
132
136
 
133
137
  ---
134
138
 
135
- ## 🎨 Desktop GUI Features
139
+ ## Desktop GUI Features
136
140
 
137
141
  - **Floating Widget**: A persistent AI character on your desktop.
138
142
  - **Animated Aura**: The widget breathes and glows when Mint is thinking or proactive.
@@ -142,15 +146,15 @@ When running in `agent` mode, Mint monitors your system in the background:
142
146
 
143
147
  ---
144
148
 
145
- ## πŸ›‘οΈ Security & Privacy
149
+ ## Security & Privacy
146
150
 
147
151
  - **Push Protection**: Automated `.gitignore` patterns for `mint-config.json` and `.env` files.
148
152
  - **History Scrubbing**: Integrated tools to ensure API keys are never leaked to Git history.
149
- - **Local First**: Built-in **Ollama** support for 100% private, offline AI processing.
153
+ - **Local First**: Built-in **Ollama** and **LM Studio (OpenAI Compatible)** support for 100% private, offline AI processing.
150
154
 
151
155
  ---
152
156
 
153
- ## πŸ› οΈ Getting Started
157
+ ## Getting Started
154
158
 
155
159
  ### Prerequisites
156
160
  - [Node.js](https://nodejs.org/) (LTS)
@@ -173,7 +177,7 @@ When running in `agent` mode, Mint monitors your system in the background:
173
177
 
174
178
  ---
175
179
 
176
- ## πŸ“‚ Project Structure
180
+ ## Project Structure
177
181
 
178
182
  ```text
179
183
  Mint/
@@ -191,6 +195,6 @@ Mint/
191
195
 
192
196
  ---
193
197
 
194
- ## πŸ›οΈ License
198
+ ## License
195
199
 
196
200
  Distributed under the **GNU Affero General Public License v3.0**. See `LICENSE` for details.
package/mint-cli.js CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  require('dotenv').config({ quiet: true });
3
3
  const { Command } = require('commander');
4
- const { handleChat, resetChat } = require('./src/AI_Brain/Gemini_API');
4
+ const { handleChat, handleGeminiChatStream, resetChat, refreshApiKeyFromConfig } = require('./src/AI_Brain/Gemini_API');
5
+ const agentOrchestrator = require('./src/AI_Brain/agent_orchestrator');
6
+ const workspaceManager = require('./src/CLI/workspace_manager');
7
+ const systemMonitor = require('./src/Plugins/system_monitor');
8
+ const { sendNotification } = require('./src/System/notifications');
5
9
  const pkg = require('./package.json');
6
10
  const { runOnboarding } = require('./src/CLI/onboarding');
7
11
  const { startAgent } = require('./src/AI_Brain/headless_agent');
@@ -128,14 +132,88 @@ program.parse(process.argv);
128
132
  * The Interactive Chat Loop β€” Gemini-style TUI
129
133
  */
130
134
  async function startInteractiveChat(initialMessage = null) {
131
- const { screen, appendMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode } = createChatUI({
135
+ let lastResponseText = "";
136
+ const { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode } = createChatUI({
132
137
  onSubmit: async (text) => {
133
138
  if (text.startsWith('/')) {
134
- // Slash commands via fake rl-compatible object
135
- const fakeRl = { close: () => { } };
136
- appendMessage('user', text);
137
- await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode);
138
- return;
139
+ if (text.startsWith('/agent')) {
140
+ const args = text.split(' ');
141
+ if (args[1] === 'list') {
142
+ appendMessage('system', `Available Agents: ${agentOrchestrator.listAgents().join(', ')}`);
143
+ } else if (args[1]) {
144
+ const success = agentOrchestrator.setAgent(args[1]);
145
+ if (success) {
146
+ const agent = agentOrchestrator.getCurrentAgent();
147
+ appendMessage('system', `Switched to Agent: ${agent.icon} ${agent.name}`);
148
+ updateStatusModel(null, agent.name); // Pass name to status bar
149
+ resetChat(); // Reset to apply new system prompt
150
+ } else {
151
+ appendMessage('error', `Agent "${args[1]}" not found. Try /agent list`);
152
+ }
153
+ } else {
154
+ const agent = agentOrchestrator.getCurrentAgent();
155
+ appendMessage('system', `Current Agent: ${agent.icon} ${agent.name}\nUsage: /agent <type> or /agent list`);
156
+ }
157
+ return;
158
+ }
159
+
160
+ if (text.startsWith('/stats')) {
161
+ appendMessage('system', 'πŸ“Š Fetching system statistics...');
162
+ const stats = await systemMonitor.execute('stats');
163
+ appendMessage('system', stats);
164
+ return;
165
+ }
166
+
167
+ if (text.startsWith('/workspace')) {
168
+ const args = text.split(' ');
169
+ const subCmd = args[1];
170
+
171
+ if (subCmd === 'add') {
172
+ const name = args[2];
173
+ const wsPath = args[3] || '.';
174
+ const instructions = args.slice(4).join(' ');
175
+ if (!name) {
176
+ appendMessage('error', 'Usage: /workspace add <name> [path] [instructions]');
177
+ } else {
178
+ workspaceManager.addWorkspace(name, wsPath, instructions);
179
+ appendMessage('system', `Workspace "${name}" registered at ${path.resolve(wsPath)}`);
180
+ resetChat();
181
+ }
182
+ } else if (subCmd === 'list') {
183
+ const all = workspaceManager.listWorkspaces();
184
+ let listMsg = "Registered Workspaces:\n";
185
+ for (const n in all) listMsg += `- ${n}: ${all[n].path}\n`;
186
+ appendMessage('system', Object.keys(all).length ? listMsg : "No workspaces registered.");
187
+ } else if (subCmd === 'remove') {
188
+ const name = args[2];
189
+ if (workspaceManager.removeWorkspace(name)) {
190
+ appendMessage('system', `Removed workspace "${name}"`);
191
+ resetChat();
192
+ } else {
193
+ appendMessage('error', `Workspace "${name}" not found.`);
194
+ }
195
+ } else {
196
+ const ws = workspaceManager.getWorkspaceByPath(process.cwd());
197
+ appendMessage('system', ws ? `Current Workspace: ${ws.name}\nPath: ${ws.path}` : "Not currently in a registered workspace.\nUsage: /workspace <add|list|remove>");
198
+ }
199
+ return;
200
+ }
201
+
202
+ if (text.startsWith('/review')) {
203
+ if (!lastResponseText) {
204
+ appendMessage('error', 'Nothing to review yet. Get a response first.');
205
+ return;
206
+ }
207
+ agentOrchestrator.setAgent('reviewer');
208
+ appendMessage('system', 'βš–οΈ Requesting second-pass review from Mint Reviewer...');
209
+ text = `Please review this previous response and provide a critique:\n\n${lastResponseText}`;
210
+ } else {
211
+ // Other slash commands
212
+ const fakeRl = { close: () => { } };
213
+ appendMessage('user', text);
214
+ await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode);
215
+ return;
216
+ }
139
217
  }
140
218
  appendMessage('user', text);
141
219
 
@@ -162,16 +240,92 @@ async function startInteractiveChat(initialMessage = null) {
162
240
  }, 1000);
163
241
 
164
242
  try {
165
- const response = await handleChat(text);
166
- clearInterval(timer);
167
- setThinking(false);
168
- appendMessage('assistant', response.response, response.timestamp);
243
+ const config = require('./src/System/config_manager').readConfig();
244
+ const provider = config.aiProvider || 'gemini';
245
+ const currentAgent = agentOrchestrator.getCurrentAgent();
246
+ updateStatusModel(null, currentAgent.name);
247
+ if (provider === 'gemini') {
248
+ // ── Streaming path (Gemini only) ──────────────────────────────────
249
+ // Gemini returns JSON so we buffer all chunks and progressively
250
+ // extract the "response" field as more of the JSON arrives.
251
+ clearInterval(timer);
252
+
253
+ let jsonBuffer = '';
254
+ let finalParsed = null;
255
+ let streamer = null;
256
+ let displayedChars = 0; // chars of response text already sent to TUI
257
+
258
+ try {
259
+ for await (const event of handleGeminiChatStream(text)) {
260
+ if (event.chunk) {
261
+ jsonBuffer += event.chunk;
262
+
263
+ // Progressively extract readable text from the growing JSON buffer
264
+ const match = jsonBuffer.match(/"response"\s*:\s*"((?:[^"\\]|\\.)*)"/s);
265
+ if (match) {
266
+ const fullText = match[1]
267
+ .replace(/\\n/g, '\n')
268
+ .replace(/\\"/g, '"')
269
+ .replace(/\\\\/g, '\\');
270
+ const newChars = fullText.slice(displayedChars);
271
+ if (newChars.length > 0) {
272
+ if (!streamer) {
273
+ setThinking(false);
274
+ streamer = streamMessage('assistant');
275
+ }
276
+ streamer.appendChunk(newChars);
277
+ displayedChars = fullText.length;
278
+ }
279
+ }
280
+ } else if (event.done) {
281
+ finalParsed = event.parsed;
282
+ // Flush any remaining response text not yet displayed
283
+ if (finalParsed && finalParsed.response) {
284
+ const remaining = finalParsed.response.slice(displayedChars);
285
+ if (!streamer) {
286
+ setThinking(false);
287
+ streamer = streamMessage('assistant');
288
+ }
289
+ if (remaining) streamer.appendChunk(remaining);
290
+ }
291
+ if (streamer) {
292
+ streamer.finalize(event.timestamp);
293
+ } else {
294
+ setThinking(false);
295
+ appendMessage('assistant',
296
+ finalParsed ? finalParsed.response : '',
297
+ event.timestamp);
298
+ }
299
+ }
300
+ }
301
+ } catch (streamErr) {
302
+ setThinking(false);
303
+ appendMessage('error', streamErr.message);
304
+ return;
305
+ }
306
+
307
+ // Execute Actions from the final parsed response
308
+ if (finalParsed) {
309
+ const { executeAction } = require('./mint-cli-logic');
310
+ if (finalParsed.action && finalParsed.action.type !== 'none') {
311
+ const result = await executeAction(finalParsed.action);
312
+ if (result) appendMessage('system', `Action: ${result}`);
313
+ }
314
+ }
169
315
 
170
- // Execute Actions
171
- const { executeAction } = require('./mint-cli-logic');
172
- if (response.action && response.action.type !== 'none') {
173
- const result = await executeAction(response.action);
174
- if (result) appendMessage('system', `Action: ${result}`);
316
+ } else {
317
+ // ── Non-streaming fallback (Ollama, Anthropic, OpenAI, etc.) ──
318
+ const response = await handleChat(text);
319
+ clearInterval(timer);
320
+ setThinking(false);
321
+ lastResponseText = response.response;
322
+ appendMessage('assistant', response.response, response.timestamp);
323
+
324
+ const { executeAction } = require('./mint-cli-logic');
325
+ if (response.action && response.action.type !== 'none') {
326
+ const result = await executeAction(response.action);
327
+ if (result) appendMessage('system', `Action: ${result}`);
328
+ }
175
329
  }
176
330
  } catch (err) {
177
331
  clearInterval(timer);
@@ -248,26 +402,47 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
248
402
  const config = readConfig();
249
403
  if (args.length === 0) {
250
404
  appendMessage('system', [
251
- `Current Model: ${config.geminiModel}`,
252
- 'Available Presets:',
253
- ' - gemini-2.5-flash (Default)',
254
- ' - gemini-3.1-flash-lite-preview',
255
- ' - gemini-3.1-flash-lite',
256
- ' - ollama (local provider)',
405
+ `Current Provider: ${config.aiProvider}`,
406
+ `Current Gemini Model: ${config.geminiModel}`,
407
+ 'Available Providers/Presets:',
408
+ ' - gemini-2.5-flash (Default Gemini)',
409
+ ' - ollama (Local provider)',
410
+ ' - anthropic (Claude)',
411
+ ' - openai (GPT)',
412
+ ' - huggingface (Inference API)',
413
+ ' - local (LM Studio / OpenAI Compatible)',
257
414
  'Usage: /models <name> to switch'
258
415
  ].join('\n'));
259
416
  } else {
260
417
  const { writeConfig } = require('./src/System/config_manager');
261
418
  const newModel = args[0];
419
+ let newProvider = 'gemini';
420
+
262
421
  if (newModel === 'ollama') {
263
- config.aiProvider = 'ollama';
422
+ newProvider = 'ollama';
423
+ } else if (newModel === 'anthropic') {
424
+ newProvider = 'anthropic';
425
+ } else if (newModel === 'openai') {
426
+ newProvider = 'openai';
427
+ } else if (newModel === 'huggingface') {
428
+ newProvider = 'huggingface';
429
+ } else if (newModel === 'local' || newModel === 'local_openai') {
430
+ newProvider = 'local_openai';
431
+ } else if (newModel.startsWith('gpt-')) {
432
+ newProvider = 'openai';
433
+ config.openaiModel = newModel;
434
+ } else if (newModel.startsWith('claude-')) {
435
+ newProvider = 'anthropic';
436
+ config.anthropicModel = newModel;
264
437
  } else {
265
- config.aiProvider = 'gemini';
438
+ newProvider = 'gemini';
266
439
  config.geminiModel = newModel;
267
440
  }
441
+
442
+ config.aiProvider = newProvider;
268
443
  writeConfig(config);
269
- appendMessage('system', `βœ… Switched to: ${newModel}`);
270
- if (updateStatusModel) updateStatusModel(newModel);
444
+ appendMessage('system', `βœ… Switched to: ${newProvider} ${newProvider === 'gemini' ? `(${newModel})` : ''}`);
445
+ if (updateStatusModel) updateStatusModel(newProvider === 'gemini' ? newModel : newProvider);
271
446
  }
272
447
  break;
273
448
 
package/package.json CHANGED
@@ -1,14 +1,24 @@
1
1
  {
2
2
  "name": "@pheem49/mint",
3
- "version": "1.3.0",
3
+ "version": "1.4.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": {
7
7
  "start": "electron .",
8
- "test": "echo \"Error: no test specified\" && exit 1",
8
+ "test": "jest --testPathPatterns=tests/",
9
+ "test:watch": "jest --testPathPatterns=tests/ --watch",
9
10
  "build:linux": "electron-builder --linux",
10
11
  "cli": "node mint-cli.js"
11
12
  },
13
+ "jest": {
14
+ "testEnvironment": "node",
15
+ "testMatch": ["**/tests/**/*.test.js"],
16
+ "collectCoverageFrom": [
17
+ "src/AI_Brain/memory_store.js",
18
+ "src/AI_Brain/knowledge_base.js",
19
+ "src/System/config_manager.js"
20
+ ]
21
+ },
12
22
  "bin": {
13
23
  "mint": "mint-cli.js"
14
24
  },
@@ -45,6 +55,7 @@
45
55
  "@vitejs/plugin-react": "^6.0.1",
46
56
  "electron": "^40.7.0",
47
57
  "electron-builder": "^26.8.1",
58
+ "jest": "^30.4.0",
48
59
  "vite": "^8.0.10"
49
60
  },
50
61
  "build": {