@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.
Files changed (33) hide show
  1. package/README.md +8 -0
  2. package/mint-cli.js +148 -921
  3. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +31 -1
  4. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +6 -1
  5. package/package.json +18 -20
  6. package/src/AI_Brain/proactive_engine.js +12 -2
  7. package/src/Automation_Layer/browser_automation.js +26 -24
  8. package/src/CLI/approval_handler.js +42 -0
  9. package/src/CLI/chat_ui.js +192 -7
  10. package/src/CLI/cli_colors.js +32 -0
  11. package/src/CLI/cli_formatters.js +89 -0
  12. package/src/CLI/code_agent.js +166 -57
  13. package/src/CLI/intent_detectors.js +181 -0
  14. package/src/CLI/interactive_chat.js +479 -0
  15. package/src/CLI/list_features.js +3 -0
  16. package/src/CLI/repo_summarizer.js +282 -0
  17. package/src/CLI/semantic_code_search.js +312 -0
  18. package/src/CLI/skill_manager.js +41 -0
  19. package/src/CLI/slash_command_handler.js +418 -0
  20. package/src/CLI/symbol_indexer.js +231 -0
  21. package/src/Channels/discord_bridge.js +11 -13
  22. package/src/Channels/line_bridge.js +10 -10
  23. package/src/Channels/slack_bridge.js +7 -12
  24. package/src/Channels/telegram_bridge.js +6 -14
  25. package/src/Channels/whatsapp_bridge.js +11 -9
  26. package/src/System/chat_history_manager.js +20 -12
  27. package/src/System/optional_require.js +23 -0
  28. package/src/UI/live2d_manager.js +211 -13
  29. package/src/UI/renderer.js +163 -3
  30. package/src/UI/settings.css +655 -420
  31. package/src/UI/settings.html +478 -432
  32. package/src/UI/settings.js +10 -8
  33. 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' && data.name === 'ExperimentalWarning' && data.message.includes('SQLite')) {
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
- const fs = require('fs');
13
- const path = require('path');
14
- const { handleChat, handleGeminiChatStream, resetChat, refreshApiKeyFromConfig, getChatTranscript } = require('./src/AI_Brain/Gemini_API');
15
- const agentOrchestrator = require('./src/AI_Brain/agent_orchestrator');
16
- const workspaceManager = require('./src/CLI/workspace_manager');
17
- const systemMonitor = require('./src/Plugins/system_monitor');
18
- const { sendNotification } = require('./src/System/notifications');
19
- const pkg = require('./package.json');
20
- const { runOnboarding } = require('./src/CLI/onboarding');
21
- const { startAgent } = require('./src/AI_Brain/headless_agent');
22
- const { displayFeatures } = require('./src/CLI/list_features');
23
- const { readConfig, writeConfig } = require('./src/System/config_manager');
24
- const { executeCodeTask } = require('./src/CLI/code_agent');
25
- const { detectCodeIntent, runChatRoutedTask } = require('./src/CLI/chat_router');
26
- const memoryStore = require('./src/AI_Brain/memory_store');
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 } = require('./src/CLI/gmail_auth');
31
- const { loadImageAsDataUri, loadClipboardImageAsDataUri } = require('./src/CLI/image_input');
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
- // Startup Info
34
- const startupConfig = readConfig();
42
+ const pkg = require('./package.json');
43
+
44
+ // ── Startup banner ───────────────────────────────────────────────────────────
45
+ const startupConfig = readConfig();
35
46
  const startupProvider = startupConfig.aiProvider || 'gemini';
36
- const startupModel = startupProvider === 'openai'
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
- function learnSkillFile(filePath) {
149
- const targetPath = path.resolve(process.cwd(), filePath);
150
- if (!fs.existsSync(targetPath)) {
151
- throw new Error(`File not found: ${targetPath}`);
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
- const content = fs.readFileSync(targetPath, 'utf8');
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
- // Chat Command (Interactive Mode)
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
- displayFeatures();
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', 'Only check whether an update is available')
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 === true,
321
- dryRun: options.dryRun === true
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>', 'Server 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...>', 'Environment variables (KEY=VALUE)')
292
+ .option('-e, --env <env...>', 'Environment variables (KEY=VALUE)')
345
293
  .action((name, command, options) => {
346
- const config = readConfig();
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 = readConfig();
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', 'Print the auth link without opening a browser')
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: options.port ? Number(options.port) : 0,
360
+ port: options.port ? Number(options.port) : 0,
420
361
  openBrowser: options.open,
421
- logger: console
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(effectiveTask, {
451
- cwd: process.cwd(),
452
- imageDataUri: image ? image.dataUri : null,
453
- imagePath: image ? image.path : null,
454
- onProgress: (info) => {
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
- }