@pheem49/mint 1.5.0 → 1.5.1

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 (78) hide show
  1. package/README.md +27 -1
  2. package/main.js +28 -14
  3. package/mint-cli-logic.js +3 -119
  4. package/mint-cli.js +497 -23
  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 +10 -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 +10 -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 +26 -1
  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/CLI/chat_router.js +18 -6
  30. package/src/CLI/chat_ui.js +396 -50
  31. package/src/CLI/code_agent.js +203 -14
  32. package/src/CLI/image_input.js +90 -0
  33. package/src/CLI/onboarding.js +72 -15
  34. package/src/CLI/updater.js +6 -4
  35. package/src/System/action_executor.js +59 -10
  36. package/src/System/config_manager.js +31 -1
  37. package/src/System/granular_automation.js +122 -53
  38. package/src/System/proactive_loop.js +19 -3
  39. package/src/System/safety_manager.js +108 -0
  40. package/src/System/sandbox_runner.js +182 -0
  41. package/src/System/system_automation.js +127 -81
  42. package/src/System/system_info.js +70 -0
  43. package/src/System/tool_registry.js +280 -0
  44. package/src/System/window_manager.js +4 -2
  45. package/src/UI/live2d_manager.js +368 -0
  46. package/src/UI/renderer.js +176 -18
  47. package/src/UI/styles.css +452 -31
  48. package/.codex +0 -0
  49. package/docs/assets/Agent_Mint.png +0 -0
  50. package/docs/assets/CLI_Screen.png +0 -0
  51. package/docs/assets/Settings.png +0 -0
  52. package/docs/assets/icon.png +0 -0
  53. package/docs/guide.html +0 -632
  54. package/docs/index.html +0 -133
  55. package/docs/style.css +0 -579
  56. package/index.html +0 -16
  57. package/src/UI/index.html +0 -126
  58. package/tech_news.txt +0 -3
  59. package/test_knowledge.txt +0 -3
  60. package/tests/action_executor_safety.test.js +0 -67
  61. package/tests/agent_orchestrator.test.js +0 -41
  62. package/tests/chat_router.test.js +0 -42
  63. package/tests/code_agent.test.js +0 -69
  64. package/tests/config_manager.test.js +0 -141
  65. package/tests/docker.test.js +0 -46
  66. package/tests/file_operations.test.js +0 -57
  67. package/tests/gmail.test.js +0 -135
  68. package/tests/gmail_auth.test.js +0 -129
  69. package/tests/google_calendar.test.js +0 -113
  70. package/tests/google_tts_urls.test.js +0 -24
  71. package/tests/memory_store.test.js +0 -185
  72. package/tests/notion.test.js +0 -121
  73. package/tests/provider_routing.test.js +0 -83
  74. package/tests/safety_manager.test.js +0 -40
  75. package/tests/spotify.test.js +0 -201
  76. package/tests/system_monitor.test.js +0 -37
  77. package/tests/updater.test.js +0 -32
  78. package/tests/workspace_manager.test.js +0 -56
package/mint-cli.js CHANGED
@@ -9,6 +9,8 @@ process.emit = function (name, data, ...args) {
9
9
  return originalEmit.apply(process, [name, data, ...args]);
10
10
  };
11
11
  const { Command } = require('commander');
12
+ const fs = require('fs');
13
+ const path = require('path');
12
14
  const { handleChat, handleGeminiChatStream, resetChat, refreshApiKeyFromConfig, getChatTranscript } = require('./src/AI_Brain/Gemini_API');
13
15
  const agentOrchestrator = require('./src/AI_Brain/agent_orchestrator');
14
16
  const workspaceManager = require('./src/CLI/workspace_manager');
@@ -21,10 +23,12 @@ const { displayFeatures } = require('./src/CLI/list_features');
21
23
  const { readConfig, writeConfig } = require('./src/System/config_manager');
22
24
  const { executeCodeTask } = require('./src/CLI/code_agent');
23
25
  const { detectCodeIntent, runChatRoutedTask } = require('./src/CLI/chat_router');
26
+ const memoryStore = require('./src/AI_Brain/memory_store');
24
27
  const readline = require('readline');
25
28
  const { createChatUI } = require('./src/CLI/chat_ui');
26
29
  const { runUpdate, runStartupAutoUpdate, shouldRunAutoUpdate } = require('./src/CLI/updater');
