@pheem49/mint 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/README.md +35 -1
  2. package/main.js +28 -14
  3. package/mint-cli-logic.js +3 -119
  4. package/mint-cli.js +201 -500
  5. package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
  6. package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
  7. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +40 -0
  8. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json +15 -0
  9. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
  10. package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
  11. package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
  12. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +15 -0
  13. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
  14. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
  15. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
  16. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
  17. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
  18. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
  19. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
  20. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
  21. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
  22. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
  23. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
  24. package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +23 -0
  25. package/package.json +40 -17
  26. package/src/AI_Brain/Gemini_API.js +147 -46
  27. package/src/AI_Brain/autonomous_brain.js +2 -1
  28. package/src/AI_Brain/memory_store.js +299 -3
  29. package/src/AI_Brain/proactive_engine.js +12 -2
  30. package/src/Automation_Layer/browser_automation.js +26 -24
  31. package/src/CLI/approval_handler.js +42 -0
  32. package/src/CLI/chat_router.js +18 -6
  33. package/src/CLI/chat_ui.js +583 -52
  34. package/src/CLI/cli_colors.js +32 -0
  35. package/src/CLI/cli_formatters.js +89 -0
  36. package/src/CLI/code_agent.js +369 -71
  37. package/src/CLI/image_input.js +90 -0
  38. package/src/CLI/intent_detectors.js +181 -0
  39. package/src/CLI/interactive_chat.js +479 -0
  40. package/src/CLI/list_features.js +3 -0
  41. package/src/CLI/onboarding.js +72 -15
  42. package/src/CLI/repo_summarizer.js +282 -0
  43. package/src/CLI/semantic_code_search.js +312 -0
  44. package/src/CLI/skill_manager.js +41 -0
  45. package/src/CLI/slash_command_handler.js +418 -0
  46. package/src/CLI/symbol_indexer.js +231 -0
  47. package/src/CLI/updater.js +6 -4
  48. package/src/Channels/discord_bridge.js +11 -13
  49. package/src/Channels/line_bridge.js +10 -10
  50. package/src/Channels/slack_bridge.js +7 -12
  51. package/src/Channels/telegram_bridge.js +6 -14
  52. package/src/Channels/whatsapp_bridge.js +11 -9
  53. package/src/System/action_executor.js +59 -10
  54. package/src/System/chat_history_manager.js +20 -12
  55. package/src/System/config_manager.js +31 -1
  56. package/src/System/granular_automation.js +122 -53
  57. package/src/System/optional_require.js +23 -0
  58. package/src/System/proactive_loop.js +19 -3
  59. package/src/System/safety_manager.js +108 -0
  60. package/src/System/sandbox_runner.js +182 -0
  61. package/src/System/system_automation.js +127 -81
  62. package/src/System/system_info.js +70 -0
  63. package/src/System/tool_registry.js +280 -0
  64. package/src/System/window_manager.js +4 -2
  65. package/src/UI/live2d_manager.js +566 -0
  66. package/src/UI/renderer.js +339 -21
  67. package/src/UI/settings.css +655 -420
  68. package/src/UI/settings.html +478 -432
  69. package/src/UI/settings.js +10 -8
  70. package/src/UI/styles.css +516 -31
  71. package/.codex +0 -0
  72. package/docs/assets/Agent_Mint.png +0 -0
  73. package/docs/assets/CLI_Screen.png +0 -0
  74. package/docs/assets/Settings.png +0 -0
  75. package/docs/assets/icon.png +0 -0
  76. package/docs/guide.html +0 -632
  77. package/docs/index.html +0 -133
  78. package/docs/style.css +0 -579
  79. package/index.html +0 -16
  80. package/src/UI/index.html +0 -126
  81. package/tech_news.txt +0 -3
  82. package/test_knowledge.txt +0 -3
  83. package/tests/action_executor_safety.test.js +0 -67
  84. package/tests/agent_orchestrator.test.js +0 -41
  85. package/tests/chat_router.test.js +0 -42
  86. package/tests/code_agent.test.js +0 -69
  87. package/tests/config_manager.test.js +0 -141
  88. package/tests/docker.test.js +0 -46
  89. package/tests/file_operations.test.js +0 -57
  90. package/tests/gmail.test.js +0 -135
  91. package/tests/gmail_auth.test.js +0 -129
  92. package/tests/google_calendar.test.js +0 -113
  93. package/tests/google_tts_urls.test.js +0 -24
  94. package/tests/memory_store.test.js +0 -185
  95. package/tests/notion.test.js +0 -121
  96. package/tests/provider_routing.test.js +0 -83
  97. package/tests/safety_manager.test.js +0 -40
  98. package/tests/spotify.test.js +0 -201
  99. package/tests/system_monitor.test.js +0 -37
  100. package/tests/updater.test.js +0 -32
  101. package/tests/workspace_manager.test.js +0 -56
package/mint-cli.js CHANGED
@@ -1,35 +1,50 @@
1
1
  #!/usr/bin/env node
2
2
  require('dotenv').config({ quiet: true });
3
+
3
4
  // Suppress experimental SQLite warning
4
5
  const originalEmit = process.emit;
