@pheem49/mint 1.2.4 → 1.4.0
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 +55 -23
- package/index.html +16 -0
- package/main.js +36 -83
- package/mint-cli-logic.js +19 -0
- package/mint-cli.js +316 -39
- package/package.json +20 -3
- package/src/AI_Brain/Gemini_API.js +439 -20
- package/src/AI_Brain/agent_orchestrator.js +73 -0
- package/src/AI_Brain/autonomous_brain.js +2 -0
- package/src/AI_Brain/knowledge_base.js +199 -125
- package/src/AI_Brain/memory_store.js +318 -0
- package/src/Automation_Layer/file_operations.js +41 -19
- package/src/CLI/chat_router.js +181 -0
- package/src/CLI/chat_ui.js +321 -110
- package/src/CLI/code_agent.js +556 -0
- package/src/CLI/code_session_memory.js +62 -0
- package/src/CLI/list_features.js +1 -0
- package/src/CLI/onboarding.js +53 -6
- package/src/CLI/workspace_manager.js +81 -0
- package/src/Plugins/mcp_manager.js +95 -0
- package/src/Plugins/plugin_manager.js +2 -2
- package/src/Plugins/spotify.js +168 -40
- package/src/Plugins/system_monitor.js +72 -0
- package/src/System/config_manager.js +61 -8
- package/src/System/granular_automation.js +88 -0
- package/src/System/notifications.js +23 -0
- package/src/UI/settings.html +167 -65
- package/src/UI/settings.js +253 -42
- package/tests/agent_orchestrator.test.js +41 -0
- package/tests/config_manager.test.js +141 -0
- package/tests/memory_store.test.js +185 -0
- package/tests/spotify.test.js +201 -0
- package/tests/system_monitor.test.js +37 -0
- package/tests/workspace_manager.test.js +41 -0
package/src/UI/settings.js
CHANGED
|
@@ -16,6 +16,7 @@ const DEFAULT_CONFIG = {
|
|
|
16
16
|
ollamaModel: 'llama3:latest',
|
|
17
17
|
enableVoiceReply: true,
|
|
18
18
|
enableCustomWorkflows: true,
|
|
19
|
+
enableAgentCollaboration: true,
|
|
19
20
|
ttsProvider: 'google',
|
|
20
21
|
ttsVolume: 1.0,
|
|
21
22
|
ttsSpeed: 1.0,
|
|
@@ -23,7 +24,8 @@ const DEFAULT_CONFIG = {
|
|
|
23
24
|
pluginSpotifyEnabled: true,
|
|
24
25
|
pluginCalendarEnabled: false,
|
|
25
26
|
pluginDiscordEnabled: false,
|
|
26
|
-
showDesktopWidget: true
|
|
27
|
+
showDesktopWidget: true,
|
|
28
|
+
mcpServers: {}
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
let currentConfig = { ...DEFAULT_CONFIG };
|
|
@@ -56,6 +58,18 @@ function applyConfig(config) {
|
|
|
56
58
|
// Apply API key
|
|
57
59
|
document.getElementById('api-key-input').value = config.apiKey || '';
|
|
58
60
|
|
|
61
|
+
const anthropicInput = document.getElementById('anthropic-api-key-input');
|
|
62
|
+
if (anthropicInput) anthropicInput.value = config.anthropicApiKey || '';
|
|
63
|
+
|
|
64
|
+
const openaiInput = document.getElementById('openai-api-key-input');
|
|
65
|
+
if (openaiInput) openaiInput.value = config.openaiApiKey || '';
|
|
66
|
+
|
|
67
|
+
const hfInput = document.getElementById('hf-api-key');
|
|
68
|
+
if (hfInput) hfInput.value = config.hfApiKey || '';
|
|
69
|
+
|
|
70
|
+
const ollamaHostInput = document.getElementById('ollama-host-input');
|
|
71
|
+
if (ollamaHostInput) ollamaHostInput.value = config.ollamaHost || 'http://localhost:11434';
|
|
72
|
+
|
|
59
73
|
// Apply Gemini model
|
|
60
74
|
applyModelSelection(config.geminiModel);
|
|
61
75
|
|
|
@@ -71,6 +85,24 @@ function applyConfig(config) {
|
|
|
71
85
|
ollamaInput.value = config.ollamaModel || 'llama3:latest';
|
|
72
86
|
}
|
|
73
87
|
|
|
88
|
+
applyModelSelectionGeneric('openai-model-select', 'openai-model-custom-row', 'openai-model-custom', config.openaiModel || 'gpt-4o');
|
|
89
|
+
applyModelSelectionGeneric('anthropic-model-select', 'anthropic-model-custom-row', 'anthropic-model-custom', config.anthropicModel || 'claude-3-5-sonnet-latest');
|
|
90
|
+
|
|
91
|
+
const hfModelInput = document.getElementById('hf-model-name');
|
|
92
|
+
if (hfModelInput) {
|
|
93
|
+
hfModelInput.value = config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const localApiBaseUrlInput = document.getElementById('local-api-base-url');
|
|
97
|
+
if (localApiBaseUrlInput) {
|
|
98
|
+
localApiBaseUrlInput.value = config.localApiBaseUrl || 'http://localhost:1234/v1';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const localModelNameInput = document.getElementById('local-model-name');
|
|
102
|
+
if (localModelNameInput) {
|
|
103
|
+
localModelNameInput.value = config.localModelName || 'local-model';
|
|
104
|
+
}
|
|
105
|
+
|
|
74
106
|
const voiceReplyToggle = document.getElementById('enable-voice-reply');
|
|
75
107
|
if (voiceReplyToggle) {
|
|
76
108
|
voiceReplyToggle.checked = config.enableVoiceReply !== false;
|
|
@@ -102,6 +134,11 @@ function applyConfig(config) {
|
|
|
102
134
|
enableWorkflowsToggle.checked = config.enableCustomWorkflows !== false;
|
|
103
135
|
}
|
|
104
136
|
|
|
137
|
+
const enableAgentCollaborationToggle = document.getElementById('enable-agent-collaboration');
|
|
138
|
+
if (enableAgentCollaborationToggle) {
|
|
139
|
+
enableAgentCollaborationToggle.checked = config.enableAgentCollaboration !== false;
|
|
140
|
+
}
|
|
141
|
+
|
|
105
142
|
// Plugins logic
|
|
106
143
|
updatePluginButton('spotify', config.pluginSpotifyEnabled);
|
|
107
144
|
updatePluginButton('calendar', config.pluginCalendarEnabled);
|
|
@@ -153,6 +190,9 @@ function applyConfig(config) {
|
|
|
153
190
|
document.getElementById('proactive-cooldown').value = cooldown;
|
|
154
191
|
updateIntervalDisplay(interval);
|
|
155
192
|
updateCooldownDisplay(cooldown);
|
|
193
|
+
|
|
194
|
+
// MCP Servers
|
|
195
|
+
renderMcpServers();
|
|
156
196
|
}
|
|
157
197
|
|
|
158
198
|
function lightenColor(hex, amount) {
|
|
@@ -164,9 +204,15 @@ function lightenColor(hex, amount) {
|
|
|
164
204
|
}
|
|
165
205
|
|
|
166
206
|
function applyModelSelection(model) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
207
|
+
applyModelSelectionGeneric('gemini-model-select', 'gemini-model-custom-row', 'gemini-model-custom', model);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function applyModelSelectionGeneric(selectId, customRowId, customInputId, model) {
|
|
211
|
+
const select = document.getElementById(selectId);
|
|
212
|
+
const customRow = document.getElementById(customRowId);
|
|
213
|
+
const customInput = document.getElementById(customInputId);
|
|
214
|
+
if (!select || !customRow || !customInput) return;
|
|
215
|
+
|
|
170
216
|
const normalized = (model || '').trim();
|
|
171
217
|
const optionValues = Array.from(select.options).map(opt => opt.value);
|
|
172
218
|
|
|
@@ -182,11 +228,16 @@ function applyModelSelection(model) {
|
|
|
182
228
|
}
|
|
183
229
|
|
|
184
230
|
function getSelectedModel() {
|
|
185
|
-
|
|
186
|
-
|
|
231
|
+
return getSelectedModelGeneric('gemini-model-select', 'gemini-model-custom', DEFAULT_CONFIG.geminiModel);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function getSelectedModelGeneric(selectId, customInputId, defaultModel) {
|
|
235
|
+
const select = document.getElementById(selectId);
|
|
236
|
+
const customInput = document.getElementById(customInputId);
|
|
237
|
+
if (!select || !customInput) return defaultModel;
|
|
187
238
|
if (select.value === 'custom') {
|
|
188
239
|
const custom = (customInput.value || '').trim();
|
|
189
|
-
return custom ||
|
|
240
|
+
return custom || defaultModel;
|
|
190
241
|
}
|
|
191
242
|
return select.value;
|
|
192
243
|
}
|
|
@@ -194,21 +245,28 @@ function getSelectedModel() {
|
|
|
194
245
|
// --- Event Listeners ---
|
|
195
246
|
|
|
196
247
|
// Close button
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
248
|
+
// Close button
|
|
249
|
+
const closeBtn = document.getElementById('close-btn');
|
|
250
|
+
if (closeBtn) {
|
|
251
|
+
closeBtn.addEventListener('click', () => {
|
|
252
|
+
window.settingsApi.closeSettings();
|
|
253
|
+
});
|
|
254
|
+
}
|
|
200
255
|
|
|
201
256
|
// Toggle API key visibility
|
|
202
|
-
document.getElementById('toggle-key')
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
257
|
+
const toggleKey = document.getElementById('toggle-key');
|
|
258
|
+
if (toggleKey) {
|
|
259
|
+
toggleKey.addEventListener('click', () => {
|
|
260
|
+
const input = document.getElementById('api-key-input');
|
|
261
|
+
input.type = input.type === 'password' ? 'text' : 'password';
|
|
262
|
+
});
|
|
263
|
+
}
|
|
206
264
|
|
|
207
265
|
async function saveApiKeyOnly() {
|
|
208
266
|
const input = document.getElementById('api-key-input');
|
|
209
267
|
const status = document.getElementById('api-save-status');
|
|
210
268
|
const btn = document.getElementById('save-api-key');
|
|
211
|
-
const apiKey = input.value.trim();
|
|
269
|
+
const apiKey = input ? input.value.trim() : '';
|
|
212
270
|
|
|
213
271
|
try {
|
|
214
272
|
const baseConfig = await window.settingsApi.getSettings();
|
|
@@ -216,26 +274,31 @@ async function saveApiKeyOnly() {
|
|
|
216
274
|
await window.settingsApi.saveSettings(nextConfig);
|
|
217
275
|
currentConfig.apiKey = apiKey;
|
|
218
276
|
|
|
219
|
-
btn.textContent = 'Saved!';
|
|
220
|
-
status.textContent = 'API key saved';
|
|
277
|
+
if (btn) btn.textContent = 'Saved!';
|
|
278
|
+
if (status) status.textContent = 'API key saved';
|
|
221
279
|
setTimeout(() => {
|
|
222
|
-
btn.textContent = 'Save API Key';
|
|
223
|
-
status.textContent = '';
|
|
280
|
+
if (btn) btn.textContent = 'Save API Key';
|
|
281
|
+
if (status) status.textContent = '';
|
|
224
282
|
}, 1500);
|
|
225
283
|
} catch (err) {
|
|
226
284
|
console.error('Failed to save API key:', err);
|
|
227
|
-
status.textContent = 'Save failed';
|
|
228
|
-
setTimeout(() => { status.textContent = ''; }, 1500);
|
|
285
|
+
if (status) status.textContent = 'Save failed';
|
|
286
|
+
setTimeout(() => { if (status) status.textContent = ''; }, 1500);
|
|
229
287
|
}
|
|
230
288
|
}
|
|
231
289
|
|
|
232
|
-
document.getElementById('save-api-key')
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
290
|
+
const saveApiKey = document.getElementById('save-api-key');
|
|
291
|
+
if (saveApiKey) saveApiKey.addEventListener('click', saveApiKeyOnly);
|
|
292
|
+
|
|
293
|
+
const apiKeyInput = document.getElementById('api-key-input');
|
|
294
|
+
if (apiKeyInput) {
|
|
295
|
+
apiKeyInput.addEventListener('keydown', (e) => {
|
|
296
|
+
if (e.key === 'Enter') {
|
|
297
|
+
e.preventDefault();
|
|
298
|
+
saveApiKeyOnly();
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
239
302
|
|
|
240
303
|
// Gemini model select
|
|
241
304
|
document.getElementById('gemini-model-select').addEventListener('change', (e) => {
|
|
@@ -253,23 +316,45 @@ document.getElementById('gemini-model-custom').addEventListener('input', (e) =>
|
|
|
253
316
|
currentConfig.geminiModel = e.target.value.trim();
|
|
254
317
|
});
|
|
255
318
|
|
|
256
|
-
//
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
319
|
+
// OpenAI model select
|
|
320
|
+
document.getElementById('openai-model-select').addEventListener('change', (e) => {
|
|
321
|
+
const customRow = document.getElementById('openai-model-custom-row');
|
|
322
|
+
if (e.target.value === 'custom') {
|
|
323
|
+
customRow.style.display = 'block';
|
|
324
|
+
currentConfig.openaiModel = (document.getElementById('openai-model-custom').value || '').trim();
|
|
325
|
+
} else {
|
|
326
|
+
customRow.style.display = 'none';
|
|
327
|
+
currentConfig.openaiModel = e.target.value;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
document.getElementById('openai-model-custom').addEventListener('input', (e) => {
|
|
332
|
+
currentConfig.openaiModel = e.target.value.trim();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Anthropic model select
|
|
336
|
+
document.getElementById('anthropic-model-select').addEventListener('change', (e) => {
|
|
337
|
+
const customRow = document.getElementById('anthropic-model-custom-row');
|
|
338
|
+
if (e.target.value === 'custom') {
|
|
339
|
+
customRow.style.display = 'block';
|
|
340
|
+
currentConfig.anthropicModel = (document.getElementById('anthropic-model-custom').value || '').trim();
|
|
264
341
|
} else {
|
|
265
|
-
|
|
266
|
-
|
|
342
|
+
customRow.style.display = 'none';
|
|
343
|
+
currentConfig.anthropicModel = e.target.value;
|
|
267
344
|
}
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
document.getElementById('anthropic-model-custom').addEventListener('input', (e) => {
|
|
348
|
+
currentConfig.anthropicModel = e.target.value.trim();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// AI Provider toggle (No-op since all sections stay visible)
|
|
352
|
+
function toggleProviderOptions(provider) {
|
|
353
|
+
// No-op
|
|
268
354
|
}
|
|
269
355
|
|
|
270
356
|
document.getElementById('ai-provider-select').addEventListener('change', (e) => {
|
|
271
357
|
currentConfig.aiProvider = e.target.value;
|
|
272
|
-
toggleProviderOptions(e.target.value);
|
|
273
358
|
});
|
|
274
359
|
|
|
275
360
|
document.getElementById('ollama-model-input').addEventListener('input', (e) => {
|
|
@@ -277,9 +362,12 @@ document.getElementById('ollama-model-input').addEventListener('input', (e) => {
|
|
|
277
362
|
});
|
|
278
363
|
|
|
279
364
|
// AI Studio link
|
|
280
|
-
document.getElementById('ai-studio-link')
|
|
281
|
-
|
|
282
|
-
|
|
365
|
+
const aiStudioLink = document.getElementById('ai-studio-link');
|
|
366
|
+
if (aiStudioLink) {
|
|
367
|
+
aiStudioLink.addEventListener('click', () => {
|
|
368
|
+
window.settingsApi.openExternal('https://aistudio.google.com/');
|
|
369
|
+
});
|
|
370
|
+
}
|
|
283
371
|
|
|
284
372
|
// Theme cards
|
|
285
373
|
document.querySelectorAll('.theme-card').forEach(card => {
|
|
@@ -396,9 +484,34 @@ if (document.getElementById('tts-pitch')) {
|
|
|
396
484
|
// Save
|
|
397
485
|
document.getElementById('save-btn').addEventListener('click', async () => {
|
|
398
486
|
currentConfig.apiKey = document.getElementById('api-key-input').value.trim();
|
|
487
|
+
|
|
488
|
+
const anthropicInput = document.getElementById('anthropic-api-key-input');
|
|
489
|
+
if (anthropicInput) currentConfig.anthropicApiKey = anthropicInput.value.trim();
|
|
490
|
+
|
|
491
|
+
const openaiInput = document.getElementById('openai-api-key-input');
|
|
492
|
+
if (openaiInput) currentConfig.openaiApiKey = openaiInput.value.trim();
|
|
493
|
+
|
|
494
|
+
const hfInput = document.getElementById('hf-api-key');
|
|
495
|
+
if (hfInput) currentConfig.hfApiKey = hfInput.value.trim();
|
|
496
|
+
|
|
497
|
+
const ollamaHostInput = document.getElementById('ollama-host-input');
|
|
498
|
+
if (ollamaHostInput) currentConfig.ollamaHost = ollamaHostInput.value.trim();
|
|
499
|
+
|
|
399
500
|
currentConfig.geminiModel = getSelectedModel();
|
|
400
501
|
currentConfig.aiProvider = document.getElementById('ai-provider-select').value;
|
|
401
502
|
currentConfig.ollamaModel = document.getElementById('ollama-model-input').value.trim();
|
|
503
|
+
|
|
504
|
+
currentConfig.openaiModel = getSelectedModelGeneric('openai-model-select', 'openai-model-custom', DEFAULT_CONFIG.openaiModel || 'gpt-4o');
|
|
505
|
+
currentConfig.anthropicModel = getSelectedModelGeneric('anthropic-model-select', 'anthropic-model-custom', DEFAULT_CONFIG.anthropicModel || 'claude-3-5-sonnet-latest');
|
|
506
|
+
|
|
507
|
+
const hfModelInput = document.getElementById('hf-model-name');
|
|
508
|
+
if (hfModelInput) currentConfig.hfModel = hfModelInput.value.trim();
|
|
509
|
+
|
|
510
|
+
const localApiBaseUrlInput = document.getElementById('local-api-base-url');
|
|
511
|
+
if (localApiBaseUrlInput) currentConfig.localApiBaseUrl = localApiBaseUrlInput.value.trim();
|
|
512
|
+
|
|
513
|
+
const localModelNameInput = document.getElementById('local-model-name');
|
|
514
|
+
if (localModelNameInput) currentConfig.localModelName = localModelNameInput.value.trim();
|
|
402
515
|
|
|
403
516
|
const voiceReplyToggle = document.getElementById('enable-voice-reply');
|
|
404
517
|
if (voiceReplyToggle) {
|
|
@@ -417,6 +530,11 @@ document.getElementById('save-btn').addEventListener('click', async () => {
|
|
|
417
530
|
currentConfig.enableCustomWorkflows = enableWorkflowsToggle.checked;
|
|
418
531
|
}
|
|
419
532
|
|
|
533
|
+
const enableAgentCollaborationToggle = document.getElementById('enable-agent-collaboration');
|
|
534
|
+
if (enableAgentCollaborationToggle) {
|
|
535
|
+
currentConfig.enableAgentCollaboration = enableAgentCollaborationToggle.checked;
|
|
536
|
+
}
|
|
537
|
+
|
|
420
538
|
const showWidgetToggle = document.getElementById('show-desktop-widget');
|
|
421
539
|
if (showWidgetToggle) {
|
|
422
540
|
currentConfig.showDesktopWidget = showWidgetToggle.checked;
|
|
@@ -433,12 +551,100 @@ document.getElementById('save-btn').addEventListener('click', async () => {
|
|
|
433
551
|
currentConfig.customBgEnd = document.getElementById('custom-bg-end').value;
|
|
434
552
|
currentConfig.customPanelBg = document.getElementById('custom-panel-bg').value;
|
|
435
553
|
|
|
554
|
+
// Ensure mcpServers is part of the saved config
|
|
555
|
+
if (!currentConfig.mcpServers) currentConfig.mcpServers = {};
|
|
556
|
+
|
|
557
|
+
console.log('[Settings] Saving config with MCP servers:', Object.keys(currentConfig.mcpServers).length);
|
|
436
558
|
await window.settingsApi.saveSettings(currentConfig);
|
|
437
559
|
const btn = document.getElementById('save-btn');
|
|
438
560
|
btn.textContent = '✅ Saved!';
|
|
439
561
|
setTimeout(() => { btn.textContent = 'Save Settings'; }, 1500);
|
|
440
562
|
});
|
|
441
563
|
|
|
564
|
+
// --- MCP Management Functions ---
|
|
565
|
+
function renderMcpServers() {
|
|
566
|
+
console.log('[Settings] Rendering MCP Servers UI...');
|
|
567
|
+
const list = document.getElementById('mcp-server-list');
|
|
568
|
+
if (!list) {
|
|
569
|
+
console.warn('[Settings] MCP list element not found in DOM.');
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
list.innerHTML = '';
|
|
573
|
+
|
|
574
|
+
const servers = currentConfig.mcpServers || {};
|
|
575
|
+
const entries = Object.entries(servers);
|
|
576
|
+
console.log(`[Settings] Found ${entries.length} servers in currentConfig.`);
|
|
577
|
+
|
|
578
|
+
if (entries.length === 0) {
|
|
579
|
+
list.innerHTML = '<p class="hint" style="text-align: center; padding: 10px;">No MCP servers connected.</p>';
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
for (const [name, cfg] of entries) {
|
|
584
|
+
const item = document.createElement('div');
|
|
585
|
+
item.style.cssText = 'display: flex; justify-content: space-between; align-items: center; padding: 12px; background: rgba(0,0,0,0.2); border-radius: 10px; border: 1px solid var(--border);';
|
|
586
|
+
|
|
587
|
+
item.innerHTML = `
|
|
588
|
+
<div style="display: flex; flex-direction: column; gap: 4px;">
|
|
589
|
+
<div style="font-weight: 600; color: var(--accent); display: flex; align-items: center; gap: 6px;">
|
|
590
|
+
<span>🌐 ${name}</span>
|
|
591
|
+
</div>
|
|
592
|
+
<div style="font-size: 0.75rem; opacity: 0.7; font-family: monospace;">${cfg.command} ${cfg.args.join(' ')}</div>
|
|
593
|
+
</div>
|
|
594
|
+
<button class="btn-danger" style="padding: 6px 12px; font-size: 0.8rem;" onclick="removeMcpServer('${name}')">Remove</button>
|
|
595
|
+
`;
|
|
596
|
+
list.appendChild(item);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
window.removeMcpServer = function(name) {
|
|
601
|
+
if (confirm(`Remove MCP server "${name}"?`)) {
|
|
602
|
+
delete currentConfig.mcpServers[name];
|
|
603
|
+
renderMcpServers();
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
document.getElementById('btn-add-mcp').addEventListener('click', () => {
|
|
608
|
+
const nameInput = document.getElementById('mcp-new-name');
|
|
609
|
+
const cmdInput = document.getElementById('mcp-new-command');
|
|
610
|
+
const argsInput = document.getElementById('mcp-new-args');
|
|
611
|
+
const envInput = document.getElementById('mcp-new-env');
|
|
612
|
+
|
|
613
|
+
const name = nameInput.value.trim();
|
|
614
|
+
const command = cmdInput.value.trim();
|
|
615
|
+
const argsStr = argsInput.value.trim();
|
|
616
|
+
const envStr = envInput.value.trim();
|
|
617
|
+
|
|
618
|
+
if (!name || !command) {
|
|
619
|
+
alert('Name and Command are required!');
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Basic args split (by space, but respecting some quotes if possible - simple for now)
|
|
624
|
+
const args = argsStr ? argsStr.split(/\s+/) : [];
|
|
625
|
+
|
|
626
|
+
let env = {};
|
|
627
|
+
if (envStr) {
|
|
628
|
+
try {
|
|
629
|
+
env = JSON.parse(envStr);
|
|
630
|
+
} catch (e) {
|
|
631
|
+
alert('Invalid JSON in Environment Variables field!');
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (!currentConfig.mcpServers) currentConfig.mcpServers = {};
|
|
637
|
+
currentConfig.mcpServers[name] = { command, args, env };
|
|
638
|
+
|
|
639
|
+
// Clear inputs
|
|
640
|
+
nameInput.value = '';
|
|
641
|
+
cmdInput.value = '';
|
|
642
|
+
argsInput.value = '';
|
|
643
|
+
envInput.value = '';
|
|
644
|
+
|
|
645
|
+
renderMcpServers();
|
|
646
|
+
});
|
|
647
|
+
|
|
442
648
|
// Custom Workflows functionality
|
|
443
649
|
const openWorkflowsBtn = document.getElementById('open-workflows-btn');
|
|
444
650
|
const reloadWorkflowsBtn = document.getElementById('reload-workflows-btn');
|
|
@@ -508,6 +714,11 @@ document.querySelectorAll('.tab-btn').forEach(btn => {
|
|
|
508
714
|
btn.classList.add('active');
|
|
509
715
|
const pane = document.getElementById(target);
|
|
510
716
|
if (pane) pane.classList.add('active');
|
|
717
|
+
|
|
718
|
+
// Re-render MCP list if switching to plugins tab
|
|
719
|
+
if (target === 'sect-plugins') {
|
|
720
|
+
renderMcpServers();
|
|
721
|
+
}
|
|
511
722
|
});
|
|
512
723
|
});
|
|
513
724
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests: agent_orchestrator.js
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const orchestrator = require('../src/AI_Brain/agent_orchestrator');
|
|
6
|
+
|
|
7
|
+
describe('Agent Orchestrator', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
orchestrator.resetAgent();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('starts with general agent', () => {
|
|
13
|
+
const agent = orchestrator.getCurrentAgent();
|
|
14
|
+
expect(agent.name).toBe('Mint Default');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('can switch to coder agent', () => {
|
|
18
|
+
orchestrator.setAgent('coder');
|
|
19
|
+
const agent = orchestrator.getCurrentAgent();
|
|
20
|
+
expect(agent.name).toBe('Mint Coder');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('can switch to researcher agent', () => {
|
|
24
|
+
orchestrator.setAgent('researcher');
|
|
25
|
+
const agent = orchestrator.getCurrentAgent();
|
|
26
|
+
expect(agent.name).toBe('Mint Researcher');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('falls back to general for invalid agent', () => {
|
|
30
|
+
orchestrator.setAgent('nonexistent');
|
|
31
|
+
const agent = orchestrator.getCurrentAgent();
|
|
32
|
+
expect(agent.name).toBe('Mint Default');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('lists available agents', () => {
|
|
36
|
+
const agents = orchestrator.listAgents();
|
|
37
|
+
expect(agents).toContain('general');
|
|
38
|
+
expect(agents).toContain('coder');
|
|
39
|
+
expect(agents).toContain('researcher');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests: config_manager.js
|
|
3
|
+
* Tests readConfig, writeConfig, and getAvailableProviders.
|
|
4
|
+
*
|
|
5
|
+
* Isolation strategy: spy on os.homedir() BEFORE requiring config_manager
|
|
6
|
+
* so CONFIG_DIR and CONFIG_PATH point to a temp directory, never touching
|
|
7
|
+
* the real ~/.config/mint/mint-config.json.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
let tempDir;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
// 1. Reset module registry so config_manager re-initialises fresh each test
|
|
18
|
+
jest.resetModules();
|
|
19
|
+
|
|
20
|
+
// 2. Create an isolated temp dir for this test
|
|
21
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mint-cfg-test-'));
|
|
22
|
+
|
|
23
|
+
// 3. Mock os.homedir() BEFORE requiring config_manager.
|
|
24
|
+
// config_manager computes CONFIG_DIR = os.homedir() + '/.config/mint'
|
|
25
|
+
// at load time, so the spy must be active when the module first loads.
|
|
26
|
+
jest.spyOn(os, 'homedir').mockReturnValue(tempDir);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
jest.restoreAllMocks();
|
|
31
|
+
jest.resetModules();
|
|
32
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Helper — always gets a fresh, isolated instance of config_manager
|
|
36
|
+
function getModule() {
|
|
37
|
+
return require('../src/System/config_manager');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── readConfig ─────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
describe('config_manager — readConfig', () => {
|
|
43
|
+
test('returns DEFAULT_CONFIG when no config file exists', () => {
|
|
44
|
+
const { readConfig } = getModule();
|
|
45
|
+
const config = readConfig();
|
|
46
|
+
expect(config).toHaveProperty('geminiModel', 'gemini-2.5-flash');
|
|
47
|
+
expect(config).toHaveProperty('aiProvider', 'gemini');
|
|
48
|
+
expect(config).toHaveProperty('language', 'th-TH');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('merges saved values with defaults (saved value wins)', () => {
|
|
52
|
+
const { readConfig, writeConfig } = getModule();
|
|
53
|
+
writeConfig({ geminiModel: 'gemini-2.0-pro' });
|
|
54
|
+
const config = readConfig();
|
|
55
|
+
expect(config.geminiModel).toBe('gemini-2.0-pro');
|
|
56
|
+
// Default fields still present
|
|
57
|
+
expect(config.aiProvider).toBe('gemini');
|
|
58
|
+
expect(config.language).toBe('th-TH');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('missing keys in saved file are filled by defaults', () => {
|
|
62
|
+
const { readConfig, writeConfig } = getModule();
|
|
63
|
+
// Write a partial config (no 'ollamaModel' key)
|
|
64
|
+
writeConfig({ geminiModel: 'my-model' });
|
|
65
|
+
const config = readConfig();
|
|
66
|
+
expect(config.ollamaModel).toBe('llama3:latest'); // default
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// ── writeConfig ────────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
describe('config_manager — writeConfig', () => {
|
|
73
|
+
test('returns { success: true } on valid write', () => {
|
|
74
|
+
const { writeConfig } = getModule();
|
|
75
|
+
const result = writeConfig({ testKey: 'testValue' });
|
|
76
|
+
expect(result).toEqual({ success: true });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('written JSON is readable back correctly', () => {
|
|
80
|
+
const { readConfig, writeConfig } = getModule();
|
|
81
|
+
writeConfig({ geminiModel: 'custom-model-test' });
|
|
82
|
+
const config = readConfig();
|
|
83
|
+
expect(config.geminiModel).toBe('custom-model-test');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('config file is actually created on disk', () => {
|
|
87
|
+
const { writeConfig, CONFIG_PATH } = getModule();
|
|
88
|
+
writeConfig({ geminiModel: 'test' });
|
|
89
|
+
expect(fs.existsSync(CONFIG_PATH)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// ── getAvailableProviders ──────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
describe('config_manager — getAvailableProviders', () => {
|
|
96
|
+
test('always includes ollama (local, no key needed)', () => {
|
|
97
|
+
const { getAvailableProviders } = getModule();
|
|
98
|
+
expect(getAvailableProviders({})).toContain('ollama');
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('includes gemini when apiKey is set', () => {
|
|
102
|
+
const { getAvailableProviders } = getModule();
|
|
103
|
+
expect(getAvailableProviders({ apiKey: 'test-key' })).toContain('gemini');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('includes anthropic when anthropicApiKey is set', () => {
|
|
107
|
+
const { getAvailableProviders } = getModule();
|
|
108
|
+
expect(getAvailableProviders({ anthropicApiKey: 'ant-key' })).toContain('anthropic');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('includes openai when openaiApiKey is set', () => {
|
|
112
|
+
const { getAvailableProviders } = getModule();
|
|
113
|
+
expect(getAvailableProviders({ openaiApiKey: 'oai-key' })).toContain('openai');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('includes huggingface when hfApiKey is set', () => {
|
|
117
|
+
const { getAvailableProviders } = getModule();
|
|
118
|
+
expect(getAvailableProviders({ hfApiKey: 'hf-key' })).toContain('huggingface');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('includes local_openai when localApiBaseUrl is set', () => {
|
|
122
|
+
const { getAvailableProviders } = getModule();
|
|
123
|
+
expect(
|
|
124
|
+
getAvailableProviders({ localApiBaseUrl: 'http://localhost:1234/v1' })
|
|
125
|
+
).toContain('local_openai');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('does NOT include gemini when no key', () => {
|
|
129
|
+
const { getAvailableProviders } = getModule();
|
|
130
|
+
const savedEnv = process.env.GEMINI_API_KEY;
|
|
131
|
+
delete process.env.GEMINI_API_KEY;
|
|
132
|
+
const providers = getAvailableProviders({ apiKey: '' });
|
|
133
|
+
expect(providers).not.toContain('gemini');
|
|
134
|
+
if (savedEnv) process.env.GEMINI_API_KEY = savedEnv;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('returns array type', () => {
|
|
138
|
+
const { getAvailableProviders } = getModule();
|
|
139
|
+
expect(Array.isArray(getAvailableProviders({}))).toBe(true);
|
|
140
|
+
});
|
|
141
|
+
});
|