27
30
  const { runGmailAuth } = require('./src/CLI/gmail_auth');
31
+ const { loadImageAsDataUri, loadClipboardImageAsDataUri } = require('./src/CLI/image_input');
28
32
 
29
33
  // Startup Info
30
34
  const startupConfig = readConfig();
@@ -56,6 +60,22 @@ const colors = {
56
60
  yellow: "\x1b[33m"
57
61
  };
58
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
+
59
79
  function formatProgress(info) {
60
80
  if (typeof info === 'string') return `${colors.gray}[Mint Code] ${info}${colors.reset}`;
61
81
 
@@ -88,6 +108,68 @@ function formatProgress(info) {
88
108
  return ` ${icon} ${colors.bright}${label}${colors.reset} ${color}${content}${colors.reset}`;
89
109
  }
90
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
+
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
+ }
168
+
169
+ const content = fs.readFileSync(targetPath, 'utf8');
170
+ return memoryStore.addLearnedSkill(path.basename(targetPath), targetPath, content);
171
+ }
172
+
91
173
  const program = new Command();
92
174
 
93
175
  program
@@ -123,8 +205,9 @@ program
123
205
  .command('chat', { isDefault: true })
124
206
  .description('Start interactive chat session with Mint')
125
207
  .argument('[message]', 'Initial message to send to Mint')
126
- .action(async (message) => {
127
- await startInteractiveChat(message);
208
+ .option('-i, --image <path>', 'Attach an image file to the initial message')
209
+ .action(async (message, options) => {
210
+ await startInteractiveChat(message, { imagePath: options.image });
128
211
  });
129
212
 
130
213
  // Onboard Command
@@ -158,6 +241,57 @@ program
158
241
  displayFeatures();
159
242
  });
160
243
 
244
+ program
245
+ .command('learn')
246
+ .description('Read a local markdown/text file and remember it as a Mint skill')
247
+ .argument('[filePath]', 'Path to a .md or .txt skill/instruction file')
248
+ .option('--delete <idOrPathOrName>', 'Delete a learned skill by id, path, or name')
249
+ .option('--list', 'List learned skills')
250
+ .action((filePath, options) => {
251
+ try {
252
+ if (options.list) {
253
+ 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
+ }
258
+ console.log(`\n${colors.bright}Learned Skills:${colors.reset}`);
259
+ skills.forEach(skill => {
260
+ console.log(`${colors.mint}#${skill.id}${colors.reset} ${skill.name}`);
261
+ console.log(` ${colors.gray}${skill.source_path}${colors.reset}`);
262
+ });
263
+ console.log('');
264
+ return;
265
+ }
266
+
267
+ if (options.delete) {
268
+ const deleted = memoryStore.deleteLearnedSkill(options.delete);
269
+ if (deleted > 0) {
270
+ console.log(`\n${colors.mint}✓${colors.reset} Deleted learned skill: ${options.delete}\n`);
271
+ } else {
272
+ console.log(`\n${colors.pink}✗${colors.reset} Learned skill not found: ${options.delete}\n`);
273
+ process.exitCode = 1;
274
+ }
275
+ return;
276
+ }
277
+
278
+ if (!filePath) {
279
+ throw new Error('Usage: mint learn <path-to-skill.md>');
280
+ }
281
+
282
+ const learned = learnSkillFile(filePath);
283
+ console.log(`\n${colors.mint}✓${colors.reset} Learned skill: ${learned.name}`);
284
+ console.log(`${colors.gray}Path: ${learned.source_path}${colors.reset}`);
285
+ if (learned.stored_length < learned.content_length) {
286
+ console.log(`${colors.gray}Stored first ${learned.stored_length} of ${learned.content_length} characters.${colors.reset}`);
287
+ }
288
+ console.log('');
289
+ } catch (error) {
290
+ console.error(`\n${colors.pink}Learn failed:${colors.reset} ${error.message}\n`);
291
+ process.exitCode = 1;
292
+ }
293
+ });
294
+
161
295
  // Task Command (Autonomous Background Task)
162
296
  program
163
297
  .command('task')
@@ -299,13 +433,24 @@ program
299
433
  .command('code')
300
434
  .description('Run Mint in workspace-aware coding mode for the current project')
301
435
  .argument('<task>', 'Coding task to execute in the current working directory')
