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