@pheem49/mint 1.5.0 → 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 (101) hide show
  1. package/README.md +35 -1
  2. package/main.js +28 -14
  3. package/mint-cli-logic.js +3 -119
  4. package/mint-cli.js +201 -500
  5. package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
  6. package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
  7. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +40 -0
  8. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json +15 -0
  9. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
  10. package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
  11. package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
  12. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +15 -0
  13. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
  14. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
  15. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
  16. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
  17. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
  18. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
  19. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
  20. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
  21. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
  22. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
  23. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
  24. package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +23 -0
  25. package/package.json +40 -17
  26. package/src/AI_Brain/Gemini_API.js +147 -46
  27. package/src/AI_Brain/autonomous_brain.js +2 -1
  28. package/src/AI_Brain/memory_store.js +299 -3
  29. package/src/AI_Brain/proactive_engine.js +12 -2
  30. package/src/Automation_Layer/browser_automation.js +26 -24
  31. package/src/CLI/approval_handler.js +42 -0
  32. package/src/CLI/chat_router.js +18 -6
  33. package/src/CLI/chat_ui.js +583 -52
  34. package/src/CLI/cli_colors.js +32 -0
  35. package/src/CLI/cli_formatters.js +89 -0
  36. package/src/CLI/code_agent.js +369 -71
  37. package/src/CLI/image_input.js +90 -0
  38. package/src/CLI/intent_detectors.js +181 -0
  39. package/src/CLI/interactive_chat.js +479 -0
  40. package/src/CLI/list_features.js +3 -0
  41. package/src/CLI/onboarding.js +72 -15
  42. package/src/CLI/repo_summarizer.js +282 -0
  43. package/src/CLI/semantic_code_search.js +312 -0
  44. package/src/CLI/skill_manager.js +41 -0
  45. package/src/CLI/slash_command_handler.js +418 -0
  46. package/src/CLI/symbol_indexer.js +231 -0
  47. package/src/CLI/updater.js +6 -4
  48. package/src/Channels/discord_bridge.js +11 -13
  49. package/src/Channels/line_bridge.js +10 -10
  50. package/src/Channels/slack_bridge.js +7 -12
  51. package/src/Channels/telegram_bridge.js +6 -14
  52. package/src/Channels/whatsapp_bridge.js +11 -9
  53. package/src/System/action_executor.js +59 -10
  54. package/src/System/chat_history_manager.js +20 -12
  55. package/src/System/config_manager.js +31 -1
  56. package/src/System/granular_automation.js +122 -53
  57. package/src/System/optional_require.js +23 -0
  58. package/src/System/proactive_loop.js +19 -3
  59. package/src/System/safety_manager.js +108 -0
  60. package/src/System/sandbox_runner.js +182 -0
  61. package/src/System/system_automation.js +127 -81
  62. package/src/System/system_info.js +70 -0
  63. package/src/System/tool_registry.js +280 -0
  64. package/src/System/window_manager.js +4 -2
  65. package/src/UI/live2d_manager.js +566 -0
  66. package/src/UI/renderer.js +339 -21
  67. package/src/UI/settings.css +655 -420
  68. package/src/UI/settings.html +478 -432
  69. package/src/UI/settings.js +10 -8
  70. package/src/UI/styles.css +516 -31
  71. package/.codex +0 -0
  72. package/docs/assets/Agent_Mint.png +0 -0
  73. package/docs/assets/CLI_Screen.png +0 -0
  74. package/docs/assets/Settings.png +0 -0
  75. package/docs/assets/icon.png +0 -0
  76. package/docs/guide.html +0 -632
  77. package/docs/index.html +0 -133
  78. package/docs/style.css +0 -579
  79. package/index.html +0 -16
  80. package/src/UI/index.html +0 -126
  81. package/tech_news.txt +0 -3
  82. package/test_knowledge.txt +0 -3
  83. package/tests/action_executor_safety.test.js +0 -67
  84. package/tests/agent_orchestrator.test.js +0 -41
  85. package/tests/chat_router.test.js +0 -42
  86. package/tests/code_agent.test.js +0 -69
  87. package/tests/config_manager.test.js +0 -141
  88. package/tests/docker.test.js +0 -46
  89. package/tests/file_operations.test.js +0 -57
  90. package/tests/gmail.test.js +0 -135
  91. package/tests/gmail_auth.test.js +0 -129
  92. package/tests/google_calendar.test.js +0 -113
  93. package/tests/google_tts_urls.test.js +0 -24
  94. package/tests/memory_store.test.js +0 -185
  95. package/tests/notion.test.js +0 -121
  96. package/tests/provider_routing.test.js +0 -83
  97. package/tests/safety_manager.test.js +0 -40
  98. package/tests/spotify.test.js +0 -201
  99. package/tests/system_monitor.test.js +0 -37
  100. package/tests/updater.test.js +0 -32
  101. package/tests/workspace_manager.test.js +0 -56
