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