@pheem49/mint 1.5.1 → 1.5.3

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 (52) hide show
  1. package/GUIDE_TH.md +7 -7
  2. package/README.md +140 -66
  3. package/assets/Agent_Mint.png +0 -0
  4. package/assets/Settings.png +0 -0
  5. package/main.js +12 -0
  6. package/mint-cli.js +148 -921
  7. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +31 -1
  8. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +6 -1
  9. package/package.json +20 -21
  10. package/preload.js +2 -0
  11. package/scripts/install_linux_desktop_entry.js +48 -0
  12. package/src/AI_Brain/Gemini_API.js +194 -491
  13. package/src/AI_Brain/autonomous_brain.js +46 -19
  14. package/src/AI_Brain/headless_agent.js +21 -2
  15. package/src/AI_Brain/proactive_engine.js +12 -2
  16. package/src/AI_Brain/provider_adapter.js +358 -0
  17. package/src/Automation_Layer/browser_automation.js +26 -24
  18. package/src/CLI/approval_handler.js +47 -0
  19. package/src/CLI/chat_router.js +7 -0
  20. package/src/CLI/chat_ui.js +586 -80
  21. package/src/CLI/cli_colors.js +115 -0
  22. package/src/CLI/cli_formatters.js +94 -0
  23. package/src/CLI/code_agent.js +825 -283
  24. package/src/CLI/intent_detectors.js +181 -0
  25. package/src/CLI/interactive_chat.js +641 -0
  26. package/src/CLI/list_features.js +3 -0
  27. package/src/CLI/repo_summarizer.js +282 -0
  28. package/src/CLI/semantic_code_search.js +312 -0
  29. package/src/CLI/skill_manager.js +41 -0
  30. package/src/CLI/slash_command_handler.js +418 -0
  31. package/src/CLI/symbol_indexer.js +231 -0
  32. package/src/CLI/updater.js +21 -1
  33. package/src/Channels/discord_bridge.js +11 -13
  34. package/src/Channels/line_bridge.js +10 -10
  35. package/src/Channels/slack_bridge.js +7 -12
  36. package/src/Channels/telegram_bridge.js +6 -14
  37. package/src/Channels/whatsapp_bridge.js +11 -9
  38. package/src/System/chat_history_manager.js +20 -12
  39. package/src/System/config_manager.js +4 -1
  40. package/src/System/ipc_handlers.js +10 -0
  41. package/src/System/optional_require.js +23 -0
  42. package/src/System/picture_store.js +109 -0
  43. package/src/System/task_manager.js +127 -0
  44. package/src/System/tool_registry.js +13 -0
  45. package/src/System/window_manager.js +16 -8
  46. package/src/UI/live2d_manager.js +246 -14
  47. package/src/UI/renderer.js +620 -45
  48. package/src/UI/settings.css +738 -439
  49. package/src/UI/settings.html +487 -432
  50. package/src/UI/settings.js +44 -10
  51. package/src/UI/styles.css +1403 -106
  52. package/privacy.txt +0 -1