5
6
  process.emit = function (name, data, ...args) {
6
- if (name === 'warning' && typeof data === 'object' && data.name === 'ExperimentalWarning' && data.message.includes('SQLite')) {
7
+ if (name === 'warning' && typeof data === 'object' &&
8
+ data.name === 'ExperimentalWarning' && data.message.includes('SQLite')) {
7
9
  return false;
8
10
  }
9
11
  return originalEmit.apply(process, [name, data, ...args]);
10
12
  };
13
+
11
14
  const { Command } = require('commander');
12
- const { handleChat, handleGeminiChatStream, resetChat, refreshApiKeyFromConfig, getChatTranscript } = require('./src/AI_Brain/Gemini_API');
13
- const agentOrchestrator = require('./src/AI_Brain/agent_orchestrator');
14
- const workspaceManager = require('./src/CLI/workspace_manager');
15
- const systemMonitor = require('./src/Plugins/system_monitor');
16
- const { sendNotification } = require('./src/System/notifications');
17
- const pkg = require('./package.json');
18
- const { runOnboarding } = require('./src/CLI/onboarding');
19
- const { startAgent } = require('./src/AI_Brain/headless_agent');
20
- const { displayFeatures } = require('./src/CLI/list_features');
21
- const { readConfig, writeConfig } = require('./src/System/config_manager');
22
- const { executeCodeTask } = require('./src/CLI/code_agent');
23
- const { detectCodeIntent, runChatRoutedTask } = require('./src/CLI/chat_router');
24
- const readline = require('readline');
25
- const { createChatUI } = require('./src/CLI/chat_ui');
15
+
16
+ // ── CLI modules ──────────────────────────────────────────────────────────────
17
+ const { colors, exitWithGoodbye } = require('./src/CLI/cli_colors');
18
+ const { formatProgress } = require('./src/CLI/cli_formatters');
19
+ const { startInteractiveChat } = require('./src/CLI/interactive_chat');
20
+ const { requestCodeApproval } = require('./src/CLI/approval_handler');
21
+ const { learnSkillFile } = require('./src/CLI/skill_manager');
22
+
23
+ // ── Feature / system modules ────────────────────────────────────────────────
24
+ const { runOnboarding } = require('./src/CLI/onboarding');
25
+ const { startAgent } = require('./src/AI_Brain/headless_agent');
26
+ const { displayFeatures } = require('./src/CLI/list_features');
27
+ const { readConfig, writeConfig } = require('./src/System/config_manager');
28
+ const { executeCodeTask } = require('./src/CLI/code_agent');
29
+ const memoryStore = require('./src/AI_Brain/memory_store');
26
30
  const { runUpdate, runStartupAutoUpdate, shouldRunAutoUpdate } = require('./src/CLI/updater');
27
- const { runGmailAuth } = require('./src/CLI/gmail_auth');
31
+ const { runGmailAuth } = require('./src/CLI/gmail_auth');
32
+ const { loadImageAsDataUri } = require('./src/CLI/image_input');
33
+ const { summarizeRepository, formatRepoSummary } = require('./src/CLI/repo_summarizer');
34
+ const { buildSymbolIndex, formatSymbolIndex } = require('./src/CLI/symbol_indexer');
35
+ const {
36
+ indexSemanticCode,
37
+ searchSemanticCode,
38
+ formatSemanticCodeIndex,
39
+ formatSemanticCodeSearch
40
+ } = require('./src/CLI/semantic_code_search');
41
+
42
+ const pkg = require('./package.json');
28
43
 
29
- // Startup Info
30
- const startupConfig = readConfig();
44
+ // ── Startup banner ───────────────────────────────────────────────────────────
45
+ const startupConfig = readConfig();
31
46
  const startupProvider = startupConfig.aiProvider || 'gemini';
32
- const startupModel = startupProvider === 'openai'
47
+ const startupModel = startupProvider === 'openai'
33
48
  ? (startupConfig.openaiModel || 'gpt-4o')
34
49
  : startupProvider === 'anthropic'
35
50
  ? (startupConfig.anthropicModel || 'claude-3-5-sonnet-latest')
@@ -38,56 +53,17 @@ const startupModel = startupProvider === 'openai'
38
53
  : startupProvider === 'ollama'
39
54
  ? (startupConfig.ollamaModel || 'llama3:latest')
40
55
  : (startupConfig.geminiModel || 'gemini-2.5-flash');
41
- const startupNow = new Date();
42
- const startupTime = startupNow.toLocaleString('th-TH', {
43
- day: '2-digit', month: '2-digit', year: 'numeric',
44
- hour: '2-digit', minute: '2-digit', hour12: false
45
- }).replace(',', '');
46
- console.log(`\x1b[38;5;121m[Mint] v${pkg.version} | ${startupTime} | Active AI: ${startupProvider} • ${startupModel}\x1b[0m`);
47
-
48
- // ANSI Colors
49
- const colors = {
50
- reset: "\x1b[0m",
51
- bright: "\x1b[1m",
52
- mint: "\x1b[38;5;121m",
53
- pink: "\x1b[38;5;213m",
54
- gray: "\x1b[90m",
55
- cyan: "\x1b[36m",
56
- yellow: "\x1b[33m"
57
- };
58
-
59
- function formatProgress(info) {
60
- if (typeof info === 'string') return `${colors.gray}[Mint Code] ${info}${colors.reset}`;
61
56
 
62
- const { step, phase, action, target, message } = info;
63
-
64
- if (action === 'ask_user') {
65
- return `\n${colors.mint}✓${colors.reset} ${colors.bright}Ask User${colors.reset}\n${colors.gray} ${target || message || ''}${colors.reset}`;
66
- }
67
-
68
- let icon = `${colors.mint}✓${colors.reset}`;
69
- let label = action || phase;
70
- let color = colors.reset;
71
-
72
- switch (action) {
73
- case 'thinking':
74
- return `\n${colors.yellow}* ${colors.bright}Thinking${colors.reset}`;
75
- case 'web_search': label = 'WebSearch'; break;
76
- case 'list_files':
77
- case 'find_path': label = 'Explored'; break;
78
- case 'read_file': label = 'ReadFile'; break;
79
- case 'search_code': label = 'SearchText'; break;
80
- case 'apply_patch':
81
- case 'write_file': label = 'Edited'; break;
82
- case 'run_shell': label = 'Ran command'; break;
83
- case 'json_repair': icon = '*'; label = 'Repairing JSON'; break;
84
- case 'reviewer_start': label = 'Reviewing'; break;
85
- }
57
+ const startupNow = new Date();
58
+ const startupTime = startupNow.toLocaleString('th-TH', {
59
+ day: '2-digit', month: '2-digit', year: 'numeric',
60
+ hour: '2-digit', minute: '2-digit', hour12: false
61
+ }).replace(',', '');
62
+ console.log(`${colors.mint}[Mint] v${pkg.version} | ${startupTime} | Active AI: ${startupProvider} • ${startupModel}${colors.reset}`);
86
63
 
87
- const content = target || message || '';
88
- return ` ${icon} ${colors.bright}${label}${colors.reset} ${color}${content}${colors.reset}`;
89
- }
64
+ process.once('SIGINT', () => exitWithGoodbye(0));
90
65
 
66
+ // ── Commander program ────────────────────────────────────────────────────────
91
67
  const program = new Command();
92
68
 
93
69
  program
@@ -95,20 +71,11 @@ program
95
71
  .description('Mint - Your Personal AI Assistant CLI')
96
72
  .version(pkg.version);
97
73
 
74
+ // Auto-update hook
98
75
  program.hook('preAction', async (thisCommand, actionCommand) => {
99
- if (actionCommand.name() === 'update' || process.env.MINT_SKIP_AUTO_UPDATE === '1') {
100
- return;
101
- }
102
-
76
+ if (actionCommand.name() === 'update' || process.env.MINT_SKIP_AUTO_UPDATE === '1') return;
103
77
  const config = readConfig();
104
- if (config.enableAutoUpdate === false) {
105
- return;
106
- }
107
-
108
- if (!shouldRunAutoUpdate(config)) {
109
- return;
110
- }
111
-
78
+ if (config.enableAutoUpdate === false || !shouldRunAutoUpdate(config)) return;
112
79
  console.log(`${colors.gray}[Mint Update] Checking for updates...${colors.reset}`);
113
80
  const result = await runStartupAutoUpdate(config, writeConfig);
114
81
  if (result.status === 'updated') {
@@ -118,16 +85,17 @@ program.hook('preAction', async (thisCommand, actionCommand) => {
118
85
  }
119
86
  });
120
87
 
121
- // Chat Command (Interactive Mode)
88
+ // ── Commands ─────────────────────────────────────────────────────────────────
89
+
122
90
  program
123
91
  .command('chat', { isDefault: true })
124
92
  .description('Start interactive chat session with Mint')
125
93
  .argument('[message]', 'Initial message to send to Mint')
126
- .action(async (message) => {
127
- await startInteractiveChat(message);
94
+ .option('-i, --image <path>', 'Attach an image file to the initial message')
95
+ .action(async (message, options) => {
96
+ await startInteractiveChat(message, { imagePath: options.image });
128
97
  });
129
98
 
130
- // Onboard Command
131
99
  program
132
100
  .command('onboard')
133
101
  .description('Setup Mint for the first time')
@@ -136,7 +104,6 @@ program
136
104
  await runOnboarding(options);
137
105
  });
138
106
 
139
- // Agent Command (Headless Daemon Mode)
140
107
  program
141
108
  .command('agent')
142
109
  .description('Run Mint as a background agent (headless)')
@@ -150,15 +117,135 @@ program
150
117
  await startAgent();
151
118
  });
152
119
 
153
- // List Command
154
120
  program
155
121
  .command('list')
156
122
  .description('Show list of Mint features and commands')
157
- .action(() => {
158
- displayFeatures();
123
+ .action(() => displayFeatures());
124
+
125
+ program
126
+ .command('summarize')
127
+ .alias('summary')
128
+ .description('Summarize a repository structure, tooling, git state, and key files')
129
+ .argument('[path]', 'Repository path to summarize', process.cwd())
130
+ .option('--json', 'Print raw JSON summary')
131
+ .action((targetPath, options) => {
132
+ try {
133
+ const summary = summarizeRepository(targetPath);
134
+ if (options.json) { console.log(JSON.stringify(summary, null, 2)); return; }
135
+ console.log(`\n${formatRepoSummary(summary)}\n`);
136
+ } catch (error) {
137
+ console.error(`\n${colors.pink}Summarize failed:${colors.reset} ${error.message}\n`);
138
+ process.exitCode = 1;
139
+ }
140
+ });
141
+
142
+ program
143
+ .command('symbols')
144
+ .alias('symbol-index')
145
+ .description('Build a source symbol index for the current repository')
146
+ .argument('[path]', 'Repository path to index', process.cwd())
147
+ .option('--json', 'Print raw JSON symbol index')
148
+ .option('--limit <count>', 'Limit formatted symbols shown', value => Number(value), 80)
149
+ .action((targetPath, options) => {
150
+ try {
151
+ const index = buildSymbolIndex(targetPath);
152
+ if (options.json) { console.log(JSON.stringify(index, null, 2)); return; }
153
+ console.log(`\n${formatSymbolIndex(index, { limit: options.limit })}\n`);
154
+ } catch (error) {
155
+ console.error(`\n${colors.pink}Symbol index failed:${colors.reset} ${error.message}\n`);
156
+ process.exitCode = 1;
157
+ }
158
+ });
159
+
160
+ const semanticCodeCommand = program
161
+ .command('semantic-code')
162
+ .alias('semantic')
163
+ .description('Index and search source code semantically with embeddings');
164
+
165
+ semanticCodeCommand
166
+ .command('index')
167
+ .description('Create embeddings for source code chunks in a repository')
168
+ .argument('[path]', 'Repository path to index', process.cwd())
169
+ .option('--json', 'Print raw JSON index metadata')
170
+ .action(async (targetPath, options) => {
171
+ try {
172
+ const index = await indexSemanticCode(targetPath, {
173
+ onProgress: (info) => {
174
+ if (info.current === 1 || info.current === info.total || info.current % 25 === 0) {
175
+ console.log(`${colors.gray}[Semantic Code] Embedded ${info.current}/${info.total}: ${info.file}${colors.reset}`);
176
+ }
177
+ }
178
+ });
179
+ if (options.json) { console.log(JSON.stringify(index, null, 2)); return; }
180
+ console.log(`\n${formatSemanticCodeIndex(index)}\n`);
181
+ } catch (error) {
182
+ console.error(`\n${colors.pink}Semantic code index failed:${colors.reset} ${error.message}\n`);
183
+ process.exitCode = 1;
184
+ }
185
+ });
186
+
187
+ semanticCodeCommand
188
+ .command('search')
189
+ .description('Search an existing semantic code index')
190
+ .argument('<query...>', 'Natural language code search query')
191
+ .option('--path <path>', 'Repository path to search', process.cwd())
192
+ .option('--json', 'Print raw JSON search results')
193
+ .option('--top-k <count>', 'Number of results to return', value => Number(value), 5)
194
+ .action(async (query, options) => {
195
+ try {
196
+ const results = await searchSemanticCode(query.join(' '), options.path, { topK: options.topK });
197
+ if (options.json) { console.log(JSON.stringify(results, null, 2)); return; }
198
+ console.log(`\n${formatSemanticCodeSearch(results)}\n`);
199
+ } catch (error) {
200
+ console.error(`\n${colors.pink}Semantic code search failed:${colors.reset} ${error.message}\n`);
201
+ process.exitCode = 1;
202
+ }
203
+ });
204
+
205
+ program
206
+ .command('learn')
207
+ .description('Read a local markdown/text file and remember it as a Mint skill')
208
+ .argument('[filePath]', 'Path to a .md or .txt skill/instruction file')
209
+ .option('--delete <idOrPathOrName>', 'Delete a learned skill by id, path, or name')
210
+ .option('--list', 'List learned skills')
211
+ .action((filePath, options) => {
212
+ try {
213
+ if (options.list) {
214
+ const skills = memoryStore.getLearnedSkills(50);
215
+ if (skills.length === 0) { console.log(`\n${colors.gray}No learned skills stored.${colors.reset}\n`); return; }
216
+ console.log(`\n${colors.bright}Learned Skills:${colors.reset}`);
217
+ skills.forEach(skill => {
218
+ console.log(`${colors.mint}#${skill.id}${colors.reset} ${skill.name}`);
219
+ console.log(` ${colors.gray}${skill.source_path}${colors.reset}`);
220
+ });
221
+ console.log('');
222
+ return;
223
+ }
224
+ if (options.delete) {
225
+ const deleted = memoryStore.deleteLearnedSkill(options.delete);
226
+ if (deleted > 0) {
227
+ console.log(`\n${colors.mint}✓${colors.reset} Deleted learned skill: ${options.delete}\n`);
228
+ } else {
229
+ console.log(`\n${colors.pink}✗${colors.reset} Learned skill not found: ${options.delete}\n`);
230
+ process.exitCode = 1;
231
+ }
232
+ return;
233
+ }
234
+ if (!filePath) throw new Error('Usage: mint learn <path-to-skill.md>');
235
+
236
+ const learned = learnSkillFile(filePath);
237
+ console.log(`\n${colors.mint}✓${colors.reset} Learned skill: ${learned.name}`);
238
+ console.log(`${colors.gray}Path: ${learned.source_path}${colors.reset}`);
239
+ if (learned.stored_length < learned.content_length) {
240
+ console.log(`${colors.gray}Stored first ${learned.stored_length} of ${learned.content_length} characters.${colors.reset}`);
241
+ }
242
+ console.log('');
243
+ } catch (error) {
244
+ console.error(`\n${colors.pink}Learn failed:${colors.reset} ${error.message}\n`);
245
+ process.exitCode = 1;
246
+ }
159
247
  });
160
248
 
161
- // Task Command (Autonomous Background Task)
162
249
  program
163
250
  .command('task')
164
251
  .description('Delegate a complex task to the background agent')
@@ -176,23 +263,18 @@ program
176
263
  program
177
264
  .command('update')
178
265
  .description('Check for and install the latest Mint CLI version from npm')
179
- .option('--check', 'Only check whether an update is available')
266
+ .option('--check', 'Only check whether an update is available')
180
267
  .option('--dry-run', 'Show the npm update operation without installing')
181
268
  .action(async (options) => {
182
269
  console.log(`\n${colors.mint}${colors.bright}[Mint Update]${colors.reset} Checking npm for updates...`);
183
-
184
270
  try {
185
271
  const result = await runUpdate({
186
- checkOnly: options.check === true,
187
- dryRun: options.dryRun === true
272
+ checkOnly: options.check === true,
273
+ dryRun: options.dryRun === true
188
274
  });
189
-
190
275
  const color = result.status === 'error' ? colors.pink : colors.mint;
191
276
  console.log(`${color}${result.message}${colors.reset}\n`);
192
-
193
- if (result.status === 'error') {
194
- process.exitCode = 1;
195
- }
277
+ if (result.status === 'error') process.exitCode = 1;
196
278
  } catch (error) {
197
279
  console.error(`${colors.pink}Update failed: ${error.message}${colors.reset}\n`);
198
280
  process.exitCode = 1;
@@ -204,28 +286,21 @@ program
204
286
  .description('Manage MCP (Model Context Protocol) servers')
205
287
  .addCommand(new Command('add')
206
288
  .description('Add a new MCP server')
207
- .argument('<name>', 'Server name')
289
+ .argument('<name>', 'Server name')
208
290
  .argument('<command>', 'Command to run (e.g. npx)')
209
291
  .option('-a, --args <args...>', 'Command arguments')
210
- .option('-e, --env <env...>', 'Environment variables (KEY=VALUE)')
292
+ .option('-e, --env <env...>', 'Environment variables (KEY=VALUE)')
211
293
  .action((name, command, options) => {
212
- const config = readConfig();
294
+ const config = readConfig();
213
295
  const mcpServers = config.mcpServers || {};
214
-
215
- const env = {};
296
+ const env = {};
216
297
  if (options.env) {
217
298
  options.env.forEach(kv => {
218
299
  const [k, v] = kv.split('=');
219
300
  if (k && v) env[k] = v;
220
301
  });
221
302
  }
222
-
223
- mcpServers[name] = {
224
- command,
225
- args: options.args || [],
226
- env
227
- };
228
-
303
+ mcpServers[name] = { command, args: options.args || [], env };
229
304
  config.mcpServers = mcpServers;
230
305
  writeConfig(config);
231
306
  console.log(`\n${colors.mint}✓${colors.reset} MCP server "${name}" added successfully.`);
@@ -248,7 +323,7 @@ program
248
323
  .addCommand(new Command('list')
249
324
  .description('List configured MCP servers')
250
325
  .action(() => {
251
- const config = readConfig();
326
+ const config = readConfig();
252
327
  const servers = Object.keys(config.mcpServers || {});
253
328
  if (servers.length === 0) {
254
329
  console.log(`\n${colors.gray}No MCP servers configured.${colors.reset}`);
@@ -278,13 +353,13 @@ program
278
353
  .addCommand(new Command('auth')
279
354
  .description('Open Google OAuth login and save a Gmail refresh token')
280
355
  .option('--port <port>', 'Local callback port, defaults to a random available port')
281
- .option('--no-open', 'Print the auth link without opening a browser')
356
+ .option('--no-open', 'Print the auth link without opening a browser')
282
357
  .action(async (options) => {
283
358
  try {
284
359
  const result = await runGmailAuth({
285
- port: options.port ? Number(options.port) : 0,
360
+ port: options.port ? Number(options.port) : 0,
286
361
  openBrowser: options.open,
287
- logger: console
362
+ logger: console
288
363
  });
289
364
  console.log(`\n${colors.mint}✓${colors.reset} Gmail connected for ${result.userId}. Refresh token saved.`);
290
365
  console.log(`${colors.gray}Scopes: ${result.scopes.join(', ')}${colors.reset}\n`);
@@ -299,16 +374,22 @@ program
299
374
  .command('code')
300
375
  .description('Run Mint in workspace-aware coding mode for the current project')
301
376
  .argument('<task>', 'Coding task to execute in the current working directory')
302
- .action(async (task) => {
377
+ .option('-i, --image <path>', 'Attach an image file as context for the coding task')
378
+ .action(async (task, options) => {
303
379
  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
-
306
380
  try {
381
+ let image = null;
382
+ if (options.image) {
383
+ image = loadImageAsDataUri(options.image);
384
+ console.log(`${colors.gray}[Mint Code] Image: ${image.path}${colors.reset}`);
385
+ }
386
+ console.log(`${colors.gray}[Mint Code] Task: ${task}${colors.reset}\n`);
387
+
307
388
  const result = await executeCodeTask(task, {
308
- cwd: process.cwd(),
309
- onProgress: (info) => {
310
- console.log(formatProgress(info));
311
- },
389
+ cwd: process.cwd(),
390
+ imageDataUri: image ? image.dataUri : null,
391
+ imagePath: image ? image.path : null,
392
+ onProgress: (info) => console.log(formatProgress(info)),
312
393
  requestApproval: requestCodeApproval
313
394
  });
314
395
 
@@ -322,388 +403,8 @@ program
322
403
  }
323
404
  });
324
405
 
406
+ // ── Parse ────────────────────────────────────────────────────────────────────
325
407
  program.parseAsync(process.argv).catch((error) => {
326
408
  console.error(`${colors.pink}${error.message}${colors.reset}`);
327
409
  process.exitCode = 1;
328
410
  });
329
-
330
- /**
331
- * The Interactive Chat Loop — Gemini-style TUI
332
- */
333
- async function startInteractiveChat(initialMessage = null) {
334
- let lastResponseText = "";
335
- const formatErrorMessage = (err) => err && err.message ? err.message : String(err || 'Unknown error');
336
-
337
- const ui = await createChatUI({
338
- onSubmit: async (text) => {
339
- const { screen, appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode, appendCodeStep, updateWorkspace, askUser } = ui;
340
- if (text.startsWith('/')) {
341
- if (text.startsWith('/agent')) {
342
- const args = text.split(' ');
343
- if (args[1] === 'list') {
344
- appendMessage('system', `Available Agents: ${agentOrchestrator.listAgents().join(', ')}`);
345
- } else if (args[1]) {
346
- const success = agentOrchestrator.setAgent(args[1]);
347
- if (success) {
348
- const agent = agentOrchestrator.getCurrentAgent();
349
- appendMessage('system', `Switched to Agent: ${agent.icon} ${agent.name}`);
350
- updateStatusModel(agent.name); // Pass name to status bar
351
- resetChat(); // Reset to apply new system prompt
352
- } else {
353
- appendMessage('error', `Agent "${args[1]}" not found. Try /agent list`);
354
- }
355
- } else {
356
- const agent = agentOrchestrator.getCurrentAgent();
357
- appendMessage('system', `Current Agent: ${agent.icon} ${agent.name}\nUsage: /agent <type> or /agent list`);
358
- }
359
- return;
360
- }
361
-
362
- if (text.startsWith('/stats')) {
363
- appendMessage('system', '📊 Fetching system statistics...');
364
- const stats = await systemMonitor.execute('stats');
365
- appendMessage('system', stats);
366
- return;
367
- }
368
-
369
- if (text.startsWith('/workspace')) {
370
- const args = text.split(' ');
371
- const subCmd = args[1];
372
-
373
- if (subCmd === 'add') {
374
- const name = args[2];
375
- const wsPath = args[3] || '.';
376
- const instructions = args.slice(4).join(' ');
377
- if (!name) {
378
- appendMessage('error', 'Usage: /workspace add <name> [path] [instructions]');
379
- } else {
380
- workspaceManager.addWorkspace(name, wsPath, instructions);
381
- appendMessage('system', `Workspace "${name}" registered at ${path.resolve(wsPath)}`);
382
- resetChat();
383
- }
384
- } else if (subCmd === 'list') {
385
- const all = workspaceManager.listWorkspaces();
386
- let listMsg = "Registered Workspaces:\n";
387
- for (const n in all) listMsg += `- ${n}: ${all[n].path}\n`;
388
- appendMessage('system', Object.keys(all).length ? listMsg : "No workspaces registered.");
389
- } else if (subCmd === 'remove') {
390
- const name = args[2];
391
- if (workspaceManager.removeWorkspace(name)) {
392
- appendMessage('system', `Removed workspace "${name}"`);
393
- resetChat();
394
- } else {
395
- appendMessage('error', `Workspace "${name}" not found.`);
396
- }
397
- } else if (subCmd === 'use' || subCmd === 'switch') {
398
- const name = args[2];
399
- const all = workspaceManager.listWorkspaces();
400
- if (all[name]) {
401
- const newPath = all[name].path;
402
- try {
403
- process.chdir(newPath);
404
- updateWorkspace(newPath);
405
- appendMessage('system', `✓ Switched to workspace "${name}" at ${newPath}`);
406
- resetChat();
407
- } catch (e) {
408
- appendMessage('error', `Failed to change directory: ${e.message}`);
409
- }
410
- } else {
411
- appendMessage('error', `Workspace "${name}" not found. Try /workspace list`);
412
- }
413
- } else {
414
- const ws = workspaceManager.getWorkspaceByPath(process.cwd());
415
- appendMessage('system', ws ? `Current Workspace: ${ws.name}\nPath: ${ws.path}` : `Not currently in a registered workspace.\nActive Path: ${process.cwd()}\nUsage: /workspace <add|use|list|remove>`);
416
- }
417
- return;
418
- }
419
-
420
- if (text.startsWith('/review')) {
421
- if (!lastResponseText) {
422
- appendMessage('error', 'Nothing to review yet. Get a response first.');
423
- return;
424
- }
425
- agentOrchestrator.setAgent('reviewer');
426
- appendMessage('system', '⚖️ Requesting second-pass review from Mint Reviewer...');
427
- text = `Please review this previous response and provide a critique:\n\n${lastResponseText}`;
428
- } else {
429
- // Other slash commands
430
- const fakeRl = { close: () => { } };
431
- appendMessage('user', text);
432
- await handleSlashCommandUI(text, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace);
433
- return;
434
- }
435
- }
436
- appendMessage('user', text);
437
-
438
- const transcript = await getChatTranscript();
439
- if (setMode) setMode('Agent');
440
-
441
- let seconds = 0;
442
- setThinking(true, seconds);
443
- const timer = setInterval(() => {
444
- seconds++;
445
- setThinking(true, seconds);
446
- }, 1000);
447
-
448
- try {
449
- const config = require('./src/System/config_manager').readConfig();
450
- const availableProviders = require('./src/System/config_manager').getAvailableProviders(config);
451
- const preferredProvider = require('./src/CLI/code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
452
-
453
- const result = await executeCodeTask(text, {
454
- cwd: process.cwd(),
455
- requestApproval,
456
- askUser,
457
- provider: preferredProvider,
458
- history: transcript,
459
- onProgress: (info) => {
460
- if (appendCodeStep) appendCodeStep(info);
461
- }
462
- });
463
-
464
- clearInterval(timer);
465
- setThinking(false);
466
- lastResponseText = result.summary;
467
- appendMessage('assistant', result.summary, { providerInfo: result.providerInfo });
468
-
469
- } catch (err) {
470
- clearInterval(timer);
471
- setThinking(false);
472
- appendMessage('error', formatErrorMessage(err));
473
- } finally {
474
- if (setMode) setMode('Chat');
475
- }
476
- },
477
- 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);
483
- }
484
- });
485
-
486
- // Handle initial message if passed via CLI arg
487
- if (initialMessage) {
488
- const { appendMessage, streamMessage, setThinking, updateStatusModel, copyLastResponse, requestApproval, setMode, appendCodeStep, updateWorkspace, askUser } = ui;
489
- appendMessage('user', initialMessage);
490
- const transcript = await getChatTranscript();
491
- if (setMode) setMode('Agent');
492
-
493
- let seconds = 0;
494
- setThinking(true, seconds);
495
- const timer = setInterval(() => {
496
- seconds++;
497
- setThinking(true, seconds);
498
- }, 1000);
499
-
500
- try {
501
- const config = require('./src/System/config_manager').readConfig();
502
- const availableProviders = require('./src/System/config_manager').getAvailableProviders(config);
503
- const preferredProvider = require('./src/CLI/code_agent')._helpers.selectSupportedCodeProvider(config, availableProviders);
504
-
505
- const result = await executeCodeTask(initialMessage, {
506
- cwd: process.cwd(),
507
- requestApproval,
508
- askUser,
509
- provider: preferredProvider,
510
- history: transcript,
511
- onProgress: (info) => {
512
- if (appendCodeStep) appendCodeStep(info);
513
- }
514
- });
515
-
516
- clearInterval(timer);
517
- setThinking(false);
518
- lastResponseText = result.summary;
519
- appendMessage('assistant', result.summary, { providerInfo: result.providerInfo });
520
-
521
- } catch (err) {
522
- clearInterval(timer);
523
- setThinking(false);
524
- appendMessage('error', formatErrorMessage(err));
525
- } finally {
526
- if (setMode) setMode('Chat');
527
- }
528
- }
529
- }
530
-
531
- /**
532
- * Handles slash commands within the TUI context
533
- */
534
- async function handleSlashCommandUI(input, appendMessage, updateStatusModel, copyLastResponse, setThinking, requestApproval, setMode, appendCodeStep, updateWorkspace) {
535
- const parts = input.split(' ');
536
- const command = parts[0].toLowerCase();
537
- const args = parts.slice(1);
538
-
539
- switch (command) {
540
- case '/help':
541
- case '/?':
542
- appendMessage('system', [
543
- 'Mint Slash Commands:',
544
- ' /code <task> — Force workspace Code Mode',
545
- ' /cd <path> — Change current working directory',
546
- ' /models [name] — List or switch Gemini models',
547
- ' /config — Show current configuration',
548
- ' /copy — Copy last response to clipboard',
549
- ' /clear — Clear conversation history',
550
- ' /reset — Reset conversation history',
551
- ' /exit — Exit Mint'
552
- ].join('\n'));
553
- break;
554
-
555
- case '/cd':
556
- if (args.length === 0) {
557
- appendMessage('system', `Current Directory: ${process.cwd()}`);
558
- break;
559
- }
560
- try {
561
- const newPath = path.resolve(process.cwd(), args[0]);
562
- if (fs.existsSync(newPath) && fs.lstatSync(newPath).isDirectory()) {
563
- process.chdir(newPath);
564
- if (updateWorkspace) updateWorkspace(newPath);
565
- appendMessage('system', `✓ Directory changed to: ${newPath}`);
566
- } else {
567
- appendMessage('error', `Directory not found: ${newPath}`);
568
- }
569
- } catch (err) {
570
- appendMessage('error', `Error: ${err.message}`);
571
- }
572
- break;
573
-
574
- case '/model':
575
- case '/models':
576
- const config = readConfig();
577
- if (args.length === 0) {
578
- appendMessage('system', [
579
- `Current Provider: ${config.aiProvider}`,
580
- `Current Gemini Model: ${config.geminiModel}`,
581
- 'Available Providers/Presets:',
582
- ' - gemini-2.5-flash (Default Gemini)',
583
- ' - ollama (Local provider)',
584
- ' - anthropic (Claude)',
585
- ' - openai (GPT)',
586
- ' - huggingface (Inference API)',
587
- ' - local (LM Studio / OpenAI Compatible)',
588
- 'Usage: /models <name> to switch'
589
- ].join('\n'));
590
- } else {
591
- const { writeConfig } = require('./src/System/config_manager');
592
- const newModel = args[0];
593
- let newProvider = 'gemini';
594
-
595
- if (newModel === 'ollama') {
596
- newProvider = 'ollama';
597
- } else if (newModel === 'anthropic') {
598
- newProvider = 'anthropic';
599
- } else if (newModel === 'openai') {
600
- newProvider = 'openai';
601
- } else if (newModel === 'huggingface') {
602
- newProvider = 'huggingface';
603
- } else if (newModel === 'local' || newModel === 'local_openai') {
604
- newProvider = 'local_openai';
605
- } else if (newModel.startsWith('gpt-')) {
606
- newProvider = 'openai';
607
- config.openaiModel = newModel;
608
- } else if (newModel.startsWith('claude-')) {
609
- newProvider = 'anthropic';
610
- config.anthropicModel = newModel;
611
- } else {
612
- newProvider = 'gemini';
613
- config.geminiModel = newModel;
614
- }
615
-
616
- config.aiProvider = newProvider;
617
- writeConfig(config);
618
- appendMessage('system', `✅ Switched to: ${newProvider} ${newProvider === 'gemini' ? `(${newModel})` : ''}`);
619
- if (updateStatusModel) updateStatusModel(newProvider === 'gemini' ? newModel : newProvider);
620
- }
621
- break;
622
-
623
- case '/code':
624
- if (args.length === 0) {
625
- appendMessage('system', 'Usage: /code <task>');
626
- break;
627
- }
628
- await runChatRoutedTask(`/code ${args.join(' ')}`, {
629
- appendMessage,
630
- setThinking,
631
- requestApproval,
632
- appendCodeStep,
633
- setMode,
634
- askUser: () => Promise.resolve(''),
635
- history: await getChatTranscript()
636
- });
637
- break;
638
-
639
- case '/config':
640
- const currentCfg = readConfig();
641
- appendMessage('system', [
642
- 'Current Configuration:',
643
- ` Version : v${pkg.version}`,
644
- ` Provider : ${currentCfg.aiProvider}`,
645
- ` Model : ${currentCfg.geminiModel}`,
646
- ` Ollama : ${currentCfg.ollamaModel}`,
647
- ` Voice : ${currentCfg.enableVoiceReply ? 'ON' : 'OFF'}`,
648
- ` Language : ${currentCfg.language}`,
649
- ` API Key : ${currentCfg.apiKey ? 'SET (****)' : 'NOT SET'}`
650
- ].join('\n'));
651
- break;
652
-
653
- case '/copy':
654
- if (copyLastResponse && copyLastResponse()) {
655
- appendMessage('system', '✓ Last response copied to clipboard.');
656
- } else {
657
- appendMessage('system', '✖ Nothing to copy, or xclip/xsel not installed.');
658
- }
659
- break;
660
-
661
- case '/clear':
662
- case '/reset':
663
- resetChat();
664
- appendMessage('system', 'Conversation history cleared.');
665
- break;
666
-
667
- case '/exit':
668
- case '/quit':
669
- process.exit(0);
670
- break;
671
-
672
- default:
673
- appendMessage('system', `Unknown command: ${command}. Type /help for options.`);
674
- }
675
- }
676
-
677
- async function requestCodeApproval(request) {
678
- const typeLabel = request.type === 'shell'
679
- ? 'Shell Command'
680
- : request.type === 'patch'
681
- ? 'Patch Edit'
682
- : 'File Write';
683
-
684
- console.log(`\n${colors.yellow}${colors.bright}[Approval Required]${colors.reset} ${typeLabel}`);
685
- if (request.label) {
686
- console.log(`${colors.gray}${request.label}${colors.reset}`);
687
- }
688
- if (request.preview) {
689
- console.log(`${colors.gray}${request.preview}${colors.reset}\n`);
690
- }
691
-
692
- const rl = readline.createInterface({
693
- input: process.stdin,
694
- output: process.stdout
695
- });
696
-
697
- const answer = await new Promise((resolve) => {
698
- rl.question('Approve this action? [y/N]: ', (value) => {
699
- rl.close();
700
- resolve((value || '').trim().toLowerCase());
701
- });
702
- });
703
-
704
- const approved = answer === 'y' || answer === 'yes';
705
- console.log(approved
706
- ? `${colors.mint}[Mint Code] Approved.${colors.reset}\n`
707
- : `${colors.pink}[Mint Code] Denied.${colors.reset}\n`);
708
- return approved;
709
- }