@@ -0,0 +1,479 @@
1
+ 'use strict';
2
+
3
+ const { colors, exitWithGoodbye } = require('./cli_colors');
4
+ const { splitResponseSentences } = require('./cli_formatters');
5
+ const {
6
+ isRepoSummaryRequest,
7
+ parseRepoSummaryArgs,
8
+ isSymbolIndexRequest,
9
+ parseSymbolIndexArgs,
10
+ isSemanticCodeSearchRequest,
11
+ parseSemanticCodeArgs,
12
+ extractSemanticCodeQuery
13
+ } = require('./intent_detectors');
14
+ const { handleSlashCommandUI } = require('./slash_command_handler');
15
+ const { createChatUI } = require('./chat_ui');
16
+ const { loadImageAsDataUri, loadClipboardImageAsDataUri } = require('./image_input');
17
+ const { summarizeRepository, formatRepoSummary } = require('./repo_summarizer');
18
+ const { buildSymbolIndex, formatSymbolIndex } = require('./symbol_indexer');
19
+ const {
20
+ indexSemanticCode,
21
+ searchSemanticCode,
22
+ formatSemanticCodeIndex,
23
+ formatSemanticCodeSearch
24
+ } = require('./semantic_code_search');
25
+ const { handleChat, getChatTranscript } = require('../AI_Brain/Gemini_API');
26
+ const agentOrchestrator = require('../AI_Brain/agent_orchestrator');
27
+ const systemMonitor = require('../Plugins/system_monitor');
28
+ const workspaceManager = require('./workspace_manager');
29
+ const { executeCodeTask } = require('./code_agent');
30
+ const { resetChat } = require('../AI_Brain/Gemini_API');
31
+
32
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Internal helpers
36
+ // ---------------------------------------------------------------------------
37
+
38
+ /**
39
+ * Streams response text sentence-by-sentence into the TUI.
40
+ */
41
+ async function streamAssistantSentences(text, appendMessage, metadata = {}, streamMessage = null) {
42
+ const sentences = splitResponseSentences(text);
43
+ const chunks = sentences.filter(s => String(s || '').trim());
44
+
45
+ if (typeof streamMessage === 'function') {
46
+ const stream = streamMessage(metadata);
47
+ for (let i = 0; i < chunks.length; i++) {
48
+ stream.appendChunk(chunks[i]);
49
+ if (i < chunks.length - 1) await sleep(90);
50
+ }
51
+ stream.finalize();
52
+ return;
53
+ }
54
+
55
+ for (let i = 0; i < chunks.length; i++) {
56
+ appendMessage('assistant', chunks[i], i === 0 ? metadata : {});
57
+ if (i < chunks.length - 1) await sleep(90);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Runs a timer that increments seconds and calls setThinking every 1s.
63
+ * Returns a cancel function.
64
+ */
65
+ function startThinkingTimer(setThinking) {
66
+ let seconds = 0;
67
+ setThinking(true, seconds);
68
+ const timer = setInterval(() => {
69
+ seconds++;
70
+ setThinking(true, seconds);
71
+ }, 1000);
72
+ return () => clearInterval(timer);
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Local tool message senders
77
+ // ---------------------------------------------------------------------------
78
+
79
+ async function sendRepoSummaryMessage({ rawArgs = '', appendMessage, streamMessage, setThinking }) {
80
+ const formatErr = (err) => err && err.message ? err.message : String(err || 'Unknown error');
81
+ try {
82
+ if (typeof setThinking === 'function') setThinking(false);
83
+ const opts = parseRepoSummaryArgs(rawArgs);
84
+ const summary = summarizeRepository(opts.targetPath);
85
+ const responseText = opts.json ? JSON.stringify(summary, null, 2) : formatRepoSummary(summary);
86
+ await streamAssistantSentences(responseText, appendMessage, {}, streamMessage);
87
+ return responseText;
88
+ } catch (err) {
89
+ if (typeof setThinking === 'function') setThinking(false);
90
+ appendMessage('error', formatErr(err));
91
+ return '';
92
+ }
93
+ }
94
+
95
+ async function sendSymbolIndexMessage({ rawArgs = '', appendMessage, streamMessage, setThinking }) {
96
+ const formatErr = (err) => err && err.message ? err.message : String(err || 'Unknown error');
97
+ try {
98
+ if (typeof setThinking === 'function') setThinking(false);
99
+ const opts = parseSymbolIndexArgs(rawArgs);
100
+ const index = buildSymbolIndex(opts.targetPath);
101
+ const responseText = opts.json
102
+ ? JSON.stringify(index, null, 2)
103
+ : formatSymbolIndex(index, { limit: opts.limit });
104
+ await streamAssistantSentences(responseText, appendMessage, {}, streamMessage);
105
+ return responseText;
106
+ } catch (err) {
107
+ if (typeof setThinking === 'function') setThinking(false);
108
+ appendMessage('error', formatErr(err));
109
+ return '';
110
+ }
111
+ }
112
+
113
+ async function sendSemanticCodeMessage({ rawArgs = '', appendMessage, streamMessage, setThinking, appendCodeStep }) {
114
+ const formatErr = (err) => err && err.message ? err.message : String(err || 'Unknown error');
115
+ const opts = parseSemanticCodeArgs(rawArgs);
116
+ try {
117
+ if (typeof setThinking === 'function') setThinking(true, 0);
118
+ let responseText;
119
+
120
+ if (opts.mode === 'index') {
121
+ const index = await indexSemanticCode(opts.targetPath, {
122
+ onProgress: (info) => {
123
+ if (typeof appendCodeStep === 'function' &&
124
+ (info.current === 1 || info.current === info.total || info.current % 25 === 0)) {
125
+ appendCodeStep({ action: 'semantic_code_index', target: `${info.current}/${info.total} ${info.file}` });
126
+ }
127
+ }
128
+ });
129
+ responseText = opts.json ? JSON.stringify(index, null, 2) : formatSemanticCodeIndex(index);
130
+ } else {
131
+ if (!opts.query) throw new Error('Usage: /semantic-code search <query>');
132
+ const results = await searchSemanticCode(opts.query, opts.targetPath, { topK: opts.topK });
133
+ responseText = opts.json ? JSON.stringify(results, null, 2) : formatSemanticCodeSearch(results);
134
+ }
135
+
136
+ if (typeof setThinking === 'function') setThinking(false);
137
+ await streamAssistantSentences(responseText, appendMessage, {}, streamMessage);
138
+ return responseText;
139
+ } catch (err) {
140
+ if (typeof setThinking === 'function') setThinking(false);
141
+ appendMessage('error', formatErr(err));
142
+ return '';
143
+ }
144
+ }
145
+
146
+ async function sendImageMessage({ images, image, prompt, appendMessage, streamMessage, setThinking, appendCodeStep }) {
147
+ const formatErr = (err) => err && err.message ? err.message : String(err || 'Unknown error');
148
+ const imageList = images || (image ? [image] : []);
149
+ const message = prompt || 'Analyze this image.';
150
+ const labels = imageList.map((_, i) => `[Image #${i + 1}]`).join(' ');
151
+ const displayMessage = labels && message.includes(labels) ? message : `${message}\n${labels}`;
152
+
153
+ appendMessage('user', displayMessage);
154
+ if (appendCodeStep) {
155
+ appendCodeStep({
156
+ thought: imageList.length > 1
157
+ ? `Analyzing ${imageList.length} attached images before answering.`
158
+ : 'Analyzing the attached image before answering.'
159
+ });
160
+ }
161
+
162
+ const cancelTimer = startThinkingTimer(setThinking);
163
+ try {
164
+ const result = await handleChat(message, imageList.map(item => item.dataUri), null);
165
+ cancelTimer();
166
+ setThinking(false);
167
+
168
+ const responseText = result.response || '';
169
+ await streamAssistantSentences(responseText, appendMessage, { providerInfo: result.providerInfo }, streamMessage);
170
+ return { responseText, labels, imageList };
171
+ } catch (err) {
172
+ cancelTimer();
173
+ setThinking(false);
174
+ appendMessage('error', formatErr(err));
175
+ return { responseText: '', labels, imageList };
176
+ }
177
+ }
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // Agent task execution (shared by onSubmit + initial message)
181
+ // ---------------------------------------------------------------------------
182
+
183
+ async function runAgentTask(text, { appendMessage, streamMessage, setThinking, requestApproval, askUser, setMode, appendCodeStep }, sharedState) {
184
+ const formatErr = (err) => err && err.message ? err.message : String(err || 'Unknown error');
185
+ const transcript = await getChatTranscript();
186
+ const contextualHistory = sharedState.recentImageContextText
187
+ ? [...transcript, { sender: 'system', text: sharedState.recentImageContextText, timestamp: new Date().toISOString() }]
188
+ : transcript;
189
+
190
+ if (setMode) setMode('Agent');
191
+ const cancelTimer = startThinkingTimer(setThinking);
192
+
193
+ try {
194
+ const config = require('../System/config_manager').readConfig();
195
+ const availableProviders = require('../System/config_manager').getAvailableProviders(config);
196
+ const preferredProvider = require('./code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
197
+ let streamedFinalSummary = false;
198
+
199
+ const result = await executeCodeTask(text, {
200
+ cwd: process.cwd(),
201
+ requestApproval,
202
+ askUser,
203
+ provider: preferredProvider,
204
+ history: contextualHistory,
205
+ onProgress: (info) => { if (appendCodeStep) appendCodeStep(info); },
206
+ onFinalSummary: async (info) => {
207
+ cancelTimer();
208
+ setThinking(false);
209
+ streamedFinalSummary = true;
210
+ await streamAssistantSentences(info.summary, appendMessage, { providerInfo: info.providerInfo }, streamMessage);
211
+ }
212
+ });
213
+
214
+ cancelTimer();
215
+ setThinking(false);
216
+ sharedState.lastResponseText = result.summary;
217
+ if (!streamedFinalSummary) {
218
+ await streamAssistantSentences(result.summary, appendMessage, { providerInfo: result.providerInfo }, streamMessage);
219
+ }
220
+ } catch (err) {
221
+ cancelTimer();
222
+ setThinking(false);
223
+ appendMessage('error', formatErr(err));
224
+ } finally {
225
+ if (setMode) setMode('Agent');
226
+ }
227
+ }
228
+
229
+ // ---------------------------------------------------------------------------
230
+ // Public: startInteractiveChat
231
+ // ---------------------------------------------------------------------------
232
+
233
+ /**
234
+ * Starts the interactive TUI chat session.
235
+ *
236
+ * @param {string|null} initialMessage Optional first message (from CLI arg).
237
+ * @param {{ imagePath?: string }} options
238
+ */
239
+ async function startInteractiveChat(initialMessage = null, options = {}) {
240
+ const formatErr = (err) => err && err.message ? err.message : String(err || 'Unknown error');
241
+
242
+ // Shared mutable state between onSubmit closures
243
+ const sharedState = {
244
+ lastResponseText: '',
245
+ recentImageContextText: ''
246
+ };
247
+
248
+ // -----------------------------------------------------------------------
249
+ const ui = await createChatUI({
250
+ onPasteImage: async () => {
251
+ try {
252
+ const image = loadClipboardImageAsDataUri();
253
+ return { label: image.path, image };
254
+ } catch (err) {
255
+ throw new Error(formatErr(err));
256
+ }
257
+ },
258
+
259
+ onSubmit: async (text, submitOptions = {}) => {
260
+ const {
261
+ appendMessage, streamMessage, setThinking, updateStatusModel,
262
+ copyLastResponse, requestApproval, setMode, appendCodeStep,
263
+ updateWorkspace, askUser, attachImage, setInputText,
264
+ setPendingPasteText, setFastMode, toggleFastMode, getFastMode
265
+ } = ui;
266
+
267
+ // ── Image submission ────────────────────────────────────────────
268
+ if (submitOptions.images && submitOptions.images.length > 0) {
269
+ const images = submitOptions.images.map(item => item.image || item);
270
+ const { responseText, labels, imageList } = await sendImageMessage({
271
+ images,
272
+ prompt: text.trim() || 'Analyze this image.',
273
+ appendMessage, streamMessage, setThinking, appendCodeStep
274
+ });
275
+ sharedState.lastResponseText = responseText;
276
+ if (responseText) {
277
+ sharedState.recentImageContextText = [
278
+ `Recent image context: the user attached ${imageList.length} image(s) labelled ${labels || '[Image #1]'}.`,
279
+ 'The terminal UI displays image attachments as labels only; it does not render thumbnails inside the chat.',
280
+ `Assistant response to those image(s): ${responseText}`
281
+ ].join('\n');
282
+ }
283
+ return;
284
+ }
285
+
286
+ // ── Slash commands ──────────────────────────────────────────────
287
+ if (text.startsWith('/')) {
288
+ if (text.startsWith('/agent')) {
289
+ const aArgs = text.split(' ');
290
+ if (aArgs[1] === 'list') {
291
+ appendMessage('system', `Available Agents: ${agentOrchestrator.listAgents().join(', ')}`);
292
+ } else if (aArgs[1]) {
293
+ const success = agentOrchestrator.setAgent(aArgs[1]);
294
+ if (success) {
295
+ const agent = agentOrchestrator.getCurrentAgent();
296
+ appendMessage('system', `Switched to Agent: ${agent.icon} ${agent.name}`);
297
+ updateStatusModel(agent.name);
298
+ resetChat();
299
+ } else {
300
+ appendMessage('error', `Agent "${aArgs[1]}" not found. Try /agent list`);
301
+ }
302
+ } else {
303
+ const agent = agentOrchestrator.getCurrentAgent();
304
+ appendMessage('system', `Current Agent: ${agent.icon} ${agent.name}\nUsage: /agent <type> or /agent list`);
305
+ }
306
+ return;
307
+ }
308
+
309
+ if (text.startsWith('/stats')) {
310
+ appendMessage('system', '📊 Fetching system statistics...');
311
+ const stats = await systemMonitor.execute('stats');
312
+ appendMessage('system', stats);
313
+ return;
314
+ }
315
+
316
+ if (text.startsWith('/workspace')) {
317
+ const wArgs = text.split(' ');
318
+ const subCmd = wArgs[1];
319
+ if (subCmd === 'add') {
320
+ const name = wArgs[2];
321
+ const wsPath = wArgs[3] || '.';
322
+ const instructions = wArgs.slice(4).join(' ');
323
+ if (!name) {
324
+ appendMessage('error', 'Usage: /workspace add <name> [path] [instructions]');
325
+ } else {
326
+ workspaceManager.addWorkspace(name, wsPath, instructions);
327
+ appendMessage('system', `Workspace "${name}" registered at ${require('path').resolve(wsPath)}`);
328
+ resetChat();
329
+ }
330
+ } else if (subCmd === 'list') {
331
+ const all = workspaceManager.listWorkspaces();
332
+ let listMsg = 'Registered Workspaces:\n';
333
+ for (const n in all) listMsg += `- ${n}: ${all[n].path}\n`;
334
+ appendMessage('system', Object.keys(all).length ? listMsg : 'No workspaces registered.');
335
+ } else if (subCmd === 'remove') {
336
+ const name = wArgs[2];
337
+ if (workspaceManager.removeWorkspace(name)) {
338
+ appendMessage('system', `Removed workspace "${name}"`);
339
+ resetChat();
340
+ } else {
341
+ appendMessage('error', `Workspace "${name}" not found.`);
342
+ }
343
+ } else if (subCmd === 'use' || subCmd === 'switch') {
344
+ const name = wArgs[2];
345
+ const all = workspaceManager.listWorkspaces();
346
+ if (all[name]) {
347
+ const newPath = all[name].path;
348
+ try {
349
+ process.chdir(newPath);
350
+ updateWorkspace(newPath);
351
+ appendMessage('system', `✓ Switched to workspace "${name}" at ${newPath}`);
352
+ resetChat();
353
+ } catch (e) {
354
+ appendMessage('error', `Failed to change directory: ${e.message}`);
355
+ }
356
+ } else {
357
+ appendMessage('error', `Workspace "${name}" not found. Try /workspace list`);
358
+ }
359
+ } else {
360
+ const ws = workspaceManager.getWorkspaceByPath(process.cwd());
361
+ appendMessage('system', ws
362
+ ? `Current Workspace: ${ws.name}\nPath: ${ws.path}`
363
+ : `Not currently in a registered workspace.\nActive Path: ${process.cwd()}\nUsage: /workspace <add|use|list|remove>`);
364
+ }
365
+ return;
366
+ }
367
+
368
+ if (text.startsWith('/review')) {
369
+ if (!sharedState.lastResponseText) {
370
+ appendMessage('error', 'Nothing to review yet. Get a response first.');
371
+ return;
372
+ }
373
+ agentOrchestrator.setAgent('reviewer');
374
+ appendMessage('system', '⚖️ Requesting second-pass review from Mint Reviewer...');
375
+ text = `Please review this previous response and provide a critique:\n\n${sharedState.lastResponseText}`;
376
+ } else {
377
+ if (!text.startsWith('/image') && !text.startsWith('/paste')) {
378
+ appendMessage('user', text);
379
+ }
380
+ const slashResult = await handleSlashCommandUI(
381
+ text, appendMessage, updateStatusModel, copyLastResponse,
382
+ setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace, {
383
+ sendImageMessage, formatErrorMessage: formatErr,
384
+ attachImage, setInputText, setPendingPasteText,
385
+ setFastMode, toggleFastMode, getFastMode,
386
+ sendRepoSummaryMessage, sendSymbolIndexMessage,
387
+ sendSemanticCodeMessage, streamAssistantSentences,
388
+ streamMessage
389
+ }
390
+ );
391
+ if (slashResult && slashResult.lastResponseText) {
392
+ sharedState.lastResponseText = slashResult.lastResponseText;
393
+ }
394
+ return;
395
+ }
396
+ }
397
+
398
+ appendMessage('user', text);
399
+
400
+ // ── Local tool shortcuts (natural language) ─────────────────────
401
+ if (isRepoSummaryRequest(text)) {
402
+ const r = await sendRepoSummaryMessage({ appendMessage, streamMessage, setThinking });
403
+ sharedState.lastResponseText = r;
404
+ return;
405
+ }
406
+ if (isSymbolIndexRequest(text)) {
407
+ const r = await sendSymbolIndexMessage({ appendMessage, streamMessage, setThinking });
408
+ sharedState.lastResponseText = r;
409
+ return;
410
+ }
411
+ if (isSemanticCodeSearchRequest(text)) {
412
+ const query = extractSemanticCodeQuery(text);
413
+ const r = await sendSemanticCodeMessage({
414
+ rawArgs: `search ${query}`,
415
+ appendMessage, streamMessage, setThinking, appendCodeStep
416
+ });
417
+ sharedState.lastResponseText = r;
418
+ return;
419
+ }
420
+
421
+ // ── Normal agent task ───────────────────────────────────────────
422
+ await runAgentTask(text, {
423
+ appendMessage, streamMessage, setThinking,
424
+ requestApproval, askUser, setMode, appendCodeStep
425
+ }, sharedState);
426
+ },
427
+
428
+ onExit: () => exitWithGoodbye(0)
429
+ });
430
+
431
+ // ── Handle initial CLI --image option ───────────────────────────────────
432
+ if (options.imagePath) {
433
+ const { appendMessage, streamMessage, setThinking, appendCodeStep } = ui;
434
+ const image = loadImageAsDataUri(options.imagePath);
435
+ const prompt = initialMessage || 'Analyze this image.';
436
+ const { responseText, labels, imageList } = await sendImageMessage({
437
+ images: [image], prompt, appendMessage, streamMessage, setThinking, appendCodeStep
438
+ });
439
+ sharedState.lastResponseText = responseText;
440
+ if (responseText) {
441
+ sharedState.recentImageContextText = [
442
+ `Recent image context: the user attached ${imageList.length} image(s) labelled ${labels || '[Image #1]'}.`,
443
+ 'The terminal UI displays image attachments as labels only; it does not render thumbnails inside the chat.',
444
+ `Assistant response to those image(s): ${responseText}`
445
+ ].join('\n');
446
+ }
447
+ return;
448
+ }
449
+
450
+ // ── Handle initial CLI message argument ─────────────────────────────────
451
+ if (initialMessage) {
452
+ const { appendMessage, streamMessage, setThinking, requestApproval, setMode, appendCodeStep, askUser } = ui;
453
+ appendMessage('user', initialMessage);
454
+
455
+ if (isRepoSummaryRequest(initialMessage)) {
456
+ sharedState.lastResponseText = await sendRepoSummaryMessage({ appendMessage, streamMessage, setThinking });
457
+ return;
458
+ }
459
+ if (isSymbolIndexRequest(initialMessage)) {
460
+ sharedState.lastResponseText = await sendSymbolIndexMessage({ appendMessage, streamMessage, setThinking });
461
+ return;
462
+ }
463
+ if (isSemanticCodeSearchRequest(initialMessage)) {
464
+ const query = extractSemanticCodeQuery(initialMessage);
465
+ sharedState.lastResponseText = await sendSemanticCodeMessage({
466
+ rawArgs: `search ${query}`,
467
+ appendMessage, streamMessage, setThinking, appendCodeStep
468
+ });
469
+ return;
470
+ }
471
+
472
+ await runAgentTask(initialMessage, {
473
+ appendMessage, streamMessage, setThinking,
474
+ requestApproval, askUser, setMode, appendCodeStep
475
+ }, sharedState);
476
+ }
477
+ }
478
+
479
+ module.exports = { startInteractiveChat };
@@ -22,6 +22,9 @@ function displayFeatures() {
22
22
  const commands = [
23
23
  { cmd: 'mint', desc: 'Start interactive chat session (Default)' },
24
24
  { cmd: 'mint code "<task>"', desc: 'Run workspace-aware coding agent in current directory' },
25
+ { cmd: 'mint summarize [path]', desc: 'Summarize repository structure, tooling, git state, and key files' },
26
+ { cmd: 'mint symbols [path]', desc: 'Build a source symbol index for supported languages' },
27
+ { cmd: 'mint semantic-code', desc: 'Index and search code semantically with embeddings' },
25
28
  { cmd: 'mint gmail auth', desc: 'Connect Gmail OAuth and save refresh token' },
26
29
  { cmd: 'mint mcp', desc: 'Manage Model Context Protocol (MCP) servers' },
27
30
  { cmd: 'mint task "<task>"', desc: 'Queue an autonomous task for the background agent' },
@@ -4,6 +4,35 @@ const { readConfig, writeConfig } = require('../System/config_manager');
4
4
  const { installDaemon } = require('../System/daemon_manager');
5
5
  const { runGmailAuth } = require('./gmail_auth');
6
6
 
7
+ const CUSTOM_MODEL_VALUE = '__custom_model__';
8
+ const ANTHROPIC_MODEL_CHOICES = [
9
+ 'claude-3-5-sonnet-latest',
10
+ 'claude-3-opus-latest',
11
+ 'claude-3-5-haiku-latest'
12
+ ];
13
+ const OPENAI_MODEL_CHOICES = [
14
+ 'gpt-4o',
15
+ 'gpt-4o-mini',
16
+ 'o1-preview',
17
+ 'o1-mini'
18
+ ];
19
+
20
+ function buildModelChoices(models, currentModel) {
21
+ const choices = models.map(model => ({ name: model, value: model }));
22
+ if (currentModel && !models.includes(currentModel)) {
23
+ choices.push({ name: `${currentModel} (current)`, value: currentModel });
24
+ }
25
+ choices.push({ name: 'Custom...', value: CUSTOM_MODEL_VALUE });
26
+ return choices;
27
+ }
28
+
29
+ function resolveCustomModelSelection(answers, modelKey, customKey, fallbackModel) {
30
+ if (answers[modelKey] !== CUSTOM_MODEL_VALUE) return;
31
+ const customModel = typeof answers[customKey] === 'string' ? answers[customKey].trim() : '';
32
+ answers[modelKey] = customModel || fallbackModel;
33
+ delete answers[customKey];
34
+ }
35
+
7
36
  /**
8
37
  * Onboarding Wizard for Mint CLI
9
38
  */
@@ -56,10 +85,10 @@ async function runOnboarding(options = {}) {
56
85
  { name: 'Gmail API', value: 'gmail', checked: config.pluginGmailEnabled },
57
86
  { name: 'Notion API', value: 'notion', checked: config.pluginNotionEnabled },
58
87
  new inquirer.Separator(),
59
- { name: 'Anthropic (Claude)', value: 'anthropic', checked: config.aiProvider === 'anthropic' || !!config.anthropicApiKey },
60
- { name: 'OpenAI (GPT-4o)', value: 'openai', checked: config.aiProvider === 'openai' || !!config.openaiApiKey },
61
- { name: 'Hugging Face', value: 'hf', checked: config.aiProvider === 'huggingface' || !!config.hfApiKey },
62
- { name: 'Local AI (LM Studio/Ollama)', value: 'local', checked: config.aiProvider === 'local_openai' || (!!config.localApiBaseUrl && config.localApiBaseUrl.length > 0) },
88
+ { name: 'Anthropic (Claude)', value: 'anthropic', checked: !!config.anthropicApiKey },
89
+ { name: 'OpenAI (GPT-4o)', value: 'openai', checked: !!config.openaiApiKey },
90
+ { name: 'Hugging Face', value: 'hf', checked: !!config.hfApiKey },
91
+ { name: 'Local AI (LM Studio/Ollama)', value: 'local', checked: !!(config.localApiBaseUrl && config.localApiBaseUrl.length > 0) },
63
92
  new inquirer.Separator(),
64
93
  { name: 'Google Search API', value: 'google_search', checked: !!config.googleSearchApiKey },
65
94
  { name: 'Brave Search API', value: 'brave_search', checked: !!config.braveSearchApiKey },
@@ -257,7 +286,23 @@ async function runOnboarding(options = {}) {
257
286
  type: 'input',
258
287
  name: 'anthropicApiKey',
259
288
  message: 'Enter Anthropic API Key:',
260
- default: config.anthropicApiKey
289
+ default: config.anthropicApiKey,
290
+ validate: (input) => input.trim().length > 0 ? true : 'Anthropic API Key is required when Anthropic is selected.'
291
+ });
292
+ dynamicQuestions.push({
293
+ type: 'list',
294
+ name: 'anthropicModel',
295
+ message: 'Select Anthropic model:',
296
+ choices: buildModelChoices(ANTHROPIC_MODEL_CHOICES, config.anthropicModel),
297
+ default: config.anthropicModel || 'claude-3-5-sonnet-latest'
298
+ });
299
+ dynamicQuestions.push({
300
+ type: 'input',
301
+ name: 'anthropicModelCustom',
302
+ message: 'Enter custom Anthropic model:',
303
+ default: config.anthropicModel || 'claude-3-5-sonnet-latest',
304
+ when: (answers) => answers.anthropicModel === CUSTOM_MODEL_VALUE,
305
+ validate: (input) => input.trim().length > 0 ? true : 'Model name is required.'
261
306
  });
262
307
  }
263
308
 
@@ -266,7 +311,23 @@ async function runOnboarding(options = {}) {
266
311
  type: 'input',
267
312
  name: 'openaiApiKey',
268
313
  message: 'Enter OpenAI API Key:',
269
- default: config.openaiApiKey
314
+ default: config.openaiApiKey,
315
+ validate: (input) => input.trim().length > 0 ? true : 'OpenAI API Key is required when OpenAI is selected.'
316
+ });
317
+ dynamicQuestions.push({
318
+ type: 'list',
319
+ name: 'openaiModel',
320
+ message: 'Select OpenAI model:',
321
+ choices: buildModelChoices(OPENAI_MODEL_CHOICES, config.openaiModel),
322
+ default: config.openaiModel || 'gpt-4o'
323
+ });
324
+ dynamicQuestions.push({
325
+ type: 'input',
326
+ name: 'openaiModelCustom',
327
+ message: 'Enter custom OpenAI model:',
328
+ default: config.openaiModel || 'gpt-4o',
329
+ when: (answers) => answers.openaiModel === CUSTOM_MODEL_VALUE,
330
+ validate: (input) => input.trim().length > 0 ? true : 'Model name is required.'
270
331
  });
271
332
  }
272
333
 
@@ -297,19 +358,15 @@ async function runOnboarding(options = {}) {
297
358
 
298
359
  if (dynamicQuestions.length > 0) {
299
360
  const extraAnswers = await inquirer.prompt(dynamicQuestions);
361
+ resolveCustomModelSelection(extraAnswers, 'anthropicModel', 'anthropicModelCustom', config.anthropicModel || 'claude-3-5-sonnet-latest');
362
+ resolveCustomModelSelection(extraAnswers, 'openaiModel', 'openaiModelCustom', config.openaiModel || 'gpt-4o');
300
363
  config = { ...config, ...extraAnswers };
301
364
 
302
365
  }
303
366
 
304
- // Ensure aiProvider reflects the selected primary AI. If no optional AI
305
- // provider is selected, keep Gemini as the safe default from basic setup.
306
- if (!selections.includes('skip')) {
307
- if (selections.includes('anthropic')) config.aiProvider = 'anthropic';
308
- else if (selections.includes('openai')) config.aiProvider = 'openai';
309
- else if (selections.includes('hf')) config.aiProvider = 'huggingface';
310
- else if (selections.includes('local')) config.aiProvider = 'local_openai';
311
- else config.aiProvider = 'gemini';
312
- }
367
+ // Onboarding treats Gemini as the primary AI. Other providers are optional
368
+ // configured backends that become available only when credentials are set.
369
+ config.aiProvider = 'gemini';
313
370
 
314
371
  // Save configuration
315
372
  writeConfig(config);