@@ -0,0 +1,641 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+ const { colors, exitWithGoodbye } = require('./cli_colors');
5
+ const { splitResponseSentences } = require('./cli_formatters');
6
+ const {
7
+ isRepoSummaryRequest,
8
+ parseRepoSummaryArgs,
9
+ isSymbolIndexRequest,
10
+ parseSymbolIndexArgs,
11
+ isSemanticCodeSearchRequest,
12
+ parseSemanticCodeArgs,
13
+ extractSemanticCodeQuery
14
+ } = require('./intent_detectors');
15
+ const { handleSlashCommandUI } = require('./slash_command_handler');
16
+ const { createChatUI } = require('./chat_ui');
17
+ const { loadImageAsDataUri, loadClipboardImageAsDataUri } = require('./image_input');
18
+ const { summarizeRepository, formatRepoSummary } = require('./repo_summarizer');
19
+ const { buildSymbolIndex, formatSymbolIndex } = require('./symbol_indexer');
20
+ const {
21
+ indexSemanticCode,
22
+ searchSemanticCode,
23
+ formatSemanticCodeIndex,
24
+ formatSemanticCodeSearch
25
+ } = require('./semantic_code_search');
26
+ const { handleChat, getChatTranscript } = require('../AI_Brain/Gemini_API');
27
+ const agentOrchestrator = require('../AI_Brain/agent_orchestrator');
28
+ const systemMonitor = require('../Plugins/system_monitor');
29
+ const workspaceManager = require('./workspace_manager');
30
+ const { executeCodeTask } = require('./code_agent');
31
+ const { resetChat } = require('../AI_Brain/Gemini_API');
32
+ const { saveChatImages } = require('../System/picture_store');
33
+
34
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
35
+
36
+ function createSessionStats() {
37
+ return {
38
+ sessionId: crypto.randomUUID(),
39
+ startedAt: Date.now(),
40
+ activeStartedAt: null,
41
+ agentActiveMs: 0,
42
+ toolCalls: { total: 0, success: 0, failed: 0 },
43
+ modelUsage: {}
44
+ };
45
+ }
46
+
47
+ function addUsageRow(stats, row = {}) {
48
+ const provider = row.provider || 'unknown';
49
+ const model = row.model || 'unknown';
50
+ const key = `${provider}:${model}`;
51
+ if (!stats.modelUsage[key]) {
52
+ stats.modelUsage[key] = {
53
+ provider,
54
+ model,
55
+ requests: 0,
56
+ inputTokens: 0,
57
+ cacheReads: 0,
58
+ outputTokens: 0,
59
+ reasoningTokens: 0,
60
+ totalTokens: 0
61
+ };
62
+ }
63
+
64
+ const target = stats.modelUsage[key];
65
+ target.requests += Number(row.requests) || 1;
66
+ target.inputTokens += Number(row.inputTokens) || 0;
67
+ target.cacheReads += Number(row.cacheReads) || 0;
68
+ target.outputTokens += Number(row.outputTokens) || 0;
69
+ target.reasoningTokens += Number(row.reasoningTokens) || 0;
70
+ target.totalTokens += Number(row.totalTokens) || 0;
71
+ }
72
+
73
+ function normalizeProviderUsage(providerInfo = {}) {
74
+ const usage = providerInfo.usage;
75
+ if (Array.isArray(usage)) return usage;
76
+ if (!usage || typeof usage !== 'object') {
77
+ return [{
78
+ provider: providerInfo.provider,
79
+ model: providerInfo.model,
80
+ requests: 1
81
+ }];
82
+ }
83
+
84
+ return [{
85
+ provider: providerInfo.provider,
86
+ model: providerInfo.model,
87
+ requests: 1,
88
+ inputTokens: usage.promptTokenCount || usage.prompt_tokens || usage.input_tokens,
89
+ cacheReads: usage.cachedContentTokenCount ||
90
+ (usage.prompt_tokens_details && usage.prompt_tokens_details.cached_tokens) ||
91
+ usage.cache_read_input_tokens,
92
+ outputTokens: usage.candidatesTokenCount || usage.completion_tokens || usage.output_tokens,
93
+ reasoningTokens: usage.thoughtsTokenCount ||
94
+ (usage.completion_tokens_details && usage.completion_tokens_details.reasoning_tokens),
95
+ totalTokens: usage.totalTokenCount || usage.total_tokens
96
+ }];
97
+ }
98
+
99
+ function recordProviderInfo(stats, providerInfo) {
100
+ if (!providerInfo) return;
101
+ for (const row of normalizeProviderUsage(providerInfo)) {
102
+ addUsageRow(stats, row);
103
+ }
104
+ }
105
+
106
+ function markAgentActive(stats, active) {
107
+ const now = Date.now();
108
+ if (active && !stats.activeStartedAt) {
109
+ stats.activeStartedAt = now;
110
+ return;
111
+ }
112
+ if (!active && stats.activeStartedAt) {
113
+ stats.agentActiveMs += now - stats.activeStartedAt;
114
+ stats.activeStartedAt = null;
115
+ }
116
+ }
117
+
118
+ function buildExitSummary(stats) {
119
+ const activeMs = stats.agentActiveMs + (stats.activeStartedAt ? Date.now() - stats.activeStartedAt : 0);
120
+ const total = stats.toolCalls.total;
121
+ return {
122
+ message: 'Agent powering down. Goodbye!',
123
+ sessionId: stats.sessionId,
124
+ toolCalls: {
125
+ ...stats.toolCalls,
126
+ successRate: total ? (stats.toolCalls.success / total) * 100 : 0
127
+ },
128
+ wallMs: Date.now() - stats.startedAt,
129
+ agentActiveMs: activeMs,
130
+ modelUsage: Object.values(stats.modelUsage),
131
+ quotaHint: 'Use /models to view model quota information'
132
+ };
133
+ }
134
+
135
+ // ---------------------------------------------------------------------------
136
+ // Internal helpers
137
+ // ---------------------------------------------------------------------------
138
+
139
+ /**
140
+ * Streams response text sentence-by-sentence into the TUI.
141
+ */
142
+ async function streamAssistantSentences(text, appendMessage, metadata = {}, streamMessage = null) {
143
+ const sentences = splitResponseSentences(text);
144
+ const chunks = sentences.filter(s => String(s || '').trim());
145
+
146
+ if (typeof streamMessage === 'function') {
147
+ const stream = streamMessage(metadata);
148
+ for (let i = 0; i < chunks.length; i++) {
149
+ stream.appendChunk(chunks[i]);
150
+ if (i < chunks.length - 1) await sleep(90);
151
+ }
152
+ stream.finalize();
153
+ return;
154
+ }
155
+
156
+ for (let i = 0; i < chunks.length; i++) {
157
+ appendMessage('assistant', chunks[i], i === 0 ? metadata : {});
158
+ if (i < chunks.length - 1) await sleep(90);
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Runs a timer that increments seconds and calls setThinking every 1s.
164
+ * Returns a cancel function.
165
+ */
166
+ function startThinkingTimer(setThinking) {
167
+ let seconds = 0;
168
+ setThinking(true, seconds);
169
+ const timer = setInterval(() => {
170
+ seconds++;
171
+ setThinking(true, seconds);
172
+ }, 1000);
173
+ return () => clearInterval(timer);
174
+ }
175
+
176
+ // ---------------------------------------------------------------------------
177
+ // Local tool message senders
178
+ // ---------------------------------------------------------------------------
179
+
180
+ async function sendRepoSummaryMessage({ rawArgs = '', appendMessage, streamMessage, setThinking }) {
181
+ const formatErr = (err) => err && err.message ? err.message : String(err || 'Unknown error');
182
+ try {
183
+ if (typeof setThinking === 'function') setThinking(false);
184
+ const opts = parseRepoSummaryArgs(rawArgs);
185
+ const summary = summarizeRepository(opts.targetPath);
186
+ const responseText = opts.json ? JSON.stringify(summary, null, 2) : formatRepoSummary(summary);
187
+ await streamAssistantSentences(responseText, appendMessage, {}, streamMessage);
188
+ return responseText;
189
+ } catch (err) {
190
+ if (typeof setThinking === 'function') setThinking(false);
191
+ appendMessage('error', formatErr(err));
192
+ return '';
193
+ }
194
+ }
195
+
196
+ async function sendSymbolIndexMessage({ rawArgs = '', appendMessage, streamMessage, setThinking }) {
197
+ const formatErr = (err) => err && err.message ? err.message : String(err || 'Unknown error');
198
+ try {
199
+ if (typeof setThinking === 'function') setThinking(false);
200
+ const opts = parseSymbolIndexArgs(rawArgs);
201
+ const index = buildSymbolIndex(opts.targetPath);
202
+ const responseText = opts.json
203
+ ? JSON.stringify(index, null, 2)
204
+ : formatSymbolIndex(index, { limit: opts.limit });
205
+ await streamAssistantSentences(responseText, appendMessage, {}, streamMessage);
206
+ return responseText;
207
+ } catch (err) {
208
+ if (typeof setThinking === 'function') setThinking(false);
209
+ appendMessage('error', formatErr(err));
210
+ return '';
211
+ }
212
+ }
213
+
214
+ async function sendSemanticCodeMessage({ rawArgs = '', appendMessage, streamMessage, setThinking, appendCodeStep }) {
215
+ const formatErr = (err) => err && err.message ? err.message : String(err || 'Unknown error');
216
+ const opts = parseSemanticCodeArgs(rawArgs);
217
+ try {
218
+ if (typeof setThinking === 'function') setThinking(true, 0);
219
+ let responseText;
220
+
221
+ if (opts.mode === 'index') {
222
+ const index = await indexSemanticCode(opts.targetPath, {
223
+ onProgress: (info) => {
224
+ if (typeof appendCodeStep === 'function' &&
225
+ (info.current === 1 || info.current === info.total || info.current % 25 === 0)) {
226
+ appendCodeStep({ action: 'semantic_code_index', target: `${info.current}/${info.total} ${info.file}` });
227
+ }
228
+ }
229
+ });
230
+ responseText = opts.json ? JSON.stringify(index, null, 2) : formatSemanticCodeIndex(index);
231
+ } else {
232
+ if (!opts.query) throw new Error('Usage: /semantic-code search <query>');
233
+ const results = await searchSemanticCode(opts.query, opts.targetPath, { topK: opts.topK });
234
+ responseText = opts.json ? JSON.stringify(results, null, 2) : formatSemanticCodeSearch(results);
235
+ }
236
+
237
+ if (typeof setThinking === 'function') setThinking(false);
238
+ await streamAssistantSentences(responseText, appendMessage, {}, streamMessage);
239
+ return responseText;
240
+ } catch (err) {
241
+ if (typeof setThinking === 'function') setThinking(false);
242
+ appendMessage('error', formatErr(err));
243
+ return '';
244
+ }
245
+ }
246
+
247
+ function hasAllImageLabels(message = '', imageCount = 0) {
248
+ const text = String(message || '');
249
+ for (let index = 1; index <= imageCount; index++) {
250
+ if (!text.includes(`[Image #${index}]`)) return false;
251
+ }
252
+ return imageCount > 0;
253
+ }
254
+
255
+ function formatImageDisplayMessage(message = '', labels = '', imageCount = 0) {
256
+ if (!labels) return message;
257
+ return hasAllImageLabels(message, imageCount) ? message : `${message}\n${labels}`;
258
+ }
259
+
260
+ async function sendImageMessage({ images, image, prompt, appendMessage, streamMessage, setThinking, appendCodeStep, stats }) {
261
+ const formatErr = (err) => err && err.message ? err.message : String(err || 'Unknown error');
262
+ const imageList = images || (image ? [image] : []);
263
+ const message = prompt || 'Analyze this image.';
264
+ const labels = imageList.map((_, i) => `[Image #${i + 1}]`).join(' ');
265
+ const displayMessage = formatImageDisplayMessage(message, labels, imageList.length);
266
+
267
+ appendMessage('user', displayMessage);
268
+ if (appendCodeStep) {
269
+ appendCodeStep({
270
+ thought: imageList.length > 1
271
+ ? `Analyzing ${imageList.length} attached images before answering.`
272
+ : 'Analyzing the attached image before answering.'
273
+ });
274
+ }
275
+
276
+ const cancelTimer = startThinkingTimer(setThinking);
277
+ if (stats) markAgentActive(stats, true);
278
+ try {
279
+ const imageDataUris = imageList.map(item => item.dataUri);
280
+ const result = await handleChat(message, imageDataUris, null);
281
+ try {
282
+ saveChatImages(imageDataUris, { source: 'cli', message });
283
+ } catch (saveError) {
284
+ console.error('[Pictures] Failed to save CLI image:', saveError.message);
285
+ }
286
+ cancelTimer();
287
+ setThinking(false);
288
+ if (stats) markAgentActive(stats, false);
289
+
290
+ const responseText = result.response || '';
291
+ if (stats) recordProviderInfo(stats, result.providerInfo);
292
+ await streamAssistantSentences(responseText, appendMessage, { providerInfo: result.providerInfo }, streamMessage);
293
+ return { responseText, labels, imageList };
294
+ } catch (err) {
295
+ cancelTimer();
296
+ setThinking(false);
297
+ if (stats) markAgentActive(stats, false);
298
+ appendMessage('error', formatErr(err));
299
+ return { responseText: '', labels, imageList };
300
+ }
301
+ }
302
+
303
+ // ---------------------------------------------------------------------------
304
+ // Agent task execution (shared by onSubmit + initial message)
305
+ // ---------------------------------------------------------------------------
306
+
307
+ async function runAgentTask(text, { appendMessage, streamMessage, setThinking, requestApproval, askUser, setMode, appendCodeStep }, sharedState) {
308
+ const formatErr = (err) => err && err.message ? err.message : String(err || 'Unknown error');
309
+ const transcript = await getChatTranscript();
310
+ const contextualHistory = sharedState.recentImageContextText
311
+ ? [...transcript, { sender: 'system', text: sharedState.recentImageContextText, timestamp: new Date().toISOString() }]
312
+ : transcript;
313
+
314
+ if (setMode) setMode('Agent');
315
+ const cancelTimer = startThinkingTimer(setThinking);
316
+ markAgentActive(sharedState.stats, true);
317
+
318
+ try {
319
+ const config = require('../System/config_manager').readConfig();
320
+ const availableProviders = require('../System/config_manager').getAvailableProviders(config);
321
+ const preferredProvider = require('./code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
322
+ let streamedFinalSummary = false;
323
+
324
+ const result = await executeCodeTask(text, {
325
+ cwd: process.cwd(),
326
+ requestApproval,
327
+ askUser,
328
+ provider: preferredProvider,
329
+ history: contextualHistory,
330
+ onProgress: (info) => {
331
+ if (info && info.phase === 'tool_call') {
332
+ sharedState.stats.toolCalls.total += 1;
333
+ if (info.status === 'success') sharedState.stats.toolCalls.success += 1;
334
+ else sharedState.stats.toolCalls.failed += 1;
335
+ }
336
+ if (appendCodeStep) appendCodeStep(info);
337
+ },
338
+ onFinalSummary: async (info) => {
339
+ cancelTimer();
340
+ setThinking(false);
341
+ markAgentActive(sharedState.stats, false);
342
+ recordProviderInfo(sharedState.stats, info.providerInfo);
343
+ streamedFinalSummary = true;
344
+ await streamAssistantSentences(info.summary, appendMessage, { providerInfo: info.providerInfo }, streamMessage);
345
+ }
346
+ });
347
+
348
+ cancelTimer();
349
+ setThinking(false);
350
+ markAgentActive(sharedState.stats, false);
351
+ sharedState.lastResponseText = result.summary;
352
+ if (!streamedFinalSummary) {
353
+ recordProviderInfo(sharedState.stats, result.providerInfo);
354
+ await streamAssistantSentences(result.summary, appendMessage, { providerInfo: result.providerInfo }, streamMessage);
355
+ }
356
+ } catch (err) {
357
+ cancelTimer();
358
+ setThinking(false);
359
+ markAgentActive(sharedState.stats, false);
360
+ appendMessage('error', formatErr(err));
361
+ } finally {
362
+ if (setMode) setMode('Agent');
363
+ }
364
+ }
365
+
366
+ // ---------------------------------------------------------------------------
367
+ // Public: startInteractiveChat
368
+ // ---------------------------------------------------------------------------
369
+
370
+ /**
371
+ * Starts the interactive TUI chat session.
372
+ *
373
+ * @param {string|null} initialMessage Optional first message (from CLI arg).
374
+ * @param {{ imagePath?: string }} options
375
+ */
376
+ async function startInteractiveChat(initialMessage = null, options = {}) {
377
+ const formatErr = (err) => err && err.message ? err.message : String(err || 'Unknown error');
378
+
379
+ // Shared mutable state between onSubmit closures
380
+ const sharedState = {
381
+ lastResponseText: '',
382
+ recentImageContextText: '',
383
+ isBusy: false,
384
+ stats: createSessionStats()
385
+ };
386
+
387
+ // -----------------------------------------------------------------------
388
+ let ui;
389
+ ui = await createChatUI({
390
+ onPasteImage: async () => {
391
+ try {
392
+ const image = loadClipboardImageAsDataUri();
393
+ return { label: image.path, image };
394
+ } catch (err) {
395
+ throw new Error(formatErr(err));
396
+ }
397
+ },
398
+
399
+ onSubmit: async (text, submitOptions = {}) => {
400
+ if (sharedState.isBusy) {
401
+ ui.appendMessage('system', 'Mint is still working on the previous request. Please wait for it to finish before sending another command.');
402
+ return;
403
+ }
404
+ sharedState.isBusy = true;
405
+
406
+ const {
407
+ appendMessage, streamMessage, setThinking, updateStatusModel,
408
+ copyLastResponse, requestApproval, setMode, appendCodeStep,
409
+ updateWorkspace, askUser, attachImage, setInputText,
410
+ setPendingPasteText, setFastMode, toggleFastMode, getFastMode
411
+ } = ui;
412
+
413
+ try {
414
+ // ── Image submission ────────────────────────────────────────
415
+ if (submitOptions.images && submitOptions.images.length > 0) {
416
+ const images = submitOptions.images.map(item => item.image || item);
417
+ const { responseText, labels, imageList } = await sendImageMessage({
418
+ images,
419
+ prompt: text.trim() || 'Analyze this image.',
420
+ appendMessage, streamMessage, setThinking, appendCodeStep,
421
+ stats: sharedState.stats
422
+ });
423
+ sharedState.lastResponseText = responseText;
424
+ if (responseText) {
425
+ sharedState.recentImageContextText = [
426
+ `Recent image context: the user attached ${imageList.length} image(s) labelled ${labels || '[Image #1]'}.`,
427
+ 'The terminal UI displays image attachments as labels only; it does not render thumbnails inside the chat.',
428
+ `Assistant response to those image(s): ${responseText}`
429
+ ].join('\n');
430
+ }
431
+ return;
432
+ }
433
+
434
+ // ── Slash commands ──────────────────────────────────────────
435
+ if (text.startsWith('/')) {
436
+ if (text.startsWith('/agent')) {
437
+ const aArgs = text.split(' ');
438
+ if (aArgs[1] === 'list') {
439
+ appendMessage('system', `Available Agents: ${agentOrchestrator.listAgents().join(', ')}`);
440
+ } else if (aArgs[1]) {
441
+ const success = agentOrchestrator.setAgent(aArgs[1]);
442
+ if (success) {
443
+ const agent = agentOrchestrator.getCurrentAgent();
444
+ appendMessage('system', `Switched to Agent: ${agent.icon} ${agent.name}`);
445
+ updateStatusModel(agent.name);
446
+ resetChat();
447
+ } else {
448
+ appendMessage('error', `Agent "${aArgs[1]}" not found. Try /agent list`);
449
+ }
450
+ } else {
451
+ const agent = agentOrchestrator.getCurrentAgent();
452
+ appendMessage('system', `Current Agent: ${agent.icon} ${agent.name}\nUsage: /agent <type> or /agent list`);
453
+ }
454
+ return;
455
+ }
456
+
457
+ if (text.startsWith('/stats')) {
458
+ appendMessage('system', '📊 Fetching system statistics...');
459
+ const stats = await systemMonitor.execute('stats');
460
+ appendMessage('system', stats);
461
+ return;
462
+ }
463
+
464
+ if (text.startsWith('/workspace')) {
465
+ const wArgs = text.split(' ');
466
+ const subCmd = wArgs[1];
467
+ if (subCmd === 'add') {
468
+ const name = wArgs[2];
469
+ const wsPath = wArgs[3] || '.';
470
+ const instructions = wArgs.slice(4).join(' ');
471
+ if (!name) {
472
+ appendMessage('error', 'Usage: /workspace add <name> [path] [instructions]');
473
+ } else {
474
+ workspaceManager.addWorkspace(name, wsPath, instructions);
475
+ appendMessage('system', `Workspace "${name}" registered at ${require('path').resolve(wsPath)}`);
476
+ resetChat();
477
+ }
478
+ } else if (subCmd === 'list') {
479
+ const all = workspaceManager.listWorkspaces();
480
+ let listMsg = 'Registered Workspaces:\n';
481
+ for (const n in all) listMsg += `- ${n}: ${all[n].path}\n`;
482
+ appendMessage('system', Object.keys(all).length ? listMsg : 'No workspaces registered.');
483
+ } else if (subCmd === 'remove') {
484
+ const name = wArgs[2];
485
+ if (workspaceManager.removeWorkspace(name)) {
486
+ appendMessage('system', `Removed workspace "${name}"`);
487
+ resetChat();
488
+ } else {
489
+ appendMessage('error', `Workspace "${name}" not found.`);
490
+ }
491
+ } else if (subCmd === 'use' || subCmd === 'switch') {
492
+ const name = wArgs[2];
493
+ const all = workspaceManager.listWorkspaces();
494
+ if (all[name]) {
495
+ const newPath = all[name].path;
496
+ try {
497
+ process.chdir(newPath);
498
+ updateWorkspace(newPath);
499
+ appendMessage('system', `✓ Switched to workspace "${name}" at ${newPath}`);
500
+ resetChat();
501
+ } catch (e) {
502
+ appendMessage('error', `Failed to change directory: ${e.message}`);
503
+ }
504
+ } else {
505
+ appendMessage('error', `Workspace "${name}" not found. Try /workspace list`);
506
+ }
507
+ } else {
508
+ const ws = workspaceManager.getWorkspaceByPath(process.cwd());
509
+ appendMessage('system', ws
510
+ ? `Current Workspace: ${ws.name}\nPath: ${ws.path}`
511
+ : `Not currently in a registered workspace.\nActive Path: ${process.cwd()}\nUsage: /workspace <add|use|list|remove>`);
512
+ }
513
+ return;
514
+ }
515
+
516
+ if (text.startsWith('/review')) {
517
+ if (!sharedState.lastResponseText) {
518
+ appendMessage('error', 'Nothing to review yet. Get a response first.');
519
+ return;
520
+ }
521
+ agentOrchestrator.setAgent('reviewer');
522
+ appendMessage('system', '⚖️ Requesting second-pass review from Mint Reviewer...');
523
+ text = `Please review this previous response and provide a critique:\n\n${sharedState.lastResponseText}`;
524
+ } else {
525
+ if (!text.startsWith('/image') && !text.startsWith('/paste')) {
526
+ appendMessage('user', text);
527
+ }
528
+ const slashResult = await handleSlashCommandUI(
529
+ text, appendMessage, updateStatusModel, copyLastResponse,
530
+ setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace, {
531
+ sendImageMessage: (args) => sendImageMessage({ ...args, stats: sharedState.stats }),
532
+ formatErrorMessage: formatErr,
533
+ attachImage, setInputText, setPendingPasteText,
534
+ setFastMode, toggleFastMode, getFastMode,
535
+ sendRepoSummaryMessage, sendSymbolIndexMessage,
536
+ sendSemanticCodeMessage, streamAssistantSentences,
537
+ streamMessage
538
+ }
539
+ );
540
+ if (slashResult && slashResult.lastResponseText) {
541
+ sharedState.lastResponseText = slashResult.lastResponseText;
542
+ }
543
+ return;
544
+ }
545
+ }
546
+
547
+ appendMessage('user', text);
548
+
549
+ // ── Local tool shortcuts (natural language) ─────────────────
550
+ if (isRepoSummaryRequest(text)) {
551
+ const r = await sendRepoSummaryMessage({ appendMessage, streamMessage, setThinking });
552
+ sharedState.lastResponseText = r;
553
+ return;
554
+ }
555
+ if (isSymbolIndexRequest(text)) {
556
+ const r = await sendSymbolIndexMessage({ appendMessage, streamMessage, setThinking });
557
+ sharedState.lastResponseText = r;
558
+ return;
559
+ }
560
+ if (isSemanticCodeSearchRequest(text)) {
561
+ const query = extractSemanticCodeQuery(text);
562
+ const r = await sendSemanticCodeMessage({
563
+ rawArgs: `search ${query}`,
564
+ appendMessage, streamMessage, setThinking, appendCodeStep
565
+ });
566
+ sharedState.lastResponseText = r;
567
+ return;
568
+ }
569
+
570
+ // ── Default to guarded Code Agent ───────────────────────────
571
+ await runAgentTask(text, {
572
+ appendMessage, streamMessage, setThinking,
573
+ requestApproval, askUser, setMode, appendCodeStep
574
+ }, sharedState);
575
+ } finally {
576
+ sharedState.isBusy = false;
577
+ }
578
+ },
579
+
580
+ onExit: () => {
581
+ if (ui && typeof ui.unmount === 'function') ui.unmount();
582
+ exitWithGoodbye(0, buildExitSummary(sharedState.stats));
583
+ }
584
+ });
585
+
586
+ // ── Handle initial CLI --image option ───────────────────────────────────
587
+ if (options.imagePath) {
588
+ const { appendMessage, streamMessage, setThinking, appendCodeStep } = ui;
589
+ const image = loadImageAsDataUri(options.imagePath);
590
+ const prompt = initialMessage || 'Analyze this image.';
591
+ const { responseText, labels, imageList } = await sendImageMessage({
592
+ images: [image], prompt, appendMessage, streamMessage, setThinking, appendCodeStep,
593
+ stats: sharedState.stats
594
+ });
595
+ sharedState.lastResponseText = responseText;
596
+ if (responseText) {
597
+ sharedState.recentImageContextText = [
598
+ `Recent image context: the user attached ${imageList.length} image(s) labelled ${labels || '[Image #1]'}.`,
599
+ 'The terminal UI displays image attachments as labels only; it does not render thumbnails inside the chat.',
600
+ `Assistant response to those image(s): ${responseText}`
601
+ ].join('\n');
602
+ }
603
+ return;
604
+ }
605
+
606
+ // ── Handle initial CLI message argument ─────────────────────────────────
607
+ if (initialMessage) {
608
+ const { appendMessage, streamMessage, setThinking, requestApproval, setMode, appendCodeStep, askUser } = ui;
609
+ appendMessage('user', initialMessage);
610
+
611
+ if (isRepoSummaryRequest(initialMessage)) {
612
+ sharedState.lastResponseText = await sendRepoSummaryMessage({ appendMessage, streamMessage, setThinking });
613
+ return;
614
+ }
615
+ if (isSymbolIndexRequest(initialMessage)) {
616
+ sharedState.lastResponseText = await sendSymbolIndexMessage({ appendMessage, streamMessage, setThinking });
617
+ return;
618
+ }
619
+ if (isSemanticCodeSearchRequest(initialMessage)) {
620
+ const query = extractSemanticCodeQuery(initialMessage);
621
+ sharedState.lastResponseText = await sendSemanticCodeMessage({
622
+ rawArgs: `search ${query}`,
623
+ appendMessage, streamMessage, setThinking, appendCodeStep
624
+ });
625
+ return;
626
+ }
627
+
628
+ await runAgentTask(initialMessage, {
629
+ appendMessage, streamMessage, setThinking,
630
+ requestApproval, askUser, setMode, appendCodeStep
631
+ }, sharedState);
632
+ }
633
+ }
634
+
635
+ module.exports = {
636
+ startInteractiveChat,
637
+ _helpers: {
638
+ hasAllImageLabels,
639
+ formatImageDisplayMessage
640
+ }
641
+ };
@@ -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' },