@pheem49/mint 1.5.1 → 1.5.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 +8 -0
- package/mint-cli.js +148 -921
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +31 -1
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +6 -1
- package/package.json +18 -20
- package/src/AI_Brain/proactive_engine.js +12 -2
- package/src/Automation_Layer/browser_automation.js +26 -24
- package/src/CLI/approval_handler.js +42 -0
- package/src/CLI/chat_ui.js +192 -7
- package/src/CLI/cli_colors.js +32 -0
- package/src/CLI/cli_formatters.js +89 -0
- package/src/CLI/code_agent.js +166 -57
- package/src/CLI/intent_detectors.js +181 -0
- package/src/CLI/interactive_chat.js +479 -0
- package/src/CLI/list_features.js +3 -0
- package/src/CLI/repo_summarizer.js +282 -0
- package/src/CLI/semantic_code_search.js +312 -0
- package/src/CLI/skill_manager.js +41 -0
- package/src/CLI/slash_command_handler.js +418 -0
- package/src/CLI/symbol_indexer.js +231 -0
- package/src/Channels/discord_bridge.js +11 -13
- package/src/Channels/line_bridge.js +10 -10
- package/src/Channels/slack_bridge.js +7 -12
- package/src/Channels/telegram_bridge.js +6 -14
- package/src/Channels/whatsapp_bridge.js +11 -9
- package/src/System/chat_history_manager.js +20 -12
- package/src/System/optional_require.js +23 -0
- package/src/UI/live2d_manager.js +211 -13
- package/src/UI/renderer.js +163 -3
- package/src/UI/settings.css +655 -420
- package/src/UI/settings.html +478 -432
- package/src/UI/settings.js +10 -8
- package/src/UI/styles.css +89 -25
package/mint-cli.js
CHANGED
|
@@ -1,39 +1,50 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
require('dotenv').config({ quiet: true });
|
|
3
|
+
|
|
3
4
|
// Suppress experimental SQLite warning
|
|
4
5
|
const originalEmit = process.emit;
|
|
5
6
|
process.emit = function (name, data, ...args) {
|
|
6
|
-
if (name === 'warning' && typeof data === 'object' &&
|
|
7
|
+
if (name === 'warning' && typeof data === 'object' &&
|
|
8
|
+
data.name === 'ExperimentalWarning' && data.message.includes('SQLite')) {
|
|
7
9
|
return false;
|
|
8
10
|
}
|
|
9
11
|
return originalEmit.apply(process, [name, data, ...args]);
|
|
10
12
|
};
|
|
13
|
+
|
|
11
14
|
const { Command } = require('commander');
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const {
|
|
22
|
-
const {
|
|
23
|
-
const {
|
|
24
|
-
const {
|
|
25
|
-
const {
|
|
26
|
-
const memoryStore
|
|
27
|
-
const readline = require('readline');
|
|
28
|
-
const { createChatUI } = require('./src/CLI/chat_ui');
|
|
15
|
+
|
|
16
|
+
// ── CLI modules ──────────────────────────────────────────────────────────────
|
|
17
|
+
const { colors, exitWithGoodbye } = require('./src/CLI/cli_colors');
|
|
18
|
+
const { formatProgress } = require('./src/CLI/cli_formatters');
|
|
19
|
+
const { startInteractiveChat } = require('./src/CLI/interactive_chat');
|
|
20
|
+
const { requestCodeApproval } = require('./src/CLI/approval_handler');
|
|
21
|
+
const { learnSkillFile } = require('./src/CLI/skill_manager');
|
|
22
|
+
|
|
23
|
+
// ── Feature / system modules ────────────────────────────────────────────────
|
|
24
|
+
const { runOnboarding } = require('./src/CLI/onboarding');
|
|
25
|
+
const { startAgent } = require('./src/AI_Brain/headless_agent');
|
|
26
|
+
const { displayFeatures } = require('./src/CLI/list_features');
|
|
27
|
+
const { readConfig, writeConfig } = require('./src/System/config_manager');
|
|
28
|
+
const { executeCodeTask } = require('./src/CLI/code_agent');
|
|
29
|
+
const memoryStore = require('./src/AI_Brain/memory_store');
|
|
29
30
|
const { runUpdate, runStartupAutoUpdate, shouldRunAutoUpdate } = require('./src/CLI/updater');
|
|
30
|
-
const { runGmailAuth }
|
|
31
|
-
const { loadImageAsDataUri
|
|
31
|
+
const { runGmailAuth } = require('./src/CLI/gmail_auth');
|
|
32
|
+
const { loadImageAsDataUri } = require('./src/CLI/image_input');
|
|
33
|
+
const { summarizeRepository, formatRepoSummary } = require('./src/CLI/repo_summarizer');
|
|
34
|
+
const { buildSymbolIndex, formatSymbolIndex } = require('./src/CLI/symbol_indexer');
|
|
35
|
+
const {
|
|
36
|
+
indexSemanticCode,
|
|
37
|
+
searchSemanticCode,
|
|
38
|
+
formatSemanticCodeIndex,
|
|
39
|
+
formatSemanticCodeSearch
|
|
40
|
+
} = require('./src/CLI/semantic_code_search');
|
|
32
41
|
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
const pkg = require('./package.json');
|
|
43
|
+
|
|
44
|
+
// ── Startup banner ───────────────────────────────────────────────────────────
|
|
45
|
+
const startupConfig = readConfig();
|
|
35
46
|
const startupProvider = startupConfig.aiProvider || 'gemini';
|
|
36
|
-
const startupModel
|
|
47
|
+
const startupModel = startupProvider === 'openai'
|
|
37
48
|
? (startupConfig.openaiModel || 'gpt-4o')
|
|
38
49
|
: startupProvider === 'anthropic'
|
|
39
50
|
? (startupConfig.anthropicModel || 'claude-3-5-sonnet-latest')
|
|
@@ -42,134 +53,17 @@ const startupModel = startupProvider === 'openai'
|
|
|
42
53
|
: startupProvider === 'ollama'
|
|
43
54
|
? (startupConfig.ollamaModel || 'llama3:latest')
|
|
44
55
|
: (startupConfig.geminiModel || 'gemini-2.5-flash');
|
|
45
|
-
const startupNow = new Date();
|
|
46
|
-
const startupTime = startupNow.toLocaleString('th-TH', {
|
|
47
|
-
day: '2-digit', month: '2-digit', year: 'numeric',
|
|
48
|
-
hour: '2-digit', minute: '2-digit', hour12: false
|
|
49
|
-
}).replace(',', '');
|
|
50
|
-
console.log(`\x1b[38;5;121m[Mint] v${pkg.version} | ${startupTime} | Active AI: ${startupProvider} • ${startupModel}\x1b[0m`);
|
|
51
|
-
|
|
52
|
-
// ANSI Colors
|
|
53
|
-
const colors = {
|
|
54
|
-
reset: "\x1b[0m",
|
|
55
|
-
bright: "\x1b[1m",
|
|
56
|
-
mint: "\x1b[38;5;121m",
|
|
57
|
-
pink: "\x1b[38;5;213m",
|
|
58
|
-
gray: "\x1b[90m",
|
|
59
|
-
cyan: "\x1b[36m",
|
|
60
|
-
yellow: "\x1b[33m"
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
let isExiting = false;
|
|
64
|
-
|
|
65
|
-
function exitWithGoodbye(code = 0) {
|
|
66
|
-
if (isExiting) return;
|
|
67
|
-
isExiting = true;
|
|
68
|
-
|
|
69
|
-
process.stdout.write('\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l');
|
|
70
|
-
process.stdout.write('\x1b[?25h');
|
|
71
|
-
console.log(`\n${colors.pink}Goodbye! See you again soon!${colors.reset}\n`);
|
|
72
|
-
process.exit(code);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
process.once('SIGINT', () => {
|
|
76
|
-
exitWithGoodbye(0);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
function formatProgress(info) {
|
|
80
|
-
if (typeof info === 'string') return `${colors.gray}[Mint Code] ${info}${colors.reset}`;
|
|
81
|
-
|
|
82
|
-
const { step, phase, action, target, message } = info;
|
|
83
|
-
|
|
84
|
-
if (action === 'ask_user') {
|
|
85
|
-
return `\n${colors.mint}✓${colors.reset} ${colors.bright}Ask User${colors.reset}\n${colors.gray} ${target || message || ''}${colors.reset}`;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
let icon = `${colors.mint}✓${colors.reset}`;
|
|
89
|
-
let label = action || phase;
|
|
90
|
-
let color = colors.reset;
|
|
91
|
-
|
|
92
|
-
switch (action) {
|
|
93
|
-
case 'thinking':
|
|
94
|
-
return `\n${colors.yellow}* ${colors.bright}Thinking${colors.reset}`;
|
|
95
|
-
case 'web_search': label = 'WebSearch'; break;
|
|
96
|
-
case 'list_files':
|
|
97
|
-
case 'find_path': label = 'Explored'; break;
|
|
98
|
-
case 'read_file': label = 'ReadFile'; break;
|
|
99
|
-
case 'search_code': label = 'SearchText'; break;
|
|
100
|
-
case 'apply_patch':
|
|
101
|
-
case 'write_file': label = 'Edited'; break;
|
|
102
|
-
case 'run_shell': label = 'Ran command'; break;
|
|
103
|
-
case 'json_repair': icon = '*'; label = 'Repairing JSON'; break;
|
|
104
|
-
case 'reviewer_start': label = 'Reviewing'; break;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const content = target || message || '';
|
|
108
|
-
return ` ${icon} ${colors.bright}${label}${colors.reset} ${color}${content}${colors.reset}`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function formatMemoryInteractions(interactions, title = 'Remembered interactions') {
|
|
112
|
-
if (!Array.isArray(interactions) || interactions.length === 0) {
|
|
113
|
-
return `${title}:\n(no memories found)`;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const lines = [`${title}:`];
|
|
117
|
-
interactions.forEach((item, index) => {
|
|
118
|
-
const when = item.created_at ? ` (${item.created_at})` : '';
|
|
119
|
-
const id = item.id ? `#${item.id} ` : '';
|
|
120
|
-
lines.push(`${index + 1}. ${id}User${when}: ${item.user_text}`);
|
|
121
|
-
lines.push(` Mint: ${item.ai_text}`);
|
|
122
|
-
});
|
|
123
|
-
return lines.join('\n');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
127
|
-
|
|
128
|
-
function splitResponseSentences(text) {
|
|
129
|
-
const normalized = String(text || '').replace(/\r\n/g, '\n').trim();
|
|
130
|
-
if (!normalized) return [];
|
|
131
|
-
|
|
132
|
-
const sentences = [];
|
|
133
|
-
let buffer = '';
|
|
134
|
-
for (const char of normalized) {
|
|
135
|
-
buffer += char;
|
|
136
|
-
if (/[.!?。!?…\n]/u.test(char)) {
|
|
137
|
-
const sentence = buffer.trim();
|
|
138
|
-
if (sentence) sentences.push(sentence);
|
|
139
|
-
buffer = '';
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const rest = buffer.trim();
|
|
144
|
-
if (rest) sentences.push(rest);
|
|
145
|
-
return sentences.length > 0 ? sentences : [normalized];
|
|
146
|
-
}
|
|
147
56
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const stat = fs.statSync(targetPath);
|
|
155
|
-
if (!stat.isFile()) {
|
|
156
|
-
throw new Error(`Path is not a file: ${targetPath}`);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const ext = path.extname(targetPath).toLowerCase();
|
|
160
|
-
if (ext !== '.md' && ext !== '.txt') {
|
|
161
|
-
throw new Error('Mint learn currently supports .md and .txt files only.');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const maxBytes = 256 * 1024;
|
|
165
|
-
if (stat.size > maxBytes) {
|
|
166
|
-
throw new Error(`File is too large (${stat.size} bytes). Limit is ${maxBytes} bytes.`);
|
|
167
|
-
}
|
|
57
|
+
const startupNow = new Date();
|
|
58
|
+
const startupTime = startupNow.toLocaleString('th-TH', {
|
|
59
|
+
day: '2-digit', month: '2-digit', year: 'numeric',
|
|
60
|
+
hour: '2-digit', minute: '2-digit', hour12: false
|
|
61
|
+
}).replace(',', '');
|
|
62
|
+
console.log(`${colors.mint}[Mint] v${pkg.version} | ${startupTime} | Active AI: ${startupProvider} • ${startupModel}${colors.reset}`);
|
|
168
63
|
|
|
169
|
-
|
|
170
|
-
return memoryStore.addLearnedSkill(path.basename(targetPath), targetPath, content);
|
|
171
|
-
}
|
|
64
|
+
process.once('SIGINT', () => exitWithGoodbye(0));
|
|
172
65
|
|
|
66
|
+
// ── Commander program ────────────────────────────────────────────────────────
|
|
173
67
|
const program = new Command();
|
|
174
68
|
|
|
175
69
|
program
|
|
@@ -177,20 +71,11 @@ program
|
|
|
177
71
|
.description('Mint - Your Personal AI Assistant CLI')
|
|
178
72
|
.version(pkg.version);
|
|
179
73
|
|
|
74
|
+
// Auto-update hook
|
|
180
75
|
program.hook('preAction', async (thisCommand, actionCommand) => {
|
|
181
|
-
if (actionCommand.name() === 'update' || process.env.MINT_SKIP_AUTO_UPDATE === '1')
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
76
|
+
if (actionCommand.name() === 'update' || process.env.MINT_SKIP_AUTO_UPDATE === '1') return;
|
|
185
77
|
const config = readConfig();
|
|
186
|
-
if (config.enableAutoUpdate === false)
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (!shouldRunAutoUpdate(config)) {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
78
|
+
if (config.enableAutoUpdate === false || !shouldRunAutoUpdate(config)) return;
|
|
194
79
|
console.log(`${colors.gray}[Mint Update] Checking for updates...${colors.reset}`);
|
|
195
80
|
const result = await runStartupAutoUpdate(config, writeConfig);
|
|
196
81
|
if (result.status === 'updated') {
|
|
@@ -200,7 +85,8 @@ program.hook('preAction', async (thisCommand, actionCommand) => {
|
|
|
200
85
|
}
|
|
201
86
|
});
|
|
202
87
|
|
|
203
|
-
//
|
|
88
|
+
// ── Commands ─────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
204
90
|
program
|
|
205
91
|
.command('chat', { isDefault: true })
|
|
206
92
|
.description('Start interactive chat session with Mint')
|
|
@@ -210,7 +96,6 @@ program
|
|
|
210
96
|
await startInteractiveChat(message, { imagePath: options.image });
|
|
211
97
|
});
|
|
212
98
|
|
|
213
|
-
// Onboard Command
|
|
214
99
|
program
|
|
215
100
|
.command('onboard')
|
|
216
101
|
.description('Setup Mint for the first time')
|
|
@@ -219,7 +104,6 @@ program
|
|
|
219
104
|
await runOnboarding(options);
|
|
220
105
|
});
|
|
221
106
|
|
|
222
|
-
// Agent Command (Headless Daemon Mode)
|
|
223
107
|
program
|
|
224
108
|
.command('agent')
|
|
225
109
|
.description('Run Mint as a background agent (headless)')
|
|
@@ -233,12 +117,89 @@ program
|
|
|
233
117
|
await startAgent();
|
|
234
118
|
});
|
|
235
119
|
|
|
236
|
-
// List Command
|
|
237
120
|
program
|
|
238
121
|
.command('list')
|
|
239
122
|
.description('Show list of Mint features and commands')
|
|
240
|
-
.action(() =>
|
|
241
|
-
|
|
123
|
+
.action(() => displayFeatures());
|
|
124
|
+
|
|
125
|
+
program
|
|
126
|
+
.command('summarize')
|
|
127
|
+
.alias('summary')
|
|
128
|
+
.description('Summarize a repository structure, tooling, git state, and key files')
|
|
129
|
+
.argument('[path]', 'Repository path to summarize', process.cwd())
|
|
130
|
+
.option('--json', 'Print raw JSON summary')
|
|
131
|
+
.action((targetPath, options) => {
|
|
132
|
+
try {
|
|
133
|
+
const summary = summarizeRepository(targetPath);
|
|
134
|
+
if (options.json) { console.log(JSON.stringify(summary, null, 2)); return; }
|
|
135
|
+
console.log(`\n${formatRepoSummary(summary)}\n`);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error(`\n${colors.pink}Summarize failed:${colors.reset} ${error.message}\n`);
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
program
|
|
143
|
+
.command('symbols')
|
|
144
|
+
.alias('symbol-index')
|
|
145
|
+
.description('Build a source symbol index for the current repository')
|
|
146
|
+
.argument('[path]', 'Repository path to index', process.cwd())
|
|
147
|
+
.option('--json', 'Print raw JSON symbol index')
|
|
148
|
+
.option('--limit <count>', 'Limit formatted symbols shown', value => Number(value), 80)
|
|
149
|
+
.action((targetPath, options) => {
|
|
150
|
+
try {
|
|
151
|
+
const index = buildSymbolIndex(targetPath);
|
|
152
|
+
if (options.json) { console.log(JSON.stringify(index, null, 2)); return; }
|
|
153
|
+
console.log(`\n${formatSymbolIndex(index, { limit: options.limit })}\n`);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error(`\n${colors.pink}Symbol index failed:${colors.reset} ${error.message}\n`);
|
|
156
|
+
process.exitCode = 1;
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const semanticCodeCommand = program
|
|
161
|
+
.command('semantic-code')
|
|
162
|
+
.alias('semantic')
|
|
163
|
+
.description('Index and search source code semantically with embeddings');
|
|
164
|
+
|
|
165
|
+
semanticCodeCommand
|
|
166
|
+
.command('index')
|
|
167
|
+
.description('Create embeddings for source code chunks in a repository')
|
|
168
|
+
.argument('[path]', 'Repository path to index', process.cwd())
|
|
169
|
+
.option('--json', 'Print raw JSON index metadata')
|
|
170
|
+
.action(async (targetPath, options) => {
|
|
171
|
+
try {
|
|
172
|
+
const index = await indexSemanticCode(targetPath, {
|
|
173
|
+
onProgress: (info) => {
|
|
174
|
+
if (info.current === 1 || info.current === info.total || info.current % 25 === 0) {
|
|
175
|
+
console.log(`${colors.gray}[Semantic Code] Embedded ${info.current}/${info.total}: ${info.file}${colors.reset}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
if (options.json) { console.log(JSON.stringify(index, null, 2)); return; }
|
|
180
|
+
console.log(`\n${formatSemanticCodeIndex(index)}\n`);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error(`\n${colors.pink}Semantic code index failed:${colors.reset} ${error.message}\n`);
|
|
183
|
+
process.exitCode = 1;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
semanticCodeCommand
|
|
188
|
+
.command('search')
|
|
189
|
+
.description('Search an existing semantic code index')
|
|
190
|
+
.argument('<query...>', 'Natural language code search query')
|
|
191
|
+
.option('--path <path>', 'Repository path to search', process.cwd())
|
|
192
|
+
.option('--json', 'Print raw JSON search results')
|
|
193
|
+
.option('--top-k <count>', 'Number of results to return', value => Number(value), 5)
|
|
194
|
+
.action(async (query, options) => {
|
|
195
|
+
try {
|
|
196
|
+
const results = await searchSemanticCode(query.join(' '), options.path, { topK: options.topK });
|
|
197
|
+
if (options.json) { console.log(JSON.stringify(results, null, 2)); return; }
|
|
198
|
+
console.log(`\n${formatSemanticCodeSearch(results)}\n`);
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error(`\n${colors.pink}Semantic code search failed:${colors.reset} ${error.message}\n`);
|
|
201
|
+
process.exitCode = 1;
|
|
202
|
+
}
|
|
242
203
|
});
|
|
243
204
|
|
|
244
205
|
program
|
|
@@ -251,10 +212,7 @@ program
|
|
|
251
212
|
try {
|
|
252
213
|
if (options.list) {
|
|
253
214
|
const skills = memoryStore.getLearnedSkills(50);
|
|
254
|
-
if (skills.length === 0) {
|
|
255
|
-
console.log(`\n${colors.gray}No learned skills stored.${colors.reset}\n`);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
215
|
+
if (skills.length === 0) { console.log(`\n${colors.gray}No learned skills stored.${colors.reset}\n`); return; }
|
|
258
216
|
console.log(`\n${colors.bright}Learned Skills:${colors.reset}`);
|
|
259
217
|
skills.forEach(skill => {
|
|
260
218
|
console.log(`${colors.mint}#${skill.id}${colors.reset} ${skill.name}`);
|
|
@@ -263,7 +221,6 @@ program
|
|
|
263
221
|
console.log('');
|
|
264
222
|
return;
|
|
265
223
|
}
|
|
266
|
-
|
|
267
224
|
if (options.delete) {
|
|
268
225
|
const deleted = memoryStore.deleteLearnedSkill(options.delete);
|
|
269
226
|
if (deleted > 0) {
|
|
@@ -274,10 +231,7 @@ program
|
|
|
274
231
|
}
|
|
275
232
|
return;
|
|
276
233
|
}
|
|
277
|
-
|
|
278
|
-
if (!filePath) {
|
|
279
|
-
throw new Error('Usage: mint learn <path-to-skill.md>');
|
|
280
|
-
}
|
|
234
|
+
if (!filePath) throw new Error('Usage: mint learn <path-to-skill.md>');
|
|
281
235
|
|
|
282
236
|
const learned = learnSkillFile(filePath);
|
|
283
237
|
console.log(`\n${colors.mint}✓${colors.reset} Learned skill: ${learned.name}`);
|
|
@@ -292,7 +246,6 @@ program
|
|
|
292
246
|
}
|
|
293
247
|
});
|
|
294
248
|
|
|
295
|
-
// Task Command (Autonomous Background Task)
|
|
296
249
|
program
|
|
297
250
|
.command('task')
|
|
298
251
|
.description('Delegate a complex task to the background agent')
|
|
@@ -310,23 +263,18 @@ program
|
|
|
310
263
|
program
|
|
311
264
|
.command('update')
|
|
312
265
|
.description('Check for and install the latest Mint CLI version from npm')
|
|
313
|
-
.option('--check',
|
|
266
|
+
.option('--check', 'Only check whether an update is available')
|
|
314
267
|
.option('--dry-run', 'Show the npm update operation without installing')
|
|
315
268
|
.action(async (options) => {
|
|
316
269
|
console.log(`\n${colors.mint}${colors.bright}[Mint Update]${colors.reset} Checking npm for updates...`);
|
|
317
|
-
|
|
318
270
|
try {
|
|
319
271
|
const result = await runUpdate({
|
|
320
|
-
checkOnly: options.check
|
|
321
|
-
dryRun:
|
|
272
|
+
checkOnly: options.check === true,
|
|
273
|
+
dryRun: options.dryRun === true
|
|
322
274
|
});
|
|
323
|
-
|
|
324
275
|
const color = result.status === 'error' ? colors.pink : colors.mint;
|
|
325
276
|
console.log(`${color}${result.message}${colors.reset}\n`);
|
|
326
|
-
|
|
327
|
-
if (result.status === 'error') {
|
|
328
|
-
process.exitCode = 1;
|
|
329
|
-
}
|
|
277
|
+
if (result.status === 'error') process.exitCode = 1;
|
|
330
278
|
} catch (error) {
|
|
331
279
|
console.error(`${colors.pink}Update failed: ${error.message}${colors.reset}\n`);
|
|
332
280
|
process.exitCode = 1;
|
|
@@ -338,28 +286,21 @@ program
|
|
|
338
286
|
.description('Manage MCP (Model Context Protocol) servers')
|
|
339
287
|
.addCommand(new Command('add')
|
|
340
288
|
.description('Add a new MCP server')
|
|
341
|
-
.argument('<name>',
|
|
289
|
+
.argument('<name>', 'Server name')
|
|
342
290
|
.argument('<command>', 'Command to run (e.g. npx)')
|
|
343
291
|
.option('-a, --args <args...>', 'Command arguments')
|
|
344
|
-
.option('-e, --env <env...>',
|
|
292
|
+
.option('-e, --env <env...>', 'Environment variables (KEY=VALUE)')
|
|
345
293
|
.action((name, command, options) => {
|
|
346
|
-
const config
|
|
294
|
+
const config = readConfig();
|
|
347
295
|
const mcpServers = config.mcpServers || {};
|
|
348
|
-
|
|
349
|
-
const env = {};
|
|
296
|
+
const env = {};
|
|
350
297
|
if (options.env) {
|
|
351
298
|
options.env.forEach(kv => {
|
|
352
299
|
const [k, v] = kv.split('=');
|
|
353
300
|
if (k && v) env[k] = v;
|
|
354
301
|
});
|
|
355
302
|
}
|
|
356
|
-
|
|
357
|
-
mcpServers[name] = {
|
|
358
|
-
command,
|
|
359
|
-
args: options.args || [],
|
|
360
|
-
env
|
|
361
|
-
};
|
|
362
|
-
|
|
303
|
+
mcpServers[name] = { command, args: options.args || [], env };
|
|
363
304
|
config.mcpServers = mcpServers;
|
|
364
305
|
writeConfig(config);
|
|
365
306
|
console.log(`\n${colors.mint}✓${colors.reset} MCP server "${name}" added successfully.`);
|
|
@@ -382,7 +323,7 @@ program
|
|
|
382
323
|
.addCommand(new Command('list')
|
|
383
324
|
.description('List configured MCP servers')
|
|
384
325
|
.action(() => {
|
|
385
|
-
const config
|
|
326
|
+
const config = readConfig();
|
|
386
327
|
const servers = Object.keys(config.mcpServers || {});
|
|
387
328
|
if (servers.length === 0) {
|
|
388
329
|
console.log(`\n${colors.gray}No MCP servers configured.${colors.reset}`);
|
|
@@ -412,13 +353,13 @@ program
|
|
|
412
353
|
.addCommand(new Command('auth')
|
|
413
354
|
.description('Open Google OAuth login and save a Gmail refresh token')
|
|
414
355
|
.option('--port <port>', 'Local callback port, defaults to a random available port')
|
|
415
|
-
.option('--no-open',
|
|
356
|
+
.option('--no-open', 'Print the auth link without opening a browser')
|
|
416
357
|
.action(async (options) => {
|
|
417
358
|
try {
|
|
418
359
|
const result = await runGmailAuth({
|
|
419
|
-
port:
|
|
360
|
+
port: options.port ? Number(options.port) : 0,
|
|
420
361
|
openBrowser: options.open,
|
|
421
|
-
logger:
|
|
362
|
+
logger: console
|
|
422
363
|
});
|
|
423
364
|
console.log(`\n${colors.mint}✓${colors.reset} Gmail connected for ${result.userId}. Refresh token saved.`);
|
|
424
365
|
console.log(`${colors.gray}Scopes: ${result.scopes.join(', ')}${colors.reset}\n`);
|
|
@@ -436,24 +377,19 @@ program
|
|
|
436
377
|
.option('-i, --image <path>', 'Attach an image file as context for the coding task')
|
|
437
378
|
.action(async (task, options) => {
|
|
438
379
|
console.log(`\n${colors.mint}${colors.bright}[Mint Code]${colors.reset} Workspace: ${process.cwd()}`);
|
|
439
|
-
|
|
440
380
|
try {
|
|
441
|
-
let effectiveTask = task;
|
|
442
381
|
let image = null;
|
|
443
382
|
if (options.image) {
|
|
444
383
|
image = loadImageAsDataUri(options.image);
|
|
445
384
|
console.log(`${colors.gray}[Mint Code] Image: ${image.path}${colors.reset}`);
|
|
446
385
|
}
|
|
447
|
-
|
|
448
386
|
console.log(`${colors.gray}[Mint Code] Task: ${task}${colors.reset}\n`);
|
|
449
387
|
|
|
450
|
-
const result = await executeCodeTask(
|
|
451
|
-
cwd:
|
|
452
|
-
imageDataUri:
|
|
453
|
-
imagePath:
|
|
454
|
-
onProgress:
|
|
455
|
-
console.log(formatProgress(info));
|
|
456
|
-
},
|
|
388
|
+
const result = await executeCodeTask(task, {
|
|
389
|
+
cwd: process.cwd(),
|
|
390
|
+
imageDataUri: image ? image.dataUri : null,
|
|
391
|
+
imagePath: image ? image.path : null,
|
|
392
|
+
onProgress: (info) => console.log(formatProgress(info)),
|
|
457
393
|
requestApproval: requestCodeApproval
|
|
458
394
|
});
|
|
459
395
|
|
|
@@ -467,717 +403,8 @@ program
|
|
|
467
403
|
}
|
|
468
404
|
});
|
|
469
405
|
|
|
406
|
+
// ── Parse ────────────────────────────────────────────────────────────────────
|
|
470
407
|
program.parseAsync(process.argv).catch((error) => {
|
|
471
408
|
console.error(`${colors.pink}${error.message}${colors.reset}`);
|
|
472
409
|
process.exitCode = 1;
|
|
473
410
|
});
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* The Interactive Chat Loop — Gemini-style TUI
|
|
477
|
-
*/
|
|
478
|
-
async function startInteractiveChat(initialMessage = null, options = {}) {
|
|
479
|
-
let lastResponseText = "";
|
|
480
|
-
let recentImageContextText = "";
|
|
481
|
-
const formatErrorMessage = (err) => err && err.message ? err.message : String(err || 'Unknown error');
|
|
482
|
-
const streamAssistantSentences = async (text, appendMessage, metadata = {}, streamMessage = null) => {
|
|
483
|
-
const sentences = splitResponseSentences(text);
|
|
484
|
-
if (typeof streamMessage === 'function') {
|
|
485
|
-
const stream = streamMessage(metadata);
|
|
486
|
-
for (let index = 0; index < sentences.length; index++) {
|
|
487
|
-
const prefix = index === 0 ? '' : ' ';
|
|
488
|
-
stream.appendChunk(`${prefix}${sentences[index]}`);
|
|
489
|
-
if (index < sentences.length - 1) {
|
|
490
|
-
await sleep(90);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
stream.finalize();
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
for (let index = 0; index < sentences.length; index++) {
|
|
498
|
-
appendMessage('assistant', sentences[index], index === 0 ? metadata : {});
|
|
499
|
-
if (index < sentences.length - 1) {
|
|
500
|
-
await sleep(90);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
const sendImageMessage = async ({ images, image, prompt, appendMessage, streamMessage, setThinking, appendCodeStep }) => {
|
|
505
|
-
const imageList = images || (image ? [image] : []);
|
|
506
|
-
const message = prompt || 'Analyze this image.';
|
|
507
|
-
const labels = imageList.map((_, index) => `[Image #${index + 1}]`).join(' ');
|
|
508
|
-
const displayMessage = labels && message.includes(labels)
|
|
509
|
-
? message
|
|
510
|
-
: `${message}\n${labels}`;
|
|
511
|
-
appendMessage('user', displayMessage);
|
|
512
|
-
if (appendCodeStep) {
|
|
513
|
-
appendCodeStep({
|
|
514
|
-
thought: imageList.length > 1
|
|
515
|
-
? `Analyzing ${imageList.length} attached images before answering.`
|
|
516
|
-
: 'Analyzing the attached image before answering.'
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
let seconds = 0;
|
|
521
|
-
setThinking(true, seconds);
|
|
522
|
-
const timer = setInterval(() => {
|
|
523
|
-
seconds++;
|
|
524
|
-
setThinking(true, seconds);
|
|
525
|
-
}, 1000);
|
|
526
|
-
|
|
527
|
-
try {
|
|
528
|
-
const result = await handleChat(message, imageList.map(item => item.dataUri), null);
|
|
529
|
-
clearInterval(timer);
|
|
530
|
-
setThinking(false);
|
|
531
|
-
const responseText = result.response || '';
|
|
532
|
-
lastResponseText = responseText;
|
|
533
|
-
recentImageContextText = [
|
|
534
|
-
`Recent image context: the user attached ${imageList.length} image(s) labelled ${labels || '[Image #1]'}.`,
|
|
535
|
-
'The terminal UI displays image attachments as labels only; it does not render thumbnails inside the chat.',
|
|
536
|
-
`Assistant response to those image(s): ${responseText}`
|
|
537
|
-
].join('\n');
|
|
538
|
-
await streamAssistantSentences(responseText, appendMessage, { providerInfo: result.providerInfo }, streamMessage);
|
|
539
|
-
return responseText;
|
|
540
|
-
} catch (err) {
|
|
541
|
-
clearInterval(timer);
|
|
542
|
-
setThinking(false);
|
|
543
|
-
appendMessage('error', formatErrorMessage(err));
|
|
544
|
-
return '';
|
|
545
|
-
}
|
|
546
|
-
};
|
|
547
|
-
|
|
548
|
-
const ui = await createChatUI({
|
|
549
|
-
onPasteImage: async () => {
|
|
550
|
-
try {
|
|
551
|
-
const image = loadClipboardImageAsDataUri();
|
|
552
|
-
return { label: image.path, image };
|
|
553
|
-
} catch (err) {
|
|
554
|
-
throw new Error(formatErrorMessage(err));
|
|
555
|
-
}
|
|
556
|
-
},
|
|
557
|
-
onSubmit: async (text, submitOptions = {}) => {
|
|
558
|
-
const { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode, appendCodeStep, updateWorkspace, askUser, attachImage, setInputText, setPendingPasteText, setFastMode, toggleFastMode, getFastMode } = ui;
|
|
559
|
-
if (submitOptions.images && submitOptions.images.length > 0) {
|
|
560
|
-
const images = submitOptions.images.map(item => item.image || item);
|
|
561
|
-
await sendImageMessage({
|
|
562
|
-
images,
|
|
563
|
-
prompt: text.trim() || 'Analyze this image.',
|
|
564
|
-
appendMessage,
|
|
565
|
-
streamMessage,
|
|
566
|
-
setThinking,
|
|
567
|
-
appendCodeStep
|
|
568
|
-
});
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if (text.startsWith('/')) {
|
|
573
|
-
if (text.startsWith('/agent')) {
|
|
574
|
-
const args = text.split(' ');
|
|
575
|
-
if (args[1] === 'list') {
|
|
576
|
-
appendMessage('system', `Available Agents: ${agentOrchestrator.listAgents().join(', ')}`);
|
|
577
|
-
} else if (args[1]) {
|
|
578
|
-
const success = agentOrchestrator.setAgent(args[1]);
|
|
579
|
-
if (success) {
|
|
580
|
-
const agent = agentOrchestrator.getCurrentAgent();
|
|
581
|
-
appendMessage('system', `Switched to Agent: ${agent.icon} ${agent.name}`);
|
|
582
|
-
updateStatusModel(agent.name); // Pass name to status bar
|
|
583
|
-
resetChat(); // Reset to apply new system prompt
|
|
584
|
-
} else {
|
|
585
|
-
appendMessage('error', `Agent "${args[1]}" not found. Try /agent list`);
|
|
586
|
-
}
|
|
587
|
-
} else {
|
|
588
|
-
const agent = agentOrchestrator.getCurrentAgent();
|
|
589
|
-
appendMessage('system', `Current Agent: ${agent.icon} ${agent.name}\nUsage: /agent <type> or /agent list`);
|
|
590
|
-
}
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (text.startsWith('/stats')) {
|
|
595
|
-
appendMessage('system', '📊 Fetching system statistics...');
|
|
596
|
-
const stats = await systemMonitor.execute('stats');
|
|
597
|
-
appendMessage('system', stats);
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
if (text.startsWith('/workspace')) {
|
|
602
|
-
const args = text.split(' ');
|
|
603
|
-
const subCmd = args[1];
|
|
604
|
-
|
|
605
|
-
if (subCmd === 'add') {
|
|
606
|
-
const name = args[2];
|
|
607
|
-
const wsPath = args[3] || '.';
|
|
608
|
-
const instructions = args.slice(4).join(' ');
|
|
609
|
-
if (!name) {
|
|
610
|
-
appendMessage('error', 'Usage: /workspace add <name> [path] [instructions]');
|
|
611
|
-
} else {
|
|
612
|
-
workspaceManager.addWorkspace(name, wsPath, instructions);
|
|
613
|
-
appendMessage('system', `Workspace "${name}" registered at ${path.resolve(wsPath)}`);
|
|
614
|
-
resetChat();
|
|
615
|
-
}
|
|
616
|
-
} else if (subCmd === 'list') {
|
|
617
|
-
const all = workspaceManager.listWorkspaces();
|
|
618
|
-
let listMsg = "Registered Workspaces:\n";
|
|
619
|
-
for (const n in all) listMsg += `- ${n}: ${all[n].path}\n`;
|
|
620
|
-
appendMessage('system', Object.keys(all).length ? listMsg : "No workspaces registered.");
|
|
621
|
-
} else if (subCmd === 'remove') {
|
|
622
|
-
const name = args[2];
|
|
623
|
-
if (workspaceManager.removeWorkspace(name)) {
|
|
624
|
-
appendMessage('system', `Removed workspace "${name}"`);
|
|
625
|
-
resetChat();
|
|
626
|
-
} else {
|
|
627
|
-
appendMessage('error', `Workspace "${name}" not found.`);
|
|
628
|
-
}
|
|
629
|
-
} else if (subCmd === 'use' || subCmd === 'switch') {
|
|
630
|
-
const name = args[2];
|
|
631
|
-
const all = workspaceManager.listWorkspaces();
|
|
632
|
-
if (all[name]) {
|
|
633
|
-
const newPath = all[name].path;
|
|
634
|
-
try {
|
|
635
|
-
process.chdir(newPath);
|
|
636
|
-
updateWorkspace(newPath);
|
|
637
|
-
appendMessage('system', `✓ Switched to workspace "${name}" at ${newPath}`);
|
|
638
|
-
resetChat();
|
|
639
|
-
} catch (e) {
|
|
640
|
-
appendMessage('error', `Failed to change directory: ${e.message}`);
|
|
641
|
-
}
|
|
642
|
-
} else {
|
|
643
|
-
appendMessage('error', `Workspace "${name}" not found. Try /workspace list`);
|
|
644
|
-
}
|
|
645
|
-
} else {
|
|
646
|
-
const ws = workspaceManager.getWorkspaceByPath(process.cwd());
|
|
647
|
-
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>`);
|
|
648
|
-
}
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
if (text.startsWith('/review')) {
|
|
653
|
-
if (!lastResponseText) {
|
|
654
|
-
appendMessage('error', 'Nothing to review yet. Get a response first.');
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
agentOrchestrator.setAgent('reviewer');
|
|
658
|
-
appendMessage('system', '⚖️ Requesting second-pass review from Mint Reviewer...');
|
|
659
|
-
text = `Please review this previous response and provide a critique:\n\n${lastResponseText}`;
|
|
660
|
-
} else {
|
|
661
|
-
// Other slash commands
|
|
662
|
-
const fakeRl = { close: () => { } };
|
|
663
|
-
if (!text.startsWith('/image') && !text.startsWith('/paste')) {
|
|
664
|
-
appendMessage('user', text);
|
|
665
|
-
}
|
|
666
|
-
const slashResult = await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace, {
|
|
667
|
-
sendImageMessage,
|
|
668
|
-
formatErrorMessage,
|
|
669
|
-
attachImage,
|
|
670
|
-
setInputText,
|
|
671
|
-
setPendingPasteText,
|
|
672
|
-
setFastMode,
|
|
673
|
-
toggleFastMode,
|
|
674
|
-
getFastMode,
|
|
675
|
-
streamAssistantSentences,
|
|
676
|
-
streamMessage
|
|
677
|
-
});
|
|
678
|
-
if (slashResult && slashResult.lastResponseText) {
|
|
679
|
-
lastResponseText = slashResult.lastResponseText;
|
|
680
|
-
}
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
appendMessage('user', text);
|
|
685
|
-
|
|
686
|
-
const transcript = await getChatTranscript();
|
|
687
|
-
const contextualHistory = recentImageContextText
|
|
688
|
-
? [...transcript, { sender: 'system', text: recentImageContextText, timestamp: new Date().toISOString() }]
|
|
689
|
-
: transcript;
|
|
690
|
-
if (setMode) setMode('Agent');
|
|
691
|
-
|
|
692
|
-
let seconds = 0;
|
|
693
|
-
setThinking(true, seconds);
|
|
694
|
-
const timer = setInterval(() => {
|
|
695
|
-
seconds++;
|
|
696
|
-
setThinking(true, seconds);
|
|
697
|
-
}, 1000);
|
|
698
|
-
|
|
699
|
-
try {
|
|
700
|
-
const config = require('./src/System/config_manager').readConfig();
|
|
701
|
-
const availableProviders = require('./src/System/config_manager').getAvailableProviders(config);
|
|
702
|
-
const preferredProvider = require('./src/CLI/code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
|
|
703
|
-
let streamedFinalSummary = false;
|
|
704
|
-
|
|
705
|
-
const result = await executeCodeTask(text, {
|
|
706
|
-
cwd: process.cwd(),
|
|
707
|
-
requestApproval,
|
|
708
|
-
askUser,
|
|
709
|
-
provider: preferredProvider,
|
|
710
|
-
history: contextualHistory,
|
|
711
|
-
onProgress: (info) => {
|
|
712
|
-
if (appendCodeStep) appendCodeStep(info);
|
|
713
|
-
},
|
|
714
|
-
onFinalSummary: async (info) => {
|
|
715
|
-
clearInterval(timer);
|
|
716
|
-
setThinking(false);
|
|
717
|
-
streamedFinalSummary = true;
|
|
718
|
-
await streamAssistantSentences(info.summary, appendMessage, { providerInfo: info.providerInfo }, streamMessage);
|
|
719
|
-
}
|
|
720
|
-
});
|
|
721
|
-
|
|
722
|
-
clearInterval(timer);
|
|
723
|
-
setThinking(false);
|
|
724
|
-
lastResponseText = result.summary;
|
|
725
|
-
if (!streamedFinalSummary) {
|
|
726
|
-
await streamAssistantSentences(result.summary, appendMessage, { providerInfo: result.providerInfo }, streamMessage);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
} catch (err) {
|
|
730
|
-
clearInterval(timer);
|
|
731
|
-
setThinking(false);
|
|
732
|
-
appendMessage('error', formatErrorMessage(err));
|
|
733
|
-
} finally {
|
|
734
|
-
if (setMode) setMode('Agent');
|
|
735
|
-
}
|
|
736
|
-
},
|
|
737
|
-
onExit: () => {
|
|
738
|
-
exitWithGoodbye(0);
|
|
739
|
-
}
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
// Handle initial image if passed via CLI option.
|
|
743
|
-
if (options.imagePath) {
|
|
744
|
-
const { appendMessage, streamMessage, setThinking, appendCodeStep } = ui;
|
|
745
|
-
const image = loadImageAsDataUri(options.imagePath);
|
|
746
|
-
const prompt = initialMessage || 'Analyze this image.';
|
|
747
|
-
await sendImageMessage({ images: [image], prompt, appendMessage, streamMessage, setThinking, appendCodeStep });
|
|
748
|
-
|
|
749
|
-
return;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
// Handle initial message if passed via CLI arg
|
|
753
|
-
if (initialMessage) {
|
|
754
|
-
const { appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode, appendCodeStep, updateWorkspace, askUser } = ui;
|
|
755
|
-
appendMessage('user', initialMessage);
|
|
756
|
-
const transcript = await getChatTranscript();
|
|
757
|
-
const contextualHistory = recentImageContextText
|
|
758
|
-
? [...transcript, { sender: 'system', text: recentImageContextText, timestamp: new Date().toISOString() }]
|
|
759
|
-
: transcript;
|
|
760
|
-
if (setMode) setMode('Agent');
|
|
761
|
-
|
|
762
|
-
let seconds = 0;
|
|
763
|
-
setThinking(true, seconds);
|
|
764
|
-
const timer = setInterval(() => {
|
|
765
|
-
seconds++;
|
|
766
|
-
setThinking(true, seconds);
|
|
767
|
-
}, 1000);
|
|
768
|
-
|
|
769
|
-
try {
|
|
770
|
-
const config = require('./src/System/config_manager').readConfig();
|
|
771
|
-
const availableProviders = require('./src/System/config_manager').getAvailableProviders(config);
|
|
772
|
-
const preferredProvider = require('./src/CLI/code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
|
|
773
|
-
let streamedFinalSummary = false;
|
|
774
|
-
|
|
775
|
-
const result = await executeCodeTask(initialMessage, {
|
|
776
|
-
cwd: process.cwd(),
|
|
777
|
-
requestApproval,
|
|
778
|
-
askUser,
|
|
779
|
-
provider: preferredProvider,
|
|
780
|
-
history: contextualHistory,
|
|
781
|
-
onProgress: (info) => {
|
|
782
|
-
if (appendCodeStep) appendCodeStep(info);
|
|
783
|
-
},
|
|
784
|
-
onFinalSummary: async (info) => {
|
|
785
|
-
clearInterval(timer);
|
|
786
|
-
setThinking(false);
|
|
787
|
-
streamedFinalSummary = true;
|
|
788
|
-
await streamAssistantSentences(info.summary, appendMessage, { providerInfo: info.providerInfo }, streamMessage);
|
|
789
|
-
}
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
clearInterval(timer);
|
|
793
|
-
setThinking(false);
|
|
794
|
-
lastResponseText = result.summary;
|
|
795
|
-
if (!streamedFinalSummary) {
|
|
796
|
-
await streamAssistantSentences(result.summary, appendMessage, { providerInfo: result.providerInfo }, streamMessage);
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
} catch (err) {
|
|
800
|
-
clearInterval(timer);
|
|
801
|
-
setThinking(false);
|
|
802
|
-
appendMessage('error', formatErrorMessage(err));
|
|
803
|
-
} finally {
|
|
804
|
-
if (setMode) setMode('Agent');
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
/**
|
|
810
|
-
* Handles slash commands within the TUI context
|
|
811
|
-
*/
|
|
812
|
-
async function handleSlashCommandUI(input, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace, helpers = {}) {
|
|
813
|
-
const parts = input.split(' ');
|
|
814
|
-
const command = parts[0].toLowerCase();
|
|
815
|
-
const args = parts.slice(1);
|
|
816
|
-
|
|
817
|
-
switch (command) {
|
|
818
|
-
case '/help':
|
|
819
|
-
case '/?':
|
|
820
|
-
appendMessage('system', [
|
|
821
|
-
'Mint Slash Commands:',
|
|
822
|
-
' /image <path> [prompt] — Attach an image from your computer',
|
|
823
|
-
' /paste [prompt] — Attach an image from your clipboard',
|
|
824
|
-
' /fast [on|off] — Hide or show thinking/progress output',
|
|
825
|
-
' /learn <path> — Remember a .md/.txt file as a Mint skill',
|
|
826
|
-
' /code <task> — Force workspace Code Mode',
|
|
827
|
-
' /cd <path> — Change current working directory',
|
|
828
|
-
' /models [name] — List or switch Gemini models',
|
|
829
|
-
' /memory [cmd] — Manage long-term memory',
|
|
830
|
-
' /config — Show current configuration',
|
|
831
|
-
' /copy — Copy last response to clipboard',
|
|
832
|
-
' /clear — Clear conversation history',
|
|
833
|
-
' /reset — Reset conversation history',
|
|
834
|
-
' /exit — Exit Mint'
|
|
835
|
-
].join('\n'));
|
|
836
|
-
break;
|
|
837
|
-
|
|
838
|
-
case '/fast': {
|
|
839
|
-
if (!helpers.toggleFastMode || !helpers.setFastMode || !helpers.getFastMode) {
|
|
840
|
-
appendMessage('error', 'Fast mode is not available in this UI.');
|
|
841
|
-
break;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
const option = (args[0] || '').toLowerCase();
|
|
845
|
-
let enabled;
|
|
846
|
-
if (option === 'on' || option === 'true' || option === '1') {
|
|
847
|
-
enabled = helpers.setFastMode(true);
|
|
848
|
-
} else if (option === 'off' || option === 'false' || option === '0') {
|
|
849
|
-
enabled = helpers.setFastMode(false);
|
|
850
|
-
} else if (option === 'status') {
|
|
851
|
-
enabled = helpers.getFastMode();
|
|
852
|
-
} else {
|
|
853
|
-
enabled = helpers.toggleFastMode();
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
appendMessage('system', `Fast mode: ${enabled ? 'ON' : 'OFF'}`);
|
|
857
|
-
break;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
case '/learn': {
|
|
861
|
-
const filePath = input.slice(command.length).trim();
|
|
862
|
-
if (!filePath) {
|
|
863
|
-
appendMessage('system', 'Usage: /learn <path-to-skill.md>');
|
|
864
|
-
break;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
try {
|
|
868
|
-
const learned = learnSkillFile(filePath);
|
|
869
|
-
appendMessage('system', [
|
|
870
|
-
`✓ Learned skill: ${learned.name}`,
|
|
871
|
-
`Path: ${learned.source_path}`,
|
|
872
|
-
learned.stored_length < learned.content_length
|
|
873
|
-
? `Stored first ${learned.stored_length} of ${learned.content_length} characters.`
|
|
874
|
-
: `Stored ${learned.stored_length} characters.`
|
|
875
|
-
].join('\n'));
|
|
876
|
-
} catch (err) {
|
|
877
|
-
appendMessage('error', err && err.message ? err.message : String(err || 'Unknown error'));
|
|
878
|
-
}
|
|
879
|
-
break;
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
case '/image': {
|
|
883
|
-
if (args.length === 0) {
|
|
884
|
-
appendMessage('system', 'Usage: /image <path> [prompt]');
|
|
885
|
-
break;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
const imagePath = args[0];
|
|
889
|
-
const prompt = args.slice(1).join(' ').trim();
|
|
890
|
-
|
|
891
|
-
try {
|
|
892
|
-
const image = loadImageAsDataUri(imagePath);
|
|
893
|
-
if (helpers.attachImage) {
|
|
894
|
-
helpers.attachImage({ label: image.path, image });
|
|
895
|
-
if (prompt && helpers.setInputText) {
|
|
896
|
-
helpers.setInputText(prompt);
|
|
897
|
-
}
|
|
898
|
-
appendMessage('system', 'Attached image. Press Enter to send.');
|
|
899
|
-
} else {
|
|
900
|
-
appendMessage('error', 'Image attachment is not available in this UI.');
|
|
901
|
-
}
|
|
902
|
-
} catch (err) {
|
|
903
|
-
appendMessage('error', err && err.message ? err.message : String(err || 'Unknown error'));
|
|
904
|
-
}
|
|
905
|
-
break;
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
case '/paste': {
|
|
909
|
-
try {
|
|
910
|
-
const image = loadClipboardImageAsDataUri();
|
|
911
|
-
if (helpers.attachImage) {
|
|
912
|
-
helpers.attachImage({ label: image.path, image });
|
|
913
|
-
const prompt = args.join(' ').trim();
|
|
914
|
-
if (prompt && helpers.setInputText) {
|
|
915
|
-
helpers.setInputText(prompt);
|
|
916
|
-
}
|
|
917
|
-
appendMessage('system', 'Attached clipboard image. Press Enter to send.');
|
|
918
|
-
} else {
|
|
919
|
-
appendMessage('error', 'Image attachment is not available in this UI.');
|
|
920
|
-
}
|
|
921
|
-
} catch (err) {
|
|
922
|
-
appendMessage('error', helpers.formatErrorMessage ? helpers.formatErrorMessage(err) : (err && err.message ? err.message : String(err || 'Unknown error')));
|
|
923
|
-
}
|
|
924
|
-
break;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
case '/memory': {
|
|
928
|
-
const subCommand = (args[0] || 'list').toLowerCase();
|
|
929
|
-
const query = args.slice(1).join(' ').trim();
|
|
930
|
-
|
|
931
|
-
if (subCommand === 'help') {
|
|
932
|
-
appendMessage('system', [
|
|
933
|
-
'Memory Commands:',
|
|
934
|
-
' /memory list [n] — Show recent remembered interactions',
|
|
935
|
-
' /memory search <query> — Search remembered interactions',
|
|
936
|
-
' /memory skills — Show learned skill files',
|
|
937
|
-
' /memory skills delete <id|path|name> — Delete a learned skill',
|
|
938
|
-
' /memory profile — Show remembered profile fields',
|
|
939
|
-
' /memory context [q] — Show context Mint injects into prompts',
|
|
940
|
-
' /memory delete <id> — Delete one remembered interaction',
|
|
941
|
-
' /memory export [path] — Export memory snapshot as JSON',
|
|
942
|
-
' /memory clear — Clear episodic interaction memories'
|
|
943
|
-
].join('\n'));
|
|
944
|
-
break;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
if (subCommand === 'profile') {
|
|
948
|
-
const profile = memoryStore.getAllProfile();
|
|
949
|
-
appendMessage('system', Object.keys(profile).length
|
|
950
|
-
? JSON.stringify(profile, null, 2)
|
|
951
|
-
: 'No profile memory stored yet.');
|
|
952
|
-
break;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
if (subCommand === 'skills') {
|
|
956
|
-
if ((args[1] || '').toLowerCase() === 'delete') {
|
|
957
|
-
const identifier = args.slice(2).join(' ').trim();
|
|
958
|
-
if (!identifier) {
|
|
959
|
-
appendMessage('system', 'Usage: /memory skills delete <id|path|name>');
|
|
960
|
-
break;
|
|
961
|
-
}
|
|
962
|
-
const deleted = memoryStore.deleteLearnedSkill(identifier);
|
|
963
|
-
appendMessage('system', deleted > 0
|
|
964
|
-
? `Deleted learned skill: ${identifier}`
|
|
965
|
-
: `Learned skill not found: ${identifier}`);
|
|
966
|
-
break;
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
const skills = memoryStore.getLearnedSkills(20);
|
|
970
|
-
appendMessage('system', skills.length
|
|
971
|
-
? [
|
|
972
|
-
'Learned skills:',
|
|
973
|
-
...skills.map((skill) => `#${skill.id} ${skill.name}\n ${skill.source_path}`)
|
|
974
|
-
].join('\n')
|
|
975
|
-
: 'No learned skills stored yet.');
|
|
976
|
-
break;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
if (subCommand === 'context') {
|
|
980
|
-
const ctx = memoryStore.getUserContext(query);
|
|
981
|
-
appendMessage('system', ctx || 'No memory context stored yet.');
|
|
982
|
-
break;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
if (subCommand === 'search') {
|
|
986
|
-
if (!query) {
|
|
987
|
-
appendMessage('system', 'Usage: /memory search <query>');
|
|
988
|
-
break;
|
|
989
|
-
}
|
|
990
|
-
const results = memoryStore.searchInteractions(query, 10);
|
|
991
|
-
appendMessage('system', formatMemoryInteractions(results, `Search results for "${query}"`));
|
|
992
|
-
break;
|
|
993
|
-
}
|
|
994
|
-
|
|
995
|
-
if (subCommand === 'export') {
|
|
996
|
-
const exportPath = query
|
|
997
|
-
? path.resolve(process.cwd(), query)
|
|
998
|
-
: path.join(process.cwd(), `mint-memory-export-${Date.now()}.json`);
|
|
999
|
-
fs.writeFileSync(exportPath, JSON.stringify(memoryStore.exportMemorySnapshot(), null, 2), 'utf8');
|
|
1000
|
-
appendMessage('system', `Memory exported to: ${exportPath}`);
|
|
1001
|
-
break;
|
|
1002
|
-
}
|
|
1003
|
-
|
|
1004
|
-
if (subCommand === 'delete') {
|
|
1005
|
-
const id = Number.parseInt(args[1] || '', 10);
|
|
1006
|
-
if (!Number.isFinite(id)) {
|
|
1007
|
-
appendMessage('system', 'Usage: /memory delete <id>');
|
|
1008
|
-
break;
|
|
1009
|
-
}
|
|
1010
|
-
const deleted = memoryStore.deleteInteractionMemory(id);
|
|
1011
|
-
appendMessage('system', deleted ? `Deleted memory #${id}.` : `Memory #${id} was not found.`);
|
|
1012
|
-
break;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
if (subCommand === 'clear') {
|
|
1016
|
-
memoryStore.clearInteractionMemories();
|
|
1017
|
-
appendMessage('system', 'Cleared episodic interaction memories. Profile memory is unchanged.');
|
|
1018
|
-
break;
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
const limit = Number.parseInt(args[0] || '10', 10);
|
|
1022
|
-
const interactions = memoryStore.getRecentInteractions(Number.isFinite(limit) ? limit : 10);
|
|
1023
|
-
appendMessage('system', formatMemoryInteractions(interactions, 'Recent remembered interactions'));
|
|
1024
|
-
break;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
case '/cd':
|
|
1028
|
-
if (args.length === 0) {
|
|
1029
|
-
appendMessage('system', `Current Directory: ${process.cwd()}`);
|
|
1030
|
-
break;
|
|
1031
|
-
}
|
|
1032
|
-
try {
|
|
1033
|
-
const newPath = path.resolve(process.cwd(), args[0]);
|
|
1034
|
-
if (fs.existsSync(newPath) && fs.lstatSync(newPath).isDirectory()) {
|
|
1035
|
-
process.chdir(newPath);
|
|
1036
|
-
if (updateWorkspace) updateWorkspace(newPath);
|
|
1037
|
-
appendMessage('system', `✓ Directory changed to: ${newPath}`);
|
|
1038
|
-
} else {
|
|
1039
|
-
appendMessage('error', `Directory not found: ${newPath}`);
|
|
1040
|
-
}
|
|
1041
|
-
} catch (err) {
|
|
1042
|
-
appendMessage('error', `Error: ${err.message}`);
|
|
1043
|
-
}
|
|
1044
|
-
break;
|
|
1045
|
-
|
|
1046
|
-
case '/model':
|
|
1047
|
-
case '/models':
|
|
1048
|
-
const config = readConfig();
|
|
1049
|
-
if (args.length === 0) {
|
|
1050
|
-
appendMessage('system', [
|
|
1051
|
-
`Current Provider: ${config.aiProvider}`,
|
|
1052
|
-
`Current Gemini Model: ${config.geminiModel}`,
|
|
1053
|
-
'Available Providers/Presets:',
|
|
1054
|
-
' - gemini-2.5-flash (Default Gemini)',
|
|
1055
|
-
' - ollama (Local provider)',
|
|
1056
|
-
' - anthropic (Claude)',
|
|
1057
|
-
' - openai (GPT)',
|
|
1058
|
-
' - huggingface (Inference API)',
|
|
1059
|
-
' - local (LM Studio / OpenAI Compatible)',
|
|
1060
|
-
'Usage: /models <name> to switch'
|
|
1061
|
-
].join('\n'));
|
|
1062
|
-
} else {
|
|
1063
|
-
const { writeConfig } = require('./src/System/config_manager');
|
|
1064
|
-
const newModel = args[0];
|
|
1065
|
-
let newProvider = 'gemini';
|
|
1066
|
-
|
|
1067
|
-
if (newModel === 'ollama') {
|
|
1068
|
-
newProvider = 'ollama';
|
|
1069
|
-
} else if (newModel === 'anthropic') {
|
|
1070
|
-
newProvider = 'anthropic';
|
|
1071
|
-
} else if (newModel === 'openai') {
|
|
1072
|
-
newProvider = 'openai';
|
|
1073
|
-
} else if (newModel === 'huggingface') {
|
|
1074
|
-
newProvider = 'huggingface';
|
|
1075
|
-
} else if (newModel === 'local' || newModel === 'local_openai') {
|
|
1076
|
-
newProvider = 'local_openai';
|
|
1077
|
-
} else if (newModel.startsWith('gpt-')) {
|
|
1078
|
-
newProvider = 'openai';
|
|
1079
|
-
config.openaiModel = newModel;
|
|
1080
|
-
} else if (newModel.startsWith('claude-')) {
|
|
1081
|
-
newProvider = 'anthropic';
|
|
1082
|
-
config.anthropicModel = newModel;
|
|
1083
|
-
} else {
|
|
1084
|
-
newProvider = 'gemini';
|
|
1085
|
-
config.geminiModel = newModel;
|
|
1086
|
-
}
|
|
1087
|
-
|
|
1088
|
-
config.aiProvider = newProvider;
|
|
1089
|
-
writeConfig(config);
|
|
1090
|
-
appendMessage('system', `✅ Switched to: ${newProvider} ${newProvider === 'gemini' ? `(${newModel})` : ''}`);
|
|
1091
|
-
if (updateStatusModel) updateStatusModel(newProvider === 'gemini' ? newModel : newProvider);
|
|
1092
|
-
}
|
|
1093
|
-
break;
|
|
1094
|
-
|
|
1095
|
-
case '/code':
|
|
1096
|
-
if (args.length === 0) {
|
|
1097
|
-
appendMessage('system', 'Usage: /code <task>');
|
|
1098
|
-
break;
|
|
1099
|
-
}
|
|
1100
|
-
await runChatRoutedTask(`/code ${args.join(' ')}`, {
|
|
1101
|
-
appendMessage,
|
|
1102
|
-
setThinking,
|
|
1103
|
-
requestApproval,
|
|
1104
|
-
appendCodeStep,
|
|
1105
|
-
setMode,
|
|
1106
|
-
streamAssistantSentences: helpers.streamAssistantSentences,
|
|
1107
|
-
streamMessage: helpers.streamMessage,
|
|
1108
|
-
askUser: () => Promise.resolve(''),
|
|
1109
|
-
history: await getChatTranscript()
|
|
1110
|
-
});
|
|
1111
|
-
break;
|
|
1112
|
-
|
|
1113
|
-
case '/config':
|
|
1114
|
-
const currentCfg = readConfig();
|
|
1115
|
-
appendMessage('system', [
|
|
1116
|
-
'Current Configuration:',
|
|
1117
|
-
` Version : v${pkg.version}`,
|
|
1118
|
-
` Provider : ${currentCfg.aiProvider}`,
|
|
1119
|
-
` Model : ${currentCfg.geminiModel}`,
|
|
1120
|
-
` Ollama : ${currentCfg.ollamaModel}`,
|
|
1121
|
-
` Voice : ${currentCfg.enableVoiceReply ? 'ON' : 'OFF'}`,
|
|
1122
|
-
` Language : ${currentCfg.language}`,
|
|
1123
|
-
` API Key : ${currentCfg.apiKey ? 'SET (****)' : 'NOT SET'}`
|
|
1124
|
-
].join('\n'));
|
|
1125
|
-
break;
|
|
1126
|
-
|
|
1127
|
-
case '/copy':
|
|
1128
|
-
if (copyLastResponse && copyLastResponse()) {
|
|
1129
|
-
appendMessage('system', '✓ Last response copied to clipboard.');
|
|
1130
|
-
} else {
|
|
1131
|
-
appendMessage('system', '✖ Nothing to copy, or xclip/xsel not installed.');
|
|
1132
|
-
}
|
|
1133
|
-
break;
|
|
1134
|
-
|
|
1135
|
-
case '/clear':
|
|
1136
|
-
case '/reset':
|
|
1137
|
-
resetChat();
|
|
1138
|
-
appendMessage('system', 'Conversation history cleared.');
|
|
1139
|
-
break;
|
|
1140
|
-
|
|
1141
|
-
case '/exit':
|
|
1142
|
-
case '/quit':
|
|
1143
|
-
exitWithGoodbye(0);
|
|
1144
|
-
break;
|
|
1145
|
-
|
|
1146
|
-
default:
|
|
1147
|
-
appendMessage('system', `Unknown command: ${command}. Type /help for options.`);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
async function requestCodeApproval(request) {
|
|
1152
|
-
const typeLabel = request.type === 'shell'
|
|
1153
|
-
? 'Shell Command'
|
|
1154
|
-
: request.type === 'patch'
|
|
1155
|
-
? 'Patch Edit'
|
|
1156
|
-
: 'File Write';
|
|
1157
|
-
|
|
1158
|
-
console.log(`\n${colors.yellow}${colors.bright}[Approval Required]${colors.reset} ${typeLabel}`);
|
|
1159
|
-
if (request.label) {
|
|
1160
|
-
console.log(`${colors.gray}${request.label}${colors.reset}`);
|
|
1161
|
-
}
|
|
1162
|
-
if (request.preview) {
|
|
1163
|
-
console.log(`${colors.gray}${request.preview}${colors.reset}\n`);
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
const rl = readline.createInterface({
|
|
1167
|
-
input: process.stdin,
|
|
1168
|
-
output: process.stdout
|
|
1169
|
-
});
|
|
1170
|
-
|
|
1171
|
-
const answer = await new Promise((resolve) => {
|
|
1172
|
-
rl.question('Approve this action? [y/N]: ', (value) => {
|
|
1173
|
-
rl.close();
|
|
1174
|
-
resolve((value || '').trim().toLowerCase());
|
|
1175
|
-
});
|
|
1176
|
-
});
|
|
1177
|
-
|
|
1178
|
-
const approved = answer === 'y' || answer === 'yes';
|
|
1179
|
-
console.log(approved
|
|
1180
|
-
? `${colors.mint}[Mint Code] Approved.${colors.reset}\n`
|
|
1181
|
-
: `${colors.pink}[Mint Code] Denied.${colors.reset}\n`);
|
|
1182
|
-
return approved;
|
|
1183
|
-
}
|