302
- .action(async (task) => {
436
+ .option('-i, --image <path>', 'Attach an image file as context for the coding task')
437
+ .action(async (task, options) => {
303
438
  console.log(`\n${colors.mint}${colors.bright}[Mint Code]${colors.reset} Workspace: ${process.cwd()}`);
304
- console.log(`${colors.gray}[Mint Code] Task: ${task}${colors.reset}\n`);
305
439
 
306
440
  try {
307
- const result = await executeCodeTask(task, {
441
+ let effectiveTask = task;
442
+ let image = null;
443
+ if (options.image) {
444
+ image = loadImageAsDataUri(options.image);
445
+ console.log(`${colors.gray}[Mint Code] Image: ${image.path}${colors.reset}`);
446
+ }
447
+
448
+ console.log(`${colors.gray}[Mint Code] Task: ${task}${colors.reset}\n`);
449
+
450
+ const result = await executeCodeTask(effectiveTask, {
308
451
  cwd: process.cwd(),
452
+ imageDataUri: image ? image.dataUri : null,
453
+ imagePath: image ? image.path : null,
309
454
  onProgress: (info) => {
310
455
  console.log(formatProgress(info));
311
456
  },
@@ -330,13 +475,100 @@ program.parseAsync(process.argv).catch((error) => {
330
475
  /**
331
476
  * The Interactive Chat Loop — Gemini-style TUI
332
477
  */
333
- async function startInteractiveChat(initialMessage = null) {
478
+ async function startInteractiveChat(initialMessage = null, options = {}) {
334
479
  let lastResponseText = "";
480
+ let recentImageContextText = "";
335
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
+ };
336
547
 
337
548
  const ui = await createChatUI({
338
- onSubmit: async (text) => {
339
- const { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode, appendCodeStep, updateWorkspace, askUser } = ui;
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
+
340
572
  if (text.startsWith('/')) {
341
573
  if (text.startsWith('/agent')) {
342
574
  const args = text.split(' ');
@@ -428,14 +660,33 @@ async function startInteractiveChat(initialMessage = null) {
428
660
  } else {
429
661
  // Other slash commands
430
662
  const fakeRl = { close: () => { } };
431
- appendMessage('user', text);
432
- await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace);
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
+ }
433
681
  return;
434
682
  }
435
683
  }
436
684
  appendMessage('user', text);
437
685
 
438
686
  const transcript = await getChatTranscript();
687
+ const contextualHistory = recentImageContextText
688
+ ? [...transcript, { sender: 'system', text: recentImageContextText, timestamp: new Date().toISOString() }]
689
+ : transcript;
439
690
  if (setMode) setMode('Agent');
440
691
 
441
692
  let seconds = 0;
@@ -449,45 +700,63 @@ async function startInteractiveChat(initialMessage = null) {
449
700
  const config = require('./src/System/config_manager').readConfig();
450
701
  const availableProviders = require('./src/System/config_manager').getAvailableProviders(config);
451
702
  const preferredProvider = require('./src/CLI/code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
703
+ let streamedFinalSummary = false;
452
704
 
453
705
  const result = await executeCodeTask(text, {
454
706
  cwd: process.cwd(),
455
707
  requestApproval,
456
708
  askUser,
457
709
  provider: preferredProvider,
458
- history: transcript,
710
+ history: contextualHistory,
459
711
  onProgress: (info) => {
460
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);
461
719
  }
462
720
  });
463
721
 
464
722
  clearInterval(timer);
465
723
  setThinking(false);
466
724
  lastResponseText = result.summary;
467
- appendMessage('assistant', result.summary, { providerInfo: result.providerInfo });
725
+ if (!streamedFinalSummary) {
726
+ await streamAssistantSentences(result.summary, appendMessage, { providerInfo: result.providerInfo }, streamMessage);
727
+ }
468
728
 
469
729
  } catch (err) {
470
730
  clearInterval(timer);
471
731
  setThinking(false);
472
732
  appendMessage('error', formatErrorMessage(err));
473
733
  } finally {
474
- if (setMode) setMode('Chat');
734
+ if (setMode) setMode('Agent');
475
735
  }
476
736
  },
477
737
  onExit: () => {
478
- // Explicitly restore terminal state and disable ALL mouse tracking modes
479
- process.stdout.write('\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l');
480
- process.stdout.write('\x1b[?25h'); // Show cursor
481
- console.log(`\n${colors.pink}Goodbye! See you again soon!${colors.reset}\n`);
482
- process.exit(0);
738
+ exitWithGoodbye(0);
483
739
  }
484
740
  });
485
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
+
486
752
  // Handle initial message if passed via CLI arg
487
753
  if (initialMessage) {
488
754
  const { appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode, appendCodeStep, updateWorkspace, askUser } = ui;
489
755
  appendMessage('user', initialMessage);
490
756
  const transcript = await getChatTranscript();
757
+ const contextualHistory = recentImageContextText
758
+ ? [...transcript, { sender: 'system', text: recentImageContextText, timestamp: new Date().toISOString() }]
759
+ : transcript;
491
760
  if (setMode) setMode('Agent');
492
761
 
493
762
  let seconds = 0;
@@ -501,29 +770,38 @@ async function startInteractiveChat(initialMessage = null) {
501
770
  const config = require('./src/System/config_manager').readConfig();
502
771
  const availableProviders = require('./src/System/config_manager').getAvailableProviders(config);
503
772
  const preferredProvider = require('./src/CLI/code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
773
+ let streamedFinalSummary = false;
504
774
 
505
775
  const result = await executeCodeTask(initialMessage, {
506
776
  cwd: process.cwd(),
507
777
  requestApproval,
508
778
  askUser,
509
779
  provider: preferredProvider,
510
- history: transcript,
780
+ history: contextualHistory,
511
781
  onProgress: (info) => {
512
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);
513
789
  }
514
790
  });
515
791
 
516
792
  clearInterval(timer);
517
793
  setThinking(false);
518
794
  lastResponseText = result.summary;
519
- appendMessage('assistant', result.summary, { providerInfo: result.providerInfo });
795
+ if (!streamedFinalSummary) {
796
+ await streamAssistantSentences(result.summary, appendMessage, { providerInfo: result.providerInfo }, streamMessage);
797
+ }
520
798
 
521
799
  } catch (err) {
522
800
  clearInterval(timer);
523
801
  setThinking(false);
524
802
  appendMessage('error', formatErrorMessage(err));
525
803
  } finally {
526
- if (setMode) setMode('Chat');
804
+ if (setMode) setMode('Agent');
527
805
  }
528
806
  }
529
807
  }
@@ -531,7 +809,7 @@ async function startInteractiveChat(initialMessage = null) {
531
809
  /**
532
810
  * Handles slash commands within the TUI context
533
811
  */
534
- async function handleSlashCommandUI(input, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace) {
812
+ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace, helpers = {}) {
535
813
  const parts = input.split(' ');
536
814
  const command = parts[0].toLowerCase();
537
815
  const args = parts.slice(1);
@@ -541,9 +819,14 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
541
819
  case '/?':
542
820
  appendMessage('system', [
543
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',
544
826
  ' /code <task> — Force workspace Code Mode',
545
827
  ' /cd <path> — Change current working directory',
546
828
  ' /models [name] — List or switch Gemini models',
829
+ ' /memory [cmd] — Manage long-term memory',
547
830
  ' /config — Show current configuration',
548
831
  ' /copy — Copy last response to clipboard',
549
832
  ' /clear — Clear conversation history',
@@ -552,6 +835,195 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
552
835
  ].join('\n'));
553
836
  break;
554
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
+
555
1027
  case '/cd':
556
1028
  if (args.length === 0) {
557
1029
  appendMessage('system', `Current Directory: ${process.cwd()}`);
@@ -631,6 +1103,8 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
631
1103
  requestApproval,
632
1104
  appendCodeStep,
633
1105
  setMode,
1106
+ streamAssistantSentences: helpers.streamAssistantSentences,
1107
+ streamMessage: helpers.streamMessage,
634
1108
  askUser: () => Promise.resolve(''),
635
1109
  history: await getChatTranscript()
636
1110
  });
@@ -666,7 +1140,7 @@ async function handleSlashCommandUI(input, appendMessage, updateStatusModel, cop
666
1140
 
667
1141
  case '/exit':
668
1142
  case '/quit':
669
- process.exit(0);
1143
+ exitWithGoodbye(0);
670
1144
  break;
671
1145
 
672
1146
  default: