@pheem49/mint 1.4.0 → 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/.codex +0 -0
- package/README.md +101 -148
- package/main.js +21 -1
- package/mint-cli-logic.js +23 -8
- package/mint-cli.js +223 -137
- package/package.json +1 -1
- package/src/AI_Brain/Gemini_API.js +38 -24
- package/src/AI_Brain/agent_orchestrator.js +6 -6
- package/src/AI_Brain/proactive_engine.js +2 -8
- package/src/Automation_Layer/file_operations.js +136 -6
- package/src/Automation_Layer/open_app.js +72 -43
- package/src/Automation_Layer/open_website.js +3 -3
- package/src/CLI/chat_router.js +70 -24
- package/src/CLI/chat_ui.js +197 -44
- package/src/CLI/code_agent.js +337 -93
- package/src/CLI/list_features.js +3 -1
- package/src/CLI/workspace_manager.js +15 -6
- package/src/Plugins/docker.js +12 -10
- package/src/System/config_manager.js +1 -1
- package/src/System/custom_workflows.js +9 -2
- package/tests/chat_router.test.js +42 -0
- package/tests/code_agent.test.js +69 -0
- package/tests/docker.test.js +46 -0
- package/tests/file_operations.test.js +57 -0
- package/tests/provider_routing.test.js +67 -0
- package/tests/workspace_manager.test.js +15 -0
package/mint-cli.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
require('dotenv').config({ quiet: true });
|
|
3
|
+
// Suppress experimental SQLite warning
|
|
4
|
+
const originalEmit = process.emit;
|
|
5
|
+
process.emit = function (name, data, ...args) {
|
|
6
|
+
if (name === 'warning' && typeof data === 'object' && data.name === 'ExperimentalWarning' && data.message.includes('SQLite')) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
return originalEmit.apply(process, [name, data, ...args]);
|
|
10
|
+
};
|
|
3
11
|
const { Command } = require('commander');
|
|
4
|
-
const { handleChat, handleGeminiChatStream, resetChat, refreshApiKeyFromConfig } = require('./src/AI_Brain/Gemini_API');
|
|
12
|
+
const { handleChat, handleGeminiChatStream, resetChat, refreshApiKeyFromConfig, getChatTranscript } = require('./src/AI_Brain/Gemini_API');
|
|
5
13
|
const agentOrchestrator = require('./src/AI_Brain/agent_orchestrator');
|
|
6
14
|
const workspaceManager = require('./src/CLI/workspace_manager');
|
|
7
15
|
const systemMonitor = require('./src/Plugins/system_monitor');
|
|
@@ -37,10 +45,42 @@ const colors = {
|
|
|
37
45
|
yellow: "\x1b[33m"
|
|
38
46
|
};
|
|
39
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
|
+
|
|
40
80
|
const program = new Command();
|
|
41
81
|
|
|
42
82
|
program
|
|
43
|
-
.name('mint
|
|
83
|
+
.name('mint')
|
|
44
84
|
.description('Mint - Your Personal AI Assistant CLI')
|
|
45
85
|
.version(pkg.version);
|
|
46
86
|
|
|
@@ -99,6 +139,79 @@ program
|
|
|
99
139
|
console.log(`${colors.gray}You will receive a notification when it's done.${colors.reset}\n`);
|
|
100
140
|
});
|
|
101
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
|
+
|
|
102
215
|
program
|
|
103
216
|
.command('code')
|
|
104
217
|
.description('Run Mint in workspace-aware coding mode for the current project')
|
|
@@ -110,8 +223,8 @@ program
|
|
|
110
223
|
try {
|
|
111
224
|
const result = await executeCodeTask(task, {
|
|
112
225
|
cwd: process.cwd(),
|
|
113
|
-
onProgress: (
|
|
114
|
-
console.log(
|
|
226
|
+
onProgress: (info) => {
|
|
227
|
+
console.log(formatProgress(info));
|
|
115
228
|
},
|
|
116
229
|
requestApproval: requestCodeApproval
|
|
117
230
|
});
|
|
@@ -133,7 +246,7 @@ program.parse(process.argv);
|
|
|
133
246
|
*/
|
|
134
247
|
async function startInteractiveChat(initialMessage = null) {
|
|
135
248
|
let lastResponseText = "";
|
|
136
|
-
const { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode } = createChatUI({
|
|
249
|
+
const { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode, appendCodeStep, updateWorkspace, askUser } = createChatUI({
|
|
137
250
|
onSubmit: async (text) => {
|
|
138
251
|
if (text.startsWith('/')) {
|
|
139
252
|
if (text.startsWith('/agent')) {
|
|
@@ -145,7 +258,7 @@ async function startInteractiveChat(initialMessage = null) {
|
|
|
145
258
|
if (success) {
|
|
146
259
|
const agent = agentOrchestrator.getCurrentAgent();
|
|
147
260
|
appendMessage('system', `Switched to Agent: ${agent.icon} ${agent.name}`);
|
|
148
|
-
updateStatusModel(
|
|
261
|
+
updateStatusModel(agent.name); // Pass name to status bar
|
|
149
262
|
resetChat(); // Reset to apply new system prompt
|
|
150
263
|
} else {
|
|
151
264
|
appendMessage('error', `Agent "${args[1]}" not found. Try /agent list`);
|
|
@@ -192,9 +305,25 @@ async function startInteractiveChat(initialMessage = null) {
|
|
|
192
305
|
} else {
|
|
193
306
|
appendMessage('error', `Workspace "${name}" not found.`);
|
|
194
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
|
+
}
|
|
195
324
|
} else {
|
|
196
325
|
const ws = workspaceManager.getWorkspaceByPath(process.cwd());
|
|
197
|
-
appendMessage('system', ws ? `Current Workspace: ${ws.name}\nPath: ${ws.path}` :
|
|
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>`);
|
|
198
327
|
}
|
|
199
328
|
return;
|
|
200
329
|
}
|
|
@@ -211,27 +340,15 @@ async function startInteractiveChat(initialMessage = null) {
|
|
|
211
340
|
// Other slash commands
|
|
212
341
|
const fakeRl = { close: () => { } };
|
|
213
342
|
appendMessage('user', text);
|
|
214
|
-
await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode);
|
|
343
|
+
await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace);
|
|
215
344
|
return;
|
|
216
345
|
}
|
|
217
346
|
}
|
|
218
347
|
appendMessage('user', text);
|
|
219
348
|
|
|
220
|
-
const
|
|
221
|
-
if (
|
|
222
|
-
appendMessage('system', `Router: entering Code Mode. ${routeDecision.reason}`);
|
|
223
|
-
await runChatRoutedTask(text, {
|
|
224
|
-
appendMessage,
|
|
225
|
-
setThinking,
|
|
226
|
-
requestApproval,
|
|
227
|
-
setMode
|
|
228
|
-
});
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
setMode('Chat');
|
|
349
|
+
const transcript = await getChatTranscript();
|
|
350
|
+
if (setMode) setMode('Agent');
|
|
233
351
|
|
|
234
|
-
// Start thinking timer
|
|
235
352
|
let seconds = 0;
|
|
236
353
|
setThinking(true, seconds);
|
|
237
354
|
const timer = setInterval(() => {
|
|
@@ -241,96 +358,31 @@ async function startInteractiveChat(initialMessage = null) {
|
|
|
241
358
|
|
|
242
359
|
try {
|
|
243
360
|
const config = require('./src/System/config_manager').readConfig();
|
|
244
|
-
const
|
|
245
|
-
const
|
|
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
|
-
}
|
|
361
|
+
const availableProviders = require('./src/System/config_manager').getAvailableProviders(config);
|
|
362
|
+
const preferredProvider = require('./src/CLI/code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
|
|
306
363
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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);
|
|
314
372
|
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
clearInterval(timer);
|
|
376
|
+
setThinking(false);
|
|
377
|
+
lastResponseText = result.summary;
|
|
378
|
+
appendMessage('assistant', result.summary);
|
|
315
379
|
|
|
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
|
-
}
|
|
329
|
-
}
|
|
330
380
|
} catch (err) {
|
|
331
381
|
clearInterval(timer);
|
|
332
382
|
setThinking(false);
|
|
333
383
|
appendMessage('error', err.message);
|
|
384
|
+
} finally {
|
|
385
|
+
if (setMode) setMode('Chat');
|
|
334
386
|
}
|
|
335
387
|
},
|
|
336
388
|
onExit: () => {
|
|
@@ -346,30 +398,43 @@ async function startInteractiveChat(initialMessage = null) {
|
|
|
346
398
|
// Handle initial message if passed via CLI arg
|
|
347
399
|
if (initialMessage) {
|
|
348
400
|
appendMessage('user', initialMessage);
|
|
349
|
-
const
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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++;
|
|
408
|
+
setThinking(true, seconds);
|
|
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(),
|
|
355
418
|
requestApproval,
|
|
356
|
-
|
|
419
|
+
askUser,
|
|
420
|
+
provider: preferredProvider,
|
|
421
|
+
history: transcript,
|
|
422
|
+
onProgress: (info) => {
|
|
423
|
+
if (appendCodeStep) appendCodeStep(info);
|
|
424
|
+
}
|
|
357
425
|
});
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
setThinking(false);
|
|
371
|
-
appendMessage('error', err.message);
|
|
372
|
-
}
|
|
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');
|
|
373
438
|
}
|
|
374
439
|
}
|
|
375
440
|
}
|
|
@@ -377,7 +442,7 @@ async function startInteractiveChat(initialMessage = null) {
|
|
|
377
442
|
/**
|
|
378
443
|
* Handles slash commands within the TUI context
|
|
379
444
|
*/
|
|
380
|
-
async function handleSlashCommandUI(input, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode) {
|
|
445
|
+
async function handleSlashCommandUI(input, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace) {
|
|
381
446
|
const parts = input.split(' ');
|
|
382
447
|
const command = parts[0].toLowerCase();
|
|
383
448
|
const args = parts.slice(1);
|
|
@@ -387,16 +452,36 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
|
|
|
387
452
|
case '/?':
|
|
388
453
|
appendMessage('system', [
|
|
389
454
|
'Mint Slash Commands:',
|
|
390
|
-
' /code <task>
|
|
391
|
-
' /
|
|
392
|
-
' /
|
|
393
|
-
' /
|
|
394
|
-
' /
|
|
395
|
-
' /
|
|
396
|
-
' /
|
|
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
|
+
' /reset — Reset conversation history',
|
|
462
|
+
' /exit — Exit Mint'
|
|
397
463
|
].join('\n'));
|
|
398
464
|
break;
|
|
399
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
|
+
|
|
400
485
|
case '/model':
|
|
401
486
|
case '/models':
|
|
402
487
|
const config = readConfig();
|
|
@@ -454,8 +539,9 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
|
|
|
454
539
|
await runChatRoutedTask(`/code ${args.join(' ')}`, {
|
|
455
540
|
appendMessage,
|
|
456
541
|
setThinking,
|
|
457
|
-
|
|
458
|
-
setMode
|
|
542
|
+
appendCodeStep,
|
|
543
|
+
setMode,
|
|
544
|
+
history: await getChatTranscript()
|
|
459
545
|
});
|
|
460
546
|
break;
|
|
461
547
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pheem49/mint",
|
|
3
|
-
"version": "1.4.
|
|
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": {
|
|
@@ -58,11 +58,13 @@ Always respond exactly with valid JSON containing NO MARKDOWN FORMATTING (do not
|
|
|
58
58
|
{
|
|
59
59
|
"response": "Your conversational reply here (Matches user language).",
|
|
60
60
|
"action": {
|
|
61
|
-
"type": "none" | "open_url" | "open_app" | "search" | "web_automation" | "create_folder" | "open_file" | "open_folder" | "delete_file" | "clipboard_write" | "system_info" | "plugin" | "learn_file" | "learn_folder" | "system_automation" | "mcp_tool" | "mouse_click" | "mouse_move" | "type_text" | "key_tap",
|
|
61
|
+
"type": "none" | "open_url" | "open_app" | "search" | "web_automation" | "create_folder" | "open_file" | "open_folder" | "find_path" | "delete_file" | "clipboard_write" | "system_info" | "plugin" | "learn_file" | "learn_folder" | "system_automation" | "mcp_tool" | "mouse_click" | "mouse_move" | "type_text" | "key_tap",
|
|
62
62
|
|
|
63
63
|
"pluginName": "only if type is plugin",
|
|
64
64
|
"server": "only if type is mcp_tool (server name)",
|
|
65
65
|
"target": "target string based on type (tool name if mcp_tool, text to type if type_text, key name if key_tap)",
|
|
66
|
+
"pathType": "optional for find_path: 'file' | 'dir' | 'any'",
|
|
67
|
+
"openAfter": true,
|
|
66
68
|
"x": 0-1000, // required for mouse_click and mouse_move
|
|
67
69
|
"y": 0-1000, // required for mouse_click and mouse_move
|
|
68
70
|
"button": 1 | 2 | 3, // optional for mouse_click, 1=left, 2=middle, 3=right
|
|
@@ -86,6 +88,12 @@ Output: { "response": "สวัสดีค่ะ! หนูชื่อมิ
|
|
|
86
88
|
Input: "Create a folder named Projects"
|
|
87
89
|
Output: { "response": "Sure thing! I'm creating a folder named 'Projects' for you right now.", "action": { "type": "create_folder", "target": "Projects" } }
|
|
88
90
|
|
|
91
|
+
Input: "หาโฟลเดอร์ xidaidai ให้หน่อย" or "find the xidaidai folder"
|
|
92
|
+
Output: { "response": "ได้เลยค่ะ มิ้นท์จะค้นหาโฟลเดอร์ xidaidai ให้", "action": { "type": "find_path", "target": "xidaidai", "pathType": "dir", "openAfter": false } }
|
|
93
|
+
|
|
94
|
+
Input: "เปิดโฟลเดอร์ xidaidai ให้หน่อย" or "open the xidaidai folder"
|
|
95
|
+
Output: { "response": "ได้เลยค่ะ มิ้นท์จะหาแล้วเปิดโฟลเดอร์ xidaidai ให้", "action": { "type": "find_path", "target": "xidaidai", "pathType": "dir", "openAfter": true } }
|
|
96
|
+
|
|
89
97
|
Input: "วันนี้วันที่เท่าไร" or "What date is today?" or "today's date" or "วันเวลา"
|
|
90
98
|
Output: { "response": "แป๊บนึงนะคะ มิ้นท์จะดูให้ค่า", "action": { "type": "system_info", "target": "" } }
|
|
91
99
|
|
|
@@ -165,6 +173,13 @@ function resolveGeminiModel() {
|
|
|
165
173
|
}
|
|
166
174
|
}
|
|
167
175
|
|
|
176
|
+
function getProviderAttemptOrder(config) {
|
|
177
|
+
const provider = config.aiProvider || 'gemini';
|
|
178
|
+
const availableProviders = getAvailableProviders(config);
|
|
179
|
+
const alternates = availableProviders.filter(p => p !== provider);
|
|
180
|
+
return [provider, ...alternates];
|
|
181
|
+
}
|
|
182
|
+
|
|
168
183
|
// Chat session — maintains conversation history within the session
|
|
169
184
|
let chat = null;
|
|
170
185
|
let activeModel = resolveGeminiModel();
|
|
@@ -214,21 +229,6 @@ function shouldUseKnowledgeSearch(message) {
|
|
|
214
229
|
async function handleChat(message, base64Image = null, base64Audio = null) {
|
|
215
230
|
try {
|
|
216
231
|
const config = readConfig();
|
|
217
|
-
const provider = config.aiProvider || 'gemini';
|
|
218
|
-
|
|
219
|
-
// Ensure API Key is loaded and Client is initialized before every chat
|
|
220
|
-
const currentKey = resolveApiKey();
|
|
221
|
-
if (!currentKey) {
|
|
222
|
-
return {
|
|
223
|
-
response: "I couldn't find your Gemini API Key. Please run 'mint onboard' to set it up!",
|
|
224
|
-
action: { type: "none", target: "" }
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (!ai || activeApiKey !== currentKey) {
|
|
229
|
-
initAiClient();
|
|
230
|
-
createChat(readChatHistory());
|
|
231
|
-
}
|
|
232
232
|
|
|
233
233
|
let finalMessage = message;
|
|
234
234
|
|
|
@@ -245,13 +245,7 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
|
|
|
245
245
|
}
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
const
|
|
249
|
-
const availableProviders = getAvailableProviders(config);
|
|
250
|
-
|
|
251
|
-
// Ensure the requested provider is prioritized. If not available, fallback to the first available.
|
|
252
|
-
let providersToTry = [provider];
|
|
253
|
-
const alternates = availableProviders.filter(p => p !== provider);
|
|
254
|
-
providersToTry = providersToTry.concat(alternates);
|
|
248
|
+
const providersToTry = getProviderAttemptOrder(config);
|
|
255
249
|
|
|
256
250
|
for (let i = 0; i < providersToTry.length; i++) {
|
|
257
251
|
const currentProv = providersToTry[i];
|
|
@@ -272,6 +266,23 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
|
|
|
272
266
|
return await handleHuggingFaceChat(finalMessage, base64Image, config);
|
|
273
267
|
}
|
|
274
268
|
|
|
269
|
+
const currentKey = resolveApiKey();
|
|
270
|
+
if (!currentKey) {
|
|
271
|
+
if (i === providersToTry.length - 1) {
|
|
272
|
+
return {
|
|
273
|
+
response: "I couldn't find your Gemini API Key. Please run 'mint onboard' to set it up!",
|
|
274
|
+
action: { type: "none", target: "" }
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
console.warn("[Fallback System] Gemini API key missing. Skipping Gemini provider.");
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!ai || activeApiKey !== currentKey) {
|
|
282
|
+
initAiClient();
|
|
283
|
+
createChat(readChatHistory());
|
|
284
|
+
}
|
|
285
|
+
|
|
275
286
|
return await handleGeminiChat(finalMessage, base64Image, base64Audio);
|
|
276
287
|
} catch (error) {
|
|
277
288
|
console.error(`[Fallback System] Provider '${currentProv}' failed:`, error.message);
|
|
@@ -887,5 +898,8 @@ module.exports = {
|
|
|
887
898
|
resetChat,
|
|
888
899
|
getChatTranscript,
|
|
889
900
|
translateImageContent,
|
|
890
|
-
refreshApiKeyFromConfig
|
|
901
|
+
refreshApiKeyFromConfig,
|
|
902
|
+
_helpers: {
|
|
903
|
+
getProviderAttemptOrder
|
|
904
|
+
}
|
|
891
905
|
};
|
|
@@ -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
|
|
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
const { GoogleGenAI } = require('@google/genai');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const { app } = require('electron');
|
|
5
2
|
const { readConfig } = require('../System/config_manager');
|
|
6
3
|
|
|
7
4
|
// ============================================================
|
|
@@ -76,11 +73,8 @@ function resolveGeminiModel() {
|
|
|
76
73
|
|
|
77
74
|
function getMinSuggestionIntervalMs() {
|
|
78
75
|
try {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
82
|
-
return (cfg.proactiveCooldown || 120) * 1000;
|
|
83
|
-
}
|
|
76
|
+
const cfg = readConfig();
|
|
77
|
+
return (cfg.proactiveCooldown || 120) * 1000;
|
|
84
78
|
} catch {
|
|
85
79
|
// ignore
|
|
86
80
|
}
|