@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.
- package/README.md +27 -1
- package/main.js +28 -14
- package/mint-cli-logic.js +3 -119
- package/mint-cli.js +497 -23
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +10 -0
- 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
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
- 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
- package/package.json +26 -1
- package/src/AI_Brain/Gemini_API.js +147 -46
- package/src/AI_Brain/autonomous_brain.js +2 -1
- package/src/AI_Brain/memory_store.js +299 -3
- package/src/CLI/chat_router.js +18 -6
- package/src/CLI/chat_ui.js +396 -50
- package/src/CLI/code_agent.js +203 -14
- package/src/CLI/image_input.js +90 -0
- package/src/CLI/onboarding.js +72 -15
- package/src/CLI/updater.js +6 -4
- package/src/System/action_executor.js +59 -10
- package/src/System/config_manager.js +31 -1
- package/src/System/granular_automation.js +122 -53
- package/src/System/proactive_loop.js +19 -3
- package/src/System/safety_manager.js +108 -0
- package/src/System/sandbox_runner.js +182 -0
- package/src/System/system_automation.js +127 -81
- package/src/System/system_info.js +70 -0
- package/src/System/tool_registry.js +280 -0
- package/src/System/window_manager.js +4 -2
- package/src/UI/live2d_manager.js +368 -0
- package/src/UI/renderer.js +176 -18
- package/src/UI/styles.css +452 -31
- package/.codex +0 -0
- package/docs/assets/Agent_Mint.png +0 -0
- package/docs/assets/CLI_Screen.png +0 -0
- package/docs/assets/Settings.png +0 -0
- package/docs/assets/icon.png +0 -0
- package/docs/guide.html +0 -632
- package/docs/index.html +0 -133
- package/docs/style.css +0 -579
- package/index.html +0 -16
- package/src/UI/index.html +0 -126
- package/tech_news.txt +0 -3
- package/test_knowledge.txt +0 -3
- package/tests/action_executor_safety.test.js +0 -67
- package/tests/agent_orchestrator.test.js +0 -41
- package/tests/chat_router.test.js +0 -42
- package/tests/code_agent.test.js +0 -69
- package/tests/config_manager.test.js +0 -141
- package/tests/docker.test.js +0 -46
- package/tests/file_operations.test.js +0 -57
- package/tests/gmail.test.js +0 -135
- package/tests/gmail_auth.test.js +0 -129
- package/tests/google_calendar.test.js +0 -113
- package/tests/google_tts_urls.test.js +0 -24
- package/tests/memory_store.test.js +0 -185
- package/tests/notion.test.js +0 -121
- package/tests/provider_routing.test.js +0 -83
- package/tests/safety_manager.test.js +0 -40
- package/tests/spotify.test.js +0 -201
- package/tests/system_monitor.test.js +0 -37
- package/tests/updater.test.js +0 -32
- 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
|
-
.
|
|
127
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
339
|
-
|
|
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
|
-
|
|
432
|
-
|
|
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:
|
|
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
|
-
|
|
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('
|
|
734
|
+
if (setMode) setMode('Agent');
|
|
475
735
|
}
|
|
476
736
|
},
|
|
477
737
|
onExit: () => {
|
|
478
|
-
|
|
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:
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
1143
|
+
exitWithGoodbye(0);
|
|
670
1144
|
break;
|
|
671
1145
|
|
|
672
1146
|
default:
|